#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
// #include "bitmaps.h"
// #include "pitches.h"
// #include "constant.h"
// #define WELCOME_STATE 0
#define GAMEPLAY_STATE 1
#define GAMVEOVER_STATE 2
#define TURN_UP 0
#define TURN_RIGHT 1
#define TURN_LEFT 3
#define TURN_DOWN 2
#define SW_PIN A2
#define BUZZER_PIN 10
#define VRX A0
#define VRY A1
const int screenWidth = 128;
const int screenHeight = 64;
const int gameWidth = 64;
const int gameHeight = 32;
const int maxLength = 464;
const int startLength = 6;
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
const unsigned char kSplashScreen[] PROGMEM = {
0x00,0x00,0x00,0x0F,0xFF,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x08,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x38,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x21,0xFE,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xE1,0xFE,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x87,0xF9,0x84,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x0F,0x87,0xF9,0x84,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x08,0x1F,0x9F,0xE4,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x38,0x1F,0x9F,0xE4,0x00,0x3F,0xC0,0x00,0x00,0x00,
0x00,0x00,0x21,0xFF,0xFF,0xE4,0x00,0x20,0x40,0x00,0x00,0x00,
0x00,0x00,0xE1,0xFF,0xFF,0xE7,0x00,0x20,0x40,0x00,0x00,0x00,
0x00,0x00,0x87,0xFF,0xFF,0xF9,0x00,0x26,0x40,0x00,0x00,0x00,
0x00,0x03,0x87,0xFF,0xFF,0xF9,0x00,0xE6,0x40,0x00,0x00,0x00,
0x00,0x02,0x1F,0xFE,0x7F,0xE1,0x00,0x86,0x40,0x00,0x00,0x00,
0x00,0x02,0x1F,0xFE,0x7F,0xE1,0x00,0x86,0x70,0x00,0x00,0x00,
0x00,0x02,0x7F,0x9E,0x1F,0xE7,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x0E,0x7F,0x9E,0x1F,0xE4,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x08,0x7E,0x07,0x9F,0x84,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x08,0x7E,0x07,0x9F,0x84,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xF9,0xE1,0xE0,0x1C,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xF9,0x21,0xE0,0x10,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xE1,0x38,0x00,0x70,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xE1,0x08,0x00,0x70,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xE7,0x0F,0xF8,0x10,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xE4,0x00,0x08,0x1C,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xE4,0x00,0x0E,0x04,0x00,0x9F,0x90,0x00,0x00,0x00,
0x00,0x09,0xE4,0x00,0x02,0x07,0x00,0x9F,0x93,0xFF,0x00,0x00,
0x00,0x09,0xE4,0x00,0x03,0x81,0x00,0x9F,0x92,0x01,0x00,0x00,
0x00,0x09,0xE7,0x00,0x00,0x81,0x00,0x9F,0x9E,0x01,0xC0,0x00,
0x00,0x09,0xF9,0x00,0x00,0x9F,0x00,0x9F,0x80,0x78,0x40,0x00,
0x00,0x09,0xF9,0xC0,0x00,0x90,0xFF,0x9F,0x80,0x78,0x7C,0x00,
0x00,0x08,0x78,0x40,0x00,0xF0,0x80,0x1F,0x87,0xFE,0x04,0x00,
0x00,0x08,0x78,0x43,0xFC,0xFF,0x80,0x1F,0x87,0xFE,0x07,0xC0,
0x00,0x0E,0x1E,0x42,0x04,0x86,0x1F,0x87,0x9F,0xFE,0x60,0x40,
0x3C,0x02,0x1E,0x7E,0x07,0x86,0x1F,0x87,0x9F,0xFE,0x60,0x70,
0x24,0x03,0x9F,0x99,0xF8,0x78,0x7F,0xE7,0xFF,0xF8,0x7E,0x10,
0xE7,0x00,0x9F,0x99,0xF8,0x78,0x7F,0xE7,0xFF,0xF8,0x7E,0x10,
0x81,0x00,0x81,0xE1,0xFF,0xFE,0x61,0xE7,0xFF,0x81,0x87,0x90,
0x81,0xC0,0x81,0xE1,0xFF,0xFE,0x61,0xE7,0xFF,0x81,0x87,0x90,
0x98,0x40,0xF9,0xF8,0x7F,0xFE,0x61,0xE7,0xF8,0x1F,0x87,0x90,
0x98,0x40,0x09,0xF8,0x7F,0xFE,0x61,0xE7,0xF8,0x1F,0x87,0x90,
0x9E,0x40,0x08,0x7E,0x7F,0xFE,0x7F,0xE7,0xF8,0x7F,0xFE,0x10,
0x9E,0x7C,0x08,0x7E,0x7F,0xFE,0x7F,0xE7,0xF8,0x7F,0xFE,0x10,
0xE7,0x84,0x0E,0x7E,0x7F,0xFE,0x7F,0xE7,0xFE,0x1F,0x80,0x70,
0x27,0x87,0xFE,0x7E,0x7F,0xFE,0x7F,0xE7,0xFE,0x1F,0x80,0x40,
0x27,0xE0,0x00,0x7E,0x78,0x7E,0x7F,0xE1,0x9F,0x87,0xE7,0xC0,
0x27,0xE0,0x00,0x7E,0x78,0x7E,0x7F,0xE1,0x9F,0x87,0xE7,0x00,
0x21,0xFF,0xFF,0xFE,0x79,0x9E,0x61,0xF9,0x87,0xE7,0xF9,0x00,
0x21,0xFF,0xFF,0xFE,0x79,0x9E,0x61,0xF9,0x87,0xE7,0xF9,0xC0,
0x38,0x7F,0xFF,0xE0,0x79,0x9E,0x66,0x79,0x81,0xE7,0xFE,0x40,
0x08,0x7F,0xFF,0xE0,0x79,0x9E,0x66,0x79,0x81,0xE7,0xFE,0x40,
0x0E,0x00,0x00,0x06,0x01,0x80,0x1E,0x00,0x18,0x61,0xFE,0x40,
0x02,0x00,0x00,0x06,0x01,0x80,0x12,0x00,0x18,0x61,0xFE,0x40,
0x03,0xFF,0xFF,0xFF,0xFF,0xFF,0xF3,0xFF,0xFE,0x06,0x01,0xC0,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x06,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xFF,0xFF,0x00
};
const unsigned char kGameOver[] PROGMEM = {
0x00,0xFF,0xF0,0x00,0x00,0x00,0x3F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x80,0x10,0x00,0x00,0x00,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x03,0x80,0x1C,0x00,0x00,0x00,0xE0,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x02,0x7F,0xE4,0x00,0x00,0x00,0x87,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0E,0x7F,0xE4,0x00,0x00,0x00,0x87,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x09,0xFF,0xE4,0x00,0x00,0x00,0x9F,0xE4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x39,0xFF,0xE4,0x00,0x00,0x03,0x9F,0xE7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x27,0xE0,0x1C,0x00,0x00,0x02,0x1F,0xE1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x27,0xE0,0x10,0x00,0x00,0x02,0x1F,0xE1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x27,0x87,0xF0,0x00,0x00,0x02,0x7F,0xF9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x27,0x84,0x00,0x00,0x00,0x02,0x7F,0xF9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x27,0x9C,0x00,0x00,0x00,0x02,0x7F,0xF9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x27,0x93,0xFF,0xFF,0xFF,0xFE,0x7F,0xF9,0xFF,0xFF,0xFF,0xC0,0x00,0x00,0x00,0x00,
0x27,0x92,0x06,0x19,0x99,0x80,0x7F,0xF9,0x86,0x06,0x60,0x40,0x00,0x00,0x00,0x00,
0x27,0x9E,0x06,0x19,0x99,0x80,0x7F,0xF9,0x86,0x06,0x60,0x7C,0x00,0x00,0x00,0x00,
0x27,0x99,0xF9,0xE6,0x66,0x7E,0x7F,0xF8,0x79,0xF9,0x9F,0x84,0x00,0x00,0x00,0x00,
0x27,0x99,0xF9,0xE6,0x66,0x7E,0x7F,0xF8,0x79,0xF9,0x9F,0x87,0x3F,0xF0,0x00,0x00,
0x27,0x99,0xF9,0xFF,0xFF,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xE1,0x20,0x10,0x00,0x00,
0x27,0x99,0xF9,0xFF,0xFF,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xE1,0xE0,0x1C,0x00,0x00,
0x27,0x9E,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0x7E,0x1F,0xE4,0x00,0x00,
0x27,0x9E,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0x7E,0x1F,0xE7,0x00,0x00,
0x27,0xE1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF9,0x9F,0xFF,0x99,0x00,0x00,
0x27,0xE1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF9,0x9F,0xFF,0x99,0xC0,0x00,
0x21,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0xFF,0xF9,0xE7,0xFE,0x06,0x40,0x00,
0x21,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0xFF,0xF9,0x27,0xFE,0x06,0x40,0x00,
0x39,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0xE1,0xFF,0xF9,0x21,0xE7,0x9E,0x40,0x00,
0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0xE1,0xFF,0xF9,0x21,0xE7,0x9E,0x40,0x00,
0x09,0xFF,0x9E,0x1E,0x66,0x7E,0x1F,0xE1,0x99,0xF9,0xE1,0x39,0x81,0xFE,0x40,0x00,
0x09,0xFF,0x9E,0x1E,0x66,0x7E,0x1F,0xE1,0x99,0xF9,0xE1,0x09,0x81,0xFE,0x40,0x00,
0x09,0x80,0x61,0xE1,0x99,0x81,0xE0,0x1E,0x7E,0x06,0x1F,0x09,0xE7,0xF8,0x40,0x00,
0xF9,0x80,0x61,0x21,0x99,0x81,0x20,0x12,0x42,0x06,0x10,0x09,0xE7,0xF8,0x43,0xC0,
0x87,0x9F,0xFF,0x3F,0xFF,0xFF,0x3F,0xF3,0xC3,0xFF,0xF0,0x0E,0x7F,0xE0,0x42,0x40,
0x87,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x7F,0xE0,0x7E,0x70,
0x9E,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x18,0x00,0x10,
0x9E,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x18,0x00,0x10,
0xE1,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFE,0x00,0x70,
0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x40,
0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xFF,0xC0
};
Adafruit_SSD1306 lcd(screenWidth, screenHeight, &Wire, -1);
enum GameState {
WELCOME_STATE,
GAMEPLAY_STATE,
GAMEOVER_STATE
};
int gameState = WELCOME_STATE;
bool isWantDisplayFlickeing = false;
unsigned long lastDisplayFlickering = 0;
bool isWantCollectFruitSound = false;
bool isWantButtonPressedSound = false;
unsigned long lastBuzzerPlayed = 0;
struct Position {
int x, y;
bool operator==(const Position& other) const {
return x == other.x && y == other.y;
}
Position& operator+=(const Position& other) {
x += other.x;
y += other.y;
return *this;
}
};
void draw_square(Position pos, int color = WHITE) {
lcd.fillRect(pos.x * 2, pos.y * 2, 2, 2, color);
}
bool test_position(Position pos) {
return lcd.getPixel(pos.x * 2, pos.y * 2);
}
const Position kDirPos[4] = {
{0,-1}, // Up
{1, 0}, // Right
{0, 1}, // Down
{-1, 0} // Left
};
struct Player {
Player() {
reset();
}
Position pos;
unsigned char tail[maxLength];
unsigned char direction;
int size, moved;
void reset() {
pos = {32, 16};
direction = 1;
size = startLength;
memset(tail, 0, sizeof(tail));
moved = 0;
}
bool isTurningDirIsVaild(int dir) {
if (dir == TURN_LEFT && direction == TURN_RIGHT) {
return false;
} else if (dir == TURN_RIGHT && direction == TURN_LEFT) {
return false;
} else if (dir == TURN_DOWN && direction == TURN_UP) {
return false;
} else if (dir == TURN_UP && direction == TURN_DOWN) {
return false;
}
return true;
}
void turn(int dir) {
if (!isTurningDirIsVaild(dir)) return;
direction = dir;
}
void update() {
for(int i = maxLength - 1; i > 0; --i) {
tail[i] = tail[i] << 2 | ((tail[i - 1] >> 6) & 3);
}
tail[0] = tail[0] << 2 | ((direction + 2) % 4);
pos += kDirPos[direction];
if(moved < size) {
moved++;
}
}
void render() const {
draw_square(pos);
if(moved < size) {
return;
}
Position tailpos = pos;
for(int i = 0; i < size; ++i) {
tailpos += kDirPos[(tail[(i >> 2)] >> ((i & 3) * 2)) & 3];
}
draw_square(tailpos, BLACK);
}
} player;
struct Item {
Position pos;
void spawn() {
pos.x = random(1, 63);
pos.y = random(1, 31);
}
void render() const {
draw_square(pos);
}
} item;
void waitForInput() {
do {
handelSound();
} while(digitalRead(SW_PIN) == HIGH);
isWantButtonPressedSound = true;
lastBuzzerPlayed = millis();
handelSound();
}
void pushToStart() {
lcd.setCursor(26,57);
lcd.print(F("Push to start"));
}
void playIntro() {
lcd.clearDisplay();
lcd.drawBitmap(18, 0, kSplashScreen, 92, 56, WHITE);
pushToStart();
lcd.display();
}
void playGameover() {
lcd.clearDisplay();
lcd.drawBitmap(4, 0, kGameOver, 124, 38, WHITE);
int score = player.size - startLength;
lcd.setCursor(26, 34);
lcd.print(F("Score: "));
lcd.print(score);
int hiscore;
EEPROM.get(0, hiscore);
if(score > hiscore) {
EEPROM.put(0, score);
hiscore = score;
lcd.setCursor(4, 44);
lcd.print(F("NEW"));
}
lcd.setCursor(26, 44);
lcd.print(F("Hi-Score: "));
lcd.print(hiscore);
pushToStart();
lcd.display();
}
void resetGame() {
lcd.clearDisplay();
for(int x = 0; x < gameWidth; ++x) {
draw_square({x, 0});
draw_square({x, 31});
}
for(int y = 0; y < gameHeight; ++y) {
draw_square({0, y});
draw_square({63, y});
}
player.reset();
item.spawn();
}
void updateGame() {
player.update();
}
bool isGameOver() {
if (player.pos == item.pos) return false;
return test_position(player.pos);
}
void input() {
int x = analogRead(VRX);
int y = analogRead(VRY);
if (x <= 100 - 50) {
player.turn(TURN_LEFT);
} else if (x >= 900 + 100) {
player.turn(TURN_RIGHT);
} else if (y <= 100 - 50) {
player.turn(TURN_UP);
} else if (y >= 900 + 100) {
player.turn(TURN_DOWN);
}
}
void render() {
player.render();
item.render();
lcd.display();
}
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
pinMode(VRX, INPUT);
pinMode(VRY, INPUT);
pinMode(SW_PIN, INPUT);
analogWrite(BUZZER_PIN, 1);
digitalWrite(SW_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH);
noTone(BUZZER_PIN);
lcd.begin(SSD1306_SWITCHCAPVCC, 0x3C);
lcd.setTextColor(WHITE);
}
void loop() {
handelGameState();
handelSound();
handelDisplayFlickring();
}
void handelGameState() {
switch (gameState) {
case WELCOME_STATE:
playIntro();
if (digitalRead(SW_PIN) == LOW) {
playBtnClickedSound();
isWantDisplayFlickeing = true;
lastDisplayFlickering = millis();
resetGame();
gameState = GAMEPLAY_STATE;
}
break;
case GAMEPLAY_STATE:
input();
player.update();
if (player.pos == item.pos) {
isWantCollectFruitSound = true;
lastBuzzerPlayed = millis();
player.size++;
item.spawn();
} else if (isGameOver()) {
gameState = GAMVEOVER_STATE;
isWantDisplayFlickeing = true;
lastDisplayFlickering = millis();
}
render();
break;
case GAMVEOVER_STATE:
playGameover();
if (digitalRead(SW_PIN) == LOW) {
playBtnClickedSound();
isWantDisplayFlickeing = true;
lastDisplayFlickering = millis();
resetGame();
gameState = GAMEPLAY_STATE;
}
break;
}
}
void playBtnClickedSound() {
isWantButtonPressedSound = true;
lastBuzzerPlayed = millis();
}
void handelDisplayFlickring() {
if (isWantDisplayFlickeing) {
if (millis() <= lastDisplayFlickering + 50) {
lcd.invertDisplay(true);
} else {
lcd.invertDisplay(false);
}
}
}
void handelSound() {
if (isWantCollectFruitSound) {
if (millis() <= lastBuzzerPlayed + 100) {
tone(BUZZER_PIN,NOTE_B5, 100);
} else if (millis() <= lastBuzzerPlayed + 455) {
tone(BUZZER_PIN,NOTE_E6, 850);
} else {
noTone(BUZZER_PIN);
isWantCollectFruitSound = false;
}
} else if (isWantButtonPressedSound) {
if (millis() <= lastBuzzerPlayed + 200) {
tone(BUZZER_PIN, NOTE_AS2, 120);
} else {
noTone(BUZZER_PIN);
isWantButtonPressedSound = false;
}
} else {
noTone(BUZZER_PIN);
}
}