v0.4.2 fixed passenger trains not stopping at office. changeed revenue for train completing run - 1 point to every player when train exits division
This commit is contained in:
+66
-24
@@ -332,7 +332,7 @@ describe('ABS Signals amend the collision rule', () => {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('one Revenue for every train that clears your section', () => {
|
||||
describe('one Revenue to EVERY player when a train completes its run', () => {
|
||||
/** A train sitting on the A/D track, made up and ready to highball. */
|
||||
const readyToLeave = (s: GameState, direction: 'east' | 'west' = 'east'): string => {
|
||||
const id = 'leaving';
|
||||
@@ -348,35 +348,61 @@ describe('one Revenue for every train that clears your section', () => {
|
||||
return id;
|
||||
};
|
||||
|
||||
it('pays the owner when a train departs onto the Mainline', () => {
|
||||
it('pays nothing for merely leaving the Office', () => {
|
||||
/**
|
||||
* THE RULE CHANGED IN v0.4.2. It used to pay 1 to the Office a train departed, which meant a
|
||||
* five-Office railroad paid five times for one train and paid most to whoever it happened to
|
||||
* pass first. Departing is now worth nothing on its own.
|
||||
*/
|
||||
const s = game();
|
||||
const before = s.players[0]!.revenue;
|
||||
readyToLeave(s, 'east');
|
||||
|
||||
const r = advance(s);
|
||||
advance(s);
|
||||
|
||||
assert.equal(s.players[0]!.revenue, before + 1, 'clearing a train earned nothing');
|
||||
assert.ok(
|
||||
r.events.some((e) => e.type === 'revenueChanged' && e.reason === 'a train cleared your section'),
|
||||
'no revenue event was emitted for the departure',
|
||||
);
|
||||
|
||||
// Paid ONCE. Running off the end of the Division later is not a second departure — that far
|
||||
// Division Point belongs to whoever is seated at it.
|
||||
const after = s.players[0]!.revenue;
|
||||
for (let i = 0; i < 8; i++) { s.movedThisPhase = new Set(); s.clock.phase = 'mainline'; advance(s); }
|
||||
assert.equal(s.players[0]!.revenue, after, 'the same train was paid for a second time on its way out');
|
||||
assert.equal(s.players[0]!.revenue, before, 'departing the Office still paid');
|
||||
});
|
||||
|
||||
it('pays nothing for a local crew, which is switching rather than departing', () => {
|
||||
it('pays every player once the train runs off the end of the Division', () => {
|
||||
const s = game();
|
||||
const before = s.players.map((p) => p.revenue);
|
||||
readyToLeave(s, 'east');
|
||||
|
||||
// Run the Mainline Phase until the train has crossed the Division and left it.
|
||||
const seen: typeof s.players[number]['revenue'][] = [];
|
||||
let completed: { type: string; side?: string } | undefined;
|
||||
for (let i = 0; i < 20 && !completed; i++) {
|
||||
s.movedThisPhase = new Set();
|
||||
s.clock.phase = 'mainline';
|
||||
const r = advance(s);
|
||||
completed = r.events.find((e) => e.type === 'trainCompleted');
|
||||
if (completed) {
|
||||
assert.equal(completed.side, 'east', 'an eastbound train left by the wrong Division Point');
|
||||
assert.ok(
|
||||
r.events.some((e) => e.type === 'revenueChanged' && e.reason === 'a train completed its run'),
|
||||
'no revenue event was emitted for the completed run',
|
||||
);
|
||||
}
|
||||
seen.push(s.players[0]!.revenue);
|
||||
}
|
||||
assert.ok(completed, 'the train never left the Division');
|
||||
assert.deepEqual(s.players.map((p) => p.revenue), before.map((r) => r + 1), 'not every player was paid');
|
||||
|
||||
// Paid ONCE — the train is gone, so there is nothing left to pay again.
|
||||
const after = s.players.map((p) => p.revenue);
|
||||
for (let i = 0; i < 8; i++) { s.movedThisPhase = new Set(); s.clock.phase = 'mainline'; advance(s); }
|
||||
assert.deepEqual(s.players.map((p) => p.revenue), after, 'the same run was paid twice');
|
||||
});
|
||||
|
||||
it('pays nothing for a local crew, which is switching rather than running', () => {
|
||||
const s = game();
|
||||
const before = s.players[0]!.revenue;
|
||||
const id = readyToLeave(s, 'east');
|
||||
s.trays.get(id)!.trainNumber = null;
|
||||
|
||||
advance(s);
|
||||
for (let i = 0; i < 20; i++) { s.movedThisPhase = new Set(); s.clock.phase = 'mainline'; advance(s); }
|
||||
|
||||
assert.equal(s.players[0]!.revenue, before, 'a switching crew was paid as though it had cleared the section');
|
||||
assert.equal(s.players[0]!.revenue, before, 'a switching crew was paid as though it had completed a run');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -391,8 +417,9 @@ describe('§8.1 — a train that just arrived from the mainline stops for the St
|
||||
* Nothing states the rule, which makes it exactly the kind of property a later change to the loop
|
||||
* would break in silence. These pin it.
|
||||
*
|
||||
* It is also what gives the printed **Expedite** rule something to do — an Expedited train is
|
||||
* granted a second `moveTrain` in the same phase precisely because the general rule forbids it.
|
||||
* It is also what gives the printed **Expedite** rule something to do — an Expedited train departs
|
||||
* at the END of the Stage it arrived, precisely because the general rule forbids it departing at
|
||||
* all. It stands through Load/Unload first, which is what makes its coaches workable.
|
||||
*/
|
||||
const arriving = (s: GameState, trainNumber: number, isExtra = false): string => {
|
||||
const id = 'arriving';
|
||||
@@ -425,9 +452,13 @@ describe('§8.1 — a train that just arrived from the mainline stops for the St
|
||||
);
|
||||
});
|
||||
|
||||
it('unless its card prints Expedite, which exists to override exactly this', () => {
|
||||
// Q3. Train 3 prints `expedite`, so it arrives and departs in one Stage — still subject to the
|
||||
// rest of §8.1, which is why the clearance path is re-entered rather than skipped.
|
||||
it('unless its card prints Expedite — but it still stands through Load/Unload first', () => {
|
||||
/**
|
||||
* Q3, as revised in v0.4.2. An Expedite train leaves the Stage it arrives, but at the END of that
|
||||
* Stage rather than inside the Mainline Phase. It used to be gone before Load/Unload ran, which
|
||||
* made the coaches on every coach-carrying express (1/2, 5/6, 19) permanently unworkable —
|
||||
* measured, Train 2 arrived 32 times in 60 games and stood for Load/Unload in none of them.
|
||||
*/
|
||||
const s = game();
|
||||
assert.equal(trainProfile(3, false)?.rules.expedite, true, 'train 3 should be an Expedite train');
|
||||
const id = arriving(s, 3);
|
||||
@@ -435,8 +466,19 @@ describe('§8.1 — a train that just arrived from the mainline stops for the St
|
||||
advance(s);
|
||||
|
||||
assert.equal(
|
||||
s.trays.get(id)!.position.at, 'mainline',
|
||||
'an Expedite train should leave again the Stage it arrives',
|
||||
s.trays.get(id)!.position.at, 'grid',
|
||||
'an Expedite train left inside the Mainline Phase, before Load/Unload could reach it',
|
||||
);
|
||||
assert.equal(s.trays.get(id)!.departsThisStage, true, 'it is not flagged to leave at the end of the Stage');
|
||||
assert.ok(areaOf(s, 0).adOccupancy.includes(id), 'it is not standing on an A/D track');
|
||||
|
||||
// Run the end of the Stage directly. Load/Unload waits on the player, and the thing under test
|
||||
// is the departure pass that follows it — not that phase's own input handling.
|
||||
s.clock.phase = 'shiftChange';
|
||||
advance(s);
|
||||
assert.notEqual(
|
||||
s.trays.get(id)?.position.at, 'grid',
|
||||
'an Expedite train should be gone by the end of the Stage it arrived',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user