From 495090873cf62a1ca698cee18a2d292fc30aa292 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 27 May 2023 19:28:16 +0200 Subject: Stuff. --- play.css | 43 +- play.html | 139 ----- play.js | 750 ++++++++++++++--------- rules.js | 1738 +++++++++++++++++++++++++++++++++++++++------------- tools/genmove.js | 115 ++++ tools/genpieces.js | 33 + 6 files changed, 1939 insertions(+), 879 deletions(-) create mode 100644 tools/genmove.js create mode 100644 tools/genpieces.js diff --git a/play.css b/play.css index 97928f0..49885b1 100644 --- a/play.css +++ b/play.css @@ -22,7 +22,7 @@ header.your_turn.player_green { background-color: darkseagreen; } width: 2550px; height: 1650px; box-shadow: 0px 1px 10px #0008; - margin-bottom: 36px; + margin-bottom: 100px; } #map { @@ -96,6 +96,7 @@ svg .sea.action { background-repeat: no-repeat; } +.dice.d0 { display: none; } .dice.d1 { background-position: 0% 0; } .dice.d2 { background-position: 20% 0; } .dice.d3 { background-position: 40% 0; } @@ -171,7 +172,7 @@ body.p2 #Galatia_Governor { display: none } .goths { background-color: #3a9cd6; } .nomads { background-color: #f99d1c; } .sassanids { background-color: #8e5ca6; } -.rival { background-color: #b8b996; } +.rival_emperor { background-color: #b8b996; } .militia, .legion { background-color: #ffffff; } .neutral { background-color: #e3dedc; } .no_place_governor { background-color: #6e6e6e; } @@ -187,12 +188,17 @@ body.p2 #Galatia_Governor { display: none } .goths { border-color: #68c6ff #0073ab #0073ab #68c6ff; box-shadow: 0 0 0 1px #002759, 1px 2px 4px #0008; } .nomads { border-color: #ffcf60 #c56c00 #c56c00 #ffcf60; box-shadow: 0 0 0 1px #610700, 1px 2px 4px #0008; } .sassanids { border-color: #b17dca #6d3c83 #6d3c83 #b17dca; box-shadow: 0 0 0 1px #2f0042, 1px 2px 4px #0008; } -.rival { border-color: #eaebc7 #888968 #888968 #eaebc7; box-shadow: 0 0 0 1px #323214, 1px 2px 4px #0008; } +.rival_emperor { border-color: #eaebc7 #888968 #888968 #eaebc7; box-shadow: 0 0 0 1px #323214, 1px 2px 4px #0008; } .neutral { border-color: #fffefc #a9a4a2 #a9a4a2 #fffefc; box-shadow: 0 0 0 1px #403d3b, 1px 2px 4px #0008; } .militia { border-color: #ffffff #b2b2b2 #b2b2b2 #ffffff; box-shadow: 0 0 0 1px #434343, 1px 2px 4px #0008; } .legion { border-color: #ffffff #b2b2b2 #b2b2b2 #ffffff; box-shadow: 0 0 0 1px #434343, 1px 2px 4px #0008; } .no_place_governor { border-color: #8e8e8e #505050 #505050 #8e8e8e; box-shadow: 0 0 0 1px #191919, 1px 2px 4px #0008; } +.red.action { box-shadow: 0 0 0 1px #680000, 0 0 0 4px white; } +.blue.action { box-shadow: 0 0 0 1px #113854, 0 0 0 4px white; } +.yellow.action { box-shadow: 0 0 0 1px #553a00, 0 0 0 4px white; } +.green.action { box-shadow: 0 0 0 1px #033600, 0 0 0 4px white; } + #legion_0 { background-position: 0px 0px } #legion_1 { background-position: -55px 0px } #legion_2 { background-position: -110px 0px } @@ -237,7 +243,7 @@ body.p2 #Galatia_Governor { display: none } } .militia, .legion, -.alamanni, .franks, .goths, .nomads, .sassanids, .rival, +.alamanni, .franks, .goths, .nomads, .sassanids, .rival_emperor, .general, .emperor_turns, .no_place_governor { width: 55px; height: 55px; @@ -248,6 +254,13 @@ body.p2 #Galatia_Governor { display: none } border-style: solid; } +.general.unavailable { background-size: 330px 55px } +.general.unavailable.n1 { background-position: -55px 0 } +.general.unavailable.n2 { background-position: -110px 0 } +.general.unavailable.n3 { background-position: -165px 0 } +.general.unavailable.n4 { background-position: -220px 0 } +.general.unavailable.n5 { background-position: -275px 0 } + .no_place_governor { display: none; margin: 4px 0 0 4px; @@ -266,6 +279,13 @@ body.p2 #Galatia_Governor { display: none } border-style: solid; } +.governor.unavailable { background-size: 300px 50px } +.governor.unavailable.n1 { background-position: -50px 0 } +.governor.unavailable.n2 { background-position: -100px 0 } +.governor.unavailable.n3 { background-position: -150px 0 } +.governor.unavailable.n4 { background-position: -200px 0 } +.governor.unavailable.n5 { background-position: -250px 0 } + .castra, .quaestor, .mob, .mob_x2, .seat_of_power, .breakaway { width: 54px; height: 64px; @@ -327,6 +347,9 @@ body.p2 #Galatia_Governor { display: none } .red.emperor_turns { background-image: url(images/red_emperor_turns.png) } .yellow.emperor_turns { background-image: url(images/yellow_emperor_turns.png) } +.general.unavailable { background-image: url(images/general_numbers.png) } +.governor.unavailable { background-image: url(images/governor_numbers.png) } + .castra { background-image: url(images/castra.svg) } .quaestor { background-image: url(images/quaestor.svg) } .mob { background-image: url(images/mob.svg) } @@ -464,15 +487,5 @@ body.p2 #Galatia_Governor { display: none } flex-wrap: wrap; padding: 18px; gap: 18px; + min-height: 350px; } - -#action_body { - display: block; -} - -.action_row { - display: flex; - flex-wrap: wrap; - margin: 8px; -} - diff --git a/play.html b/play.html index 4d37f73..ee69553 100644 --- a/play.html +++ b/play.html @@ -119,19 +119,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -145,159 +132,33 @@ -
-
Available Leaders
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
Actions
-
- -
- - - - - - - -
- -
- - - -
- -
- - - - - - -
- -
- - - - - - - - - - -
- -
-
-
Played / Events
-
Hand
-
-
-
-
-
Draw
-
-
-
-
Discard
-
Market
-
-
-
-
-
-
-
-
-
diff --git a/play.js b/play.js index 3559f92..6a08084 100644 --- a/play.js +++ b/play.js @@ -1,5 +1,93 @@ "use strict" +// === SYNC with rules.js === + +const TRIBE_COUNT = [ 0, 5, 3, 4, 5 ] + +const ITALIA = 0 +const ASIA = 1 +const GALLIA = 2 +const MACEDONIA = 3 +const PANNONIA = 4 +const THRACIA = 5 + +const AEGYPTUS = 6 +const AFRICA = 7 +const HISPANIA = 8 + +const BRITANNIA = 9 +const GALATIA = 10 +const SYRIA = 11 + +const ALAMANNI_HOMELAND = 12 +const FRANKS_HOMELAND = 13 +const GOTHS_HOMELAND = 14 +const NOMADS_HOMELAND = 15 +const SASSANIDS_HOMELAND = 16 + +const MARE_OCCIDENTALE = 17 +const MARE_ORIENTALE = 18 +const OCEANUS_ATLANTICUS = 19 +const PONTUS_EUXINUS = 20 + +const AVAILABLE = 21 +const UNAVAILABLE = 22 + +const ARMY = 23 + +const first_barbarian = [ 0, 10, 20, 30, 40 ] +const first_governor = [ 0, 6, 12, 18 ] +const first_general = [ 0, 6, 12, 18 ] + +const REGION_NAME = [ + "Italia", + "Asia", + "Gallia", + "Macedonia", + "Pannonia", + "Thracia", + "Aegyptus", + "Africa", + "Hispania", + "Britannia", + "Galatia", + "Syria", + "Alamanni Homeland", + "Franks Homeland", + "Goths Homeland", + "Nomads Homeland", + "Sassanids Homeland", + "Mare Occidentale", + "Mare Orientale", + "Oceanus Atlanticus", + "Pontus Euxinus", + "Available", + "Unavailable", +] + +function is_no_place_governor(province) { return province >= view.support.length } +function get_support(province) { return view.support[province] } +function get_barbarian_location(id) { return view.barbarians[id] & 63 } +function is_barbarian_inactive(id) { return view.barbarians[id] & 64 } +function get_legion_location(ix) { return view.legions[ix] & 63 } +function is_legion_reduced(ix) { return view.legions[ix] & 64 } +function is_legion_unused(ix) { return view.legions[ix] === AVAILABLE } +function get_governor_location(id, loc) { return view.governors[id] & 63 } +function get_general_location(id) { return view.generals[id] & 63 } +function is_general_inside_capital(id) { return view.generals[id] & 64 } +function has_general_castra(id) { return view.castra & (1 << id) } +function has_militia_castra(province) { return view.mcastra & (1 << province) } +function has_quaestor(province) { return view.quaestor & (1 << province) } +function has_militia(province) { return view.militia & (1 << province) } +function get_mobs(province) { return view.mobs[province] } +function has_amphitheater(province) { return view.amphitheater & (1 << province) } +function has_basilica(province) { return view.basilica & (1 << province) } +function has_limes(province) { return view.limes & (1 << province) } +function is_breakaway(province) { return view.breakaway & (1 << province) } +function is_seat_of_power(province) { return view.seat_of_power & (1 << province) } + +// === END SYNC === + function set_has(set, item) { let a = 0 let b = set.length - 1 @@ -16,83 +104,95 @@ function set_has(set, item) { return false } -const REGION_LAYOUT = [ - [1038,743,70,70], - [1793,1380,70,70], - [741,1204,70,70], - [1790,908,70,70], - [325,177,70,70], - [2048,842,70,70], - [554,418,70,70], - [249,892,70,70], - [1477,850,70,70], - [1214,536,70,70], - [2174,1193,70,70], - [1594,631,70,70], - [1370,200,195,15], - [900,200,135,15], - [1840,235,130,15], - [705,1495,165,25], - [2295,980,190,25], - [720,890,90,60], - [1770,1170,100,60], - [130,495,80,50], - [1970,620,130,60], -] +const PLAYER_CLASS = [ "red", "blue", "yellow", "green" ] + +const BOXES = { + "Thracia Support": [1502,720,258,52], + "Syria Support": [2034,1280,258,52], + "Pannonia Support": [1154,626,258,53], + "Macedonia Support": [1384,936,258,53], + "Hispania Support": [154,980,258,53], + "Gallia Support": [460,507,258,53], + "Galatia Support": [1954,931,258,53], + "Britannia Support": [231,260,258,52], + "Asia Support": [1679,1000,258,52], + "Africa Support": [647,1290,258,53], + "Aegyptus Support": [1700,1468,258,53], + "Italia Support 2": [1054,887,258,52], + "Italia Support 1": [1028,835,258,52], + "Thracia Capital": [1594,631,70,70], + "Syria Capital": [2174,1193,70,70], + "Pannonia Capital": [1214,536,70,70], + "Macedonia Capital": [1477,850,70,70], + "Italia Capital": [1038,743,70,70], + "Hispania Capital": [249,892,70,70], + "Gallia Capital": [554,418,70,70], + "Galatia Capital": [2048,842,70,70], + "Britannia Capital": [325,177,70,70], + "Asia Capital": [1790,908,70,70], + "Africa Capital": [741,1204,70,70], + "Aegyptus Capital": [1793,1380,70,70], + "Pontus Euxinus XY": [1970,620,130,60], + "Mare Orientale XY": [1770,1170,100,60], + "Mare Occidentale XY": [720,890,90,60], + "Oceanus Atlanticus XY": [130,495,80,50], + "Nomads XY": [705,1495,165,25], + "Sassanids XY": [2295,980,190,25], + "Goths XY": [1840,235,130,15], + "Alamanni XY": [1370,200,195,15], + "Franks XY": [900,200,135,15], + "SCORE TRACK": [40,40,2469,80], + "CRISIS TABLE": [2195,189,262,326], +} -const REGION_LAYOUT2 = [ - [1054,887,258,52], - [1700,1468,258,53], - [647,1290,258,53], - [1679,1000,258,52], - [231,260,258,52], - [1954,931,258,53], - [460,507,258,53], - [154,980,258,53], - [1384,936,258,53], - [1154,626,258,53], - [2034,1280,258,52], - [1502,720,258,52], +const LAYOUT_XY = [ + BOXES["Italia Capital"], + BOXES["Asia Capital"], + BOXES["Gallia Capital"], + BOXES["Macedonia Capital"], + BOXES["Pannonia Capital"], + BOXES["Thracia Capital"], + BOXES["Aegyptus Capital"], + BOXES["Africa Capital"], + BOXES["Hispania Capital"], + BOXES["Britannia Capital"], + BOXES["Galatia Capital"], + BOXES["Syria Capital"], + BOXES["Alamanni XY"], + BOXES["Franks XY"], + BOXES["Goths XY"], + BOXES["Nomads XY"], + BOXES["Sassanids XY"], + BOXES["Mare Occidentale XY"], + BOXES["Mare Orientale XY"], + BOXES["Oceanus Atlanticus XY"], + BOXES["Pontus Euxinus XY"], ] -const REGION_NAME = [ - "Italia", - "Aegyptus", - "Africa", - "Asia", - "Britannia", - "Galatia", - "Gallia", - "Hispania", - "Macedonia", - "Pannonia", - "Syria", - "Thracia", - "Alamanni", - "Franks", - "Goths", - "Nomads", - "Sassanids", - "Mare Occidentale", - "Mare Orientale", - "Oceanus Atlanticus", - "Pontus Euxinus", +const LAYOUT_SUPPORT = [ + BOXES["Italia Support 1"], + BOXES["Asia Support"], + BOXES["Gallia Support"], + BOXES["Macedonia Support"], + BOXES["Pannonia Support"], + BOXES["Thracia Support"], + BOXES["Aegyptus Support"], + BOXES["Africa Support"], + BOXES["Hispania Support"], + BOXES["Britannia Support"], + BOXES["Galatia Support"], + BOXES["Syria Support"], ] let ui = { cards: [], - barbarians: [], - legions: [], militia: [], - barbarian_leaders: [], - rival_emperors: [], body: document.querySelector("body"), header: document.querySelector("header"), - available_generals: document.getElementById("available_generals"), - available_governors: document.getElementById("available_governors"), hand: document.getElementById("hand"), draw: document.getElementById("draw"), discard: document.getElementById("discard"), + played: document.getElementById("played"), market: document.getElementById("market"), pieces: document.getElementById("pieces"), legacy: [ @@ -109,29 +209,32 @@ let ui = { ], regions: [ document.getElementById("mapsvg").getElementById("region_italia"), - document.getElementById("mapsvg").getElementById("region_aegyptus"), - document.getElementById("mapsvg").getElementById("region_africa"), document.getElementById("mapsvg").getElementById("region_asia"), - document.getElementById("mapsvg").getElementById("region_britannia"), - document.getElementById("mapsvg").getElementById("region_galatia"), document.getElementById("mapsvg").getElementById("region_gallia"), - document.getElementById("mapsvg").getElementById("region_hispania"), document.getElementById("mapsvg").getElementById("region_macedonia"), document.getElementById("mapsvg").getElementById("region_pannonia"), - document.getElementById("mapsvg").getElementById("region_syria"), document.getElementById("mapsvg").getElementById("region_thracia"), + + document.getElementById("mapsvg").getElementById("region_aegyptus"), + document.getElementById("mapsvg").getElementById("region_africa"), + document.getElementById("mapsvg").getElementById("region_hispania"), + + document.getElementById("mapsvg").getElementById("region_britannia"), + document.getElementById("mapsvg").getElementById("region_galatia"), + document.getElementById("mapsvg").getElementById("region_syria"), + document.getElementById("mapsvg").getElementById("region_alamanni"), document.getElementById("mapsvg").getElementById("region_franks"), document.getElementById("mapsvg").getElementById("region_goths"), document.getElementById("mapsvg").getElementById("region_nomads"), document.getElementById("mapsvg").getElementById("region_sassanids"), + document.getElementById("mapsvg").getElementById("region_mare_occidentale"), document.getElementById("mapsvg").getElementById("region_mare_orientale"), document.getElementById("mapsvg").getElementById("region_oceanus_atlanticus"), document.getElementById("mapsvg").getElementById("region_pontus_euxinus"), ], capital: [], - province_governor: [], quaestor: [], amphitheater: [], basilica: [], @@ -142,75 +245,30 @@ let ui = { document.getElementById("barbarian_die_1"), document.getElementById("barbarian_die_2"), ], - generals: [ - [ - document.getElementById("red_general_0"), - document.getElementById("red_general_1"), - document.getElementById("red_general_2"), - document.getElementById("red_general_3"), - document.getElementById("red_general_4"), - document.getElementById("red_general_5"), - ], - [ - document.getElementById("blue_general_0"), - document.getElementById("blue_general_1"), - document.getElementById("blue_general_2"), - document.getElementById("blue_general_3"), - document.getElementById("blue_general_4"), - document.getElementById("blue_general_5"), - ], - [ - document.getElementById("yellow_general_0"), - document.getElementById("yellow_general_1"), - document.getElementById("yellow_general_2"), - document.getElementById("yellow_general_3"), - document.getElementById("yellow_general_4"), - document.getElementById("yellow_general_5"), - ], - [ - document.getElementById("green_general_0"), - document.getElementById("green_general_1"), - document.getElementById("green_general_2"), - document.getElementById("green_general_3"), - document.getElementById("green_general_4"), - document.getElementById("green_general_5"), - ], - ], - governors: [ - [ - document.getElementById("red_governor_0"), - document.getElementById("red_governor_1"), - document.getElementById("red_governor_2"), - document.getElementById("red_governor_3"), - document.getElementById("red_governor_4"), - document.getElementById("red_governor_5"), - ], - [ - document.getElementById("blue_governor_0"), - document.getElementById("blue_governor_1"), - document.getElementById("blue_governor_2"), - document.getElementById("blue_governor_3"), - document.getElementById("blue_governor_4"), - document.getElementById("blue_governor_5"), - ], - [ - document.getElementById("yellow_governor_0"), - document.getElementById("yellow_governor_1"), - document.getElementById("yellow_governor_2"), - document.getElementById("yellow_governor_3"), - document.getElementById("yellow_governor_4"), - document.getElementById("yellow_governor_5"), - ], - [ - document.getElementById("green_governor_0"), - document.getElementById("green_governor_1"), - document.getElementById("green_governor_2"), - document.getElementById("green_governor_3"), - document.getElementById("green_governor_4"), - document.getElementById("green_governor_5"), - ], - ], + neutral_governors: [], + barbarian_leaders: [], + rival_emperors: [], + legions: [], + barbarians: [ [], [], [], [], [] ], + generals: [ [], [], [], [] ], + governors: [ [], [], [], [] ], castra: [ [], [], [], [] ], + mcastra: [], +} + +function get_province_governor_player(where) { + let np = view.legacy.length + for (let p = 0; p < np; ++p) + for (let i = 0; i < 6; ++i) + if (get_governor_location(first_governor[p] + i) === where) + return p + return -1 +} + +function is_neutral_province(where) { + if (is_no_place_governor(where)) + return false + return get_province_governor_player(where) < 0 } function show(elt) { @@ -234,30 +292,36 @@ function create(t, p, ...c) { return e } -function create_piece(p) { +function create_thing(p) { let e = create("div", p) ui.pieces.appendChild(e) return e } +function create_piece(id, action, css_class, dom_id) { + if (dom_id) + return create_thing({ className: css_class + " hide", id: dom_id, my_action: action, my_id: id }) + return create_thing({ className: css_class + " hide", my_action: action, my_id: id }) +} + let action_register = [] -function register_action(e, action, id) { - e.my_action = action - e.my_id = id - e.onmousedown = on_click_action - action_register.push(e) +function register_action(target, action, id) { + target.my_action = action + target.my_id = id + target.onmousedown = (evt) => on_click_action(evt, target) + action_register.push(target) } -function on_click_action(evt) { +function on_click_action(evt, target) { if (evt.button === 0) { - if (evt.target.my_stack) { + if (target.my_stack) { evt.stopPropagation() - if (focus_stack(evt.target.my_stack)) - if (!send_action(evt.target.my_action, evt.target.my_id)) + if (focus_stack(target.my_stack)) + if (!send_action(target.my_action, target.my_id)) blur_stack() } else { - if (send_action(evt.target.my_action, evt.target.my_id)) + if (send_action(target.my_action, target.my_id)) evt.stopPropagation() } } @@ -269,8 +333,8 @@ document.getElementById("map").addEventListener("mousedown", function (evt) { }) function create_building(region, className, xoff, yoff) { - let [ x, y, w, h ] = REGION_LAYOUT2[region] - let e = create_piece({ className }) + let [ x, y, w, h ] = LAYOUT_SUPPORT[region] + let e = create_thing({ className }) e.style.left = x + (w>>1) + xoff - 46 + "px" e.style.top = y + h + yoff + "px" return e @@ -290,11 +354,6 @@ function on_init() { c += n } - function init_barbarians(b, n, className) { - for (let i = 0; i < n; ++i) - ui.barbarians[b + i] = create_piece({ className, my_action: "barbarian", my_id: b + i }) - } - init_cards(12, "card influence_m1") init_cards(12, "card influence_s1") init_cards(12, "card influence_p1") @@ -308,20 +367,38 @@ function on_init() { init_cards(6, "card influence_s4") init_cards(6, "card influence_p4") - init_barbarians(0, 10, "alamanni hide") - init_barbarians(10, 10, "franks hide") - init_barbarians(20, 10, "goths hide") - init_barbarians(30, 10, "nomads hide") - init_barbarians(40, 10, "sassanids hide") - for (let i = 0; i < 33; ++i) - ui.legions[i] = create_piece({ className: "legion hide", id: "legion_" + i, my_action: "legion", my_id: i }) + ui.legions[i] = create_piece(i, "legion", "legion", "legion_" + i) + + ui.barbarian_leaders[0] = create_piece(0, "barbarian_leader", "goths", "cniva") + ui.barbarian_leaders[1] = create_piece(1, "barbarian_leader", "sassanids", "ardashir") + ui.barbarian_leaders[2] = create_piece(2, "barbarian_leader", "sassanids", "shapur") + + ui.rival_emperors[0] = create_piece(0, "rival_emperor", "rival_emperor", "postumus") + ui.rival_emperors[1] = create_piece(1, "rival_emperor", "rival_emperor", "priest_king") + ui.rival_emperors[2] = create_piece(2, "rival_emperor", "rival_emperor", "zenobia") + + for (let i = 0; i < 10; ++i) { + ui.barbarians[0][i] = create_piece(0 + i, "barbarian", "alamanni") + ui.barbarians[1][i] = create_piece(10 + i, "barbarian", "franks") + ui.barbarians[2][i] = create_piece(20 + i, "barbarian", "goths") + ui.barbarians[3][i] = create_piece(30 + i, "barbarian", "nomads") + ui.barbarians[4][i] = create_piece(40 + i, "barbarian", "sassanids") + } + + for (let p = 0; p < 4; ++p) { + for (let g = 0; g < 6; ++g) { + ui.castra[p][g] = create_thing({ className: "castra hide" }) + ui.governors[p][g] = create_piece(p * 6 + g, "governor", PLAYER_CLASS[p] + " governor n" + g) + ui.generals[p][g] = create_piece(p * 6 + g, "general", PLAYER_CLASS[p] + " general n" + g) + } + } for (let region = 0; region < 12; ++region) { - ui.militia[region] = create_piece({ className: "militia hide", my_action: "militia", my_id: region }) + ui.mcastra[region] = create_thing({ className: "castra hide" }) + ui.militia[region] = create_thing({ className: "militia hide", my_action: "militia", my_id: region }) ui.capital[region] = document.getElementById(REGION_NAME[region] + "_Capital") ui.quaestor[region] = document.getElementById(REGION_NAME[region] + "_Quaestor") - ui.province_governor[region] = document.getElementById(REGION_NAME[region] + "_Governor") if (true) { ui.amphitheater[region] = create_building(region, "amphitheater hide", -48 - 3, 6) @@ -332,28 +409,15 @@ function on_init() { ui.basilica[region] = create_building(region, "basilica hide", 0, 6) ui.limes[region] = create_building(region, "limes hide", 96 + 5, 6) } - } - for (let region = 0; region < 12; ++region) register_action(ui.capital[region], "capital", region) - for (let region = 0; region < 12 + 5 + 4; ++region) register_action(ui.regions[region], "region", region) - - for (let pi = 0; pi < 4; ++pi) { - for (let ai = 0; ai < 6; ++ai) { - ui.castra[pi][ai] = create_piece({ className: "castra hide" }) - register_action(ui.generals[pi][ai], "general", 100 + 100 * pi + ai) - register_action(ui.governors[pi][ai], "governor", 100 + 100 * pi + ai) - } + + ui.neutral_governors[region] = create_thing({ className: "neutral governor hide" }) + } + for (let region = 12; region < 21; ++region) { + register_action(ui.regions[region], "region", region) } - - ui.barbarian_leaders[0] = create_piece({ id: "cniva", className: "goths hide", my_action: "barbarian_leader", my_id: 0 }) - ui.barbarian_leaders[1] = create_piece({ id: "ardashir", className: "goths hide", my_action: "barbarian_leader", my_id: 1 }) - ui.barbarian_leaders[2] = create_piece({ id: "shapur", className: "goths hide", my_action: "barbarian_leader", my_id: 2 }) - - ui.rival_emperors[0] = create_piece({ id: "postumus", className: "rival hide", my_action: "rival_emperor", my_id: 0 }) - ui.rival_emperors[1] = create_piece({ id: "priest_king", className: "rival hide", my_action: "rival_emperor", my_id: 1 }) - ui.rival_emperors[2] = create_piece({ id: "zenobia", className: "rival hide", my_action: "rival_emperor", my_id: 2 }) } let stack_count = new Array(12 + 5).fill(0) @@ -378,7 +442,7 @@ function blur_stack() { } function layout_stack(id, list, region, in_capital) { - let [ x, y, w, h ] = REGION_LAYOUT[region] + let [ x, y, w, h ] = LAYOUT_XY[region] let dx = 8 let dy = 8 let z = 1 @@ -414,25 +478,53 @@ function layout_stack(id, list, region, in_capital) { } } +function layout_available(list, dx, x0, y0) { + let y = 1650 + 45 - y0 + let x = 25 + x0 + for (let item of list) { + item.style.left = x + "px" + item.style.top = y + "px" + item.style.zIndex = 1 + item.my_stack = 0 + x += dx + } +} + +function layout_governor(e, color, region) { + e.className = color + " governor s" + get_support(region) + e.style.left = (LAYOUT_SUPPORT[region][0] - 1) + "px" + e.style.top = (LAYOUT_SUPPORT[region][1] - 1) + "px" +} + +function layout_governor_available(e, color) { + e.className = color + " governor" +} + +function layout_governor_unavailable(e, color, ix) { + e.className = color + " governor n" + ix +} + function on_update() { + let player_count = view.legacy.length + stack_cache = {} ui.body.classList.toggle("p1", view.solo === 1) - ui.body.classList.toggle("p2", view.players.length === 2) - ui.body.classList.toggle("p3", view.players.length === 3) - ui.body.classList.toggle("p4", view.players.length === 4) + ui.body.classList.toggle("p2", player_count === 2) + ui.body.classList.toggle("p3", player_count === 3) + ui.body.classList.toggle("p4", player_count === 4) ui.header.classList.toggle("player_red", view.current === 0) ui.header.classList.toggle("player_blue", view.current === 1) ui.header.classList.toggle("player_yellow", view.current === 2) ui.header.classList.toggle("player_green", view.current === 3) - if (view.players.length < 4) + if (player_count < 4) hide(document.getElementById("role_Green")) else show(document.getElementById("role_Green")) - if (view.players.length < 3) + if (player_count < 3) hide(document.getElementById("role_Yellow")) else show(document.getElementById("role_Yellow")) @@ -442,131 +534,120 @@ function on_update() { ui.discard.replaceChildren() ui.market.replaceChildren() - for (let i = 0; i < view.players.length; ++i) { - let legacy = view.players[i].legacy - let turns = view.players[i].emperor_turns + for (let pi = 0; pi < player_count; ++pi) { + let legacy = view.legacy[pi] + let turns = view.emperor_turns[pi] if (legacy > 40) { legacy -= 40 - ui.legacy[i].classList.toggle("legacy_40", true) + ui.legacy[pi].classList.toggle("legacy_40", true) } else { - ui.legacy[i].classList.toggle("legacy_40", false) + ui.legacy[pi].classList.toggle("legacy_40", false) } let y = 30 - for (let k = 0; k < i; ++k) { - let k_legacy = view.players[k].legacy + for (let k = 0; k < pi; ++k) { + let k_legacy = view.legacy[k] if (k_legacy > 40) k_legacy -= 40 if (legacy === k_legacy) y += 20 } - show(ui.legacy[i]) - ui.legacy[i].style.left = Math.round(43 + legacy * 60.2) + "px" - ui.legacy[i].style.top = (2 + y) + "px" + show(ui.legacy[pi]) + ui.legacy[pi].style.left = Math.round(43 + legacy * 60.2) + "px" + ui.legacy[pi].style.top = (2 + y) + "px" y = 30 - for (let k = 0; k < i; ++k) { - let k_turns = view.players[k].emperor_turns + for (let k = 0; k < pi; ++k) { + let k_turns = view.emperor_turns[k] if (turns === k_turns) y += 20 } - show(ui.emperor_turns[i]) - ui.emperor_turns[i].style.left = Math.round(41 + turns * 60.2) + "px" - ui.emperor_turns[i].style.top = (0 + y) + "px" + show(ui.emperor_turns[pi]) + ui.emperor_turns[pi].style.left = Math.round(41 + turns * 60.2) + "px" + ui.emperor_turns[pi].style.top = (0 + y) + "px" } - for (let pi = view.players.length; pi < 4; ++pi) { + for (let pi = player_count; pi < 4; ++pi) { hide(ui.legacy[pi]) hide(ui.emperor_turns[pi]) - for (let ai = 0; ai < 6; ++ai) { - ui.generals[pi][ai].remove() - ui.governors[pi][ai].remove() - } } for (let region = 0; region < 12; ++region) { - let who = -1 - for (let pi = 0; pi < view.players.length; ++pi) - for (let ai = 0; ai < 6; ++ai) - if (view.players[pi].governors[ai] === region) - who = pi - if (who < 0) - ui.province_governor[region].classList = "neutral governor s" + view.support[region] - else if (who === 0) - ui.province_governor[region].classList = "red governor s" + view.support[region] - else if (who === 1) - ui.province_governor[region].classList = "blue governor s" + view.support[region] - else if (who === 2) - ui.province_governor[region].classList = "yellow governor s" + view.support[region] - else if (who === 3) - ui.province_governor[region].classList = "green governor s" + view.support[region] - - if (view.quaestor & (1 << region)) + if (has_quaestor(region)) show(ui.quaestor[region]) else hide(ui.quaestor[region]) - if (view.amphitheater & (1 << region)) + if (has_amphitheater(region)) show(ui.amphitheater[region]) else hide(ui.amphitheater[region]) - if (view.basilica & (1 << region)) + if (has_basilica(region)) show(ui.basilica[region]) else hide(ui.basilica[region]) - if (view.limes & (1 << region)) + if (has_limes(region)) show(ui.limes[region]) else hide(ui.limes[region]) - if (view.militia & (1 << region)) + if (has_militia(region)) show(ui.militia[region]) else hide(ui.militia[region]) } for (let i = 0; i < 33; ++i) { - if (view.legions[i] >= 0) { + if (is_legion_unused(i)) + hide(ui.legions[i]) + else show(ui.legions[i]) - if (view.is_legion_reduced[i]) - ui.legions[i].classList.toggle("reduced", true) + if (is_legion_reduced(i)) + ui.legions[i].classList.toggle("reduced", true) + else + ui.legions[i].classList.toggle("reduced", false) + } + + // TODO: Cniva, Ardashir, and Shapur + // TODO: Zenobia, Postumus, Priest King + + let tribe_count = TRIBE_COUNT[player_count] + for (let tribe = 0; tribe < TRIBE_COUNT[player_count]; ++tribe) { + for (let i = 0; i < 10; ++i) { + show(ui.barbarians[tribe][i]) + if (is_barbarian_inactive(first_barbarian[tribe] + i)) + ui.barbarians[tribe][i].classList.toggle("inactive", true) else - ui.legions[i].classList.toggle("reduced", false) - } else { - hide(ui.legions[i]) + ui.barbarians[tribe][i].classList.toggle("inactive", false) } } - for (let i = 0; i < 50; ++i) { - if (view.barbarians[i] >= 0) { - show(ui.barbarians[i]) - if (view.is_barbarian_inactive[i]) - ui.barbarians[i].classList.toggle("inactive", true) - else - ui.barbarians[i].classList.toggle("inactive", false) - } else { - hide(ui.barbarians[i]) + for (let tribe = TRIBE_COUNT[player_count]; tribe < 5; ++tribe) { + for (let i = 0; i < 10; ++i) { + hide(ui.barbarians[tribe][i]) } } stack_count.fill(1) for (let region = 0; region < 12 + 5; ++region) { - for (let tribe = 0; tribe < 5; ++tribe) { + for (let tribe = 0; tribe < tribe_count; ++tribe) { let active_barbarians = [] let inactive_barbarians = [] - for (let i = tribe * 10; i < tribe * 10 + 10; ++i) { - // TODO: Cniva, Ardashir, and Shapur - if (view.barbarians[i] === region) { - if (view.is_barbarian_inactive[i]) - inactive_barbarians.push(ui.barbarians[i]) + // TODO: Cniva, Ardashir, and Shapur + for (let i = 0; i < 10; ++i) { + let loc = get_barbarian_location(first_barbarian[tribe], i) + let inactive = is_barbarian_inactive(first_barbarian[tribe], i) + if (loc === region) { + if (inactive) + inactive_barbarians.push(ui.barbarians[tribe][i]) else - active_barbarians.push(ui.barbarians[i]) + active_barbarians.push(ui.barbarians[tribe][i]) } } if (active_barbarians.length > 0) @@ -577,100 +658,145 @@ function on_update() { } for (let region = 0; region < 12; ++region) { - if (view.militia & (1 << region)) { + if (has_militia(region)) { let lone_militia = true - for (let pi = 0; pi < view.players.length; ++pi) { - for (let ai = 0; ai < 6; ++ai) { - if (view.players[pi].generals[ai] === region) - if (view.players[pi].capital & (1 << ai)) - lone_militia = false + for (let pi = 0; pi < player_count; ++pi) { + for (let i = 0; i < 6; ++i) { + let loc = get_general_location(first_general[pi] + i) + let inside = is_general_inside_capital(first_general[pi] + i) + if (loc === region && inside) + lone_militia = false + } + } + if (lone_militia) { + let mcastra = has_militia_castra(region) + if (mcastra) { + show(ui.mcastra[region]) + layout_stack(0, [ ui.militia[region], ui.mcastra[region] ], region, true) + } else { + hide(ui.mcastra[region]) + layout_stack(0, [ ui.militia[region] ], region, true) } } - if (lone_militia) - layout_stack(0, [ ui.militia[region] ], region, true) + } + + if (is_no_place_governor(region)) { + hide(ui.neutral_governors[region]) + } else { + if (is_neutral_province(region)) { + show(ui.neutral_governors[region]) + layout_governor(ui.neutral_governors[region], "neutral", region) + } else { + hide(ui.neutral_governors[region]) + } } } - for (let pi = 0; pi < view.players.length; ++pi) { - let p = view.players[pi] + for (let pi = 0; pi < player_count; ++pi) { + let avail_stack = [] for (let ai = 0; ai < 6; ++ai) { - let id = 100 + 100 * pi + ai - - let r = p.generals[ai] + let id = ARMY + first_general[pi] + ai + let region = get_general_location(first_general[pi] + ai) + let inside = is_general_inside_capital(first_general[pi] + ai) + let castra = has_general_castra(first_general[pi] + ai) let e = ui.generals[pi][ai] - if (r >= 0) { + show(e) + if (region < 21) { let stack = [] - if (e.parentNode !== ui.pieces) - ui.pieces.appendChild(e) - if ((view.militia & (1 << r)) && (p.capital & (1 << ai))) - stack.push(ui.militia[r]) + if (has_militia(region) && inside) + stack.push(ui.militia[region]) - for (let i = 0; i < 33; ++i) { - if (view.legions[i] === id) { - stack.push(ui.legions[i]) + for (let tribe = 0; tribe < tribe_count; ++tribe) { + for (let i = 0; i < 10; ++i) { + let loc = get_barbarian_location(first_barbarian[tribe] + i) + if (loc === id) + stack.push(ui.barbarians[tribe][i]) } } - for (let i = 0; i < 50; ++i) - if (view.barbarians[i] === id) - stack.push(ui.barbarians[i]) + for (let i = 0; i < 33; ++i) { + let loc = get_legion_location(i) + if (loc === id) + stack.push(ui.legions[i]) + } stack.push(e) - if (p.castra & (1 << ai)) { + if (castra) { show(ui.castra[pi][ai]) stack.push(ui.castra[pi][ai]) } else { hide(ui.castra[pi][ai]) } - if (p.capital & (1 << ai)) - layout_stack(id, stack, r, true) + if (inside) + layout_stack(id, stack, region, true) else - layout_stack(id, stack, r, false) + layout_stack(id, stack, region, false) } else { - if (e.parentNode !== ui.available_generals) - ui.available_generals.appendChild(e) + avail_stack.push(e) } + e.classList.toggle("unavailable", region === UNAVAILABLE) + } + layout_available(avail_stack, 63, pi * 625 + 0, 30) + } - if (p.governors[ai] < 0) - show(ui.governors[pi][ai]) - else - hide(ui.governors[pi][ai]) + for (let pi = 0; pi < player_count; ++pi) { + let avail_stack = [] + for (let ai = 0; ai < 6; ++ai) { + let id = 100 + 100 * pi + ai + let region = get_governor_location(first_governor[pi] + ai) + let e = ui.governors[pi][ai] + if (region >= 1 && region < 12) { + layout_governor(e, PLAYER_CLASS[pi], region) + } else { + if (region === AVAILABLE) + layout_governor_available(e, PLAYER_CLASS[pi]) + else + layout_governor_unavailable(e, PLAYER_CLASS[pi], ai) + avail_stack.push(e) + } + e.classList.toggle("unavailable", region === UNAVAILABLE) } + layout_available(avail_stack, 58, pi * 625 + 325, 27) } - if (view.dice[0] > 0) { - ui.dice[0].className = "dice black d" + view.dice[0] - ui.dice[1].className = "dice white d" + view.dice[1] - } else { - ui.dice[0].className = "dice hide" - ui.dice[1].className = "dice hide" + ui.dice[0].className = "dice black d" + view.dice[0] + ui.dice[1].className = "dice white d" + view.dice[1] + ui.dice[2].className = "dice black d" + view.dice[2] + ui.dice[3].className = "dice white d" + view.dice[3] + + if (view.events) { + for (let c of view.events) + ui.played.appendChild(ui.cards[c]) } - if (view.dice[2] > 0) { - ui.dice[2].className = "dice black d" + view.dice[0] - ui.dice[3].className = "dice white d" + view.dice[1] - } else { - ui.dice[2].className = "dice hide" - ui.dice[3].className = "dice hide" + + ui.played.replaceChildren() + if (view.played) { + for (let c of view.played) + ui.played.appendChild(ui.cards[c]) } + ui.hand.replaceChildren() if (view.hand) { for (let c of view.hand) ui.hand.appendChild(ui.cards[c]) } + ui.draw.replaceChildren() if (view.draw) { for (let c of view.draw) ui.draw.appendChild(ui.cards[c]) } + ui.discard.replaceChildren() if (view.discard) { for (let c of view.discard) ui.discard.appendChild(ui.cards[c]) } + ui.market.replaceChildren() for (let pile of view.market) { if (pile.length > 0) ui.market.appendChild(ui.cards[pile[0]]) @@ -679,6 +805,32 @@ function on_update() { for (let e of action_register) e.classList.toggle("action", is_action(e.my_action, e.my_id)) + action_button("enter", "Enter Capital") + action_button("leave", "Leave Capital") + + action_button("spend", "Spend") + action_button("roll", "Roll") + + action_button("militia", "Place Militia") + action_button("support", "Increase Support Level") + action_button("hold_games", "Hold Games") + action_button("build_improvement", "Build an Improvement") + + action_button("disperse_mob", "Disperse Mob") + action_button("train_legions", "Train Legions") + action_button("add_legion_to_army", "Add Legion to Army") + action_button("move_army", "Move Army") + action_button("initiate_battle", "Initiate Battle") + + action_button("amphitheater", "Amphitheater") + action_button("basilica", "Basilica") + action_button("limes", "Limes") + + action_button("recall", "Recall") + action_button("recruit", "Recruit") + action_button("place", "Place") + action_button("create_army", "Create Army") + action_button("end_actions", "End Actions") action_button("done", "Done") diff --git a/rules.js b/rules.js index f1b960e..da23666 100644 --- a/rules.js +++ b/rules.js @@ -1,12 +1,28 @@ "use strict" -// === CONSTANTS AND DATA === +var game +var view +const states = {} const P1 = "Red" const P2 = "Blue" const P3 = "Yellow" const P4 = "Green" +exports.scenarios = [ "Standard" ] + +exports.roles = function (scenario, options) { + if (options.players == 1) + return [ "Solo" ] + if (options.players == 2) + return [ P1, P2 ] + if (options.players == 3) + return [ P1, P2, P3 ] + return [ P1, P2, P3, P4 ] +} + +// === CONSTANTS === + const PLAYER_NAMES = [ P1, P2, P3, P4 ] const PLAYER_INDEX = { @@ -18,23 +34,26 @@ const PLAYER_INDEX = { "Observer": -1, } -const NO_PLACE_GOVERNOR = -1 -const OFF_MAP = -1 -const AVAILABLE = -1 -const UNAVAILABLE = -2 +const MILITARY = 0 +const SENATE = 1 +const POPULACE = 2 + +const LEGION_COUNT = 33 + +// REGIONS const ITALIA = 0 -const AEGYPTUS = 1 -const AFRICA = 2 -const ASIA = 3 -const BRITANNIA = 4 -const GALATIA = 5 -const GALLIA = 6 -const HISPANIA = 7 -const MACEDONIA = 8 -const PANNONIA = 9 -const SYRIA = 10 -const THRACIA = 11 +const ASIA = 1 +const GALLIA = 2 +const MACEDONIA = 3 +const PANNONIA = 4 +const THRACIA = 5 +const AEGYPTUS = 6 +const AFRICA = 7 +const HISPANIA = 8 +const BRITANNIA = 9 +const GALATIA = 10 +const SYRIA = 11 const ALAMANNI_HOMELAND = 12 const FRANKS_HOMELAND = 13 const GOTHS_HOMELAND = 14 @@ -44,40 +63,70 @@ const MARE_OCCIDENTALE = 17 const MARE_ORIENTALE = 18 const OCEANUS_ATLANTICUS = 19 const PONTUS_EUXINUS = 20 - -const ARMY = [ - [ 100, 101, 102, 103, 104, 105 ], - [ 200, 201, 202, 203, 204, 205 ], - [ 300, 301, 302, 303, 304, 305 ], - [ 400, 401, 402, 403, 404, 405 ] -] +const AVAILABLE = 21 +const UNAVAILABLE = 22 +const ARMY = 23 const REGION_NAME = [ "Italia", - "Aegyptus", - "Africa", "Asia", - "Britannia", - "Galatia", "Gallia", - "Hispania", "Macedonia", "Pannonia", - "Syria", "Thracia", + "Aegyptus", + "Africa", + "Hispania", + "Britannia", + "Galatia", + "Syria", "Alamanni Homeland", "Franks Homeland", "Goths Homeland", "Nomads Homeland", "Sassanids Homeland", + "Mare Occidentale", + "Mare Orientale", + "Oceanus Atlanticus", + "Pontus Euxinus", + "Available", + "Unavailable", ] +const ADJACENT = [ + /* ITALIA */ [ GALLIA, PANNONIA, MARE_OCCIDENTALE ], + /* ASIA */ [ THRACIA, GALATIA, MARE_ORIENTALE, PONTUS_EUXINUS ], + /* GALLIA */ [ ITALIA, PANNONIA, HISPANIA, FRANKS_HOMELAND, MARE_OCCIDENTALE, OCEANUS_ATLANTICUS ], + /* MACEDONIA */ [ PANNONIA, THRACIA, MARE_OCCIDENTALE, MARE_ORIENTALE ], + /* PANNONIA */ [ ITALIA, GALLIA, MACEDONIA, THRACIA, ALAMANNI_HOMELAND, FRANKS_HOMELAND, MARE_OCCIDENTALE ], + /* THRACIA */ [ ASIA, MACEDONIA, PANNONIA, ALAMANNI_HOMELAND, GOTHS_HOMELAND, MARE_ORIENTALE, PONTUS_EUXINUS ], + /* AEGYPTUS */ [ AFRICA, SYRIA, NOMADS_HOMELAND, MARE_ORIENTALE ], + /* AFRICA */ [ AEGYPTUS, HISPANIA, NOMADS_HOMELAND, MARE_OCCIDENTALE, MARE_ORIENTALE, OCEANUS_ATLANTICUS ], + /* HISPANIA */ [ GALLIA, AFRICA, MARE_OCCIDENTALE, OCEANUS_ATLANTICUS ], + /* BRITANNIA */ [ OCEANUS_ATLANTICUS ], + /* GALATIA */ [ ASIA, SYRIA, SASSANIDS_HOMELAND, MARE_ORIENTALE, PONTUS_EUXINUS ], + /* SYRIA */ [ AEGYPTUS, GALATIA, SASSANIDS_HOMELAND, MARE_ORIENTALE ], + /* ALAMANNI_HOMELAND */ [ PANNONIA, THRACIA, FRANKS_HOMELAND, GOTHS_HOMELAND ], + /* FRANKS_HOMELAND */ [ GALLIA, PANNONIA, ALAMANNI_HOMELAND ], + /* GOTHS_HOMELAND */ [ THRACIA, ALAMANNI_HOMELAND, PONTUS_EUXINUS ], + /* NOMADS_HOMELAND */ [ AEGYPTUS, AFRICA, OCEANUS_ATLANTICUS ], + /* SASSANIDS_HOMELAND */ [ GALATIA, SYRIA, PONTUS_EUXINUS ], + /* MARE_OCCIDENTALE */ [ ITALIA, GALLIA, MACEDONIA, PANNONIA, AFRICA, HISPANIA, MARE_ORIENTALE, OCEANUS_ATLANTICUS ], + /* MARE_ORIENTALE */ [ ASIA, MACEDONIA, THRACIA, AEGYPTUS, AFRICA, GALATIA, SYRIA, MARE_OCCIDENTALE ], + /* OCEANUS_ATLANTICUS */ [ GALLIA, AFRICA, HISPANIA, BRITANNIA, NOMADS_HOMELAND, MARE_OCCIDENTALE ], + /* PONTUS_EUXINUS */ [ ASIA, THRACIA, GALATIA, GOTHS_HOMELAND, SASSANIDS_HOMELAND ], +] + +// BARBARIANS + const ALAMANNI = 0 const FRANKS = 1 const GOTHS = 2 const NOMADS = 3 const SASSANIDS = 4 +const TRIBE_COUNT = [ 0, 5, 3, 4, 5 ] + const BARBARIAN_NAME = [ "Alamanni", "Franks", @@ -86,22 +135,6 @@ const BARBARIAN_NAME = [ "Sassanids", ] -const ALAMANNI_UNITS = [ 0, 9 ] -const FRANKS_UNITS = [ 10, 19 ] -const GOTHS_UNITS = [ 20, 29 ] -const NOMADS_UNITS = [ 30, 39 ] -const SASSANIDS_UNITS = [ 40, 49 ] - -const LEGION_UNITS = [ 50, 82 ] - -const BARBARIAN_UNITS = [ - ALAMANNI_UNITS, - FRANKS_UNITS, - GOTHS_UNITS, - NOMADS_UNITS, - SASSANIDS_UNITS, -] - const BARBARIAN_HOMELAND = [ ALAMANNI_HOMELAND, FRANKS_HOMELAND, @@ -140,43 +173,7 @@ const BARBARIAN_INVASION = [ ], ] -// 12x -const CARD_M1 = [ 1, 12 ] -const CARD_S1 = [ 13, 24 ] -const CARD_P1 = [ 25, 36 ] - -// 9x -const CARD_M2 = [ 37, 45 ] -const CARD_S2 = [ 46, 54 ] -const CARD_P2 = [ 55, 63 ] - -// 8x -const CARD_M3 = [ 64, 71 ] -const CARD_S3 = [ 72, 79 ] -const CARD_P3 = [ 80, 87 ] - -// 6x -const CARD_M4 = [ 88, 93 ] -const CARD_S4 = [ 94, 99 ] -const CARD_P4 = [ 100, 105 ] - -const EVENT_PLAGUE_OF_CYPRIAN = 1 -const EVENT_ARDASHIR = 2 -const EVENT_PRIEST_KING = 3 -const EVENT_PALMYRA_ALLIES = 4 -const EVENT_SHAPUR_I = 5 -const EVENT_POSTUMUS = 6 -const EVENT_LUDI_SAECULARES = 7 -const EVENT_CNIVA = 8 -const EVENT_ZENOBIA = 9 -const EVENT_BAD_AUGURIES = 10 -const EVENT_RAIDING_PARTIES = 11 -const EVENT_PREPARING_FOR_WAR = 12 -const EVENT_INFLATION = 13 -const EVENT_GOOD_AUGURIES = 14 -const EVENT_DIOCLETIAN = 15 - -const CRISIS_TABLE_4P = [ 0, 0, +const CRISIS_TABLE_4P = [ 0, SASSANIDS, FRANKS, @@ -190,7 +187,7 @@ const CRISIS_TABLE_4P = [ 0, 0, 0, ] -const CRISIS_TABLE_3P = [ 0, 0, +const CRISIS_TABLE_3P = [ 0, FRANKS, SASSANIDS, @@ -204,7 +201,7 @@ const CRISIS_TABLE_3P = [ 0, 0, 0, ] -const CRISIS_TABLE_2P = [ 0, 0, +const CRISIS_TABLE_2P = [ 0, FRANKS, ALAMANNI, @@ -218,355 +215,471 @@ const CRISIS_TABLE_2P = [ 0, 0, 0, ] -// === +// CARDS -var game -var view +const EVENT_PLAGUE_OF_CYPRIAN = 1 +const EVENT_ARDASHIR = 2 +const EVENT_PRIEST_KING = 3 +const EVENT_PALMYRA_ALLIES = 4 +const EVENT_SHAPUR_I = 5 +const EVENT_POSTUMUS = 6 +const EVENT_LUDI_SAECULARES = 7 +const EVENT_CNIVA = 8 +const EVENT_ZENOBIA = 9 +const EVENT_BAD_AUGURIES = 10 +const EVENT_RAIDING_PARTIES = 11 +const EVENT_PREPARING_FOR_WAR = 12 +const EVENT_INFLATION = 13 +const EVENT_GOOD_AUGURIES = 14 +const EVENT_DIOCLETIAN = 15 -const states = {} +// 12x +const CARD_M1 = [ 1, 12 ] +const CARD_S1 = [ 13, 24 ] +const CARD_P1 = [ 25, 36 ] -exports.scenarios = [ "Standard" ] +// 9x +const CARD_M2 = [ 37, 45 ] +const CARD_S2 = [ 46, 54 ] +const CARD_P2 = [ 55, 63 ] -exports.roles = function (scenario, options) { - if (options.players == 1) - return [ "Solo" ] - if (options.players == 2) - return [ P1, P2 ] - if (options.players == 3) - return [ P1, P2, P3 ] - return [ P1, P2, P3, P4 ] -} +// 8x +const CARD_M3 = [ 64, 71 ] +const CARD_S3 = [ 72, 79 ] +const CARD_P3 = [ 80, 87 ] -function setup_player_deck(pi) { - return [ - CARD_M1[0] + (pi * 3) + 0, - CARD_M1[0] + (pi * 3) + 1, - CARD_M1[0] + (pi * 3) + 2, - CARD_S1[0] + (pi * 3) + 0, - CARD_S1[0] + (pi * 3) + 1, - CARD_S1[0] + (pi * 3) + 2, - CARD_P1[0] + (pi * 3) + 0, - CARD_P1[0] + (pi * 3) + 1, - CARD_P1[0] + (pi * 3) + 2, - ] -} +// 6x +const CARD_M4 = [ 88, 93 ] +const CARD_S4 = [ 94, 99 ] +const CARD_P4 = [ 100, 105 ] -function setup_events() { - let deck = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ] - shuffle(deck) - // Shuffle Diocletian with last 3 cards - array_insert(deck, 11 + random(4), 15) - return deck +function card_name(c) { + if (c >= CARD_M1[0] && c <= CARD_M1[1]) return "M1" + if (c >= CARD_M2[0] && c <= CARD_M2[1]) return "M2" + if (c >= CARD_M3[0] && c <= CARD_M3[1]) return "M3" + if (c >= CARD_M4[0] && c <= CARD_M4[1]) return "M4" + if (c >= CARD_S1[0] && c <= CARD_S1[1]) return "S1" + if (c >= CARD_S2[0] && c <= CARD_S2[1]) return "S2" + if (c >= CARD_S3[0] && c <= CARD_S3[1]) return "S3" + if (c >= CARD_S4[0] && c <= CARD_S4[1]) return "S4" + if (c >= CARD_P1[0] && c <= CARD_P1[1]) return "P1" + if (c >= CARD_P2[0] && c <= CARD_P2[1]) return "P2" + if (c >= CARD_P3[0] && c <= CARD_P3[1]) return "P3" + if (c >= CARD_P4[0] && c <= CARD_P4[1]) return "P4" + return "??" +} + +function card_event_name(c) { + if (c >= CARD_M1[0] && c <= CARD_M1[1]) return "None" + if (c >= CARD_M2[0] && c <= CARD_M2[1]) return "Castra" + if (c >= CARD_M3[0] && c <= CARD_M3[1]) return "Flanking Maneuver" + if (c >= CARD_M4[0] && c <= CARD_M4[1]) return "Praetorian Guard" + if (c >= CARD_S1[0] && c <= CARD_S1[1]) return "None" + if (c >= CARD_S2[0] && c <= CARD_S2[1]) return "Tribute" + if (c >= CARD_S3[0] && c <= CARD_S3[1]) return "Foederati" + if (c >= CARD_S4[0] && c <= CARD_S4[1]) return "Damnatio Memoriae" + if (c >= CARD_P1[0] && c <= CARD_P1[1]) return "None" + if (c >= CARD_P2[0] && c <= CARD_P2[1]) return "Quaestor" + if (c >= CARD_P3[0] && c <= CARD_P3[1]) return "Mob" + if (c >= CARD_P4[0] && c <= CARD_P4[1]) return "Pretender" + return "None" +} + +function add_card_ip(c) { + if (c >= CARD_M1[0] && c <= CARD_M1[1]) return game.ip[MILITARY] += 1 + if (c >= CARD_M2[0] && c <= CARD_M2[1]) return game.ip[MILITARY] += 2 + if (c >= CARD_M3[0] && c <= CARD_M3[1]) return game.ip[MILITARY] += 3 + if (c >= CARD_M4[0] && c <= CARD_M4[1]) return game.ip[MILITARY] += 4 + if (c >= CARD_S1[0] && c <= CARD_S1[1]) return game.ip[SENATE] += 1 + if (c >= CARD_S2[0] && c <= CARD_S2[1]) return game.ip[SENATE] += 2 + if (c >= CARD_S3[0] && c <= CARD_S3[1]) return game.ip[SENATE] += 3 + if (c >= CARD_S4[0] && c <= CARD_S4[1]) return game.ip[SENATE] += 4 + if (c >= CARD_P1[0] && c <= CARD_P1[1]) return game.ip[POPULACE] += 1 + if (c >= CARD_P2[0] && c <= CARD_P2[1]) return game.ip[POPULACE] += 2 + if (c >= CARD_P3[0] && c <= CARD_P3[1]) return game.ip[POPULACE] += 3 + if (c >= CARD_P4[0] && c <= CARD_P4[1]) return game.ip[POPULACE] += 4 } - -function setup_market_pile(cards) { - let pile = [] - for (let c = cards[0]; c <= cards[1]; ++c) - pile.push(c) - return pile + +function is_region(where) { + return where < AVAILABLE } - -function setup_barbarians(tribe) { - for (let i = BARBARIAN_UNITS[tribe][0]; i <= BARBARIAN_UNITS[tribe][1]; ++i) - game.barbarians[i] = BARBARIAN_HOMELAND[tribe] + +function is_province(where) { + return where < 12 } -function remove_barbarians(tribe) { - for (let i = BARBARIAN_UNITS[tribe][0]; i <= BARBARIAN_UNITS[tribe][1]; ++i) - game.barbarians[i] = OFF_MAP +function is_sea(where) { + return where >= MARE_OCCIDENTALE && where <= PONTUS_EUXINUS } -exports.setup = function (seed, scenario, options) { - let player_count = options.players || 4 +function current_hand() { return game.hand[game.current] } +function current_draw() { return game.draw[game.current] } +function current_discard() { return game.discard[game.current] } - game = { - seed: seed, - log: [], - undo: [], - active: 0, - current: 0, - state: "setup_province", - first: 0, - events: null, - active_events: [], - support: new Array(12).fill(1), - mobs: 0, - militia: 0, - quaestor: 0, - legions: new Array(33).fill(OFF_MAP), - is_legion_reduced: new Array(33).fill(0), - barbarians: new Array(50).fill(OFF_MAP), - is_barbarian_inactive: new Array(50).fill(1), - barbarian_leaders: [ OFF_MAP, OFF_MAP, OFF_MAP ], - rival_emperors: [ OFF_MAP, OFF_MAP, OFF_MAP ], - amphitheater: 0, - basilica: 0, - limes: 0, - dice: [ 0, 0, 0, 0 ], // first two are crisis table dice, second two are barbarian homeland dice - market: null, - players: [], - } +// === BOARD STATE === - game.events = setup_events() - game.market = [ - setup_market_pile(CARD_M2), - setup_market_pile(CARD_S2), - setup_market_pile(CARD_P2), - setup_market_pile(CARD_M3), - setup_market_pile(CARD_S3), - setup_market_pile(CARD_P3), - setup_market_pile(CARD_M4), - setup_market_pile(CARD_S4), - setup_market_pile(CARD_P4), - ] +function is_no_place_governor(province) { + return province >= game.support.length +} - setup_barbarians(ALAMANNI) - setup_barbarians(FRANKS) - setup_barbarians(GOTHS) - setup_barbarians(NOMADS) - setup_barbarians(SASSANIDS) +function get_support(province) { return game.support[province] } +function set_support(province, level) { game.support[province] = level } - if (player_count === 1) { - game.solo = 1 - player_count = 4 - } +function get_barbarian_location(id) { return game.barbarians[id] & 63 } +function set_barbarian_location(id, loc) { game.barbarians[id] = loc } +function is_barbarian_inactive(id) { return game.barbarians[id] & 64 } +function is_barbarian_active(id) { return !is_barbarian_inactive(id) } +function set_barbarian_inactive(id) { game.barbarians[id] |= 64 } +function set_barbarian_active(id) { game.barbarians[id] &= 63 } - for (let pi = 0; pi < player_count; ++pi) { - game.players[pi] = { - legacy: 0, - emperor_turns: 0, - hand: [], - draw: setup_player_deck(pi), - discard: [], - generals: [ AVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], - governors: [ AVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], - capital: 0, - castra: 0, - } - } +function get_legion_location(ix) { return game.legions[ix] & 63 } +function set_legion_location(ix, loc) { game.legions[ix] = loc } +function is_legion_reduced(ix) { return game.legions[ix] & 64 } +function set_legion_reduced(ix) { game.legions[ix] |= 64 } +function set_legion_full_strength(ix) { game.legions[ix] &= 63 } - if (player_count === 4) { - game.support[ITALIA] = 8 - } +function is_legion_unused(ix) { + return game.legions[ix] === AVAILABLE +} - if (player_count === 3) { - game.support[ITALIA] = 6 - game.support[HISPANIA] = NO_PLACE_GOVERNOR - game.support[AFRICA] = NO_PLACE_GOVERNOR - game.support[AEGYPTUS] = NO_PLACE_GOVERNOR - remove_barbarians(NOMADS) - } +function get_governor_location(id, loc) { return game.governors[id] & 63 } +function set_governor_location(id, loc) { game.governors[id] = loc } +function get_general_location(id) { return game.generals[id] & 63 } - if (player_count === 2) { - game.support[ITALIA] = 4 - game.support[BRITANNIA] = NO_PLACE_GOVERNOR - game.support[HISPANIA] = NO_PLACE_GOVERNOR - game.support[AFRICA] = NO_PLACE_GOVERNOR - game.support[AEGYPTUS] = NO_PLACE_GOVERNOR - game.support[SYRIA] = NO_PLACE_GOVERNOR - game.support[GALATIA] = NO_PLACE_GOVERNOR - remove_barbarians(NOMADS) - remove_barbarians(SASSANIDS) - } +function set_general_location(id, loc) { game.generals[id] = loc } +function is_general_inside_capital(id) { return game.generals[id] & 64 } +function set_general_inside_capital(id) { game.generals[id] |= 64 } +function set_general_outside_capital(id) { game.generals[id] &= 63 } - game.first = game.current = random(player_count) - log(PLAYER_NAMES[game.first] + " is First Player!") +function has_general_castra(id) { return game.castra & (1 << id) } +function add_general_castra(id) { game.castra |= (1 << id) } +function remove_general_castra(id) { game.castra &= ~(1 << id) } - return save_game() -} +function has_militia_castra(province) { return game.mcastra & (1 << province) } +function add_militia_castra(province) { game.mcastra |= (1 << province) } +function remove_militia_castra(province) { game.mcastra &= ~(1 << province) } -function load_game(state) { - game = state -} +function has_quaestor(province) { return game.quaestor & (1 << province) } +function add_quaestor(province) { game.quaestor |= (1 << province) } +function remove_quaestor(province) { game.quaestor &= ~(1 << province) } -function save_game() { - if (game.solo) - game.active = "Solo" - else - game.active = PLAYER_NAMES[game.current] - return game -} +function has_militia(province) { return game.militia & (1 << province) } +function add_militia(province) { game.militia |= (1 << province) } +function remove_militia(province) { game.militia &= ~(1 << province) } -exports.action = function (state, player, action, arg) { - load_game(state) - let S = states[game.state] - if (action in S) { - S[action](arg) - } else { - if (action === "undo" && game.undo && game.undo.length > 0) - pop_undo() - else - throw new Error("Invalid action: " + action) - } - return save_game() -} +function has_mob(province) { return game.mobs[province] > 0 } +function get_mobs(province) { return game.mobs[province] } +function add_one_mob(province) { game.mobs[province] ++ } +function remove_one_mob(province) { game.mobs[province] -- } +function remove_all_mobs(province) { game.mobs[province] = 0 } -function is_current_player(player) { - if (player === 4) - return true - return game.current === player -} +function has_amphitheater(province) { return game.amphitheater & (1 << province) } +function has_basilica(province) { return game.basilica & (1 << province) } +function has_limes(province) { return game.limes & (1 << province) } +function add_amphitheater(province) { game.amphitheater |= (1 << province) } +function add_basilica(province) { game.basilica |= (1 << province) } +function add_limes(province) { game.limes |= (1 << province) } -exports.view = function (state, player_name) { - let player = PLAYER_INDEX[player_name] +function is_breakaway(province) { return game.breakaway & (1 << province) } +function add_breakaway(province) { game.breakaway |= (1 << province) } +function remove_breakaway(province) { game.breakaway &= ~(1 << province) } - load_game(state) +function is_seat_of_power(province) { return game.seat_of_power & (1 << province) } +function add_seat_of_power(province) { game.seat_of_power |= (1 << province) } +function remove_seat_of_power(province) { game.seat_of_power &= ~(1 << province) } - view = { - log: game.log, - current: game.current, - prompt: null, +// === TRANSIENT STATE === - militia: game.militia, - support: game.support, - quaestor: game.quaestor, - mobs: game.mobs, - legions: game.legions, - is_legion_reduced: game.is_legion_reduced, - barbarians: game.barbarians, - is_barbarian_inactive: game.is_barbarian_inactive, - barbarian_leaders: game.barbarian_leaders, - rival_emperors: game.rival_emperors, - amphitheater: game.amphitheater, - basilica: game.basilica, - limes: game.limes, - dice: game.dice, - market: game.market, - players: [], - } +function has_placed_governor(province) { return game.placed & (1 << province) } +function set_placed_governor(province) { game.placed |= (1 << province) } - for (let i = 0; i < game.players.length; ++i) { - view.players[i] = { - legacy: game.players[i].legacy, - emperor_turns: game.players[i].emperor_turns, - generals: game.players[i].generals, - governors: game.players[i].governors, - capital: game.players[i].capital, - castra: game.players[i].castra, - } - } +function has_general_battled(id) { return game.battled & (1 << id) } +function set_general_battled(id) { game.battled |= (1 << id) } - if (game.state === "game_over") { - view.prompt = game.victory - } else if (player !== game.current && player_name !== "Solo") { - let inactive = states[game.state].inactive || game.state - view.prompt = `Waiting for ${PLAYER_NAMES[game.current]} \u2014 ${inactive}...` - } else { - view.actions = {} - states[game.state].prompt() - view.prompt = PLAYER_NAMES[game.current] + ": " + view.prompt - if (game.undo && game.undo.length > 0) - view.actions.undo = 1 - else - view.actions.undo = 0 - } +function has_militia_battled(province) { return game.mbattled & (1 << province) } +function set_militia_battled(province) { game.mbattled |= (1 << province) } - if (player >= 0 && player <= game.players.length) { - view.hand = game.players[player].hand - view.draw = game.players[player].draw - view.discard = game.players[player].discard - } +// === COMPOUND STATE === - save_game() - return view +function for_each_current_general(f) { + let a = game.current * 6 + for (let id = a; id < a + 6; ++id) + f(id, get_general_location(id), is_general_inside_capital(id)) } -// === MISC === +function for_each_current_governor(f) { + let a = game.current * 6 + for (let id = a; id < a + 6; ++id) + f(id, get_governor_location(id)) +} -function log(msg) { - game.log.push(msg) +function for_each_general(f) { + let n = game.legacy.length * 6 + for (let id = 0; id < n; ++id) + f(id, get_general_location(id), is_general_inside_capital(id)) } -function log_br() { - if (game.log.length > 0 && game.log[game.log.length - 1] !== "") - game.log.push("") +function for_each_governor(f) { + let n = game.legacy.length * 6 + for (let id = 0; id < n; ++id) + f(id, get_governor_location(id)) } -function log_h1(msg) { - log_br() - log(".h1 " + msg) - log_br() +function find_general(f) { + let n = game.legacy.length * 6 + for (let id = 0; id < n; ++id) + if (f(id, get_general_location(id), is_general_inside_capital(id))) + return id + return -1 } -function log_h2(msg) { - log_br() - log(".h2 " + msg) - log_br() +function find_governor(f) { + let n = game.legacy.length * 6 + for (let id = 0; id < n; ++id) + if (f(id, get_governor_location(id))) + return id + return -1 } -function logi(msg) { - game.log.push(">" + msg) +function some_general(f) { + let n = game.legacy.length * 6 + for (let id = 0; id < n; ++id) + if (f(id, get_general_location(id), is_general_inside_capital(id))) + return true + return false } -function logii(msg) { - game.log.push(">>" + msg) +function some_governor(f) { + let n = game.legacy.length * 6 + for (let id = 0; id < n; ++id) + if (f(id, get_governor_location(id))) + return true + return false } -// === STATES === - function next_player() { - return (game.current + 1) % game.players.length + return (game.current + 1) % game.legacy.length +} + +function find_unused_legion() { + for (let ix = 0; ix < LEGION_COUNT; ++ix) + if (get_legion_location(ix) === AVAILABLE) + return ix + return -1 } -function get_governor(r) { - for (let p = 0; p < game.players.length; ++p) { - for (let i = 0; i < 6; ++i) - if (game.players[p].governors[i] === r) - return p +function can_build_improvement(province) { + if (has_mob(province)) + return false + if (has_active_barbarians(province)) + return false + if (has_rival_emperor(province)) + return false + if (has_enemy_army_in_capital(province)) + return false + return true +} + +function update_italia_support() { + if (is_neutral_province(ITALIA)) { + let n = 1 + for (let s = 1; s < 12; ++s) + if (is_neutral_province(s)) + ++n + if (n > 8) + n = 8 + set_support(ITALIA, n) } +} + +function get_player_count() { + return game.legacy.length +} + +function can_enter_capital(where) { + // No Capital + if (is_no_place_governor(where)) + return false + + // Occupied by General + if (some_general((id, loc, cap) => loc === where && cap)) + return false + + // Occupied by opponent Militia + if (has_militia(where)) { + if (!is_province_you_govern(where)) + return false + } + + return true +} + +function get_province_governor(where) { + return find_governor((id, loc) => loc === where) +} + +function get_capital_general(where) { + return find_general((id, loc, cap) => loc === where && cap) +} + +function is_neutral_province(where) { + if (is_no_place_governor(where)) + return false + return get_province_governor(where) < 0 +} + +function is_province_you_govern(where) { + let a = game.current * 6 + let id = get_province_governor(where) + if (id >= a && id < a + 6) + return true + return false +} + +function has_active_barbarians(where) { + return false // TODO +} + +function has_rival_emperor(where) { + return false // TODO +} + +function has_enemy_army_in_capital(where) { + return false // TODO +} + +function spend_ip(type, n) { + game.ip[type] -= n +} + +function can_place_governor(where) { + if (is_province_you_govern(where)) + return false + // Recalled or already attempted to place. + if (has_placed_governor(where)) + return false + // Cannot Place in breakaway provinces + if (is_breakaway(where) || is_seat_of_power(where)) + return false + return true +} + +function find_active_barbarian(tribe) { + let home = BARBARIAN_HOMELAND[tribe] + for (let i = 0; i < 10; ++i) + if (get_barbarian_location(tribe * 10 + i) === home) + if (is_barbarian_active(tribe * 10 + i)) + return tribe * 10 + i return -1 } -function is_neutral_province(r) { - return (game.support[r] !== NO_PLACE_GOVERNOR) && (get_governor(r) < 0) +function count_active_barbarians_at_home(tribe) { + let home = BARBARIAN_HOMELAND[tribe] + let n = 0 + for (let i = 0; i < 10; ++i) + if (get_barbarian_location(tribe * 10 + i) === home) + if (is_barbarian_active(tribe * 10 + i)) + n += 1 + return n } -function find_legion() { - for (let i = 0; i < 33; ++i) - if (game.legions[i] < 0) - return i +function find_inactive_barbarian_at_home(tribe) { + let home = BARBARIAN_HOMELAND[tribe] + for (let i = 0; i < 10; ++i) + if (get_barbarian_location(tribe * 10 + i) === home) + if (is_barbarian_inactive(tribe * 10 + i)) + return tribe * 10 + i return -1 } -function current_hand() { - return game.players[game.current].hand +function activate_one_barbarian(tribe) { + let i = find_inactive_barbarian_at_home(tribe) + if (i >= 0) + set_barbarian_active(i) +} + +function count_barbarians_in_province(tribe, where) { + let n = 0 + for (let i = 0; i < 10; ++i) + if (get_barbarian_location(tribe * 10 + i) === where) + n += 1 + return n } -function current_draw() { - return game.players[game.current].draw + +function count_legions_in_army(id) { + let n = 0 + for (let i = 0; i < LEGION_COUNT; ++i) + if (get_legion_location(i) === ARMY + id) + ++n + return n } -function add_militia(r) { - game.militia |= (1 << r) +function find_reduced_legion_in_army(id) { + for (let i = 0; i < LEGION_COUNT; ++i) + if (get_legion_location(i) === ARMY + id && is_legion_reduced(i)) + return i + return -1 } -function remove_militia(r) { - game.militia &= ~(1 << r) +function has_reduced_legions_in_army(id) { + return find_reduced_legion_in_army(id) >= 0 } -function get_support(r) { - return game.support[r] +function count_units_in_army(id) { + let n = 0 + for (let i = 0; i < LEGION_COUNT; ++i) + if (get_legion_location(i) === ARMY + id) + ++n + for (let loc of game.barbarians) + if (loc === ARMY + id) + ++n + return n } -function set_support(r, level) { - game.support[r] = level +function roll_dice(count, target) { + let hits = 0 + console.log("roll_dice", count, target) + while (count > 0) { + let summary = [] + let sixes = 0 + for (let i = 0; i < count; ++i) { + let die = roll_die() + if (die === 6) + sixes += 1 + if (die >= target) { + summary.push("B" + die) + hits += 1 + } else { + summary.push("W" + die) + } + } + log("Rolled " + summary.join(" ")) + count = sixes + } + return hits } +// === SETUP === + states.setup_province = { prompt() { view.prompt = "Select a starting Province." - for (let r = 1; r < 12; ++r) - if (is_neutral_province(r)) - gen_action("capital", r) + for (let where = 2; where <= 12; ++where) + if (is_neutral_province(where)) + gen_action("capital", where) }, - capital(r) { + capital(where) { push_undo() - let p = game.current - game.players[p].generals[0] = r - game.players[p].governors[0] = r - game.players[p].capital = 1 - game.legions[find_legion()] = ARMY[p][0] - add_militia(r) + + set_governor_location(game.current * 6 + 0, where) + + add_militia(where) + + set_general_location(game.current * 6 + 0, where) + set_general_inside_capital(game.current * 6 + 0) + set_legion_location(find_unused_legion(), ARMY + game.current * 6 + 0) + game.state = "setup_hand" }, } @@ -577,7 +690,7 @@ states.setup_hand = { let hand = current_hand() if (hand.length < 5) { for (let c of current_draw()) - gen_action("card", c) + gen_action_card(c) } else { view.actions.done = 1 } @@ -596,6 +709,8 @@ states.setup_hand = { }, } +// === UPKEEP === + function goto_start_turn() { log_h2(PLAYER_NAMES[game.current]) goto_upkeep() @@ -607,6 +722,8 @@ function goto_upkeep() { // === CRISIS === +// TODO: manual barbarian invasions! + function goto_crisis() { game.dice[0] = roll_die() game.dice[1] = roll_die() @@ -622,11 +739,11 @@ function goto_crisis() { if (sum === 7) return goto_event() - if (game.players.length === 2) - return goto_barbarian_crisis(CRISIS_TABLE_2P[sum]) - if (game.players.length === 3) - return goto_barbarian_crisis(CRISIS_TABLE_3P[sum]) - return goto_barbarian_crisis(CRISIS_TABLE_4P[sum]) + if (game.legacy.length === 2) + return goto_barbarian_crisis(CRISIS_TABLE_2P[sum - 2]) + if (game.legacy.length === 3) + return goto_barbarian_crisis(CRISIS_TABLE_3P[sum - 2]) + return goto_barbarian_crisis(CRISIS_TABLE_4P[sum - 2]) } function goto_ira_deorum() { @@ -634,8 +751,10 @@ function goto_ira_deorum() { activate_one_barbarian(ALAMANNI) activate_one_barbarian(FRANKS) activate_one_barbarian(GOTHS) - activate_one_barbarian(NOMADS) - activate_one_barbarian(SASSANIDS) + if (game.legacy.length > 3) + activate_one_barbarian(NOMADS) + if (game.legacy.length > 2) + activate_one_barbarian(SASSANIDS) goto_take_actions() } @@ -651,62 +770,6 @@ function goto_event() { goto_take_actions() } -function is_barbarian_active(i) { - return !game.is_barbarian_inactive[i] -} - -function is_barbarian_inactive(i) { - return game.is_barbarian_inactive[i] -} - -function set_barbarian_active(i) { - game.is_barbarian_inactive[i] = 0 -} - -function set_barbarian_inactive(i) { - game.is_barbarian_inactive[i] = 1 -} - -function find_active_barbarian(tribe) { - let home = BARBARIAN_HOMELAND[tribe] - for (let i = tribe * 10; i < tribe * 10 + 10; ++i) - if (game.barbarians[i] === home && is_barbarian_active(i)) - return i - return -1 -} - -function find_inactive_barbarian(tribe) { - let home = BARBARIAN_HOMELAND[tribe] - for (let i = tribe * 10; i < tribe * 10 + 10; ++i) - if (game.barbarians[i] === home && is_barbarian_inactive(i)) - return i - return -1 -} - -function count_active_barbarians_at_home(tribe) { - let home = BARBARIAN_HOMELAND[tribe] - let n = 0 - for (let i = tribe * 10; i < tribe * 10 + 10; ++i) - if (game.barbarians[i] === home && is_barbarian_active(i)) - n += 1 - return n -} - -function count_barbarians(tribe, region) { - // TODO: count leaders - let n = 0 - for (let i = tribe * 10; i < tribe * 10 + 10; ++i) - if (game.barbarians[i] === region) - n += 1 - return n -} - -function activate_one_barbarian(tribe) { - let i = find_inactive_barbarian(tribe) - if (i >= 0) - set_barbarian_active(i) -} - function goto_barbarian_crisis(tribe) { logi(BARBARIAN_NAME[tribe]) activate_one_barbarian(tribe) @@ -722,11 +785,11 @@ function goto_barbarian_crisis(tribe) { goto_take_actions() } -function invade_with_active_barbarian(tribe, region) { +function invade_with_active_barbarian(tribe, where) { // TODO: move leaders first - let i = find_active_barbarian(tribe) - if (i >= 0) - game.barbarians[i] = region + let b = find_active_barbarian(tribe) + if (b >= 0) + set_barbarian_location(tribe * 10 + b, where) } function goto_barbarian_invasion(tribe, black, white) { @@ -739,7 +802,7 @@ function goto_barbarian_invasion(tribe, black, white) { let k = 0 for (let i = 0; i < black;) { - let n = count_barbarians(tribe, path[k]) + let n = count_barbarians_in_province(tribe, path[k]) if (n < 3) { invade_with_active_barbarian(tribe, path[k]) ++i @@ -756,34 +819,833 @@ function goto_barbarian_invasion(tribe, black, white) { function goto_take_actions() { game.state = "take_actions" + game.ip = [ 0, 0, 0 ] + game.played = [] + game.used = [] + game.placed = 0 // only place governor once (and no place if recalled) +} + +function prompt(s) { + view.prompt = s +} + +function prompt_take_actions(label) { + let [ mip, sip, pip ] = game.ip + prompt(`${label}: ${mip} Military, ${sip} Senate, ${pip} Populace.`) +} + +function action_take_actions_card(c) { + push_undo() + let hand = current_hand() + if (set_has(hand, c)) { + set_delete(hand, c) + set_add(game.played, c) + add_card_ip(c) + } else if (set_has(game.played, c)) { + log("TODO - use event") + set_remove(game.played, c) + set_add(current_discard(), c) + } +} + +function action_take_actions_end_actions() { + push_undo() + goto_support_check() } states.take_actions = { prompt() { + let player = game.current + let [ mip, sip, pip ] = game.ip + + prompt_take_actions("Take Actions") + for (let c of current_hand()) + gen_action_card(c) + + for (let i = 0; i < 6; ++i) { + let id = game.current * 6 + i + let where = get_governor_location(id) + if ((where === UNAVAILABLE) && (sip >= i)) + gen_action_governor(id) + if ((where === AVAILABLE) && (sip >= 1)) + gen_action_governor(id) + if (is_region(where) && (sip >= 1 || pip >= 1)) + gen_action_governor(id) + } + + for (let i = 0; i < 6; ++i) { + let id = game.current * 6 + i + let where = get_general_location(id) + if ((where === UNAVAILABLE) && (mip >= i)) + gen_action_general(id) + if ((where === AVAILABLE) && (mip >= 1)) + gen_action_general(id) + // if (is_region(where) && (mip >= 1)) + if (is_region(where) && (mip >= 1 || where <= 12)) + gen_action_general(id) + } + view.actions.end_actions = 1 }, - end_actions() { - goto_expand_pretender_empire() + card: action_take_actions_card, + end_actions: action_take_actions_end_actions, + governor(id) { + push_undo() + let where = get_governor_location(id) + if (where === UNAVAILABLE) { + spend_ip(SENATE, id % 6) + set_governor_location(id, AVAILABLE) + } + else if (where === AVAILABLE) { + game.who = id + spend_ip(SENATE, 1) + game.state = "place_governor_where" + game.misc = { spend: 1, where: -1 } + } + else { + game.who = id + game.state = "take_actions_governor" + } + }, + general(id) { + push_undo() + let where = get_general_location(id) + if (where === UNAVAILABLE) { + spend_ip(MILITARY, id % 6) + set_general_location(id, AVAILABLE) + } + else if (where === AVAILABLE) { + spend_ip(MILITARY, 1) + game.who = id + game.state = "create_army" + } + else { + game.who = id + game.state = "take_actions_general" + } + }, +} + +states.take_actions_governor = { + prompt() { + let [ mip, sip, pip ] = game.ip + let governor = game.who + let where = get_governor_location(game.who) + + prompt_take_actions("Take Governor Actions") + for (let c of current_hand()) + gen_action_card(c) + view.actions.done = 1 + + // Recruit Governor + if (where === UNAVAILABLE) { + if (sip >= game.who % 6) + view.actions.recruit = 1 + } + + // Place Governor + if (where === AVAILABLE) { + if (sip >= 1) + view.actions.place = 1 + } + + if (where >= 0) { + // Recall Governor + if (sip >= 2) + view.actions.recall = 1 + + // Increase Support Level + let support = game.support[where] + if (support < 4 && where !== ITALIA) { + if (pip > support) + view.actions.support = 1 + + } + + // Place Militia + if (!has_militia(where)) { + if (pip >= 2) + view.actions.militia = 1 + } + + // Hold Games + if (has_mob(where)) { + if (pip >= 2) + view.actions.hold_games = 1 + } + + // Build an Improvement + if (!has_amphitheater(where) || !has_basilica(where) || !has_limes(where)) { + if (can_build_improvement(where)) + if (pip >= 3) + view.actions.build_improvement = 1 + } + + // TODO: Initiate Battle with Militia not stacked with General + } + }, + card: action_take_actions_card, + end_actions: action_take_actions_end_actions, + governor(id) { + push_undo() + game.state = "take_actions" + }, + recruit() { + push_undo() + log("Recruited Governor " + (game.who % 6) + ".") + spend_ip(SENATE, game.who % 6) + set_governor_location(game.who, AVAILABLE) + }, + place() { + push_undo() + spend_ip(SENATE, 1) + game.state = "place_governor" + game.misc = { spend: 1 } + }, + recall() { + push_undo() + let where = get_governor_location(game.who) + log("Recalled Governor from S" + where + ".") + spend_ip(SENATE, 2) + set_governor_location(game.who, AVAILABLE) + set_support(where, 1) + set_placed_governor(where) + update_italia_support() + }, + support() { + push_undo() + let where = get_governor_location(game.who) + let support = game.support[where] + log("Built Support in S" + where + ".") + spend_ip(POPULACE, support + 1) + game.support[where] = support + 1 + }, + hold_games() { + push_undo() + let where = get_governor_location(game.who) + log("Held Games in S" + where + ".") + spend_ip(POPULACE, 3) + remove_one_mob(where) + }, + build_improvement() { + push_undo() + game.state = "build_improvement" + spend_ip(POPULACE, 3) + }, + done() { + push_undo() + game.state = "take_actions" + }, +} + +states.take_actions_general = { + prompt() { + let [ mip, sip, pip ] = game.ip + let where = get_general_location(game.who) + + prompt_take_actions("Take General Actions") + for (let c of current_hand()) + gen_action_card(c) + view.actions.done = 1 + + gen_action_general(game.who) + + // Recruit General + if (where === UNAVAILABLE) { + if (mip >= game.who % 6) + view.actions.recruit = 1 + } + + // Create Army + if (where === AVAILABLE) { + if (mip >= 1 && find_unused_legion() >= 0) + view.actions.create_army = 1 + } + + if (where >= 0) { + // Add Legion to Army + if (is_province_you_govern(where)) { + let cost = count_legions_in_army(game.who) + 1 + if (mip >= cost) + view.actions.add_legion_to_army = 1 + } + + // Train Legions + if (has_reduced_legions_in_army(game.who)) { + if (mip >= 1) + view.actions.train_legions = 1 + } + + // Disperse Mob + if (has_mob(where)) { + if (mip >= 1) + view.actions.disperse_mob = 1 + } + + if (!has_general_battled(game.who)) { + // Move Army + if (mip >= 1) { + for (let to of ADJACENT[where]) { + if (!is_sea(to)) + gen_action_region(to) + else if (mip >= 2) + gen_action_region(to) + } + } + + // Initiate Battle + + // Free Action: Enter/Leave capital + if (where < 12) { + if (is_general_inside_capital(game.who)) + view.actions.leave = 1 + else if (can_enter_capital(where)) + view.actions.enter = 1 + } + } + } + + }, + card: action_take_actions_card, + end_actions: action_take_actions_end_actions, + general(id) { + push_undo() + game.state = "take_actions" + }, + recruit() { + push_undo() + let general = game.who + log("Recruited General " + general + ".") + spend_ip(MILITARY, general) + set_piece_location(game.who, AVAILABLE) + }, + add_legion_to_army() { + push_undo() + let general = game.who + let cost = count_legions_in_army(game.who) + 1 + log("Added Legion to Army.") + spend_ip(MILITARY, cost) + set_legion_location(find_unused_legion(), ARMY + game.who) + }, + train_legions() { + push_undo() + let general = game.who + let i = find_reduced_legion_in_army(game.who) + log("Trained Legions.") + spend_ip(MILITARY, 1) + set_legion_full_strength(i) + }, + create_army() { + push_undo() + spend_ip(MILITARY, 1) + game.state = "create_army" + }, + region(to) { + push_undo() + move_army_to(game.who, to) + }, + enter() { + push_undo() + set_general_inside_capital(game.who) + }, + leave() { + push_undo() + set_general_outside_capital(game.who) + }, + capital() { + this.enter() + }, + done() { + push_undo() + game.state = "take_actions" + }, +} + +states.place_governor_where = { + prompt() { + prompt("Place Governor.") + for (let where = 0; where < 12; ++where) { + if (can_place_governor(where)) + gen_action_region(where) + } + }, + region(where) { + push_undo() + game.misc.where = where + game.state = "place_governor_spend" + }, +} + +// ACTION: PLACE GOVERNOR + +function place_governor(new_governor, where) { + let support = get_support(where) + let new_support = Math.max(1, support - 1) + + // TODO: Italia support + + let old_governor = get_province_governor(where) + if (old_governor >= 0) + set_governor_location(old_governor, AVAILABLE) + + set_governor_location(new_governor, where) + set_support(where, new_support) +} + +function calc_needed_votes(where) { + let n = get_support(where) * 2 // base number of votes + let old_governor = get_province_governor(where) + if (old_governor >= 0) { + let old_player = old_governor / 6 | 0 + let army_general = get_capital_general(where) + if (army_general >= 0) { + let army_player = army_general / 6 | 0 + let army_size = count_units_in_army(army_general) + if (army_player === old_player) + n += army_size + else if (army_player === game.current) + n -= army_size + } + if (has_militia(where)) + n += 1 + } + console.log("votes needed", where, n) + return Math.max(1, n) +} + +states.place_governor_spend = { + prompt() { + let [ mip, sip, pip ] = game.ip + let need = calc_needed_votes(game.misc.where) + prompt("Place Governor: " + game.misc.spend + " IP spent; " + need + " votes needed.") + if (sip >= 1) + view.actions.spend = 1 + else + view.actions.spend = 0 + view.actions.roll = 1 + }, + spend() { + push_undo() + spend_ip(SENATE, 1) + game.misc.spend += 1 + }, + roll() { + let need = calc_needed_votes(game.misc.where) + let have = 0 + + set_placed_governor(game.misc.where) + + log("Place Governor in S" + game.misc.where) + if (is_neutral_province(game.misc.where)) + have = roll_dice(game.misc.spend, 1) + else if (has_quaestor(game.misc.where)) + have = roll_dice(game.misc.spend, 3) + else + have = roll_dice(game.misc.spend, 2) + + if (have >= need) + place_governor(game.who, game.misc.where) + + game.misc = null + game.state = "take_actions" + }, +} + +// ACTION: BUILD IMPROVEMENT + +states.build_improvement = { + prompt() { + let where = get_governor_location(game.who) + prompt("Build Improvement.") + if (!has_amphitheater(where)) + view.actions.amphitheater = 1 + if (!has_basilica(where)) + view.actions.basilica = 1 + if (!has_limes(where)) + view.actions.limes = 1 + }, + amphitheater() { + let where = get_governor_location(game.who) + add_amphitheater(where) + log("Built Amphitheater in S" + where + ".") + game.state = "take_actions_governor" + }, + basilica() { + let where = get_governor_location(game.who) + add_basilica(where) + log("Built Basilica in S" + where + ".") + game.state = "take_actions_governor" + }, + limes() { + let where = get_governor_location(game.who) + add_limes(where) + log("Built Limes in S" + where + ".") + game.state = "take_actions_governor" + }, +} + +// ACTION: CREATE ARMY + +states.create_army = { + prompt() { + prompt("Create Army.") + for (let i = 0; i < 6; ++i) { + let where = get_governor_location(game.current * 6 + i) + if (is_province(where)) + gen_action_region(where) + } + }, + region(where) { + push_undo() + log("Created Army in S" + where + ".") + set_general_location(game.who, where) + if (can_enter_capital(where)) + set_general_inside_capital(game.who) + set_legion_location(find_unused_legion(), ARMY + game.who) + game.state = "take_actions_general" + }, +} + +// ACTION: MOVE ARMY + +states.move_army_at_sea = { + prompt() { + prompt("Move Army.") + let [ mip, sip, pip ] = game.ip + let who = view.who = game.who + let from = get_piece_location(who) + for (let to of ADJACENT[from]) { + if (is_sea(to)) { + if (mip >= 2) + gen_action_region(to) + } else { + gen_action_region(to) + } + } + }, + region(to) { + push_undo() + move_army_to(game.who, to) }, } +function move_army_to(who, to) { + log("Moved Army to S" + to + ".") + + spend_ip(MILITARY, 1) + set_general_location(who, to) + if (can_enter_capital(to)) + set_general_inside_capital(who) + + if (is_sea(to)) + game.state = "move_army_at_sea" + else if (game.ip[MILITARY] > 0) + game.state = "take_actions_general" + else + game.state = "take_actions" +} + +// === SUPPORT CHECK === + +function goto_support_check() { + goto_expand_pretender_empire() +} + +// === EXPAND PRETENDER EMPIRE === + function goto_expand_pretender_empire() { goto_gain_legacy() } +// === GAIN LEGACY === + function goto_gain_legacy() { goto_buy_trash_cards() } +// === BUY / TRASH CARDS === + function goto_buy_trash_cards() { + let discard = current_discard() + for (let c of game.played) { + set_add(discard, c) + } + game.played.length = 0 + game.state = "buy_trash_discard" + game.misc = { + count: 0, + pp: 0, + } goto_end_of_turn() } +// === END OF TURN === + function goto_end_of_turn() { game.current = next_player() goto_start_turn() } +// === SETUP === + +function setup_player_deck(player) { + return [ + CARD_M1[0] + (player * 3) + 0, + CARD_M1[0] + (player * 3) + 1, + CARD_M1[0] + (player * 3) + 2, + CARD_S1[0] + (player * 3) + 0, + CARD_S1[0] + (player * 3) + 1, + CARD_S1[0] + (player * 3) + 2, + CARD_P1[0] + (player * 3) + 0, + CARD_P1[0] + (player * 3) + 1, + CARD_P1[0] + (player * 3) + 2, + ] +} + +function setup_events() { + let deck = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ] + shuffle(deck) + // Shuffle Diocletian with last 3 cards + array_insert(deck, 11 + random(4), 15) + return deck +} + +function setup_market_pile(cards) { + let pile = [] + for (let c = cards[0]; c <= cards[1]; ++c) + pile.push(c) + return pile +} + +function setup_barbarians(tribe, home) { + for (let i = 0; i < 10; ++i) { + set_barbarian_location(tribe * 10 + i, home) + set_barbarian_inactive(tribe * 10 + i) + } +} + +exports.setup = function (seed, scenario, options) { + let real_player_count = options.players || 4 + let player_count = real_player_count + if (player_count === 1) + player_count = 4 + let tribe_count = TRIBE_COUNT[player_count] + + game = { + seed: seed, + log: [], + undo: [], + active: 0, + current: 0, + state: "setup_province", + first: 0, + events: null, + active_events: [], + + ip: [], + who: -1, + played: [], + used: [], + placed: 0, + battled: 0, + + // grab bag of temporary data for the current procedure + misc: null, + + support: new Array(12).fill(1), + mobs: new Array(12).fill(0), + militia: 0, + quaestor: 0, + castra: 0, + mcastra: 0, + amphitheater: 0, + basilica: 0, + limes: 0, + breakaway: 0, + seat_of_power: 0, + + governors: new Array(6 * player_count).fill(UNAVAILABLE), + generals: new Array(6 * player_count).fill(UNAVAILABLE), + legions: new Array(LEGION_COUNT).fill(AVAILABLE), + barbarians: new Array(10 * tribe_count).fill(0), + rivals: [ UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], + barbarian_leaders: [ UNAVAILABLE, UNAVAILABLE, UNAVAILABLE ], + + dice: [ 0, 0, 0, 0 ], // first two are crisis table dice, second two are barbarian homeland dice + market: null, + + // per-player data + legacy: new Array(player_count).fill(0), + emperor_turns: new Array(player_count).fill(0), + hand: [], + draw: [], + discard: [], + } + + if (real_player_count === 1) + game.solo = 1 + + game.events = setup_events() + + game.market = [ + setup_market_pile(CARD_M2), + setup_market_pile(CARD_S2), + setup_market_pile(CARD_P2), + setup_market_pile(CARD_M3), + setup_market_pile(CARD_S3), + setup_market_pile(CARD_P3), + setup_market_pile(CARD_M4), + setup_market_pile(CARD_S4), + setup_market_pile(CARD_P4), + ] + + setup_barbarians(ALAMANNI, ALAMANNI_HOMELAND) + setup_barbarians(FRANKS, FRANKS_HOMELAND) + setup_barbarians(GOTHS, GOTHS_HOMELAND) + if (player_count >= 3) + setup_barbarians(NOMADS, NOMADS_HOMELAND) + if (player_count >= 4) + setup_barbarians(SASSANIDS, SASSANIDS_HOMELAND) + + for (let player = 0; player < player_count; ++player) { + game.hand[player] = [] + game.draw[player] = setup_player_deck(player) + game.discard[player] = [] + } + + update_italia_support() + + game.first = game.current = random(player_count) + log("First Player is " + PLAYER_NAMES[game.first] + "!") + + return save_game() +} + +function load_game(state) { + game = state +} + +function save_game() { + if (game.solo) + game.active = "Solo" + else + game.active = PLAYER_NAMES[game.current] + return game +} + +exports.action = function (state, player, action, arg) { + load_game(state) + let S = states[game.state] + if (action in S) { + S[action](arg) + } else { + if (action === "undo" && game.undo && game.undo.length > 0) + pop_undo() + else + throw new Error("Invalid action: " + action) + } + return save_game() +} + +function is_current_player(player) { + if (player === 4) + return true + return game.current === player +} + +exports.view = function (state, player_name) { + load_game(state) + + let player = PLAYER_INDEX[player_name] + if (game.solo) + player = game.current + let player_count = game.legacy.length + + view = { + log: game.log, + current: game.current, + prompt: null, + + support: game.support, + mobs: game.mobs, + militia: game.militia, + quaestor: game.quaestor, + castra: game.castra, + amphitheater: game.amphitheater, + basilica: game.basilica, + limes: game.limes, + + governors: game.governors, + generals: game.generals, + legions: game.legions, + barbarians: game.barbarians, + rivals: game.rivals, + barbarian_leaders: game.barbarian_leaders, + + dice: game.dice, + events: game.active_events, + played: game.played, + market: game.market, + + legacy: game.legacy, + emperor_turns: game.emperor_turns, + } + + if (game.state === "game_over") { + view.prompt = game.victory + } else if (player !== game.current && player_name !== "Solo") { + let inactive = states[game.state].inactive || game.state + view.prompt = `Waiting for ${PLAYER_NAMES[game.current]} \u2014 ${inactive}...` + } else { + view.actions = {} + states[game.state].prompt() + if (game.undo && game.undo.length > 0) + view.actions.undo = 1 + else + view.actions.undo = 0 + } + + if (player >= 0 && player < player_count) { + view.hand = game.hand[player] + view.draw = game.draw[player] + view.discard = game.discard[player] + } + + save_game() + return view +} + +// === MISC === + +function log(msg) { + game.log.push(msg) +} + +function log_br() { + if (game.log.length > 0 && game.log[game.log.length - 1] !== "") + game.log.push("") +} + +function log_h1(msg) { + log_br() + log(".h1 " + msg) + log_br() +} + +function log_h2(msg) { + log_br() + log(".h2 " + msg) + log_br() +} + +function logi(msg) { + game.log.push(">" + msg) +} + +function logii(msg) { + game.log.push(">>" + msg) +} + // === COMMON LIBRARY === function roll_die() { @@ -796,6 +1658,30 @@ function gen_action(action, argument) { set_add(view.actions[action], argument) } +function gen_action_general(ix) { + gen_action("general", ix) +} + +function gen_action_governor(ix) { + gen_action("governor", ix) +} + +function gen_action_legion(ix) { + gen_action("legion", ix) +} + +function gen_action_barbarian(ix) { + gen_action("barbarian", ix) +} + +function gen_action_region(where) { + gen_action("region", where) +} + +function gen_action_card(c) { + gen_action("card", c) +} + function clear_undo() { if (game.undo.length > 0) game.undo = [] diff --git a/tools/genmove.js b/tools/genmove.js new file mode 100644 index 0000000..a1dbc19 --- /dev/null +++ b/tools/genmove.js @@ -0,0 +1,115 @@ +"use strict" + +function array_insert(array, index, item) { + for (let i = array.length; i > index; --i) + array[i] = array[i - 1] + array[index] = item +} + +function set_add(set, item) { + let a = 0 + let b = set.length - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = set[m] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else + return + } + array_insert(set, a, item) +} + +const ITALIA = 1 +const ASIA = 2 +const GALLIA = 3 +const MACEDONIA = 4 +const PANNONIA = 5 +const THRACIA = 6 + +const AEGYPTUS = 7 +const AFRICA = 8 +const HISPANIA = 9 + +const BRITANNIA = 10 +const GALATIA = 11 +const SYRIA = 12 + +const ALAMANNI_HOMELAND = 13 +const FRANKS_HOMELAND = 14 +const GOTHS_HOMELAND = 15 +const NOMADS_HOMELAND = 16 +const SASSANIDS_HOMELAND = 17 + +const MARE_OCCIDENTALE = 18 +const MARE_ORIENTALE = 19 +const OCEANUS_ATLANTICUS = 20 +const PONTUS_EUXINUS = 21 + +const names = [ + "null", + "ITALIA", + "ASIA", + "GALLIA", + "MACEDONIA", + "PANNONIA", + "THRACIA", + "AEGYPTUS", + "AFRICA", + "HISPANIA", + "BRITANNIA", + "GALATIA", + "SYRIA", + "ALAMANNI_HOMELAND", + "FRANKS_HOMELAND", + "GOTHS_HOMELAND", + "NOMADS_HOMELAND", + "SASSANIDS_HOMELAND", + "MARE_OCCIDENTALE", + "MARE_ORIENTALE", + "OCEANUS_ATLANTICUS", + "PONTUS_EUXINUS", +] + +const ADJACENT = [ [] ] + +for (let a = 1; a <= 21; ++a) + ADJACENT[a] = [] + +function adj(a, ...bs) { + for (let b of bs) { + set_add(ADJACENT[a], b) + set_add(ADJACENT[b], a) + } +} + +adj(ITALIA, GALLIA, PANNONIA, MARE_OCCIDENTALE) +adj(AEGYPTUS, AFRICA, SYRIA, NOMADS_HOMELAND, MARE_ORIENTALE) +adj(AFRICA, HISPANIA, AEGYPTUS, NOMADS_HOMELAND, MARE_OCCIDENTALE, MARE_ORIENTALE, OCEANUS_ATLANTICUS) +adj(ASIA, THRACIA, GALATIA, PONTUS_EUXINUS, MARE_ORIENTALE) +adj(BRITANNIA, OCEANUS_ATLANTICUS) +adj(GALATIA, ASIA, SYRIA, SASSANIDS_HOMELAND, PONTUS_EUXINUS, MARE_ORIENTALE) +adj(GALLIA, HISPANIA, ITALIA, PANNONIA, FRANKS_HOMELAND, OCEANUS_ATLANTICUS, MARE_OCCIDENTALE) +adj(HISPANIA, AFRICA, GALLIA, OCEANUS_ATLANTICUS, MARE_OCCIDENTALE) +adj(MACEDONIA, PANNONIA, THRACIA, MARE_OCCIDENTALE, MARE_ORIENTALE) +adj(PANNONIA, ITALIA, GALLIA, THRACIA, MACEDONIA, FRANKS_HOMELAND, ALAMANNI_HOMELAND, MARE_OCCIDENTALE) +adj(SYRIA, AEGYPTUS, GALATIA, SASSANIDS_HOMELAND, MARE_ORIENTALE) +adj(THRACIA, PANNONIA, MACEDONIA, ASIA, GOTHS_HOMELAND, PONTUS_EUXINUS, MARE_ORIENTALE) + +adj(ALAMANNI_HOMELAND, FRANKS_HOMELAND, PANNONIA, THRACIA, GOTHS_HOMELAND) +adj(FRANKS_HOMELAND, GALLIA, PANNONIA, ALAMANNI_HOMELAND) +adj(GOTHS_HOMELAND, ALAMANNI_HOMELAND, THRACIA, PONTUS_EUXINUS) +adj(NOMADS_HOMELAND, OCEANUS_ATLANTICUS, AFRICA, AEGYPTUS) +adj(SASSANIDS_HOMELAND, PONTUS_EUXINUS, GALATIA, SYRIA) + +adj(MARE_OCCIDENTALE, OCEANUS_ATLANTICUS, MARE_ORIENTALE) + +adj(ALAMANNI_HOMELAND) + +console.log("const ADJACENT = [") +for (let i = 0; i <= 21; ++i) { + console.log("\t/*", names[i], "*/ [", ADJACENT[i].map(i => names[i]).join(", "), "],") +} +console.log("]") diff --git a/tools/genpieces.js b/tools/genpieces.js new file mode 100644 index 0000000..01d7edb --- /dev/null +++ b/tools/genpieces.js @@ -0,0 +1,33 @@ +var ix = 0 + +function mk(name, n) { + var a = ix + var b = ix + n - 1 + console.log("const " + name + "_data = " + a) + ix = b + 1 +} + +function size(name) { + console.log("const " + name + "_size = " + (ix)) +} + +mk("province", 12) +mk("legion", 33) +mk("rival_emperor", 3) +mk("barbarian_leader", 3) +mk("alamanni", 10) +mk("franks", 10) +mk("goths", 10) +mk("red_governor", 6) +mk("red_general", 6) +mk("blue_governor", 6) +mk("blue_general", 6) +size("data_2p") +mk("nomads", 10) +mk("yellow_governor", 6) +mk("yellow_general", 6) +size("data_3p") +mk("sassanids", 10) +mk("green_governor", 6) +mk("green_general", 6) +size("data_4p") -- cgit v1.2.3