diff --git a/samples/boards/microbit/pong/Makefile b/samples/boards/microbit/pong/Makefile new file mode 100644 index 00000000000..20ad981a51f --- /dev/null +++ b/samples/boards/microbit/pong/Makefile @@ -0,0 +1,4 @@ +BOARD ?= bbc_microbit +CONF_FILE = prj.conf + +include ${ZEPHYR_BASE}/Makefile.test diff --git a/samples/boards/microbit/pong/README.rst b/samples/boards/microbit/pong/README.rst new file mode 100644 index 00000000000..4dae9854bb7 --- /dev/null +++ b/samples/boards/microbit/pong/README.rst @@ -0,0 +1,31 @@ +.. _microbit_pong: + +BBC micro:bit pong game +####################### + +Overview +******** + +Play pong over as single player or Bluetooth between two micro:bit +devices. + +The game works by controlling a paddle with the two buttons of the +micro:bit (labeled A and B). Initially the playing mode is selected: use +button A to toggle between single- and multi-player, and press button B +to select the current choice. To start the game, the player with the +ball launches the ball by pressing both buttons. + +When multi-player mode has been selected the game will try to look for +and connect to a second micro:bit which has also been set into multi- +player mode. + +If the board has a piezo buzzer connected to pin 0, this will be used to +generate beeps whenever the ball hits a wall or the paddle. + +Building +******** + +.. code-block:: console + + $ cd samples/boards/microbit/pong + $ make diff --git a/samples/boards/microbit/pong/prj.conf b/samples/boards/microbit/pong/prj.conf new file mode 100644 index 00000000000..bfb004c9b5b --- /dev/null +++ b/samples/boards/microbit/pong/prj.conf @@ -0,0 +1,8 @@ +CONFIG_BLUETOOTH=y +CONFIG_BLUETOOTH_CENTRAL=y +CONFIG_BLUETOOTH_PERIPHERAL=y +CONFIG_BLUETOOTH_GATT_CLIENT=y +CONFIG_BLUETOOTH_GATT_DYNAMIC_DB=n +CONFIG_BLUETOOTH_DEVICE_NAME="Zephyr Pong" +CONFIG_GPIO=y +CONFIG_MICROBIT_DISPLAY=y diff --git a/samples/boards/microbit/pong/src/Makefile b/samples/boards/microbit/pong/src/Makefile new file mode 100644 index 00000000000..00066e15678 --- /dev/null +++ b/samples/boards/microbit/pong/src/Makefile @@ -0,0 +1 @@ +obj-y = main.o diff --git a/samples/boards/microbit/pong/src/main.c b/samples/boards/microbit/pong/src/main.c new file mode 100644 index 00000000000..d28b9553b9e --- /dev/null +++ b/samples/boards/microbit/pong/src/main.c @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* Define this to do a single-device game */ +#define SOLO 1 + +/* The micro:bit has a 5x5 LED display, using (x, y) notation the top-left + * corner has coordinates (0, 0) and the bottom-right has (4, 4). To make + * the game dynamics more natural, the uses a virtual 50x50 coordinate + * system where top-left is (0, 0) and bottom-right is (49, 49). + */ + +#define PIXEL_SIZE 10 /* Virtual coordinates per real pixel */ + +#define GAME_REFRESH K_MSEC(100) /* Animation refresh rate of the game */ + +#define PADDLE_ROW 4 /* Real Y coordinate of the paddle */ +#define PADDLE_MIN 0 /* Minimum paddle real X coordinate */ +#define PADDLE_MAX 3 /* Maximum paddle real X coordinate */ + +#define BALL_VEL_Y_START -4 /* Default ball vertical speed */ + +#define BALL_POS_X_MIN 0 /* Maximum ball X coordinate */ +#define BALL_POS_X_MAX 49 /* Maximum ball X coordinate */ +#define BALL_POS_Y_MIN 0 /* Maximum ball Y coordinate */ +#define BALL_POS_Y_MAX 39 /* Maximum ball Y coordinate */ + +#define START_THRESHOLD K_MSEC(100) /* Max time between A & B press */ +#define RESTART_THRESHOLD K_SECONDS(3) /* Time before restart is allowed */ + +#define REAL_TO_VIRT(r) ((r) * 10) +#define VIRT_TO_REAL(v) ((v) / 10) + +/* Ball starting position (just to the left of the paddle mid-point) */ +#define BALL_START (struct x_y){ 4, BALL_POS_Y_MAX } + +struct x_y { + int x; + int y; +}; + +static bool started; +static s64_t ended; + +static struct k_delayed_work refresh; + +/* Semaphore to indicate that there was an update to the display */ +static struct k_sem disp_update = K_SEM_INITIALIZER(disp_update, 0, 1); + +/* X coordinate of the left corner of the paddle */ +static volatile int paddle_x = PADDLE_MIN; + +/* Ball position */ +static struct x_y ball_pos = BALL_START; + +/* Ball velocity */ +static struct x_y ball_vel = { 0, 0 }; + +static s64_t a_timestamp; +static s64_t b_timestamp; + +static bool ball_visible(void) +{ + return (ball_pos.y >= BALL_POS_Y_MIN); +} + +static void check_start(void) +{ + u32_t delta; + u8_t rnd; + + if (!a_timestamp || !b_timestamp) { + return; + } + + if (a_timestamp > b_timestamp) { + delta = a_timestamp - b_timestamp; + } else { + delta = b_timestamp - a_timestamp; + } + + printk("delta %u ms\n", delta); + + if (delta > START_THRESHOLD) { + return; + } + + ball_vel.y = BALL_VEL_Y_START; + + bt_rand(&rnd, sizeof(rnd)); + rnd %= 8; + + if (a_timestamp > b_timestamp) { + ball_vel.x = 2 + rnd; + } else { + ball_vel.x = -2 - rnd; + } + + started = true; + k_delayed_work_submit(&refresh, K_NO_WAIT); +} + +static void game_ended(bool won) +{ + struct mb_display *disp = mb_display_get(); + const char *str; + + ended = k_uptime_get(); + started = false; + + if (won) { + str = "You won!"; + } else { + str = "You lost!"; + } + + printk("%s\n", str); + + mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP, + K_MSEC(500), "%s", str); +} + +static void game_refresh(struct k_work *work) +{ + ball_pos.x += ball_vel.x; + ball_pos.y += ball_vel.y; + + /* Ball went over to the other side */ + if (ball_pos.y < BALL_POS_Y_MIN) { +#if defined(SOLO) + ball_pos.y = -ball_pos.y; + ball_vel.y = -ball_vel.y; +#else + k_sem_give(&disp_update); + return; +#endif + } + + /* Check for side-wall collision */ + if (ball_pos.x < BALL_POS_X_MIN) { + ball_pos.x = -ball_pos.x; + ball_vel.x = -ball_vel.x; + } else if (ball_pos.x > BALL_POS_X_MAX) { + ball_pos.x = (2 * BALL_POS_X_MAX) - ball_pos.x; + ball_vel.x = -ball_vel.x; + } + + /* Ball approaching paddle */ + if (ball_vel.y > 0 && ball_pos.y > BALL_POS_Y_MAX) { + if (ball_pos.x < REAL_TO_VIRT(paddle_x) || + ball_pos.x >= REAL_TO_VIRT(paddle_x + 2)) { + game_ended(false); + return; + } + + ball_pos.y = (2 * BALL_POS_Y_MAX) - ball_pos.y; + ball_vel.y = -ball_vel.y; + } + + k_delayed_work_submit(&refresh, GAME_REFRESH); + k_sem_give(&disp_update); +} + +static void game_init(void) +{ + ended = 0; + + ball_pos = BALL_START; + paddle_x = PADDLE_MIN; + + a_timestamp = 0; + b_timestamp = 0; + + k_sem_give(&disp_update); +} + +static void button_pressed(struct device *dev, struct gpio_callback *cb, + u32_t pins) +{ + if (ended && (k_uptime_get() - ended) > RESTART_THRESHOLD) { + game_init(); + return; + } + + if (pins & BIT(SW0_GPIO_PIN)) { + printk("A pressed\n"); + + if (!started) { + a_timestamp = k_uptime_get(); + check_start(); + } + + if (paddle_x > PADDLE_MIN) { + paddle_x--; + if (!started) { + ball_pos.x -= PIXEL_SIZE; + } + + k_sem_give(&disp_update); + } + } else { + printk("B pressed\n"); + + if (!started) { + b_timestamp = k_uptime_get(); + check_start(); + } + + if (paddle_x < PADDLE_MAX) { + paddle_x++; + if (!started) { + ball_pos.x += PIXEL_SIZE; + } + + k_sem_give(&disp_update); + } + } +} + +static void configure_buttons(void) +{ + static struct gpio_callback button_cb; + struct device *gpio; + + gpio = device_get_binding(SW0_GPIO_NAME); + + gpio_pin_configure(gpio, SW0_GPIO_PIN, + (GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE | + GPIO_INT_ACTIVE_LOW)); + gpio_pin_configure(gpio, SW1_GPIO_PIN, + (GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE | + GPIO_INT_ACTIVE_LOW)); + gpio_init_callback(&button_cb, button_pressed, + BIT(SW0_GPIO_PIN) | BIT(SW1_GPIO_PIN)); + gpio_add_callback(gpio, &button_cb); + + gpio_pin_enable_callback(gpio, SW0_GPIO_PIN); + gpio_pin_enable_callback(gpio, SW1_GPIO_PIN); +} + +void main(void) +{ + struct mb_display *disp = mb_display_get(); + + configure_buttons(); + + k_delayed_work_init(&refresh, game_refresh); + + game_init(); + + printk("Started\n"); + + while (1) { + struct mb_image img = { }; + + k_sem_take(&disp_update, K_FOREVER); + + if (ended) { + continue; + } + + img.row[PADDLE_ROW] = (BIT(paddle_x) | BIT(paddle_x + 1)); + + if (ball_visible()) { + img.row[VIRT_TO_REAL(ball_pos.y)] = + BIT(VIRT_TO_REAL(ball_pos.x)); + } + + mb_display_image(disp, MB_DISPLAY_MODE_SINGLE, + K_FOREVER, &img, 1); + } +} diff --git a/samples/boards/microbit/pong/testcase.ini b/samples/boards/microbit/pong/testcase.ini new file mode 100644 index 00000000000..0763266506b --- /dev/null +++ b/samples/boards/microbit/pong/testcase.ini @@ -0,0 +1,4 @@ +[test] +build_only = true +tags = samples +platform_whitelist = bbc_microbit