A snake game on two 8x8 LED matrices
Advanced
Or see every lesson.
- 455parts on the bench
- 43boards running now
- 1.00xreal time, on every board
Snake is the game from old phones: a line of dots crawls around the screen, eats an apple, gets longer, and dies when it hits a wall or itself. It is a step up from Simon, because it needs a moving picture, a direction, a body that grows, and input read between frames.
The circuit above plays it on an Arduino Uno with two MAX7219 8x8 LED matrix modules, one above the other, for a field eight dots wide and sixteen tall. A thumb stick steers. Press Run and the snake waits, three dots long with an apple somewhere on the field. Push the stick, or press it, and the snake sets off. Leave it alone and it crawls straight into the bottom wall in about three and a half seconds; the serial monitor says "bitten". Press the stick to play again.
What you need
- An Arduino Uno
- Two MAX7219 8x8 LED matrix modules
- A thumb stick module (two pots and a push switch)
- A breadboard and jumper wires
Wiring
| Part and pin | Goes to | Note |
|---|---|---|
| Both matrices DIN | Pin 11 | Shared data |
| Both matrices CLK | Pin 13 | Shared clock |
| Top matrix CS | Pin 10 | Each module has its own chip select |
| Bottom matrix CS | Pin 9 | |
| Matrices VCC and GND | 5 V and GND | The MAX7219 wants 4 V or more |
| Stick VRX | A0 | Left and right |
| Stick VRY | A1 | Up and down |
| Stick SW | Pin 2 | Press to start or restart |
| Stick VCC and GND | 5 V and GND |
The display
A MAX7219 holds eight bytes, one per row of the matrix, and scans them out by itself, so the Uno only writes a byte when something changes. The two modules share data and clock, and each has its own chip select. Whatever the Uno sends while a CS pin is low goes to that module only.
That costs one pin more than chaining the modules (DOUT of one into DIN of the next, which the stacker game does), and it pays back here: a snake step changes two dots, the new head and the old tail, usually in the same half. With a select each, a step rewrites one module and leaves the other alone.
The example uses mokxi_max7219.h, a small driver that ships with Mokxi: setPixel(), clear() and show(). On a real board the usual choice is the LedControl library from the Library Manager, which Mokxi also has as LedControl.h, with setLed(module, row, col, state) doing the same job as setPixel().
The body, the apple and the walls
The snake is a ring buffer: an array of 128 cell numbers, one per dot on the field, with an index for the head. A cell number is row times 8 plus column. Each step works out the next cell from the heading, writes it at the head, and lights it. If the snake did not just eat, it also turns off the tail cell, so the snake moves without being redrawn. Two dots change per step, and the other 126 are left as they are.
Eating is a comparison: if the new head cell is the apple cell, the length goes up by one, the step time drops by 8 ms (from 240 ms, until it is under 90), and a new apple is placed on a free cell. Dying is two checks: the new cell is off the field, or it is already part of the body. The walls are walls here, not a wrap around, so you can lose.
The apple’s position comes from a tiny random number generator (a 16-bit LFSR). Its state is mixed with millis() when the player restarts, so each new game is different.
Steering with a thumb stick
A thumb stick reports a position, two numbers from 0 to 1023, but a snake needs a direction. So the sketch reads the stick as a push: whichever axis is further from center wins, and only if it is more than 160 counts past the dead zone. Let go and the stick springs back, and the snake carries on.
A turn straight back into the snake’s own neck is refused. Without that rule, a quick flick from down to up would end the game on the spot.
The stick is read every few milliseconds between steps, not just once per step. A flick that starts and ends inside one 240 ms step would otherwise be missed.
static void steer() {
int16_t x = stick.dx();
int16_t y = stick.dy();
int8_t wantX = 0, wantY = 0;
int16_t ax = x < 0 ? -x : x;
int16_t ay = y < 0 ? -y : y;
if (ax > ay) {
if (ax > THROW) wantX = x > 0 ? 1 : -1;
} else if (ay > THROW) {
wantY = y > 0 ? 1 : -1;
}
if (wantX == 0 && wantY == 0) return;
waiting = false; // the first push starts the game
if (wantX != 0 && dx != 0) return; // no reversing into the neck
if (wantY != 0 && dy != 0) return;
dx = wantX;
dy = wantY;
}Common mistakes
Half the field stays dark. Usually that module’s CS wire is loose or on the wrong pin, or its VCC is on 3.3 V. The MAX7219 wants 4 V or more.
The snake turns by itself the moment the game starts. A real stick does not rest at exactly 512, so compare against the rest position read at power up, not against 512, and add a dead zone. Reading two analog pins back to back on an Uno has a second trap: the first reading after switching channels can carry the last channel’s value, so throw it away.
The game is over before you touch it. On a field sixteen rows tall a snake moving every 240 ms reaches the wall in about three seconds. The sketch waits for the first push for that reason.
Using delay() for the step time. A delay of 240 ms means the stick is not read for 240 ms. Use millis() and read the stick in between.
A body array that is too small. Size it for the whole field, 128 cells, so a long snake never writes past the end.
Questions
How do I make a snake game on an 8x8 LED matrix with Arduino?
Drive the matrix with a MAX7219 and LedControl, store the snake as an array of cells, move the head one cell per step, turn off the tail unless the snake just ate, and end the game when the head leaves the grid or lands on the body.
Can I play snake on one 8x8 matrix?
Yes. Change the field to 8 rows and drop the second module. It is a short game on 64 dots, which is why this one uses two modules for 128.
Can I use an OLED or a TFT instead?
Yes. The logic is the same; only the drawing calls change. Mokxi has a TFT version too, on a 240x320 ILI9341, linked below this page.
Can I use buttons instead of a joystick?
Yes. Four buttons on INPUT_PULLUP pins, each setting dx and dy, with the same no-reversing check.
Keep going
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.