#include <InputDebounce.h>
#include "game_defs.h"
#include "Ssd1306Screen.h"
//#define USE_INPUT_DEBOUNCE_LIB
// Uncomment to snake change direction randomly. Direction inputs are also deactived
// #define AUTO_MOVE
#define UP_PIN 7
#define DOWN_PIN 10
#define LEFT_PIN 9
#define RIGHT_PIN 6
#define ACTION_PIN 8
typedef struct {
Position head;
Position tail;
} Snake;
Direction moveField[FIELD_HEIGHT][FIELD_WIDTH];
Position fruitPosition;
Snake snake;
Direction snakeDirection;
GameState gameState;
uint16_t snakeVelocity;
uint8_t fruitCounter;
uint8_t level;
uint16_t score;
uint64_t lastMoveTime;
Screen* screen;
bool upPressed;
bool downPressed;
bool leftPressed;
bool rightPressed;
bool actionPressed;
#ifdef USE_INPUT_DEBOUNCE_LIB
static InputDebounce upPin;
static InputDebounce downPin;
static InputDebounce leftPin;
static InputDebounce rightPin;
static InputDebounce actoinPin;
#endif
void initNewGame();
void paintGame();
bool checkCollision();
GameState moveSnake();
Position nextHeadPosition();
Position createFruitPosition();
#ifdef USE_INPUT_DEBOUNCE_LIB
void buttonPressed(uint8_t pinNumber);
void buttonReleased(uint8_t pinNumber);
#endif
void setup() {
Serial.begin(9600);
#ifdef USE_INPUT_DEBOUNCE_LIB
upPin.registerCallbacks(buttonPressed, buttonReleased);
downPin.registerCallbacks(buttonPressed, buttonReleased);
leftPin.registerCallbacks(buttonPressed, buttonReleased);
rightPin.registerCallbacks(buttonPressed, buttonReleased);
actoinPin.registerCallbacks(buttonPressed, buttonReleased);
upPin.setup(UP_PIN);
downPin.setup(DOWN_PIN);
leftPin.setup(LEFT_PIN);
rightPin.setup(RIGHT_PIN);
actoinPin.setup(ACTION_PIN);
#else
pinMode(UP_PIN, INPUT_PULLUP);
pinMode(DOWN_PIN, INPUT_PULLUP);
pinMode(LEFT_PIN, INPUT_PULLUP);
pinMode(RIGHT_PIN, INPUT_PULLUP);
pinMode(ACTION_PIN, INPUT_PULLUP);
#endif
screen = new Ssd1306Screen();
screen->begin();
initNewGame();
}
void loop() {
upPressed = false;
downPressed = false;
leftPressed = false;
rightPressed = false;
actionPressed = false;
#ifdef USE_INPUT_DEBOUNCE_LIB
unsigned long now = millis();
upPin.process(now);
downPin.process(now);
leftPin.process(now);
rightPin.process(now);
actoinPin.process(now);
#else
if(!digitalRead(UP_PIN)) {
upPressed = true;
}
if(!digitalRead(DOWN_PIN)) {
downPressed = true;
}
if(!digitalRead(LEFT_PIN)) {
leftPressed = true;
}
if(!digitalRead(RIGHT_PIN)) {
rightPressed = true;
}
if(!digitalRead(ACTION_PIN)) {
actionPressed = true;
}
#endif
moveSnake();
screen->show();
}
void initNewGame() {
lastMoveTime = millis();
gameState = RUNNING;
fruitCounter = 0;
snakeDirection = RIGHT;
snakeVelocity = 1000;
level = 1;
score = 0;
screen->clear();
for(int row = 0; row < FIELD_HEIGHT; row++) {
for(int column = 0; column < FIELD_WIDTH; column++) {
moveField[row][column] = NONE;
}
}
snake = (Snake) {
(Position) {5, 3},
(Position) {5, 1}
};
moveField[5][1] = RIGHT;
moveField[5][2] = RIGHT;
moveField[5][3] = RIGHT;
for(int row = 0; row < FIELD_HEIGHT; row++) {
for(int column = 0; column < FIELD_WIDTH; column++) {
if(moveField[row][column] != NONE) {
screen->drawCell(row, column);
}
}
}
fruitPosition = createFruitPosition();
screen->drawFruit(fruitPosition.row, fruitPosition.column);
screen->drawGameFrame();
screen->drawPlacarNames();
screen->drawPlacarValues(score, level);
}
GameState moveSnake() {
if(upPressed) { snakeDirection = UP; }
if(downPressed) { snakeDirection = DOWN; }
if(leftPressed) { snakeDirection = LEFT; }
if(rightPressed) { snakeDirection = RIGHT; }
uint64_t currentMoveTime = millis();
if(currentMoveTime - lastMoveTime < snakeVelocity) return;
lastMoveTime = currentMoveTime;
#ifdef AUTO_MOVE
if(random(10) < 4) {
if(snakeDirection == RIGHT || snakeDirection == LEFT) {
snakeDirection = random(2) ? UP : DOWN;
}
if(snakeDirection == UP || snakeDirection == DOWN) {
snakeDirection = random(2) ? LEFT : RIGHT;
}
}
#endif
Position targetHeadPosition = nextHeadPosition();
if(checkCollision(targetHeadPosition.row, targetHeadPosition.column)) {
#ifndef AUTO_MOVE
return;
#else
Direction directions[] = {DOWN, UP, LEFT, RIGHT};
int retries = 100;
while(retries >= 0) {
snakeDirection = directions[random(4)];
targetHeadPosition = nextHeadPosition();
if(!checkCollision(targetHeadPosition.row, targetHeadPosition.column)) break;
retries--;
}
#endif
}
moveField[snake.head.row][snake.head.column] = snakeDirection;
snake.head = targetHeadPosition;
screen->drawCell(snake.head.row, snake.head.column);
if(snake.head.row == fruitPosition.row && snake.head.column == fruitPosition.column) {
fruitCounter++;
score += fruitCounter;
if(fruitCounter < 197) {
fruitPosition = createFruitPosition();
screen->drawFruit(fruitPosition.row, fruitPosition.column);
}
if(level < 30 && fruitCounter % 6 == 1) {
snakeVelocity -= 30;
level++;
}
screen->drawPlacarValues(score, level);
if(fruitCounter == 197) {
return GAME_OVER;
}
return RUNNING;
}
screen->clearCell(snake.tail.row, snake.tail.column);
Direction tailDirection = moveField[snake.tail.row][snake.tail.column];
moveField[snake.tail.row][snake.tail.column] = NONE;
if(tailDirection == RIGHT) {
snake.tail.column++;
} else if(tailDirection == LEFT) {
snake.tail.column--;
} else if(tailDirection == DOWN) {
snake.tail.row++;
} else if(tailDirection == UP) {
snake.tail.row--;
}
return RUNNING;
}
Position nextHeadPosition() {
Position nextPosition = snake.head;
if(snakeDirection == RIGHT) {
nextPosition.column++;
} else if(snakeDirection == LEFT) {
nextPosition.column--;
} else if(snakeDirection == DOWN) {
nextPosition.row++;
} else if(snakeDirection == UP) {
nextPosition.row--;
}
return nextPosition;
}
bool checkCollision(int8_t row, int8_t column) {
return row < 0
|| row >= FIELD_HEIGHT
|| column < 0
|| column >= FIELD_WIDTH
|| moveField[row][column] != NONE;
}
Position createFruitPosition() {
randomSeed(millis() + snake.head.row + snake.head.column + analogRead(0) + analogRead(1));
uint8_t columnOffset = random(FIELD_WIDTH);
for(uint8_t i = 0; i < FIELD_WIDTH; i++) {
uint8_t rowOffset = random(FIELD_HEIGHT);
for(uint8_t j = 0; j < FIELD_HEIGHT; j++) {
uint8_t column = (columnOffset + i) % FIELD_WIDTH;
uint8_t row = (rowOffset + j) % FIELD_HEIGHT;
if(moveField[row][column] == NONE) {
return (Position) {row, column};
}
}
}
}
#ifdef USE_INPUT_DEBOUNCE_LIB
void buttonPressed(uint8_t pinNumber) {
if(pinNumber == UP_PIN) {
upPressed = true;
} else if(pinNumber == DOWN_PIN) {
downPressed = true;
} else if(pinNumber == LEFT_PIN) {
leftPressed = true;
} else if(pinNumber == RIGHT_PIN) {
rightPressed = true;
} else if(pinNumber == ACTION_PIN) {
actionPressed = true;
}
}
void buttonReleased(uint8_t pinNumber) {
}
#endif