The T3 is a minimalistic handheld game console.
Specs:
- Power: 3 AA batteries
- Processor: ESP8266
- Input: 6 buttons (directions, A, B)
- Display: 3×3 truecolor
- Sound: 1-bit PWM piezo
When turned on, the console displays a “T3” welcome animation and enters the game selection menu. Press left/right to select a game, and press A to launch it. Currently these “games” are available:
Simulates a 6-sided die. Press A to re-roll. When ready, the result is shown colorfully.
The game shows a sequnce of directions (left – yellow; up – red; right – green; down – blue); then a single white pixel in the middle. Initially, the sequence just contains one direction.
The player's job is to repeat the sequence. If they're successful, a new direction is added to the sequence, and a new turn begins. If they fail, the sequence is reset, and the game starts over.
First, set the game up: press left/right to choose Human vs. Computer, or Human vs. Human. You can also press up/down to choose a player's color, and B to select if the human or computer goes first. When ready, press A to start.
Players take turns putting their colors on the board. The first on to get 3 in a row – horizontally, vertically, or diagonally – wins. In case of tie, the game is repeated.
A rudimentary simulator is included to aid development and to run games without a physical console. The simulator is not accurate--it runs with the speed and memory of the host computer--but it's useful for validating high-level logic.
To run the simulator, you will need Python 3.5+ and MicroPython, plus the Pyglet and Click libraries. On Fedora, you can install the prerequisites with:
sudo dnf install python3 micropython python3-pyglet python3-click
(Please contribute instructions for other systems. Note: the emulator won't work on Windows without major changes.)
Run the included t3
script to launch the simulator.
Control it with arrow keys, and Z/X for the A/B buttons.
To run a particular game directly, pass its name as a command-line argument.
The simulator doesn't currently emit sound.
Each T3 cartridge starts with a "magic comment" and selection-screen animation, for example:
# T3 Cartridge
# 1/5 0f0000 0f0000 0f0000 000000 0f0000 000000 000000 000000 000000
# 1/5 000f00 000000 000000 000f00 000f00 000000 000f00 000000 000000
# 1/5 000000 000000 000000 000000 00000f 000000 00000f 00000f 00000f
# 1/5 000000 000000 0f0f00 000000 0f0f00 0f0f00 000000 000000 0f0f00
The line # T3 Cartridge
is mandatory.
The subsequent lines give the duration (in seconds, with one optional /
for
entering a fraction), and hex RGB values for all the 9 LEDs.
This sequence is repeated on the game selection screen.
The rest of the cartridge file is a Python module, which must define
a generator iterator called main
, which implements the game.
Whenever the function yield
s a number, it is suspended for that many seconds.
Alternately, it can yield t3.wait_for_input()
to wait for button events.
Display and input updates happen when main
is suspended, so it is necessary
to yield
often.
The yield
expression result is a wait info object that exposes how long
the wait actually took (which will usually be slightly more than requested),
and which buttons were/are pressed:
info.elapsed
: The elapsed time, in secondsstate.pressed[btn]
: true for buttons that were pressed during the waitstate.released[btn]
: true for buttons that were released during the waitstate.held[btn]
: true for buttons that are currently pressed
The button-related attributes should be indexed by button objects such
as t3.left
– see below.
The cartridge module may import the t3
module, and use the following
functionality from it:
The color of the i
-th pixel, as a 3-tuple of ints from 0 to 255.
May be read or set.
The color of the pixel in row x
and column y
, as a 3-tuple of ints
from 0 to 255. May be read or set.
Animates the given LED (which may be given as i
or (x, y)
,
see t3.display[...]
above) from its current color to the given color,
using the given number of steps.
This is a coroutine that should be started using t3.start_task
.
Converts a color given by hue, lightness, and saturation to the RGB format
needed by t3.display
. The arguments should be floats from 0 to 1.
Returns a random float from the interval (a, b]
, using the hardware
randomness generator.
In the emulator, this chooses from a fixed sequence.
Returns a random integer from range(a, b)
, using the hardware
randomness generator.
In the emulator, this chooses from a fixed sequence.
Starts a new task, which should be a generator iterator that yields a number
whenever it pauses execution – same as main
.
When yielded from main
or a coroutine started with t3.start_task
,
waits until the next button press or release.
The buttons, as objects of class t3._Button
.
You should never create new instances of this class.
The value of this Button: False for not pressed; True for pressed.
Starts playing a tone of the given pitch, using a rectangular wave.
The duty
is a number from 0 to 1024 that gives the wave's duty cycle;
this can be used to emit different "timbres".
Call tone(0)
to stop playing a sound.
Here are notes on building the console.
(Questions and pull requests aimed towards making them more obvious would be more than welcome!)
Part | Count |
---|---|
NodeMCU board | 1 |
Mini breadboard with mounting holes | 1 |
Tactile switches 6x6x1mm | 6 |
Piezo sounder, 14mm diameter, 7mm height (excl. leads) | 1 |
Sliding switch, 10x2.5x7mm | 1 |
WS2812B LED modules (1cm circular PCB) | 9 (in 3x3 square) |
Screws, 2x25 (metric) | 6 |
AA Batteries | 3 |
Wires | |
3D printer filament | |
Translucent sheet (parchment paper) | |
Sheet of clear plactic |
- 3D printer
- Soldering equipment
- Screwdriver
- Sharp knife
- Marker
Flash MicroPython onto the NodeMCU board, and upload all files from the
sw
directory to it.
Print the printed parts from the 3d
directory.
They'll fit (barely) on a 200×200mm print bed.
Put the buttons in the printed button pads. On the reverse side, solder one wire to all 4 innermost contacts on the pad with 4 buttons. Solder another wire to the 2 innermost contacts on the pad with 2 buttons. Then, solder one wire each (total 6 wires) to all the outermost contacts.
Solder wires to the leads of the speaker.
Connect spacts of the batteries in the battery holder together, and attach power wires, like this:
-------- + BATTERY - ---.
|
.---------------------'
|
'--- + BATTERY - ---.
|
.---------------------'
|
'--- + BATTERY - ---------------
Solder the WS2812B modules together into a 3x3 array. All +
pads should
be connected together; all -
pads should also be connected, and the
individual modules should be chained together using the DI (Data In) and
DO (Data Out) pads, like this:
.-- 2 <- 1 <- 0
'------------------.
.-- 5 <- 4 <- 3 <-'
'------------------.
8 <- 7 <- 6 <-'
The modules should be tightly packed, and should fit tightly into the
space on the display on the 3D printed base.
Solder wires to the first WS2812B's +
, -
, and DI
pads.
Using two screws, attach the mini breadboard to the 3D printed base.
Snap the speaker into its holder at the top of the console.
Then, attach the button pads to the lower two "columns" in the base.
Connect wires to the breadboard accrording to the diagram below. Always leave the outermost column empty, so you'll be able to insert the NodeMCU later:
--- ooooo ooooo ---
--- (A0) ooooo ooooo (D0) ---
common: 4 buttons (G) ooooo ooooo (D1) DI of WS2812B array
+ of WS2812B array (VU) ooooo ooooo (D2) "left" button
--- (S3) ooooo ooooo (D3) "A" button
--- (S2) ooooo ooooo (D4) "B" button
--- (S1) ooooo ooooo (3V) ---
--- (SC) ooooo ooooo (G) common: 2 buttons
--- (SO) ooooo ooooo (D5) "down" button
--- (SK) ooooo ooooo (D6) "up" button
speaker (G) ooooo ooooo (D7) "right" button
--- (3V) ooooo ooooo (D8) speaker
--- (EN) ooooo ooooo (RX) ---
--- (RS) ooooo ooooo (TX) ---
wire (for switch) (G) ooooo ooooo (G) ---
+ of battery holder (VI) ooooo ooooo (3V) ---
--- ooooo ooooo ---
Solder the "wire (for switch)" to the middle terminal of the sliding switch,
and the wire from the -
terminal to one of the other terminals.
Put the sliding switch in place at the top of the console.
Insert the NodeMCU board into the breadboard, as indicated in the diagram above.
Using a hot glue gun, attach the battery holder to the base.
Cut a piece of the translucent sheet in the shape of the top cover. Cut out holes for the buttons (but not the display), the sliding switch, and two screws. Cut out the same shape with the clear plastic.
You can draw markings for the buttons and switch on the translucent cover.
Snap the top cover into the top part of the console. Layer the translucent paper and clear plastic on top, and secure with two screws.
Insert the batteries into the holder.
Attach the bottom cover – insert it into the hooks first, then close it.
Your console is now complete.