#include <MD_MAX72xx.h>
#include <LiquidCrystal_I2C.h>
// Joystick pins
#define BUTTON 2
#define VERT A4
#define HORZ A5
volatile int vert, horz;
volatile bool button;
// Variables for if the joystick is used
volatile bool isUp = false, isDown = false, isRight = false, isLeft = false;
// LCD attributes
#define i2c_addr 0x27
#define lcd_rows 2
#define lcd_columns 16
LiquidCrystal_I2C lcd(i2c_addr, lcd_columns, lcd_rows);
// Arrows for the LCD
const byte arrowUp[8] = {B00100,B01110,B11111,B00100,B00100,B00100,B00100,B00100}, arrowDown[8] = {B00100,B00100,B00100,B00100,B00100,B11111,B01110,B00100}, arrowRight[8] = {B00000,B00100,B00110,B11111,B00110,B00100,B00000,B00000}, arrowLeft[8] = {B00000,B00100,B01100,B11111,B01100,B00100,B00000,B00000}, arrowUpH[8] = {B11011,B10001,B00000,B11011,B11011,B11011,B11011,B11011}, arrowDownH[8] = {B11011,B11011,B11011,B11011,B11011,B00000,B10001,B11011}, arrowRightH[8] = {B11111,B11011,B11001,B00000,B11001,B11011,B11111,B11111}, arrowLeftH[8] = {B11111,B11011,B10011,B00000,B10011,B11011,B11111,B11111};
// Some strings for the LCD
#define MAINNAME " SNAKE GAME "
#define MAINSCROLL " Press down joystick to play. "
#define DEADSCROLL "YOU DIED! Press reset to restart. "
// Specify display hardware type
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
// Display Size and CS Pin
#define MAX_DEVICES 4
#define CS_PIN 12
// Create a display object
MD_MAX72XX display = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
//some defined matrix states
const uint16_t main1[16] PROGMEM = {0x0000, 0x7FFE, 0x4002, 0x5FFA, 0x500A, 0x57EA, 0x542A, 0x55AA, 0x55AA, 0x542A, 0x57EA, 0x500A, 0x5FFA, 0x4002, 0x7FFE, 0x0000}, main2[16] PROGMEM = {0xFFFF, 0x8001, 0xBFFD, 0xA005, 0xAFF5, 0xA815, 0xABD5, 0xAA55, 0xAA55, 0xABD5, 0xA815, 0xAFF5, 0xA005, 0xBFFD, 0x8001, 0xFFFF};
const uint16_t start1[16] PROGMEM = {0x0000, 0x0000, 0x0FF0, 0x0FF0, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0F80, 0x0F80, 0x0000, 0x0000}, start2[16] PROGMEM = {0x0000, 0x0000, 0x0FF0, 0x0FF0, 0x0C00, 0x0C00, 0x0C00, 0x0FF0, 0x0FF0, 0x0030, 0x0030, 0x0030, 0x0FF0, 0x0FF0, 0x0000, 0x0000}, start3[16] PROGMEM = {0x0000, 0x0000, 0x0FF0, 0x0FF0, 0x0030, 0x0030, 0x0030, 0x0FF0, 0x0FF0, 0x0030, 0x0030, 0x0030, 0x0FF0, 0x0FF0, 0x0000, 0x0000};
const uint16_t skull[16] PROGMEM = {0x0000, 0x07E0, 0x0A50, 0x0A50, 0x0FF0, 0x1FF8, 0x3E7C, 0x3F7C, 0x3FFC, 0x399C, 0x399C, 0x3FFC, 0x1FF8, 0x0FF0, 0x0000};
//postions of the snake and food
uint8_t snakehead = 0x98;
uint16_t snakebody[256] = {0x88, 0x78, 0x68, 0x100, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t food;
//the game score
int score = 0;
bool dead = false;
//clock cycles
int c;
//all functions that are defined later
void matrixImage(uint16_t [16]);
int snakelength();
void checkforcollision();
void placefood();
// Whether it is the game or the main menu
#define MAINMENU false
#define GAME true
bool scene;
//check if it is setting up the game
#define SETUP true
#define INGAME false
bool gamescene;
// Directions
#define UP 0
#define RIGHT 1
#define DOWN 2
#define LEFT 3
int dir = RIGHT;
void setup() {
//the interrupt stuff
//enables port C and D
PCICR |= B00000110;
//enables pin A4 and A5
PCMSK1 |= B00110000;
//enables pins D2
PCMSK2 |= B00000100;
scene = MAINMENU;
gamescene = SETUP;
randomSeed(analogRead(A0));
pinMode(BUTTON, INPUT);
pinMode(VERT, INPUT);
pinMode(HORZ, INPUT);
// Intialize the display objects
display.begin();
lcd.init();
// Set the intensity (brightness) of the display (0-15) and backlight of LCD
display.control(MD_MAX72XX::INTENSITY, 10);
lcd.setBacklight(HIGH);
// Creating custom arrow characters
lcd.createChar(0, arrowUp);lcd.createChar(1, arrowDown);lcd.createChar(2, arrowRight);lcd.createChar(3, arrowLeft);lcd.createChar(4, arrowUpH);lcd.createChar(5, arrowDownH);lcd.createChar(6, arrowRightH);lcd.createChar(7, arrowLeftH);
}
void loop() {
switch (scene){
case MAINMENU:
//prints main text
lcd.setCursor(0,0);
for (int i=0; i<16; i++) lcd.print(((c)%16)-i?MAINNAME[i]:' ');
//prints scrolling text
lcd.setCursor(0, 1);
for(int i=0; i<16; i++) lcd.print(MAINSCROLL[((i+c)%40)]);
matrixImage((c/3)%2?(main1):(main2), true);
if (button){ scene = GAME; lcd.clear(); }
break;
case GAME:
switch (gamescene){
case SETUP:
lcd.setCursor(0,0);
lcd.print("starting...");
matrixImage(start3, true);
delay(1000);
matrixImage(start2, true);
delay(1000);
matrixImage(start1, true);
delay(1000);
display.clear();
gamescene = INGAME;
placefood();
lcd.clear();
break;
case INGAME:
if (collision()) dead = true;
bool grow;
if (dead){
matrixImage(skull, true);
lcd.setCursor(0, 1);
lcd.print("Final score: ");lcd.print(score);
lcd.setCursor(0, 0);
for(int i=0; i<16; i++) lcd.print(DEADSCROLL[((i+c)%36)]);
}
else{
//dynamic arrows
lcd.setCursor(0,0);
if (isUp) lcd.write((byte)4);
else lcd.write((byte)0);
lcd.setCursor(1,0);
if (isDown) lcd.write((byte)5);
else lcd.write((byte)1);
lcd.setCursor(0,1);
if (isRight) lcd.write((byte)6);
else lcd.write((byte)2);
lcd.setCursor(1,1);
if (isLeft) lcd.write((byte)7);
else lcd.write((byte)3);
//rest of the lcd stuff
lcd.setCursor(3,1);
lcd.print("Score:");
lcd.print(score);
grow = false;
if (snakehead==food){
placefood();
grow = true;
score+=(snakelength()/16)+1;
}
if (isUp&&(dir!=DOWN)) dir = UP; if (isDown&&(dir!=UP)) dir = DOWN; if (isRight&&(dir!=LEFT)) dir = RIGHT; if (isLeft&&(dir!=RIGHT)) dir = LEFT;
int sl = snakelength()-(grow?0:2);
for (int i=sl; i>0; i--){
snakebody[i] = snakebody[i-1];
}
snakebody[0] = snakehead;
switch (dir){
case UP:
snakehead = ((snakehead&15)==15)?snakehead&B11110000:snakehead+1;
break;
case RIGHT:
snakehead = ((snakehead>>4)==15)?snakehead&B00001111:snakehead+16;
break;
case DOWN:
snakehead = ((snakehead&15)==0)?snakehead|B00001111:snakehead-1;
break;
case LEFT:
snakehead = ((snakehead>>4)==0)?snakehead|B11110000:snakehead-16;
break;
}
//rendering the snakeposition
uint16_t snakeimage[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int headx = snakehead>>4, heady = snakehead&15;
snakeimage[heady] += (0x8000)>>headx;
for (int i=(snakelength()-2); i>=0; i--)
{
int px = snakebody[i]>>4, py = snakebody[i]&15;
snakeimage[py] += (0x8000)>>px;
}
snakeimage[food&15] |= (0x8000)>>(food>>4);
matrixImage(snakeimage, false);
}
break;
}
break;
}
isUp = false, isDown = false, isRight = false, isLeft = false;
c++;
}
int conx(int ogx, int ogy) {return ((ogy<8)?(16+ogx):(15-ogx));}
int cony(int ogy) {return ((ogy<8)?(ogy):(15-ogy));}
int snakelength(){
int sbp = 0;
do
{
sbp++;
}
while (snakebody[sbp]!=0x100);
snakebody[sbp]=0x100;
return sbp+1;
}
bool collision() {
for (int i=0; i<snakelength(); i++) if (snakebody[i] == snakehead) {return true;}
return false;
}
//displays a 16x16 image on the led matrix
void matrixImage(unsigned int* image, bool flash){
for (int b=0; b<16; b++)
{
for (int a=0; a<16; a++)
{
display.setPoint(cony(b), conx(a, b), (((flash?(pgm_read_word(&image[b])):image[b])>>(15-a))&1));
}
}
display.update();
}
bool insnake(int n){
if (n==snakehead) return true;
for (int k=0; k<snakelength()-2; k++){
if (n==snakebody[k]) return true;
}
return false;
}
void placefood(){
int rando = random((256-snakelength()));
do{
rando++;
rando%=256;
}
while(insnake(rando));
food = rando;
}
ISR (PCINT1_vect){
// Reads the horizontal and vertical values
horz = map(analogRead(HORZ), 0, 1023, 1, -1);
vert = map(analogRead(VERT), 0, 1023, -1, 1);
if (vert==1&&!(isRight||isLeft)) isUp=true;
if (vert==-1&&!(isRight||isLeft)) isDown=true;
if (horz==1&&!(isUp||isDown)) isRight=true;
if (horz==-1&&!(isUp||isDown)) isLeft=true;
}
ISR (PCINT2_vect){
// Reads the joystick's button
button = digitalRead(BUTTON);
}