feat: honest AI opponent in quick game
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s

New Game's quick game gains an explicit opponent selector — 🤖 AI (default)
or 👤 Random player. AI starts a game seated with a pooled robot that joins
and moves at once: no per-move timeout (a 7-day inactivity loss reusing the
turn-timeout sweeper), chat/nudge disabled, no statistics, the opponent shown
as 🤖 everywhere. The random path (disguised robot) is unchanged.

Driven by one game flag (games.vs_ai), set only on AI-started games so the
disguised path is never revealed; Matchmaker.StartVsAI seats the robot
directly (no open pool); the robot replies event-driven via the game service's
after-commit/after-create hook. Wire: vs_ai on EnqueueRequest + GameView.
This commit is contained in:
Ilia Denisov
2026-06-15 20:14:24 +02:00
parent 91d5c341ef
commit aa765a0c06
56 changed files with 901 additions and 86 deletions
+8 -2
View File
@@ -227,7 +227,9 @@ IV 🏅; active games show Your move 🟢 / Opponent's move ⏳; invitations use
Decline for an invitee and a waiting/Cancel state for the inviter; a single-word-rule
invitation adds a **"One word per turn"** line to the card. Creating a game lives in
`NewGame.svelte`: **"Play with friends"** (pick invitees, then variant / move time /
hints) and **auto-match** (random opponent). The auto-match variant plaques are
hints) and **quick game**. Quick game opens with a two-button **🤖 AI / 👤 Random player**
segmented select (the `.seg`/`.opt` style, **AI the default**) in place of the old subtitle — AI
is an honest robot you knowingly play, Random player is auto-match against a human. The variant plaques are
**mutually-exclusive selects** — a tap **highlights** one (an accent inset border) instead
of starting a game; a lone offered variant is pre-selected, and a bottom **Start game**
button (disabled until a variant is chosen) confirms. For a **Russian** variant (either
@@ -237,7 +239,11 @@ IV 🏅; active games show Your move 🟢 / Opponent's move ⏳; invitations use
opponent's score card (and the game's lobby row) reads the localized **"searching for opponent"**
placeholder, the add-friend 🤝 is hidden, and resign and the chat's send/nudge are disabled; an
**opponent_joined** push restores them in place when a human or robot takes the seat, and a line
under Start game notes the wait can take a while (the app may be closed meanwhile).
under Start game notes the wait can take a while (the app may be closed meanwhile). With **AI**
selected there is no wait: the move-clock line instead reads **"Loss after 7 days of inactivity"**
and the "searching" hint is hidden; the game starts already seated, the opponent shows as **🤖**
everywhere (score card, lobby row, turn line), the add-friend 🤝 is never drawn, and the chat's
send field and 🛎️ nudge stay **disabled** while the 🔎 dictionary word-check keeps working.
- **Statistics** (`screens/Stats.svelte`, the lobby 📊 tab): a 2-column grid of stat
cards (wins / losses / draws / games / win-rate / best game / best move) — pure
numbers, no charts.