2026-03-14 20:22:06 +01:00
|
|
|
#include "Arduino_LED_Matrix.h"
|
|
|
|
|
|
2026-03-15 21:14:04 +01:00
|
|
|
#include "src/config.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-14 20:22:06 +01:00
|
|
|
|
|
|
|
|
|
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] = {
|
2026-03-14 20:22:06 +01:00
|
|
|
{ 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 },
|
2026-03-14 20:22:06 +01:00
|
|
|
{ 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;
|
|
|
|
|
|
2026-03-19 18:26:27 +01:00
|
|
|
bool need_refresh= true;
|
2026-03-18 18:12:38 +01:00
|
|
|
uint8_t hits= 0;
|
2026-03-14 20:22:06 +01:00
|
|
|
long exec_t2= millis();
|
|
|
|
|
|
2026-03-17 18:42:48 +01:00
|
|
|
enum game_statuses : uint8_t {
|
2026-03-19 22:23:29 +01:00
|
|
|
MENU,
|
2026-03-17 18:42:48 +01:00
|
|
|
TIMER,
|
|
|
|
|
RUN,
|
|
|
|
|
SCORE,
|
|
|
|
|
GAMEOVER,
|
|
|
|
|
WAIT,
|
|
|
|
|
};
|
2026-03-21 10:24:15 +01:00
|
|
|
game_statuses game_status= MENU;
|
2026-03-17 18:42:48 +01:00
|
|
|
|
2026-03-21 11:19:40 +01:00
|
|
|
enum game_modes : uint8_t {PVP, PVC, CVC};
|
|
|
|
|
game_modes game_mode = PVP;
|
|
|
|
|
|
|
|
|
|
|
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, 2);
|
|
|
|
|
BotPaddle bot_pad2(4, MATRIX_WIDTH-1, 2);
|
|
|
|
|
|
2026-03-21 11:19:40 +01:00
|
|
|
uint8_t current_gmode_idx= 0;
|
|
|
|
|
bool update_menu= 1;
|
|
|
|
|
bool mode_selected= 0;
|
|
|
|
|
|
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
|
|
|
|
2026-03-14 20:22:06 +01:00
|
|
|
void setup() {
|
|
|
|
|
Serial.begin(9600);
|
2026-03-15 20:29:56 +01:00
|
|
|
// start LED matrix
|
2026-03-14 20:22:06 +01:00
|
|
|
matrix.begin();
|
2026-03-16 22:37:53 +01:00
|
|
|
|
2026-03-14 20:22:06 +01:00
|
|
|
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());
|
2026-03-14 20:22:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop() {
|
2026-03-17 18:42:48 +01:00
|
|
|
long exec_t1= millis();
|
2026-03-16 22:37:53 +01:00
|
|
|
|
2026-03-17 18:42:48 +01:00
|
|
|
switch (game_status) {
|
2026-03-17 23:25:30 +01:00
|
|
|
|
2026-03-21 11:19:40 +01:00
|
|
|
case MENU: {
|
|
|
|
|
if (digitalRead(P2_BTN_BOTTOM) == LOW && current_gmode_idx < sizeof(frame_gmodes)/sizeof(frame_gmodes[0]) -1) {
|
|
|
|
|
current_gmode_idx += 1;
|
|
|
|
|
update_menu= true;
|
|
|
|
|
}
|
|
|
|
|
else if (digitalRead(P2_BTN_UP) == LOW && current_gmode_idx > 0) {
|
|
|
|
|
update_menu= true;
|
|
|
|
|
current_gmode_idx -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 22:23:29 +01:00
|
|
|
// 1. P vs P
|
2026-03-21 11:19:40 +01:00
|
|
|
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && game_modes(current_gmode_idx) == PVP) {
|
2026-03-21 10:24:15 +01:00
|
|
|
p1= &human_pad1;
|
|
|
|
|
p2= &human_pad2;
|
2026-03-21 11:19:40 +01:00
|
|
|
mode_selected= true;
|
2026-03-21 10:24:15 +01:00
|
|
|
}
|
2026-03-19 22:23:29 +01:00
|
|
|
// 2. P vs CPU
|
2026-03-21 11:19:40 +01:00
|
|
|
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && game_modes(current_gmode_idx) == PVC) {
|
2026-03-21 10:24:15 +01:00
|
|
|
p1= &human_pad1;
|
|
|
|
|
p2= &bot_pad2;
|
2026-03-21 11:19:40 +01:00
|
|
|
mode_selected= true;
|
2026-03-21 10:24:15 +01:00
|
|
|
}
|
2026-03-19 22:23:29 +01:00
|
|
|
// 3. CPU vs CPU
|
2026-03-21 11:19:40 +01:00
|
|
|
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && game_modes(current_gmode_idx) == CVC) {
|
2026-03-21 10:24:15 +01:00
|
|
|
p1= &bot_pad1;
|
|
|
|
|
p2= &bot_pad2;
|
2026-03-21 11:19:40 +01:00
|
|
|
mode_selected= true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update_menu) {
|
|
|
|
|
// show menu on the matrix
|
|
|
|
|
const byte (*current_gmode)[12]= frame_gmodes[current_gmode_idx];
|
|
|
|
|
matrix.loadPixels((uint8_t*)current_gmode, MATRIX_HEIGHT * MATRIX_WIDTH);
|
|
|
|
|
update_menu= false;
|
|
|
|
|
delay(300);
|
|
|
|
|
}
|
|
|
|
|
else if (mode_selected) {
|
2026-03-21 10:24:15 +01:00
|
|
|
engine.set_players(p1, p2);
|
|
|
|
|
renderer.set_players(p1, p2);
|
|
|
|
|
game_status= TIMER;
|
|
|
|
|
}
|
2026-03-19 22:23:29 +01:00
|
|
|
break;
|
2026-03-21 11:19:40 +01:00
|
|
|
}
|
2026-03-19 22:23:29 +01:00
|
|
|
|
2026-03-17 18:42:48 +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);
|
2026-03-17 18:42:48 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2026-03-18 21:41:51 +01:00
|
|
|
if (exec_t1 - exec_t2 > engine.ball_movement_delay()) {
|
2026-03-18 18:12:38 +01:00
|
|
|
engine.run();
|
2026-03-18 21:41:51 +01:00
|
|
|
if (engine.get_event() == P1SCORE || engine.get_event() == P2SCORE)
|
2026-03-17 18:42:48 +01:00
|
|
|
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;
|
2026-03-17 18:42:48 +01:00
|
|
|
}
|
|
|
|
|
// 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;
|
2026-03-17 18:42:48 +01:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SCORE:
|
2026-03-18 21:41:51 +01:00
|
|
|
delay(300);
|
2026-03-18 19:46:52 +01:00
|
|
|
renderer.render_score();
|
2026-03-18 21:41:51 +01:00
|
|
|
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)
|
2026-03-17 18:42:48 +01:00
|
|
|
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();
|
2026-03-18 21:41:51 +01:00
|
|
|
exec_t2= millis() + FIRST_START_BALL_DELAY;
|
2026-03-18 19:00:50 +01:00
|
|
|
}
|
2026-03-17 18:42:48 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GAMEOVER:
|
2026-03-18 19:46:52 +01:00
|
|
|
renderer.render_winner();
|
2026-03-17 18:42:48 +01:00
|
|
|
game_status= WAIT;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case WAIT:
|
|
|
|
|
// keep showing the winner waiting for a restart
|
2026-03-16 22:37:53 +01:00
|
|
|
// 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 18:42:48 +01:00
|
|
|
game_status= TIMER;
|
2026-03-17 23:25:30 +01:00
|
|
|
engine.reset();
|
2026-03-16 22:37:53 +01:00
|
|
|
}
|
2026-03-17 18:42:48 +01:00
|
|
|
break;
|
2026-03-16 22:37:53 +01:00
|
|
|
}
|
2026-03-18 18:24:52 +01:00
|
|
|
delay(50);
|
2026-03-14 21:35:19 +01:00
|
|
|
}
|