1 /*
2 * Copyright (c) 2017 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/kernel.h>
8 #include <zephyr/sys/printk.h>
9 #include <zephyr/drivers/gpio.h>
10 #include <zephyr/device.h>
11 #include <string.h>
12 #include <zephyr/drivers/pwm.h>
13 #include <zephyr/debug/stack.h>
14
15 #include <zephyr/display/mb_display.h>
16
17 #include <zephyr/bluetooth/bluetooth.h>
18
19 #include "pong.h"
20
21 /* The micro:bit has a 5x5 LED display, using (x, y) notation the top-left
22 * corner has coordinates (0, 0) and the bottom-right has (4, 4). To make
23 * the game dynamics more natural, the uses a virtual 50x50 coordinate
24 * system where top-left is (0, 0) and bottom-right is (49, 49).
25 */
26
27 #define SCROLL_SPEED 400 /* Text scrolling speed */
28
29 #define PIXEL_SIZE 10 /* Virtual coordinates per real pixel */
30
31 #define GAME_REFRESH K_MSEC(100) /* Animation refresh rate of the game */
32
33 #define PADDLE_ROW 4 /* Real Y coordinate of the paddle */
34 #define PADDLE_MIN 0 /* Minimum paddle real X coordinate */
35 #define PADDLE_MAX 3 /* Maximum paddle real X coordinate */
36
37 #define BALL_VEL_Y_START -4 /* Default ball vertical speed */
38
39 #define BALL_POS_X_MIN 0 /* Maximum ball X coordinate */
40 #define BALL_POS_X_MAX 49 /* Maximum ball X coordinate */
41 #define BALL_POS_Y_MIN 0 /* Maximum ball Y coordinate */
42 #define BALL_POS_Y_MAX 39 /* Maximum ball Y coordinate */
43
44 #define START_THRESHOLD 100 /* Max time between A & B press */
45 #define RESTART_THRESHOLD (2 * MSEC_PER_SEC) /* Time before restart is
46 * allowed
47 */
48
49 #define REAL_TO_VIRT(r) ((r) * 10)
50 #define VIRT_TO_REAL(v) ((v) / 10)
51
52 /* Ball starting position (just to the left of the paddle mid-point) */
53 #define BALL_START (struct x_y){ 4, BALL_POS_Y_MAX }
54
55 struct x_y {
56 int x;
57 int y;
58 };
59
60 enum pong_state {
61 INIT,
62 MULTI,
63 SINGLE,
64 CONNECTED,
65 };
66
67 static enum pong_state pg_state = INIT;
68
69 struct pong_choice {
70 int val;
71 const char *str;
72 };
73
74 struct pong_selection {
75 const struct pong_choice *choice;
76 size_t choice_count;
77 void (*complete)(int val);
78 };
79
80 static int select_idx;
81 static const struct pong_selection *select;
82
83 static const struct pong_choice mode_choice[] = {
84 { SINGLE, "Single" },
85 { MULTI, "Multi" },
86 };
87
88 static bool remote_lost;
89 static bool started;
90 static int64_t ended;
91
92 static struct k_work_delayable refresh;
93
94 /* Semaphore to indicate that there was an update to the display */
95 static K_SEM_DEFINE(disp_update, 0, 1);
96
97 /* X coordinate of the left corner of the paddle */
98 static volatile int paddle_x = PADDLE_MIN;
99
100 /* Ball position */
101 static struct x_y ball_pos = BALL_START;
102
103 /* Ball velocity */
104 static struct x_y ball_vel = { 0, 0 };
105
106 static int64_t a_timestamp;
107 static int64_t b_timestamp;
108
109 #define SOUND_PERIOD_PADDLE PWM_USEC(200)
110 #define SOUND_PERIOD_WALL PWM_USEC(1000)
111
112 static const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_PATH(zephyr_user));
113
114 static enum sound_state {
115 SOUND_IDLE, /* No sound */
116 SOUND_PADDLE, /* Ball has hit the paddle */
117 SOUND_WALL, /* Ball has hit a wall */
118 } sound_state;
119
120 static const struct gpio_dt_spec sw0_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
121 static const struct gpio_dt_spec sw1_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios);
122
123 /* ensure SW0 & SW1 are on same gpio controller */
124 BUILD_ASSERT(DT_SAME_NODE(DT_GPIO_CTLR(DT_ALIAS(sw0), gpios), DT_GPIO_CTLR(DT_ALIAS(sw1), gpios)));
125
beep(uint32_t period)126 static inline void beep(uint32_t period)
127 {
128 pwm_set_dt(&pwm, period, period / 2);
129 }
130
sound_set(enum sound_state state)131 static void sound_set(enum sound_state state)
132 {
133 switch (state) {
134 case SOUND_IDLE:
135 beep(0);
136 break;
137 case SOUND_PADDLE:
138 beep(SOUND_PERIOD_PADDLE);
139 break;
140 case SOUND_WALL:
141 beep(SOUND_PERIOD_WALL);
142 break;
143 }
144
145 sound_state = state;
146 }
147
pong_select(const struct pong_selection * sel)148 static void pong_select(const struct pong_selection *sel)
149 {
150 struct mb_display *disp = mb_display_get();
151
152 if (select) {
153 printk("Other selection still busy\n");
154 return;
155 }
156
157 select = sel;
158 select_idx = 0;
159
160 mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
161 SCROLL_SPEED, "%s", select->choice[select_idx].str);
162 }
163
pong_select_change(void)164 static void pong_select_change(void)
165 {
166 struct mb_display *disp = mb_display_get();
167
168 select_idx = (select_idx + 1) % select->choice_count;
169 mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
170 SCROLL_SPEED, "%s", select->choice[select_idx].str);
171 }
172
pong_select_complete(void)173 static void pong_select_complete(void)
174 {
175 struct mb_display *disp = mb_display_get();
176 void (*complete)(int val) = select->complete;
177 int val = select->choice[select_idx].val;
178
179 mb_display_stop(disp);
180
181 select = NULL;
182 complete(val);
183 }
184
game_init(bool initiator)185 static void game_init(bool initiator)
186 {
187 started = false;
188 ended = 0;
189
190 ball_pos = BALL_START;
191 if (!initiator) {
192 ball_pos.y = -1;
193 }
194
195 paddle_x = PADDLE_MIN;
196
197 a_timestamp = 0;
198 b_timestamp = 0;
199 }
200
mode_selected(int val)201 static void mode_selected(int val)
202 {
203 struct mb_display *disp = mb_display_get();
204
205 pg_state = val;
206
207 switch (pg_state) {
208 case SINGLE:
209 game_init(true);
210 k_sem_give(&disp_update);
211 break;
212 case MULTI:
213 ble_connect();
214 mb_display_print(disp,
215 MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
216 SCROLL_SPEED, "Connecting...");
217 break;
218 default:
219 printk("Unknown state %d\n", pg_state);
220 return;
221 }
222 }
223
224 static const struct pong_selection mode_selection = {
225 .choice = mode_choice,
226 .choice_count = ARRAY_SIZE(mode_choice),
227 .complete = mode_selected,
228 };
229
ball_visible(void)230 static bool ball_visible(void)
231 {
232 return (ball_pos.y >= BALL_POS_Y_MIN);
233 }
234
check_start(void)235 static void check_start(void)
236 {
237 uint32_t delta;
238 uint8_t rnd;
239
240 if (!a_timestamp || !b_timestamp) {
241 return;
242 }
243
244 if (a_timestamp > b_timestamp) {
245 delta = a_timestamp - b_timestamp;
246 } else {
247 delta = b_timestamp - a_timestamp;
248 }
249
250 printk("delta %u ms\n", delta);
251
252 if (delta > START_THRESHOLD) {
253 return;
254 }
255
256 ball_vel.y = BALL_VEL_Y_START;
257
258 bt_rand(&rnd, sizeof(rnd));
259 rnd %= 8;
260
261 if (a_timestamp > b_timestamp) {
262 ball_vel.x = 2 + rnd;
263 } else {
264 ball_vel.x = -2 - rnd;
265 }
266
267 started = true;
268 remote_lost = false;
269 k_work_reschedule(&refresh, K_NO_WAIT);
270 }
271
game_ended(bool won)272 static void game_ended(bool won)
273 {
274 struct mb_display *disp = mb_display_get();
275
276 if (sound_state != SOUND_IDLE) {
277 sound_set(SOUND_IDLE);
278 }
279
280 remote_lost = won;
281 ended = k_uptime_get();
282 started = false;
283
284 if (won) {
285 struct mb_image img = MB_IMAGE({ 0, 1, 0, 1, 0 },
286 { 0, 1, 0, 1, 0 },
287 { 0, 0, 0, 0, 0 },
288 { 1, 0, 0, 0, 1 },
289 { 0, 1, 1, 1, 0 });
290 mb_display_image(disp, MB_DISPLAY_MODE_SINGLE,
291 RESTART_THRESHOLD, &img, 1);
292 printk("You won!\n");
293 } else {
294 struct mb_image img = MB_IMAGE({ 0, 1, 0, 1, 0 },
295 { 0, 1, 0, 1, 0 },
296 { 0, 0, 0, 0, 0 },
297 { 0, 1, 1, 1, 0 },
298 { 1, 0, 0, 0, 1 });
299 mb_display_image(disp, MB_DISPLAY_MODE_SINGLE,
300 RESTART_THRESHOLD, &img, 1);
301 printk("You lost!\n");
302 }
303
304 k_work_reschedule(&refresh, K_MSEC(RESTART_THRESHOLD));
305 }
306
307 #if CONFIG_THREAD_MONITOR
game_stack_dump(const struct k_thread * thread,void * user_data)308 static void game_stack_dump(const struct k_thread *thread, void *user_data)
309 {
310 ARG_UNUSED(user_data);
311
312 log_stack_usage(thread);
313 }
314 #endif
315
game_refresh(struct k_work * work)316 static void game_refresh(struct k_work *work)
317 {
318 if (sound_state != SOUND_IDLE) {
319 sound_set(SOUND_IDLE);
320 #if CONFIG_THREAD_MONITOR
321 k_thread_foreach(game_stack_dump, NULL);
322 #endif
323 }
324
325 if (pg_state == INIT) {
326 pong_select(&mode_selection);
327 return;
328 }
329
330 if (ended) {
331 game_init(pg_state == SINGLE || remote_lost);
332 k_sem_give(&disp_update);
333 return;
334 }
335
336 ball_pos.x += ball_vel.x;
337 ball_pos.y += ball_vel.y;
338
339 /* Ball went over to the other side */
340 if (ball_vel.y < 0 && ball_pos.y < BALL_POS_Y_MIN) {
341 if (pg_state == SINGLE) {
342 ball_pos.y = -ball_pos.y;
343 ball_vel.y = -ball_vel.y;
344 sound_set(SOUND_WALL);
345 } else {
346 ble_send_ball(BALL_POS_X_MAX - ball_pos.x, ball_pos.y,
347 -ball_vel.x, -ball_vel.y);
348 k_sem_give(&disp_update);
349 return;
350 }
351 }
352
353 /* Check for side-wall collision */
354 if (ball_pos.x < BALL_POS_X_MIN) {
355 ball_pos.x = -ball_pos.x;
356 ball_vel.x = -ball_vel.x;
357 sound_set(SOUND_WALL);
358 } else if (ball_pos.x > BALL_POS_X_MAX) {
359 ball_pos.x = (2 * BALL_POS_X_MAX) - ball_pos.x;
360 ball_vel.x = -ball_vel.x;
361 sound_set(SOUND_WALL);
362 }
363
364 /* Ball approaching paddle */
365 if (ball_vel.y > 0 && ball_pos.y > BALL_POS_Y_MAX) {
366 if (ball_pos.x < REAL_TO_VIRT(paddle_x) ||
367 ball_pos.x >= REAL_TO_VIRT(paddle_x + 2)) {
368 game_ended(false);
369 if (pg_state == CONNECTED) {
370 ble_send_lost();
371 }
372 return;
373 }
374
375 ball_pos.y = (2 * BALL_POS_Y_MAX) - ball_pos.y;
376
377 /* Make the game play gradually harder */
378 if (ball_vel.y < PIXEL_SIZE) {
379 ball_vel.y++;
380 }
381
382 ball_vel.y = -ball_vel.y;
383
384 sound_set(SOUND_PADDLE);
385 }
386
387 k_work_reschedule(&refresh, GAME_REFRESH);
388 k_sem_give(&disp_update);
389 }
390
pong_ball_received(int8_t x_pos,int8_t y_pos,int8_t x_vel,int8_t y_vel)391 void pong_ball_received(int8_t x_pos, int8_t y_pos, int8_t x_vel, int8_t y_vel)
392 {
393 printk("ball_received(%d, %d, %d, %d)\n", x_pos, y_pos, x_vel, y_vel);
394
395 ball_pos.x = x_pos;
396 ball_pos.y = y_pos;
397 ball_vel.x = x_vel;
398 ball_vel.y = y_vel;
399
400 k_work_reschedule(&refresh, K_NO_WAIT);
401 }
402
button_pressed(const struct device * dev,struct gpio_callback * cb,uint32_t pins)403 static void button_pressed(const struct device *dev, struct gpio_callback *cb,
404 uint32_t pins)
405 {
406 /* Filter out spurious presses */
407 if (pins & BIT(sw0_gpio.pin)) {
408 printk("A pressed\n");
409 if (k_uptime_delta(&a_timestamp) < 100) {
410 printk("Too quick A presses\n");
411 return;
412 }
413 } else {
414 printk("B pressed\n");
415 if (k_uptime_delta(&b_timestamp) < 100) {
416 printk("Too quick B presses\n");
417 return;
418 }
419 }
420
421 if (ended && (k_uptime_get() - ended) > RESTART_THRESHOLD) {
422 int busy = k_work_cancel_delayable(&refresh);
423
424 if (busy != 0) {
425 printk("WARNING: Data-race (work and event)\n");
426 }
427
428 game_init(pg_state == SINGLE || remote_lost);
429 k_sem_give(&disp_update);
430 return;
431 }
432
433 if (pg_state == MULTI) {
434 ble_cancel_connect();
435 pg_state = INIT;
436 pong_select(&mode_selection);
437 return;
438 }
439
440 if (pins & BIT(sw0_gpio.pin)) {
441 if (select) {
442 pong_select_change();
443 return;
444 }
445
446 if (!started) {
447 check_start();
448 }
449
450 if (paddle_x > PADDLE_MIN) {
451 paddle_x--;
452 if (!started) {
453 ball_pos.x -= PIXEL_SIZE;
454 }
455
456 k_sem_give(&disp_update);
457 }
458 } else {
459 if (select) {
460 pong_select_complete();
461 return;
462 }
463
464 if (!started) {
465 check_start();
466 }
467
468 if (paddle_x < PADDLE_MAX) {
469 paddle_x++;
470 if (!started) {
471 ball_pos.x += PIXEL_SIZE;
472 }
473
474 k_sem_give(&disp_update);
475 }
476 }
477 }
478
pong_conn_ready(bool initiator)479 void pong_conn_ready(bool initiator)
480 {
481 pg_state = CONNECTED;
482 game_init(initiator);
483 k_sem_give(&disp_update);
484 }
485
pong_remote_disconnected(void)486 void pong_remote_disconnected(void)
487 {
488 pg_state = INIT;
489 k_work_reschedule(&refresh, K_SECONDS(1));
490 }
491
pong_remote_lost(void)492 void pong_remote_lost(void)
493 {
494 printk("Remote lost!\n");
495 game_ended(true);
496 }
497
configure_buttons(void)498 static void configure_buttons(void)
499 {
500 static struct gpio_callback button_cb_data;
501
502 /* since sw0_gpio.port == sw1_gpio.port, we only need to check ready once */
503 if (!gpio_is_ready_dt(&sw0_gpio)) {
504 printk("%s: device not ready.\n", sw0_gpio.port->name);
505 return;
506 }
507
508 gpio_pin_configure_dt(&sw0_gpio, GPIO_INPUT);
509 gpio_pin_configure_dt(&sw1_gpio, GPIO_INPUT);
510
511 gpio_pin_interrupt_configure_dt(&sw0_gpio, GPIO_INT_EDGE_TO_ACTIVE);
512 gpio_pin_interrupt_configure_dt(&sw1_gpio, GPIO_INT_EDGE_TO_ACTIVE);
513
514 gpio_init_callback(&button_cb_data, button_pressed,
515 BIT(sw0_gpio.pin) | BIT(sw1_gpio.pin));
516
517 gpio_add_callback(sw0_gpio.port, &button_cb_data);
518 }
519
main(void)520 int main(void)
521 {
522 struct mb_display *disp = mb_display_get();
523
524 configure_buttons();
525
526 k_work_init_delayable(&refresh, game_refresh);
527
528 if (!pwm_is_ready_dt(&pwm)) {
529 printk("%s: device not ready.\n", pwm.dev->name);
530 return 0;
531 }
532
533 ble_init();
534
535 pong_select(&mode_selection);
536
537 printk("Started\n");
538
539 while (1) {
540 struct mb_image img = { };
541
542 k_sem_take(&disp_update, K_FOREVER);
543
544 if (ended) {
545 continue;
546 }
547
548 img.row[PADDLE_ROW] = (BIT(paddle_x) | BIT(paddle_x + 1));
549
550 if (ball_visible()) {
551 img.row[VIRT_TO_REAL(ball_pos.y)] =
552 BIT(VIRT_TO_REAL(ball_pos.x));
553 }
554
555 mb_display_image(disp, MB_DISPLAY_MODE_SINGLE,
556 SYS_FOREVER_MS, &img, 1);
557 }
558 return 0;
559 }
560