Skip to content

Instantly share code, notes, and snippets.

@mooware
Created June 30, 2017 03:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mooware/860eedb5a0a910fd98c7fb990fc5b749 to your computer and use it in GitHub Desktop.
Save mooware/860eedb5a0a910fd98c7fb990fc5b749 to your computer and use it in GitHub Desktop.
Snake for Arduino on a LED matrix
// snake on a MAX7219 led matrix
// NOTE: assuming the chip and pins are on the left side of the led matrix,
// row = 0, col = 0 is at the upper left corner
#include <LedControl.h>
// we're using a membrane keypad as controller
#include <Keypad.h>
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
//define the cymbols on the buttons of the keypads
char hexaKeys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 3, 2}; //connect to the column pinouts of the keypad
//initialize an instance of class NewKeypad
Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
// configuration
const int CLK_PIN = 10;
const int CS_PIN = 11;
const int DATA_PIN = 12;
const int DISPLAY_ID = 0;
const int DISPLAY_COUNT = 1;
const int DISPLAY_INTENSITY = 6; // brightness, 0..15
const int DISPLAY_SIZE = 8; // led matrix is 8 by 8
const int START_SPEED = 800; // initial game speed
const int SPEED_STEP = 10; // speed-up for every point
const int ANIMATION_TIME = 500;
// 2d position datatype
struct pos
{
char col;
char row;
// allow adding another pos
pos &operator+=(const pos &add) {
col += add.col;
row += add.row;
return *this;
}
// comparison
bool operator==(const pos &other) const { return col == other.col && row == other.row; }
// check if another pos is the opposite direction of this one
bool isInverse(const pos &other) const { return col == -other.col && row == -other.row; }
};
// list of current snake points
pos snake[DISPLAY_SIZE * DISPLAY_SIZE];
// number of items in snake array
int snakelen;
// current moving direction
pos snakedir;
// next point to collect
pos nextpoint;
// next direction change
pos nextdir;
// run game logic every N milliseconds
int gamespeed;
// directions
const pos DIR_NONE = { 0, 0 };
const pos DIR_LEFT = { -1, 0 };
const pos DIR_RIGHT = { +1, 0 };
const pos DIR_UP = { 0, -1 };
const pos DIR_DOWN = { 0, +1 };
LedControl leds(DATA_PIN, CLK_PIN, CS_PIN, DISPLAY_COUNT);
// read user input and change direction if necessary
void readInput() {
char key = customKeypad.getKey();
pos newdir;
switch (key) {
case '2': newdir = DIR_UP; break;
case '4': newdir = DIR_LEFT; break;
case '6': newdir = DIR_RIGHT; break;
case '8': newdir = DIR_DOWN; break;
default: return; // no relevant key pressed
}
if (newdir == snakedir || newdir.isInverse(snakedir))
return; // same or opposite direction are ignored
nextdir = newdir;
}
void setLed(const pos &p, bool show) {
leds.setLed(DISPLAY_ID, p.row, p.col, show);
}
// screen transition effect, fill display with spiral inside-out
void swirl() {
pos p = { 0, 0 };
pos dirs[] = { DIR_RIGHT, DIR_DOWN, DIR_LEFT, DIR_UP };
leds.clearDisplay(DISPLAY_ID);
delay(ANIMATION_TIME);
int drawlen = DISPLAY_SIZE - 1;
while (drawlen > 0) {
// move along the outside of the display, switch directions at the last led
for (pos d : dirs) {
for (int i = 0; i < drawlen; ++i) {
setLed(p, true);
delay(ANIMATION_TIME / 10);
p += d;
}
}
// make the drawing bounds smaller, move to new starting point
drawlen -= 2;
p.col++;
p.row++;
}
leds.clearDisplay(DISPLAY_ID);
}
// create a new point at a random position
pos newPoint() {
pos result;
// endless loop because we have to retry if the new point collides with the snake.
// can obviously not be used if there are no more free spaces.
while (true) {
result.col = random(DISPLAY_SIZE);
result.row = random(DISPLAY_SIZE);
// avoid colliding with the snake itself
int collisions = 0;
for (int i = 0; i < snakelen; ++i) {
if (snake[i] == result)
++collisions;
}
if (collisions == 0)
return result;
}
}
void resetGame() {
gamespeed = START_SPEED;
// snake always starts at the same place
snakelen = 3;
snake[0] = { 3, 1 };
snake[1] = { 2, 1 };
snake[2] = { 1, 1 };
snakedir = DIR_NONE;
nextdir = DIR_NONE;
nextpoint = newPoint();
// do a nice animation at the start
swirl();
// draw the snake initially, later it will be drawn incrementally
for (int i = 0; i < snakelen; ++i) {
setLed(snake[i], true);
}
}
bool moveSnake() {
snakedir = nextdir;
if (snakedir == DIR_NONE)
return true;
pos newpos = snake[0];
newpos += snakedir;
// new position cannot be out of bounds
if (newpos.row < 0 || newpos.row >= DISPLAY_SIZE ||
newpos.col < 0 || newpos.col >= DISPLAY_SIZE)
return false;
// new position cannot collide with the snake (except the tail, which will be removed)
for (int i = 0; i < snakelen - 1; ++i) {
if (snake[i] == newpos)
return false;
}
// check if point was collected
bool collected = (newpos == nextpoint);
if (collected) {
++snakelen;
if (snakelen == (DISPLAY_SIZE * DISPLAY_SIZE))
return false; // no more space, game over
gamespeed -= SPEED_STEP;
} else {
// incremental snake drawing, remove the tail and add the head
pos tail = snake[snakelen - 1];
setLed(tail, false);
setLed(newpos, true);
}
// actually move snake
for (int i = snakelen - 2; i >= 0; --i)
snake[i+1] = snake[i];
snake[0] = newpos;
// make a new point to collect
if (collected)
nextpoint = newPoint();
return true;
}
void setup() {
// matrix is in power-saving mode on startup, wake it up
leds.shutdown(DISPLAY_ID, false);
leds.setIntensity(DISPLAY_ID, DISPLAY_INTENSITY);
leds.clearDisplay(DISPLAY_ID);
// analog pin 0 should not be connected, use for random seed
randomSeed(analogRead(0));
resetGame();
}
void loop() {
bool ok = moveSnake();
if (!ok) {
// game over, reset game state
delay(6 * ANIMATION_TIME);
resetGame();
return;
}
// let the next point blink for a bit. also check for inputs.
bool blinkstate = false;
for (int i = 0; i < 6; ++i) {
setLed(nextpoint, blinkstate);
blinkstate = !blinkstate;
// have to poll very frequently to not miss any inputs
for (int j = 0; j < 5; ++j) {
readInput();
delay(gamespeed / (5*6));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment