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
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:
@@ -12,6 +12,7 @@
|
||||
canNudge = false,
|
||||
waiting = false,
|
||||
nudgeOnCooldown = false,
|
||||
vsAi = false,
|
||||
onsend,
|
||||
onnudge,
|
||||
}: {
|
||||
@@ -34,6 +35,9 @@
|
||||
// are disabled (there is no one to message or hurry yet).
|
||||
waiting?: boolean;
|
||||
nudgeOnCooldown?: boolean;
|
||||
// vsAi disables both controls in an honest-AI game: the opponent is a robot, so there is
|
||||
// nobody to message or hurry. The fields still show (turn-driven), but disabled.
|
||||
vsAi?: boolean;
|
||||
onsend: (text: string) => void;
|
||||
onnudge: () => void;
|
||||
} = $props();
|
||||
@@ -67,9 +71,10 @@
|
||||
maxlength="60"
|
||||
placeholder={t('chat.placeholder')}
|
||||
bind:value={text}
|
||||
onkeydown={(e) => e.key === 'Enter' && !busy && send()}
|
||||
disabled={vsAi}
|
||||
onkeydown={(e) => e.key === 'Enter' && !busy && !vsAi && send()}
|
||||
/>
|
||||
<button class="iconbtn" onclick={send} disabled={busy || waiting || !connection.online} aria-label={t('chat.send')}>⬆️</button>
|
||||
<button class="iconbtn" onclick={send} disabled={busy || waiting || vsAi || !connection.online} aria-label={t('chat.send')}>⬆️</button>
|
||||
{:else if myTurn}
|
||||
<!-- Own turn, the one allowed message already sent: a caption holds the row until the
|
||||
next turn (no nudge — there is no one to hurry on your own turn). -->
|
||||
@@ -77,7 +82,7 @@
|
||||
{:else if canNudge}
|
||||
<!-- A flex:1 caption keeps the nudge pinned right whether or not the cooldown text shows. -->
|
||||
<span class="cooldown">{nudgeOnCooldown ? t('chat.awaitingReply') : ''}</span>
|
||||
<button class="iconbtn" onclick={onnudge} disabled={busy || waiting || nudgeOnCooldown || !connection.online} aria-label={t('chat.nudgeAction')}>🛎️</button>
|
||||
<button class="iconbtn" onclick={onnudge} disabled={busy || waiting || nudgeOnCooldown || vsAi || !connection.online} aria-label={t('chat.nudgeAction')}>🛎️</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user