28 Commits

Author SHA1 Message Date
andrea
8c402ff13a return to main menu after game finishes
Some checks failed
Arduino Pong CI / build (macos-latest) (push) Has been cancelled
Arduino Pong CI / build (ubuntu-latest) (push) Has been cancelled
Arduino Pong CI / build (windows-latest) (push) Has been cancelled
Arduino Pong CD / release (push) Has been cancelled
2026-03-21 12:16:07 +01:00
andrea
d544867269 return to main menu after game finishes
Some checks failed
Arduino Pong CI / build (macos-latest) (push) Has been cancelled
Arduino Pong CI / build (ubuntu-latest) (push) Has been cancelled
Arduino Pong CI / build (windows-latest) (push) Has been cancelled
Arduino Pong CD / release (push) Has been cancelled
2026-03-21 12:12:39 +01:00
andrea
d18415f472 select difficulty level (when play against bot) using a dedicated frame to show a graphics menu
Some checks failed
Arduino Pong CI / build (macos-latest) (push) Has been cancelled
Arduino Pong CI / build (ubuntu-latest) (push) Has been cancelled
Arduino Pong CI / build (windows-latest) (push) Has been cancelled
2026-03-21 12:10:52 +01:00
andrea
cef9f09c79 render visual menu on led matrix to easly allow user choose the game modes 2026-03-21 11:19:40 +01:00
andrea
a9aed6b8d0 choose game mode
pvp, pvc, cvc
2026-03-21 10:24:15 +01:00
andrea
7f63ae9a31 Revert "allow user choose between game modes, pvp, pvc, cvc"
i need to test it

This reverts commit a6c218061d.
2026-03-19 23:51:45 +01:00
andrea
a6c218061d allow user choose between game modes, pvp, pvc, cvc 2026-03-19 23:28:34 +01:00
andrea
8e5fe2143c random shift ball vertical position after score 2026-03-19 22:32:01 +01:00
andrea
fbad1a87d6 min 2026-03-19 22:24:23 +01:00
andrea
4bb6d6d6ee start thinking about MENU status 2026-03-19 22:23:29 +01:00
andrea
6ea443d92a single player with difficlut level balancement 2026-03-19 20:34:16 +01:00
andrea
2838b5fd14 start implementi difficulty level, but needs some balancements 2026-03-19 19:38:10 +01:00
andrea
69a8fb9dc4 first bot implementation
bot is now able to automatically move, but for now is too skilled, i need to reduce his skills
2026-03-19 18:49:58 +01:00
andrea
eac8c59d96 reframe check_pad_movement logics and inherith the base Paddle class 2026-03-19 18:26:27 +01:00
andrea
68dfce8b12 Merge branch 'master.use_classes'
Some checks failed
Arduino Pong CI / build (macos-latest) (push) Has been cancelled
Arduino Pong CI / build (ubuntu-latest) (push) Has been cancelled
Arduino Pong CI / build (windows-latest) (push) Has been cancelled
Arduino Pong CD / release (push) Has been cancelled
2026-03-18 21:45:39 +01:00
andrea
49a772f6de clean logs and improve engine handling by removing hits and movment_delay from the loop to the engine 2026-03-18 21:41:51 +01:00
andrea
abc0ddb661 fix collision state 2026-03-18 20:49:25 +01:00
andrea
33e070c442 added renderer.cpp/h files 2026-03-18 20:38:31 +01:00
andrea
1f6e65c5ec renderer class 2026-03-18 19:46:52 +01:00
andrea
7afa70c9d8 fix ball bouncing on pad behavior
now the ball bounces on pad when hit the pad, not when it reaches the edge of the "screen"
2026-03-18 19:12:09 +01:00
andrea
792c0f835c clean main sketch file 2026-03-18 19:00:50 +01:00
andrea
5afa726015 minors 2026-03-18 18:24:52 +01:00
andrea
48fef4be86 move some logics to loop() 2026-03-18 18:12:38 +01:00
andrea
48c2610561 TODO 2026-03-17 23:43:37 +01:00
andrea
3d8881ab3d total reframe with classes 2026-03-17 23:25:30 +01:00
andrea
d8efabd6da start refactoring pong entities to use classes 2026-03-17 20:11:21 +01:00
andrea
c774e11923 Improve arduino_pong.ino using enum to handle game statuses and switch case for better readibility and little memory improvements
Some checks failed
Arduino Pong CI / build (macos-latest) (push) Has been cancelled
Arduino Pong CI / build (ubuntu-latest) (push) Has been cancelled
Arduino Pong CI / build (windows-latest) (push) Has been cancelled
Arduino Pong CD / release (push) Has been cancelled
2026-03-17 18:42:48 +01:00
andrea
b3f6aeb3fe Show scores on the matrix, show winner message once someone reches 9, add start ball movement delay on start and restart after score, and fix uncentered ball on restart
Some checks failed
Arduino Pong CI / build (macos-latest) (push) Has been cancelled
Arduino Pong CI / build (ubuntu-latest) (push) Has been cancelled
Arduino Pong CI / build (windows-latest) (push) Has been cancelled
2026-03-16 22:37:53 +01:00
18 changed files with 943 additions and 194 deletions

View File

@@ -1,12 +1,11 @@
#include "Arduino_LED_Matrix.h"
#include "src/config.h"
#include "src/pong_render.h"
#include "src/pong_player.h"
#include "src/pong_ball.h"
#include "src/renderer.h"
#include "src/engine.h"
#include "src/paddle.h"
#include "src/ball.h"
// create LED matrix object
ArduinoLEDMatrix matrix;
// initial pong frame matrix
byte frame[MATRIX_HEIGHT][MATRIX_WIDTH] = {
@@ -20,25 +19,50 @@ byte frame[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
// players coordinates
int p1_start= 1;
int p2_start= 4;
// initials balls coordinates
int ball_x= BALL_RESET_X;
int ball_y= BALL_RESET_Y;
int need_refresh= 1;
int ball_delay= INITIAL_BALL_DELAY;
ArduinoLEDMatrix matrix;
bool need_refresh= true;
uint8_t hits= 0;
long exec_t2= millis();
enum game_statuses : uint8_t {
MENU,
MENU_BOT_SKILLS,
TIMER,
RUN,
SCORE,
GAMEOVER,
WAIT,
};
game_statuses game_status= MENU;
enum game_modes : uint8_t {PVP, PVC, CVC};
game_modes game_mode = PVP;
Ball ball(4, 6);
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);
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;
Engine engine(ball, INITIAL_BALL_DELAY);
Renderer renderer(ball, frame, matrix);
void setup() {
Serial.begin(9600);
// start LED matrix
matrix.begin();
pinMode(P1_BTN_UP, INPUT_PULLUP);
pinMode(P1_BTN_BOTTOM, INPUT_PULLUP);
pinMode(P2_BTN_UP, INPUT_PULLUP);
@@ -49,16 +73,143 @@ void setup() {
void loop() {
long exec_t1= millis();
pong_move_p1(p1_start, need_refresh);
pong_move_p2(p2_start, need_refresh);
if (exec_t1 - exec_t2 > ball_delay) {
move_ball(ball_x, ball_y, ball_delay, p1_start, p2_start, need_refresh);
exec_t2= exec_t1;
}
if (need_refresh) {
render_matrix(frame, p1_start, p2_start, ball_x, ball_y);
matrix.renderBitmap(frame, MATRIX_HEIGHT, MATRIX_WIDTH);
need_refresh= 0;
switch (game_status) {
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;
game_mode= PVP;
}
// 1. P vs P
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && game_modes(current_gmode_idx) == PVP) {
p1= &human_pad1;
p2= &human_pad2;
mode_selected= true;
}
// 2. P vs CPU
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && game_modes(current_gmode_idx) == PVC) {
p1= &human_pad1;
p2= &bot_pad2;
mode_selected= true;
update_menu= false;
game_mode= PVC;
}
// 3. CPU vs CPU
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW && game_modes(current_gmode_idx) == CVC) {
p1= &bot_pad1;
p2= &bot_pad2;
mode_selected= true;
update_menu= false;
game_mode= CVC;
}
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) {
engine.set_players(p1, p2);
renderer.set_players(p1, p2);
if (game_mode == PVC || game_mode == CVC) {
game_status= MENU_BOT_SKILLS;
delay(300); // avoid accidental double click for next menu
}
else game_status= TIMER;
}
break;
}
case MENU_BOT_SKILLS: {
if (digitalRead(P2_BTN_BOTTOM) == LOW && current_bot_menu_idx < sizeof(frame_bot_skills)/sizeof(frame_bot_skills[0]) -1) {
current_bot_menu_idx += 1;
update_menu_bot_skills= true;
}
else if (digitalRead(P2_BTN_UP) == LOW && current_bot_menu_idx > 0) {
current_bot_menu_idx -= 1;
update_menu_bot_skills= true;
}
else if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW) {
if (!p1 -> is_human()) p1 -> set_skills(current_bot_menu_idx + 1);
if (!p2 -> is_human()) p2 -> set_skills(current_bot_menu_idx + 1);
game_status= TIMER;
update_menu_bot_skills= false;
}
if (update_menu_bot_skills) {
const byte (*current_skill_frame)[12]= frame_bot_skills[current_bot_menu_idx];
matrix.loadPixels((uint8_t*)current_skill_frame, MATRIX_HEIGHT * MATRIX_WIDTH);
update_menu= false;
delay(300);
}
break;
}
case TIMER:
for (int i = START_TIMER; i >= 0; i--) {
renderer.render_timer(i);
delay(1000);
}
game_status= RUN;
// delay the first ball movement
exec_t2= millis() + FIRST_START_BALL_DELAY;
break;
case RUN:
need_refresh= engine.control_players();
if (exec_t1 - exec_t2 > engine.ball_movement_delay()) {
engine.run();
if (engine.get_event() == P1SCORE || engine.get_event() == P2SCORE)
game_status= SCORE;
exec_t2= exec_t1;
need_refresh= true;
}
// rerender matrix only if something is changed
if (need_refresh) {
renderer.render_matrix();
need_refresh= false;
}
break;
case SCORE:
delay(300);
renderer.render_score();
engine.restart_ball();
delay(1000);
if (p1 -> get_score() >= MAX_POINTS || p2 -> get_score() >= MAX_POINTS)
game_status= GAMEOVER;
else {
game_status= RUN;
// before move again the ball wait a second
renderer.render_matrix();
exec_t2= millis() + FIRST_START_BALL_DELAY;
}
break;
case GAMEOVER:
renderer.render_winner();
game_status= WAIT;
break;
case WAIT:
// keep showing the winner waiting for a restart
// restart game once one button is pressed
if (digitalRead(P1_BTN_UP) == LOW || digitalRead(P1_BTN_BOTTOM) == LOW ||
digitalRead(P2_BTN_UP) == LOW || digitalRead(P2_BTN_BOTTOM) == LOW) {
game_status= MENU;
engine.reset();
}
break;
}
delay(50);
}

52
src/ball.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "ball.h"
void Ball::_init_directions(int8_t &_direction) {
if (random(2) == 0) _direction= 1;
else _direction= -1;
}
void Ball::move() {
if (!_direction_x) this -> _init_directions(_direction_x);
if (!_direction_y) this -> _init_directions(_direction_y);
// if (_x < 0 || _x > MATRIX_WIDTH-1 || _y < 0 || _y > MATRIX_HEIGHT-1) {
// this -> reset_position();
// }
if (_x + _direction_x >= 0 && _x + _direction_x < MATRIX_WIDTH)
_x+= _direction_x;
if (_y + _direction_y >= 0 && _y + _direction_y < MATRIX_HEIGHT)
_y+= _direction_y;
}
void Ball::bounce_on_pad() {
_direction_x *= -1;
}
void Ball::bounce_on_sides() {
_direction_y *= -1;
}
int8_t Ball::get_direction_x() {
return _direction_x;
}
int8_t Ball::get_direction_y() {
return _direction_y;
}
void Ball::reset_position () {
_x= BALL_RESET_X;
int8_t delta= random(3);
if (random(2) == 0) delta *= -1;
_y= BALL_RESET_Y + delta;
this -> _init_directions(_direction_x);
this -> _init_directions(_direction_y);
}
uint8_t Ball::get_x() {
return _x;
}
uint8_t Ball::get_y() {
return _y;
}

27
src/ball.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef BALL_H
#define BALL_H
#include <Arduino.h>
#include "config.h"
class Ball {
private:
uint8_t _x, _y;
int8_t _direction_x, _direction_y;
void _init_directions(int8_t &_direction);
public:
Ball (uint8_t _x, uint8_t _y) : _x(_x), _y(_y) {}
void move();
void bounce_on_pad();
void bounce_on_sides();
int8_t get_direction_x();
int8_t get_direction_y();
void reset_position ();
uint8_t get_x();
uint8_t get_y();
};
#endif

View File

@@ -7,5 +7,9 @@
#define MATRIX_HEIGHT 8
#define BALL_RESET_X (MATRIX_WIDTH / 2)
#define BALL_RESET_Y (MATRIX_HEIGHT / 2)
#define BAR_LENGTH 3
#define PADDLE_LENGTH 3
#define INITIAL_BALL_DELAY 200
#define MAX_POINTS 9
#define START_TIMER 3
#define FIRST_START_BALL_DELAY 500

96
src/engine.cpp Normal file
View File

@@ -0,0 +1,96 @@
#include "engine.h"
bool Engine::_check_pad_ball_collision(Paddle *p) {
uint8_t ppos= p -> get_position();
for (int p= ppos; p < ppos + PADDLE_LENGTH; p++) {
if (_ball.get_y() == p) {
return true;
}
}
return false;
}
void Engine::run() {
// if (_event == P1SCORE || _event == P2SCORE) this -> _soft_reset();
_event= NONE;
_ball.move();
uint8_t bx= _ball.get_x();
uint8_t by= _ball.get_y();
int8_t ball_dir= _ball.get_direction_x();
// pad is 1 pixel far from the edge, so i need to calc this delta
// check also if the ball is already moving away, to skip useless checks
if (bx <= 1 && ball_dir < 0) {
// score the point only if ball reached the edge
if (this -> _check_pad_ball_collision(_p1) && bx <= 1) {
_ball.bounce_on_pad();
_event= P1_COLLISION;
_hits++;
}
else if (bx <= 0) {
// p2 scores
_p2 -> increase_score();
_event= P2SCORE;
return;
}
}
else if (bx >= MATRIX_WIDTH-2 && ball_dir > 0) {
// score the point only if ball reached the edge
if (this -> _check_pad_ball_collision(_p2) && bx >= MATRIX_WIDTH-2) {
_ball.bounce_on_pad();
_event= P2_COLLISION;
_hits++;
}
else if (bx >= MATRIX_WIDTH-1) {
// p1 scores
_p1 -> increase_score();
_event= P1SCORE;
return;
}
}
if (by == 0 || by == MATRIX_HEIGHT-1) {
_ball.bounce_on_sides();
_event= WALL_COLLISION;
}
if (_hits >= 6 && _ball_mv_delay >= 80) {
_hits= 0;
_ball_mv_delay -= 20;
}
}
void Engine::set_players(Paddle *p1, Paddle *p2) {
_p1= p1;
_p2= p2;
}
bool Engine::control_players() {
bool need_refresh= false;
if (_p1 -> is_human()) need_refresh |= _p1 -> check_pad_movement();
else need_refresh |= _p1 -> check_pad_movement(_ball);
if (_p2 -> is_human()) need_refresh |= _p2 -> check_pad_movement();
else need_refresh |= _p2 -> check_pad_movement(_ball);
return need_refresh;
}
uint8_t Engine::ball_movement_delay() {
return _ball_mv_delay;
}
EngineEvents Engine::get_event() {
return _event;
}
void Engine::restart_ball() {
_ball.reset_position();
_ball_mv_delay= INITIAL_BALL_DELAY;
}
void Engine::reset() {
this -> restart_ball();
_p1 -> reset();
_p2 -> reset();
}

37
src/engine.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef ENGINE_H
#define ENGINE_H
#include <Arduino.h>
#include "ball.h"
#include "paddle.h"
#include "config.h"
enum EngineEvents : uint8_t {NONE, P1SCORE, P2SCORE, P1_COLLISION, P2_COLLISION, WALL_COLLISION};
class Engine {
private:
Paddle* _p1;
Paddle* _p2;
Ball& _ball;
EngineEvents _event= NONE;
uint8_t _ball_mv_delay;
uint8_t _hits = 0;
bool _check_pad_ball_collision(Paddle *p);
public:
// inizialize Engine constructor, linking all args with private args
Engine(Ball &ball, uint8_t ball_mv_delay)
: _ball(ball), _ball_mv_delay(ball_mv_delay) {}
void run();
void set_players(Paddle *p_one, Paddle *p_two);
bool control_players();
uint8_t ball_movement_delay();
EngineEvents get_event();
void restart_ball();
void reset();
};
#endif

238
src/font.cpp Normal file
View File

@@ -0,0 +1,238 @@
#include "font.h";
const uint32_t pone_wins[5][4] = {
{
0x78,
0xc4847844,
0x440e000,
500
},
{
0x11,
0x1101501,
0x501f0000,
500
},
{
0xe,
0x400400,
0x400e0000,
500
},
{
0x9,
0xd00b00,
0x90090000,
500
},
{
0xf,
0x800f00,
0x100f0000,
500
}
};
const uint32_t ptwo_wins[5][4] = {
{
0x79,
0xe48279e4,
0x1041e000,
500
},
{
0x11,
0x1101501,
0x501f0000,
500
},
{
0xe,
0x400400,
0x400e0000,
500
},
{
0x9,
0xd00b00,
0x90090000,
500
},
{
0xf,
0x800f00,
0x100f0000,
500
}
};
const byte font_pong[10][8][3] = {
// Number 0
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 1 },
{ 1, 0, 1 },
{ 1, 0, 1 },
{ 1, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 0 }
},
// Number 1
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 0 },
{ 0, 1, 0 },
{ 0, 1, 0 },
{ 0, 1, 0 },
{ 1, 1, 1 },
{ 0, 0, 0 }
},
// Number 2
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 1 },
{ 0, 0, 1 },
{ 1, 1, 1 },
{ 1, 0, 0 },
{ 1, 1, 1 },
{ 0, 0, 0 }
},
// Number 3
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 1 },
{ 0, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 0 }
},
// Number 4
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 0, 1 },
{ 1, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 1 },
{ 0, 0, 1 },
{ 0, 0, 0 }
},
// Number 5
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 1 },
{ 1, 0, 0 },
{ 1, 1, 1 },
{ 0, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 0 }
},
// Number 6
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 0, 0 },
{ 1, 0, 0 },
{ 1, 1, 1 },
{ 1, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 0 }
},
// Number 7
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 1 },
{ 0, 0, 1 },
{ 0, 0, 1 },
{ 0, 0, 1 },
{ 0, 0, 1 },
{ 0, 0, 0 }
},
// Number 8
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 1 },
{ 1, 0, 1 },
{ 1, 1, 1 },
{ 1, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 0 }
},
// Number 9
{
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 1, 1, 1 },
{ 1, 0, 1 },
{ 1, 1, 1 },
{ 0, 0, 1 },
{ 0, 0, 1 },
{ 0, 0, 0 }
},
};
const byte frame_pvp[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 },
{ 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0 },
{ 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0 },
{ 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0 },
{ 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
const byte frame_pvc[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 },
{ 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0 },
{ 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0 },
{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
const byte frame_cvc[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0 },
{ 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
const byte (*frame_gmodes[3])[12]= {frame_pvp, frame_pvc, frame_cvc};
const byte frame_bot_skill_easy[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
const byte frame_bot_skill_hard[MATRIX_HEIGHT][MATRIX_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0 },
{ 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
const byte (*frame_bot_skills[3])[12]= {frame_bot_skill_easy, frame_bot_skill_hard};

20
src/font.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef FONT_H
#define FONT_H
#include <Arduino.h>
#include "config.h"
extern const uint32_t pone_wins[5][4];
extern const uint32_t ptwo_wins[5][4];
extern const byte font_pong[10][8][3];
extern const byte frame_pvp[MATRIX_HEIGHT][MATRIX_WIDTH];
extern const byte frame_pvc[MATRIX_HEIGHT][MATRIX_WIDTH];
extern const byte frame_cvc[MATRIX_HEIGHT][MATRIX_WIDTH];
extern const byte (*frame_gmodes[3])[12];
extern const byte frame_bot_skill_easy[MATRIX_HEIGHT][MATRIX_WIDTH];
extern const byte frame_bot_skill_hard[MATRIX_HEIGHT][MATRIX_WIDTH];
extern const byte (*frame_bot_skills[3])[12];
#endif

127
src/paddle.cpp Normal file
View File

@@ -0,0 +1,127 @@
#include "paddle.h"
#include <cstdio>
void Paddle::move_pad_up() {
if (_pos_y > 0) {
_pos_y -= 1;
}
}
void Paddle::move_pad_down() {
if (_pos_y + _height < MATRIX_HEIGHT) {
_pos_y += 1;
}
}
void run_paddle() {
}
uint8_t Paddle::get_position() {
return _pos_y;
}
bool Paddle::is_human() {
return _human;
}
void Paddle::increase_score() {
if (_score <= 9) _score += 1;
}
uint8_t Paddle::get_score() {
return _score;
}
void Paddle::reset() {
_score= 0;
}
bool Paddle::check_pad_movement() {
// redefine me
return false;
}
bool Paddle::check_pad_movement(Ball &ball) {
// redefine me
return false;
}
uint8_t Paddle::get_skills() {
return 0;
}
void Paddle::set_skills(uint8_t skills) {
}
bool HumanPaddle::check_pad_movement() {
bool need_refresh= false;
if (digitalRead(_pin_btn_top) == LOW) {
this -> move_pad_up();
need_refresh= true;
}
else if (digitalRead(_pin_btn_bottom) == LOW) {
this -> move_pad_down();
need_refresh= true;
}
return need_refresh;
}
bool BotPaddle::check_pad_movement(Ball &ball) {
uint8_t ball_y= ball.get_y();
int8_t ball_dir= ball.get_direction_x();
int8_t ball_dir_ver= ball.get_direction_y();
uint8_t skills= this -> get_skills();
// ball is moving left and pad is on right, do not move
if (ball_dir < 0 && _pos_x > MATRIX_WIDTH / 2) return false;
// ball is moving right and pad is on left, do not move
else if (ball_dir > 0 && _pos_x < MATRIX_WIDTH / 2) return false;
uint8_t ball_x= ball.get_x();
int8_t ball_distance= ball_x - _pos_x;
if (ball_distance < 0) ball_distance *= -1;
switch (skills) {
case 1:
if (ball_distance > 3) return false;
break;
case 2:
if (ball_distance > 4) return false;
break;
}
uint8_t move_chances= random(skills * 10) % 2;
if (!move_chances) return false;
enum Movement {NONE, UP, DOWN};
Movement _movment= NONE;
for (uint8_t py= _pos_y; py < _pos_y+PADDLE_LENGTH-1; py++) {
if (_pos_y - ball_y >= 0 && ball_dir_ver < 0) {
_movment= UP;
break;
}
if (_pos_y - ball_y <= 0 && ball_dir_ver > 0) {
_movment= DOWN;
break;
}
}
if (_movment == UP) {
this -> move_pad_up();
return true;
}
if (_movment == DOWN) {
this -> move_pad_down();
return true;
}
return false;
}
uint8_t BotPaddle::get_skills() {
return _skills;
}
void BotPaddle::set_skills(uint8_t skills) {
if (skills < 0) _skills= 0;
else if (skills > 1) _skills= 1;
else _skills= skills;
}

57
src/paddle.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef PADDLE_H
#define PADDLE_H
#include <Arduino.h>
#include <cstdint>
#include "config.h"
#include "ball.h"
class Paddle {
protected:
// define player coordinates
uint8_t _pos_y;
uint8_t _height= PADDLE_LENGTH;
uint8_t _score= 0;
bool _human;
public:
Paddle (uint8_t pos_y, bool human) : _pos_y(pos_y), _human(human) {}
void move_pad_up();
void move_pad_down();
uint8_t get_position();
bool is_human();
void increase_score();
uint8_t get_score();
void reset();
virtual bool check_pad_movement();
virtual bool check_pad_movement(Ball &ball);
virtual uint8_t get_skills();
virtual void set_skills(uint8_t skills);
};
class HumanPaddle : public Paddle {
private:
uint8_t _pin_btn_top;
uint8_t _pin_btn_bottom;
public:
HumanPaddle(uint8_t position, uint8_t pin_btn_top, uint8_t pin_btn_bottom)
: Paddle(position, true), _pin_btn_top(pin_btn_top), _pin_btn_bottom(pin_btn_bottom) {}
bool check_pad_movement();
};
class BotPaddle : public Paddle {
private:
uint8_t _pos_x;
uint8_t _skills; // this is the difficulty level
public:
BotPaddle(uint8_t position, uint8_t pos_x)
: Paddle(position, false), _pos_x(pos_x) {}
bool check_pad_movement(Ball &ball);
uint8_t get_skills();
void set_skills(uint8_t skills);
};
#endif

View File

@@ -1,88 +0,0 @@
#include <Arduino.h>
#include "config.h"
#include "pong_player.h"
// used to increase speed when game is too easy
int hits= 0;
int p1_score= 0;
int p2_score= 0;
// initially ball has no movements
// once game/round starts, balls gets random x and y movements
int ball_move_x= 0;
int ball_move_y= 0;
void point_scored(int &ball_x, int &ball_y, int &ball_delay) {
ball_x= BALL_RESET_X;
ball_y= BALL_RESET_Y;
Serial.print("P1: ");
Serial.print(p1_score);
Serial.print(" - ");
Serial.print("P2: ");
Serial.print(p2_score);
Serial.println();
hits= 0;
ball_delay= INITIAL_BALL_DELAY;
}
void move_ball(int &ball_x, int &ball_y, int &ball_delay, int p1_start, int p2_start, int &need_refresh) {
need_refresh= 1;
if (ball_x < 0 || ball_x > MATRIX_WIDTH-1 || ball_y < 0 || ball_y > MATRIX_HEIGHT-1) {
// ball out of matrix limits
ball_x= BALL_RESET_X;
ball_y= BALL_RESET_Y;
return;
}
// if ball is not moving, get random direction
// this is the initial position
if (ball_move_x == 0 || ball_move_y == 0) {
// extract random number between 0 or 1 to select the directions
if (random(2) == 0) ball_move_x= 1;
else ball_move_x= -1;
if (random(2) == 0) ball_move_y= 1;
else ball_move_y= -1;
}
else if (ball_x == 0) {
// if p1 collision: reverse x, go left
if (!ball_player_collision(p1_start, ball_y)) {
// else p2 score, reset board
p2_score += 1;
Serial.println("Player 2 Scores");
point_scored(ball_x, ball_y, ball_delay);
}
else {
hits += 1;
ball_move_x= ball_move_x * -1;
}
}
else if (ball_x == MATRIX_WIDTH-1) {
if (!ball_player_collision(p2_start, ball_y)) {
// else p1 score, reset board
p1_score += 1;
Serial.println("Player 1 Scores");
point_scored(ball_x, ball_y, ball_delay);
}
else {
hits += 1;
ball_move_x= ball_move_x * -1;
}
}
if (ball_y == 0 || ball_y == MATRIX_HEIGHT-1) {
// reverse y, go down
ball_move_y= ball_move_y * -1;
}
if (hits >= 6 && ball_delay >= 80) {
// increase ball speed
hits = 0;
ball_delay -= 20;
}
ball_x+= ball_move_x;
ball_y+= ball_move_y;
}

View File

@@ -1,7 +0,0 @@
#ifndef PONG_BALL_H
#define PONG_BALL_H
void point_scored();
void move_ball(int &ball_x, int &ball_y, int &loop_delay, int p1_start, int p2_start, int &need_refresh);
#endif

View File

@@ -1,33 +0,0 @@
#include <Arduino.h>
#include "config.h"
int ball_player_collision(int player, int ball_y) {
for (int p= player; p < player + BAR_LENGTH; p++) {
if (ball_y == p) {
return 1;
}
}
return 0;
}
int pong_move_p1(int &p1_start, int &need_refresh) {
if (digitalRead(P1_BTN_UP) == LOW && p1_start > 0) {
p1_start -= 1;
need_refresh= 1;
}
else if (digitalRead(P1_BTN_BOTTOM) == LOW && p1_start < 5) {
p1_start += 1;
need_refresh= 1;
}
}
int pong_move_p2(int &p2_start, int &need_refresh) {
if (digitalRead(P2_BTN_UP) == LOW && p2_start > 0) {
p2_start -= 1;
need_refresh= 1;
}
else if (digitalRead(P2_BTN_BOTTOM) == LOW && p2_start < 5) {
p2_start += 1;
need_refresh= 1;
}
}

View File

@@ -1,8 +0,0 @@
#ifndef PONG_PLAYER_H
#define PONG_PLAYER_H
int ball_player_collision(int player, int ball_y);
int pong_move_p1(int &p1_start, int &need_refresh);
int pong_move_p2(int &p2_start, int &need_refresh);
#endif

View File

@@ -1,22 +0,0 @@
#include <Arduino.h>
#include "config.h"
void render_matrix(byte frame[MATRIX_HEIGHT][MATRIX_WIDTH], int p1_start, int p2_start, int ball_x, int ball_y) {
// clear
for (int x=0; x < MATRIX_WIDTH; x++) {
for (int y=0; y < MATRIX_HEIGHT; y++) {
frame[y][x]= 0;
}
}
// players coords
for (int i= p1_start; i < p1_start+BAR_LENGTH; i++) {
frame[i][0]= 1;
}
for (int i= p2_start; i < p2_start+BAR_LENGTH; i++) {
frame[i][MATRIX_WIDTH-1]= 1;
}
// ball coords
frame[ball_y][ball_x]= 1;
}

View File

@@ -1,9 +0,0 @@
#ifndef PONG_RENDER_H
#define PONG_RENDER_H
#include <Arduino.h>
#include "Arduino_LED_Matrix.h"
void render_matrix(byte frame[MATRIX_HEIGHT][MATRIX_WIDTH], int p1_start, int p2_start, int ball_x, int ball_y);
#endif

73
src/renderer.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "renderer.h"
void Renderer::_clear_matrix() {
for (int x=0; x < MATRIX_WIDTH; x++) {
for (int y=0; y < MATRIX_HEIGHT; y++) {
_frame[y][x]= 0;
}
}
}
void Renderer::set_players(Paddle *p1, Paddle *p2) {
_p1= p1;
_p2= p2;
}
void Renderer::render_timer(uint8_t seconds) {
this -> _clear_matrix();
for (int h=0; h < 8; h++) {
for (int w=0; w < 3; w++) {
_frame[h][w+5]= font_pong[seconds][h][w];
}
}
_matrix.renderBitmap(_frame, MATRIX_HEIGHT, MATRIX_WIDTH);
}
void Renderer::render_matrix() {
this -> _clear_matrix();
uint8_t p1pos= _p1 -> get_position();
uint8_t p2pos= _p2 -> get_position();
// players coords
for (int i= p1pos; i < p1pos+PADDLE_LENGTH; i++) {
_frame[i][0]= 1;
}
for (int i= p2pos; i < p2pos+PADDLE_LENGTH; i++) {
_frame[i][MATRIX_WIDTH-1]= 1;
}
// ball coords
uint8_t bx= _ball.get_x();
uint8_t by= _ball.get_y();
_frame[by][bx]= 1;
_matrix.renderBitmap(_frame, MATRIX_HEIGHT, MATRIX_WIDTH);
}
void Renderer::render_score() {
this -> _clear_matrix();
_frame[4][5]= 1;
_frame[4][6]= 1;
for (int h=0; h < 8; h++) {
for (int w=0; w < 3; w++) {
_frame[h][w+1]= font_pong[_p1 -> get_score()][h][w];
}
}
for (int h=0; h < 8; h++) {
for (int w=0; w < 3; w++) {
_frame[h][w+8]= font_pong[_p2 -> get_score()][h][w];
}
}
_matrix.renderBitmap(_frame, MATRIX_HEIGHT, MATRIX_WIDTH);
}
void Renderer::render_winner() {
this -> _clear_matrix();
// check winner
if (_p1 -> get_score() > _p2 -> get_score())
_matrix.loadSequence(pone_wins);
else
_matrix.loadSequence(ptwo_wins);
_matrix.play(true);
}

34
src/renderer.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef RENDERER_H
#define RENDERER_H
#include <Arduino.h>
#include "Arduino_LED_Matrix.h"
#include "config.h"
#include "font.h"
#include "paddle.h"
#include "ball.h"
class Renderer {
private:
// define player coordinates
Paddle* _p1;
Paddle* _p2;
Ball& _ball;
byte (&_frame)[MATRIX_HEIGHT][MATRIX_WIDTH];
ArduinoLEDMatrix& _matrix;
void _clear_matrix();
public:
Renderer (Ball &ball, byte (&frame)[MATRIX_HEIGHT][MATRIX_WIDTH], ArduinoLEDMatrix &matrix)
: _ball(ball), _frame(frame), _matrix(matrix) {}
void set_players(Paddle *p1, Paddle *p2);
void render_timer(uint8_t seconds);
void render_matrix();
void render_score();
void render_winner();
};
#endif