diff --git a/games/brickmatch/Kconfig b/games/brickmatch/Kconfig index 1567a4ebc..e51be23fc 100644 --- a/games/brickmatch/Kconfig +++ b/games/brickmatch/Kconfig @@ -4,7 +4,7 @@ # config GAMES_BRICKMATCH - bool "Brickmatch Game" + tristate "Brickmatch Game" default n ---help--- Enable Brickmatch games. Brickmatch is like a mix @@ -88,6 +88,10 @@ config GAMES_BRICKMATCH_USE_GESTURE config GAMES_BRICKMATCH_USE_GPIO bool "GPIO pins as Input" + +config GAMES_BRICKMATCH_USE_TOUCHSCREEN + bool "Touchscreen swipe as input" + depends on INPUT_TOUCHSCREEN endchoice if GAMES_BRICKMATCH_USE_GPIO @@ -118,4 +122,34 @@ config GAMES_BRICKMATCH_RIGHT_KEY_PATH endif #GAMES_BRICKMATCH_USE_GPIO +if GAMES_BRICKMATCH_USE_TOUCHSCREEN + +config GAMES_BRICKMATCH_TOUCH_DEVPATH + string "Touchscreen device path" + default "/dev/input0" + ---help--- + Path of the touchscreen device to read swipe gestures from. + +config GAMES_BRICKMATCH_TOUCH_THRESHOLD + int "Minimum swipe distance" + range 1 32767 + default 24 + ---help--- + Minimum movement in calibrated touchscreen coordinate units before + a drag is reported as a direction. + +endif #GAMES_BRICKMATCH_USE_TOUCHSCREEN + +if !GAMES_BRICKMATCH_USE_LED_MATRIX + +config GAMES_BRICKMATCH_YOFFSET + int "Top framebuffer rows to reserve" + range 0 500000 + default 0 + ---help--- + Leave this many rows at the top of the framebuffer unchanged. This + allows a launcher or supervisor to reserve a status or control bar. + +endif # !GAMES_BRICKMATCH_USE_LED_MATRIX + endif diff --git a/games/brickmatch/bm_input_touch.h b/games/brickmatch/bm_input_touch.h new file mode 100644 index 000000000..09ea71833 --- /dev/null +++ b/games/brickmatch/bm_input_touch.h @@ -0,0 +1,183 @@ +/**************************************************************************** + * apps/games/brickmatch/bm_input_touch.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __APPS_GAMES_BRICKMATCH_BM_INPUT_TOUCH_H +#define __APPS_GAMES_BRICKMATCH_BM_INPUT_TOUCH_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "bm_inputs.h" + +/**************************************************************************** + * Preprocessor Definitions + ****************************************************************************/ + +/* Tracks the in-progress drag so each dev_read_input() call only has to + * look at the latest sample, not replay history. Reset on TOUCH_UP so a + * finger lift always starts the next swipe's baseline fresh; re-baselined + * (rather than requiring a lift) after firing a direction, so a single + * continued drag can fire more than one swipe. + */ + +static bool g_bm_touch_active; +static int32_t g_bm_touch_origin_x; +static int32_t g_bm_touch_origin_y; + +/**************************************************************************** + * Name: dev_input_init + * + * Description: + * Initialize input method. + * + * Parameters: + * dev - Input state data + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int dev_input_init(FAR struct input_state_s *dev) +{ + dev->fd_touch = open(CONFIG_GAMES_BRICKMATCH_TOUCH_DEVPATH, + O_RDONLY | O_NONBLOCK); + if (dev->fd_touch < 0) + { + int errcode = errno; + fprintf(stderr, "ERROR: Failed to open %s: %d\n", + CONFIG_GAMES_BRICKMATCH_TOUCH_DEVPATH, errcode); + return -errcode; + } + + g_bm_touch_active = false; + + return OK; +} + +/**************************************************************************** + * Name: dev_read_input + * + * Description: + * Read inputs and returns result in input state data. Interprets a + * drag/swipe gesture rather than an absolute tap position, since + * brickmatch's board layout has no natural on-screen buttons to tap - + * this mirrors how the gesture-sensor backend (bm_input_gesture.h) + * reports discrete directions, just derived from a touchscreen instead + * of a dedicated gesture sensor. + * + * Parameters: + * dev - Input state data + * + * Returned Value: + * Zero (OK) + * + ****************************************************************************/ + +int dev_read_input(FAR struct input_state_s *dev) +{ + struct touch_sample_s sample; + int32_t dx; + int32_t dy; + ssize_t nbytes; + + dev->dir = DIR_NONE; + + nbytes = read(dev->fd_touch, &sample, sizeof(sample)); + if (nbytes < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + usleep(10000); + return OK; + } + + return -errno; + } + + if (nbytes != sizeof(sample) || sample.npoints < 1) + { + return OK; + } + + if (sample.point[0].flags & TOUCH_UP) + { + g_bm_touch_active = false; + return OK; + } + + if ((sample.point[0].flags & TOUCH_POS_VALID) == 0) + { + return OK; + } + + if (sample.point[0].flags & TOUCH_DOWN) + { + g_bm_touch_active = true; + g_bm_touch_origin_x = sample.point[0].x; + g_bm_touch_origin_y = sample.point[0].y; + return OK; + } + + if (!g_bm_touch_active) + { + return OK; + } + + dx = sample.point[0].x - g_bm_touch_origin_x; + dy = sample.point[0].y - g_bm_touch_origin_y; + + if (abs(dx) < CONFIG_GAMES_BRICKMATCH_TOUCH_THRESHOLD && + abs(dy) < CONFIG_GAMES_BRICKMATCH_TOUCH_THRESHOLD) + { + return OK; + } + + if (abs(dx) > abs(dy)) + { + dev->dir = dx > 0 ? DIR_RIGHT : DIR_LEFT; + } + else + { + dev->dir = dy > 0 ? DIR_DOWN : DIR_UP; + } + + g_bm_touch_origin_x = sample.point[0].x; + g_bm_touch_origin_y = sample.point[0].y; + + return OK; +} + +#endif /* __APPS_GAMES_BRICKMATCH_BM_INPUT_TOUCH_H */ diff --git a/games/brickmatch/bm_inputs.h b/games/brickmatch/bm_inputs.h index 9497b3135..f68c8047b 100644 --- a/games/brickmatch/bm_inputs.h +++ b/games/brickmatch/bm_inputs.h @@ -64,6 +64,9 @@ struct input_state_s #ifdef CONFIG_GAMES_BRICKMATCH_USE_GPIO int fd_gpio; #endif +#ifdef CONFIG_GAMES_BRICKMATCH_USE_TOUCHSCREEN + int fd_touch; +#endif int dir; /* Direction to move the blocks */ }; diff --git a/games/brickmatch/bm_main.c b/games/brickmatch/bm_main.c index b9e63ca5d..dc3e7bd50 100644 --- a/games/brickmatch/bm_main.c +++ b/games/brickmatch/bm_main.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,10 @@ #include "bm_input_gpio.h" #endif +#ifdef CONFIG_GAMES_BRICKMATCH_USE_TOUCHSCREEN +#include "bm_input_touch.h" +#endif + /**************************************************************************** * Preprocessor Definitions ****************************************************************************/ @@ -93,6 +98,17 @@ struct screen_state_s struct fb_overlayinfo_s oinfo; #endif FAR void *fbmem; + +#ifndef CONFIG_GAMES_BRICKMATCH_USE_LED_MATRIX + /* Absolute placement of the game_screen_s board within the physical + * framebuffer. draw_rect() and update_screen() add these to every + * board-relative area, so the rest of the coordinate math stays in + * board-local coordinates that start at (0, 0). + */ + + int abs_xoff; + int abs_yoff; +#endif }; struct game_screen_s @@ -112,6 +128,19 @@ struct game_screen_s * Private Data ****************************************************************************/ +/* Set only by quit_signal_handler(), an async-signal-safe flag. Actual + * shutdown happens from the main loop so a launcher can ask either a + * built-in application or loadable module to stop cooperatively. + */ + +static volatile sig_atomic_t g_quit_requested; + +static void quit_signal_handler(int signo) +{ + (void)signo; + g_quit_requested = 1; +} + #ifndef CONFIG_GAMES_BRICKMATCH_USE_LED_MATRIX static const char g_default_fbdev[] = "/dev/fb0"; #else @@ -133,7 +162,7 @@ int prev_board[ROW][COL] = /* Colors used in the game plus Black */ -static const uint16_t pallete[NCOLORS + 1] = +static const uint16_t palette[NCOLORS + 1] = { RGB16_BLACK, RGB16_BLUE, @@ -173,13 +202,14 @@ static void draw_rect(FAR struct screen_state_s *state, int x; int y; - row = (FAR uint8_t *)state->fbmem + state->pinfo.stride * area->y; + row = (FAR uint8_t *)state->fbmem + + state->pinfo.stride * (area->y + state->abs_yoff); for (y = 0; y < area->h; y++) { - dest = ((FAR uint16_t *)row) + area->x; + dest = ((FAR uint16_t *)row) + area->x + state->abs_xoff; for (x = 0; x < area->w; x++) { - *dest++ = pallete[color]; + *dest++ = palette[color]; } row += state->pinfo.stride; @@ -204,11 +234,17 @@ static void draw_rect(FAR struct screen_state_s *state, void update_screen(FAR struct screen_state_s *state, FAR struct fb_area_s *area) { +#ifdef CONFIG_FB_UPDATE + struct fb_area_s abs_area; int ret; -#ifdef CONFIG_FB_UPDATE + abs_area.x = area->x + state->abs_xoff; + abs_area.y = area->y + state->abs_yoff; + abs_area.w = area->w; + abs_area.h = area->h; + ret = ioctl(state->fd_fb, FBIO_UPDATE, - (unsigned long)((uintptr_t)area)); + (unsigned long)((uintptr_t)&abs_area)); if (ret < 0) { int errcode = errno; @@ -410,7 +446,7 @@ void draw_board(FAR struct screen_state_s *state, { for (y = 1; y <= BOARDY_SIZE; y++) { - rgb_val = RGB16TO24(pallete[board[x][y]]); + rgb_val = RGB16TO24(palette[board[x][y]]); tmp = dim_color(rgb_val, 0.15); *bp++ = ws2812_gamma_correct(tmp); } @@ -990,7 +1026,23 @@ int main(int argc, FAR char *argv[]) struct input_state_s input; struct screen_state_s state; struct fb_area_s area; + struct sigaction sa; int ret; + int status = EXIT_SUCCESS; +#ifndef CONFIG_GAMES_BRICKMATCH_USE_LED_MATRIX + int usable_yres; +#endif + + g_quit_requested = 0; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = quit_signal_handler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGTERM, &sa, NULL) < 0) + { + fprintf(stderr, "ERROR: Failed to install SIGTERM handler: %d\n", + errno); + return EXIT_FAILURE; + } init_board(); @@ -1024,12 +1076,23 @@ int main(int argc, FAR char *argv[]) printf(" yres: %u\n", state.vinfo.yres); printf(" nplanes: %u\n", state.vinfo.nplanes); - /* Setup the screen information */ + /* Setup the screen information. The board is square and sized to the + * smaller display dimension after the configured top rows are reserved. + * Place that square below the reserved rows and horizontally center it. + */ + + if (state.vinfo.yres <= CONFIG_GAMES_BRICKMATCH_YOFFSET) + { + fprintf(stderr, "ERROR: Framebuffer is shorter than the y offset\n"); + close(state.fd_fb); + return EXIT_FAILURE; + } + + usable_yres = state.vinfo.yres - CONFIG_GAMES_BRICKMATCH_YOFFSET; + screen.xres = state.vinfo.xres > usable_yres ? + usable_yres : state.vinfo.xres; + screen.yres = screen.xres; - screen.xres = state.vinfo.xres > state.vinfo.yres ? - state.vinfo.yres : state.vinfo.xres; - screen.yres = state.vinfo.yres > state.vinfo.xres ? - state.vinfo.xres : state.vinfo.yres; screen.xoff = (screen.xres % NCOLORS) / 2; screen.yoff = (screen.yres % NCOLORS) / 2; screen.steps = 2; @@ -1037,6 +1100,9 @@ int main(int argc, FAR char *argv[]) screen.blklen = (screen.xres / NCOLORS); screen.ncolors = NCOLORS; + state.abs_xoff = (state.vinfo.xres - screen.xres) / 2; + state.abs_yoff = CONFIG_GAMES_BRICKMATCH_YOFFSET; + /* Get the display planeinfo */ ret = ioctl(state.fd_fb, FBIOGET_PLANEINFO, @@ -1057,6 +1123,14 @@ int main(int argc, FAR char *argv[]) printf(" display: %u\n", state.pinfo.display); printf(" bpp: %u\n", state.pinfo.bpp); + if (state.pinfo.xres_virtual < state.abs_xoff + screen.xres || + state.pinfo.yres_virtual < state.abs_yoff + screen.yres) + { + fprintf(stderr, "ERROR: Virtual framebuffer is too small\n"); + close(state.fd_fb); + return EXIT_FAILURE; + } + state.fbmem = mmap(NULL, state.pinfo.fblen, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, state.fd_fb, 0); if (state.fbmem == MAP_FAILED) @@ -1088,15 +1162,37 @@ int main(int argc, FAR char *argv[]) draw_board(&state, &area, &screen); - dev_input_init(&input); + ret = dev_input_init(&input); + if (ret < 0) + { + fprintf(stderr, "ERROR: Failed to initialize input: %d\n", ret); +#ifndef CONFIG_GAMES_BRICKMATCH_USE_LED_MATRIX + munmap(state.fbmem, state.pinfo.fblen); +#else + free(state.fbmem); +#endif + close(state.fd_fb); + return EXIT_FAILURE; + } input.dir = DIR_NONE; - while (1) + while (!g_quit_requested) { - while (input.dir == DIR_NONE) + while (input.dir == DIR_NONE && !g_quit_requested) { ret = dev_read_input(&input); + if (ret < 0) + { + fprintf(stderr, "ERROR: Failed to read input: %d\n", ret); + status = EXIT_FAILURE; + g_quit_requested = 1; + } + } + + if (g_quit_requested) + { + break; } screen.dir = input.dir; @@ -1168,5 +1264,16 @@ int main(int argc, FAR char *argv[]) fill_edge(); } - return 0; +#ifndef CONFIG_GAMES_BRICKMATCH_USE_LED_MATRIX + munmap(state.fbmem, state.pinfo.fblen); +#else + free(state.fbmem); +#endif + +#ifdef CONFIG_GAMES_BRICKMATCH_USE_TOUCHSCREEN + close(input.fd_touch); +#endif + close(state.fd_fb); + + return status; }