Created
June 30, 2017 03:02
-
-
Save mooware/860eedb5a0a910fd98c7fb990fc5b749 to your computer and use it in GitHub Desktop.
Snake for Arduino on a LED matrix
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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