mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-01 20:29:00 +00:00
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:
parent
d7b08fd412
commit
5e09f0b0de
4 changed files with 344 additions and 17 deletions
|
|
@ -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
|
||||
|
|
|
|||
183
games/brickmatch/bm_input_touch.h
Normal file
183
games/brickmatch/bm_input_touch.h
Normal 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 */
|
||||
|
|
@ -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 */
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue