games/brickmatch: Add touchscreen input and module support.

Allow Brickmatch to build as either a built-in application or loadable module. Add configurable touchscreen swipe input with aligned samples, release handling, and explicit read-error propagation.

Handle SIGTERM cooperatively, make the framebuffer top offset configurable for launcher controls, validate physical and virtual dimensions, and release input, framebuffer, and device resources on exit.

Assisted-by: OpenAI Codex:gpt-5.6-sol
Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
This commit is contained in:
aviralgarg05 2026-07-12 20:52:43 +05:30
parent d7b08fd412
commit 5e09f0b0de
4 changed files with 344 additions and 17 deletions

View file

@ -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

View file

@ -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 <nuttx/config.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <nuttx/input/touchscreen.h>
#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 */

View file

@ -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 */
};

View file

@ -28,6 +28,7 @@
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@ -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;
}