summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-05-27 19:28:16 +0200
committerTor Andersson <tor@ccxvii.net>2023-07-07 18:39:23 +0200
commit495090873cf62a1ca698cee18a2d292fc30aa292 (patch)
tree7b179325adf10a13ab3827ddafe320249a2d3e06
parent069a9310cea47805d5b8d55d04961b9e5b3d9527 (diff)
downloadtime-of-crisis-495090873cf62a1ca698cee18a2d292fc30aa292.tar.gz
Stuff.
-rw-r--r--play.css43
-rw-r--r--play.html139
-rw-r--r--play.js750
-rw-r--r--rules.js1722
-rw-r--r--tools/genmove.js115
-rw-r--r--tools/genpieces.js33
6 files changed, 1931 insertions, 871 deletions
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 @@
<div id="Syria_Quaestor" class="quaestor hide" style="left:1977px;top:1274px"></div>
<div id="Thracia_Quaestor" class="quaestor hide" style="left:1445px;top:714px"></div>
-<div id="Italia_Governor" class="neutral governor s8" style="left:1026px;top:834px"></div>
-<div id="Aegyptus_Governor" class="neutral governor s1" style="left:1698px;top:1467px"></div>
-<div id="Africa_Governor" class="neutral governor s1" style="left:646px;top:1289px"></div>
-<div id="Asia_Governor" class="neutral governor s1" style="left:1677px;top:999px"></div>
-<div id="Britannia_Governor" class="neutral governor s1" style="left:229px;top:259px"></div>
-<div id="Galatia_Governor" class="neutral governor s1" style="left:1952px;top:930px"></div>
-<div id="Gallia_Governor" class="neutral governor s1" style="left:459px;top:506px"></div>
-<div id="Hispania_Governor" class="neutral governor s1" style="left:153px;top:979px"></div>
-<div id="Macedonia_Governor" class="neutral governor s1" style="left:1382px;top:935px"></div>
-<div id="Pannonia_Governor" class="neutral governor s1" style="left:1152px;top:625px"></div>
-<div id="Syria_Governor" class="neutral governor s1" style="left:2032px;top:1279px"></div>
-<div id="Thracia_Governor" class="neutral governor s1" style="left:1501px;top:719px"></div>
-
<div id="Aegyptus_NPG" class="no_place_governor" style="left:1793px;top:1380px"></div>
<div id="Africa_NPG" class="no_place_governor" style="left:741px;top:1204px"></div>
<div id="Britannia_NPG" class="no_place_governor" style="left:325px;top:177px"></div>
@@ -145,159 +132,33 @@
</div>
</div>
-<div id="available_panel" class="panel">
-<div id="available_header" class="panel_header">Available Leaders</div>
-<div id="available_body" class="panel_body">
-
-<div id="available_generals" class="action_row" style="gap:6px">
-<div id="red_general_0" class="red general"></div>
-<div id="red_general_1" class="red general"></div>
-<div id="red_general_2" class="red general"></div>
-<div id="red_general_3" class="red general"></div>
-<div id="red_general_4" class="red general"></div>
-<div id="red_general_5" class="red general"></div>
-<div id="blue_general_0" class="blue general"></div>
-<div id="blue_general_1" class="blue general"></div>
-<div id="blue_general_2" class="blue general"></div>
-<div id="blue_general_3" class="blue general"></div>
-<div id="blue_general_4" class="blue general"></div>
-<div id="blue_general_5" class="blue general"></div>
-<div id="yellow_general_0" class="yellow general"></div>
-<div id="yellow_general_1" class="yellow general"></div>
-<div id="yellow_general_2" class="yellow general"></div>
-<div id="yellow_general_3" class="yellow general"></div>
-<div id="yellow_general_4" class="yellow general"></div>
-<div id="yellow_general_5" class="yellow general"></div>
-<div id="green_general_0" class="green general"></div>
-<div id="green_general_1" class="green general"></div>
-<div id="green_general_2" class="green general"></div>
-<div id="green_general_3" class="green general"></div>
-<div id="green_general_4" class="green general"></div>
-<div id="green_general_5" class="green general"></div>
-</div>
-
-<div id="available_governors" class="action_row" style="gap:11px;padding-left:2px">
-<div id="red_governor_0" class="red governor"></div>
-<div id="red_governor_1" class="red governor"></div>
-<div id="red_governor_2" class="red governor"></div>
-<div id="red_governor_3" class="red governor"></div>
-<div id="red_governor_4" class="red governor"></div>
-<div id="red_governor_5" class="red governor"></div>
-<div id="blue_governor_0" class="blue governor"></div>
-<div id="blue_governor_1" class="blue governor"></div>
-<div id="blue_governor_2" class="blue governor"></div>
-<div id="blue_governor_3" class="blue governor"></div>
-<div id="blue_governor_4" class="blue governor"></div>
-<div id="blue_governor_5" class="blue governor"></div>
-<div id="yellow_governor_0" class="yellow governor"></div>
-<div id="yellow_governor_1" class="yellow governor"></div>
-<div id="yellow_governor_2" class="yellow governor"></div>
-<div id="yellow_governor_3" class="yellow governor"></div>
-<div id="yellow_governor_4" class="yellow governor"></div>
-<div id="yellow_governor_5" class="yellow governor"></div>
-<div id="green_governor_0" class="green governor"></div>
-<div id="green_governor_1" class="green governor"></div>
-<div id="green_governor_2" class="green governor"></div>
-<div id="green_governor_3" class="green governor"></div>
-<div id="green_governor_4" class="green governor"></div>
-<div id="green_governor_5" class="green governor"></div>
-</div>
-
-</div>
-</div>
-
-<div id="action_panel" class="panel">
-<div id="action_header" class="panel_header">Actions</div>
-<div id="action_body" class="panel_body">
-
-<div class="action_row">
-<button>Recruit General</button>
-<button>Add Legion to Army</button>
-<button>Create Army</button>
-<button>Train Legions</button>
-<button>Move Army</button>
-<button>Initiate Battle</button>
-<button>Disperse Mob</button>
-</div>
-
-<div class="action_row">
-<button>Recruit Governor</button>
-<button>Place Governor</button>
-<button>Recall Governor</button>
-</div>
-
-<div class="action_row">
-<button>Increase Support Level</button>
-<button>Place Militia</button>
-<button>Hold Games</button>
-<button id="build_limes">Build Limes</button>
-<button id="build_amphitheater">Build Amphitheater</button>
-<button id="build_basilica">Build Basilica</button>
-</div>
-
-<div class="action_row">
-
-<button>Castra</button>
-<button>Flanking Maneuver</button>
-<button>Praetorian Guard</button>
-<button>Tribute</button>
-<button>Foederati</button>
-<button>Damnatio Memoriae</button>
-<button>Quaestor</button>
-<button>Mob</button>
-<button>Pretender</button>
-</div>
-
-</div>
-</div>
-
<div id="played_panel" class="panel">
<div id="played_header" class="panel_header">Played / Events</div>
<div id="played" class="panel_body">
- <div class="card event_back"></div>
</div>
</div>
<div id="hand_panel" class="panel">
<div id="hand_header" class="panel_header">Hand</div>
<div id="hand" class="panel_body">
- <div class="card influence_m1"></div>
- <div class="card influence_m1"></div>
- <div class="card influence_m1"></div>
- <div class="card influence_s1"></div>
- <div class="card influence_s1"></div>
</div>
</div>
<div id="draw_panel" class="panel">
<div id="draw_header" class="panel_header">Draw</div>
<div id="draw" class="panel_body">
- <div class="card influence_s1"></div>
- <div class="card influence_p1"></div>
- <div class="card influence_p1"></div>
- <div class="card influence_p1"></div>
</div>
</div>
<div id="discard_panel" class="panel">
<div id="discard_header" class="panel_header">Discard</div>
<div id="discard" class="panel_body">
- <div class="card influence_back"></div>
</div>
</div>
<div id="market_panel" class="panel">
<div id="market_header" class="panel_header">Market</div>
<div id="market" class="panel_body">
- <div class="card influence_m2"></div>
- <div class="card influence_s2"></div>
- <div class="card influence_p2"></div>
- <div class="card influence_m3"></div>
- <div class="card influence_s3"></div>
- <div class="card influence_p3"></div>
- <div class="card influence_m4"></div>
- <div class="card influence_s4"></div>
- <div class="card influence_p4"></div>
</div>
</div>
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 is_region(where) {
+ return where < AVAILABLE
+}
+
+function is_province(where) {
+ return where < 12
}
-function setup_market_pile(cards) {
- let pile = []
- for (let c = cards[0]; c <= cards[1]; ++c)
- pile.push(c)
- return pile
+function is_sea(where) {
+ return where >= MARE_OCCIDENTALE && where <= PONTUS_EUXINUS
}
-function setup_barbarians(tribe) {
- for (let i = BARBARIAN_UNITS[tribe][0]; i <= BARBARIAN_UNITS[tribe][1]; ++i)
- game.barbarians[i] = BARBARIAN_HOMELAND[tribe]
+function current_hand() { return game.hand[game.current] }
+function current_draw() { return game.draw[game.current] }
+function current_discard() { return game.discard[game.current] }
+
+// === BOARD STATE ===
+
+function is_no_place_governor(province) {
+ return province >= game.support.length
}
-function remove_barbarians(tribe) {
- for (let i = BARBARIAN_UNITS[tribe][0]; i <= BARBARIAN_UNITS[tribe][1]; ++i)
- game.barbarians[i] = OFF_MAP
+function get_support(province) { return game.support[province] }
+function set_support(province, level) { game.support[province] = level }
+
+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 }
+
+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 }
+
+function is_legion_unused(ix) {
+ return game.legions[ix] === AVAILABLE
}
-exports.setup = function (seed, scenario, options) {
- let player_count = options.players || 4
+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 }
- 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: [],
- }
+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.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 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) }
- setup_barbarians(ALAMANNI)
- setup_barbarians(FRANKS)
- setup_barbarians(GOTHS)
- setup_barbarians(NOMADS)
- setup_barbarians(SASSANIDS)
+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) }
- if (player_count === 1) {
- game.solo = 1
- player_count = 4
- }
+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) }
- 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 has_militia(province) { return game.militia & (1 << province) }
+function add_militia(province) { game.militia |= (1 << province) }
+function remove_militia(province) { game.militia &= ~(1 << province) }
- if (player_count === 4) {
- game.support[ITALIA] = 8
- }
+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 }
- 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 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) }
- 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 is_breakaway(province) { return game.breakaway & (1 << province) }
+function add_breakaway(province) { game.breakaway |= (1 << province) }
+function remove_breakaway(province) { game.breakaway &= ~(1 << province) }
- game.first = game.current = random(player_count)
- log(PLAYER_NAMES[game.first] + " is First Player!")
+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) }
- return save_game()
+// === TRANSIENT STATE ===
+
+function has_placed_governor(province) { return game.placed & (1 << province) }
+function set_placed_governor(province) { game.placed |= (1 << province) }
+
+function has_general_battled(id) { return game.battled & (1 << id) }
+function set_general_battled(id) { game.battled |= (1 << id) }
+
+function has_militia_battled(province) { return game.mbattled & (1 << province) }
+function set_militia_battled(province) { game.mbattled |= (1 << province) }
+
+// === COMPOUND STATE ===
+
+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))
}
-function load_game(state) {
- game = state
+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 save_game() {
- if (game.solo)
- game.active = "Solo"
- else
- game.active = PLAYER_NAMES[game.current]
- return game
+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))
}
-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 for_each_governor(f) {
+ let n = game.legacy.length * 6
+ for (let id = 0; id < n; ++id)
+ f(id, get_governor_location(id))
}
-function is_current_player(player) {
- if (player === 4)
- return true
- return game.current === player
+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
}
-exports.view = function (state, player_name) {
- let player = PLAYER_INDEX[player_name]
+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
+}
- load_game(state)
+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
+}
- view = {
- log: game.log,
- current: game.current,
- prompt: null,
+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
+}
- 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 next_player() {
+ return (game.current + 1) % game.legacy.length
+}
- 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 find_unused_legion() {
+ for (let ix = 0; ix < LEGION_COUNT; ++ix)
+ if (get_legion_location(ix) === AVAILABLE)
+ return ix
+ return -1
+}
- 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 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
+}
- 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
+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
}
- save_game()
- return view
+ return true
}
-// === MISC ===
+function get_province_governor(where) {
+ return find_governor((id, loc) => loc === where)
+}
-function log(msg) {
- game.log.push(msg)
+function get_capital_general(where) {
+ return find_general((id, loc, cap) => loc === where && cap)
}
-function log_br() {
- if (game.log.length > 0 && game.log[game.log.length - 1] !== "")
- game.log.push("")
+function is_neutral_province(where) {
+ if (is_no_place_governor(where))
+ return false
+ return get_province_governor(where) < 0
}
-function log_h1(msg) {
- log_br()
- log(".h1 " + msg)
- log_br()
+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 log_h2(msg) {
- log_br()
- log(".h2 " + msg)
- log_br()
+function has_active_barbarians(where) {
+ return false // TODO
}
-function logi(msg) {
- game.log.push(">" + msg)
+function has_rival_emperor(where) {
+ return false // TODO
}
-function logii(msg) {
- game.log.push(">>" + msg)
+function has_enemy_army_in_capital(where) {
+ return false // TODO
}
-// === STATES ===
+function spend_ip(type, n) {
+ game.ip[type] -= n
+}
-function next_player() {
- return (game.current + 1) % game.players.length
+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 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 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")