Files
arduino_pong/arduino_pong.ino

217 lines
6.0 KiB
Arduino
Raw Normal View History

#include "Arduino_LED_Matrix.h"
2026-03-15 21:14:04 +01:00
#include "src/config.h"
2026-03-21 13:52:54 +01:00
#include "src/menu.h"
2026-03-18 19:46:52 +01:00
#include "src/renderer.h"
#include "src/engine.h"
2026-03-17 23:25:30 +01:00
#include "src/paddle.h"
#include "src/ball.h"
2026-03-15 20:29:56 +01:00
// initial pong frame matrix
2026-03-15 17:07:03 +01:00
byte frame[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
2026-03-15 20:29:56 +01:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
2026-03-18 19:46:52 +01:00
ArduinoLEDMatrix matrix;
bool need_refresh= true;
2026-03-18 18:12:38 +01:00
uint8_t hits= 0;
long exec_t2= millis();
enum game_statuses : uint8_t {
2026-03-19 22:23:29 +01:00
MENU,
MENU_BOT_SKILLS,
TIMER,
RUN,
SCORE,
GAMEOVER,
WAIT,
};
2026-03-21 10:24:15 +01:00
game_statuses game_status= MENU;
2026-03-17 23:25:30 +01:00
Ball ball(4, 6);
2026-03-21 10:24:15 +01:00
Paddle* p1= nullptr;
Paddle* p2= nullptr;
HumanPaddle human_pad1(1, P1_BTN_UP, P1_BTN_BOTTOM);
HumanPaddle human_pad2(4, P2_BTN_UP, P2_BTN_BOTTOM);
BotPaddle bot_pad1(1, 0);
BotPaddle bot_pad2(4, MATRIX_WIDTH-1);
2026-03-21 13:52:54 +01:00
Menu menu;
2026-03-21 10:24:15 +01:00
2026-03-21 13:52:54 +01:00
// uint8_t current_gmode_idx= 0;
// bool update_menu= true;
// bool mode_selected= false;
// uint8_t current_bot_menu_idx= 0;
// bool update_menu_bot_skills= true;
2026-03-21 10:24:15 +01:00
Engine engine(ball, INITIAL_BALL_DELAY);
Renderer renderer(ball, frame, matrix);
2026-03-15 20:29:56 +01:00
void setup() {
Serial.begin(9600);
2026-03-15 20:29:56 +01:00
// start LED matrix
matrix.begin();
pinMode(P1_BTN_UP, INPUT_PULLUP);
pinMode(P1_BTN_BOTTOM, INPUT_PULLUP);
pinMode(P2_BTN_UP, INPUT_PULLUP);
pinMode(P2_BTN_BOTTOM, INPUT_PULLUP);
2026-03-15 20:29:56 +01:00
randomSeed(millis());
}
void loop() {
long exec_t1= millis();
switch (game_status) {
2026-03-17 23:25:30 +01:00
case MENU: {
2026-03-21 13:52:54 +01:00
// switch modes
if (digitalRead(P2_BTN_BOTTOM) == LOW) {
menu.next_mode();
const byte (*current_gmode)[12]= frame_gmodes[menu.get_mode()];
matrix.loadPixels((uint8_t*)current_gmode, MATRIX_HEIGHT * MATRIX_WIDTH);
delay(300);
}
2026-03-21 13:52:54 +01:00
else if (digitalRead(P2_BTN_UP) == LOW) {
menu.prev_mode();
const byte (*current_gmode)[12]= frame_gmodes[menu.get_mode()];
matrix.loadPixels((uint8_t*)current_gmode, MATRIX_HEIGHT * MATRIX_WIDTH);
delay(300);
}
2026-03-19 22:23:29 +01:00
// 1. P vs P
2026-03-21 13:52:54 +01:00
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && menu.number_of_bots() == 0) {
2026-03-21 10:24:15 +01:00
p1= &human_pad1;
p2= &human_pad2;
2026-03-21 13:52:54 +01:00
engine.set_players(p1, p2);
renderer.set_players(p1, p2);
game_status= TIMER;
2026-03-21 10:24:15 +01:00
}
2026-03-19 22:23:29 +01:00
// 2. P vs CPU
2026-03-21 13:52:54 +01:00
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && menu.number_of_bots() == 1) {
2026-03-21 10:24:15 +01:00
p1= &human_pad1;
p2= &bot_pad2;
2026-03-21 13:52:54 +01:00
engine.set_players(p1, p2);
renderer.set_players(p1, p2);
game_status= MENU_BOT_SKILLS;
delay(300); // avoid accidental double click for next menu
2026-03-21 10:24:15 +01:00
}
2026-03-19 22:23:29 +01:00
// 3. CPU vs CPU
2026-03-21 13:52:54 +01:00
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && menu.number_of_bots() == 2) {
2026-03-21 10:24:15 +01:00
p1= &bot_pad1;
p2= &bot_pad2;
2026-03-21 13:52:54 +01:00
engine.set_players(p1, p2);
renderer.set_players(p1, p2);
game_status= MENU_BOT_SKILLS;
delay(300); // avoid accidental double click for next menu
}
2026-03-21 13:52:54 +01:00
else {
const byte (*current_gmode)[12]= frame_gmodes[menu.get_mode()];
matrix.loadPixels((uint8_t*)current_gmode, MATRIX_HEIGHT * MATRIX_WIDTH);
}
2026-03-21 13:52:54 +01:00
break;
}
case MENU_BOT_SKILLS: {
2026-03-21 13:52:54 +01:00
// switch difficulty level
if (digitalRead(P2_BTN_BOTTOM) == LOW) {
menu.increase_skills();
const byte (*current_skill_frame)[12]= frame_bot_skills[menu.get_skill()];
matrix.loadPixels((uint8_t*)current_skill_frame, MATRIX_HEIGHT * MATRIX_WIDTH);
delay(300);
}
2026-03-21 13:52:54 +01:00
else if (digitalRead(P2_BTN_UP) == LOW) {
menu.decrease_skills();
const byte (*current_skill_frame)[12]= frame_bot_skills[menu.get_skill()];
matrix.loadPixels((uint8_t*)current_skill_frame, MATRIX_HEIGHT * MATRIX_WIDTH);
delay(300);
}
2026-03-21 13:52:54 +01:00
// choose difficulty level
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW) {
2026-03-21 13:52:54 +01:00
if (!p1 -> is_human()) p1 -> set_skills(menu.get_skill() + 1);
if (!p2 -> is_human()) p2 -> set_skills(menu.get_skill() + 1);
2026-03-21 10:24:15 +01:00
game_status= TIMER;
2026-03-21 13:52:54 +01:00
}
2026-03-21 13:52:54 +01:00
else {
const byte (*current_skill_frame)[12]= frame_bot_skills[menu.get_skill()];
matrix.loadPixels((uint8_t*)current_skill_frame, MATRIX_HEIGHT * MATRIX_WIDTH);
2026-03-21 10:24:15 +01:00
}
2026-03-19 22:23:29 +01:00
break;
}
2026-03-19 22:23:29 +01:00
case TIMER:
for (int i = START_TIMER; i >= 0; i--) {
2026-03-18 19:46:52 +01:00
renderer.render_timer(i);
2026-03-18 18:24:52 +01:00
delay(1000);
}
game_status= RUN;
// delay the first ball movement
exec_t2= millis() + FIRST_START_BALL_DELAY;
break;
case RUN:
2026-03-19 22:23:29 +01:00
need_refresh= engine.control_players();
2026-03-18 19:00:50 +01:00
if (exec_t1 - exec_t2 > engine.ball_movement_delay()) {
2026-03-18 18:12:38 +01:00
engine.run();
if (engine.get_event() == P1SCORE || engine.get_event() == P2SCORE)
game_status= SCORE;
2026-03-18 19:00:50 +01:00
exec_t2= exec_t1;
2026-03-18 18:12:38 +01:00
need_refresh= true;
}
// rerender matrix only if something is changed
if (need_refresh) {
2026-03-18 19:46:52 +01:00
renderer.render_matrix();
2026-03-17 23:25:30 +01:00
need_refresh= false;
}
break;
case SCORE:
delay(300);
2026-03-18 19:46:52 +01:00
renderer.render_score();
engine.restart_ball();
2026-03-18 19:00:50 +01:00
delay(1000);
2026-03-21 10:24:15 +01:00
if (p1 -> get_score() >= MAX_POINTS || p2 -> get_score() >= MAX_POINTS)
game_status= GAMEOVER;
2026-03-18 19:00:50 +01:00
else {
game_status= RUN;
// before move again the ball wait a second
2026-03-18 19:46:52 +01:00
renderer.render_matrix();
exec_t2= millis() + FIRST_START_BALL_DELAY;
2026-03-18 19:00:50 +01:00
}
break;
case GAMEOVER:
2026-03-18 19:46:52 +01:00
renderer.render_winner();
game_status= WAIT;
break;
case WAIT:
// keep showing the winner waiting for a restart
// restart game once one button is pressed
2026-03-19 22:24:23 +01:00
if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW ||
digitalRead(P2_BTN_UP) == LOW || digitalRead(P2_BTN_BOTTOM) == LOW) {
2026-03-17 23:25:30 +01:00
engine.reset();
2026-03-21 13:52:54 +01:00
game_status= MENU;
delay(300);
}
break;
}
2026-03-18 18:24:52 +01:00
delay(50);
2026-03-14 21:35:19 +01:00
}