feat(offline): randomise the hotseat seating (random first mover)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m7s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s

The 2-4 hotseat players now start in a random seating order (so who goes
first, and the turn order, is random) — matching the online games. The
engine starts at seat 0, so shuffling the seats at creation picks the
starter; the shuffle is seed-driven (a distinct derivation from the tile
bag), so replay reproduces it. Scoped to hotseat — vs_ai stays human-first.
- lib/roster.ts: shuffleSeeded (unit-tested); NewGame shuffles buildSeats
  with the game seed before create.
- e2e: pin a seed that keeps the roster order so the lock assertions stay
  deterministic.
This commit is contained in:
Ilia Denisov
2026-07-07 16:20:53 +02:00
parent 80cc2a76e8
commit a7f483792f
4 changed files with 53 additions and 8 deletions
+4 -2
View File
@@ -41,8 +41,10 @@ test.describe('offline hotseat (pass-and-play)', () => {
await enterLobby(page);
await goOffline(page);
// A known bag seed so the locked seat deals a full rack deterministically.
await page.evaluate(() => (window as unknown as { __mock: { setLocalSeed(s: string): void } }).__mock.setLocalSeed('1'));
// A known seed: the bag deals a full rack deterministically, AND the seeded seating shuffle keeps
// the roster order (Ann first) so the locked-seat assertions below are stable. (Seat order is
// randomised at start; the shuffle is seed-driven — seed '1' would seat Bob first, '2' Ann first.)
await page.evaluate(() => (window as unknown as { __mock: { setLocalSeed(s: string): void } }).__mock.setLocalSeed('2'));
// New game -> the offline mode selector now offers "with friends" (hotseat).
await page.locator('button.tab').nth(0).click();