* Snake!

* Add spiLock to snake score saving

* Check fixes

* More careful locking

* WIP: Big Display Node

* Update src/graphics/HUB75Display.cpp

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add HUB75 Native

* Add Tetris game module with core logic and UI integration

- Implement TetrisGame class for game logic, including piece movement, rotation, and line clearing.
- Create TetrisModule class to manage game state, input handling, and rendering on OLED display.
- Introduce high score tracking with persistence and optional mesh broadcasting.
- Define UI states for title, playing, paused, game over, and high scores.
- Implement input handling for game controls and state transitions.
- Add rendering functions for the game board, high scores, and title screen.

* feat(snake): add snake graphics and update display logic in SnakeModule

* Prompt for Initials for Tetris, too

* refactor games module

* Games refactor

* hub75 native double buffer

* Games tuning

* Make joystick repeat events on held button

* Add clouds and colors

* Fix breakout and more color

* difficulty tuning

* trunk

* refactor game announcements, etc

* Scale chirpy gravity as game speed increases

* Portduino, check for hub75 display before reading in hub75 options

* Final(?) games tuning

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Properly ignore input when games screen not shown

* Fail gracefully when HUB75 is selected but not supported.

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Ixitxachitl <kramerfm@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Jonathan Bennett
2026-07-16 18:35:33 -05:00
committed by GitHub
parent d3d02af817
commit 8d1fbbf55f
41 changed files with 4106 additions and 34 deletions

View File

@@ -79,6 +79,16 @@ static int axisZone(int value)
return 0;
}
void LinuxJoystick::emitEvent(input_broker_event event)
{
InputEvent e = {};
e.inputEvent = event;
e.source = this->_originName;
e.kbchar = 0;
// LOG_DEBUG("joystick: %s event %d", this->_originName, event);
this->notifyObservers(&e);
}
int32_t LinuxJoystick::runOnce()
{
if (firstTime) {
@@ -107,8 +117,6 @@ int32_t LinuxJoystick::runOnce()
if (nfds < 0) {
perror("joystick: epoll_wait failed");
return disable();
} else if (nfds == 0) {
return 50;
}
for (int i = 0; i < nfds; i++) {
@@ -117,42 +125,53 @@ int32_t LinuxJoystick::runOnce()
if (rd < (signed int)sizeof(struct input_event))
continue;
for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
e.kbchar = 0;
unsigned int type = evs[j].type;
unsigned int code = evs[j].code;
int value = evs[j].value;
if (type == EV_ABS) {
// D-pad reports as ABS_X / ABS_Y with digital 0 / 127 / 255 values.
// Emit exactly once when an axis crosses from center to an edge.
// D-pad reports as ABS_X / ABS_Y with digital 0 / 127 / 255 values. Emit on the
// transition to an edge and arm auto-repeat; a held direction repeats below.
if (code == ABS_X) {
int zone = axisZone(value);
if (zone != axisZone(lastX) && zone != 0)
e.inputEvent = (zone < 0) ? INPUT_BROKER_LEFT : INPUT_BROKER_RIGHT;
lastX = value;
if (zone != heldX) {
heldX = zone;
if (zone != 0) {
emitEvent((zone < 0) ? INPUT_BROKER_LEFT : INPUT_BROKER_RIGHT);
nextRepeatX = millis() + JOY_REPEAT_DELAY_MS;
}
}
} else if (code == ABS_Y) {
int zone = axisZone(value);
if (zone != axisZone(lastY) && zone != 0)
e.inputEvent = (zone < 0) ? INPUT_BROKER_UP : INPUT_BROKER_DOWN;
lastY = value;
if (zone != heldY) {
heldY = zone;
if (zone != 0) {
emitEvent((zone < 0) ? INPUT_BROKER_UP : INPUT_BROKER_DOWN);
nextRepeatY = millis() + JOY_REPEAT_DELAY_MS;
}
}
}
} else if (type == EV_KEY && value == 1) {
// Look up the configured action for this button; unmapped buttons are ignored.
// Buttons fire once per press (no auto-repeat).
auto mapped = buttonMap.find(code);
if (mapped != buttonMap.end())
e.inputEvent = mapped->second;
}
if (e.inputEvent != INPUT_BROKER_NONE) {
LOG_DEBUG("joystick: %s event %d", this->_originName, e.inputEvent);
this->notifyObservers(&e);
emitEvent(mapped->second);
}
}
}
// Auto-repeat held D-pad directions. Signed comparison is wraparound-safe.
uint32_t now = millis();
if (heldX != 0 && (int32_t)(now - nextRepeatX) >= 0) {
emitEvent((heldX < 0) ? INPUT_BROKER_LEFT : INPUT_BROKER_RIGHT);
nextRepeatX = now + JOY_REPEAT_INTERVAL_MS;
}
if (heldY != 0 && (int32_t)(now - nextRepeatY) >= 0) {
emitEvent((heldY < 0) ? INPUT_BROKER_UP : INPUT_BROKER_DOWN);
nextRepeatY = now + JOY_REPEAT_INTERVAL_MS;
}
return 50; // Poll every 50msec
}

View File

@@ -20,6 +20,10 @@
#define JOY_AXIS_LOW 64 // below this -> "min" edge (0)
#define JOY_AXIS_HIGH 192 // above this -> "max" edge (255)
// D-pad auto-repeat while a direction is held (typematic).
#define JOY_REPEAT_DELAY_MS 400 // hold this long before repeats start
#define JOY_REPEAT_INTERVAL_MS 150 // then repeat this often
class LinuxJoystick : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
@@ -27,10 +31,18 @@ class LinuxJoystick : public Observable<const InputEvent *>, public concurrency:
void init(); // Registers this source with the InputBroker
void deInit(); // Strictly for cleanly "rebooting" the binary on native
// Current held D-pad zone, updated the instant the axis moves (independent of the typematic
// auto-repeat). -1 = left/up edge, 0 = centered, +1 = right/down edge. Lets a game poll for
// smooth continuous control instead of waiting for the slow repeat events.
int heldXZone() const { return heldX; }
int heldYZone() const { return heldY; }
protected:
virtual int32_t runOnce() override;
private:
void emitEvent(input_broker_event event);
const char *_originName;
bool firstTime = true;
@@ -42,10 +54,12 @@ class LinuxJoystick : public Observable<const InputEvent *>, public concurrency:
int fd = -1;
int epollfd = -1;
// Latch the last emitted axis position so a held direction fires exactly
// once (on the transition away from center), not a continuous stream.
int lastX = JOY_AXIS_CENTER;
int lastY = JOY_AXIS_CENTER;
// D-pad auto-repeat state: currently held zone per axis (-1 / 0 / +1) and the
// next millis() timestamp at which to re-emit while the direction is held.
int heldX = 0;
int heldY = 0;
uint32_t nextRepeatX = 0;
uint32_t nextRepeatY = 0;
};
extern LinuxJoystick *aLinuxJoystick;
#endif