diff --git a/README.md b/README.md index 8008580..81f50d5 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,20 @@ the Baltic and made Denmark worthless. `tools/datc.py` turns the Diplomacy Adjudicator Test Cases into `src/game/datc.json` -- the orders and their annotated outcomes, not the -document's prose, which is Lucas Kruijswijk's writing. **133 of 136 movement -cases pass.** They are the reason this adjudicator is worth trusting: they -found that orders were never validated, that the paradox rule did not -terminate, and that five of my own hand-written cases rested on a support -Paris cannot give. +document's prose, which is Lucas Kruijswijk's writing. Every one of the 176 +published cases is parsed and run. + +| Section | | Passing | +| --- | --- | --- | +| 6.A - 6.G | movement | 133 / 139 | +| 6.H | retreating | 18 / 18 | +| 6.I | building | 7 / 7 | +| 6.J | civil disorder | 13 / 13 | + +They are the reason this adjudicator is worth trusting: they found that +orders were never validated, that the paradox rule did not terminate, that +civil disorder was measuring the wrong distance, and that five of my own +hand-written cases rested on a support Paris cannot give. Three cases are excluded rather than failed. 6.A.6, 6.B.10 and 6.B.11 state their setup in prose -- "Germany has a fleet in London" -- and the fixture is diff --git a/src/game/datc-phases.test.ts b/src/game/datc-phases.test.ts new file mode 100644 index 0000000..9ea45a6 --- /dev/null +++ b/src/game/datc-phases.test.ts @@ -0,0 +1,174 @@ +import { describe, expect, it } from 'vitest' +import cases from './datc.json' +import { adjudicate } from './adjudicate' +import { boardFrom, type Order, type Unit } from './orders' +import type { Power } from './map' +import { + applyAdjustments, + civilDisorderDisbands, + applyMoves, + applyRetreats, + type AdjustOrder, + type Ownership, + type RetreatOrder, +} from './turn' + +/** + * The published cases for the two phases that are not movement. + * + * They are shaped differently and so they need their own harness. A retreat + * case plays a movement phase first and then a RETREATS section; a winter + * case states the position with "Germany owns SC Kiel" lines instead of + * ordering it into existence, then gives builds or disbands. + */ + +interface Case { + id: string + title: string + units: Unit[] + orders: Order[] + expect: Record + retreats: Order[] + expectRetreat: Record + adjust: { + type: 'build' | 'disband' | 'auto' + at: string + unit: 'army' | 'fleet' + power: Power + }[] + expectAdjust: Record + own: Record +} + +const all = cases as unknown as Case[] +const province = (id: string) => id.split('/')[0]! + +// ------------------------------------------------------------------ 6.H + +describe('DATC retreats', () => { + const retreating = all.filter((c) => c.retreats.length > 0) + + it.each(retreating.map((c) => [c.id, c.title, c] as const))('%s %s', (_id, _t, c) => { + const board = boardFrom(c.units) + const outcome = adjudicate(board, c.orders) + const after = applyMoves(board, c.orders, outcome) + + for (const [at, marks] of Object.entries(c.expect)) { + if (marks.includes('dislodged')) { + expect(outcome.dislodged.has(at), `${at} dislodged`).toBe(true) + } + if (marks.includes('stands')) { + expect(outcome.dislodged.has(at), `${at} stands`).toBe(false) + } + } + + /* + * There are no supports in the retreat phase, and nothing else either -- + * a beaten unit goes somewhere it may go, or it is gone. Anything that is + * not a move is read here as a disband, which is what the rules do with + * it. + */ + const orders: RetreatOrder[] = c.retreats.map((o) => + o.type === 'move' ? { type: 'retreat', at: o.at, to: o.to } : { type: 'disband', at: o.at }, + ) + const { board: final } = applyRetreats(after, outcome, orders) + + for (const [at, marks] of Object.entries(c.expectRetreat)) { + const order = c.retreats.find((o) => province(o.at) === at) + const to = order && order.type === 'move' ? province(order.to) : null + const beaten = outcome.dislodged.get(at)?.unit + + /* + * Did the beaten unit end up where it was aimed? Both halves have to + * exist to say yes. A unit that was never dislodged has no retreat to + * make, and an empty destination holds nobody -- and comparing those + * two nothings to each other says "arrived", which is how this harness + * first read a unit ordered to move during the retreat phase as having + * moved. + */ + const arrived = + beaten !== undefined && + to !== null && + final.get(to)?.power === beaten.power && + final.get(to)?.type === beaten.type + + if (marks.includes('succeeds')) { + expect(arrived, `${at} retreat succeeds`).toBe(true) + } + if (marks.includes('fails') || marks.includes('illegal')) { + expect(arrived, `${at} retreat ${marks.join(',')}`).toBe(false) + } + } + }) +}) + +// ------------------------------------------------------------- 6.I, 6.J + +/** + * Civil disorder: what goes when a power has stopped answering. + * + * The rule is furthest from the supply centres that power *owns* -- not the + * ones it started with. A Russia driven out of Moscow and holding Sweden + * measures from Sweden, and these cases are what say so; the first version + * of this measured from home centres and was quietly wrong for every power + * that had lost one. + */ +describe('DATC civil disorder', () => { + const auto = all.filter((c) => c.adjust.some((a) => a.type === 'auto')) + + it.each(auto.map((c) => [c.id, c.title, c] as const))('%s %s', (_id, _t, c) => { + const own: Ownership = new Map(Object.entries(c.own) as [string, Power][]) + const power = c.adjust[0]!.power + const wanted = c.adjust.filter((a) => a.type === 'auto') + + /* + * Anything the power actually said comes first. Civil disorder is only + * for the shortfall -- a power that disbanded one of the two it owed has + * not stopped answering, it has half answered, and the automatic rule + * picks from what is left rather than from what it started with. + */ + const said: AdjustOrder[] = c.adjust + .filter((a) => a.type !== 'auto') + .map((a) => + a.type === 'build' + ? { type: 'build', at: a.at, unit: a.unit } + : { type: 'disband', at: a.at, unit: a.unit }, + ) + const { board } = applyAdjustments(own, boardFrom(c.units), power, said) + + const removed = civilDisorderDisbands(own, board, power, wanted.length) + expect(removed.map((u) => province(u.at)).sort()).toEqual( + wanted.map((a) => province(a.at)).sort(), + ) + }) +}) + +describe('DATC builds and disbands', () => { + const winter = all.filter((c) => c.adjust.some((a) => a.type !== 'auto')) + + it.each(winter.map((c) => [c.id, c.title, c] as const))('%s %s', (_id, _t, c) => { + const own: Ownership = new Map(Object.entries(c.own) as [string, Power][]) + const board = boardFrom(c.units) + const power = c.adjust[0]!.power + + const orders: AdjustOrder[] = c.adjust + .filter((o) => o.type !== 'auto') + .map((o) => + o.type === 'build' + ? { type: 'build', at: o.at, unit: o.unit } + : { type: 'disband', at: o.at, unit: o.unit }, + ) + const { board: after, illegal } = applyAdjustments(own, board, power, orders) + + for (const [at, marks] of Object.entries(c.expectAdjust)) { + const order = c.adjust.find((o) => province(o.at) === at)! + if (marks.includes('illegal')) { + expect(illegal.has(at), `${at} illegal`).toBe(true) + } + if (marks.includes('succeeds')) { + expect(illegal.has(at), `${at} succeeds`).toBe(false) + expect(after.has(at), `${at} ${order.type}`).toBe(order.type === 'build') + } + } + }) +}) diff --git a/src/game/datc.json b/src/game/datc.json index e328893..fa9115d 100644 --- a/src/game/datc.json +++ b/src/game/datc.json @@ -22,7 +22,12 @@ "nth": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.2", @@ -47,7 +52,12 @@ "lvp": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.3", @@ -72,7 +82,12 @@ "kie": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.4", @@ -97,7 +112,12 @@ "kie": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.5", @@ -183,12 +203,22 @@ "wal": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.6", "title": "ordering a unit of another country", "units": [ + { + "power": "england", + "type": "fleet", + "at": "lon" + }, { "power": "germany", "type": "fleet", @@ -208,7 +238,12 @@ "lon": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.7", @@ -248,7 +283,12 @@ "nth": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.8", @@ -304,7 +344,12 @@ "illegal", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.9", @@ -329,7 +374,12 @@ "rom": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.10", @@ -382,7 +432,12 @@ "apu": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.11", @@ -422,7 +477,12 @@ "ven": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.A.12", @@ -477,7 +537,12 @@ "ven": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.1", @@ -502,7 +567,12 @@ "por": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.2", @@ -527,7 +597,12 @@ "gas": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.3", @@ -552,7 +627,12 @@ "gas": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.4", @@ -607,7 +687,12 @@ "wes": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.5", @@ -660,7 +745,12 @@ "lyo": [ "stands" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.6", @@ -743,7 +833,12 @@ "lyo": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.7", @@ -813,7 +908,12 @@ "wes": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.8", @@ -883,7 +983,12 @@ "wes": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.9", @@ -953,12 +1058,22 @@ "wes": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.10", "title": "unit ordered with wrong coast", "units": [ + { + "power": "france", + "type": "fleet", + "at": "spa/sc" + }, { "power": "france", "type": "fleet", @@ -978,12 +1093,22 @@ "spa": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.11", "title": "coast cannot be ordered to change", "units": [ + { + "power": "france", + "type": "fleet", + "at": "spa/nc" + }, { "power": "france", "type": "fleet", @@ -1003,7 +1128,12 @@ "spa": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.12", @@ -1028,7 +1158,12 @@ "gas": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.13", @@ -1068,6 +1203,36 @@ "con": [ "fails" ] + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} + }, + { + "id": "6.B.14", + "title": "building with unspecified coast", + "units": [], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "stp", + "unit": "fleet", + "power": "russia" + } + ], + "expectAdjust": { + "stp": [ + "fails" + ] + }, + "own": { + "stp": "russia" } }, { @@ -1138,7 +1303,12 @@ "wes": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.16", @@ -1206,7 +1376,12 @@ "por": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.17", @@ -1259,7 +1434,12 @@ "wes": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.18", @@ -1299,7 +1479,12 @@ "bla": [ "available" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.B.19", @@ -1339,7 +1524,12 @@ "lyo": [ "available" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.1", @@ -1394,7 +1584,12 @@ "smy": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.2", @@ -1464,7 +1659,12 @@ "con": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.3", @@ -1534,7 +1734,12 @@ "bul": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.4", @@ -1649,7 +1854,12 @@ "nap": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.5", @@ -1780,7 +1990,12 @@ "tun": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.6", @@ -1850,7 +2065,12 @@ "bel": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.7", @@ -1935,7 +2155,12 @@ "bur": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.8", @@ -2020,7 +2245,12 @@ "ser": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.C.9", @@ -2105,7 +2335,12 @@ "bul": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.1", @@ -2173,7 +2408,12 @@ "tyr": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.2", @@ -2256,7 +2496,12 @@ "tyr": [ "cut" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.3", @@ -2324,7 +2569,12 @@ "ion": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.4", @@ -2394,7 +2644,12 @@ "pru": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.5", @@ -2479,7 +2734,12 @@ "pru": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.6", @@ -2564,7 +2824,12 @@ "bot": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.7", @@ -2650,7 +2915,12 @@ "fin": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.8", @@ -2734,7 +3004,12 @@ "bul": [ "invalid" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.9", @@ -2802,7 +3077,12 @@ "tri": [ "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.10", @@ -2855,7 +3135,12 @@ "mun": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.11", @@ -2925,7 +3210,12 @@ "war": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.12", @@ -2978,7 +3268,12 @@ "ven": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.13", @@ -3048,7 +3343,12 @@ "apu": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.14", @@ -3131,7 +3431,12 @@ "adr": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.15", @@ -3187,7 +3492,12 @@ "fails", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.16", @@ -3255,7 +3565,12 @@ "bel": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.17", @@ -3341,7 +3656,12 @@ "arm": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.18", @@ -3442,7 +3762,12 @@ "arm": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.19", @@ -3513,7 +3838,12 @@ "fails", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.20", @@ -3581,7 +3911,12 @@ "eng": [ "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.21", @@ -3680,7 +4015,12 @@ "ber": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.22", @@ -3751,7 +4091,12 @@ "ber": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.23", @@ -3822,7 +4167,12 @@ "mar": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.24", @@ -3905,7 +4255,12 @@ "wes": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.25", @@ -3975,7 +4330,12 @@ "pru": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.26", @@ -4045,7 +4405,12 @@ "pru": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.27", @@ -4128,7 +4493,12 @@ "pru": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.28", @@ -4198,7 +4568,12 @@ "bul": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.29", @@ -4268,7 +4643,12 @@ "bul": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.30", @@ -4338,7 +4718,12 @@ "bul": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.31", @@ -4378,7 +4763,12 @@ "bla": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.32", @@ -4448,7 +4838,12 @@ "yor": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.33", @@ -4518,7 +4913,12 @@ "bul": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.34", @@ -4619,7 +5019,12 @@ "lvn": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.D.35", @@ -4720,7 +5125,12 @@ "smy": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.1", @@ -4791,7 +5201,12 @@ "fails", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.2", @@ -4846,7 +5261,12 @@ "mun": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.3", @@ -4901,7 +5321,12 @@ "kie": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.4", @@ -5061,7 +5486,12 @@ "ruh": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.5", @@ -5237,7 +5667,12 @@ "ruh": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.6", @@ -5352,7 +5787,12 @@ "ruh": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.7", @@ -5450,7 +5890,12 @@ "nwy": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.8", @@ -5550,7 +5995,12 @@ "nwy": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.9", @@ -5650,7 +6100,12 @@ "nwy": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.10", @@ -5765,7 +6220,12 @@ "nwy": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.11", @@ -5880,7 +6340,12 @@ "wes": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.12", @@ -5965,7 +6430,12 @@ "rum": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.13", @@ -6078,7 +6548,12 @@ "nwy": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.14", @@ -6118,7 +6593,12 @@ "edi": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.E.15", @@ -6278,7 +6758,12 @@ "pru": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.1", @@ -6348,7 +6833,12 @@ "bla": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.2", @@ -6403,7 +6893,12 @@ "par": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.3", @@ -6473,7 +6968,12 @@ "par": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.4", @@ -6528,7 +7028,12 @@ "ska": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.5", @@ -6628,7 +7133,12 @@ "den": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.6", @@ -6759,7 +7269,12 @@ "bur": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.7", @@ -6784,11 +7299,6 @@ "power": "germany", "type": "fleet", "at": "ska" - }, - { - "power": "england", - "type": "fleet", - "at": "nth" } ], "orders": [ @@ -6819,18 +7329,12 @@ "to": "nth", "viaConvoy": false, "power": "germany" - }, - { - "type": "move", - "at": "nth", - "to": "hol", - "viaConvoy": false, - "power": "england" } ], "expect": { "nth": [ - "succeeds" + "disrupted", + "dislodged" ], "lon": [ "fails" @@ -6841,7 +7345,24 @@ "ska": [ "succeeds" ] - } + }, + "retreats": [ + { + "type": "move", + "at": "nth", + "to": "hol", + "viaConvoy": false, + "power": "england" + } + ], + "expectRetreat": { + "nth": [ + "succeeds" + ] + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.8", @@ -6927,7 +7448,12 @@ "bel": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.9", @@ -7013,7 +7539,12 @@ "mao": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.10", @@ -7099,7 +7630,12 @@ "mao": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.11", @@ -7185,7 +7721,12 @@ "mao": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.12", @@ -7271,7 +7812,12 @@ "mao": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.13", @@ -7357,7 +7903,12 @@ "den": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.14", @@ -7428,7 +7979,12 @@ "disrupted", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.15", @@ -7544,7 +8100,12 @@ "naf": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.16", @@ -7644,7 +8205,12 @@ "bel": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.17", @@ -7759,7 +8325,12 @@ "bel": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.18", @@ -7859,7 +8430,12 @@ "ska": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.19", @@ -7944,7 +8520,12 @@ "rom": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.20", @@ -8045,7 +8626,12 @@ "eas": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.21", @@ -8177,7 +8763,12 @@ "cut", "destroyed" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.22", @@ -8309,7 +8900,12 @@ "disrupted", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.23", @@ -8469,7 +9065,12 @@ "nth": [ "disrupted" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.24", @@ -8615,7 +9216,12 @@ "disrupted", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.25", @@ -8805,7 +9411,12 @@ "swe": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.26", @@ -8967,7 +9578,12 @@ "iri": [ "available" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.27", @@ -9128,7 +9744,12 @@ "gre": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.28", @@ -9534,7 +10155,12 @@ "bre": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.29", @@ -9949,7 +10575,12 @@ "bre": [ "cut" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.F.30", @@ -10035,7 +10666,12 @@ "bre": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.1", @@ -10090,7 +10726,12 @@ "swe": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.2", @@ -10145,7 +10786,12 @@ "ska": [ "available" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.3", @@ -10231,7 +10877,12 @@ "disrupted", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.4", @@ -10333,7 +10984,12 @@ "fails", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.5", @@ -10403,7 +11059,12 @@ "ion": [ "available" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.6", @@ -10514,7 +11175,12 @@ "nao": [ "available" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.7", @@ -10584,7 +11250,12 @@ "bot": [ "illegal" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.8", @@ -10639,7 +11310,12 @@ "hol": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.9", @@ -10709,7 +11385,12 @@ "swe": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.10", @@ -10840,7 +11521,12 @@ "nth": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.11", @@ -10926,7 +11612,12 @@ "bar": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.12", @@ -11041,7 +11732,12 @@ "iri": [ "available" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.13", @@ -11112,7 +11808,12 @@ "alb": [ "succeeds" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.14", @@ -11243,7 +11944,12 @@ "bar": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.15", @@ -11344,7 +12050,12 @@ "fails", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.16", @@ -11459,7 +12170,12 @@ "nwg": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.17", @@ -11574,7 +12290,12 @@ "nwg": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.18", @@ -11704,7 +12425,12 @@ "wal": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.19", @@ -11774,7 +12500,12 @@ "spa": [ "fails" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.G.20", @@ -11860,7 +12591,12 @@ "disrupted", "dislodged" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.1", @@ -11900,21 +12636,6 @@ "power": "italy", "type": "fleet", "at": "aeg" - }, - { - "power": "austria", - "type": "fleet", - "at": "tri" - }, - { - "power": "austria", - "type": "army", - "at": "ser" - }, - { - "power": "turkey", - "type": "fleet", - "at": "gre" } ], "orders": [ @@ -11960,7 +12681,32 @@ "from": "ion", "to": "gre", "power": "italy" - }, + } + ], + "expect": { + "tri": [ + "dislodged" + ], + "ser": [ + "stands" + ], + "gre": [ + "dislodged" + ], + "ven": [ + "given" + ], + "tyr": [ + "succeeds" + ], + "ion": [ + "succeeds" + ], + "aeg": [ + "given" + ] + }, + "retreats": [ { "type": "move", "at": "tri", @@ -11983,7 +12729,7 @@ "power": "turkey" } ], - "expect": { + "expectRetreat": { "tri": [ "fails" ], @@ -11992,20 +12738,11 @@ ], "gre": [ "fails" - ], - "ven": [ - "given" - ], - "tyr": [ - "succeeds" - ], - "ion": [ - "succeeds" - ], - "aeg": [ - "given" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.2", @@ -12051,21 +12788,6 @@ "type": "army", "at": "fin" }, - { - "power": "russia", - "type": "fleet", - "at": "hol" - }, - { - "power": "england", - "type": "fleet", - "at": "nwy" - }, - { - "power": "russia", - "type": "fleet", - "at": "edi" - }, { "power": "russia", "type": "fleet", @@ -12129,7 +12851,38 @@ "type": "hold", "at": "hol", "power": "russia" - }, + } + ], + "expect": { + "lvp": [ + "succeeds" + ], + "yor": [ + "given" + ], + "nwy": [ + "dislodged" + ], + "kie": [ + "given" + ], + "ruh": [ + "succeeds" + ], + "edi": [ + "dislodged" + ], + "swe": [ + "given" + ], + "fin": [ + "succeeds" + ], + "hol": [ + "dislodged" + ] + }, + "retreats": [ { "type": "move", "at": "nwy", @@ -12152,35 +12905,20 @@ "power": "russia" } ], - "expect": { - "lvp": [ - "succeeds" - ], - "yor": [ - "given" - ], + "expectRetreat": { "nwy": [ "fails" ], - "kie": [ - "given" - ], - "ruh": [ - "succeeds" - ], "edi": [ "fails" ], - "swe": [ - "given" - ], - "fin": [ - "succeeds" - ], "hol": [ "illegal" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.3", @@ -12205,16 +12943,6 @@ "power": "germany", "type": "army", "at": "ruh" - }, - { - "power": "england", - "type": "army", - "at": "hol" - }, - { - "power": "england", - "type": "fleet", - "at": "nth" } ], "orders": [ @@ -12241,7 +12969,23 @@ "to": "hol", "viaConvoy": false, "power": "germany" - }, + } + ], + "expect": { + "nth": [ + "stands" + ], + "hol": [ + "dislodged" + ], + "kie": [ + "given" + ], + "ruh": [ + "succeeds" + ] + }, + "retreats": [ { "type": "move", "at": "hol", @@ -12257,20 +13001,17 @@ "power": "england" } ], - "expect": { - "nth": [ - "illegal" - ], + "expectRetreat": { "hol": [ "illegal" ], - "kie": [ - "given" - ], - "ruh": [ - "succeeds" + "nth": [ + "illegal" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.4", @@ -12295,16 +13036,6 @@ "power": "germany", "type": "army", "at": "ruh" - }, - { - "power": "england", - "type": "army", - "at": "hol" - }, - { - "power": "england", - "type": "fleet", - "at": "nth" } ], "orders": [ @@ -12331,7 +13062,23 @@ "to": "hol", "viaConvoy": false, "power": "germany" - }, + } + ], + "expect": { + "nth": [ + "stands" + ], + "hol": [ + "dislodged" + ], + "kie": [ + "given" + ], + "ruh": [ + "succeeds" + ] + }, + "retreats": [ { "type": "move", "at": "hol", @@ -12347,20 +13094,17 @@ "power": "england" } ], - "expect": { - "nth": [ - "illegal" - ], + "expectRetreat": { "hol": [ "succeeds" ], - "kie": [ - "given" - ], - "ruh": [ - "succeeds" + "nth": [ + "illegal" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.5", @@ -12376,11 +13120,6 @@ "type": "fleet", "at": "bla" }, - { - "power": "turkey", - "type": "fleet", - "at": "ank" - }, { "power": "turkey", "type": "fleet", @@ -12406,13 +13145,6 @@ "type": "hold", "at": "ank", "power": "turkey" - }, - { - "type": "move", - "at": "ank", - "to": "bla", - "viaConvoy": false, - "power": "turkey" } ], "expect": { @@ -12422,10 +13154,27 @@ "bla": [ "succeeds" ], + "ank": [ + "dislodged" + ] + }, + "retreats": [ + { + "type": "move", + "at": "ank", + "to": "bla", + "viaConvoy": false, + "power": "turkey" + } + ], + "expectRetreat": { "ank": [ "illegal" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.6", @@ -12451,11 +13200,6 @@ "type": "army", "at": "sil" }, - { - "power": "italy", - "type": "army", - "at": "vie" - }, { "power": "italy", "type": "army", @@ -12495,13 +13239,6 @@ "type": "hold", "at": "vie", "power": "italy" - }, - { - "type": "move", - "at": "vie", - "to": "boh", - "viaConvoy": false, - "power": "italy" } ], "expect": { @@ -12517,10 +13254,27 @@ "sil": [ "fails" ], + "vie": [ + "dislodged" + ] + }, + "retreats": [ + { + "type": "move", + "at": "vie", + "to": "boh", + "viaConvoy": false, + "power": "italy" + } + ], + "expectRetreat": { "vie": [ "illegal" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.7", @@ -12555,16 +13309,6 @@ "power": "italy", "type": "army", "at": "boh" - }, - { - "power": "italy", - "type": "army", - "at": "boh" - }, - { - "power": "italy", - "type": "army", - "at": "vie" } ], "orders": [ @@ -12605,20 +13349,6 @@ "type": "hold", "at": "boh", "power": "italy" - }, - { - "type": "move", - "at": "boh", - "to": "tyr", - "viaConvoy": false, - "power": "italy" - }, - { - "type": "move", - "at": "vie", - "to": "tyr", - "viaConvoy": false, - "power": "italy" } ], "expect": { @@ -12635,12 +13365,39 @@ "succeeds" ], "vie": [ - "fails" + "dislodged" ], "boh": [ + "dislodged" + ] + }, + "retreats": [ + { + "type": "move", + "at": "boh", + "to": "tyr", + "viaConvoy": false, + "power": "italy" + }, + { + "type": "move", + "at": "vie", + "to": "tyr", + "viaConvoy": false, + "power": "italy" + } + ], + "expectRetreat": { + "boh": [ + "fails" + ], + "vie": [ "fails" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.8", @@ -12686,21 +13443,6 @@ "type": "army", "at": "fin" }, - { - "power": "russia", - "type": "fleet", - "at": "hol" - }, - { - "power": "england", - "type": "fleet", - "at": "nwy" - }, - { - "power": "russia", - "type": "fleet", - "at": "edi" - }, { "power": "russia", "type": "fleet", @@ -12764,7 +13506,38 @@ "type": "hold", "at": "hol", "power": "russia" - }, + } + ], + "expect": { + "lvp": [ + "succeeds" + ], + "yor": [ + "given" + ], + "nwy": [ + "dislodged" + ], + "kie": [ + "given" + ], + "ruh": [ + "succeeds" + ], + "edi": [ + "dislodged" + ], + "swe": [ + "given" + ], + "fin": [ + "succeeds" + ], + "hol": [ + "dislodged" + ] + }, + "retreats": [ { "type": "move", "at": "nwy", @@ -12787,35 +13560,20 @@ "power": "russia" } ], - "expect": { - "lvp": [ - "succeeds" - ], - "yor": [ - "given" - ], + "expectRetreat": { "nwy": [ "fails" ], - "kie": [ - "given" - ], - "ruh": [ - "succeeds" - ], "edi": [ "fails" ], - "swe": [ - "given" - ], - "fin": [ - "succeeds" - ], "hol": [ "fails" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.9", @@ -12850,11 +13608,6 @@ "power": "russia", "type": "army", "at": "pru" - }, - { - "power": "germany", - "type": "fleet", - "at": "kie" } ], "orders": [ @@ -12897,13 +13650,6 @@ "to": "ber", "viaConvoy": false, "power": "russia" - }, - { - "type": "move", - "at": "kie", - "to": "ber", - "viaConvoy": false, - "power": "germany" } ], "expect": { @@ -12917,7 +13663,7 @@ "succeeds" ], "kie": [ - "succeeds" + "dislodged" ], "sil": [ "given" @@ -12926,7 +13672,35 @@ "fails", "dislodged" ] - } + }, + "retreats": [ + { + "type": "move", + "at": "kie", + "to": "ber", + "viaConvoy": false, + "power": "germany" + } + ], + "expectRetreat": { + "kie": [ + "succeeds" + ] + }, + "adjust": [ + { + "type": "disband", + "at": "pru", + "unit": "army", + "power": "russia" + } + ], + "expectAdjust": { + "pru": [ + "succeeds" + ] + }, + "own": {} }, { "id": "6.H.10", @@ -12961,16 +13735,6 @@ "power": "russia", "type": "army", "at": "sil" - }, - { - "power": "england", - "type": "army", - "at": "kie" - }, - { - "power": "germany", - "type": "army", - "at": "pru" } ], "orders": [ @@ -13011,7 +13775,29 @@ "from": "war", "to": "pru", "power": "russia" - }, + } + ], + "expect": { + "kie": [ + "dislodged" + ], + "ber": [ + "succeeds" + ], + "mun": [ + "given" + ], + "pru": [ + "dislodged" + ], + "war": [ + "succeeds" + ], + "sil": [ + "given" + ] + }, + "retreats": [ { "type": "move", "at": "kie", @@ -13027,26 +13813,17 @@ "power": "germany" } ], - "expect": { + "expectRetreat": { "kie": [ "illegal" ], - "ber": [ - "succeeds" - ], - "mun": [ - "given" - ], "pru": [ "succeeds" - ], - "war": [ - "succeeds" - ], - "sil": [ - "given" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.11", @@ -13077,11 +13854,6 @@ "type": "fleet", "at": "lyo" }, - { - "power": "italy", - "type": "army", - "at": "mar" - }, { "power": "italy", "type": "army", @@ -13128,13 +13900,6 @@ "type": "hold", "at": "mar", "power": "italy" - }, - { - "type": "move", - "at": "mar", - "to": "gas", - "viaConvoy": false, - "power": "italy" } ], "expect": { @@ -13153,10 +13918,27 @@ "lyo": [ "available" ], + "mar": [ + "dislodged" + ] + }, + "retreats": [ + { + "type": "move", + "at": "mar", + "to": "gas", + "viaConvoy": false, + "power": "italy" + } + ], + "expectRetreat": { "mar": [ "succeeds" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.12", @@ -13211,11 +13993,6 @@ "power": "russia", "type": "army", "at": "cly" - }, - { - "power": "england", - "type": "army", - "at": "lvp" } ], "orders": [ @@ -13288,18 +14065,12 @@ "from": "edi", "to": "lvp", "power": "russia" - }, - { - "type": "move", - "at": "lvp", - "to": "edi", - "viaConvoy": false, - "power": "england" } ], "expect": { "lvp": [ - "succeeds" + "fails", + "dislodged" ], "iri": [ "available" @@ -13329,7 +14100,35 @@ "cly": [ "given" ] - } + }, + "retreats": [ + { + "type": "move", + "at": "lvp", + "to": "edi", + "viaConvoy": false, + "power": "england" + } + ], + "expectRetreat": { + "lvp": [ + "succeeds" + ] + }, + "adjust": [ + { + "type": "disband", + "at": "eng", + "unit": "fleet", + "power": "england" + } + ], + "expectAdjust": { + "eng": [ + "succeeds" + ] + }, + "own": {} }, { "id": "6.H.13", @@ -13354,11 +14153,6 @@ "power": "france", "type": "army", "at": "bre" - }, - { - "power": "england", - "type": "army", - "at": "pic" } ], "orders": [ @@ -13387,18 +14181,11 @@ "from": "par", "to": "pic", "power": "france" - }, - { - "type": "move", - "at": "pic", - "to": "lon", - "viaConvoy": false, - "power": "england" } ], "expect": { "pic": [ - "illegal" + "dislodged" ], "eng": [ "invalid" @@ -13409,7 +14196,24 @@ "bre": [ "given" ] - } + }, + "retreats": [ + { + "type": "move", + "at": "pic", + "to": "lon", + "viaConvoy": false, + "power": "england" + } + ], + "expectRetreat": { + "pic": [ + "illegal" + ] + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.14", @@ -13449,16 +14253,6 @@ "power": "germany", "type": "army", "at": "mar" - }, - { - "power": "england", - "type": "army", - "at": "pic" - }, - { - "power": "france", - "type": "army", - "at": "bur" } ], "orders": [ @@ -13506,7 +14300,32 @@ "to": "bur", "viaConvoy": false, "power": "germany" - }, + } + ], + "expect": { + "pic": [ + "dislodged" + ], + "eng": [ + "invalid" + ], + "par": [ + "succeeds" + ], + "bre": [ + "given" + ], + "bur": [ + "dislodged" + ], + "mun": [ + "given" + ], + "mar": [ + "succeeds" + ] + }, + "retreats": [ { "type": "move", "at": "pic", @@ -13522,29 +14341,17 @@ "power": "france" } ], - "expect": { + "expectRetreat": { "pic": [ "fails" ], - "eng": [ - "invalid" - ], - "par": [ - "succeeds" - ], - "bre": [ - "given" - ], "bur": [ "fails" - ], - "mun": [ - "given" - ], - "mar": [ - "succeeds" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.15", @@ -13564,11 +14371,6 @@ "power": "russia", "type": "fleet", "at": "bla" - }, - { - "power": "russia", - "type": "fleet", - "at": "con" } ], "orders": [ @@ -13590,7 +14392,20 @@ "from": "bul/ec", "to": "con", "power": "russia" - }, + } + ], + "expect": { + "con": [ + "dislodged" + ], + "bul": [ + "succeeds" + ], + "bla": [ + "given" + ] + }, + "retreats": [ { "type": "move", "at": "con", @@ -13599,17 +14414,14 @@ "power": "russia" } ], - "expect": { + "expectRetreat": { "con": [ "illegal" - ], - "bul": [ - "succeeds" - ], - "bla": [ - "given" ] - } + }, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.16", @@ -13662,7 +14474,12 @@ "mao": [ "given" ] - } + }, + "retreats": [], + "expectRetreat": {}, + "adjust": [], + "expectAdjust": {}, + "own": {} }, { "id": "6.H.17", @@ -13692,11 +14509,6 @@ "power": "italy", "type": "fleet", "at": "tys" - }, - { - "power": "france", - "type": "fleet", - "at": "wes" } ], "orders": [ @@ -13732,13 +14544,6 @@ "to": "wes", "viaConvoy": false, "power": "italy" - }, - { - "type": "move", - "at": "wes", - "to": "spa/sc", - "viaConvoy": false, - "power": "france" } ], "expect": { @@ -13749,7 +14554,7 @@ "fails" ], "wes": [ - "illegal" + "dislodged" ], "tun": [ "given" @@ -13757,6 +14562,709 @@ "tys": [ "succeeds" ] + }, + "retreats": [ + { + "type": "move", + "at": "wes", + "to": "spa/sc", + "viaConvoy": false, + "power": "france" + } + ], + "expectRetreat": { + "wes": [ + "illegal" + ] + }, + "adjust": [], + "expectAdjust": {}, + "own": {} + }, + { + "id": "6.I.1", + "title": "too many build orders", + "units": [ + { + "power": "germany", + "type": "army", + "at": "pru" + }, + { + "power": "germany", + "type": "army", + "at": "sil" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "war", + "unit": "army", + "power": "germany" + }, + { + "type": "build", + "at": "kie", + "unit": "army", + "power": "germany" + }, + { + "type": "build", + "at": "mun", + "unit": "army", + "power": "germany" + } + ], + "expectAdjust": { + "war": [ + "illegal" + ], + "kie": [ + "succeeds" + ], + "mun": [ + "illegal" + ] + }, + "own": { + "kie": "germany", + "mun": "germany", + "war": "germany" + } + }, + { + "id": "6.I.2", + "title": "fleets cannot be built on inland provinces", + "units": [], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "mos", + "unit": "fleet", + "power": "russia" + } + ], + "expectAdjust": { + "mos": [ + "illegal" + ] + }, + "own": { + "mos": "russia" + } + }, + { + "id": "6.I.3", + "title": "supply center must be empty for building", + "units": [ + { + "power": "germany", + "type": "army", + "at": "ber" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "ber", + "unit": "army", + "power": "germany" + } + ], + "expectAdjust": { + "ber": [ + "illegal" + ] + }, + "own": { + "ber": "germany", + "war": "germany" + } + }, + { + "id": "6.I.4", + "title": "both coasts must be empty for building", + "units": [ + { + "power": "russia", + "type": "fleet", + "at": "stp/sc" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "stp/nc", + "unit": "fleet", + "power": "russia" + } + ], + "expectAdjust": { + "stp": [ + "illegal" + ] + }, + "own": { + "mos": "russia", + "stp": "russia" + } + }, + { + "id": "6.I.5", + "title": "building in home supply center that is not owned", + "units": [ + { + "power": "germany", + "type": "fleet", + "at": "bal" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "ber", + "unit": "army", + "power": "germany" + } + ], + "expectAdjust": { + "ber": [ + "illegal" + ] + }, + "own": { + "hol": "germany", + "war": "germany" + } + }, + { + "id": "6.I.6", + "title": "building in owned supply center that is not a home supply center", + "units": [ + { + "power": "germany", + "type": "army", + "at": "hol" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "war", + "unit": "army", + "power": "germany" + } + ], + "expectAdjust": { + "war": [ + "illegal" + ] + }, + "own": { + "war": "germany", + "hol": "germany" + } + }, + { + "id": "6.I.7", + "title": "only one build in a home supply center", + "units": [], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "build", + "at": "mos", + "unit": "army", + "power": "russia" + }, + { + "type": "build", + "at": "mos", + "unit": "army", + "power": "russia" + } + ], + "expectAdjust": { + "mos": [ + "illegal" + ] + }, + "own": { + "mos": "russia", + "bud": "russia" + } + }, + { + "id": "6.J.1", + "title": "too many disband orders", + "units": [ + { + "power": "france", + "type": "army", + "at": "par" + }, + { + "power": "france", + "type": "army", + "at": "pic" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "disband", + "at": "lyo", + "unit": "fleet", + "power": "france" + }, + { + "type": "disband", + "at": "pic", + "unit": "army", + "power": "france" + }, + { + "type": "disband", + "at": "par", + "unit": "army", + "power": "france" + } + ], + "expectAdjust": { + "lyo": [ + "illegal" + ], + "pic": [ + "succeeds" + ], + "par": [ + "illegal" + ] + }, + "own": { + "par": "france" + } + }, + { + "id": "6.J.2", + "title": "removing the same unit twice", + "units": [ + { + "power": "france", + "type": "army", + "at": "par" + }, + { + "power": "france", + "type": "fleet", + "at": "nth" + }, + { + "power": "france", + "type": "fleet", + "at": "eng" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "disband", + "at": "nth", + "unit": "fleet", + "power": "france" + }, + { + "type": "disband", + "at": "nth", + "unit": "fleet", + "power": "france" + }, + { + "type": "auto", + "at": "eng", + "unit": "fleet", + "power": "france" + } + ], + "expectAdjust": { + "nth": [ + "illegal" + ] + }, + "own": { + "par": "france" + } + }, + { + "id": "6.J.3", + "title": "civil disorder two armies with different distance", + "units": [ + { + "power": "russia", + "type": "army", + "at": "lvn" + }, + { + "power": "russia", + "type": "army", + "at": "pru" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "pru", + "unit": "army", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "stp": "russia" + } + }, + { + "id": "6.J.4", + "title": "civil disorder two armies with equal distance", + "units": [ + { + "power": "russia", + "type": "army", + "at": "lvn" + }, + { + "power": "russia", + "type": "army", + "at": "ukr" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "lvn", + "unit": "army", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "mos": "russia" + } + }, + { + "id": "6.J.5", + "title": "civil disorder two fleets with different distance", + "units": [ + { + "power": "russia", + "type": "fleet", + "at": "ska" + }, + { + "power": "russia", + "type": "fleet", + "at": "nao" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "nao", + "unit": "fleet", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "stp": "russia" + } + }, + { + "id": "6.J.6", + "title": "civil disorder two fleets with equal distance", + "units": [ + { + "power": "russia", + "type": "fleet", + "at": "bot" + }, + { + "power": "russia", + "type": "fleet", + "at": "nth" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "bot", + "unit": "fleet", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "mun": "russia" + } + }, + { + "id": "6.J.7", + "title": "civil disorder two fleets and army with equal distance", + "units": [ + { + "power": "russia", + "type": "army", + "at": "boh" + }, + { + "power": "russia", + "type": "fleet", + "at": "ska" + }, + { + "power": "russia", + "type": "fleet", + "at": "nth" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "nth", + "unit": "fleet", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "stp": "russia", + "war": "russia" + } + }, + { + "id": "6.J.8", + "title": "civil disorder a fleet with shorter distance than the army", + "units": [ + { + "power": "russia", + "type": "army", + "at": "tyr" + }, + { + "power": "russia", + "type": "fleet", + "at": "bal" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "tyr", + "unit": "army", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "war": "russia" + } + }, + { + "id": "6.J.9", + "title": "civil disorder must be counted from both coasts, south coast shortest", + "units": [ + { + "power": "russia", + "type": "army", + "at": "alb" + }, + { + "power": "russia", + "type": "army", + "at": "sev" + }, + { + "power": "russia", + "type": "fleet", + "at": "bal" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "alb", + "unit": "army", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "stp": "russia", + "sev": "russia" + } + }, + { + "id": "6.J.10", + "title": "civil disorder must be counted from both coasts, north coast shortest", + "units": [ + { + "power": "russia", + "type": "army", + "at": "alb" + }, + { + "power": "russia", + "type": "army", + "at": "sev" + }, + { + "power": "russia", + "type": "fleet", + "at": "ska" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "alb", + "unit": "army", + "power": "russia" + } + ], + "expectAdjust": {}, + "own": { + "stp": "russia", + "sev": "russia" + } + }, + { + "id": "6.J.11", + "title": "civil disorder counting convoying distance", + "units": [ + { + "power": "italy", + "type": "army", + "at": "alb" + }, + { + "power": "italy", + "type": "army", + "at": "pie" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "pie", + "unit": "army", + "power": "italy" + } + ], + "expectAdjust": {}, + "own": { + "nap": "italy" + } + }, + { + "id": "6.J.12", + "title": "distance to owned supply center", + "units": [ + { + "power": "italy", + "type": "army", + "at": "war" + }, + { + "power": "italy", + "type": "army", + "at": "tus" + } + ], + "orders": [], + "expect": {}, + "retreats": [], + "expectRetreat": {}, + "adjust": [ + { + "type": "auto", + "at": "tus", + "unit": "army", + "power": "italy" + } + ], + "expectAdjust": {}, + "own": { + "war": "italy" } } ] \ No newline at end of file diff --git a/src/game/turn.test.ts b/src/game/turn.test.ts index 346709d..e9aafb0 100644 --- a/src/game/turn.test.ts +++ b/src/game/turn.test.ts @@ -211,9 +211,10 @@ describe('the winter', () => { expect(buildOptions(own, boardFrom([]), 'germany').some((o) => o.at === 'ber')).toBe(false) }) - it('removes what is furthest from home when nobody says', () => { + it('removes what is furthest from the centres it owns when nobody says', () => { const board = boardFrom([A('germany', 'mun'), A('germany', 'ber'), A('germany', 'spa')]) - expect(civilDisorderDisbands(board, 'germany', 1)[0]!.at).toBe('spa') + const own = openingOwnership() + expect(civilDisorderDisbands(own, board, 'germany', 1)[0]!.at).toBe('spa') }) it('knows an eliminated power and a solo when it sees one', () => { diff --git a/src/game/turn.ts b/src/game/turn.ts index 3477157..48e823b 100644 --- a/src/game/turn.ts +++ b/src/game/turn.ts @@ -178,27 +178,49 @@ export function buildOptions(own: Ownership, board: Board, power: Power): BuildO * what matters is that it is the same arbitrary everywhere, so two * adjudicators never disagree about a game nobody was playing. */ -export function civilDisorderDisbands(board: Board, power: Power, howMany: number): Unit[] { - const homes = Object.entries(PROVINCES) - .filter(([, p]) => p.home === power) - .map(([id]) => id) +export function civilDisorderDisbands( + own: Ownership, + board: Board, + power: Power, + howMany: number, +): Unit[] { + /* + * Distance is measured to the centres this power *owns*, not to the ones + * it was born with. A Russia driven out of Moscow and holding Sweden + * measures from Sweden -- home is where the supply is, and a unit far from + * any of it is the one that is no longer doing anything. + */ + const homes = [...own.entries()].filter(([, p]) => p === power).map(([id]) => id) - const distance = (unit: Unit): number => { - const graph = unit.type === 'army' ? ARMY : FLEET - const start = unit.type === 'army' ? base(unit.at) : unit.at - const seen = new Set([start]) - let edge = [start] - for (let step = 0; edge.length > 0; step++) { - if (edge.some((id) => homes.includes(base(id)))) return step - const next: string[] = [] - for (const id of edge) { - for (const to of graph[id] ?? []) if (!seen.has(to)) (seen.add(to), next.push(to)) - } - edge = next + /* + * Distance is measured over the whole map, not over the graph the unit + * itself moves on. + * + * That looks wrong for about a minute. An army counts sea provinces it + * would need convoying across -- Albania is two steps from Naples over the + * Adriatic, not unreachable -- and a fleet counts inland provinces it could + * never enter, so the Baltic is two steps from Warsaw. Both are what the + * published cases require, and both make sense once you see what is being + * measured: not how the unit would get home, but how far out of the way it + * is. A fleet in the Baltic is still in the middle of Russia's business. A + * fleet in the North Atlantic is not. + */ + const seen = new Set() + let edge = homes.map(base) + const away = new Map() + for (let step = 0; edge.length > 0; step++) { + const next: string[] = [] + for (const id of edge) { + if (seen.has(id)) continue + seen.add(id) + away.set(id, step) + for (const to of neighbours(id)) if (!seen.has(to)) next.push(to) } - return Number.MAX_SAFE_INTEGER + edge = next } + const distance = (unit: Unit): number => away.get(base(unit.at)) ?? Number.MAX_SAFE_INTEGER + return [...board.values()] .filter((u) => u.power === power) .map((u) => ({ u, d: distance(u) })) @@ -220,3 +242,74 @@ export function soloWinner(own: Ownership): Power | null { for (const power of POWERS) if (centreCount(own, power) >= 18) return power return null } + +export type AdjustOrder = + | { type: 'build'; at: string; unit: UnitType } + | { type: 'disband'; at: string; unit?: UnitType } + +export interface AdjustResult { + board: Board + /** Orders that were refused, by province. */ + illegal: Set +} + +/** + * Builds and disbands, taken one at a time in the order they were given. + * + * One at a time matters. A power owed one unit that orders three builds has + * not made three mistakes, it has made two -- the first order is carried out + * and the rest are refused. Working out the whole set and rejecting it + * wholesale would be tidier and would punish a slip far harder than the + * rules do. + */ +export function applyAdjustments( + own: Ownership, + board: Board, + power: Power, + given: readonly AdjustOrder[], +): AdjustResult { + const next: Board = new Map(board) + const illegal = new Set() + const owed = adjustmentFor(own, next, power) + let done = 0 + + for (const order of given) { + const at = base(order.at) + + if (order.type === 'build') { + const allowed = buildOptions(own, next, power).some( + (o) => o.at === order.at && o.type === order.unit, + ) + if (!allowed || done >= owed) { + illegal.add(at) + continue + } + next.set(at, { power, type: order.unit, at: order.at }) + done++ + continue + } + + const unit = next.get(at) + const removable = + unit !== undefined && + unit.power === power && + (order.unit === undefined || unit.type === order.unit) + if (!removable || done >= -owed) { + illegal.add(at) + continue + } + next.delete(at) + done++ + } + + return { board: next, illegal } +} + +/** Everywhere touching a province, by land or by water, whoever could go. */ +function neighbours(id: string): string[] { + const out = new Set(ARMY[id] ?? []) + const coasts = PROVINCES[id]?.coasts + const keys = coasts ? coasts.map((c) => `${id}/${c}`) : [id] + for (const key of keys) for (const to of FLEET[key] ?? []) out.add(base(to)) + return [...out] +} diff --git a/tools/datc.py b/tools/datc.py index 5e6e712..e84f13f 100644 --- a/tools/datc.py +++ b/tools/datc.py @@ -70,18 +70,69 @@ def main() -> int: end = heads[idx + 1][0] if idx + 1 < len(heads) else len(lines) m = re.match(r'\s*(6\.[A-Z]\.\d+)\.\s+TEST CASE,\s*(.*)', head) case = {'id': m.group(1), 'title': m.group(2).strip().lower(), - 'units': [], 'orders': [], 'expect': {}} + 'units': [], 'orders': [], 'expect': {}, + 'retreats': [], 'expectRetreat': {}, + 'adjust': [], 'expectAdjust': {}, 'own': {}} power = None ok = True + # Cases in section H run a movement phase and then a retreat phase. + phase = 'move' for line in lines[start + 1:end]: s = line.strip() if not s: continue + if s.upper() == 'RETREATS': + phase = 'retreat' + continue + p = s.rstrip(':').strip().lower() if s.endswith(':') and p in POWERS: power = p continue + + # "Germany owns SC Kiel" / "Germany owns A Prussia": the winter + # cases state the position this way rather than by ordering it. + if m3 := re.match(r'^(\w+)\s+owns\s+(SC|A|F)\s+(.*?)\s*$', s, re.I): + who = m3.group(1).lower() + if who not in POWERS: + continue + try: + where = place(m3.group(3)) + except KeyError as e: + unknown.add(str(e)); ok = False; break + if m3.group(2).upper() == 'SC': + case['own'][where.split('/')[0]] = who + else: + kind = 'army' if m3.group(2).upper() == 'A' else 'fleet' + case['units'].append({'power': who, 'type': kind, 'at': where}) + case['own'].setdefault(where.split('/')[0], who) if False else None + continue + + # "Build A Kiel" / "Disband F Gulf of Lyon" / "Remove A Paris". + if m3 := re.match(r'^(Build|Disband|Remove|Automatic disband)\s+([AF])\s+(.*?)\s*$', s, re.I): + body, marks = m3.group(3), [] + while True: + m4 = re.search(r',?\s*(' + '|'.join(ANNOTATIONS) + r')\s*$', body, re.I) + if not m4: + break + marks.insert(0, m4.group(1).lower()) + body = body[:m4.start()].rstrip() + try: + where = place(body) + except KeyError as e: + unknown.add(str(e)); ok = False; break + kind = 'army' if m3.group(2).upper() == 'A' else 'fleet' + verb = m3.group(1).lower() + case['adjust'].append({ + 'type': 'build' if verb == 'build' + else 'auto' if verb == 'automatic disband' else 'disband', + 'at': where, 'unit': kind, 'power': power, + }) + if marks: + case['expectAdjust'][where.split('/')[0]] = marks + continue + if not re.match(r'^[AF]\s', s) or power is None: continue @@ -124,12 +175,17 @@ def main() -> int: ok = False break - case['units'].append({'power': power, 'type': unit_type, 'at': at}) - case['orders'].append({**order, 'power': power}) - if marks: - case['expect'][at.split('/')[0]] = marks + if phase == 'move': + case['units'].append({'power': power, 'type': unit_type, 'at': at}) + case['orders'].append({**order, 'power': power}) + if marks: + case['expect'][at.split('/')[0]] = marks + else: + case['retreats'].append({**order, 'power': power}) + if marks: + case['expectRetreat'][at.split('/')[0]] = marks - if ok and case['orders']: + if ok and (case['orders'] or case['adjust']): cases.append(case) if unknown: