Draw freight on the card, and teach the bot to build a railroad

This commit is contained in:
Jesse
2026-08-04 04:59:23 -04:00
parent e23d81eade
commit 982952fd6c
11 changed files with 1288 additions and 89 deletions
+42 -2
View File
@@ -6,8 +6,8 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { applyIntent, check, areaOf } from '../src/engine/apply.ts';
import { HAND_LIMIT, MOVES_PER_LOCAL_OPS } from '../src/engine/content.ts';
import { applyIntent, check, areaOf, facilityCarTypes } from '../src/engine/apply.ts';
import { HAND_LIMIT, INDUSTRY_PROFILES, MOVES_PER_LOCAL_OPS } from '../src/engine/content.ts';
import type { Intent } from '../src/engine/intents.ts';
import { legalActions } from '../src/engine/legal.ts';
import { createGame } from '../src/engine/setup.ts';
@@ -595,3 +595,43 @@ describe('legalActions shares its rules with apply (component 6)', () => {
assert.equal(legalActions(s, 0).length, 0);
});
});
describe('an industry handles every commodity its profile names (regression)', () => {
it('derives car types from the sheet rather than a hand-written map', () => {
// REGRESSION, and the reason tank cars were dropped ZERO times in 100 games.
//
// Two maps of industry -> car type existed. The engine's returned `carTypes[0]`, so the second
// commodity of a two-commodity industry was unreachable — a Power Plant burns coal OR oil and
// a Grocer's Warehouse receives dry goods OR perishables. The bot kept its own switch beside
// it, which had drifted further: it named `produceShed` and `oilRefinery`, neither of which is
// an industry, and omitted `freightHouse`, `refinery` and `packingSheds`, which are. Half the
// industries in the deck therefore never asked for a car at all.
//
// Asserting equality with the profile is the point: it fails if anyone writes a third map.
for (const p of INDUSTRY_PROFILES) {
const f = {
kind: 'freight',
subtype: p.kind,
allows: { outbound: p.baseOut > 0, inbound: p.baseIn > 0 },
outboundBox: [], inboundBox: [],
capacity: { outbound: p.baseOut, inbound: p.baseIn },
menAtWork: [null, null, null],
industryTrack: { length: 2, cars: [] },
laborers: p.baseLoaders, porters: 0,
usedThisStage: { laborers: 0, porters: 0 },
} as never;
assert.deepEqual(
[...facilityCarTypes(f)],
[...p.carTypes],
`${p.kind} handles ${p.carTypes.join('/')} on the sheet`,
);
}
});
it('names both commodities of the two-commodity industries', () => {
// Guards the specific collapse: `carTypes[0]` alone passes the test above only if someone also
// rewrites the profiles, but silently fails this one.
const twoCommodity = INDUSTRY_PROFILES.filter((p) => p.carTypes.length > 1).map((p) => p.kind);
assert.deepEqual(twoCommodity, ['powerPlant', 'grocersWarehouse']);
});
});