Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Kwieeciol/c5fa41f685ccb9a81803b0775da27465 to your computer and use it in GitHub Desktop.

Select an option

Save Kwieeciol/c5fa41f685ccb9a81803b0775da27465 to your computer and use it in GitHub Desktop.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int SCREEN_WIDTH = 128;
const int SCREEN_HEIGHT = 64;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// --- PINOUT ---
#define BTN_UP 2
#define BTN_DOWN 3
#define BTN_LEFT 4
#define BTN_RIGHT 5
const uint8_t PINS[4] = { BTN_UP, BTN_DOWN, BTN_LEFT, BTN_RIGHT };
bool btnCur[4];
bool btnPrev[4];
// --- MASTER MENU VARIABLES ---
int gameMode = 0; // 0 = Master Menu, 1 = Snake, 2 = Flappy
int menuSelection = 1;
// --- BIT-COMPRESSED SNAKE VARIABLES ---
#define CELL 4
#define COLS (128 / CELL) // 32 Columns
#define ROWS (64 / CELL) // 16 Rows
#define MAXLEN (COLS * ROWS) // 512 Max Segments
struct Pt { int8_t x, y; };
Pt snakeHead; // Full 2-byte absolute coordinates for the head
uint8_t snakeBody[MAXLEN / 4]; // 512 segments / 4 segments per byte = 128 bytes
uint16_t snakeLen;
uint8_t snakeDir, snakeWantDir; // 0=UP, 1=DOWN, 2=RIGHT, 3=LEFT
Pt food;
uint32_t snakeScore = 0;
uint32_t snakeHiScore = 0;
uint16_t snakeStepMs;
uint32_t snakeLastStep;
uint8_t snakeState = 0; // 0=start, 1=play, 2=dead, 3=win
uint32_t snakeDeadAt;
// --- FLAPPY BIRD VARIABLES ---
volatile bool flapped = false;
uint8_t flappyState = 0;
uint32_t flappyScore = 0;
uint32_t flappyHiScore = 0;
uint32_t flappyDeadAt = 0;
const int FLAPPY_WIDTH = 64;
const int FLAPPY_HEIGHT = 128;
const int BIRD_X = 16;
const int BIRD_RADIUS = 2;
float birdY, birdVy;
const float GRAVITY = 0.18;
const float FLAP_IMPULSE = -2.4;
float pipeX;
int pipeGapY;
bool pipePassed;
const int PIPE_WIDTH = 12;
int PIPE_GAP_HEIGHT = 36;
float PIPE_SPEED = 1.5;
// ==========================================
// INTERRUPT FOR FLAPPY
// ==========================================
void flapISR() {
flapped = true;
}
// ==========================================
// SNAKE BITSTREAM HELPER FUNCTIONS
// ==========================================
// Engineered by yours truly, Machanzo (Jakub) <3
// Reads 2 bits from the packed byte array for a given segment index
uint8_t getSegmentDir(uint16_t idx) {
uint16_t bitIdx = (idx - 1) * 2;
uint16_t byteIdx = bitIdx / 8;
uint8_t bitOffset = bitIdx % 8;
return (snakeBody[byteIdx] >> bitOffset) & 0x03;
}
// Writes 2 bits into the packed byte array for a given segment index
void setSegmentDir(uint16_t idx, uint8_t dir) {
uint16_t bitIdx = (idx - 1) * 2;
uint16_t byteIdx = bitIdx / 8;
uint8_t bitOffset = bitIdx % 8;
snakeBody[byteIdx] &= ~(0x03 << bitOffset); // Clear old 2 bits
snakeBody[byteIdx] |= ((dir & 0x03) << bitOffset); // Inject new 2 bits
}
void noweJedzenie() {
if (snakeLen >= MAXLEN) return; // Map full, cannot spawn food
bool ok;
do {
ok = true;
food.x = random(0, COLS);
food.y = random(0, ROWS);
// Check collision with head
if (food.x == snakeHead.x && food.y == snakeHead.y) { ok = false; continue; }
// Check collision with decoded body segments
Pt temp = snakeHead;
for (uint16_t i = 1; i < snakeLen; i++) {
uint8_t relDir = getSegmentDir(i);
if (relDir == 0) temp.y--;
else if (relDir == 1) temp.y++;
else if (relDir == 2) temp.x++;
else if (relDir == 3) temp.x--;
if (food.x == temp.x && food.y == temp.y) { ok = false; break; }
}
} while (!ok);
}
void startSnake() {
snakeLen = 3;
snakeDir = snakeWantDir = 2; // Start heading RIGHT
snakeScore = 0;
snakeStepMs = 150;
snakeLastStep = millis();
snakeHead.x = COLS / 2;
snakeHead.y = ROWS / 2;
// Set up trailing initial segments relative to previous parts
setSegmentDir(1, 3); // Segment 1 is LEFT relative to head
setSegmentDir(2, 3); // Segment 2 is LEFT relative to Segment 1
noweJedzenie();
snakeState = 1;
}
// ==========================================
// FLAPPY HELPER FUNCTIONS
// ==========================================
void startFlappy() {
birdY = 64.0;
birdVy = 0.0;
pipeX = FLAPPY_WIDTH;
PIPE_GAP_HEIGHT = 50;
pipeGapY = 64 - PIPE_GAP_HEIGHT / 2;
flappyScore = 0;
pipePassed = false;
flapped = false;
flappyState = 1;
PIPE_SPEED = 1.5;
}
// ==========================================
// SETUP
// ==========================================
void setup() {
Serial.begin(9600);
for (uint8_t i = 0; i < 4; i++) {
pinMode(PINS[i], INPUT_PULLUP);
btnCur[i] = (digitalRead(PINS[i]) == LOW);
btnPrev[i] = btnCur[i];
}
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true);
}
randomSeed(analogRead(A0));
display.clearDisplay();
}
// ==========================================
// MAIN LOOP & STATE MACHINE
// ==========================================
void loop() {
for (uint8_t i = 0; i < 4; i++) {
btnPrev[i] = btnCur[i];
btnCur[i] = (digitalRead(PINS[i]) == LOW);
}
display.clearDisplay();
display.setTextColor(WHITE);
if (gameMode == 0) runMasterMenu();
else if (gameMode == 1) runSnakeGame();
else if (gameMode == 2) runFlappyBird();
display.display();
delay(5);
}
// ==========================================
// MASTER MENU
// ==========================================
void runMasterMenu() {
display.setRotation(0);
display.setTextSize(2);
display.setCursor(10, 5);
display.print(F("CHOOSE:"));
display.setTextSize(1);
if (menuSelection == 1) {
display.fillRect(10, 28, 100, 11, WHITE);
display.setTextColor(BLACK);
} else {
display.setTextColor(WHITE);
}
display.setCursor(12, 30);
display.print(F("1. SNAKE"));
if (menuSelection == 2) {
display.fillRect(10, 43, 100, 11, WHITE);
display.setTextColor(BLACK);
} else {
display.setTextColor(WHITE);
}
display.setCursor(12, 45);
display.print(F("2. FLAP BALL"));
display.setTextColor(WHITE);
if (btnCur[0] && !btnPrev[0]) menuSelection = 1; // UP
if (btnCur[1] && !btnPrev[1]) menuSelection = 2; // DOWN
if (btnCur[3] && !btnPrev[3]) { // RIGHT selects
if (menuSelection == 1) {
snakeState = 0;
gameMode = 1;
} else {
flappyState = 0;
display.setRotation(1);
flapped = false;
attachInterrupt(digitalPinToInterrupt(BTN_UP), flapISR, FALLING);
gameMode = 2;
}
}
}
// ==========================================
// SNAKE GAME LOGIC (BIT-COMPRESSED)
// ==========================================
void runSnakeGame() {
// STATE 0: START SCREEN
if (snakeState == 0) {
display.setTextSize(2);
display.setCursor(35, 8);
display.print(F("SNAKE"));
display.setTextSize(1);
display.setCursor(15, 32);
display.print(F("Nacisnij: START"));
display.setCursor(15, 42);
display.print(F("LEWO: Do Menu"));
display.setCursor(22, 54);
display.print(F("REKORD: "));
display.print(snakeHiScore);
if ((btnCur[0] && !btnPrev[0]) || (btnCur[1] && !btnPrev[1]) || (btnCur[3] && !btnPrev[3])) {
startSnake();
}
if (btnCur[2] && !btnPrev[2]) gameMode = 0;
}
// STATE 1: PLAYING
else if (snakeState == 1) {
// New Input handling matching modified direction indices (0=UP, 1=DOWN, 2=RIGHT, 3=LEFT)
if (btnCur[0] && !btnPrev[0] && snakeWantDir != 1) snakeWantDir = 0; // UP
if (btnCur[1] && !btnPrev[1] && snakeWantDir != 0) snakeWantDir = 1; // DOWN
if (btnCur[2] && !btnPrev[2] && snakeWantDir != 2) snakeWantDir = 3; // LEFT
if (btnCur[3] && !btnPrev[3] && snakeWantDir != 3) snakeWantDir = 2; // RIGHT
if (millis() - snakeLastStep >= snakeStepMs) {
snakeLastStep = millis();
snakeDir = snakeWantDir;
// Predict next location of head
Pt nextHead = snakeHead;
if (snakeDir == 0) nextHead.y--;
if (snakeDir == 1) nextHead.y++;
if (snakeDir == 2) nextHead.x++;
if (snakeDir == 3) nextHead.x--;
// Wall hit check
bool dead = (nextHead.x < 0 || nextHead.x >= COLS || nextHead.y < 0 || nextHead.y >= ROWS);
// Self-collision verification via real-time decoding loop
if (!dead) {
Pt temp = snakeHead;
for (uint16_t i = 1; i < snakeLen; i++) {
uint8_t relDir = getSegmentDir(i);
if (relDir == 0) temp.y--;
else if (relDir == 1) temp.y++;
else if (relDir == 2) temp.x++;
else if (relDir == 3) temp.x--;
if (temp.x == nextHead.x && temp.y == nextHead.y) { dead = true; break; }
}
}
if (dead) {
if (snakeScore > snakeHiScore) snakeHiScore = snakeScore;
snakeState = 2;
snakeDeadAt = millis();
} else {
bool ate = (nextHead.x == food.x && nextHead.y == food.y);
if (ate) {
snakeLen++;
snakeScore++;
if (snakeScore % 5 == 0 && snakeStepMs > 60) snakeStepMs -= 10;
}
// Win Verification Check
if (snakeLen >= MAXLEN) {
if (snakeScore > snakeHiScore) snakeHiScore = snakeScore;
snakeState = 3; // Jump to Win State
} else {
// Bitshift relative segments along bitstream sequence
for (uint16_t i = snakeLen - 1; i > 1; i--) {
setSegmentDir(i, getSegmentDir(i - 1));
}
// Segment 1 relative placement is inversion of movement vector (snakeDir XOR 1)
setSegmentDir(1, snakeDir ^ 1);
snakeHead = nextHead; // Advance absolute head coordinates
if (ate) noweJedzenie();
}
}
}
// DRAW GRID ENGINE
display.fillRect(food.x*CELL, food.y*CELL, CELL, CELL, WHITE); // Food
display.fillRect(snakeHead.x*CELL, snakeHead.y*CELL, CELL, CELL, WHITE); // Head
Pt drawPt = snakeHead;
for (uint16_t i = 1; i < snakeLen; i++) {
uint8_t relDir = getSegmentDir(i);
if (relDir == 0) drawPt.y--;
else if (relDir == 1) drawPt.y++;
else if (relDir == 2) drawPt.x++;
else if (relDir == 3) drawPt.x--;
display.fillRect(drawPt.x*CELL, drawPt.y*CELL, CELL-1, CELL-1, WHITE); // Render segment
}
display.setTextSize(1);
display.setCursor(0, 0);
display.print(snakeScore);
}
// STATE 2: GAME OVER SCREEN
else if (snakeState == 2) {
display.setTextSize(2);
display.setCursor(10, 6);
display.print(F("GAME OVER"));
display.setTextSize(1);
display.setCursor(10, 28);
display.print(F("Wynik: ")); display.print(snakeScore);
display.setCursor(10, 40);
display.print(F("Rekord: ")); display.print(snakeHiScore);
if (millis() - snakeDeadAt > 1500) {
display.setCursor(10, 52);
display.print(F("Nacisnij przycisk"));
for (uint8_t i = 0; i < 4; i++) {
if (btnCur[i] && !btnPrev[i]) { snakeState = 0; break; }
}
}
}
// STATE 3: YOU WIN SCREEN
else if (snakeState == 3) {
display.setTextSize(2);
display.setCursor(15, 6);
display.print(F("YOU WIN!"));
display.setTextSize(1);
display.setCursor(10, 28);
display.print(F("Perfect Score!"));
display.setCursor(10, 40);
display.print(F("Total: ")); display.print(snakeScore);
display.setCursor(10, 52);
display.print(F("Press to Menu"));
for (uint8_t i = 0; i < 4; i++) {
if (btnCur[i] && !btnPrev[i]) { snakeState = 0; break; }
}
}
delay(15);
}
// ==========================================
// FLAPPY BIRD GAME LOGIC
// ==========================================
void runFlappyBird() {
if (flappyState == 0) {
display.setTextSize(2);
display.setCursor(8, 20);
display.print(F("FLAP"));
display.setCursor(8, 40);
display.print(F("BALL"));
display.setTextSize(1);
display.setCursor(6, 65);
display.print(F("UP: Play"));
display.setCursor(2, 80);
display.print(F("LEFT: Exit"));
display.setCursor(4, 100);
display.print(F("BEST: "));
display.print(flappyHiScore);
if (flapped) startFlappy();
if (digitalRead(BTN_LEFT) == LOW) {
detachInterrupt(digitalPinToInterrupt(BTN_UP));
flapped = false;
gameMode = 0;
}
}
else if (flappyState == 1) {
if (flapped) {
birdVy = FLAP_IMPULSE;
flapped = false;
}
birdVy += GRAVITY;
birdY += birdVy;
pipeX -= PIPE_SPEED;
if (pipeX < -PIPE_WIDTH) {
pipeX = FLAPPY_WIDTH;
pipeGapY = random(10, FLAPPY_HEIGHT - 10 - PIPE_GAP_HEIGHT);
pipePassed = false;
}
if (pipeX + PIPE_WIDTH < BIRD_X && !pipePassed) {
flappyScore++;
pipePassed = true;
PIPE_SPEED = 1.5 + (static_cast<float>(flappyScore) / 64.0);
PIPE_GAP_HEIGHT = flappyScore > 240 ? 24 : 48 - flappyScore / 20;
}
bool hitGroundOrCeiling = (birdY - BIRD_RADIUS <= 0 || birdY + BIRD_RADIUS >= FLAPPY_HEIGHT);
bool hitPipe = false;
if (BIRD_X + BIRD_RADIUS > pipeX && BIRD_X - BIRD_RADIUS < pipeX + PIPE_WIDTH) {
if (birdY - BIRD_RADIUS < pipeGapY || birdY + BIRD_RADIUS > pipeGapY + PIPE_GAP_HEIGHT) {
hitPipe = true;
}
}
if (hitGroundOrCeiling || hitPipe) {
if (flappyScore > flappyHiScore) flappyHiScore = flappyScore;
flappyState = 2;
flappyDeadAt = millis();
flapped = false;
}
display.fillCircle(BIRD_X, (int)birdY, BIRD_RADIUS, WHITE);
display.fillRect((int)pipeX, 0, PIPE_WIDTH, pipeGapY, WHITE);
display.fillRect((int)pipeX, pipeGapY + PIPE_GAP_HEIGHT, PIPE_WIDTH, FLAPPY_HEIGHT - (pipeGapY + PIPE_GAP_HEIGHT), WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.print(flappyScore);
display.setCursor(0,8);
display.print(PIPE_SPEED);
display.setCursor(0,16);
display.print(PIPE_GAP_HEIGHT);
}
else if (flappyState == 2) {
display.setTextSize(2);
display.setCursor(8, 20);
display.print(F("GAME"));
display.setCursor(8, 40);
display.print(F("OVER"));
display.setTextSize(1);
display.setCursor(8, 70);
display.print(F("Score:"));
display.print(flappyScore);
display.setCursor(8, 85);
display.print(F("Best: "));
display.print(flappyHiScore);
if (millis() - flappyDeadAt > 1000) {
if (flappyScore >= flappyHiScore && flappyScore > 0) {
display.setCursor(2, 105);
display.print(F("NEW RECORD"));
} else {
display.setCursor(8, 105);
display.print(F("UP: Play"));
display.setCursor(3, 115);
display.print(F("LEFT: Exit"));
}
if (flapped) {
flappyState = 0;
flapped = false;
}
if (digitalRead(BTN_LEFT) == LOW) {
detachInterrupt(digitalPinToInterrupt(BTN_UP));
flapped = false;
gameMode = 0;
}
} else {
flapped = false;
}
}
// delay(5);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment