fix tracks to all be EW. no more NS tracks
This commit is contained in:
+50
-19
@@ -28,8 +28,9 @@ import {
|
||||
} from '../engine/content.ts';
|
||||
import type { Intent } from '../engine/intents.ts';
|
||||
import type { Facility, GameState, TrackCard } from '../engine/state.ts';
|
||||
import type { TrackGeometry } from '../engine/content.ts';
|
||||
import { connectionsFor, variantsFor } from '../engine/track.ts';
|
||||
import type { Hand, TrackGeometry } from '../engine/content.ts';
|
||||
import type { Port } from '../engine/track.ts';
|
||||
import { connectionsFor, slopeOfPair, variantsFor } from '../engine/track.ts';
|
||||
import type { Impediment } from './narrate.ts';
|
||||
import { carLabel, clockTime, impediments, phaseLabel } from './narrate.ts';
|
||||
|
||||
@@ -49,7 +50,8 @@ export type CellView = {
|
||||
cars: string[];
|
||||
facility: FacilityView | null;
|
||||
/**
|
||||
* The port pairs this card joins, as two-letter codes — 'ew', 'ns', 'es' and so on. Taken from
|
||||
* The port pairs this card joins, as two-letter codes — 'ew' for the through track, and 'ne',
|
||||
* 'nw', 'se' or 'sw' for a 45° leg. There is no 'ns': no card joins north to south. Taken from
|
||||
* the engine's own `connectionsFor`, so a drawn rail can never claim a connection the rules do
|
||||
* not have.
|
||||
*/
|
||||
@@ -337,7 +339,7 @@ export function describeIntent(s: GameState, i: Intent): string {
|
||||
case 'track.lay':
|
||||
return (
|
||||
`lay ${i.hand === 'none' ? '' : i.hand + '-hand '}${geometryLabel(i.geometry)} ` +
|
||||
`at ${at(i.placement)}${variantLabel(i.geometry, i.variant)}`
|
||||
`at ${at(i.placement)}${variantLabel(i.geometry, i.variant, i.hand)}`
|
||||
);
|
||||
case 'switch.move':
|
||||
return `move to ${at(i.to)}${i.reverse ? ' (reverse)' : ''}`;
|
||||
@@ -803,28 +805,50 @@ function cellDescription(card: TrackCard, officeName: string, onRunning: boolean
|
||||
case 'track':
|
||||
switch (g.geometry) {
|
||||
case 'straight':
|
||||
return g.axis === 'ns' ? 'through track, north–south' : 'through track, east–west';
|
||||
return 'through track, east–west';
|
||||
case 'curved':
|
||||
case 'sharpCurved': {
|
||||
const stem = g.hand === 'left' ? 'west' : 'east';
|
||||
const arc = g.arc ?? (g.hand === 'right' ? 'se' : 'sw');
|
||||
const cost = g.geometry === 'sharpCurved' ? ' · costs TWO Moves to cross' : '';
|
||||
return `curve — joins the ${stem} end to a leg running south${cost}`;
|
||||
return `${arcPhrase(arc)}${cost}${slopePhrase(arc[0] as Port, arc[1] as Port)}`;
|
||||
}
|
||||
case 'turnout': {
|
||||
const t = g.turnout;
|
||||
if (!t) return 'turnout';
|
||||
const dir = (p: string): string =>
|
||||
({ n: 'north', s: 'south', e: 'east', w: 'west' })[p] ?? p;
|
||||
// §A.1 is the subtlety worth spelling out: the missing edge, not a one-way street.
|
||||
return (
|
||||
`turnout — stem ${dir(t.stem)}, through ${dir(t.through)}, diverges ${dir(t.diverge)} · ` +
|
||||
`a train may run stem↔through or stem↔diverge, but NEVER between ${dir(t.through)} and ${dir(t.diverge)}`
|
||||
`turnout — stem ${compass(t.stem)}, through ${compass(t.through)}, diverges ${compass(t.diverge)} at 45° · ` +
|
||||
`a train may run stem↔through or stem↔diverge, but NEVER between ${compass(t.through)} and ` +
|
||||
`${compass(t.diverge)}${slopePhrase(t.stem as Port, t.diverge as Port)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const compass = (p: string): string => ({ n: 'north', s: 'south', e: 'east', w: 'west' })[p] ?? p;
|
||||
|
||||
/**
|
||||
* A curve, as the printed card draws it: along the centre line to a frog, then out at 45° through
|
||||
* the MIDDLE of an edge. Naming both halves is what stops it reading as a quarter-circle corner.
|
||||
*/
|
||||
function arcPhrase(arc: string): string {
|
||||
const [a, b] = [arc[0] as Port, arc[1] as Port];
|
||||
const [side, leg] = a === 'n' || a === 's' ? [b, a] : [a, b];
|
||||
return `curve — runs ${compass(side!)} along the centre line, then leaves at 45° through the middle of the ${compass(leg!)} edge`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The matching rule, said out loud. Which diagonal a leg sits on decides what may sit above or
|
||||
* below it, and that is not something a player can read off the card's shape at a glance.
|
||||
*/
|
||||
function slopePhrase(a: Port, b: Port): string {
|
||||
const slope = slopeOfPair(a, b);
|
||||
if (!slope) return '';
|
||||
const mate = slope === 'ne_sw' ? 'north–east / south–west' : 'north–west / south–east';
|
||||
return ` · its 45° leg lies on the ${mate} diagonal, and only meets a card whose leg lies on the same one`;
|
||||
}
|
||||
|
||||
/** An industry's name for the screen. `office` is the Passenger Facility, not an industry. */
|
||||
function facilityLabel(key: string): string {
|
||||
return key === 'office' ? 'the Office' : (FACILITY_NAMES[key] ?? prettyKey(key));
|
||||
@@ -861,18 +885,25 @@ function objectiveOf(s: GameState): Frame['objective'] {
|
||||
*
|
||||
* "rotation 2" is not a choice anyone can make — for a curve or a turnout the orientation IS the
|
||||
* decision. Read from `variantsFor`, the same list the placement uses, so the label cannot describe
|
||||
* one rotation while the engine lays another.
|
||||
* one rotation while the engine lays another. There are only two: a printed card turns 180° but
|
||||
* never flips, so `hand` and not the rotation decides which diagonal the 45° leg lies on.
|
||||
*/
|
||||
export function variantLabel(geometry: TrackGeometry, variant: number | undefined): string {
|
||||
const v = variantsFor(geometry)[variant ?? 0];
|
||||
export function variantLabel(
|
||||
geometry: TrackGeometry,
|
||||
variant: number | undefined,
|
||||
hand: Hand = 'none',
|
||||
): string {
|
||||
const v = variantsFor(geometry, hand)[variant ?? 0];
|
||||
if (!v) return '';
|
||||
if (v.axis) return v.axis === 'ew' ? ' — east–west' : ' — north–south';
|
||||
if (v.turnout) {
|
||||
const dir = (p: string): string =>
|
||||
({ n: 'north', s: 'south', e: 'east', w: 'west' })[p] ?? p;
|
||||
return ` — stem ${dir(v.turnout.stem)}, through ${dir(v.turnout.through)}, diverges ${dir(v.turnout.diverge)}`;
|
||||
return ` — stem ${compass(v.turnout.stem)}, through ${compass(v.turnout.through)}, diverges ${compass(v.turnout.diverge)} at 45°`;
|
||||
}
|
||||
return '';
|
||||
if (v.arc) {
|
||||
const [a, b] = [v.arc[0] as Port, v.arc[1] as Port];
|
||||
const [side, leg] = a === 'n' || a === 's' ? [b, a] : [a, b];
|
||||
return ` — ${compass(side!)} to the middle of the ${compass(leg!)} edge`;
|
||||
}
|
||||
return ' — east–west';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user