v0.3.0 — playtest fixes, two new rules, and §7 enforced
This commit is contained in:
+149
-1
@@ -14,6 +14,7 @@ import assert from 'node:assert/strict';
|
||||
|
||||
import { advance } from '../src/engine/advance.ts';
|
||||
import { applyIntent, areaOf, check } from '../src/engine/apply.ts';
|
||||
import { legalActions } from '../src/engine/legal.ts';
|
||||
import {
|
||||
MAINLINE_MODIFIER_CARDS,
|
||||
MAINLINE_PROFILES,
|
||||
@@ -488,7 +489,7 @@ describe('the Limits sign moves with the Running Track (§2.1, Gap 4a)', () => {
|
||||
);
|
||||
|
||||
// The district hangs off that turnout and is not bounded by the Limits at all. Variant 1 of a
|
||||
// left-hand curve is the `ne` arc — the one on the same diagonal as the turnout's leg.
|
||||
// left-hand curve is the `nw` arc — the one on the same diagonal as the turnout's leg.
|
||||
assert.equal(
|
||||
check(s, 0, {
|
||||
type: 'card.play', cardId: trackInHand(s, 'curved', 'left'),
|
||||
@@ -529,6 +530,153 @@ describe('the Limits sign moves with the Running Track (§2.1, Gap 4a)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('a turnout may be laid on top of a card already down', () => {
|
||||
/** Lays a straight inside the Limits and returns where it went. */
|
||||
const layStraight = (s: GameState): GridCoord => {
|
||||
const area = areaOf(s, 0);
|
||||
const target = { row: area.runningRow, col: area.limitsEast.col };
|
||||
const r = applyIntent(s, 0, {
|
||||
type: 'card.play', cardId: trackInHand(s, 'straight', 'none'), placement: target, variant: 0,
|
||||
});
|
||||
assert.ok(r.ok, 'the straight should lay on the Limits sign');
|
||||
return target;
|
||||
};
|
||||
|
||||
it('upgrades a straight, which is how a district opens off track already built', () => {
|
||||
// REPORTED from playtesting: a district can only hang off a turnout, so a player who laid a
|
||||
// straight along the main and then wanted to branch there had no move at all — the piece had to
|
||||
// have been a turnout when it went down.
|
||||
const s = game();
|
||||
s.turn.option = 'draw';
|
||||
const at = layStraight(s);
|
||||
|
||||
for (const hand of ['left', 'right'] as const) {
|
||||
for (let variant = 0; variant < 2; variant++) {
|
||||
assert.equal(
|
||||
check(s, 0, { type: 'card.play', cardId: trackInHand(s, 'turnout', hand), placement: at, variant }),
|
||||
null,
|
||||
`a ${hand}-hand turnout at rotation ${variant} must be able to upgrade a straight`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const r = applyIntent(s, 0, {
|
||||
type: 'card.play', cardId: trackInHand(s, 'turnout', 'right'), placement: at, variant: 0,
|
||||
});
|
||||
assert.ok(r.ok);
|
||||
const now = areaOf(s, 0).grid.get(`${at.row},${at.col}`)!;
|
||||
assert.equal(now.geometry.kind === 'track' && now.geometry.geometry, 'turnout', 'the square is still a straight');
|
||||
});
|
||||
|
||||
it('upgrades a curve only when the turnout diverges onto the same arc', () => {
|
||||
// The ARC, not merely the diagonal. A `sw` curve and an `ne` one share a slope but leave by
|
||||
// opposite edges, so swapping one for the other would move the leg off whatever it joined.
|
||||
const s = game();
|
||||
const area = areaOf(s, 0);
|
||||
s.turn.option = 'draw';
|
||||
|
||||
// A turnout on the main, then its matching curve on the row below — the start of every siding.
|
||||
const turnoutAt = { row: area.runningRow, col: area.limitsEast.col };
|
||||
assert.ok(applyIntent(s, 0, {
|
||||
type: 'card.play', cardId: trackInHand(s, 'turnout', 'right'), placement: turnoutAt, variant: 0,
|
||||
}).ok, 'the turnout should lay on the Limits sign');
|
||||
|
||||
const curveAt = { row: area.runningRow - 1, col: turnoutAt.col };
|
||||
assert.ok(applyIntent(s, 0, {
|
||||
type: 'card.play', cardId: trackInHand(s, 'curved', 'right'), placement: curveAt, variant: 1,
|
||||
}).ok, 'the matching curve should hang under the turnout');
|
||||
|
||||
const laid = area.grid.get(`${curveAt.row},${curveAt.col}`)!;
|
||||
const arc = laid.geometry.kind === 'track' ? laid.geometry.arc : undefined;
|
||||
assert.equal(arc, 'ne', 'the fixture is not the curve this test means to upgrade');
|
||||
|
||||
// A turnout whose leg is that same `ne` arc may replace it; every other rotation may not.
|
||||
const matching: string[] = [];
|
||||
for (const hand of ['left', 'right'] as const) {
|
||||
for (let variant = 0; variant < 2; variant++) {
|
||||
const code = check(s, 0, {
|
||||
type: 'card.play', cardId: trackInHand(s, 'turnout', hand), placement: curveAt, variant,
|
||||
});
|
||||
if (code === null) matching.push(`${hand}/${variant}`);
|
||||
else assert.equal(code, 'NOT_UPGRADEABLE_TRACK', `${hand}/${variant} should be refused as a mismatch`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(matching, ['right/1'], 'exactly the turnout diverging onto `ne` should be accepted');
|
||||
});
|
||||
|
||||
it('refuses to swap the track out from under a car, or out from under an Interlocking', () => {
|
||||
const s = game();
|
||||
const area = areaOf(s, 0);
|
||||
s.turn.option = 'draw';
|
||||
const at = layStraight(s);
|
||||
const square = area.grid.get(`${at.row},${at.col}`)!;
|
||||
|
||||
square.standing = [{ type: 'boxcar', loaded: false }];
|
||||
assert.equal(
|
||||
check(s, 0, { type: 'card.play', cardId: trackInHand(s, 'turnout', 'right'), placement: at, variant: 0 }),
|
||||
'UPGRADE_OCCUPIED',
|
||||
'track was swapped out from under a standing car',
|
||||
);
|
||||
|
||||
square.standing = [];
|
||||
square.enhancements = ['interlocking'];
|
||||
assert.equal(
|
||||
check(s, 0, { type: 'card.play', cardId: trackInHand(s, 'turnout', 'right'), placement: at, variant: 0 }),
|
||||
'UPGRADE_ENHANCED',
|
||||
'the upgrade lifted a built Interlocking with the card',
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses to upgrade anything that is not a straight or a matching curve', () => {
|
||||
// The Office is east-west track like a straight, but it is not track to build over.
|
||||
const s = game();
|
||||
const area = areaOf(s, 0);
|
||||
s.turn.option = 'draw';
|
||||
assert.equal(
|
||||
check(s, 0, {
|
||||
type: 'card.play', cardId: trackInHand(s, 'turnout', 'right'),
|
||||
placement: area.officeCoord, variant: 0,
|
||||
}),
|
||||
'NOT_UPGRADEABLE_TRACK',
|
||||
'a turnout was laid over the Office',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not let a straight or a curve upgrade anything — only a turnout may', () => {
|
||||
const s = game();
|
||||
s.turn.option = 'draw';
|
||||
const at = layStraight(s);
|
||||
|
||||
assert.equal(
|
||||
check(s, 0, { type: 'card.play', cardId: trackInHand(s, 'straight', 'none'), placement: at, variant: 0 }),
|
||||
'NOT_UPGRADEABLE_TRACK',
|
||||
'a straight was laid on top of a straight',
|
||||
);
|
||||
assert.equal(
|
||||
check(s, 0, { type: 'card.play', cardId: trackInHand(s, 'curved', 'right'), placement: at, variant: 0 }),
|
||||
'NOT_UPGRADEABLE_TRACK',
|
||||
'a curve was laid on top of a straight',
|
||||
);
|
||||
});
|
||||
|
||||
it('offers the upgrade in the legal-move list, or it may as well not exist', () => {
|
||||
// The 18 Enhancement cards were permanently dead for weeks for exactly this reason: the engine
|
||||
// allowed the play and `legalIntents` never once offered a square it could go on.
|
||||
const s = game();
|
||||
s.turn.option = 'draw';
|
||||
const at = layStraight(s);
|
||||
const cardId = trackInHand(s, 'turnout', 'right');
|
||||
|
||||
const offered = legalActions(s, 0).some(
|
||||
(i) => i.type === 'card.play' && i.cardId === cardId &&
|
||||
i.placement?.row === at.row && i.placement?.col === at.col,
|
||||
);
|
||||
assert.ok(offered, 'the occupied square is never offered, so the upgrade cannot be played');
|
||||
});
|
||||
});
|
||||
|
||||
describe("a train is made up to its card's consist (§8.2)", () => {
|
||||
it('takes a caboose when the card calls for one, and refuses a fourth freight car', () => {
|
||||
// Train 9 "Heavy Freight" is freight 3 + caboose 1. It was being made up with FOUR hoppers and
|
||||
|
||||
Reference in New Issue
Block a user