Skip to content

Commit f98debf

Browse files
committed
Implement Pong game!
Signed-off-by: Daniel Schaefer <[email protected]>
1 parent acbfd60 commit f98debf

File tree

6 files changed

+244
-16
lines changed

6 files changed

+244
-16
lines changed

control.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@
3232
WIDTH = 9
3333
HEIGHT = 34
3434

35+
ARG_UP = 0
36+
ARG_DOWN = 1
37+
ARG_LEFT = 2
38+
ARG_RIGHT = 3
39+
ARG_QUIT = 4
40+
ARG_2LEFT = 5
41+
ARG_2RIGHT = 6
42+
3543
SERIAL_DEV = None
3644

3745
STOP_THREAD = False
@@ -82,6 +90,8 @@ def main():
8290
parser.add_argument("--snake", help="Snake", action="store_true")
8391
parser.add_argument("--snake-embedded",
8492
help="Snake on the module", action="store_true")
93+
parser.add_argument("--pong-embedded",
94+
help="Pong on the module", action="store_true")
8595
parser.add_argument(
8696
"--all-brightnesses", help="Show every pixel in a different brightness", action="store_true")
8797
parser.add_argument("-v", "--version",
@@ -145,6 +155,8 @@ def main():
145155
snake()
146156
elif args.snake_embedded:
147157
snake_embedded()
158+
elif args.pong_embedded:
159+
pong_embedded()
148160
elif args.eq is not None:
149161
eq(args.eq)
150162
elif args.random_eq:
@@ -444,6 +456,32 @@ def game_over():
444456
time.sleep(0.75)
445457

446458

459+
def pong_embedded():
460+
# Start game
461+
command = FWK_MAGIC + [0x10, 0x01]
462+
send_command(command)
463+
464+
from getkey import getkey, keys
465+
466+
while True:
467+
key_arg = None
468+
key = getkey()
469+
if key == keys.LEFT:
470+
key_arg = ARG_LEFT
471+
elif key == keys.RIGHT:
472+
key_arg = ARG_RIGHT
473+
elif key == 'a':
474+
key_arg = ARG_2LEFT
475+
elif key == 'd':
476+
key_arg = ARG_2RIGHT
477+
elif key == 'q':
478+
# Quit
479+
key_arg = ARG_QUIT
480+
if key_arg is not None:
481+
command = FWK_MAGIC + [0x11, key_arg]
482+
send_command(command)
483+
484+
447485
def snake_embedded():
448486
# Start game
449487
command = FWK_MAGIC + [0x10, 0x00]

src/bin/ledmatrix.rs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ use core::fmt::Write;
100100
use heapless::String;
101101

102102
use lotus_input::control::*;
103-
use lotus_input::games::snake;
103+
use lotus_input::games::{pong, snake};
104104
use lotus_input::lotus::LotusLedMatrix;
105105
use lotus_input::matrix::*;
106106
use lotus_input::patterns::*;
@@ -303,23 +303,36 @@ fn main() -> ! {
303303
}
304304

305305
// Handle game state
306-
if timer.get_counter().ticks() > game_timer + 500_000 {
306+
let game_step_diff = match state.game {
307+
Some(GameState::Pong(ref pong_state)) => 100_000 - 5_000 * pong_state.speed,
308+
Some(GameState::Snake(_)) => 500_000,
309+
_ => 500_000,
310+
};
311+
if timer.get_counter().ticks() > game_timer + game_step_diff {
307312
let _ = serial.write(b"Game step\r\n");
308-
if let Some(GameState::Snake(_)) = state.game {
309-
let random = get_random_byte(&rosc);
310-
let (direction, game_over, points, (x, y)) = snake::game_step(&mut state, random);
311-
312-
if game_over {
313-
} else {
314-
let mut text: String<64> = String::new();
315-
write!(
316-
&mut text,
317-
"Dir: {:?} Status: {}, Points: {}, Head: ({},{})\r\n",
318-
direction, game_over, points, x, y
319-
)
320-
.unwrap();
321-
let _ = serial.write(text.as_bytes());
313+
let random = get_random_byte(&rosc);
314+
match state.game {
315+
Some(GameState::Pong(_)) => {
316+
pong::game_step(&mut state, random);
317+
}
318+
Some(GameState::Snake(_)) => {
319+
let (direction, game_over, points, (x, y)) =
320+
snake::game_step(&mut state, random);
321+
322+
if game_over {
323+
// TODO: Show score
324+
} else {
325+
let mut text: String<64> = String::new();
326+
write!(
327+
&mut text,
328+
"Dir: {:?} Status: {}, Points: {}, Head: ({},{})\r\n",
329+
direction, game_over, points, x, y
330+
)
331+
.unwrap();
332+
let _ = serial.write(text.as_bytes());
333+
}
322334
}
335+
None => {}
323336
}
324337
game_timer = timer.get_counter().ticks();
325338
}

src/control.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use embedded_graphics::{
1313
#[cfg(feature = "b1display")]
1414
use heapless::String;
1515

16+
#[cfg(feature = "ledmatrix")]
17+
use crate::games::pong;
1618
#[cfg(feature = "ledmatrix")]
1719
use crate::games::snake;
1820
#[cfg(feature = "ledmatrix")]
@@ -51,6 +53,7 @@ pub enum PatternVals {
5153

5254
pub enum Game {
5355
Snake,
56+
Pong,
5457
}
5558

5659
#[derive(Clone)]
@@ -60,6 +63,8 @@ pub enum GameControlArg {
6063
Left,
6164
Right,
6265
Exit,
66+
SecondLeft,
67+
SecondRight,
6368
}
6469

6570
// TODO: Reduce size for modules that don't require other commands
@@ -182,6 +187,7 @@ pub fn parse_module_command(count: usize, buf: &[u8]) -> Option<Command> {
182187
0x08 => Some(Command::DrawGreyColBuffer),
183188
0x10 => match arg {
184189
Some(0) => Some(Command::StartGame(Game::Snake)),
190+
Some(1) => Some(Command::StartGame(Game::Pong)),
185191
_ => None,
186192
},
187193
0x11 => match arg {
@@ -190,6 +196,8 @@ pub fn parse_module_command(count: usize, buf: &[u8]) -> Option<Command> {
190196
Some(2) => Some(Command::GameControl(GameControlArg::Left)),
191197
Some(3) => Some(Command::GameControl(GameControlArg::Right)),
192198
Some(4) => Some(Command::GameControl(GameControlArg::Exit)),
199+
Some(5) => Some(Command::GameControl(GameControlArg::SecondLeft)),
200+
Some(6) => Some(Command::GameControl(GameControlArg::SecondRight)),
193201
_ => None,
194202
},
195203
0x12 => Some(Command::GameStatus),
@@ -340,12 +348,14 @@ pub fn handle_command(
340348
Command::StartGame(game) => {
341349
match game {
342350
Game::Snake => snake::start_game(state, random),
351+
Game::Pong => pong::start_game(state, random),
343352
}
344353
None
345354
}
346355
Command::GameControl(arg) => {
347356
match state.game {
348357
Some(GameState::Snake(_)) => snake::handle_control(state, arg),
358+
Some(GameState::Pong(_)) => pong::handle_control(state, arg),
349359
_ => {}
350360
}
351361
None

src/games/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod pong;
12
pub mod snake;

src/games/pong.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use crate::control::GameControlArg;
2+
use crate::matrix::{GameState, Grid, State, HEIGHT, LEDS, WIDTH};
3+
4+
use heapless::Vec;
5+
6+
const PADDLE_WIDTH: usize = 5;
7+
8+
#[derive(Clone)]
9+
struct Score {
10+
upper: u8,
11+
lower: u8,
12+
}
13+
14+
type Position = (usize, usize);
15+
type Velocity = (i8, i8);
16+
17+
#[derive(Clone)]
18+
struct Ball {
19+
pos: Position,
20+
// Not a position, more like a directional vector
21+
direction: Velocity,
22+
}
23+
24+
#[derive(Clone)]
25+
pub struct PongState {
26+
score: Score,
27+
ball: Ball,
28+
paddles: (usize, usize),
29+
pub speed: u64,
30+
}
31+
32+
pub fn start_game(state: &mut State, random: u8) {
33+
state.game = Some(GameState::Pong(PongState {
34+
score: Score { upper: 0, lower: 0 },
35+
ball: Ball {
36+
pos: (4, 20),
37+
direction: (0, 1),
38+
},
39+
paddles: (PADDLE_WIDTH / 2, PADDLE_WIDTH / 2),
40+
speed: 0,
41+
}))
42+
}
43+
pub fn handle_control(state: &mut State, arg: &GameControlArg) {
44+
if let Some(GameState::Pong(ref mut pong_state)) = state.game {
45+
match arg {
46+
GameControlArg::Left => {
47+
if pong_state.paddles.0 + PADDLE_WIDTH < WIDTH {
48+
pong_state.paddles.0 += 1;
49+
}
50+
}
51+
GameControlArg::Right => {
52+
if pong_state.paddles.0 >= 1 {
53+
pong_state.paddles.0 -= 1;
54+
}
55+
}
56+
GameControlArg::SecondLeft => {
57+
if pong_state.paddles.1 + PADDLE_WIDTH < WIDTH {
58+
pong_state.paddles.1 += 1;
59+
}
60+
}
61+
GameControlArg::SecondRight => {
62+
if pong_state.paddles.1 >= 1 {
63+
pong_state.paddles.1 -= 1;
64+
}
65+
}
66+
//GameControlArg::Exit => {
67+
_ => {
68+
// TODO
69+
}
70+
}
71+
}
72+
}
73+
74+
fn random_v(random: u8) -> Velocity {
75+
// TODO: while food == head:
76+
let x = ((random & 0xF0) >> 4) % WIDTH as u8;
77+
let y = (random & 0x0F) % HEIGHT as u8;
78+
(x as i8, y as i8)
79+
}
80+
81+
fn add_velocity(pos: Position, v: Velocity) -> Position {
82+
let (vx, vy) = v;
83+
let (x, y) = pos;
84+
(((x as i8) + vx) as usize, ((y as i8) + vy) as usize)
85+
}
86+
87+
fn hit_paddle(ball: Position, paddles: (usize, usize)) -> Option<usize> {
88+
let (x, y) = ball;
89+
if y == 1 && paddles.0 <= x && x <= paddles.0 + PADDLE_WIDTH {
90+
Some((((paddles.0) as i32) - (x as i32)).abs() as usize)
91+
} else if y == HEIGHT - 2 && paddles.1 <= x && x <= paddles.1 + PADDLE_WIDTH {
92+
Some((((paddles.1) as i32) - (x as i32)).abs() as usize)
93+
} else {
94+
None
95+
}
96+
}
97+
98+
pub fn game_step(state: &mut State, random: u8) {
99+
if let Some(GameState::Pong(ref mut pong_state)) = state.game {
100+
pong_state.ball.pos = {
101+
let (vx, vy) = pong_state.ball.direction;
102+
let (x, y) = pong_state.ball.pos;
103+
let (x, y) = add_velocity(pong_state.ball.pos, pong_state.ball.direction);
104+
let x = if x < 0 {
105+
0
106+
} else if x > WIDTH - 1 {
107+
WIDTH - 1
108+
} else {
109+
x
110+
};
111+
if x == 0 || x == WIDTH - 1 {
112+
// Hit wall, bounce back
113+
pong_state.ball.direction = (-vx, vy);
114+
}
115+
116+
let y = if y < 0 {
117+
0
118+
} else if y > HEIGHT - 1 {
119+
HEIGHT - 1
120+
} else {
121+
y
122+
};
123+
let (x, y) = if let Some(paddle_hit) = hit_paddle((x, y), pong_state.paddles) {
124+
// Hit paddle, bounce back
125+
// TODO: Change vy direction slightly depending on where the paddle was hit
126+
let (vx, vy) = pong_state.ball.direction;
127+
pong_state.ball.direction = match paddle_hit {
128+
0 => (vx - 2, -vy),
129+
1 => (vx - 1, -vy),
130+
2 => (vx, -vy),
131+
3 => (vx + 1, -vy),
132+
4 => (vx + 2, -vy),
133+
// Shouldn't occur
134+
_ => (vx, -vy),
135+
};
136+
// TODO: Not sure if I want the speed to change. Speed by angle change is already high enough
137+
//pong_state.speed += 1;
138+
(x, y)
139+
} else if y == 0 || y == HEIGHT - 1 {
140+
pong_state.speed = 0;
141+
pong_state.ball.direction = (1, 1); //random_v(random);
142+
(WIDTH / 2, HEIGHT / 2)
143+
} else {
144+
(x, y)
145+
};
146+
(x, y)
147+
};
148+
state.grid = draw_matrix(&pong_state);
149+
}
150+
}
151+
152+
fn draw_matrix(state: &PongState) -> Grid {
153+
let mut grid = Grid::default();
154+
155+
for x in state.paddles.0..state.paddles.0 + PADDLE_WIDTH {
156+
grid.0[x][0] = 0xFF;
157+
}
158+
for x in state.paddles.1..state.paddles.1 + PADDLE_WIDTH {
159+
grid.0[x][HEIGHT - 1] = 0xFF;
160+
}
161+
grid.0[state.ball.pos.0 as usize][state.ball.pos.1 as usize] = 0xFF;
162+
163+
grid
164+
}

src/matrix.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::games::pong::PongState;
12
use crate::games::snake::SnakeState;
23

34
pub const WIDTH: usize = 9;
@@ -31,4 +32,5 @@ pub enum SleepState {
3132
#[derive(Clone)]
3233
pub enum GameState {
3334
Snake(SnakeState),
35+
Pong(PongState),
3436
}

0 commit comments

Comments
 (0)