v0.4.0 — multiplayer Phases 0 and 1: seat and player split apart, turn state per player, the page behind a Session, and eight seat/player mix-ups fixed with tests that fail without them
This commit is contained in:
+26
-4
@@ -7,6 +7,7 @@
|
||||
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { turnOf } from '../src/engine/state.ts';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
@@ -1520,6 +1521,27 @@ describe('the static build', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('renders from the Frame and the Menu, never from GameState', () => {
|
||||
/**
|
||||
* THE PROPERTY THAT MAKES A REMOTE CLIENT POSSIBLE.
|
||||
*
|
||||
* `main.ts` used to reach into `game.state` in eleven places — the phase, the outcome, another
|
||||
* player's name, the hand count. That is free with the engine in the same process and impossible
|
||||
* with a server, where the client holds no state at all: it has neither the deck order nor
|
||||
* anyone else's hand, and could not be given them without handing over the game.
|
||||
*
|
||||
* Everything it needs now lives on `Frame` and `Menu`. This is a cheap guard on a property that
|
||||
* is very easy to lose — one `game.state.clock.day` would compile, run, and quietly make the
|
||||
* page unable to run against a server. See `docs/architecture/multiplayer.md` §5.
|
||||
*/
|
||||
const src = readFileSync(join(root, 'src/web/main.ts'), 'utf8');
|
||||
const reads = [...src.matchAll(/game\.state[.[]/g)];
|
||||
assert.equal(
|
||||
reads.length, 0,
|
||||
`main.ts reaches into game.state ${reads.length} time(s); it must render from Frame + Menu`,
|
||||
);
|
||||
});
|
||||
|
||||
it('asks only for elements the page actually has', () => {
|
||||
// Cheap and total: compare every $('id') in the source against the ids in the served HTML.
|
||||
// Getting this wrong does not degrade the page, it stops the game starting at all.
|
||||
@@ -1826,7 +1848,7 @@ describe('the static build', () => {
|
||||
{ type: 'caboose', loaded: false },
|
||||
],
|
||||
direction: 'east', facing: 'e',
|
||||
position: { at: 'grid', owner: 0, coord: { row: area.runningRow, col: 0 } }, movesUsed: 0,
|
||||
position: { at: 'grid', seat: 0, coord: { row: area.runningRow, col: 0 } }, movesUsed: 0,
|
||||
} as never);
|
||||
|
||||
const cell = view(game).cells.find((c) => c.row === area.runningRow && c.col === 0)!;
|
||||
@@ -1862,7 +1884,7 @@ describe('the static build', () => {
|
||||
{ type: 'caboose', loaded: false },
|
||||
],
|
||||
direction: facing === 'e' ? 'east' : 'west', facing,
|
||||
position: { at: 'grid', owner: 0, coord: { row: area.runningRow, col: 0 } }, movesUsed: 0,
|
||||
position: { at: 'grid', seat: 0, coord: { row: area.runningRow, col: 0 } }, movesUsed: 0,
|
||||
} as never);
|
||||
const cell = view(game).cells.find((c) => c.row === area.runningRow && c.col === 0)!;
|
||||
const svg = officeSvg([cell], area.runningRow);
|
||||
@@ -1899,7 +1921,7 @@ describe('the static build', () => {
|
||||
id, trainNumber: null, trainIsExtra: false, engineAt: 1,
|
||||
consist: [{ type: 'boxcar', loaded: true }, { type: 'caboose', loaded: false }],
|
||||
direction: 'east', facing: 'e',
|
||||
position: { at: 'grid', owner: 0, coord: { row: area.runningRow, col: 1 } }, movesUsed: 0,
|
||||
position: { at: 'grid', seat: 0, coord: { row: area.runningRow, col: 1 } }, movesUsed: 0,
|
||||
} as never);
|
||||
const front = describeIntent(game.state, { type: 'switch.dropCars', trayId: id, count: 1, fromNose: true });
|
||||
const back = describeIntent(game.state, { type: 'switch.dropCars', trayId: id, count: 1 });
|
||||
@@ -2276,7 +2298,7 @@ describe('the hand limit is a limit, not a toll on drawing (regression)', () =>
|
||||
if (currentActor(game) === null) break;
|
||||
const { options, groups } = actionGroups(game);
|
||||
if (groups.length === 0 || options.length === 0) break;
|
||||
if (game.state.clock.phase === 'localOps' && game.state.turn.option === 'draw') {
|
||||
if (game.state.clock.phase === 'localOps' && turnOf(game.state, 0).option === 'draw') {
|
||||
const engineAllows = options.some((o) => o.type === 'draw.end');
|
||||
assert.equal(
|
||||
engineAllows,
|
||||
|
||||
Reference in New Issue
Block a user