created harness to better test bots. improved bot play
This commit is contained in:
+35
-3
@@ -229,11 +229,23 @@ export function canBoard(s: GameState, player: PlayerIndex, at: GridCoord): bool
|
||||
return trainAtOfficeWith(s, player, (c) => c.type === 'coach' && !c.loaded);
|
||||
}
|
||||
|
||||
/** §9.2 — de-training needs an open red slot and a train carrying a loaded coach. */
|
||||
/**
|
||||
* §9.2 — de-training needs an open red slot, a train carrying a loaded coach, AND AN EMPTY COACH IN
|
||||
* THE DIVISION YARD.
|
||||
*
|
||||
* "*Requirements: a white empty coach in the Division Yard and an unoccupied red Unloading slot.
|
||||
* Replace the blue coach on the train with the white one.*" The empty coach is where the passengers
|
||||
* were sitting — it has to come from somewhere, and the rule says where.
|
||||
*
|
||||
* That requirement was missing, and the reducer conjured the coach rather than taking it, so every
|
||||
* de-training MINTED a coach: the loaded one went to the red box and a new empty one appeared in the
|
||||
* train. Measured at 1.29 cars a game created out of nothing across the two inbound paths.
|
||||
*/
|
||||
export function canDetrain(s: GameState, player: PlayerIndex, at: GridCoord): boolean {
|
||||
const f = facilityAt(s, player, at);
|
||||
if (!f || f.kind !== 'passenger' || portersLeft(f) < 1) return false;
|
||||
if (f.inboundBox.length >= f.capacity.inbound) return false;
|
||||
if (!s.yards.divisionYard.some((c) => c.type === 'coach' && !c.loaded)) return false;
|
||||
return trainAtOfficeWith(s, player, (c) => c.type === 'coach' && c.loaded);
|
||||
}
|
||||
|
||||
@@ -557,6 +569,14 @@ export function check(s: GameState, player: PlayerIndex, i: Intent): RejectionCo
|
||||
if (laborersLeft(f) < 1) return 'RESOURCE_SPENT';
|
||||
const car = f.industryTrack.cars[i.carIndex];
|
||||
if (!car || !car.loaded) return 'WRONG_CAR_TYPE';
|
||||
/**
|
||||
* §9.3 — "*Requirements: a load on the industry's track AND AN EMPTY CAR OF THAT TYPE IN THE
|
||||
* DIVISION YARD. The first Laborer replaces the load with an empty car of that type.*"
|
||||
*
|
||||
* The empty car requirement was not checked and the reducer conjured the car rather than
|
||||
* taking it, so every unload minted one. Unloading is meant to consume supply.
|
||||
*/
|
||||
if (!s.yards.divisionYard.some((c) => c.type === car.type && !c.loaded)) return 'NO_SUITABLE_CAR';
|
||||
// The load is placed on WORK, the last box, so that box must be free.
|
||||
return f.menAtWork[f.menAtWork.length - 1] === null ? null : 'BOX_FULL';
|
||||
}
|
||||
@@ -1399,8 +1419,13 @@ export function reduce(s: GameState, e: GameEvent): void {
|
||||
const tray = s.trays.get(id);
|
||||
const ci = tray?.consist.findIndex((c) => c.type === 'coach' && c.loaded) ?? -1;
|
||||
if (tray && ci >= 0) {
|
||||
// The empty coach comes OUT OF THE DIVISION YARD, as §9.2 says. It used to be conjured,
|
||||
// which minted a coach on every de-training.
|
||||
const yi = s.yards.divisionYard.findIndex((c) => c.type === 'coach' && !c.loaded);
|
||||
const empty = yi >= 0 ? s.yards.divisionYard.splice(yi, 1)[0]! : { type: 'coach' as const, loaded: false };
|
||||
refillDivisionYardIfEmpty(s);
|
||||
f.inboundBox.push(tray.consist[ci]!);
|
||||
tray.consist[ci] = { type: 'coach', loaded: false };
|
||||
tray.consist[ci] = empty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1448,7 +1473,14 @@ export function reduce(s: GameState, e: GameEvent): void {
|
||||
case 'unloadBegan': {
|
||||
const f = facilityAt(s, e.player, e.at)!;
|
||||
const ci = f.industryTrack.cars.findIndex((c) => c.loaded);
|
||||
if (ci >= 0) f.industryTrack.cars[ci] = { type: e.carType, loaded: false };
|
||||
if (ci >= 0) {
|
||||
// §9.3 — the replacement empty comes out of the Division Yard. Conjuring it here is what
|
||||
// minted a car on every unload, and it also skipped a requirement the rule states.
|
||||
const yi = s.yards.divisionYard.findIndex((c) => c.type === e.carType && !c.loaded);
|
||||
const empty = yi >= 0 ? s.yards.divisionYard.splice(yi, 1)[0]! : { type: e.carType, loaded: false };
|
||||
refillDivisionYardIfEmpty(s);
|
||||
f.industryTrack.cars[ci] = empty;
|
||||
}
|
||||
f.menAtWork[f.menAtWork.length - 1] = { type: e.carType, dir: 'in' };
|
||||
f.usedThisStage.laborers += 1;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user