Skip to content

feat(games): /games — a six-game arcade in the pit - #364

Merged
ralyodio merged 1 commit into
mainfrom
feat/games
Aug 11, 2026
Merged

feat(games): /games — a six-game arcade in the pit#364
ralyodio merged 1 commit into
mainfrom
feat/games

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

/games lists the cabinet, /games <game> plays one.

  moshcode arcade — 6 games, no menus, no options screens

  tetris     stack the bricks, clear the lines, outrun gravity
  snake      eat, grow, and try not to eat yourself
  pacman     eat the dots, dodge the ghosts, ✳ makes them edible
  tictactoe  three in a row against a perfect opponent
  chess      full rules, real opponent, pawns auto-queen
  hangman    six wrong letters and you are done for

One arcade, not six weekend projects

Every game shares one frame, one key decoder and one driver, so they look
alike by construction: a title, a status, a boxed board, and one line of keys
along the bottom. No menus, no options screens, no difficulty prompts —
/games tetris is already playing by the time the frame lands. Arrows move,
q quits, r starts another, everywhere.

  TETRIS       score 1200 · lines 12
  ┌────────────────────────────────┐
  │ · · · · ████· · · ·   NEXT     │
  │ · · · · ████· · · ·            │
  │ · · · · · · · · · ·   ████████ │
  │ · · · ████· · · · ·   LVL 2    │
  │ ████████████· ██████           │
  └────────────────────────────────┘
  ← → move · ↑ rotate · ↓ drop one · space slam · q quit

Games draw in place rather than on the alternate screen, so the board you
finished on stays in your scrollback, and an unchanged frame is not repainted
at all.

Why it is testable

The games are pure — create a state, hand it a key, hand it a tick, ask it for
rows — and everything that touches a terminal lives in runGame(). So
test/games.test.mjs (54 tests) plays real games with no TTY: it fills a
tetris well until it stacks out, walks pac-man around until the maze is
cleared, wins and loses hangman, and drives the driver itself through a fake
stdin.

Two of those tests are the kind you cannot do by hand:

  • the tic-tac-toe opponent is played 40 games of random moves and must never
    lose
  • the pac-man maze is flood-filled from pac's start, and every dot must be
    reachable — a walled-off pocket is a game that cannot be won

Chess gets the full rulebook: castling (refused through check), en passant,
promotion, check, checkmate and stalemate, each with a test. Its opponent is
an alpha-beta search that runs on the clock rather than inside the keypress,
so your own move is on the board and drawn before it starts thinking.

Surface

  • /games, /games <game> in the pit (aliases /game, /arcade, /play)
  • moshcode games [game], moshcode games --json from a shell
  • listed in /help, moshcode help games, shell completion and the generated
    README table — the existing drift guards required all of it

Playing needs a real terminal, because every game reads single keypresses;
listing works anywhere, including a pipe, and says so when it refuses.

Testing

  • node --test — 1411 pass, 0 fail
  • played tetris, pac-man and chess over a real pty; verified raw-mode input,
    redraw, gravity, ghost movement, the machine's reply, and a clean exit that
    restores the cursor

🤖 Generated with Claude Code

`/games` lists the cabinet, `/games <game>` plays one. Six games — tetris,
snake, pac-man, tic-tac-toe, chess and hangman — sharing one frame, one key
decoder and one driver, so the arcade looks like an arcade rather than six
weekend projects.

No menus and no options screens: `/games tetris` is already playing when the
frame lands, and every game reads arrows, quits on q and restarts on r, with
its controls written along the bottom of the game itself.

The games are pure — create a state, hand it a key, hand it a tick, ask it for
rows — and everything touching a terminal lives in runGame(). That is what
makes test/games.test.mjs able to finish real games of tetris and pac-man,
prove the tic-tac-toe opponent cannot be beaten in 40 games of random play,
and check chess's castling, en passant, promotion and mate detection without a
TTY anywhere near it.

Chess plays by the real rules against an alpha-beta search. Its reply runs on
the clock rather than inside the keypress, so your own move is drawn before it
starts thinking.

Also available as `moshcode games` from a shell; listing works in a pipe,
playing needs a real terminal and says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread src/cli-schema.mjs
],
seeAlso: ["help"],
note: "every game works the same way: arrows move, q quits, r starts another. "
+ "Playing needs a real terminal because they read single keypresses — `moshcode games` on its own lists them anywhere.",
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

95 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 39 | LOW: 51

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
HIGH tls-verification-disabled src/dns.mjs:741
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:61
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:75
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:101
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:265
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:269
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:314
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:499
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:675
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:677
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:736
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:782
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:852
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:955
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1063
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1199
MEDIUM js-unescaped-html-sink apps/pwa/src/routes/moshpit.mjs:1419
MEDIUM js-timing-unsafe-mac-compare apps/pwa/src/routes/settings-sync.mjs:179
MEDIUM js-dynamic-code-execution apps/pwa/test/apikey-mask.test.mjs:129
MEDIUM sql-template-interpolation apps/pwa/test/credits-webhook-event-match.test.mjs:111
MEDIUM sql-template-interpolation apps/pwa/test/credits-webhook-event-match.test.mjs:131
MEDIUM sql-template-interpolation apps/pwa/test/moshpit-terms.test.mjs:192
MEDIUM sql-string-concatenation src/cli-schema.mjs:128
MEDIUM sql-string-concatenation src/cli-schema.mjs:413
MEDIUM js-timing-unsafe-mac-compare src/settings-sync.mjs:309
MEDIUM insecure-temp-file test/dns-disable-restore.test.mjs:93
MEDIUM insecure-temp-file test/dns-disable-restore.test.mjs:310
MEDIUM insecure-temp-file test/plugins.test.mjs:152
MEDIUM insecure-temp-file test/pty.test.mjs:28
MEDIUM insecure-temp-file test/pty.test.mjs:31
MEDIUM insecure-temp-file test/pty.test.mjs:40
MEDIUM insecure-temp-file test/pty.test.mjs:42
MEDIUM insecure-temp-file test/pty.test.mjs:47
MEDIUM insecure-temp-file test/pty.test.mjs:48
MEDIUM insecure-temp-file test/pty.test.mjs:49
MEDIUM insecure-temp-file test/tabs.test.mjs:8
MEDIUM insecure-temp-file test/tabs.test.mjs:13
MEDIUM insecure-temp-file test/tabs.test.mjs:14
MEDIUM insecure-temp-file test/tabs.test.mjs:22
MEDIUM insecure-temp-file test/trust.test.mjs:240
LOW secret-generic-credential apps/pwa/test/apikey-bearer-scheme.test.mjs:30
LOW secret-generic-credential apps/pwa/test/apikey-mask.test.mjs:38
LOW secret-generic-credential apps/pwa/test/apikey-reveal.test.mjs:35
LOW secret-generic-credential apps/pwa/test/approvals-context.test.mjs:28
LOW secret-generic-credential apps/pwa/test/approvals-credits.test.mjs:28
LOW secret-generic-credential apps/pwa/test/approvals-notify.test.mjs:26

…and 45 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit b49be03 into main Aug 11, 2026
4 checks passed
@ralyodio ralyodio mentioned this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants