This program is compatible with the Arduino Uno R3, a 4×4 matrix keypad, and a 16-pin 16×2 character LCD operating in 4-bit parallel mode.
WIRING For lcd screen:
LCD 1 VSS/GND → Arduino GND
LCD 2 VDD/VCC → Arduino 5V
LCD 3 VO → Arduino GND
LCD 4 RS → Arduino D6
LCD 5 RW → Arduino GND
LCD 6 E → Arduino D7
LCD 7 D0 → NOT CONNECTED
LCD 8 D1 → NOT CONNECTED
LCD 9 D2 → NOT CONNECTED
LCD 10 D3 → NOT CONNECTED
LCD 11 D4 → Arduino D10
LCD 12 D5 → Arduino D11
LCD 13 D6 → Arduino D12
LCD 14 D7 → Arduino D8
LCD 15 LED+ → Arduino 5V
LCD 16 LED− → Arduino GND
WIRING for keypad:
Keypad R1 → D2
Keypad R2 → D3
Keypad R3 → D4
Keypad R4 → D5
Keypad C1 → D13
Keypad C2 → A0
Keypad C3 → A1
Keypad C4 → A2
PROGRAM:
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <EEPROM.h>
// =====================================================
// LCD
// =====================================================
LiquidCrystal lcd(6, 7, 8, 10, 11, 12);
// =====================================================
// KEYPAD
//
// 1 2 3 A
// 4 5 6 B
// 7 8 9 C
// * 0 # D
// =====================================================
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {
2, 3, 4, 5
};
byte colPins[COLS] = {
13, A0, A1, A2
};
Keypad keypad = Keypad(
makeKeymap(keys),
rowPins,
colPins,
ROWS,
COLS
);
// =====================================================
// LIFETIME SCORE
// =====================================================
unsigned long lifetimeScore = 0;
const int EEPROM_SCORE_ADDRESS = 0;
// =====================================================
// SCREEN
// =====================================================
const int SCREEN_WIDTH = 80;
const int SCREEN_HEIGHT = 8;
// =====================================================
// PADDLES
// =====================================================
const int PADDLE_HEIGHT = 4;
const int PLAYER_X = 1;
const int CPU_X = 78;
float playerY = 2;
float cpuY = 2;
// =====================================================
// BALL
// =====================================================
float ballX = 40;
float ballY = 4;
float ballDX = 0;
float ballDY = 0;
// =====================================================
// SCORE
// =====================================================
byte playerScore = 0;
byte cpuScore = 0;
const byte WIN_SCORE = 5;
// =====================================================
// AI DIFFICULTY
// =====================================================
byte aiDifficulty = 1;
// =====================================================
// GAME STATE
// =====================================================
bool gameRunning = false;
// =====================================================
// TIMERS
// =====================================================
unsigned long lastFrame = 0;
unsigned long lastAIUpdate = 0;
// =====================================================
// AI SETTINGS
// =====================================================
unsigned long aiReactionDelay;
float aiMoveAmount;
byte aiAccuracy;
// =====================================================
// BALL SPEED
// =====================================================
float ballSpeed;
// =====================================================
// GRAPHICS BUFFER
// =====================================================
byte screenBuffer[16][8];
// =====================================================
// LOAD LIFETIME SCORE
// =====================================================
void loadLifetimeScore() {
EEPROM.get(
EEPROM_SCORE_ADDRESS,
lifetimeScore
);
if (
lifetimeScore > 1000000UL
) {
lifetimeScore = 0;
EEPROM.put(
EEPROM_SCORE_ADDRESS,
lifetimeScore
);
}
}
// =====================================================
// SAVE LIFETIME SCORE
// =====================================================
void saveLifetimeScore() {
EEPROM.put(
EEPROM_SCORE_ADDRESS,
lifetimeScore
);
}
// =====================================================
// ADD LIFETIME POINT
// =====================================================
void addLifetimePoint() {
lifetimeScore++;
saveLifetimeScore();
}
// =====================================================
// CLEAR GRAPHICS BUFFER
// =====================================================
void clearBuffer() {
for (
byte x = 0;
x < 16;
x++
) {
for (
byte y = 0;
y < 8;
y++
) {
screenBuffer[x][y] = 0;
}
}
}
// =====================================================
// SET PIXEL
// =====================================================
void setPixel(
int x,
int y
) {
if (
x < 0 ||
x >= SCREEN_WIDTH ||
y < 0 ||
y >= SCREEN_HEIGHT
) {
return;
}
int characterX =
x / 5;
int pixelX =
x % 5;
screenBuffer[characterX][y] |=
(1 << (4 - pixelX));
}
// =====================================================
// DRAW PADDLE
// =====================================================
void drawPaddle(
int x,
int y
) {
for (
int i = 0;
i < PADDLE_HEIGHT;
i++
) {
setPixel(
x,
y + i
);
}
}
// =====================================================
// DRAW BALL
// =====================================================
void drawBall() {
setPixel(
round(ballX),
round(ballY)
);
}
// =====================================================
// RENDER GAME
// =====================================================
void renderGame() {
clearBuffer();
drawPaddle(
PLAYER_X,
round(playerY)
);
drawPaddle(
CPU_X,
round(cpuY)
);
drawBall();
// ---------------------------------------------------
// CUSTOM CHARACTER MANAGEMENT
// ---------------------------------------------------
byte patterns[8][8];
byte patternCount = 0;
byte characterNumber[16];
bool hasPattern[16];
for (
byte i = 0;
i < 16;
i++
) {
characterNumber[i] = 0;
hasPattern[i] = false;
}
// ---------------------------------------------------
// FIND UNIQUE PATTERNS
// ---------------------------------------------------
for (
byte x = 0;
x < 16;
x++
) {
bool empty = true;
for (
byte y = 0;
y < 8;
y++
) {
if (
screenBuffer[x][y] != 0
) {
empty = false;
break;
}
}
if (empty) {
continue;
}
int found = -1;
for (
byte p = 0;
p < patternCount;
p++
) {
bool same = true;
for (
byte y = 0;
y < 8;
y++
) {
if (
patterns[p][y] !=
screenBuffer[x][y]
) {
same = false;
break;
}
}
if (same) {
found = p;
break;
}
}
if (
found == -1 &&
patternCount < 8
) {
for (
byte y = 0;
y < 8;
y++
) {
patterns[patternCount][y] =
screenBuffer[x][y];
}
characterNumber[x] =
patternCount;
hasPattern[x] =
true;
patternCount++;
} else {
characterNumber[x] =
found;
hasPattern[x] =
true;
}
}
// ---------------------------------------------------
// LOAD CUSTOM CHARACTERS
// ---------------------------------------------------
for (
byte i = 0;
i < patternCount;
i++
) {
lcd.createChar(
i,
patterns[i]
);
}
// ---------------------------------------------------
// DRAW TOP ROW
// ---------------------------------------------------
for (
byte x = 0;
x < 16;
x++
) {
lcd.setCursor(
x,
0
);
if (
hasPattern[x]
) {
lcd.write(
characterNumber[x]
);
} else {
lcd.print(
" "
);
}
}
// ---------------------------------------------------
// BOTTOM SCORE
// ---------------------------------------------------
lcd.setCursor(
0,
1
);
lcd.print(
"P:"
);
lcd.print(
playerScore
);
lcd.print(
" C:"
);
lcd.print(
cpuScore
);
lcd.print(
" T:"
);
lcd.print(
lifetimeScore
);
}
// =====================================================
// GET AI NAME
// =====================================================
void getAIName(
char *name
) {
switch (
aiDifficulty
) {
case 1:
strcpy(
name,
"EASY"
);
break;
case 2:
strcpy(
name,
"NORMAL"
);
break;
case 3:
strcpy(
name,
"HARD"
);
break;
case 4:
strcpy(
name,
"EXPERT"
);
break;
case 5:
strcpy(
name,
"INSANE"
);
break;
}
}
// =====================================================
// SHOW MENU
// =====================================================
void showMenu() {
gameRunning = false;
lcd.clear();
char aiName[7];
getAIName(
aiName
);
// ---------------------------------------------------
// TOP ROW
// ---------------------------------------------------
lcd.setCursor(
0,
0
);
lcd.print(
"AI:"
);
lcd.print(
aiName
);
lcd.print(
" T:"
);
lcd.print(
lifetimeScore
);
// ---------------------------------------------------
// BOTTOM ROW
// ---------------------------------------------------
lcd.setCursor(
0,
1
);
lcd.print(
"A/B AI D START"
);
}
// =====================================================
// MENU LOOP
// =====================================================
void menuLoop() {
char key =
keypad.getKey();
if (!key) {
return;
}
// ===================================================
// DECREASE AI DIFFICULTY
// ===================================================
if (
key == 'A'
) {
if (
aiDifficulty > 1
) {
aiDifficulty--;
}
showMenu();
delay(150);
return;
}
// ===================================================
// INCREASE AI DIFFICULTY
// ===================================================
if (
key == 'B'
) {
if (
aiDifficulty < 5
) {
aiDifficulty++;
}
showMenu();
delay(150);
return;
}
// ===================================================
// START GAME
// ===================================================
if (
key == 'D'
) {
waitForKeyRelease();
startGame();
return;
}
}
// =====================================================
// CONFIGURE AI
// =====================================================
void configureAI() {
switch (
aiDifficulty
) {
// -------------------------------------------------
// EASY
// -------------------------------------------------
case 1:
aiReactionDelay = 600;
aiMoveAmount = 0.25;
aiAccuracy = 35;
ballSpeed = 0.60;
break;
// -------------------------------------------------
// NORMAL
// -------------------------------------------------
case 2:
aiReactionDelay = 450;
aiMoveAmount = 0.35;
aiAccuracy = 50;
ballSpeed = 0.70;
break;
// -------------------------------------------------
// HARD
// -------------------------------------------------
case 3:
aiReactionDelay = 300;
aiMoveAmount = 0.50;
aiAccuracy = 65;
ballSpeed = 0.80;
break;
// -------------------------------------------------
// EXPERT
// -------------------------------------------------
case 4:
aiReactionDelay = 180;
aiMoveAmount = 0.70;
aiAccuracy = 82;
ballSpeed = 0.95;
break;
// -------------------------------------------------
// INSANE
// -------------------------------------------------
case 5:
aiReactionDelay = 100;
aiMoveAmount = 0.85;
aiAccuracy = 94;
ballSpeed = 1.05;
break;
}
}
// =====================================================
// RESET BALL
// =====================================================
void resetBall(
bool towardPlayer
) {
ballX = 40;
ballY =
random(
1,
7
);
if (
towardPlayer
) {
ballDX =
-ballSpeed;
} else {
ballDX =
ballSpeed;
}
if (
random(
0,
2
) == 0
) {
ballDY =
0.20;
} else {
ballDY =
-0.20;
}
}
// =====================================================
// PLAYER CONTROLS
// =====================================================
void readPlayerControls() {
char key =
keypad.getKey();
if (!key) {
return;
}
// UP
if (
key == '1'
) {
playerY -= 1;
if (
playerY < 0
) {
playerY = 0;
}
}
// DOWN
if (
key == '4'
) {
playerY += 1;
if (
playerY >
SCREEN_HEIGHT -
PADDLE_HEIGHT
) {
playerY =
SCREEN_HEIGHT -
PADDLE_HEIGHT;
}
}
// MENU
if (
key == '*'
) {
gameRunning = false;
waitForKeyRelease();
showMenu();
}
}
// =====================================================
// CPU AI
// =====================================================
void updateCPU() {
if (
millis() -
lastAIUpdate <
aiReactionDelay
) {
return;
}
lastAIUpdate =
millis();
// Only react when ball is coming toward CPU.
if (
ballDX <= 0
) {
return;
}
float targetY =
ballY;
// AI can make mistakes.
if (
random(
0,
100
) >
aiAccuracy
) {
targetY +=
random(
-2,
3
);
}
float cpuCenter =
cpuY +
PADDLE_HEIGHT / 2.0;
// Move down
if (
targetY >
cpuCenter
) {
cpuY +=
aiMoveAmount;
}
// Move up
if (
targetY <
cpuCenter
) {
cpuY -=
aiMoveAmount;
}
// Keep CPU on screen.
if (
cpuY < 0
) {
cpuY = 0;
}
if (
cpuY >
SCREEN_HEIGHT -
PADDLE_HEIGHT
) {
cpuY =
SCREEN_HEIGHT -
PADDLE_HEIGHT;
}
}
// =====================================================
// BALL UPDATE
// =====================================================
void updateBall() {
ballX +=
ballDX;
ballY +=
ballDY;
// ---------------------------------------------------
// TOP WALL
// ---------------------------------------------------
if (
ballY <= 0
) {
ballY = 0;
ballDY =
abs(ballDY);
}
// ---------------------------------------------------
// BOTTOM WALL
// ---------------------------------------------------
if (
ballY >=
SCREEN_HEIGHT - 1
) {
ballY =
SCREEN_HEIGHT - 1;
ballDY =
-abs(ballDY);
}
// ---------------------------------------------------
// PLAYER PADDLE
// ---------------------------------------------------
if (
ballDX < 0 &&
ballX <= PLAYER_X + 1 &&
ballX >= PLAYER_X - 1
) {
if (
ballY >= playerY &&
ballY <
playerY + PADDLE_HEIGHT
) {
ballX =
PLAYER_X + 1;
ballDX =
abs(ballDX);
float hitPosition =
ballY -
(
playerY +
PADDLE_HEIGHT / 2.0
);
ballDY =
hitPosition * 0.30;
increaseBallSpeed();
}
}
// ---------------------------------------------------
// CPU PADDLE
// ---------------------------------------------------
if (
ballDX > 0 &&
ballX >= CPU_X - 1 &&
ballX <= CPU_X + 1
) {
if (
ballY >= cpuY &&
ballY <
cpuY + PADDLE_HEIGHT
) {
ballX =
CPU_X - 1;
ballDX =
-abs(ballDX);
float hitPosition =
ballY -
(
cpuY +
PADDLE_HEIGHT / 2.0
);
ballDY =
hitPosition * 0.30;
increaseBallSpeed();
}
}
// ===================================================
// PLAYER SCORES
// ===================================================
if (
ballX >= SCREEN_WIDTH
) {
playerScore++;
// Lifetime score increases permanently.
addLifetimePoint();
resetBall(false);
return;
}
// ===================================================
// CPU SCORES
// ===================================================
if (
ballX < 0
) {
cpuScore++;
resetBall(true);
return;
}
}
// =====================================================
// INCREASE BALL SPEED
// =====================================================
void increaseBallSpeed() {
float direction;
if (
ballDX > 0
) {
direction = 1;
} else {
direction = -1;
}
float speed =
abs(ballDX);
speed +=
0.025;
float maximumSpeed;
switch (
aiDifficulty
) {
case 1:
maximumSpeed = 0.90;
break;
case 2:
maximumSpeed = 1.00;
break;
case 3:
maximumSpeed = 1.15;
break;
case 4:
maximumSpeed = 1.30;
break;
default:
maximumSpeed = 1.45;
break;
}
if (
speed >
maximumSpeed
) {
speed =
maximumSpeed;
}
ballDX =
direction *
speed;
}
// =====================================================
// START GAME
// =====================================================
void startGame() {
configureAI();
playerScore = 0;
cpuScore = 0;
playerY = 2;
cpuY = 2;
resetBall(true);
lastAIUpdate =
millis();
lastFrame =
millis();
gameRunning = true;
lcd.clear();
renderGame();
}
// =====================================================
// GAME OVER
// =====================================================
void gameOver() {
gameRunning = false;
lcd.clear();
if (
playerScore >=
WIN_SCORE
) {
lcd.setCursor(
0,
0
);
lcd.print(
"YOU WIN!"
);
} else {
lcd.setCursor(
0,
0
);
lcd.print(
"CPU WINS"
);
}
lcd.setCursor(
0,
1
);
lcd.print(
"P:"
);
lcd.print(
playerScore
);
lcd.print(
" C:"
);
lcd.print(
cpuScore
);
lcd.print(
" T:"
);
lcd.print(
lifetimeScore
);
delay(1800);
showMenu();
}
// =====================================================
// WAIT FOR KEY RELEASE
// =====================================================
void waitForKeyRelease() {
while (
keypad.getKey()
) {
delay(10);
}
}
// =====================================================
// SETUP
// =====================================================
void setup() {
lcd.begin(
16,
2
);
randomSeed(
analogRead(A5)
);
loadLifetimeScore();
showMenu();
}
// =====================================================
// MAIN LOOP
// =====================================================
void loop() {
// ---------------------------------------------------
// MENU
// ---------------------------------------------------
if (!gameRunning) {
menuLoop();
return;
}
// ---------------------------------------------------
// PLAYER
// ---------------------------------------------------
readPlayerControls();
if (!gameRunning) {
return;
}
// ---------------------------------------------------
// CPU
// ---------------------------------------------------
updateCPU();
// ---------------------------------------------------
// GAME FRAME
// ---------------------------------------------------
if (
millis() -
lastFrame >=
45
) {
lastFrame =
millis();
updateBall();
// -------------------------------------------------
// WIN
// -------------------------------------------------
if (
playerScore >=
WIN_SCORE
) {
gameOver();
return;
}
// -------------------------------------------------
// CPU WIN
// -------------------------------------------------
if (
cpuScore >=
WIN_SCORE
) {
gameOver();
return;
}
renderGame();
}
}