#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
// Screen dimensions when split into 16x16 chunks is 15 by 20
// Max snake length is 100 - game is won at that point
// https://create.arduino.cc/projecthub/MisterBotBreak/how-to-use-a-tft-screen-8dbdaf
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define GREY 0xD6BA
#define TFT_DC 9
#define TFT_CS 10
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
#define JOY_VERT 0
#define JOY_HORIZ 1
#define JOY_SEL 2
#define NORTH 2
#define EAST 1
#define SOUTH -2
#define WEST -1
struct SnakeBody{
int x;
int y;
int XMax;
int XMin;
int YMax;
int YMin;
};
SnakeBody snake[100];
SnakeBody food[1];
// Size is 1 more then the length of the snake
unsigned int size = 3;
unsigned int score = 1;
unsigned int highScore = 0;
int direction = EAST;
void setup() {
// Housekeeping
tft.begin();
Serial.begin(9600);
randomSeed(analogRead(A4));
pinMode(JOY_SEL, INPUT_PULLUP);
}
void home_screen()
{
}
void loop()
{
if (digitalRead(JOY_SEL) == LOW)
{
tft.fillScreen(BLACK);
play_snake();
}
}
void play_snake()
{
// Initializng the position of the food
food[0].x = 10;
food[0].y = 10;
food[0].XMax = 16 * food[0].x + 16;
food[0].XMin = 16 * food[0].x;
food[0].YMax = 16 * food[0].y + 16;
food[0].YMin = 16 * food[0].y;
// Initializing the starting postion of the snake
snake[0].x = 6;
snake[0].y = 10;
snake[1].x = 6;
snake[1].y = 10;
// Draw boarders of the display (tft library won't let us use the whole screen)
//TO DO
for (int i = 0; i < size; i++)
{
snake[i].XMax = 16 * snake[i].x + 16;
snake[i].XMin = 16 * snake[i].x;
snake[i].YMax = 16 * snake[i].y + 16;
snake[i].YMin = 16 * snake[i].y;
}
direction = EAST;
size = 3;
bool game_state = true;
while (game_state == true)
{
// Prevents the snake from going in the exact opposite direction it is traveling in
if (direction != -read_joystick())
{
// Changes direction to the direction of the joystick
direction = read_joystick();
}
// Moves snake head in the direction of travel
snake_movement(direction);
// Moves rest of snake body accordingly (func pear is inside that checks for food collision)
update_snake();
game_state = check_collision();
}
return;
}
// Moves the head of the snake in the specified direction
void snake_movement(int direction)
{
if (direction == NORTH)
{
snake[0].y ++;
}
else if (direction == SOUTH)
{
snake[0].y --;
}
else if (direction == WEST)
{
snake[0].x --;
}
else if (direction == EAST)
{
snake[0].x ++;
}
}
// ... Reads the joystick
int read_joystick()
{
if(abs(analogRead(JOY_VERT) - 512) > 0)
{
if(analogRead(JOY_VERT) < 510)
{
return WEST;
}
else if(analogRead(JOY_VERT) > 520)
{
return EAST;
}
}
else if(abs(analogRead(JOY_HORIZ) - 512) > 0)
{
if(analogRead(JOY_HORIZ) < 510)
{
return NORTH;
}
else if(analogRead(JOY_HORIZ) > 520)
{
return SOUTH;
}
}
else
{
return direction;
}
}
// Checks for "pears" colision with snake
void pear()
{
if (food[0].x == snake[0].x && food[0].y == snake[0].y)
{
size++;
gen_cords();
//food[0].x = random(0, 14);
//food[0].y = random(0, 19);
food[0].XMax = 16 * food[0].x + 16;
food[0].XMin = 16 * food[0].x;
food[0].YMax = 16 * food[0].y + 16;
food[0].YMin = 16 * food[0].y;
}
tft.fillRect(food[0].XMin, food[0].YMin, 16, 16, RED);
}
// Generates valid random positions for the pear to spawn
void gen_cords()
{
food[0].x = random(0, 14);
food[0].y = random(0, 19);
for (int i = 0; i < size; i++)
{
if (food[0].x == snake[i].x && food[0].y == snake[i].y)
{
gen_cords();
Serial.println("RECURSION!");
break;
}
}
}
// Moves the snake
void update_snake()
{
for (int i = 0; i < size; i++)
{
tft.fillRect(snake[i].XMin, snake[i].YMin, 16, 16, BLACK);
}
for (int i = size; i > 0; i--)
{
snake[i].x = snake[i-1].x;
snake[i].y = snake[i-1].y;
}
for (int i = 0; i < size; i++)
{
snake[i].XMax = 16 * snake[i].x + 16;
snake[i].XMin = 16 * snake[i].x;
snake[i].YMax = 16 * snake[i].y + 16;
snake[i].YMin = 16 * snake[i].y;
}
for (int i = 1; i < size; i++)
{
tft.fillRect(snake[i].XMin, snake[i].YMin, 16, 16, GREEN);
}
tft.fillRect(snake[0].XMin, snake[0].YMin, 16, 16, YELLOW);
pear();
delay(300);
}
bool check_collision()
{
// Check if the head leaves the screen
if (snake[0].x > 14 || snake[0].x < 0)
{
lose();
return false;
}
if (snake[0].y > 19 || snake[0].y < 0)
{
lose();
return false;
}
for (int i = 2; i <= size; i++)
{
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y)
{
lose();
return false;
}
}
return true;
}
void win()
{
}
void lose()
{
// Save the score of the game
score = size - 3;
if (score > highScore)
{
highScore = score;
}
// Clear the game - alse end the game
clear_snake();
// Print the game over messages to the screen
tft.setTextColor(RED);
tft.setCursor(15, 75);
tft.setTextSize(4);
tft.println("GAME OVER");
// Prints the score of the game to the screen
tft.setTextColor(WHITE);
tft.setCursor(40, 135);
tft.setTextSize(3);
tft.println("SCORE: ");
tft.setCursor(150, 135);
tft.println(score);
tft.setTextSize(2);
tft.setCursor(30, 175);
tft.println("HIGH SCORE: ");
tft.setCursor(175, 175);
tft.println(highScore);
tft.setTextColor(GREEN);
tft.setTextSize(2);
tft.setCursor(35, 220);
tft.println("PRESS JOYSTICK");
tft.setCursor(40, 240);
tft.println("TO PLAY AGAIN");
// Resets the score of the game
score = 1;
}
void clear_snake()
{
for (int i = 0; i < size; i++)
{
tft.fillRect(snake[i].XMin, snake[i].YMin, 16, 16, BLACK);
}
tft.fillRect(food[0].XMin, food[0].YMin, 16, 16, BLACK);
for (int i = 100; i > 3; i--)
{
snake[i].x = -1;
snake[i].y= -1;
}
}
/*
TODO:
- Make home screen where you can select the game of your choice
- Make end screen and start screen, pressing button sends you to home screen
- Figure out how to use progmem to save high scores inbetween runs
Move onto better and harder projects
- Figure out a work around for memory that will give me the ability to
have more than one game (probably with a SD card)
Use Arduino Mega, gives me 8x the flash memory so should be capable of
running at least 4 games
*/