adding classification and division yard updates, train collisions, engine and car updates, turnout improvements.

This commit is contained in:
Jesse
2026-08-04 21:03:17 -04:00
parent 4bba991f41
commit 821d3cdffa
26 changed files with 6876 additions and 2638 deletions
+46 -1
View File
@@ -179,6 +179,19 @@ export type Frame = {
departments: string[];
/** What each face-up Department card does. */
departmentsWhat: string[];
/**
* The two yards, by car type.
*
* Rolling stock is finite and the Classification Yard only returns to service when the Division
* Yard is BARE, so watching the Division Yard run down is now real information — and the game
* showed neither yard at all.
*/
yards: {
division: { type: string; loaded: number; empty: number }[];
classification: { type: string; loaded: number; empty: number }[];
divisionTotal: number;
classificationTotal: number;
};
/** 12 slots; the train number due out at each Stage, or null. */
timetable: (number | null)[];
blocked: Impediment[];
@@ -598,6 +611,12 @@ export function snapshot(
deck: s.decks.homeOffice.length,
departments: s.decks.departments.map((id) => (id ? cardName(s, id) : '—')),
departmentsWhat: s.decks.departments.map((id) => (id ? cardDescription(s, id) : '')),
yards: {
division: countStock(s.yards.divisionYard),
classification: countStock(s.yards.classificationYard),
divisionTotal: s.yards.divisionYard.length,
classificationTotal: s.yards.classificationYard.length,
},
timetable: [...s.timetable],
decision,
wasted,
@@ -889,12 +908,38 @@ const MODIFIER_NAMES: Record<string, string> = {
};
/** A train on the board, with what it is carrying — otherwise a run looks identical empty or full. */
/** Cars in a yard, grouped by type and split loaded / empty, in a stable order. */
function countStock(
stock: readonly { type: string; loaded: boolean }[],
): { type: string; loaded: number; empty: number }[] {
const order = ['coach', 'boxcar', 'reefer', 'hopper', 'tank', 'caboose'];
const by = new Map<string, { type: string; loaded: number; empty: number }>();
for (const c of stock) {
const row = by.get(c.type) ?? { type: c.type, loaded: 0, empty: 0 };
if (c.loaded) row.loaded += 1;
else row.empty += 1;
by.set(c.type, row);
}
return [...by.values()].sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
}
function trainChip(s: GameState, id: string): TrainChip {
const t = s.trays.get(id);
if (!t) return { label: id, consist: [] };
/**
* The engine is drawn IN the consist, at the position it occupies.
*
* A Crew Tray is an engine plus its Rolling Stock, and the engine may be pulling, pushing, or in
* the middle doing both — which is a thing a player has to be able to see, since it decides which
* end cars couple onto (§A.3) and which way the train can shove. It was recorded as a boolean
* that nothing read, so every consist was drawn as an anonymous row of cars.
*/
const cars = t.consist.map(carLabel);
const at = Math.max(0, Math.min(cars.length, t.engineAt));
cars.splice(at, 0, 'ENG');
return {
label: t.trainNumber === null ? 'crew' : `T${t.trainIsExtra ? 'X' : ''}${t.trainNumber}`,
consist: t.consist.map(carLabel),
consist: cars,
};
}