v0.3.1 — multiplayer groundwork: 13 engine tests covering 2-4 player games, a seat parameter on snapshot and actionMenu so a player sees their own railroad rather than seat 0's, and the 22 opponent-directed cards held out of every deck until they are built
This commit is contained in:
+21
-9
@@ -547,6 +547,8 @@ function sampleDetail(s: GameState, kind: string, list: Intent[]): string {
|
||||
/** One readable line for a single intent. */
|
||||
export function describeIntent(s: GameState, i: Intent): string {
|
||||
const at = (c: { row: number; col: number }): string => `(${c.row},${c.col})`;
|
||||
// An intent belongs to whoever is acting, so it is described against THEIR district.
|
||||
const seat: PlayerIndex = s.clock.currentActor ?? 0;
|
||||
switch (i.type) {
|
||||
case 'localOps.choose':
|
||||
// The most consequential decision of the Stage, and it was labelled "choose switch". Say what
|
||||
@@ -575,11 +577,9 @@ export function describeIntent(s: GameState, i: Intent): string {
|
||||
* moves — the second lifts a card already down — and calling both "play" hid the fact that the
|
||||
* square was not empty. The Limits sign is excluded: laying track there is ordinary growth.
|
||||
*/
|
||||
const actor = s.clock.currentActor;
|
||||
const over =
|
||||
i.placement && actor !== null
|
||||
? s.officeAreas.get(actor)?.grid.get(`${i.placement.row},${i.placement.col}`)
|
||||
: undefined;
|
||||
const over = i.placement
|
||||
? s.officeAreas.get(seat)?.grid.get(`${i.placement.row},${i.placement.col}`)
|
||||
: undefined;
|
||||
const upgrade = over?.geometry.kind === 'track';
|
||||
return (
|
||||
`${upgrade ? 'upgrade to' : 'play'} ${cardName(s, i.cardId)}` +
|
||||
@@ -651,7 +651,7 @@ export function describeIntent(s: GameState, i: Intent): string {
|
||||
* waiting for a train that can carry it. For a passenger facility that load is passengers on
|
||||
* the platform.
|
||||
*/
|
||||
const f = areaOf(s, 0).grid.get(`${i.at.row},${i.at.col}`)?.facility ?? null;
|
||||
const f = areaOf(s, seat).grid.get(`${i.at.row},${i.at.col}`)?.facility ?? null;
|
||||
const where = f?.kind === 'passenger' ? 'onto the platform' : 'into the green Loading box';
|
||||
return i.carType === 'coach' && f?.kind === 'passenger'
|
||||
? `bring passengers ${where} at ${at(i.at)} — they wait there for a train with an empty coach`
|
||||
@@ -765,6 +765,17 @@ export function describeIntent(s: GameState, i: Intent): string {
|
||||
* Build the view-model for a state. Shared with the playable web app so the live game and the
|
||||
* replay cannot drift into two different pictures of the same board.
|
||||
*/
|
||||
/**
|
||||
* The board as ONE SEAT sees it.
|
||||
*
|
||||
* `viewer` decides whose district, whose hand and whose facilities the Frame carries — everything
|
||||
* else (the Division, the timetable, the clock) is common to the table. It defaults to seat 0, which
|
||||
* is what solitaire and every replay want, so existing callers are unaffected.
|
||||
*
|
||||
* This was hardcoded to 0 throughout. That was correct while there was one player and would have
|
||||
* been a quiet disaster with more: every seat would have been shown player 0's railroad, including
|
||||
* player 0's hand, which is the one thing the state model calls secret.
|
||||
*/
|
||||
export function snapshot(
|
||||
s: GameState,
|
||||
lines: { text: string; tone: string }[],
|
||||
@@ -772,8 +783,9 @@ export function snapshot(
|
||||
whereFrom: { row: number; col: number } | null = null,
|
||||
decision: Decision | null = null,
|
||||
wasted = false,
|
||||
viewer: PlayerIndex = 0,
|
||||
): Frame {
|
||||
const area = areaOf(s, 0);
|
||||
const area = areaOf(s, viewer);
|
||||
const trayAt = new Map<string, string>();
|
||||
for (const [id, tray] of s.trays) {
|
||||
if (tray.position.at === 'grid') {
|
||||
@@ -972,8 +984,8 @@ export function snapshot(
|
||||
*
|
||||
* Both lines must reverse together or the descriptions come apart from the names.
|
||||
*/
|
||||
hand: [...(s.decks.hands.get(0) ?? [])].reverse().map((id) => cardName(s, id)),
|
||||
handWhat: [...(s.decks.hands.get(0) ?? [])].reverse().map((id) => cardDescription(s, id)),
|
||||
hand: [...(s.decks.hands.get(viewer) ?? [])].reverse().map((id) => cardName(s, id)),
|
||||
handWhat: [...(s.decks.hands.get(viewer) ?? [])].reverse().map((id) => cardDescription(s, id)),
|
||||
deck: s.decks.homeOffice.length,
|
||||
departments: s.decks.departments.map((pile) => {
|
||||
const top = pile[pile.length - 1];
|
||||
|
||||
Reference in New Issue
Block a user