#include <LedControl.h>
LedControl lc = LedControl(12, 11, 10, 2);
// ---------------- GAME STATE ----------------
int ballX = 7, ballY = 3;
int dirX = 1, dirY = 1;
int playerPaddleY = 3;
int aiPaddleY = 3;
int playerScore = 0;
int aiScore = 0;
// ---------------- CONSTANTS ----------------
const int paddleSize = 3;
const int maxScore = 9;
// Timing (ms)
unsigned long lastBallMove = 0;
unsigned long lastPaddleMove = 0;
unsigned long lastFrame = 0;
const int originalBallInterval = 180; // ORIGINAL SPEED
int ballInterval = originalBallInterval;
int paddleInterval = 60;
// -------- AI DIFFICULTY --------
// 1 = Easy
// 2 = Medium
// 3 = Hard
int aiDifficulty = 1;
// Serve control
bool serving = true;
unsigned long serveStart = 0;
// ---------------- SETUP ----------------
void setup() {
lc.shutdown(0, false);
lc.shutdown(1, false);
lc.setIntensity(0, 8);
lc.setIntensity(1, 8);
lc.clearDisplay(0);
lc.clearDisplay(1);
randomSeed(analogRead(A5));
showTitle();
startServe();
}
// ---------------- MAIN LOOP ----------------
void loop() {
unsigned long now = millis();
readPlayer();
if (!serving && now - lastBallMove > ballInterval) {
moveBall();
checkCollisions();
lastBallMove = now;
}
if (now - lastPaddleMove > paddleInterval) {
moveAI();
lastPaddleMove = now;
}
if (now - lastFrame > 30) {
drawGame();
lastFrame = now;
}
}
// ---------------- INPUT ----------------
void readPlayer() {
playerPaddleY = map(
analogRead(A0),
0, 1023,
0, 8 - paddleSize
);
}
// ---------------- AI ----------------
void moveAI() {
int aiSpeed;
int aiError;
// Difficulty behavior
if (aiDifficulty == 1) { // EASY
aiSpeed = 1;
aiError = 3;
}
else if (aiDifficulty == 2) { // MEDIUM
aiSpeed = 1;
aiError = 1;
}
else { // HARD
aiSpeed = 2;
aiError = 0;
}
if (dirX > 0 && !serving) {
int targetY = ballY + random(-aiError, aiError + 1);
if (aiPaddleY + paddleSize / 2 < targetY) aiPaddleY += aiSpeed;
else if (aiPaddleY + paddleSize / 2 > targetY) aiPaddleY -= aiSpeed;
}
aiPaddleY = constrain(aiPaddleY, 0, 8 - paddleSize);
}
// ---------------- BALL ----------------
void moveBall() {
ballX += dirX;
ballY += dirY;
}
void checkCollisions() {
// Top / bottom
if (ballY <= 0 || ballY >= 7) dirY = -dirY;
// Player paddle
if (ballX == 1 &&
ballY >= playerPaddleY &&
ballY < playerPaddleY + paddleSize) {
dirX = 1;
speedUp();
}
// AI paddle
if (ballX == 14 &&
ballY >= aiPaddleY &&
ballY < aiPaddleY + paddleSize) {
dirX = -1;
speedUp();
}
// Score
if (ballX < 0) {
aiScore++;
showScore();
startServe();
}
if (ballX > 15) {
playerScore++;
showScore();
startServe();
}
}
// ---------------- DIFFICULTY SPEED UP ----------------
void speedUp() {
ballInterval = max(60, ballInterval - 5);
}
// ---------------- SERVE ----------------
void startServe() {
serving = true;
// ✅ RESET BALL SPEED AFTER GOAL
ballInterval = originalBallInterval;
ballX = 7;
ballY = random(1, 6);
dirX = random(0, 2) ? 1 : -1;
dirY = random(0, 2) ? 1 : -1;
countdown();
serving = false;
}
// ---------------- DRAW ----------------
void drawGame() {
lc.clearDisplay(0);
lc.clearDisplay(1);
// Player paddle
for (int i = 0; i < paddleSize; i++) {
setPixel(0, playerPaddleY + i, true);
}
// AI paddle
for (int i = 0; i < paddleSize; i++) {
setPixel(15, aiPaddleY + i, true);
}
// Ball
if (!serving) setPixel(ballX, ballY, true);
}
// ---------------- UTIL ----------------
void setPixel(int x, int y, bool state) {
x = 15 - x;
int device = x / 8;
int col = x % 8;
lc.setLed(device, y, col, state);
}
// ---------------- UI ----------------
void showTitle() {
byte title[8][16] = {
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0},
{1,1,1,0, 0,1,1,0, 1,0,0,1, 0,1,1,1},
{1,0,1,0, 1,0,0,1, 1,1,0,1, 1,0,0,0},
{1,1,1,0, 1,0,0,1, 1,1,1,1, 1,0,0,0},
{1,0,0,0, 1,0,0,1, 1,0,1,1, 1,0,1,1},
{1,0,0,0, 1,0,0,1, 1,0,0,1, 1,0,0,1},
{1,0,0,0, 0,1,1,0, 1,0,0,1, 0,1,1,1},
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0},
};
lc.clearDisplay(0);
lc.clearDisplay(1);
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 16; x++) {
setPixel(x, y, title[y][x]);
}
}
delay(2000);
}
// ---------------- SCORE DISPLAY ----------------
void showScore() {
lc.clearDisplay(0);
lc.clearDisplay(1);
for (int i = 0; i < playerScore; i++) {
lc.setLed(0, 7, i, true);
}
for (int i = 0; i < aiScore; i++) {
lc.setLed(1, 7, i, true);
}
delay(1000);
}
// ---------------- COUNTDOWN ----------------
void countdown() {
for (int i = 3; i > 0; i--) {
lc.clearDisplay(0);
lc.clearDisplay(1);
setPixel(7, 3, true);
delay(300);
lc.clearDisplay(0);
lc.clearDisplay(1);
delay(300);
}
}
/*#include <LedControl.h>
LedControl lc = LedControl(12, 11, 10, 2);
// ---------------- GAME STATE ----------------
int ballX = 7, ballY = 3;
int dirX = 1, dirY = 1;
int playerPaddleY = 3;
int aiPaddleY = 3;
int playerScore = 0;
int aiScore = 0;
// ---------------- CONSTANTS ----------------
const int paddleSize = 3;
const int maxScore = 9;
// Timing (ms)
unsigned long lastBallMove = 0;
unsigned long lastPaddleMove = 0;
unsigned long lastFrame = 0;
int ballInterval = 180; // gets faster over time
int paddleInterval = 60;
// AI tuning
int aiSpeed = 1;
int aiError = 2;
// Serve control
bool serving = true;
unsigned long serveStart = 0;
// ---------------- SETUP ----------------
void setup() {
lc.shutdown(0, false);
lc.shutdown(1, false);
lc.setIntensity(0, 8);
lc.setIntensity(1, 8);
lc.clearDisplay(0);
lc.clearDisplay(1);
randomSeed(analogRead(A5));
showTitle();
startServe();
}
// ---------------- MAIN LOOP ----------------
void loop() {
unsigned long now = millis();
readPlayer();
if (!serving && now - lastBallMove > ballInterval) {
moveBall();
checkCollisions();
lastBallMove = now;
}
if (now - lastPaddleMove > paddleInterval) {
moveAI();
lastPaddleMove = now;
}
if (now - lastFrame > 30) {
drawGame();
lastFrame = now;
}
}
// ---------------- INPUT ----------------
void readPlayer() {
playerPaddleY = map(
analogRead(A0),
0, 1023,
0, 8 - paddleSize
);
}
// ---------------- AI ----------------
void moveAI() {
if (dirX > 0 && !serving) {
int targetY = ballY + random(-aiError, aiError + 1);
if (aiPaddleY + paddleSize / 2 < targetY) aiPaddleY += aiSpeed;
else if (aiPaddleY + paddleSize / 2 > targetY) aiPaddleY -= aiSpeed;
}
aiPaddleY = constrain(aiPaddleY, 0, 8 - paddleSize);
}
// ---------------- BALL ----------------
void moveBall() {
ballX += dirX;
ballY += dirY;
}
void checkCollisions() {
// Top / bottom
if (ballY <= 0 || ballY >= 7) dirY = -dirY;
// Player paddle
if (ballX == 1 &&
ballY >= playerPaddleY &&
ballY < playerPaddleY + paddleSize) {
dirX = 1;
speedUp();
}
// AI paddle
if (ballX == 14 &&
ballY >= aiPaddleY &&
ballY < aiPaddleY + paddleSize) {
dirX = -1;
speedUp();
}
// Score
if (ballX < 0) {
aiScore++;
showScore();
startServe();
}
if (ballX > 15) {
playerScore++;
showScore();
startServe();
}
}
// ---------------- DIFFICULTY ----------------
void speedUp() {
ballInterval = max(60, ballInterval - 5);
aiError = max(0, aiError - 1);
}
// ---------------- SERVE ----------------
void startServe() {
serving = true;
// Reset ball speed after goal
ballInterval = originalBallInterval;
ballX = 7;
ballY = random(1, 6);
dirX = random(0, 2) ? 1 : -1;
dirY = random(0, 2) ? 1 : -1;
countdown();
serving = false;
}
// ---------------- DRAW ----------------
void drawGame() {
lc.clearDisplay(0);
lc.clearDisplay(1);
// Player paddle
for (int i = 0; i < paddleSize; i++) {
setPixel(0, playerPaddleY + i, true);
}
// AI paddle
for (int i = 0; i < paddleSize; i++) {
setPixel(15, aiPaddleY + i, true);
}
// Ball
if (!serving) setPixel(ballX, ballY, true);
}
// ---------------- UTIL ----------------
void setPixel(int x, int y, bool state) {
x = 15 - x; // <-- mirror horizontally
int device = x / 8;
int col = x % 8;
lc.setLed(device, y, col, state);
}
// ---------------- UI ----------------
void showTitle() {
// Proper P O N G (16x8)
byte title[8][16] = {
// P O N G
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0},
{1,1,1,0, 0,1,1,0, 1,0,0,1, 0,1,1,1},
{1,0,1,0, 1,0,0,1, 1,1,0,1, 1,0,0,0},
{1,1,1,0, 1,0,0,1, 1,1,1,1, 1,0,0,0},
{1,0,0,0, 1,0,0,1, 1,0,1,1, 1,0,1,1},
{1,0,0,0, 1,0,0,1, 1,0,0,1, 1,0,0,1},
{1,0,0,0, 0,1,1,0, 1,0,0,1, 0,1,1,1},
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0},
};
lc.clearDisplay(0);
lc.clearDisplay(1);
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 16; x++) {
setPixel(x, y, title[y][x]);
}
}
delay(2000);
}
// ---------------- SCORE DISPLAY ----------------
void showScore() {
lc.clearDisplay(0);
lc.clearDisplay(1);
for (int i = 0; i < playerScore; i++) {
lc.setLed(0, 7, i, true);
}
for (int i = 0; i < aiScore; i++) {
lc.setLed(1, 7, i, true);
}
delay(1000);
}
// ---------------- COUNTDOWN ----------------
void countdown() {
for (int i = 3; i > 0; i--) {
lc.clearDisplay(0);
lc.clearDisplay(1);
setPixel(7, 3, true);
delay(300);
lc.clearDisplay(0);
lc.clearDisplay(1);
delay(300);
}
}
*/