Draw the board as track, split the site into three pages, and fix curve geometry
Adds SVG board rendering shared by the game and the replay, a splash page with a shareable replay directory, hover tooltips for reference detail, and makes curves two-port arcs so sidings and run-arounds can finally be built.
This commit is contained in:
+19
-1
@@ -28,7 +28,7 @@ import {
|
||||
import type { Intent } from '../engine/intents.ts';
|
||||
import type { Facility, GameState, TrackCard } from '../engine/state.ts';
|
||||
import type { TrackGeometry } from '../engine/content.ts';
|
||||
import { variantsFor } from '../engine/track.ts';
|
||||
import { connectionsFor, variantsFor } from '../engine/track.ts';
|
||||
import type { Impediment } from './narrate.ts';
|
||||
import { carLabel, clockTime, impediments, phaseLabel } from './narrate.ts';
|
||||
|
||||
@@ -47,6 +47,12 @@ export type CellView = {
|
||||
tray: string | null;
|
||||
cars: string[];
|
||||
facility: FacilityView | null;
|
||||
/**
|
||||
* The port pairs this card joins, as two-letter codes — 'ew', 'ns', 'es' and so on. Taken from
|
||||
* the engine's own `connectionsFor`, so a drawn rail can never claim a connection the rules do
|
||||
* not have.
|
||||
*/
|
||||
links: string[];
|
||||
/**
|
||||
* What this card DOES, now that it is on the board.
|
||||
*
|
||||
@@ -85,6 +91,11 @@ export type DivisionView = {
|
||||
kind: string;
|
||||
label: string;
|
||||
trains: TrainChip[][];
|
||||
/**
|
||||
* How many trains may stand here, or null for no limit. 1 on most Mainline cards, 2 where the
|
||||
* card prints "trains may pass", the A/D count at an Office, unlimited at a Division Point.
|
||||
*/
|
||||
capacity: number | null;
|
||||
/** Modifiers laid on a Mainline card (Brakeman, Helpers, Realignment …). */
|
||||
modifiers: string[];
|
||||
/**
|
||||
@@ -109,6 +120,8 @@ export type Frame = {
|
||||
whereFrom: { row: number; col: number } | null;
|
||||
division: DivisionView[];
|
||||
cells: CellView[];
|
||||
/** Which grid row is the Running Track — the spine the district hangs beneath. */
|
||||
runningRow: number;
|
||||
facilities: FacilityView[];
|
||||
hand: string[];
|
||||
/** What each hand card does, in the same order — names alone are not a playable hand. */
|
||||
@@ -392,6 +405,7 @@ export function snapshot(
|
||||
label,
|
||||
running: row === area.runningRow,
|
||||
what: cellDescription(card, officeProfile(area.tier).name, row === area.runningRow),
|
||||
links: connectionsFor(card).map(([a, b]) => `${a}${b}`),
|
||||
enhancements: card.enhancements.map(prettyKey),
|
||||
tray: trayAt.get(key) ?? null,
|
||||
cars: (card.facility?.industryTrack.length ? card.facility.industryTrack.cars : card.standing).map(carLabel),
|
||||
@@ -405,6 +419,7 @@ export function snapshot(
|
||||
kind: 'dp',
|
||||
label: n.side === 'west' ? 'West DP' : 'East DP',
|
||||
trains: [n.holding.map((id) => trainChip(s, id))],
|
||||
capacity: null,
|
||||
modifiers: [],
|
||||
gradeUp: null,
|
||||
};
|
||||
@@ -421,6 +436,7 @@ export function snapshot(
|
||||
const chip = trainChip(s, t.tray);
|
||||
return { ...chip, label: `${chip.label} (${t.stagesRemaining})` };
|
||||
})],
|
||||
capacity: MAINLINE_PROFILES.find((m) => m.kind === n.card)?.trainsMayPass ? 2 : 1,
|
||||
modifiers: [
|
||||
...(n.modifiers ?? []).map(prettyKey),
|
||||
...(n.absSignals ? ['ABS Signals'] : []),
|
||||
@@ -432,6 +448,7 @@ export function snapshot(
|
||||
kind: 'office',
|
||||
label: officeProfile(areaOf(s, n.owner).tier).name,
|
||||
trains: [areaOf(s, n.owner).adOccupancy.map((id) => trainChip(s, id))],
|
||||
capacity: officeProfile(areaOf(s, n.owner).tier).adTracks,
|
||||
modifiers: [],
|
||||
gradeUp: null,
|
||||
};
|
||||
@@ -466,6 +483,7 @@ export function snapshot(
|
||||
return { piece: `${hand === 'none' ? '' : hand + '-hand '}${geometryLabel(geometry ?? '')}`, left };
|
||||
})
|
||||
.sort((a, b) => a.piece.localeCompare(b.piece)),
|
||||
runningRow: area.runningRow,
|
||||
blocked: impediments(s, 0),
|
||||
trains: [...s.trays.values()].map((t) => ({
|
||||
label: t.trainNumber === null ? 'local crew' : `Train ${t.trainIsExtra ? 'X' : ''}${t.trainNumber}`,
|
||||
|
||||
Reference in New Issue
Block a user