Skip to content

Commit 7d9ab73

Browse files
committed
Implement Snake in firmware
Signed-off-by: Daniel Schaefer <[email protected]>
1 parent a92e2c3 commit 7d9ab73

File tree

5 files changed

+172
-13
lines changed

5 files changed

+172
-13
lines changed

control.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ def main():
8080
"--random-eq", help="Random Equalizer", action="store_true")
8181
parser.add_argument("--wpm", help="WPM Demo", action="store_true")
8282
parser.add_argument("--snake", help="Snake", action="store_true")
83+
parser.add_argument("--snake-embedded",
84+
help="Snake on the module", action="store_true")
8385
parser.add_argument(
8486
"--all-brightnesses", help="Show every pixel in a different brightness", action="store_true")
8587
parser.add_argument("-v", "--version",
@@ -141,6 +143,8 @@ def main():
141143
wpm_demo()
142144
elif args.snake:
143145
snake()
146+
elif args.snake_embedded:
147+
snake_embedded()
144148
elif args.eq is not None:
145149
eq(args.eq)
146150
elif args.random_eq:
@@ -391,7 +395,7 @@ def opposite_direction(direction):
391395
return direction
392396

393397

394-
def keyscan():
398+
def snake_keyscan():
395399
from getkey import getkey, keys
396400
global direction
397401
global body
@@ -406,6 +410,28 @@ def keyscan():
406410
direction = key
407411

408412

413+
def snake_embedded_keyscan():
414+
from getkey import getkey, keys
415+
416+
while True:
417+
key_arg = None
418+
key = getkey()
419+
if key == keys.UP:
420+
key_arg = 0
421+
elif key == keys.DOWN:
422+
key_arg = 1
423+
elif key == keys.LEFT:
424+
key_arg = 2
425+
elif key == keys.RIGHT:
426+
key_arg = 3
427+
elif key == 'q':
428+
# Quit
429+
key_arg = 4
430+
if key_arg is not None:
431+
command = FWK_MAGIC + [0x11, key_arg]
432+
send_command(command)
433+
434+
409435
def game_over():
410436
global body
411437
while True:
@@ -418,6 +444,14 @@ def game_over():
418444
time.sleep(0.75)
419445

420446

447+
def snake_embedded():
448+
# Start game
449+
command = FWK_MAGIC + [0x10, 0x00]
450+
send_command(command)
451+
452+
snake_embedded_keyscan()
453+
454+
421455
def snake():
422456
from getkey import keys
423457
global direction
@@ -432,7 +466,7 @@ def snake():
432466
# Setting
433467
WRAP = False
434468

435-
thread = threading.Thread(target=keyscan, args=(), daemon=True)
469+
thread = threading.Thread(target=snake_keyscan, args=(), daemon=True)
436470
thread.start()
437471

438472
prev = datetime.now()
@@ -664,7 +698,7 @@ def send_command(command, with_response=False):
664698

665699
if with_response:
666700
res = s.read(RESPONSE_SIZE)
667-
#print(f"Received: {res}")
701+
# print(f"Received: {res}")
668702
return res
669703

670704

@@ -721,8 +755,8 @@ def gui():
721755
[sg.Button("Send '2 5 degC thunder'", k='-SEND-TEXT-')],
722756

723757
# TODO
724-
#[sg.Text("Play Snake")],
725-
#[sg.Button("Start Game", k='-PLAY-SNAKE-')],
758+
# [sg.Text("Play Snake")],
759+
# [sg.Button("Start Game", k='-PLAY-SNAKE-')],
726760

727761
[sg.Text("Equalizer")],
728762
[

src/bin/ledmatrix.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use cortex_m::delay::Delay;
88
use defmt_rtt as _;
99
use embedded_hal::digital::v2::{InputPin, OutputPin};
1010

11-
use rp2040_hal::gpio::bank0::Gpio29;
11+
use rp2040_hal::{
12+
gpio::bank0::Gpio29,
13+
rosc::{Enabled, RingOscillator},
14+
};
1215
//#[cfg(debug_assertions)]
1316
//use panic_probe as _;
1417
use rp2040_panic_usb_boot as _;
@@ -97,6 +100,7 @@ use core::fmt::Write;
97100
use heapless::String;
98101

99102
use lotus_input::control::*;
103+
use lotus_input::games::snake;
100104
use lotus_input::lotus::LotusLedMatrix;
101105
use lotus_input::matrix::*;
102106
use lotus_input::patterns::*;
@@ -127,6 +131,9 @@ fn main() -> ! {
127131
)
128132
.ok()
129133
.unwrap();
134+
//rp2040_pac::rosc::RANDOMBIT::read(&self)
135+
let rosc = rp2040_hal::rosc::RingOscillator::new(pac.ROSC);
136+
let rosc = rosc.initialize();
130137

131138
let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
132139

@@ -188,6 +195,7 @@ fn main() -> ! {
188195
animate: false,
189196
brightness: 120,
190197
sleeping: SleepState::Awake,
198+
game: None,
191199
};
192200

193201
let mut matrix = LotusLedMatrix::configure(i2c);
@@ -205,6 +213,7 @@ fn main() -> ! {
205213

206214
let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
207215
let mut prev_timer = timer.get_counter().ticks();
216+
let mut game_timer = timer.get_counter().ticks();
208217

209218
loop {
210219
// TODO: Current hardware revision does not have the sleep pin wired up :(
@@ -250,6 +259,7 @@ fn main() -> ! {
250259
// Do nothing
251260
}
252261
Ok(count) => {
262+
let random = get_random_byte(&rosc);
253263
match (parse_command(count, &buf), &state.sleeping) {
254264
(Some(Command::Sleep(go_sleeping)), _) => {
255265
handle_sleep(
@@ -262,14 +272,25 @@ fn main() -> ! {
262272
}
263273
(Some(c @ Command::BootloaderReset), _)
264274
| (Some(c @ Command::IsSleeping), _) => {
265-
if let Some(response) = handle_command(&c, &mut state, &mut matrix) {
275+
if let Some(response) =
276+
handle_command(&c, &mut state, &mut matrix, random)
277+
{
266278
let _ = serial.write(&response);
267279
};
268280
}
269281
(Some(command), SleepState::Awake) => {
282+
let mut text: String<64> = String::new();
283+
write!(
284+
&mut text,
285+
"Handling command {}:{}:{}:{}\r\n",
286+
buf[0], buf[1], buf[2], buf[3]
287+
)
288+
.unwrap();
289+
let _ = serial.write(text.as_bytes());
290+
270291
// While sleeping no command is handled, except waking up
271292
if let Some(response) =
272-
handle_command(&command, &mut state, &mut matrix)
293+
handle_command(&command, &mut state, &mut matrix, random)
273294
{
274295
let _ = serial.write(&response);
275296
};
@@ -280,7 +301,37 @@ fn main() -> ! {
280301
}
281302
}
282303
}
304+
305+
// Handle game state
306+
if timer.get_counter().ticks() > game_timer + 500_000 {
307+
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());
322+
}
323+
}
324+
game_timer = timer.get_counter().ticks();
325+
}
326+
}
327+
}
328+
329+
fn get_random_byte(rosc: &RingOscillator<Enabled>) -> u8 {
330+
let mut byte = 0;
331+
for i in 0..8 {
332+
byte += (rosc.get_random_bit() as u8) << i;
283333
}
334+
byte
284335
}
285336

286337
fn handle_sleep(

src/control.rs

Lines changed: 68 additions & 5 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::snake;
1618
#[cfg(feature = "ledmatrix")]
1719
use crate::matrix::*;
1820
#[cfg(feature = "ledmatrix")]
@@ -30,6 +32,9 @@ pub enum _CommandVals {
3032
_StageGreyCol = 0x07,
3133
_DrawGreyColBuffer = 0x08,
3234
SetText = 0x09,
35+
StartGame = 0x10,
36+
GameControl = 0x11,
37+
GameStatus = 0x12,
3338
Version = 0x20,
3439
}
3540

@@ -44,6 +49,19 @@ pub enum PatternVals {
4449
DisplayLotus2,
4550
}
4651

52+
pub enum Game {
53+
Snake,
54+
}
55+
56+
#[derive(Clone)]
57+
pub enum GameControlArg {
58+
Up,
59+
Down,
60+
Left,
61+
Right,
62+
Exit,
63+
}
64+
4765
// TODO: Reduce size for modules that don't require other commands
4866
pub enum Command {
4967
/// Get current brightness scaling
@@ -72,6 +90,9 @@ pub enum Command {
7290
DrawGreyColBuffer,
7391
#[cfg(feature = "b1display")]
7492
SetText(String<64>),
93+
StartGame(Game),
94+
GameControl(GameControlArg),
95+
GameStatus,
7596
Version,
7697
_Unknown,
7798
}
@@ -159,6 +180,19 @@ pub fn parse_module_command(count: usize, buf: &[u8]) -> Option<Command> {
159180
}
160181
}
161182
0x08 => Some(Command::DrawGreyColBuffer),
183+
0x10 => match arg {
184+
Some(0) => Some(Command::StartGame(Game::Snake)),
185+
_ => None,
186+
},
187+
0x11 => match arg {
188+
Some(0) => Some(Command::GameControl(GameControlArg::Up)),
189+
Some(1) => Some(Command::GameControl(GameControlArg::Down)),
190+
Some(2) => Some(Command::GameControl(GameControlArg::Left)),
191+
Some(3) => Some(Command::GameControl(GameControlArg::Right)),
192+
Some(4) => Some(Command::GameControl(GameControlArg::Exit)),
193+
_ => None,
194+
},
195+
0x12 => Some(Command::GameStatus),
162196
_ => None,
163197
}
164198
} else {
@@ -218,14 +252,19 @@ pub fn handle_generic_command(command: &Command) -> Option<[u8; 32]> {
218252
response[0] = bcd_device[0];
219253
response[1] = bcd_device[1];
220254
response[2] = is_pre_release() as u8;
221-
return Some(response);
255+
Some(response)
222256
}
223257
_ => None,
224258
}
225259
}
226260

227261
#[cfg(feature = "ledmatrix")]
228-
pub fn handle_command(command: &Command, state: &mut State, matrix: &mut Foo) -> Option<[u8; 32]> {
262+
pub fn handle_command(
263+
command: &Command,
264+
state: &mut State,
265+
matrix: &mut Foo,
266+
random: u8,
267+
) -> Option<[u8; 32]> {
229268
match command {
230269
Command::GetBrightness => {
231270
let mut response: [u8; 32] = [0; 32];
@@ -238,10 +277,12 @@ pub fn handle_command(command: &Command, state: &mut State, matrix: &mut Foo) ->
238277
matrix
239278
.set_scaling(state.brightness)
240279
.expect("failed to set scaling");
280+
None
241281
}
242282
Command::Percentage(p) => {
243283
//let p = if count >= 5 { buf[4] } else { 100 };
244284
state.grid = percentage(*p as u16);
285+
None
245286
}
246287
Command::Pattern(pattern) => {
247288
//let _ = serial.write("Pattern".as_bytes());
@@ -261,22 +302,31 @@ pub fn handle_command(command: &Command, state: &mut State, matrix: &mut Foo) ->
261302
PatternVals::DisplayLotus2 => state.grid = display_lotus2(),
262303
_ => {}
263304
}
305+
None
306+
}
307+
Command::SetAnimate(a) => {
308+
state.animate = *a;
309+
None
264310
}
265-
Command::SetAnimate(a) => state.animate = *a,
266311
Command::GetAnimate => {
267312
let mut response: [u8; 32] = [0; 32];
268313
response[0] = state.animate as u8;
269314
return Some(response);
270315
}
271-
Command::Draw(vals) => state.grid = draw(vals),
316+
Command::Draw(vals) => {
317+
state.grid = draw(vals);
318+
None
319+
}
272320
Command::StageGreyCol(col, vals) => {
273321
draw_grey_col(&mut state.col_buffer, *col, vals);
322+
None
274323
}
275324
Command::DrawGreyColBuffer => {
276325
// Copy the staging buffer to the real grid and display it
277326
state.grid = state.col_buffer.clone();
278327
// Zero the old staging buffer, just for good measure.
279328
state.col_buffer = percentage(0);
329+
None
280330
}
281331
// TODO: Move to handle_generic_command
282332
Command::IsSleeping => {
@@ -287,10 +337,23 @@ pub fn handle_command(command: &Command, state: &mut State, matrix: &mut Foo) ->
287337
};
288338
return Some(response);
289339
}
340+
Command::StartGame(game) => {
341+
match game {
342+
Game::Snake => snake::start_game(state, random),
343+
}
344+
None
345+
}
346+
Command::GameControl(arg) => {
347+
match state.game {
348+
Some(GameState::Snake(_)) => snake::handle_control(state, arg),
349+
_ => {}
350+
}
351+
None
352+
}
353+
Command::GameStatus => None,
290354
// TODO: Make it return something
291355
_ => return handle_generic_command(command),
292356
}
293-
None
294357
}
295358

296359
#[cfg(feature = "b1display")]

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![no_std]
22

3+
#[cfg(feature = "ledmatrix")]
4+
pub mod games;
35
#[cfg(feature = "ledmatrix")]
46
pub mod lotus;
57
#[cfg(feature = "ledmatrix")]

0 commit comments

Comments
 (0)