#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// OLED settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Button pins
const int btnUp = 32;
const int btnDown = 33;
const int btnLeft = 25;
const int btnRight = 26;
const int btnCenter = 27;
// Joystick pins
#define joyXPin 34 // Replace with actual X-axis analog pin
#define joyYPin 35 // Replace with actual Y-axis analog pin
const int joySW = 14; // Optional (joystick button)
int selectedGame = 0;
const int totalGames = 7;
// Functions declarations
void drawMenu();
void runSelectedGame();
void ticTacToe();
void snakeGame();
void tetrisGame();
void flappyBird();
void carRacing();
void asteroidShooting();
void pongGame();
void setup() {
Serial.begin(115200);
// OLED init
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
// Buttons init
pinMode(btnUp, INPUT_PULLUP);
pinMode(btnDown, INPUT_PULLUP);
pinMode(btnLeft, INPUT_PULLUP);
pinMode(btnRight, INPUT_PULLUP);
pinMode(btnCenter, INPUT_PULLUP);
pinMode(joySW, INPUT_PULLUP); // Optional
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
drawMenu();
}
void loop() {
// MENU navigation
if(digitalRead(btnUp) == LOW) {
selectedGame--;
if(selectedGame < 0) selectedGame = totalGames - 1;
drawMenu();
delay(200);
}
if(digitalRead(btnDown) == LOW) {
selectedGame++;
if(selectedGame >= totalGames) selectedGame = 0;
drawMenu();
delay(200);
}
if(digitalRead(btnCenter) == LOW) {
runSelectedGame();
}
}
void drawMenu() {
display.clearDisplay();
display.setCursor(0,0);
display.println("Select Game:");
switch(selectedGame) {
case 0: display.println("> Tic-Tac-Toe"); break;
case 1: display.println("> Snake"); break;
case 2: display.println("> Tetris"); break;
case 3: display.println("> Flappy Bird"); break;
case 4: display.println("> Car Racing"); break;
case 5: display.println("> Asteroid Shooting"); break;
case 6: display.println("> Pong"); break;
}
display.display();
}
void runSelectedGame() {
switch(selectedGame) {
case 0: ticTacToe(); break;
case 1: snakeGame(); break;
case 2: tetrisGame(); break;
case 3: flappyBird(); break;
case 4: carRacing(); break;
case 5: asteroidShooting(); break;
case 6: pongGame(); break;
}
}
// --- Game functions stubs ---
void ticTacToe() {
int board[3][3] = {0}; // 0=empty, 1=X, 2=O
int curX = 0, curY = 0;
int player = 1;
while (true) {
// Input
if (digitalRead(btnUp) == LOW && curY > 0) curY--;
if (digitalRead(btnDown) == LOW && curY < 2) curY++;
if (digitalRead(btnLeft) == LOW && curX > 0) curX--;
if (digitalRead(btnRight) == LOW && curX < 2) curX++;
if (digitalRead(btnCenter) == LOW) {
if (board[curX][curY] == 0) {
board[curX][curY] = player;
player = (player == 1) ? 2 : 1;
}
}
// Draw
display.clearDisplay();
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
int px = x * 40 + 5;
int py = y * 20 + 5;
if (board[x][y] == 1) display.drawChar(px, py, 'X', SSD1306_WHITE, BLACK, 2);
if (board[x][y] == 2) display.drawChar(px, py, 'O', SSD1306_WHITE, BLACK, 2);
}
}
// Cursor box
display.drawRect(curX * 40 + 2, curY * 20 + 2, 36, 16, SSD1306_WHITE);
display.display();
delay(150);
// Win check
for (int i = 0; i < 3; i++) {
if (board[i][0] && board[i][0] == board[i][1] && board[i][1] == board[i][2]) goto gameOver;
if (board[0][i] && board[0][i] == board[1][i] && board[1][i] == board[2][i]) goto gameOver;
}
if (board[0][0] && board[0][0] == board[1][1] && board[1][1] == board[2][2]) goto gameOver;
if (board[2][0] && board[2][0] == board[1][1] && board[1][1] == board[0][2]) goto gameOver;
}
gameOver:
display.clearDisplay();
display.setCursor(10, 25);
display.print("Player ");
display.print((player == 1) ? 2 : 1);
display.println(" Wins!");
display.display();
delay(2000);
drawMenu();
}
void snakeGame() {
const int gridSize = 4;
const int width = SCREEN_WIDTH / gridSize;
const int height = SCREEN_HEIGHT / gridSize;
int snakeX[128], snakeY[64];
int snakeLength = 3;
int foodX = random(width), foodY = random(height);
int dirX = 1, dirY = 0;
for (int i = 0; i < snakeLength; i++) {
snakeX[i] = width/2 - i;
snakeY[i] = height/2;
}
while (true) {
// Read buttons
if (digitalRead(btnUp) == LOW && dirY != 1) { dirX = 0; dirY = -1; }
if (digitalRead(btnDown) == LOW && dirY != -1) { dirX = 0; dirY = 1; }
if (digitalRead(btnLeft) == LOW && dirX != 1) { dirX = -1; dirY = 0; }
if (digitalRead(btnRight) == LOW && dirX != -1) { dirX = 1; dirY = 0; }
// Move Snake
for (int i = snakeLength - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
snakeX[0] += dirX;
snakeY[0] += dirY;
// Check wall collision
if (snakeX[0] < 0 || snakeY[0] < 0 || snakeX[0] >= width || snakeY[0] >= height)
break;
// Check self collision
for (int i = 1; i < snakeLength; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i])
goto gameOver;
}
// Check food
if (snakeX[0] == foodX && snakeY[0] == foodY) {
if (snakeLength < 128) snakeLength++;
foodX = random(width);
foodY = random(height);
}
// Draw
display.clearDisplay();
// Draw snake
for (int i = 0; i < snakeLength; i++) {
display.fillRect(snakeX[i]*gridSize, snakeY[i]*gridSize, gridSize, gridSize, SSD1306_WHITE);
}
// Draw food
display.fillRect(foodX*gridSize, foodY*gridSize, gridSize, gridSize, SSD1306_INVERSE);
display.display();
delay(100);
}
gameOver:
display.clearDisplay();
display.setCursor(10, 20);
display.setTextSize(1);
display.println("Game Over!");
display.display();
delay(1500);
drawMenu(); // Return to menu
}
// Simplified Tetris piece (1 shape only for demo)
void tetrisGame() {
const int gridW = 10, gridH = 16;
const int cellW = 5, cellH = 4;
const int gridOffsetX = 10; // Shift grid to center horizontally
const int gridOffsetY = 0;
int grid[gridW][gridH] = {0};
// Tetromino shapes (4x4)
const byte shapes[7][4][4] = {
{ // I
{0, 0, 0, 0},
{1, 1, 1, 1},
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{ // O
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{ // T
{0, 1, 0, 0},
{1, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{ // S
{0, 1, 1, 0},
{1, 1, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{ // Z
{1, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{ // J
{1, 0, 0, 0},
{1, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{ // L
{0, 0, 1, 0},
{1, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
}
};
int currentShape = random(0, 7);
int px = 3, py = 0;
int score = 0;
static int highScore = 0;
unsigned long lastFall = millis();
int fallSpeed = 500;
auto rotateShape = [](const byte shape[4][4], byte rotated[4][4]) {
for (int y = 0; y < 4; y++)
for (int x = 0; x < 4; x++)
rotated[x][3 - y] = shape[y][x];
};
auto drawGrid = [&]() {
display.clearDisplay();
// Grid boundary lines
int gridPixelW = gridW * cellW;
int gridPixelH = gridH * cellH;
display.drawRect(gridOffsetX - 1, gridOffsetY - 1, gridPixelW + 2, gridPixelH + 2, SSD1306_WHITE);
// Draw static blocks
for (int y = 0; y < gridH; y++) {
for (int x = 0; x < gridW; x++) {
if (grid[x][y])
display.fillRect(gridOffsetX + x * cellW, gridOffsetY + y * cellH, cellW - 1, cellH - 1, SSD1306_WHITE);
}
}
// Draw current shape
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (shapes[currentShape][i][j]) {
int x = px + j;
int y = py + i;
if (x >= 0 && x < gridW && y >= 0 && y < gridH)
display.fillRect(gridOffsetX + x * cellW, gridOffsetY + y * cellH, cellW - 1, cellH - 1, SSD1306_WHITE);
}
}
}
// Score display on top-right
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(95, 0);
display.print("S:");
display.println(score);
display.setCursor(95, 10);
display.print("H:");
display.println(highScore);
display.display();
};
auto canMove = [&](int dx, int dy, const byte shape[4][4]) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (shape[i][j]) {
int x = px + j + dx;
int y = py + i + dy;
if (x < 0 || x >= gridW || y >= gridH) return false;
if (y >= 0 && grid[x][y]) return false;
}
}
}
return true;
};
auto mergeToGrid = [&]() {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (shapes[currentShape][i][j]) {
int x = px + j;
int y = py + i;
if (x >= 0 && x < gridW && y >= 0 && y < gridH)
grid[x][y] = 1;
}
}
}
};
auto clearLines = [&]() {
for (int y = gridH - 1; y >= 0; y--) {
bool full = true;
for (int x = 0; x < gridW; x++) {
if (!grid[x][y]) {
full = false;
break;
}
}
if (full) {
for (int ty = y; ty > 0; ty--)
for (int x = 0; x < gridW; x++)
grid[x][ty] = grid[x][ty - 1];
for (int x = 0; x < gridW; x++) grid[x][0] = 0;
score += 10;
y++; // recheck same line
}
}
};
while (true) {
// Input
if (digitalRead(btnLeft) == LOW && canMove(-1, 0, shapes[currentShape])) {
px--; delay(100);
}
if (digitalRead(btnRight) == LOW && canMove(1, 0, shapes[currentShape])) {
px++; delay(100);
}
if (digitalRead(btnDown) == LOW && canMove(0, 1, shapes[currentShape])) {
py++; delay(50);
}
if (digitalRead(btnCenter) == LOW) {
byte rotated[4][4];
rotateShape(shapes[currentShape], rotated);
if (canMove(0, 0, rotated)) {
memcpy((void*)shapes[currentShape], rotated, sizeof(rotated));
delay(150);
}
}
// Auto fall
if (millis() - lastFall > fallSpeed) {
if (canMove(0, 1, shapes[currentShape])) {
py++;
} else {
mergeToGrid();
clearLines();
currentShape = random(0, 7);
px = 3; py = 0;
if (!canMove(0, 0, shapes[currentShape])) {
if (score > highScore) highScore = score;
display.clearDisplay();
display.setCursor(20, 20);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.println("GAME OVER");
display.setCursor(15, 40);
display.print("Score: "); display.println(score);
display.display();
delay(2000);
return;
}
}
lastFall = millis();
}
drawGrid();
}
}
void flappyBird() {
const int birdX = 20;
float birdY = SCREEN_HEIGHT / 2;
float velocity = 0;
float gravity = 0.5;
float flapStrength = -4;
int pipeX = SCREEN_WIDTH;
int pipeGapY = random(10, SCREEN_HEIGHT - 30);
const int pipeWidth = 10;
const int pipeGap = 25;
while (true) {
// Input
if (digitalRead(btnCenter) == LOW) velocity = flapStrength;
// Physics
velocity += gravity;
birdY += velocity;
// Pipe movement
pipeX -= 2;
if (pipeX < -pipeWidth) {
pipeX = SCREEN_WIDTH;
pipeGapY = random(10, SCREEN_HEIGHT - pipeGap - 10);
}
// Collision
if (birdY < 0 || birdY > SCREEN_HEIGHT - 1 ||
(birdX + 3 > pipeX && birdX < pipeX + pipeWidth &&
(birdY < pipeGapY || birdY > pipeGapY + pipeGap))) {
break;
}
// Draw
display.clearDisplay();
display.fillRect(birdX, (int)birdY, 3, 3, SSD1306_WHITE);
display.fillRect(pipeX, 0, pipeWidth, pipeGapY, SSD1306_WHITE);
display.fillRect(pipeX, pipeGapY + pipeGap, pipeWidth, SCREEN_HEIGHT - pipeGapY - pipeGap, SSD1306_WHITE);
display.display();
delay(30);
}
// Game Over
display.clearDisplay();
display.setCursor(10, 20);
display.println("Game Over");
display.display();
delay(1500);
drawMenu();
}
void carRacing() {
int playerX = SCREEN_WIDTH / 2;
int carWidth = 10;
int carHeight = 8;
int obsX = random(0, SCREEN_WIDTH - carWidth);
int obsY = 0;
while (true) {
// Move player
int joyX = analogRead(joyXPin);
if (joyX < 1000) playerX -= 2;
if (joyX > 3000) playerX += 2;
playerX = constrain(playerX, 0, SCREEN_WIDTH - carWidth);
// Move obstacle
obsY += 2;
if (obsY > SCREEN_HEIGHT) {
obsY = 0;
obsX = random(0, SCREEN_WIDTH - carWidth);
}
// Collision
if (obsY + carHeight > SCREEN_HEIGHT - carHeight &&
abs(obsX - playerX) < carWidth) {
break;
}
// Draw
display.clearDisplay();
display.fillRect(playerX, SCREEN_HEIGHT - carHeight, carWidth, carHeight, SSD1306_WHITE);
display.fillRect(obsX, obsY, carWidth, carHeight, SSD1306_WHITE);
display.display();
delay(30);
}
// Game over
display.clearDisplay();
display.setCursor(10, 20);
display.println("Game Over");
display.display();
delay(1500);
drawMenu();
}
#define MAX_BULLETS 5
#define MAX_ASTEROIDS 3
struct Bullet {
int x, y;
bool active;
};
struct Asteroid {
int x, y;
bool active;
};
void asteroidShooting() {
int shipX = SCREEN_WIDTH / 2;
int shipY = SCREEN_HEIGHT - 5;
Bullet bullets[MAX_BULLETS];
Asteroid asteroids[MAX_ASTEROIDS];
for (int i = 0; i < MAX_ASTEROIDS; i++) {
asteroids[i].x = random(0, SCREEN_WIDTH - 4);
asteroids[i].y = random(-20, 0);
asteroids[i].active = true;
}
while (true) {
// Move ship
int joyX = analogRead(joyXPin);
if (joyX < 1000) shipX -= 2;
if (joyX > 3000) shipX += 2;
shipX = constrain(shipX, 0, SCREEN_WIDTH - 5);
// Fire
if (digitalRead(btnCenter) == LOW) {
for (int i = 0; i < MAX_BULLETS; i++) {
if (!bullets[i].active) {
bullets[i] = {shipX + 2, shipY - 2, true};
break;
}
}
}
// Move bullets
for (int i = 0; i < MAX_BULLETS; i++) {
if (bullets[i].active) {
bullets[i].y -= 3;
if (bullets[i].y < 0) bullets[i].active = false;
}
}
// Move asteroids
for (int i = 0; i < MAX_ASTEROIDS; i++) {
if (asteroids[i].active) {
asteroids[i].y += 2;
if (asteroids[i].y > SCREEN_HEIGHT) {
asteroids[i].y = 0;
asteroids[i].x = random(0, SCREEN_WIDTH - 4);
}
// Check collision with ship
if (asteroids[i].y > shipY - 3 &&
abs(asteroids[i].x - shipX) < 6) {
goto gameOver;
}
// Bullet collision
for (int j = 0; j < MAX_BULLETS; j++) {
if (bullets[j].active &&
abs(bullets[j].x - asteroids[i].x) < 5 &&
abs(bullets[j].y - asteroids[i].y) < 5) {
bullets[j].active = false;
asteroids[i].y = 0;
asteroids[i].x = random(0, SCREEN_WIDTH - 4);
}
}
}
}
// Draw
display.clearDisplay();
display.fillRect(shipX, shipY, 5, 3, SSD1306_WHITE);
for (int i = 0; i < MAX_BULLETS; i++)
if (bullets[i].active)
display.drawPixel(bullets[i].x, bullets[i].y, SSD1306_WHITE);
for (int i = 0; i < MAX_ASTEROIDS; i++)
display.fillCircle(asteroids[i].x, asteroids[i].y, 2, SSD1306_WHITE);
display.display();
delay(30);
}
gameOver:
display.clearDisplay();
display.setCursor(10, 20);
display.println("Ship Destroyed!");
display.display();
delay(1500);
drawMenu();
}
void pongGame() {
int playerY = SCREEN_HEIGHT / 2 - 10;
int aiY = SCREEN_HEIGHT / 2 - 10;
int ballX = SCREEN_WIDTH / 2, ballY = SCREEN_HEIGHT / 2;
int ballDX = -2, ballDY = 1;
while (true) {
// Move player
int joyY = analogRead(joyYPin);
if (joyY < 1000) playerY -= 2;
if (joyY > 3000) playerY += 2;
playerY = constrain(playerY, 0, SCREEN_HEIGHT - 20);
// Move AI
if (aiY + 10 < ballY) aiY++;
if (aiY + 10 > ballY) aiY--;
aiY = constrain(aiY, 0, SCREEN_HEIGHT - 20);
// Move ball
ballX += ballDX;
ballY += ballDY;
if (ballY <= 0 || ballY >= SCREEN_HEIGHT - 1) ballDY = -ballDY;
// Paddle collision
if (ballX <= 3 && ballY >= playerY && ballY <= playerY + 20) ballDX = -ballDX;
if (ballX >= SCREEN_WIDTH - 4 && ballY >= aiY && ballY <= aiY + 20) ballDX = -ballDX;
if (ballX <= 0 || ballX >= SCREEN_WIDTH) break;
// Draw
display.clearDisplay();
display.fillRect(0, playerY, 3, 20, SSD1306_WHITE);
display.fillRect(SCREEN_WIDTH - 3, aiY, 3, 20, SSD1306_WHITE);
display.fillCircle(ballX, ballY, 2, SSD1306_WHITE);
display.display();
delay(30);
}
display.clearDisplay();
display.setCursor(10, 20);
display.println("Game Over!");
display.display();
delay(1500);
drawMenu();
}