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