/*
A simple Pong game, derived by Etienne,
from https://notabug.org/Maverick/WokwiPong,
based on Arduino Pong by eholk
https://github.com/eholk/Arduino-Pong
*/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define MIN(a,b) (((a)<(b)) ? (a) : (b))
#define MAX(a,b) (((a)>(b)) ? (a) : (b))
#define UP_BUTTON 2
#define DOWN_BUTTON 3
const unsigned long PADDLE_RATE = 64;
const unsigned long BALL_RATE = 16;
const uint8_t PADDLE_HEIGHT = 12;
const uint8_t SCORE_LIMIT = 9;
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
const uint8_t PLAYER_X = 115;
const uint8_t MCU_X = 12;
typedef struct {
bool game_over, win;
uint8_t player_score, mcu_score;
uint8_t ball_x = 53, ball_y = 26;
uint8_t ball_dir_x = 1, ball_dir_y = 1;
unsigned long ball_update;
unsigned long paddle_update;
uint8_t mcu_y = 16;
uint8_t player_y = 16;
} pong_game_t;
pong_game_t pong;
void setup() {
pong.game_over = false;
pong.win = false;
pong.player_score = 0;
pong.mcu_score = 0;
pong.ball_x = 53;
pong.ball_y = 26;
pong.ball_dir_x = 1;
pong.ball_dir_y = 1;
pong.mcu_y = 16;
pong.player_y = 16;
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
// Display the splash screen (we're legally required to do so)
display.display();
unsigned long start = millis();
pinMode(UP_BUTTON, INPUT_PULLUP);
pinMode(DOWN_BUTTON, INPUT_PULLUP);
display.clearDisplay();
drawCourt(pong);
while(millis() - start < 2000);
display.display();
ball_update = millis();
paddle_update = ball_update;
}
void loop() {
bool update_needed = false;
static bool up_state = false;
static bool down_state = false;
up_state |= (digitalRead(UP_BUTTON) == LOW);
down_state |= (digitalRead(DOWN_BUTTON) == LOW);
gameUpdate();
}
void gameUpdate() {
unsigned long time_ms = millis();
bool redraw = false;
if(time_ms > pong.ball_update_ms) {
pong.ball_update += BALL_RATE;
uint8_t new_x = pong.ball_x + pong.ball_dir_x;
uint8_t new_y = pong.ball_y + pong.ball_dir_y;
// Check if we hit the vertical walls
if(new_x == 0 || new_x == 127) {
ball_dir_x = -ball_dir_x;
new_x += ball_dir_x + ball_dir_x;
if (new_x < 64) {
player_scoreTone();
pong.player_score++;
} else {
mcu_scoreTone();
pong.mcu_score++;
}
if (pong.player_score == SCORE_LIMIT || pong.mcu_score == SCORE_LIMIT) {
win = player_score > mcu_score;
game_over = true;
}
}
// Check if we hit the horizontal walls.
if(new_y == 0 || new_y == 53) {
wallTone();
pong.ball_dir_y = -pong.ball_dir_y;
new_y += pong.ball_dir_y + pong.ball_dir_y;
}
// Check if we hit the CPU paddle
if(new_x == MCU_X && pong.mcu_y <= new_y && new_y <= pong.mcu_y + PADDLE_HEIGHT) {
mcuPaddleTone();
pong.ball_dir_x = -pong.ball_dir_x;
new_x += pong.ball_dir_x + pong.ball_dir_x;
}
// Check if we hit the player paddle
if(new_x == PLAYER_X && new_y >= pong.player_y && new_y <= pong.player_y + PADDLE_HEIGHT) {
playerPaddleTone();
pong.ball_dir_x = -pong.ball_dir_x;
new_x += pong.ball_dir_x + pong.ball_dir_x;
}
// Erase ball at old position, display at new position
display.drawPixel(pong.ball_x, pong.ball_y, BLACK);
display.drawPixel(new_x, new_y, WHITE);
pong.ball_x = new_x;
pong.ball_y = new_y;
redraw = true;
}
if(time_ms > pong.paddle_update_ms) {
pong.paddle_update_ms += PADDLE_RATE;
// Erase CPU paddle, update position, redraw
display.drawFastVLine(MCU_X, pong.mcu_y, PADDLE_HEIGHT, BLACK);
const uint8_t half_paddle = PADDLE_HEIGHT >> 1;
if(mcu_y + half_paddle > ball_y) {
int8_t dir = ball_x > MCU_X ? -1 : 1;
mcu_y += dir;
}
if(mcu_y + half_paddle < ball_y)
{
int8_t dir = ball_x > MCU_X ? 1 : -1;
mcu_y += dir;
}
if(mcu_y < 1)
{
mcu_y = 1;
}
if(mcu_y + PADDLE_HEIGHT > 53)
{
mcu_y = 53 - PADDLE_HEIGHT;
}
// Player paddle
display.drawFastVLine(MCU_X, mcu_y, PADDLE_HEIGHT, WHITE);
// Erase player paddle, update position, redraw
display.drawFastVLine(PLAYER_X, player_y, PADDLE_HEIGHT, BLACK);
ping.player_y = MAX(1, MIN(53-PADDLE_HEIGHT,
ping.player_y + (btn_up ? 1 : 0) + (btn_down ? -1 : 0)
));
display.drawFastVLine(PLAYER_X, player_y, PADDLE_HEIGHT, WHITE);
redraw = true;
}
if(update_needed)
{
if (game_over)
{
const char* text = win ? "YOU WIN!!" : "YOU LOSE!";
display.clearDisplay();
display.setCursor(40, 28);
display.print(text);
display.display();
delay(5000);
display.clearDisplay();
ball_x = 53;
ball_y = 26;
ball_dir_x = 1;
ball_dir_y = 1;
mcu_y = 16;
player_y = 16;
mcu_score = 0;
player_score = 0;
game_over = false;
drawCourt();
}
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 56);
display.print(mcu_score);
display.setCursor(122, 56);
display.print(player_score);
display.display();
}
}
void playerPaddleTone()
{
tone(11, 250, 25);
delay(25);
noTone(11);
}
void mcuPaddleTone()
{
tone(11, 225, 25);
delay(25);
noTone(11);
}
void wallTone()
{
tone(11, 200, 25);
delay(25);
noTone(11);
}
void player_scoreTone()
{
tone(11, 200, 25);
delay(50);
noTone(11);
delay(25);
tone(11, 250, 25);
delay(25);
noTone(11);
}
void mcu_scoreTone()
{
tone(11, 250, 25);
delay(25);
noTone(11);
delay(25);
tone(11, 200, 25);
delay(25);
noTone(11);
}
void drawCourt()
{
display.drawRect(0, 0, 128, 54, WHITE);
}
void gameOver() {
}