summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-10-02 17:33:01 +0200
committerTor Andersson <tor@ccxvii.net>2022-11-30 13:26:51 +0100
commit11016ba45398d98a02e063de65e1e1fa9d73a58c (patch)
tree284ff3715ac5346067f504d9297b964f4c56b688
parent04bd6f688a047ccaa804c4e32f64405873b2f49b (diff)
downloadcrusader-rex-11016ba45398d98a02e063de65e1e1fa9d73a58c.tar.gz
Optimize game state representation using numbers instead of string ids.
-rw-r--r--data.js837
-rw-r--r--play.css16
-rw-r--r--play.js902
-rw-r--r--rules.js863
4 files changed, 1436 insertions, 1182 deletions
diff --git a/data.js b/data.js
index 1c6ffe3..a417a9c 100644
--- a/data.js
+++ b/data.js
@@ -1,4 +1,4 @@
-"use strict";
+"use strict"
const CARDS = {
1: { name: "Assassins", event: "assassins", image: "card_assassins" },
@@ -28,418 +28,479 @@ const CARDS = {
25: { name: "a 1", moves: 1, image: "card_1" },
26: { name: "a 1", moves: 1, image: "card_1" },
27: { name: "a 1", moves: 1, image: "card_1" },
-};
-
-const BLOCKS = {};
-const ROADS = {};
-
-const SHIELDS = {
- Antioch: [ "Bohemond", "Templars", "Turcopoles" ],
- Latakia: [ "Bohemond" ],
- "Sa\xf4ne": [ "Josselin" ],
- Margat: [ "Hospitallers" ],
- Krak: [ "Hospitallers" ],
- Tartus: [ "Templars" ],
- Tripoli: [ "Bohemond", "Raymond" ],
- Beirut: [ "Turcopoles", "King Guy" ],
- Sidon: [ "Reynald (Sidon)" ],
- Beaufort: [ "Reynald (Sidon)" ],
- Tyre: [ "Conrad", "King Guy" ],
- Acre: [ "Turcopoles", "Hospitallers", "King Guy" ],
- Tiberias: [ "Turcopoles", "Raymond" ],
- Baisan: [ "Hospitallers" ],
- Caesarea: [ "Walter" ],
- Nablus: [ "Balian" ],
- Amman: [ "Templars" ],
- Jaffa: [ "King Guy" ],
- Jerusalem: [ "King Guy", "Hospitallers", "Templars" ],
- Ascalon: [ "Balian", "King Guy" ],
- Hebron: [ "King Guy" ],
- Gaza: [ "Templars" ],
- Kerak: [ "Reynald (Kerak)" ],
- Egypt: [ "Saladin", "Qara-Qush", "Yuzpah" ],
- Aleppo: [ "Saladin", "Sanjar", "Zangi" ],
- Ashtera: [ "Yazkuj" ],
- Artah: [ "Sulaiman" ],
- Damascus: [ "Saladin", "Keukburi", "Al Mashtub" ],
- Homs: [ "Tuman", "Shirkuh" ],
- Zerdana: [ "Jurdik" ],
- Baalbek: [ "Bahram" ],
- Hama: [ "Taqi al Din" ],
- Banyas: [ "Qaimaz" ]
}
-// From edit.html output
-const TOWNS = {
- "Acre":{"x":452,"y":1566},
- "Ajlun":{"x":987,"y":1542},
- "Albara":{"x":810,"y":388},
- "Aleppo":{"x":1051,"y":108},
- "Amman":{"x":1088,"y":1838},
- "Anjar":{"x":753,"y":1129},
- "Antioch":{"x":471,"y":189},
- "Artah":{"x":865,"y":149},
- "Ascalon":{"x":365,"y":2077},
- "Ashtera":{"x":1038,"y":1419},
- "Baalbek":{"x":842,"y":1008},
- "Baisan":{"x":707,"y":1685},
- "Banyas":{"x":764,"y":1362},
- "Beaufort":{"x":605,"y":1354},
- "Beersheba":{"x":444,"y":2283},
- "Beirut":{"x":527,"y":1137},
- "Botron":{"x":540,"y":995},
- "Caesarea":{"x":402,"y":1754},
- "Damascus":{"x":1059,"y":1185},
- "Damiya":{"x":847,"y":1808},
- "Dimona":{"x":630,"y":2294},
- "Egypt":{"x":202,"y":2318},
- "Gaza":{"x":300,"y":2185},
- "Hama":{"x":1035,"y":477},
- "Harim":{"x":700,"y":120},
- "Hebron":{"x":680,"y":2115},
- "Homs":{"x":1053,"y":683},
- "Jaffa":{"x":399,"y":1923},
- "Jericho":{"x":836,"y":1931},
- "Jerusalem":{"x":680,"y":1980},
- "Kassab":{"x":426,"y":339},
- "Kerak":{"x":1008,"y":2076},
- "Krak":{"x":774,"y":726},
- "Lachish":{"x":495,"y":2148},
- "Lacum":{"x":919,"y":885},
- "Latakia":{"x":401,"y":445},
- "Legio":{"x":587,"y":1658},
- "Margat":{"x":540,"y":567},
- "Masyaf":{"x":756,"y":608},
- "Monterrand":{"x":920,"y":603},
- "Nablus":{"x":643,"y":1787},
- "Qaddas":{"x":1145,"y":916},
- "Ramallah":{"x":514,"y":1950},
- "Sa\xf4ne":{"x":650,"y":430},
- "Shughur":{"x":655,"y":300},
- "Sidon":{"x":493,"y":1276},
- "St. Simeon":{"x":364,"y":211},
- "Tartus":{"x":605,"y":718},
- "Tiberias":{"x":699,"y":1560},
- "Tripoli":{"x":621,"y":882},
- "Tyre":{"x":465,"y":1397},
- "Zerdana":{"x":1021,"y":300},
- "Zoar":{"x":955,"y":2278},
- "Germania":{"x":140,"y":272},
- "France":{"x":140,"y":573},
- "England":{"x":140,"y":873},
- "Sea":{"x":320,"y":900},
- "FP":{"x":15,"y":946},
- "SP":{"x":1275-15,"y":946},
- "Dead":{"x":50,"y":80},
-};
-
-const PORTS = [];
-
-(function () {
+const BLOCKS = []
+const block_index = {}
+
+const TOWNS = []
+const town_index = {}
+
+const ROADS = {}
+const PORTS = []
+const SHIELDS = []
+
+function init_towns() {
+
+ // From edit.html output
+ const TOWNS_XY = {
+ "Acre":{"x":452,"y":1566},
+ "Ajlun":{"x":987,"y":1542},
+ "Albara":{"x":810,"y":388},
+ "Aleppo":{"x":1051,"y":108},
+ "Amman":{"x":1088,"y":1838},
+ "Anjar":{"x":753,"y":1129},
+ "Antioch":{"x":471,"y":189},
+ "Artah":{"x":865,"y":149},
+ "Ascalon":{"x":365,"y":2077},
+ "Ashtera":{"x":1038,"y":1419},
+ "Baalbek":{"x":842,"y":1008},
+ "Baisan":{"x":707,"y":1685},
+ "Banyas":{"x":764,"y":1362},
+ "Beaufort":{"x":605,"y":1354},
+ "Beersheba":{"x":444,"y":2283},
+ "Beirut":{"x":527,"y":1137},
+ "Botron":{"x":540,"y":995},
+ "Caesarea":{"x":402,"y":1754},
+ "Damascus":{"x":1059,"y":1185},
+ "Damiya":{"x":847,"y":1808},
+ "Dimona":{"x":630,"y":2294},
+ "Egypt":{"x":202,"y":2318},
+ "Gaza":{"x":300,"y":2185},
+ "Hama":{"x":1035,"y":477},
+ "Harim":{"x":700,"y":120},
+ "Hebron":{"x":680,"y":2115},
+ "Homs":{"x":1053,"y":683},
+ "Jaffa":{"x":399,"y":1923},
+ "Jericho":{"x":836,"y":1931},
+ "Jerusalem":{"x":680,"y":1980},
+ "Kassab":{"x":426,"y":339},
+ "Kerak":{"x":1008,"y":2076},
+ "Krak":{"x":774,"y":726},
+ "Lachish":{"x":495,"y":2148},
+ "Lacum":{"x":919,"y":885},
+ "Latakia":{"x":401,"y":445},
+ "Legio":{"x":587,"y":1658},
+ "Margat":{"x":540,"y":567},
+ "Masyaf":{"x":756,"y":608},
+ "Monterrand":{"x":920,"y":603},
+ "Nablus":{"x":643,"y":1787},
+ "Qaddas":{"x":1145,"y":916},
+ "Ramallah":{"x":514,"y":1950},
+ "Sa\xf4ne":{"x":650,"y":430},
+ "Shughur":{"x":655,"y":300},
+ "Sidon":{"x":493,"y":1276},
+ "St. Simeon":{"x":364,"y":211},
+ "Tartus":{"x":605,"y":718},
+ "Tiberias":{"x":699,"y":1560},
+ "Tripoli":{"x":621,"y":882},
+ "Tyre":{"x":465,"y":1397},
+ "Zerdana":{"x":1021,"y":300},
+ "Zoar":{"x":955,"y":2278},
+ "Germania":{"x":140,"y":272},
+ "France":{"x":140,"y":573},
+ "England":{"x":140,"y":873},
+ "Sea":{"x":320,"y":900},
+ "FP":{"x":15,"y":946},
+ "SP":{"x":1275-15,"y":946},
+ "Dead":{"x":50,"y":80},
+ "Nowhere":{"x":50,"y":80},
+ }
+
+ function town(axis, major_align, minor_align, wrap, region, name, rating, type) {
+ let i = town_index[name] = TOWNS.length
+ TOWNS.push({
+ name,
+ region,
+ type,
+ rating,
+ port: (type === 'port' || type === 'fortified-port'),
+ fortified_port: (type === 'fortified-port'),
+ exits: [],
+ layout: {
+ x: TOWNS_XY[name].x,
+ y: TOWNS_XY[name].y,
+ axis,
+ major: 1 - major_align,
+ minor: 1 - minor_align,
+ wrap
+ }
+ })
+ if (type === 'port' || type === 'fortified-port')
+ PORTS.push(i)
+ }
+
+ town('Y', 1.0, 1.0, 50, "Pool", "Nowhere", 0, "pool")
+ town('Y', 1.0, 1.0, 3, "Pool", "Dead", 0, "pool")
+ town('Y', 0.5, 1.0, 50, "Pool", "FP", 0, "pool")
+ town('Y', 0.5, 1.0, 50, "Pool", "SP", 0, "pool")
+ town('Y', 1.0, 1.0, 1, "Pool", "Sea", 0, "pool")
+
+ town('Y', 1.0, 0.5, 3, "Staging", "England", 3, "staging")
+ town('Y', 1.0, 0.5, 3, "Staging", "France", 3, "staging")
+ town('Y', 1.0, 0.5, 3, "Staging", "Germania", 3, "staging")
+
+ town('X', 1.0, 0.5, 3, "Syria", "Aleppo", 3, "town")
+ town('Y', 0.5, 0.5, 3, "Syria", "Artah", 1, "town")
+ town('X', 1.0, 0.5, 3, "Syria", "Zerdana", 1, "town")
+ town('X', 1.0, 0.5, 3, "Syria", "Hama", 1, "town")
+ town('X', 0.9, 0.5, 3, "Syria", "Homs", 2, "town")
+ town('X', 0.3, 0.5, 3, "Syria", "Lacum", 0, "town")
+ town('X', 0.3, 0.5, 3, "Syria", "Qaddas", 0, "town")
+ town('X', 0.5, 1.0, 3, "Syria", "Baalbek", 1, "town")
+ town('X', 0.5, 1.0, 3, "Syria", "Anjar", 0, "town")
+ town('X', 0.5, 0.5, 4, "Syria", "Damascus", 4, "town")
+ town('X', 1.0, 0.5, 3, "Syria", "Banyas", 1, "town")
+ town('X', 1.0, 0.5, 3, "Syria", "Ashtera", 1, "town")
+ town('X', 1.0, 0.5, 3, "Syria", "Ajlun", 0, "town")
+
+ town('X', 0.0, 0.5, 3, "Antioch", "St. Simeon", 0, "port")
+ town('Y', 0.5, 0.5, 3, "Antioch", "Antioch", 3, "town")
+ town('Y', 0.5, 0.5, 3, "Antioch", "Harim", 0, "town")
+ town('X', 0.5, 0.5, 3, "Antioch", "Kassab", 0, "town")
+ town('X', 0.5, 0.5, 3, "Antioch", "Shughur", 0, "town")
+ town('X', 0.0, 0.5, 3, "Antioch", "Latakia", 1, "port")
+ town('X', 0.5, 0.5, 3, "Antioch", "Sa\xf4ne", 1, "town")
+ town('Y', 0.5, 0.5, 3, "Antioch", "Albara", 0, "town")
+ town('X', 0.0, 0.5, 3, "Antioch", "Margat", 1, "port")
+
+ town('X', 0.5, 0.5, 1, "Masyaf", "Masyaf", 1, "town")
+
+ town('Y', 0.5, 0.5, 3, "Tripoli", "Monterrand", 0, "town")
+ town('X', 0.0, 0.5, 3, "Tripoli", "Tartus", 1, "port")
+ town('X', 1.0, 0.5, 3, "Tripoli", "Krak", 1, "town")
+ town('X', 0.0, 0.5, 3, "Tripoli", "Tripoli", 2, "fortified-port")
+ town('X', 0.0, 0.5, 3, "Tripoli", "Botron", 0, "town")
+
+ town('X', 0.0, 0.5, 3, "Jerusalem", "Beirut", 2, "port")
+ town('X', 0.0, 0.5, 3, "Jerusalem", "Sidon", 1, "port")
+ town('X', 0.0, 0.5, 3, "Jerusalem", "Tyre", 2, "fortified-port")
+ town('Y', 0.5, 0.5, 3, "Jerusalem", "Beaufort", 1, "town")
+ town('X', 0.0, 0.5, 3, "Jerusalem", "Acre", 3, "port")
+ town('X', 1.0, 0.5, 3, "Jerusalem", "Tiberias", 2, "town")
+ town('Y', 1.0, 0.5, 3, "Jerusalem", "Legio", 0, "town")
+ town('X', 1.0, 0.5, 3, "Jerusalem", "Baisan", 1, "town")
+ town('X', 0.0, 0.5, 3, "Jerusalem", "Caesarea", 1, "port")
+ town('X', 0.5, 0.5, 3, "Jerusalem", "Nablus", 1, "town")
+ town('X', 0.5, 0.5, 3, "Jerusalem", "Damiya", 0, "town")
+ town('X', 0.5, 0.5, 3, "Jerusalem", "Amman", 1, "town")
+ town('X', 0.0, 0.5, 3, "Jerusalem", "Jaffa", 1, "port")
+ town('Y', 0.5, 0.5, 3, "Jerusalem", "Ramallah", 0, "town")
+ town('X', 0.5, 0.4, 3, "Jerusalem", "Jerusalem", 3, "town")
+ town('Y', 0.5, 0.5, 3, "Jerusalem", "Jericho", 0, "town")
+ town('X', 0.1, 0.5, 6, "Jerusalem", "Ascalon", 2, "port")
+ town('Y', 0.5, 0.5, 3, "Jerusalem", "Lachish", 0, "town")
+ town('X', 0.5, 1.0, 3, "Jerusalem", "Hebron", 1, "town")
+ town('X', 1.0, 0.5, 3, "Jerusalem", "Kerak", 1, "town")
+ town('X', 0.5, 0.5, 6, "Jerusalem", "Gaza", 1, "town")
+ town('Y', 0.5, 0.5, 3, "Jerusalem", "Beersheba", 0, "town")
+ town('X', 0.5, 0.5, 3, "Jerusalem", "Dimona", 0, "town")
+ town('X', 1.0, 0.5, 3, "Jerusalem", "Zoar", 0, "town")
+
+ town('X', 0.5, 0.5, 4, "Egypt", "Egypt", 4, "port")
+}
+
+function init_roads() {
+ function road(a,b,type) {
+ a = town_index[a]
+ b = town_index[b]
+ let id = (a < b) ? a * 100 + b : b * 100 + a
+ ROADS[id] = type
+ TOWNS[a].exits.push(b)
+ TOWNS[b].exits.push(a)
+ }
+
+ function offmap(a,b,type) {
+ a = town_index[a]
+ b = town_index[b]
+ let id = (a < b) ? a * 100 + b : b * 100 + a
+ ROADS[id] = type
+ }
+
+ function iron_bridge(A,B) { road(A,B,"iron-bridge"); }
+ function major(A,B) { road(A,B,"major"); }
+ function minor(A,B) { road(A,B,"minor"); }
+
+ iron_bridge("Antioch", "Harim")
+ major("Harim", "Artah")
+ major("Artah", "Aleppo")
+ major("Aleppo", "Zerdana")
+ major("Zerdana", "Hama")
+ major("Hama", "Albara")
+ major("Hama", "Monterrand")
+ major("Hama", "Homs")
+ major("Albara", "Shughur")
+ major("Shughur", "Harim")
+ major("Monterrand", "Krak")
+ major("Krak", "Homs")
+ major("Krak", "Tripoli")
+ major("Tripoli", "Tartus")
+ major("Tripoli", "Botron")
+ major("Tartus", "Margat")
+ major("Margat", "Latakia")
+ major("Botron", "Beirut")
+ major("Beirut", "Sidon")
+ major("Sidon", "Tyre")
+ major("Tyre", "Beaufort")
+ major("Beaufort", "Banyas")
+ major("Banyas", "Damascus")
+ major("Damascus", "Qaddas")
+ major("Qaddas", "Homs")
+ major("Homs", "Lacum")
+ major("Lacum", "Baalbek")
+ major("Baalbek", "Anjar")
+ major("Anjar", "Beaufort")
+ major("Damascus", "Ashtera")
+ major("Ashtera", "Ajlun")
+ major("Ajlun", "Amman")
+ major("Amman", "Kerak")
+ major("Kerak", "Zoar")
+ major("Zoar", "Hebron")
+ major("Hebron", "Jerusalem")
+ major("Jerusalem", "Ramallah")
+ major("Ramallah", "Jaffa")
+ major("Jaffa", "Ascalon")
+ major("Ascalon", "Gaza")
+ major("Gaza", "Egypt")
+ major("Ajlun", "Tiberias")
+ major("Tiberias", "Acre")
+ major("Acre", "Legio")
+ major("Legio", "Baisan")
+ major("Baisan", "Tiberias")
+ major("Baisan", "Nablus")
+ major("Nablus", "Legio")
+ major("Nablus", "Jerusalem")
+ major("Acre", "Caesarea")
+ major("Caesarea", "Jaffa")
+
+ minor("St. Simeon", "Antioch")
+ minor("Antioch", "Kassab")
+ minor("Kassab", "Latakia")
+ minor("Latakia", "Sa\xf4ne")
+ minor("Sa\xf4ne", "Shughur")
+ minor("Sa\xf4ne", "Albara")
+ minor("Albara", "Zerdana")
+ minor("Zerdana", "Artah")
+
+ minor("Monterrand", "Homs")
+
+ minor("Tartus", "Krak")
+ minor("Krak", "Lacum")
+ minor("Lacum", "Qaddas")
+ minor("Tripoli", "Baalbek")
+ minor("Beirut", "Anjar")
+ minor("Anjar", "Damascus")
+ minor("Sidon", "Beaufort")
+ minor("Tiberias", "Banyas")
+ minor("Banyas", "Ashtera")
+ minor("Tyre", "Acre")
+ minor("Caesarea", "Nablus")
+ minor("Nablus", "Damiya")
+ minor("Damiya", "Baisan")
+ minor("Damiya", "Amman")
+ minor("Amman", "Jericho")
+ minor("Jericho", "Damiya")
+ minor("Jericho", "Kerak")
+ minor("Jericho", "Jerusalem")
+
+ minor("Ramallah", "Ascalon")
+ minor("Ascalon", "Lachish")
+ minor("Lachish", "Gaza")
+ minor("Gaza", "Beersheba")
+ minor("Beersheba", "Egypt")
+ minor("Beersheba", "Dimona")
+ minor("Dimona", "Zoar")
+ minor("Dimona", "Hebron")
+ minor("Hebron", "Lachish")
+
+ offmap("Germania", "St. Simeon", 'minor')
+ offmap("Germania", "Aleppo", 'major')
+ offmap("Germania", "Antioch", 'major')
+
+ for (let town of TOWNS)
+ town.exits.sort((a,b)=>a-b)
+
+}
+
+function init_blocks() {
let nomads = { Arabs: 1, Turks: 1, Kurds: 1 }
function army(rc, owner, name, home, move, steps, combat, order, plural) {
- let id = name;
+ let id = name
if (order === 'Military Orders' || order === 'Pilgrims' || order === 'Turcopoles')
- id = home + " " + name;
+ id = home + " " + name
if (order === 'Nomads')
- id += " " + nomads[name]++;
+ id += " " + nomads[name]++
if (name === 'Reynald' || name === 'Raymond')
- id += " (" + home + ")";
- if (id in BLOCKS)
- throw Error("Name clash: " + id + " order:"+order + " " + JSON.stringify(nomads));
- BLOCKS[id] = {
+ id += " (" + home + ")"
+ if (id in block_index)
+ throw Error("Name clash: " + id + " order:"+order + " " + JSON.stringify(nomads))
+
+ let home_idx = town_index[home] | 0
+ if (home === "Normandy") home_idx = town_index["England"]
+ if (home === "Aquitaine") home_idx = town_index["England"]
+ if (home === "Bourgogne") home_idx = town_index["France"]
+ if (home === "Flanders") home_idx = town_index["France"]
+
+ block_index[id] = BLOCKS.length
+ BLOCKS.push({
+ id: id,
owner: owner,
name: name,
plural: plural,
type: order.toLowerCase().replace(/ /g, "_"),
- home: home,
+ home: home_idx,
+ home_name: home,
move: move,
steps: steps,
- combat: combat,
+ initiative: combat[0],
+ fire_power: combat[1] | 0,
image: rc,
- }
+ })
}
function frank(rc, name, home, move, steps, combat, order, plural) {
- army(rc, "Franks", name, home, move, steps, combat, order, plural);
+ army(rc, "Franks", name, home, move, steps, combat, order, plural)
}
+
function saracen(rc, name, home, move, steps, combat, order, plural) {
- army(rc, "Saracens", name, home, move, steps, combat, order, plural);
+ army(rc, "Saracens", name, home, move, steps, combat, order, plural)
}
+ frank(11, "Richard", "England", 3, 4, "B4", "Crusaders", 0)
+ frank(12, "Philippe", "France", 2, 4, "B3", "Crusaders", 0)
+ frank(13, "Barbarossa", "Germania", 2, 4, "B3", "Crusaders", 0)
+ frank(14, "Templars", "Jerusalem", 3, 3, "B3", "Military Orders", 1)
+ frank(15, "Templars", "Antioch", 3, 3, "B3", "Military Orders", 1)
+ frank(16, "Templars", "Gaza", 3, 3, "B3", "Military Orders", 1)
+ frank(17, "Templars", "Tartus", 3, 2, "B3", "Military Orders", 1)
+
+ frank(21, "Robert", "Normandy", 2, 3, "B3", "Crusaders", 0)
+ frank(22, "Hugues", "Bourgogne", 2, 4, "B2", "Crusaders", 0)
+ frank(23, "Frederik", "Germania", 2, 3, "B2", "Crusaders", 0)
+ frank(24, "Hospitallers", "Jerusalem", 3, 4, "B3", "Military Orders", 1)
+ frank(25, "Hospitallers", "Acre", 3, 3, "B3", "Military Orders", 1)
+ frank(26, "Hospitallers", "Krak", 3, 2, "B3", "Military Orders", 1)
+ frank(27, "Reynald", "Sidon", 2, 3, "B2", "Outremers", 0)
+
+ frank(31, "Crossbows", "Aquitaine", 2, 3, "A2", "Crusaders", 1)
+ frank(32, "Fileps", "Flanders", 2, 3, "B3", "Crusaders", 0)
+ frank(33, "Leopold", "Germania", 2, 3, "B3", "Crusaders", 0)
+ frank(34, "Conrad", "Tyre", 2, 4, "B3", "Outremers", 0)
+ frank(35, "Balian", "Nablus", 2, 3, "B2", "Outremers", 0)
+ frank(36, "Walter", "Caesarea", 2, 3, "B2", "Outremers", 0)
+ frank(37, "Raymond", "Tiberias", 2, 3, "B2", "Outremers", 0)
+
+ frank(41, "Turcopoles", "Antioch", 3, 3, "A2", "Turcopoles", 1)
+ frank(42, "Pilgrims", "Genoa", 2, 4, "C2", "Pilgrims", 1)
+ frank(43, "Pilgrims", "Sicily", 2, 3, "C2", "Pilgrims", 1)
+ frank(44, "King Guy", "Jerusalem", 2, 4, "B2", "Outremers", 0)
+ frank(45, "Reynald", "Kerak", 3, 2, "B3", "Outremers", 0)
+ frank(46, "Bohemond", "Antioch", 2, 4, "B2", "Outremers", 0)
+ frank(47, "Raymond", "Tripoli", 2, 4, "B2", "Outremers", 0)
+
+ frank(51, "Turcopoles", "Beirut", 3, 3, "A2", "Turcopoles", 1)
+ frank(52, "Pilgrims", "Brittany", 2, 4, "C2", "Pilgrims", 1)
+ frank(53, "Josselin", "Sa\xf4ne", 2, 3, "B2", "Outremers", 0)
+
+ saracen(55, "Qara-Qush", "Egypt", 3, 3, "B3", "Emirs", 0)
+ saracen(56, "Zangi", "Aleppo", 3, 3, "B2", "Emirs", 0)
+ saracen(57, "Sanjar", "Aleppo", 3, 3, "B2", "Emirs", 0)
+
+ saracen(61, "Yazkuj", "Ashtera", 3, 2, "B2", "Emirs", 0)
+ saracen(62, "Sulaiman", "Artah", 3, 2, "B2", "Emirs", 0)
+ saracen(63, "Keukburi", "Damascus", 3, 3, "B3", "Emirs", 0)
+ saracen(64, "Shirkuh", "Homs", 3, 3, "B2", "Emirs", 0)
+ saracen(65, "Jurdik", "Zerdana", 3, 3, "B2", "Emirs", 0)
+ saracen(66, "Bahram", "Baalbek", 3, 3, "B2", "Emirs", 0)
+ saracen(67, "Tuman", "Homs", 3, 3, "B3", "Emirs", 0)
+
+ saracen(71, "Taqi al Din", "Hama", 3, 4, "A2", "Emirs", 0)
+ saracen(72, "Al Mashtub", "Damascus", 3, 4, "B3", "Emirs", 0)
+ saracen(73, "Al Adil", "Egypt", 3, 4, "A2", "Emirs", 0)
+ saracen(74, "Saladin", "Damascus", 3, 4, "A3", "Emirs", 0)
+ saracen(75, "Al Aziz", "Egypt", 3, 3, "B2", "Emirs", 0)
+ saracen(76, "Al Afdal", "Damascus", 3, 3, "B3", "Emirs", 0)
+ saracen(77, "Al Zahir", "Aleppo", 3, 3, "A2", "Emirs", 0)
+
+ saracen(81, "Yuzpah", "Egypt", 3, 4, "B2", "Emirs", 0)
+ saracen(82, "Qaimaz", "Banyas", 3, 3, "B2", "Emirs", 0)
+
+ saracen(83, "Kurds", "Damascus", 3, 4, "C1", "Nomads", 1)
+ saracen(84, "Kurds", "Damascus", 3, 4, "C1", "Nomads", 1)
+ saracen(85, "Kurds", "Damascus", 3, 3, "C2", "Nomads", 1)
+ saracen(86, "Kurds", "Damascus", 3, 3, "C2", "Nomads", 1)
+
+ saracen(91, "Turks", "Aleppo", 3, 3, "A2", "Nomads", 1)
+ saracen(92, "Turks", "Aleppo", 3, 3, "A2", "Nomads", 1)
+ saracen(93, "Turks", "Aleppo", 3, 4, "A1", "Nomads", 1)
+ saracen(94, "Turks", "Aleppo", 3, 4, "A1", "Nomads", 1)
+
+ saracen(95, "Arabs", "Egypt", 3, 3, "B2", "Nomads", 1)
+ saracen(96, "Arabs", "Egypt", 3, 3, "B2", "Nomads", 1)
+ saracen(97, "Arabs", "Egypt", 3, 4, "B1", "Nomads", 1)
+ saracen(87, "Arabs", "Egypt", 3, 4, "B1", "Nomads", 1)
+
+ // The assassins are not a real unit
+ army(54, "Assassins", "Assassins", "Masyaf", 0, 3, "A3", "Assassins", 1)
+}
- frank(11, "Richard", "England", 3, 4, "B4", "Crusaders", 0);
- frank(12, "Philippe", "France", 2, 4, "B3", "Crusaders", 0);
- frank(13, "Barbarossa", "Germania", 2, 4, "B3", "Crusaders", 0);
- frank(14, "Templars", "Jerusalem", 3, 3, "B3", "Military Orders", 1);
- frank(15, "Templars", "Antioch", 3, 3, "B3", "Military Orders", 1);
- frank(16, "Templars", "Gaza", 3, 3, "B3", "Military Orders", 1);
- frank(17, "Templars", "Tartus", 3, 2, "B3", "Military Orders", 1);
-
- frank(21, "Robert", "Normandy", 2, 3, "B3", "Crusaders", 0);
- frank(22, "Hugues", "Bourgogne", 2, 4, "B2", "Crusaders", 0);
- frank(23, "Frederik", "Germania", 2, 3, "B2", "Crusaders", 0);
- frank(24, "Hospitallers", "Jerusalem", 3, 4, "B3", "Military Orders", 1);
- frank(25, "Hospitallers", "Acre", 3, 3, "B3", "Military Orders", 1);
- frank(26, "Hospitallers", "Krak", 3, 2, "B3", "Military Orders", 1);
- frank(27, "Reynald", "Sidon", 2, 3, "B2", "Outremers", 0);
-
- frank(31, "Crossbows", "Aquitaine", 2, 3, "A2", "Crusaders", 1);
- frank(32, "Fileps", "Flanders", 2, 3, "B3", "Crusaders", 0);
- frank(33, "Leopold", "Germania", 2, 3, "B3", "Crusaders", 0);
- frank(34, "Conrad", "Tyre", 2, 4, "B3", "Outremers", 0);
- frank(35, "Balian", "Nablus", 2, 3, "B2", "Outremers", 0);
- frank(36, "Walter", "Caesarea", 2, 3, "B2", "Outremers", 0);
- frank(37, "Raymond", "Tiberias", 2, 3, "B2", "Outremers", 0);
-
- frank(41, "Turcopoles", "Antioch", 3, 3, "A2", "Turcopoles", 1);
- frank(42, "Pilgrims", "Genoa", 2, 4, "C2", "Pilgrims", 1);
- frank(43, "Pilgrims", "Sicily", 2, 3, "C2", "Pilgrims", 1);
- frank(44, "King Guy", "Jerusalem", 2, 4, "B2", "Outremers", 0);
- frank(45, "Reynald", "Kerak", 3, 2, "B3", "Outremers", 0);
- frank(46, "Bohemond", "Antioch", 2, 4, "B2", "Outremers", 0);
- frank(47, "Raymond", "Tripoli", 2, 4, "B2", "Outremers", 0);
-
- frank(51, "Turcopoles", "Beirut", 3, 3, "A2", "Turcopoles", 1);
- frank(52, "Pilgrims", "Brittany", 2, 4, "C2", "Pilgrims", 1);
- frank(53, "Josselin", "Sa\xf4ne", 2, 3, "B2", "Outremers", 0);
-
- army(54, "Assassins", "Assassins", "Masyaf", 0, 3, "A3", "Assassins", 1);
-
- saracen(55, "Qara-Qush", "Egypt", 3, 3, "B3", "Emirs", 0);
- saracen(56, "Zangi", "Aleppo", 3, 3, "B2", "Emirs", 0);
- saracen(57, "Sanjar", "Aleppo", 3, 3, "B2", "Emirs", 0);
-
- saracen(61, "Yazkuj", "Ashtera", 3, 2, "B2", "Emirs", 0);
- saracen(62, "Sulaiman", "Artah", 3, 2, "B2", "Emirs", 0);
- saracen(63, "Keukburi", "Damascus", 3, 3, "B3", "Emirs", 0);
- saracen(64, "Shirkuh", "Homs", 3, 3, "B2", "Emirs", 0);
- saracen(65, "Jurdik", "Zerdana", 3, 3, "B2", "Emirs", 0);
- saracen(66, "Bahram", "Baalbek", 3, 3, "B2", "Emirs", 0);
- saracen(67, "Tuman", "Homs", 3, 3, "B3", "Emirs", 0);
-
- saracen(71, "Taqi al Din", "Hama", 3, 4, "A2", "Emirs", 0);
- saracen(72, "Al Mashtub", "Damascus", 3, 4, "B3", "Emirs", 0);
- saracen(73, "Al Adil", "Egypt", 3, 4, "A2", "Emirs", 0);
- saracen(74, "Saladin", "Damascus", 3, 4, "A3", "Emirs", 0);
- saracen(75, "Al Aziz", "Egypt", 3, 3, "B2", "Emirs", 0);
- saracen(76, "Al Afdal", "Damascus", 3, 3, "B3", "Emirs", 0);
- saracen(77, "Al Zahir", "Aleppo", 3, 3, "A2", "Emirs", 0);
-
- saracen(81, "Yuzpah", "Egypt", 3, 4, "B2", "Emirs", 0);
- saracen(82, "Qaimaz", "Banyas", 3, 3, "B2", "Emirs", 0);
-
- saracen(83, "Kurds", "Damascus", 3, 4, "C1", "Nomads", 1);
- saracen(84, "Kurds", "Damascus", 3, 4, "C1", "Nomads", 1);
- saracen(85, "Kurds", "Damascus", 3, 3, "C2", "Nomads", 1);
- saracen(86, "Kurds", "Damascus", 3, 3, "C2", "Nomads", 1);
-
- saracen(91, "Turks", "Aleppo", 3, 3, "A2", "Nomads", 1);
- saracen(92, "Turks", "Aleppo", 3, 3, "A2", "Nomads", 1);
- saracen(93, "Turks", "Aleppo", 3, 4, "A1", "Nomads", 1);
- saracen(94, "Turks", "Aleppo", 3, 4, "A1", "Nomads", 1);
-
- saracen(95, "Arabs", "Egypt", 3, 3, "B2", "Nomads", 1);
- saracen(96, "Arabs", "Egypt", 3, 3, "B2", "Nomads", 1);
- saracen(97, "Arabs", "Egypt", 3, 4, "B1", "Nomads", 1);
- saracen(87, "Arabs", "Egypt", 3, 4, "B1", "Nomads", 1);
-
- function town(axis, major_align, minor_align, wrap, region, name, rating, type) {
- TOWNS[name].region = region;
- TOWNS[name].rating = rating;
- if (type === 'port' || type === 'fortified-port')
- TOWNS[name].port = true;
- if (type === 'fortified-port')
- TOWNS[name].fortified_port = true;
- if (TOWNS[name].port)
- PORTS.push(name);
- TOWNS[name].exits = [];
- TOWNS[name].layout_axis = axis;
- TOWNS[name].layout_major = 1 - major_align;
- TOWNS[name].layout_minor = 1 - minor_align;
- TOWNS[name].wrap = wrap;
- }
-
- town('Y', 0.5, 1.0, 50, "Pool", "FP", 0, "pool");
- town('Y', 0.5, 1.0, 50, "Pool", "SP", 0, "pool");
- town('Y', 1.0, 1.0, 3, "Pool", "Dead", 0, "pool");
- town('Y', 1.0, 1.0, 1, "Pool", "Sea", 0, "pool");
-
- town('Y', 1.0, 0.5, 3, "Staging", "England", 3, "staging");
- town('Y', 1.0, 0.5, 3, "Staging", "France", 3, "staging");
- town('Y', 1.0, 0.5, 3, "Staging", "Germania", 3, "staging");
-
-
- town('X', 1.0, 0.5, 3, "Syria", "Aleppo", 3, "town");
- town('Y', 0.5, 0.5, 3, "Syria", "Artah", 1, "town");
- town('X', 1.0, 0.5, 3, "Syria", "Zerdana", 1, "town");
- town('X', 1.0, 0.5, 3, "Syria", "Hama", 1, "town");
- town('X', 0.9, 0.5, 3, "Syria", "Homs", 2, "town");
- town('X', 0.3, 0.5, 3, "Syria", "Lacum", 0, "town");
- town('X', 0.3, 0.5, 3, "Syria", "Qaddas", 0, "town");
- town('X', 0.5, 1.0, 3, "Syria", "Baalbek", 1, "town");
- town('X', 0.5, 1.0, 3, "Syria", "Anjar", 0, "town");
- town('X', 0.5, 0.5, 4, "Syria", "Damascus", 4, "town");
- town('X', 1.0, 0.5, 3, "Syria", "Banyas", 1, "town");
- town('X', 1.0, 0.5, 3, "Syria", "Ashtera", 1, "town");
- town('X', 1.0, 0.5, 3, "Syria", "Ajlun", 0, "town");
-
- town('X', 0.0, 0.5, 3, "Antioch", "St. Simeon", 0, "port");
- town('Y', 0.5, 0.5, 3, "Antioch", "Antioch", 3, "town");
- town('Y', 0.5, 0.5, 3, "Antioch", "Harim", 0, "town");
- town('X', 0.5, 0.5, 3, "Antioch", "Kassab", 0, "town");
- town('X', 0.5, 0.5, 3, "Antioch", "Shughur", 0, "town");
- town('X', 0.0, 0.5, 3, "Antioch", "Latakia", 1, "port");
- town('X', 0.5, 0.5, 3, "Antioch", "Sa\xf4ne", 1, "town");
- town('Y', 0.5, 0.5, 3, "Antioch", "Albara", 0, "town");
- town('X', 0.0, 0.5, 3, "Antioch", "Margat", 1, "port");
-
- town('X', 0.5, 0.5, 1, "Masyaf", "Masyaf", 1, "town");
-
- town('Y', 0.5, 0.5, 3, "Tripoli", "Monterrand", 0, "town");
- town('X', 0.0, 0.5, 3, "Tripoli", "Tartus", 1, "port");
- town('X', 1.0, 0.5, 3, "Tripoli", "Krak", 1, "town");
- town('X', 0.0, 0.5, 3, "Tripoli", "Tripoli", 2, "fortified-port");
- town('X', 0.0, 0.5, 3, "Tripoli", "Botron", 0, "town");
-
- town('X', 0.0, 0.5, 3, "Jerusalem", "Beirut", 2, "port");
- town('X', 0.0, 0.5, 3, "Jerusalem", "Sidon", 1, "port");
- town('X', 0.0, 0.5, 3, "Jerusalem", "Tyre", 2, "fortified-port");
- town('Y', 0.5, 0.5, 3, "Jerusalem", "Beaufort", 1, "town");
- town('X', 0.0, 0.5, 3, "Jerusalem", "Acre", 3, "port");
- town('X', 1.0, 0.5, 3, "Jerusalem", "Tiberias", 2, "town");
- town('Y', 1.0, 0.5, 3, "Jerusalem", "Legio", 0, "town");
- town('X', 1.0, 0.5, 3, "Jerusalem", "Baisan", 1, "town");
- town('X', 0.0, 0.5, 3, "Jerusalem", "Caesarea", 1, "port");
- town('X', 0.5, 0.5, 3, "Jerusalem", "Nablus", 1, "town");
- town('X', 0.5, 0.5, 3, "Jerusalem", "Damiya", 0, "town");
- town('X', 0.5, 0.5, 3, "Jerusalem", "Amman", 1, "town");
- town('X', 0.0, 0.5, 3, "Jerusalem", "Jaffa", 1, "port");
- town('Y', 0.5, 0.5, 3, "Jerusalem", "Ramallah", 0, "town");
- town('X', 0.5, 0.4, 3, "Jerusalem", "Jerusalem", 3, "town");
- town('Y', 0.5, 0.5, 3, "Jerusalem", "Jericho", 0, "town");
- town('X', 0.1, 0.5, 6, "Jerusalem", "Ascalon", 2, "port");
- town('Y', 0.5, 0.5, 3, "Jerusalem", "Lachish", 0, "town");
- town('X', 0.5, 1.0, 3, "Jerusalem", "Hebron", 1, "town");
- town('X', 1.0, 0.5, 3, "Jerusalem", "Kerak", 1, "town");
- town('X', 0.5, 0.5, 6, "Jerusalem", "Gaza", 1, "town");
- town('Y', 0.5, 0.5, 3, "Jerusalem", "Beersheba", 0, "town");
- town('X', 0.5, 0.5, 3, "Jerusalem", "Dimona", 0, "town");
- town('X', 1.0, 0.5, 3, "Jerusalem", "Zoar", 0, "town");
-
- town('X', 0.5, 0.5, 4, "Egypt", "Egypt", 4, "port");
-
- function road(A,B,type) {
- let id = (A < B) ? (A + "/" + B) : (B + "/" + A);
- ROADS[id] = type;
- TOWNS[A].exits.push(B);
- TOWNS[B].exits.push(A);
+function init_shields() {
+ for (let i = 0; i < TOWNS.length; ++i)
+ SHIELDS[i] = []
+ function shield(town, block_names) {
+ town = town_index[town]
+ for (let name of block_names) {
+ if (name in block_index) {
+ SHIELDS[town].push(block_index[name])
+ } else {
+ for (let b = 0; b < BLOCKS.length; ++b)
+ if (BLOCKS[b].name === name)
+ SHIELDS[town].push(b)
+ }
+ }
+ SHIELDS[town].sort((a,b)=>a-b)
}
- function iron_bridge(A,B) { road(A,B,"iron-bridge"); }
- function major(A,B) { road(A,B,"major"); }
- function minor(A,B) { road(A,B,"minor"); }
+ shield("Antioch", [ "Bohemond", "Templars", "Turcopoles" ])
+ shield("Latakia", [ "Bohemond" ])
+ shield("Sa\xf4ne", [ "Josselin" ])
+ shield("Margat", [ "Hospitallers" ])
+ shield("Krak", [ "Hospitallers" ])
+ shield("Tartus", [ "Templars" ])
+ shield("Tripoli", [ "Bohemond", "Raymond" ])
+ shield("Beirut", [ "Turcopoles", "King Guy" ])
+ shield("Sidon", [ "Reynald (Sidon)" ])
+ shield("Beaufort", [ "Reynald (Sidon)" ])
+ shield("Tyre", [ "Conrad", "King Guy" ])
+ shield("Acre", [ "Turcopoles", "Hospitallers", "King Guy" ])
+ shield("Tiberias", [ "Turcopoles", "Raymond" ])
+ shield("Baisan", [ "Hospitallers" ])
+ shield("Caesarea", [ "Walter" ])
+ shield("Nablus", [ "Balian" ])
+ shield("Amman", [ "Templars" ])
+ shield("Jaffa", [ "King Guy" ])
+ shield("Jerusalem", [ "King Guy", "Hospitallers", "Templars" ])
+ shield("Ascalon", [ "Balian", "King Guy" ])
+ shield("Hebron", [ "King Guy" ])
+ shield("Gaza", [ "Templars" ])
+ shield("Kerak", [ "Reynald (Kerak)" ])
+ shield("Egypt", [ "Saladin", "Al Adil", "Al Aziz", "Al Afdal", "Al Zahir", "Qara-Qush", "Yuzpah" ])
+ shield("Aleppo", [ "Saladin", "Al Adil", "Al Aziz", "Al Afdal", "Al Zahir", "Sanjar", "Zangi" ])
+ shield("Ashtera", [ "Yazkuj" ])
+ shield("Artah", [ "Sulaiman" ])
+ shield("Damascus", [ "Saladin", "Al Adil", "Al Aziz", "Al Afdal", "Al Zahir", "Keukburi", "Al Mashtub" ])
+ shield("Homs", [ "Tuman", "Shirkuh" ])
+ shield("Zerdana", [ "Jurdik" ])
+ shield("Baalbek", [ "Bahram" ])
+ shield("Hama", [ "Taqi al Din" ])
+ shield("Banyas", [ "Qaimaz" ])
+}
- iron_bridge("Antioch", "Harim");
- major("Harim", "Artah");
- major("Artah", "Aleppo");
- major("Aleppo", "Zerdana");
- major("Zerdana", "Hama");
- major("Hama", "Albara");
- major("Hama", "Monterrand");
- major("Hama", "Homs");
- major("Albara", "Shughur");
- major("Shughur", "Harim");
- major("Monterrand", "Krak");
- major("Krak", "Homs");
- major("Krak", "Tripoli");
- major("Tripoli", "Tartus");
- major("Tripoli", "Botron");
- major("Tartus", "Margat");
- major("Margat", "Latakia");
- major("Botron", "Beirut");
- major("Beirut", "Sidon");
- major("Sidon", "Tyre");
- major("Tyre", "Beaufort");
- major("Beaufort", "Banyas");
- major("Banyas", "Damascus");
- major("Damascus", "Qaddas");
- major("Qaddas", "Homs");
- major("Homs", "Lacum");
- major("Lacum", "Baalbek");
- major("Baalbek", "Anjar");
- major("Anjar", "Beaufort");
- major("Damascus", "Ashtera");
- major("Ashtera", "Ajlun");
- major("Ajlun", "Amman");
- major("Amman", "Kerak");
- major("Kerak", "Zoar");
- major("Zoar", "Hebron");
- major("Hebron", "Jerusalem");
- major("Jerusalem", "Ramallah");
- major("Ramallah", "Jaffa");
- major("Jaffa", "Ascalon");
- major("Ascalon", "Gaza");
- major("Gaza", "Egypt");
- major("Ajlun", "Tiberias");
- major("Tiberias", "Acre");
- major("Acre", "Legio");
- major("Legio", "Baisan");
- major("Baisan", "Tiberias");
- major("Baisan", "Nablus");
- major("Nablus", "Legio");
- major("Nablus", "Jerusalem");
- major("Acre", "Caesarea");
- major("Caesarea", "Jaffa");
-
- minor("St. Simeon", "Antioch");
- minor("Antioch", "Kassab");
- minor("Kassab", "Latakia");
- minor("Latakia", "Sa\xf4ne");
- minor("Sa\xf4ne", "Shughur");
- minor("Sa\xf4ne", "Albara");
- minor("Albara", "Zerdana");
- minor("Zerdana", "Artah");
-
- minor("Monterrand", "Homs");
-
- minor("Tartus", "Krak");
- minor("Krak", "Lacum");
- minor("Lacum", "Qaddas");
- minor("Tripoli", "Baalbek");
- minor("Beirut", "Anjar");
- minor("Anjar", "Damascus");
- minor("Sidon", "Beaufort");
- minor("Tiberias", "Banyas");
- minor("Banyas", "Ashtera");
- minor("Tyre", "Acre");
- minor("Caesarea", "Nablus");
- minor("Nablus", "Damiya");
- minor("Damiya", "Baisan");
- minor("Damiya", "Amman");
- minor("Amman", "Jericho");
- minor("Jericho", "Damiya");
- minor("Jericho", "Kerak");
- minor("Jericho", "Jerusalem");
-
- minor("Ramallah", "Ascalon");
- minor("Ascalon", "Lachish");
- minor("Lachish", "Gaza");
- minor("Gaza", "Beersheba");
- minor("Beersheba", "Egypt");
- minor("Beersheba", "Dimona");
- minor("Dimona", "Zoar");
- minor("Dimona", "Hebron");
- minor("Hebron", "Lachish");
-
- // off-map roads
- ROADS["Germania/St. Simeon"] = 'minor';
- ROADS["Aleppo/Germania"] = 'major';
- ROADS["Antioch/Germania"] = 'major';
-
- // TODO: seats and alternate seats
-})();
+init_towns()
+init_roads()
+init_blocks()
+init_shields()
if (typeof module !== 'undefined')
- module.exports = { CARDS, BLOCKS, TOWNS, PORTS, ROADS, SHIELDS }
+ module.exports = { CARDS, BLOCKS, TOWNS, PORTS, ROADS, SHIELDS, block_index, town_index }
diff --git a/play.css b/play.css
index e3ad8e3..982fc4f 100644
--- a/play.css
+++ b/play.css
@@ -9,12 +9,14 @@ body.Saracens header.your_turn { background-color: lightgreen; }
.role_vp { float: right; }
#log { background-color: whitesmoke; }
-#log div { padding-left: 20px; text-indent: -12px; }
-#log .st { background-color: #246; color: white; font-weight: bold; }
+#log div { padding-left: 24px; text-indent: -12px; }
+#log div.i { padding-left: 36px; text-indent: -12px; }
+#log .h1 { background-color: #246; color: white; font-weight: bold; }
#log .F { background-color: khaki; }
#log .S { background-color: darkseagreen; }
-#log .bs { background-color: lightgray; }
-#log .br { font-style: italic; text-decoration: underline; }
+#log .h3 { background-color: lightgray; }
+#log .h4 { font-style: italic; text-decoration: underline; }
+#log .tip:hover { text-decoration: underline; cursor: pointer; }
#map #timeline {
position: absolute;
@@ -142,6 +144,12 @@ body.Saracens header.your_turn { background-color: lightgreen; }
opacity: 0.6;
z-index: 9;
}
+.town.tip {
+ opacity: 1;
+ border-color: yellow;
+ border-style: dashed;
+ z-index: 9;
+}
.town.muster {
opacity: 0.6;
border-color: brown;
diff --git a/play.js b/play.js
index a6b18f6..b675d31 100644
--- a/play.js
+++ b/play.js
@@ -1,219 +1,302 @@
-"use strict";
+"use strict"
+
+function set_has(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 true
+ }
+ return false
+}
+
+const FRANKS = "Franks"
+const SARACENS = "Saracens"
-const FRANKS = "Franks";
-const SARACENS = "Saracens";
-const ASSASSINS = "Assassins";
const ENEMY = { Saracens: "Franks", Franks: "Saracens" }
-const DEAD = "Dead";
-const F_POOL = "FP";
-const S_POOL = "SP";
-const ENGLAND = "England";
-const FRANCE = "France";
-const GERMANIA = "Germania";
+
+const NOWHERE = 0
+const DEAD = 1
+const F_POOL = 2
+const S_POOL = 3
+const SEA = 4
+
+const ENGLAND = 5
+const FRANCE = 6
+const GERMANIA = 7
+
+const SHIELD_NAMES = {}
+SHIELD_NAMES[town_index["Antioch"]] = "Bohemond, Templars, Turcopoles"
+SHIELD_NAMES[town_index["Latakia"]] = "Bohemond"
+SHIELD_NAMES[town_index["Sa\xf4ne"]] = "Josselin"
+SHIELD_NAMES[town_index["Margat"]] = "Hospitallers"
+SHIELD_NAMES[town_index["Krak"]] = "Hospitallers"
+SHIELD_NAMES[town_index["Tartus"]] = "Templars"
+SHIELD_NAMES[town_index["Tripoli"]] = "Bohemond, Raymond"
+SHIELD_NAMES[town_index["Beirut"]] = "Turcopoles, King Guy"
+SHIELD_NAMES[town_index["Sidon"]] = "Reynald (Sidon)"
+SHIELD_NAMES[town_index["Beaufort"]] = "Reynald (Sidon)"
+SHIELD_NAMES[town_index["Tyre"]] = "Conrad, King Guy"
+SHIELD_NAMES[town_index["Acre"]] = "Turcopoles, Hospitallers, King Guy"
+SHIELD_NAMES[town_index["Tiberias"]] = "Turcopoles, Raymond"
+SHIELD_NAMES[town_index["Baisan"]] = "Hospitallers"
+SHIELD_NAMES[town_index["Caesarea"]] = "Walter"
+SHIELD_NAMES[town_index["Nablus"]] = "Balian"
+SHIELD_NAMES[town_index["Amman"]] = "Templars"
+SHIELD_NAMES[town_index["Jaffa"]] = "King Guy"
+SHIELD_NAMES[town_index["Jerusalem"]] = "King Guy, Hospitallers, Templars"
+SHIELD_NAMES[town_index["Ascalon"]] = "Balian, King Guy"
+SHIELD_NAMES[town_index["Hebron"]] = "King Guy"
+SHIELD_NAMES[town_index["Gaza"]] = "Templars"
+SHIELD_NAMES[town_index["Kerak"]] = "Reynald (Kerak)"
+SHIELD_NAMES[town_index["Egypt"]] = "Saladin, Qara-Qush, Yuzpah"
+SHIELD_NAMES[town_index["Aleppo"]] = "Saladin, Sanjar, Zangi"
+SHIELD_NAMES[town_index["Ashtera"]] = "Yazkuj"
+SHIELD_NAMES[town_index["Artah"]] = "Sulaiman"
+SHIELD_NAMES[town_index["Damascus"]] = "Saladin, Keukburi, Al Mashtub"
+SHIELD_NAMES[town_index["Homs"]] = "Tuman, Shirkuh"
+SHIELD_NAMES[town_index["Zerdana"]] = "Jurdik"
+SHIELD_NAMES[town_index["Baalbek"]] = "Bahram"
+SHIELD_NAMES[town_index["Hama"]] = "Taqi al Din"
+SHIELD_NAMES[town_index["Banyas"]] = "Qaimaz"
const KINGDOM = {
"Syria": "Syria",
"Jerusalem": "Kingdom of Jerusalem",
"Antioch": "Principality of Antioch",
"Tripoli": "County of Tripoli",
-};
+}
const VICTORY_TOWNS = [
- "Aleppo", "Damascus", "Egypt",
- "Antioch", "Tripoli", "Acre", "Jerusalem"
-];
+ town_index["Aleppo"],
+ town_index["Damascus"],
+ town_index["Egypt"],
+ town_index["Antioch"],
+ town_index["Tripoli"],
+ town_index["Acre"],
+ town_index["Jerusalem"]
+]
-let label_layout = window.localStorage['crusader-rex/label-layout'] || 'spread';
+let label_layout = window.localStorage['crusader-rex/label-layout'] || 'spread'
function set_spread_layout() {
- label_layout = 'spread';
- window.localStorage['crusader-rex/label-layout'] = label_layout;
- update_map();
+ label_layout = 'spread'
+ window.localStorage['crusader-rex/label-layout'] = label_layout
+ update_map()
}
function set_stack_layout() {
- label_layout = 'stack';
- window.localStorage['crusader-rex/label-layout'] = label_layout;
- update_map();
+ label_layout = 'stack'
+ window.localStorage['crusader-rex/label-layout'] = label_layout
+ update_map()
}
function toggle_blocks() {
- document.getElementById("map").classList.toggle("hide_blocks");
+ document.getElementById("map").classList.toggle("hide_blocks")
}
let ui = {
- cards: {},
- card_backs: {},
- towns: {},
- blocks: {},
- battle_menu: {},
- battle_block: {},
+ cards: [],
+ card_backs: [],
+ towns: [],
+ blocks: [],
+ battle_menu: [],
+ battle_block: [],
present: new Set(),
}
+function on_focus_space_tip(x) {
+ ui.towns[x].classList.add("tip")
+}
+
+function on_blur_space_tip(x) {
+ ui.towns[x].classList.remove("tip")
+}
+
+function on_click_space_tip(x) {
+ ui.towns[x].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
+}
+
+function sub_space_name(match, p1, offset, string) {
+ let x = p1 | 0
+ let n = TOWNS[x].name
+ return `<span class="tip" onmouseenter="on_focus_space_tip(${x})" onmouseleave="on_blur_space_tip(${x})" onclick="on_click_space_tip(${x})">${n}</span>`
+}
+
function on_log(text) {
- let p = document.createElement("div");
- text = text.replace(/&/g, "&amp;");
- text = text.replace(/</g, "&lt;");
- text = text.replace(/>/g, "&gt;");
+ let p = document.createElement("div")
+
+ if (text.match(/^>/)) {
+ text = text.substring(1)
+ p.className = "i"
+ }
+
+ text = text.replace(/&/g, "&amp;")
+ text = text.replace(/</g, "&lt;")
+ text = text.replace(/>/g, "&gt;")
- text = text.replace(/\u2192 /g, "\u2192\xa0");
+ text = text.replace(/\u2192 /g, "\u2192\xa0")
- text = text.replace(/^([A-Z]):/, '<span class="$1"> $1 </span>');
+ text = text.replace(/^([A-Z]):/, '<span class="$1"> $1 </span>')
- if (text.match(/^~ .* ~$/))
- p.className = 'br', text = text.substring(2, text.length-2);
- else if (text.match(/^Start Franks/))
- p.className = 'F';
- else if (text.match(/^Start Saracens/))
- p.className = 'S';
- else if (text.match(/^Start /))
- p.className = 'st', text = text.replace(/\.$/, "");
- else if (text.match(/^(Battle in)/))
- p.className = 'bs';
+ text = text.replace(/#(\d+)/g, sub_space_name)
- if (text.match(/^Start /))
- text = text.substring(6);
+ if (text.match(/^\.h1 /))
+ p.className = 'h1', text = text.substring(4)
+ if (text.match(/^\.h2 F/))
+ p.className = 'h2 F', text = text.substring(4)
+ if (text.match(/^\.h2 S/))
+ p.className = 'h2 S', text = text.substring(4)
+ if (text.match(/^\.h3 /))
+ p.className = 'h3', text = text.substring(4)
+ if (text.match(/^\.h4 /))
+ p.className = 'h4', text = text.substring(4)
- p.innerHTML = text;
- return p;
+ p.innerHTML = text
+ return p
}
function on_focus_town(evt) {
- let where = evt.target.town;
- let text = where;
- if (where in SHIELDS)
- text += " \u2014 " + SHIELDS[where].join(", ");
- let kingdom = KINGDOM[TOWNS[where].region];
+ let where = evt.target.town
+ let text = TOWNS[where].name
+ if (where in SHIELD_NAMES)
+ text += " \u2014 " + SHIELD_NAMES[where]
+ let kingdom = KINGDOM[TOWNS[where].region]
if (kingdom)
- text += " \u2014 " + kingdom;
+ text += " \u2014 " + kingdom
if (VICTORY_TOWNS.includes(where))
- text += " \u2014 1 VP";
- document.getElementById("status").textContent = text;
+ text += " \u2014 1 VP"
+ document.getElementById("status").textContent = text
}
function on_blur_town(evt) {
- document.getElementById("status").textContent = "";
+ document.getElementById("status").textContent = ""
}
function on_click_town(evt) {
- let where = evt.target.town;
- send_action('town', where);
+ let where = evt.target.town
+ send_action('town', where)
}
-const STEP_TEXT = [ 0, "I", "II", "III", "IIII" ];
-const HEIR_TEXT = [ 0, '\u00b9', '\u00b2', '\u00b3', '\u2074', '\u2075' ];
+const STEP_TEXT = [ 0, "I", "II", "III", "IIII" ]
+const HEIR_TEXT = [ 0, '\u00b9', '\u00b2', '\u00b3', '\u2074', '\u2075' ]
-function block_name(who) { return who; }
+function block_name(who) { return BLOCKS[who].name; }
function block_home(who) { return BLOCKS[who].home; }
function block_owner(who) { return BLOCKS[who].owner; }
function on_focus_map_block(evt) {
- let info = BLOCKS[evt.target.block];
- let where = view.location[evt.target.block];
- if ((info.owner === player || info.owner === ASSASSINS) && where !== S_POOL && where !== F_POOL) {
- let text = info.name + " ";
+ let info = BLOCKS[evt.target.block]
+ let where = view.location[evt.target.block]
+ if ((info.owner === player || info.owner === "Assassins") && where !== S_POOL && where !== F_POOL) {
+ let text = info.name + " "
if (info.move)
- text += info.move + "-";
- text += STEP_TEXT[info.steps] + "-" + info.combat;
- document.getElementById("status").textContent = text;
+ text += info.move + "-"
+ text += STEP_TEXT[info.steps] + "-" + info.initiative + info.fire_power
+ document.getElementById("status").textContent = text
} else {
- document.getElementById("status").textContent = info.owner;
+ document.getElementById("status").textContent = info.owner
}
}
function on_blur_map_block(evt) {
- document.getElementById("status").textContent = "";
+ document.getElementById("status").textContent = ""
}
function on_click_map_block(evt) {
- let b = evt.target.block;
+ let b = evt.target.block
if (!view.battle)
- send_action('block', b);
+ send_action('block', b)
}
function on_focus_battle_block(evt) {
- let b = evt.target.block;
- let msg;
+ let b = evt.target.block
+ let msg
if (!evt.target.classList.contains("known")) {
if (block_owner(b) === FRANKS)
- msg = "Franks";
+ msg = "Franks"
else if (block_owner(b) === SARACENS)
- msg = "Saracens";
+ msg = "Saracens"
} else {
- msg = block_name(b);
+ msg = block_name(b)
}
if (view.actions && view.actions.fire && view.actions.fire.includes(b))
- msg = "Fire with " + msg;
+ msg = "Fire with " + msg
else if (view.actions && view.actions.storm && view.actions.storm.includes(b))
- msg = "Storm with " + msg;
+ msg = "Storm with " + msg
else if (view.actions && view.actions.sally && view.actions.sally.includes(b))
- msg = "Sally with " + msg;
+ msg = "Sally with " + msg
else if (view.actions && view.actions.withdraw && view.actions.withdraw.includes(b))
- msg = "Withdraw with " + msg;
+ msg = "Withdraw with " + msg
else if (view.actions && view.actions.hit && view.actions.hit.includes(b))
- msg = "Take hit on " + msg;
+ msg = "Take hit on " + msg
- document.getElementById("status").textContent = msg;
+ document.getElementById("status").textContent = msg
}
function on_blur_battle_block(evt) {
- document.getElementById("status").textContent = "";
+ document.getElementById("status").textContent = ""
}
function on_click_battle_block(evt) {
- let b = evt.target.block;
- send_action('block', b);
+ let b = evt.target.block
+ send_action('block', b)
}
function on_focus_fire(evt) {
document.getElementById("status").textContent =
- "Fire with " + block_name(evt.target.block);
+ "Fire with " + block_name(evt.target.block)
}
function on_focus_retreat(evt) {
if (view.battle.storming.includes(evt.target.block))
document.getElementById("status").textContent =
- "Withdraw with " + block_name(evt.target.block);
+ "Withdraw with " + block_name(evt.target.block)
else
document.getElementById("status").textContent =
- "Retreat with " + block_name(evt.target.block);
+ "Retreat with " + block_name(evt.target.block)
}
function on_focus_harry(evt) {
document.getElementById("status").textContent =
- "Harry with " + block_name(evt.target.block);
+ "Harry with " + block_name(evt.target.block)
}
function on_focus_charge(evt) {
document.getElementById("status").textContent =
- "Charge with " + block_name(evt.target.block);
+ "Charge with " + block_name(evt.target.block)
}
function on_focus_withdraw(evt) {
document.getElementById("status").textContent =
- "Withdraw with " + block_name(evt.target.block);
+ "Withdraw with " + block_name(evt.target.block)
}
function on_focus_storm(evt) {
document.getElementById("status").textContent =
- "Storm with " + block_name(evt.target.block);
+ "Storm with " + block_name(evt.target.block)
}
function on_focus_sally(evt) {
document.getElementById("status").textContent =
- "Sally with " + block_name(evt.target.block);
+ "Sally with " + block_name(evt.target.block)
}
function on_focus_hit(evt) {
document.getElementById("status").textContent =
- "Take hit on " + block_name(evt.target.block);
+ "Take hit on " + block_name(evt.target.block)
}
function on_blur_battle_button(evt) {
- document.getElementById("status").textContent = "";
+ document.getElementById("status").textContent = ""
}
function on_click_hit(evt) { send_action('hit', evt.target.block); }
@@ -226,573 +309,572 @@ function on_click_storm(evt) { send_action('storm', evt.target.block); }
function on_click_sally(evt) { send_action('sally', evt.target.block); }
function on_click_card(evt) {
- let c = evt.target.id.split("+")[1] | 0;
- send_action('play', c);
+ let c = evt.target.id.split("+")[1] | 0
+ send_action('play', c)
}
function build_battle_button(menu, b, c, click, enter, img_src) {
- let img = new Image();
- img.draggable = false;
- img.classList.add("action");
- img.classList.add(c);
- img.setAttribute("src", img_src);
- img.addEventListener("click", click);
- img.addEventListener("mouseenter", enter);
- img.addEventListener("mouseleave", on_blur_battle_button);
- img.block = b;
- menu.appendChild(img);
+ let img = new Image()
+ img.draggable = false
+ img.classList.add("action")
+ img.classList.add(c)
+ img.setAttribute("src", img_src)
+ img.addEventListener("click", click)
+ img.addEventListener("mouseenter", enter)
+ img.addEventListener("mouseleave", on_blur_battle_button)
+ img.block = b
+ menu.appendChild(img)
}
function battle_block_class_name(block) {
- return `block block_${block.image} ${block.owner}`;
+ return `block block_${block.image} ${block.owner}`
}
function build_battle_block(b, block) {
- let element = document.createElement("div");
- element.className = battle_block_class_name(block);
- element.addEventListener("mouseenter", on_focus_battle_block);
- element.addEventListener("mouseleave", on_blur_battle_block);
- element.addEventListener("click", on_click_battle_block);
- element.block = b;
- ui.battle_block[b] = element;
+ let element = document.createElement("div")
+ element.className = battle_block_class_name(block)
+ element.addEventListener("mouseenter", on_focus_battle_block)
+ element.addEventListener("mouseleave", on_blur_battle_block)
+ element.addEventListener("click", on_click_battle_block)
+ element.block = b
+ ui.battle_block[b] = element
- let menu_list = document.createElement("div");
- menu_list.className = "battle_menu_list";
+ let menu_list = document.createElement("div")
+ menu_list.className = "battle_menu_list"
build_battle_button(menu_list, b, "hit",
on_click_hit, on_focus_hit,
- "/images/cross-mark.svg");
+ "/images/cross-mark.svg")
build_battle_button(menu_list, b, "charge",
on_click_charge, on_focus_charge,
- "/images/mounted-knight.svg");
+ "/images/mounted-knight.svg")
build_battle_button(menu_list, b, "fire",
on_click_fire, on_focus_fire,
- "/images/pointy-sword.svg");
+ "/images/pointy-sword.svg")
build_battle_button(menu_list, b, "harry",
on_click_harry, on_focus_harry,
- "/images/arrow-flights.svg");
+ "/images/arrow-flights.svg")
build_battle_button(menu_list, b, "retreat",
on_click_retreat, on_focus_retreat,
- "/images/flying-flag.svg");
+ "/images/flying-flag.svg")
build_battle_button(menu_list, b, "withdraw",
on_click_withdraw, on_focus_withdraw,
- "/images/stone-tower.svg");
+ "/images/stone-tower.svg")
build_battle_button(menu_list, b, "storm",
on_click_storm, on_focus_storm,
- "/images/siege-tower.svg");
+ "/images/siege-tower.svg")
build_battle_button(menu_list, b, "sally",
on_click_sally, on_focus_sally,
- "/images/doorway.svg");
+ "/images/doorway.svg")
- let menu = document.createElement("div");
- menu.className = "battle_menu";
- menu.appendChild(element);
- menu.appendChild(menu_list);
- menu.block = b;
- ui.battle_menu[b] = menu;
+ let menu = document.createElement("div")
+ menu.className = "battle_menu"
+ menu.appendChild(element)
+ menu.appendChild(menu_list)
+ menu.block = b
+ ui.battle_menu[b] = menu
}
function build_map_block(b, block) {
- let element = document.createElement("div");
- element.classList.add("block");
- element.classList.add("known");
- element.classList.add(BLOCKS[b].owner);
- element.classList.add("block_" + block.image);
- element.addEventListener("mouseenter", on_focus_map_block);
- element.addEventListener("mouseleave", on_blur_map_block);
- element.addEventListener("click", on_click_map_block);
- element.block = b;
- return element;
+ let element = document.createElement("div")
+ element.classList.add("block")
+ element.classList.add("known")
+ element.classList.add(BLOCKS[b].owner)
+ element.classList.add("block_" + block.image)
+ element.addEventListener("mouseenter", on_focus_map_block)
+ element.addEventListener("mouseleave", on_blur_map_block)
+ element.addEventListener("click", on_click_map_block)
+ element.block = b
+ return element
}
function build_town(t, town) {
- let element = document.createElement("div");
- element.town = t;
- element.classList.add("town");
- element.addEventListener("mouseenter", on_focus_town);
- element.addEventListener("mouseleave", on_blur_town);
- element.addEventListener("click", on_click_town);
- ui.towns_element.appendChild(element);
- return element;
+ let element = document.createElement("div")
+ element.town = t
+ element.classList.add("town")
+ element.addEventListener("mouseenter", on_focus_town)
+ element.addEventListener("mouseleave", on_blur_town)
+ element.addEventListener("click", on_click_town)
+ ui.towns_element.appendChild(element)
+ return element
}
function build_map() {
- let element;
+ let element
- ui.blocks_element = document.getElementById("blocks");
- ui.offmap_element = document.getElementById("offmap");
- ui.towns_element = document.getElementById("towns");
+ ui.blocks_element = document.getElementById("blocks")
+ ui.offmap_element = document.getElementById("offmap")
+ ui.towns_element = document.getElementById("towns")
for (let c = 1; c <= 27; ++c) {
- ui.cards[c] = document.getElementById("card+"+c);
- ui.cards[c].addEventListener("click", on_click_card);
+ ui.cards[c] = document.getElementById("card+"+c)
+ ui.cards[c].addEventListener("click", on_click_card)
}
for (let c = 1; c <= 6; ++c)
- ui.card_backs[c] = document.getElementById("back+"+c);
-
- for (let name in TOWNS) {
- let town = TOWNS[name];
- if (name === F_POOL || name === S_POOL || name === DEAD)
- continue;
- if (name === "Sea") {
- element = document.getElementById("svgmap").getElementById("sea");
- element.town = "Sea";
- element.addEventListener("mouseenter", on_focus_town);
- element.addEventListener("mouseleave", on_blur_town);
- element.addEventListener("click", on_click_town);
- ui.towns[name] = element;
+ ui.card_backs[c] = document.getElementById("back+"+c)
+
+ for (let t = SEA; t < TOWNS.length; ++t) {
+ let town = TOWNS[t]
+ let name = town.name
+ if (t === SEA) {
+ element = document.getElementById("svgmap").getElementById("sea")
+ element.town = SEA
+ element.addEventListener("mouseenter", on_focus_town)
+ element.addEventListener("mouseleave", on_blur_town)
+ element.addEventListener("click", on_click_town)
+ ui.towns[t] = element
} else {
- element = ui.towns[name] = build_town(name, town);
- let xo = Math.round(element.offsetWidth/2);
- let yo = Math.round(element.offsetHeight/2);
- element.style.left = (town.x - xo) + "px";
- element.style.top = (town.y - yo) + "px";
+ element = ui.towns[t] = build_town(t, town)
+ let xo = Math.round(element.offsetWidth/2)
+ let yo = Math.round(element.offsetHeight/2)
+ element.style.left = (town.layout.x - xo) + "px"
+ element.style.top = (town.layout.y - yo) + "px"
}
}
- for (let b in BLOCKS) {
- let block = BLOCKS[b];
- ui.blocks[b] = build_map_block(b, block);
- build_battle_block(b, block);
+ for (let b = 0; b < BLOCKS.length; ++b) {
+ let block = BLOCKS[b]
+ ui.blocks[b] = build_map_block(b, block)
+ build_battle_block(b, block)
}
}
function update_steps(b, steps, element) {
- element.classList.remove("r0");
- element.classList.remove("r1");
- element.classList.remove("r2");
- element.classList.remove("r3");
- element.classList.add("r"+(BLOCKS[b].steps - steps));
+ element.classList.remove("r0")
+ element.classList.remove("r1")
+ element.classList.remove("r2")
+ element.classList.remove("r3")
+ element.classList.add("r"+(BLOCKS[b].steps - steps))
}
function layout_blocks(location, secret, known) {
if (label_layout === 'stack')
- document.getElementById("map").classList.add("stack_layout");
+ document.getElementById("map").classList.add("stack_layout")
else
- document.getElementById("map").classList.remove("stack_layout");
+ document.getElementById("map").classList.remove("stack_layout")
if (label_layout === 'spread' ||
(location === S_POOL || location === F_POOL || location === DEAD ||
location === ENGLAND || location === FRANCE || location === GERMANIA))
- layout_blocks_spread(location, secret, known);
+ layout_blocks_spread(location, secret, known)
else
- layout_blocks_stacked(location, secret, known);
+ layout_blocks_stacked(location, secret, known)
}
function layout_blocks_spread(town, north, south) {
- let wrap = TOWNS[town].wrap;
- let rows = [];
+ let wrap = TOWNS[town].layout.wrap
+ let rows = []
if ((north.length > wrap || south.length > wrap) || (north.length + south.length <= 3)) {
- north = north.concat(south);
- south = [];
+ north = north.concat(south)
+ south = []
}
function wrap_row(input) {
while (input.length > wrap) {
- rows.push(input.slice(0, wrap));
- input = input.slice(wrap);
+ rows.push(input.slice(0, wrap))
+ input = input.slice(wrap)
}
if (input.length > 0)
- rows.push(input);
+ rows.push(input)
}
- wrap_row(north);
- wrap_row(south);
+ wrap_row(north)
+ wrap_row(south)
- if (TOWNS[town].layout_minor > 0.5)
- rows.reverse();
+ if (TOWNS[town].layout.minor > 0.5)
+ rows.reverse()
for (let r = 0; r < rows.length; ++r) {
- let cols = rows[r];
+ let cols = rows[r]
for (let c = 0; c < cols.length; ++c)
- position_block(town, r, rows.length, c, cols.length, cols[c]);
+ position_block(town, r, rows.length, c, cols.length, cols[c])
}
}
function position_block(town, row, n_rows, col, n_cols, element) {
- let space = TOWNS[town];
- let block_size = 60+6;
- let padding = 4;
+ let space = TOWNS[town]
+ let block_size = 60+6
+ let padding = 4
if (town === ENGLAND || town === FRANCE || town === GERMANIA)
- padding = 21;
- let offset = block_size + padding;
- let row_size = (n_rows-1) * offset;
- let col_size = (n_cols-1) * offset;
- let x = space.x;
- let y = space.y;
-
- if (space.layout_axis === 'X') {
- x -= col_size * space.layout_major;
- y -= row_size * space.layout_minor;
- x += col * offset;
- y += row * offset;
+ padding = 21
+ let offset = block_size + padding
+ let row_size = (n_rows-1) * offset
+ let col_size = (n_cols-1) * offset
+ let x = space.layout.x
+ let y = space.layout.y
+
+ if (space.layout.axis === 'X') {
+ x -= col_size * space.layout.major
+ y -= row_size * space.layout.minor
+ x += col * offset
+ y += row * offset
} else {
- y -= col_size * space.layout_major;
- x -= row_size * space.layout_minor;
- y += col * offset;
- x += row * offset;
+ y -= col_size * space.layout.major
+ x -= row_size * space.layout.minor
+ y += col * offset
+ x += row * offset
}
- element.style.left = ((x - block_size/2)|0)+"px";
- element.style.top = ((y - block_size/2)|0)+"px";
+ element.style.left = ((x - block_size/2)|0)+"px"
+ element.style.top = ((y - block_size/2)|0)+"px"
}
function layout_blocks_stacked(location, secret, known) {
- let s = secret.length;
- let k = known.length;
- let both = secret.length > 0 && known.length > 0;
- let i = 0;
+ let s = secret.length
+ let k = known.length
+ let both = secret.length > 0 && known.length > 0
+ let i = 0
while (secret.length > 0)
- position_block_stacked(location, i++, (s-1)/2, both ? 1 : 0, secret.shift());
- i = 0;
+ position_block_stacked(location, i++, (s-1)/2, both ? 1 : 0, secret.shift())
+ i = 0
while (known.length > 0)
- position_block_stacked(location, i++, (k-1)/2, 0, known.shift());
+ position_block_stacked(location, i++, (k-1)/2, 0, known.shift())
}
function position_block_stacked(location, i, c, k, element) {
- let space = TOWNS[location];
- let block_size = 60+6;
- let x = space.x + (i - c) * 16 + k * 12;
- let y = space.y + (i - c) * 16 - k * 12;
- element.style.left = ((x - block_size/2)|0)+"px";
- element.style.top = ((y - block_size/2)|0)+"px";
+ let space = TOWNS[location]
+ let block_size = 60+6
+ let x = space.x + (i - c) * 16 + k * 12
+ let y = space.y + (i - c) * 16 - k * 12
+ element.style.left = ((x - block_size/2)|0)+"px"
+ element.style.top = ((y - block_size/2)|0)+"px"
}
function show_block(element) {
if (element.parentElement !== ui.blocks_element)
- ui.blocks_element.appendChild(element);
+ ui.blocks_element.appendChild(element)
}
function hide_block(element) {
if (element.parentElement !== ui.offmap_element)
- ui.offmap_element.appendChild(element);
+ ui.offmap_element.appendChild(element)
}
function is_known_block(info, who, town) {
if (view.game_over && player === 'Observer')
- return true;
+ return true
if (town === DEAD)
- return true;
+ return true
if ((town === S_POOL || town === F_POOL) && who !== view.who)
- return false;
- if (info.owner === player || info.owner === ASSASSINS || who === view.assassinate)
- return true;
- return false;
+ return false
+ if (info.owner === player || info.owner === "Assassins" || who === view.assassinate)
+ return true
+ return false
}
function update_map() {
- let layout = {};
+ let layout = {}
- document.getElementById("frank_vp").textContent = view.f_vp + " VP";
- document.getElementById("saracen_vp").textContent = view.s_vp + " VP";
- document.getElementById("timeline").className = "year_" + view.year;
+ document.getElementById("frank_vp").textContent = view.f_vp + " VP"
+ document.getElementById("saracen_vp").textContent = view.s_vp + " VP"
+ document.getElementById("timeline").className = "year_" + view.year
if (view.turn < 1)
document.getElementById("turn_info").textContent =
- "Year " + view.year;
+ "Year " + view.year
else if (view.turn < 6)
document.getElementById("turn_info").textContent =
- "Turn " + view.turn + " of Year " + view.year;
+ "Turn " + view.turn + " of Year " + view.year
else
document.getElementById("turn_info").textContent =
- "Winter Turn of Year " + view.year;
+ "Winter Turn of Year " + view.year
- for (let town in TOWNS)
- layout[town] = { north: [], south: [] };
+ for (let t = 0; t < TOWNS.length; ++t)
+ layout[t] = { north: [], south: [] }
- for (let b in view.location) {
- let info = BLOCKS[b];
- let element = ui.blocks[b];
- let town = view.location[b];
- let moved = view.moved[b] ? " moved" : "";
+ for (let b = 0; b < BLOCKS.length; ++b) {
+ let info = BLOCKS[b]
+ let element = ui.blocks[b]
+ let town = view.location[b]
+ let moved = set_has(view.moved, b) ? " moved" : ""
if (town === DEAD) {
- moved = " moved";
+ moved = " moved"
}
- if (town === null) {
- town = DEAD;
- moved = " removed";
+ if (town === NOWHERE) {
+ town = DEAD
+ moved = " removed"
}
if (is_known_block(info, b, town)) {
- let image = " block_" + info.image;
- let steps = " r" + (info.steps - view.steps[b]);
- let known = " known";
- element.classList = info.owner + known + " block" + image + steps + moved;
+ let image = " block_" + info.image
+ let steps = " r" + (info.steps - view.steps[b])
+ let known = " known"
+ element.classList = info.owner + known + " block" + image + steps + moved
} else {
- let besieging = "";
+ let besieging = ""
if (view.sieges[town] === info.owner) {
if (view.winter_campaign === town)
- besieging = " winter_campaign";
+ besieging = " winter_campaign"
else
- besieging = " besieging";
+ besieging = " besieging"
}
- let jihad = "";
+ let jihad = ""
if (view.jihad === town && info.owner === view.p1)
- jihad = " jihad";
- element.classList = info.owner + " block" + moved + besieging + jihad;
+ jihad = " jihad"
+ element.classList = info.owner + " block" + moved + besieging + jihad
}
if (town !== DEAD) {
if (info.owner === FRANKS)
- layout[town].north.push(element);
+ layout[town].north.push(element)
else
- layout[town].south.push(element);
+ layout[town].south.push(element)
}
- show_block(element);
+ show_block(element)
}
- for (let b in view.location) {
- let info = BLOCKS[b];
- let element = ui.blocks[b];
- let town = view.location[b];
+ for (let b = 0; b < BLOCKS.length; ++b) {
+ let info = BLOCKS[b]
+ let element = ui.blocks[b]
+ let town = view.location[b]
if (town === DEAD) {
if (info.owner === FRANKS)
- layout[F_POOL].north.unshift(element);
+ layout[F_POOL].north.unshift(element)
else
- layout[S_POOL].south.unshift(element);
+ layout[S_POOL].south.unshift(element)
}
}
- for (let b in view.location) {
- let info = BLOCKS[b];
- let element = ui.blocks[b];
- let town = view.location[b];
- if (town === null) {
+ for (let b = 0; b < BLOCKS.length; ++b) {
+ let info = BLOCKS[b]
+ let element = ui.blocks[b]
+ let town = view.location[b]
+ if (town === NOWHERE) {
if (info.owner === FRANKS)
- layout[F_POOL].north.unshift(element);
+ layout[F_POOL].north.unshift(element)
else
- layout[S_POOL].south.unshift(element);
+ layout[S_POOL].south.unshift(element)
}
}
- for (let town in TOWNS)
- layout_blocks(town, layout[town].north, layout[town].south);
+ for (let t = 0; t < TOWNS.length; ++t)
+ layout_blocks(t, layout[t].north, layout[t].south)
- for (let where in TOWNS) {
- if (ui.towns[where]) {
- ui.towns[where].classList.remove('highlight');
- ui.towns[where].classList.remove('muster');
+ for (let t = SEA; t < TOWNS.length; ++t) {
+ if (ui.towns[t]) {
+ ui.towns[t].classList.remove('highlight')
+ ui.towns[t].classList.remove('muster')
}
}
if (view.actions && view.actions.town)
- for (let where of view.actions.town)
- ui.towns[where].classList.add('highlight');
+ for (let t of view.actions.town)
+ ui.towns[t].classList.add('highlight')
if (view.muster)
- ui.towns[view.muster].classList.add('muster');
+ ui.towns[view.muster].classList.add('muster')
if (!view.battle) {
if (view.actions && view.actions.block)
for (let b of view.actions.block)
- ui.blocks[b].classList.add('highlight');
+ ui.blocks[b].classList.add('highlight')
}
- if (view.who && !view.battle)
- ui.blocks[view.who].classList.add('selected');
+ if (view.who >= 0 && !view.battle)
+ ui.blocks[view.who].classList.add('selected')
for (let b of view.castle)
- ui.blocks[b].classList.add('castle');
+ ui.blocks[b].classList.add('castle')
}
function update_card_display(element, card, prior_card) {
if (!card && !prior_card) {
- element.className = "show card card_back";
+ element.className = "show card card_back"
} else if (prior_card) {
- element.className = "show card prior " + CARDS[prior_card].image;
+ element.className = "show card prior " + CARDS[prior_card].image
} else {
- element.className = "show card " + CARDS[card].image;
+ element.className = "show card " + CARDS[card].image
}
}
function update_cards() {
- update_card_display(document.getElementById("frank_card"), view.f_card, view.prior_f_card);
- update_card_display(document.getElementById("saracen_card"), view.s_card, view.prior_s_card);
+ update_card_display(document.getElementById("frank_card"), view.f_card, view.prior_f_card)
+ update_card_display(document.getElementById("saracen_card"), view.s_card, view.prior_s_card)
for (let c = 1; c <= 27; ++c) {
- let element = ui.cards[c];
+ let element = ui.cards[c]
if (view.hand.includes(c)) {
- element.classList.add("show");
+ element.classList.add("show")
if (view.actions && view.actions.play) {
if (view.actions.play.includes(c)) {
- element.classList.add("enabled");
- element.classList.remove("disabled");
+ element.classList.add("enabled")
+ element.classList.remove("disabled")
} else {
- element.classList.remove("enabled");
- element.classList.add("disabled");
+ element.classList.remove("enabled")
+ element.classList.add("disabled")
}
} else {
- element.classList.remove("enabled");
- element.classList.remove("disabled");
+ element.classList.remove("enabled")
+ element.classList.remove("disabled")
}
} else {
- element.classList.remove("show");
+ element.classList.remove("show")
}
}
- let n = view.hand.length;
+ let n = view.hand.length
for (let c = 1; c <= 6; ++c)
if (c <= n && player === 'Observer')
- ui.card_backs[c].classList.add("show");
+ ui.card_backs[c].classList.add("show")
else
- ui.card_backs[c].classList.remove("show");
+ ui.card_backs[c].classList.remove("show")
}
function compare_blocks(a, b) {
- let aa = BLOCKS[a].combat;
- let bb = BLOCKS[b].combat;
+ let aa = BLOCKS[a].initiative + BLOCKS[a].fire_power
+ let bb = BLOCKS[b].initiative + BLOCKS[b].fire_power
if (aa === bb)
- return (a < b) ? -1 : (a > b) ? 1 : 0;
- return (aa < bb) ? -1 : (aa > bb) ? 1 : 0;
+ return (a < b) ? -1 : (a > b) ? 1 : 0
+ return (aa < bb) ? -1 : (aa > bb) ? 1 : 0
}
function insert_battle_block(root, node, block) {
for (let i = 0; i < root.children.length; ++i) {
- let prev = root.children[i];
+ let prev = root.children[i]
if (compare_blocks(prev.block, block) > 0) {
- root.insertBefore(node, prev);
- return;
+ root.insertBefore(node, prev)
+ return
}
}
- root.appendChild(node);
+ root.appendChild(node)
}
function update_battle() {
function fill_cell(name, list, show) {
- let cell = document.getElementById(name);
+ let cell = document.getElementById(name)
- ui.present.clear();
+ ui.present.clear()
for (let block of list) {
- ui.present.add(block);
+ ui.present.add(block)
if (!cell.contains(ui.battle_menu[block]))
- insert_battle_block(cell, ui.battle_menu[block], block);
+ insert_battle_block(cell, ui.battle_menu[block], block)
- ui.battle_menu[block].className = "battle_menu";
+ ui.battle_menu[block].className = "battle_menu"
if (view.actions && view.actions.fire && view.actions.fire.includes(block))
- ui.battle_menu[block].classList.add('fire');
+ ui.battle_menu[block].classList.add('fire')
if (view.actions && view.actions.retreat && view.actions.retreat.includes(block))
- ui.battle_menu[block].classList.add('retreat');
+ ui.battle_menu[block].classList.add('retreat')
if (view.actions && view.actions.harry && view.actions.harry.includes(block))
- ui.battle_menu[block].classList.add('harry');
+ ui.battle_menu[block].classList.add('harry')
if (view.actions && view.actions.charge && view.actions.charge.includes(block))
- ui.battle_menu[block].classList.add('charge');
+ ui.battle_menu[block].classList.add('charge')
if (view.actions && view.actions.withdraw && view.actions.withdraw.includes(block))
- ui.battle_menu[block].classList.add('withdraw');
+ ui.battle_menu[block].classList.add('withdraw')
if (view.actions && view.actions.storm && view.actions.storm.includes(block))
- ui.battle_menu[block].classList.add('storm');
+ ui.battle_menu[block].classList.add('storm')
if (view.actions && view.actions.sally && view.actions.sally.includes(block))
- ui.battle_menu[block].classList.add('sally');
+ ui.battle_menu[block].classList.add('sally')
if (view.actions && view.actions.charge && view.actions.charge.includes(block))
- ui.battle_menu[block].classList.add('charge');
+ ui.battle_menu[block].classList.add('charge')
if (view.actions && view.actions.treachery && view.actions.treachery.includes(block))
- ui.battle_menu[block].classList.add('treachery');
+ ui.battle_menu[block].classList.add('treachery')
if (view.actions && view.actions.hit && view.actions.hit.includes(block))
- ui.battle_menu[block].classList.add('hit');
+ ui.battle_menu[block].classList.add('hit')
- let class_name = battle_block_class_name(BLOCKS[block]);
+ let class_name = battle_block_class_name(BLOCKS[block])
if (view.actions && view.actions.block && view.actions.block.includes(block))
- class_name += " highlight";
- if (view.moved[block])
- class_name += " moved";
+ class_name += " highlight"
+ if (set_has(view.moved, block))
+ class_name += " moved"
if (block === view.who)
- class_name += " selected";
+ class_name += " selected"
if (block === view.battle.halfhit)
- class_name += " halfhit";
+ class_name += " halfhit"
if (view.jihad === view.battle.town && block_owner(block) === view.p1)
- class_name += " jihad";
+ class_name += " jihad"
if (view.battle.sallying.includes(block))
- show = true;
+ show = true
if (view.battle.storming.includes(block))
- show = true;
+ show = true
if (show || block_owner(block) === player) {
- class_name += " known";
- ui.battle_block[block].className = class_name;
- update_steps(block, view.steps[block], ui.battle_block[block], false);
+ class_name += " known"
+ ui.battle_block[block].className = class_name
+ update_steps(block, view.steps[block], ui.battle_block[block], false)
} else {
- ui.battle_block[block].className = class_name;
+ ui.battle_block[block].className = class_name
}
}
- for (let b in BLOCKS) {
+ for (let b = 0; b < BLOCKS.length; ++b) {
if (!ui.present.has(b)) {
if (cell.contains(ui.battle_menu[b]))
- cell.removeChild(ui.battle_menu[b]);
+ cell.removeChild(ui.battle_menu[b])
}
}
}
if (player === FRANKS) {
- fill_cell("ER", view.battle.SR, false);
- fill_cell("EC", view.battle.SC, view.battle.show_castle);
- fill_cell("EF", view.battle.SF, view.battle.show_field);
- fill_cell("FF", view.battle.FF, view.battle.show_field);
- fill_cell("FC", view.battle.FC, view.battle.show_castle);
- fill_cell("FR", view.battle.FR, false);
- document.getElementById("FC").className = "c" + view.battle.FCS;
- document.getElementById("EC").className = "c" + view.battle.SCS;
+ fill_cell("ER", view.battle.SR, false)
+ fill_cell("EC", view.battle.SC, view.battle.show_castle)
+ fill_cell("EF", view.battle.SF, view.battle.show_field)
+ fill_cell("FF", view.battle.FF, view.battle.show_field)
+ fill_cell("FC", view.battle.FC, view.battle.show_castle)
+ fill_cell("FR", view.battle.FR, false)
+ document.getElementById("FC").className = "c" + view.battle.FCS
+ document.getElementById("EC").className = "c" + view.battle.SCS
} else {
- fill_cell("ER", view.battle.FR, false);
- fill_cell("EC", view.battle.FC, view.battle.show_castle);
- fill_cell("EF", view.battle.FF, view.battle.show_field);
- fill_cell("FF", view.battle.SF, view.battle.show_field);
- fill_cell("FC", view.battle.SC, view.battle.show_castle);
- fill_cell("FR", view.battle.SR, false);
- document.getElementById("EC").className = "c" + view.battle.FCS;
- document.getElementById("FC").className = "c" + view.battle.SCS;
+ fill_cell("ER", view.battle.FR, false)
+ fill_cell("EC", view.battle.FC, view.battle.show_castle)
+ fill_cell("EF", view.battle.FF, view.battle.show_field)
+ fill_cell("FF", view.battle.SF, view.battle.show_field)
+ fill_cell("FC", view.battle.SC, view.battle.show_castle)
+ fill_cell("FR", view.battle.SR, false)
+ document.getElementById("EC").className = "c" + view.battle.FCS
+ document.getElementById("FC").className = "c" + view.battle.SCS
}
}
-let flash_timer = 0;
+let flash_timer = 0
function start_flash() {
- let element = document.getElementById("battle_message");
- let tick = true;
+ let element = document.getElementById("battle_message")
+ let tick = true
if (flash_timer)
- return;
+ return
flash_timer = setInterval(function () {
if (!view.flash_next) {
- element.textContent = view.battle ? view.battle.flash : "";
- clearInterval(flash_timer);
- flash_timer = 0;
+ element.textContent = view.battle ? view.battle.flash : ""
+ clearInterval(flash_timer)
+ flash_timer = 0
} else {
- element.textContent = tick ? view.battle.flash : view.flash_next;
- tick = !tick;
+ element.textContent = tick ? view.battle.flash : view.flash_next
+ tick = !tick
}
- }, 1000);
+ }, 1000)
}
function on_update() {
- action_button("eliminate", "Eliminate");
- action_button("winter_campaign", "Winter campaign");
- action_button("sea_move", "Sea move");
- action_button("end_sea_move", "End sea move");
- action_button("group_move", "Group move");
- action_button("end_group_move", "End group move");
- action_button("muster", "Muster");
- action_button("end_muster", "End muster");
- action_button("end_retreat", "End retreat");
- action_button("end_regroup", "End regroup");
- action_button("end_move_phase", "End move phase");
- action_button("pass", "Pass");
- action_button("next", "Next");
- action_button("undo", "Undo");
-
- document.getElementById("frank_vp").textContent = view.f_vp;
- document.getElementById("saracen_vp").textContent = view.s_vp;
-
- update_cards();
- update_map();
+ action_button("eliminate", "Eliminate")
+ action_button("winter_campaign", "Winter campaign")
+ action_button("sea_move", "Sea move")
+ action_button("end_sea_move", "End sea move")
+ action_button("group_move", "Group move")
+ action_button("end_group_move", "End group move")
+ action_button("muster", "Muster")
+ action_button("end_muster", "End muster")
+ action_button("end_retreat", "End retreat")
+ action_button("end_regroup", "End regroup")
+ action_button("end_move_phase", "End move phase")
+ action_button("pass", "Pass")
+ action_button("next", "Next")
+ action_button("undo", "Undo")
+
+ document.getElementById("frank_vp").textContent = view.f_vp
+ document.getElementById("saracen_vp").textContent = view.s_vp
+
+ update_cards()
+ update_map()
if (view.battle) {
- document.getElementById("battle_header").textContent = view.battle.title;
- document.getElementById("battle_message").textContent = view.battle.flash;
+ document.getElementById("battle_header").textContent = view.battle.title
+ document.getElementById("battle_message").textContent = view.battle.flash
if (view.flash_next)
- start_flash();
- document.getElementById("battle").classList.add("show");
- update_battle();
+ start_flash()
+ document.getElementById("battle").classList.add("show")
+ update_battle()
} else {
- document.getElementById("battle").classList.remove("show");
+ document.getElementById("battle").classList.remove("show")
}
}
-build_map();
+build_map()
-drag_element_with_mouse("#battle", "#battle_header");
-scroll_with_middle_mouse("main", 3);
+drag_element_with_mouse("#battle", "#battle_header")
+scroll_with_middle_mouse("main", 3)
diff --git a/rules.js b/rules.js
index 49b2799..4b0798c 100644
--- a/rules.js
+++ b/rules.js
@@ -16,38 +16,54 @@ exports.roles = [
"Saracens",
]
-const { CARDS, BLOCKS, TOWNS, PORTS, ROADS, SHIELDS } = require('./data')
+const { CARDS, BLOCKS, TOWNS, PORTS, ROADS, SHIELDS, block_index, town_index } = require('./data')
const FRANKS = "Franks"
const SARACENS = "Saracens"
-const ASSASSINS = "Assassins"
const OBSERVER = "Observer"
const BOTH = "Both"
-const ELIMINATED = null
-const DEAD = "Dead"
-const F_POOL = "FP"
-const S_POOL = "SP"
-const SEA = "Sea"
-const ENGLAND = "England"
-const FRANCE = "France"
-const GERMANIA = "Germania"
-const TYRE = "Tyre"
-const TRIPOLI = "Tripoli"
-const ALEPPO = "Aleppo"
-const ANTIOCH = "Antioch"
-const ST_SIMEON = "St. Simeon"
-const DAMASCUS = "Damascus"
-const MASYAF = "Masyaf"
-const SALADIN = "Saladin"
+
+const NOWHERE = 0
+const DEAD = 1
+const F_POOL = 2
+const S_POOL = 3
+const SEA = 4
+
+const ENGLAND = 5
+const FRANCE = 6
+const GERMANIA = 7
+
+const first_town = 5 // TODO: exclude staging areas? include sea?
+const last_town = TOWNS.length - 1
+const last_block = BLOCKS.length - 2 // assassins are not a real block
+
+const ALEPPO = town_index["Aleppo"]
+const ANTIOCH = town_index["Antioch"]
+const DAMASCUS = town_index["Damascus"]
+const MASYAF = town_index["Masyaf"]
+const ST_SIMEON = town_index["St. Simeon"]
+const TRIPOLI = town_index["Tripoli"]
+const TYRE = town_index["Tyre"]
+
+const NOBODY = -1
+const ASSASSINS = block_index["Assassins"]
+const RICHARD = block_index["Richard"]
+const ROBERT = block_index["Robert"]
+const CROSSBOWS = block_index["Crossbows"]
+const SALADIN = block_index["Saladin"]
+const AL_ADIL = block_index["Al Adil"]
+const AL_AZIZ = block_index["Al Aziz"]
+const AL_AFDAL = block_index["Al Afdal"]
+const AL_ZAHIR = block_index["Al Zahir"]
+
+const ENGLISH_CRUSADERS = [ RICHARD, ROBERT, CROSSBOWS ]
+const GERMAN_CRUSADERS = [ "Barbarossa", "Frederik", "Leopold" ].map(name => block_index[name])
+const FRENCH_CRUSADERS = [ "Philippe", "Hugues", "Fileps" ].map(name => block_index[name])
+const SALADIN_FAMILY = [ SALADIN, AL_ADIL, AL_AZIZ, AL_AFDAL, AL_ZAHIR ]
const INTRIGUE = 3
const WINTER_CAMPAIGN = 6
-const ENGLISH_CRUSADERS = [ "Richard", "Robert", "Crossbows" ]
-const GERMAN_CRUSADERS = [ "Barbarossa", "Frederik", "Leopold" ]
-const FRENCH_CRUSADERS = [ "Philippe", "Hugues", "Fileps" ]
-const SALADIN_FAMILY = [ "Saladin", "Al Adil", "Al Aziz", "Al Afdal", "Al Zahir" ]
-
const GERMAN_ROADS = [ ST_SIMEON, ANTIOCH, ALEPPO ]
const KINGDOMS = {
@@ -59,8 +75,13 @@ const KINGDOMS = {
}
const VICTORY_TOWNS = [
- "Aleppo", "Damascus", "Egypt",
- "Antioch", "Tripoli", "Acre", "Jerusalem"
+ town_index["Aleppo"],
+ town_index["Damascus"],
+ town_index["Egypt"],
+ town_index["Antioch"],
+ town_index["Tripoli"],
+ town_index["Acre"],
+ town_index["Jerusalem"]
]
// serif cirled numbers
@@ -72,16 +93,6 @@ const ATTACK_MARK = "*"
const RESERVE_MARK_1 = "\u2020"
const RESERVE_MARK_2 = "\u2021"
-// Only used by UI layer for layout. remove from game logic.
-delete TOWNS[DEAD]
-delete TOWNS[F_POOL]
-delete TOWNS[S_POOL]
-delete TOWNS[SEA]
-
-// Quick lists for fast iteration
-const BLOCKLIST = Object.keys(BLOCKS)
-const TOWNLIST = Object.keys(TOWNS)
-
let states = {}
let game = null
@@ -96,6 +107,10 @@ function log(s) {
game.log.push(s)
}
+function logi(s) {
+ game.log.push(">" + s)
+}
+
function active_adjective() {
return (game.active === FRANKS ? "Frank" : "Saracen")
}
@@ -113,7 +128,7 @@ function log_move_start(from) {
function log_move_continue(to, mark) {
if (mark)
- game.move_buf.push(to + mark)
+ game.move_buf.push("#" + to + mark)
else
game.move_buf.push(to)
}
@@ -125,28 +140,42 @@ function log_move_end() {
}
function print_summary(text, skip_if_empty = false) {
+ if (skip_if_empty && game.summary.length === 0) {
+ delete game.summary
+ return
+ }
+
+ let lines = game.summary.map(function (move) {
+ let s = ""
+ for (let i = 0; i < move.length; ++i) {
+ let x = move[i]
+ if (i > 0)
+ s += " \u2192 "
+ if (typeof x === 'number')
+ s += "#" + x
+ else
+ s += x
+ }
+ return s
+ }).sort()
+ delete game.summary
+
+ log(text)
+
+ let last = lines[0]
let n = 0
- function print_move(last) {
- return "\n" + n + " " + last.join(" \u2192 ")
- }
- if (!skip_if_empty || game.summary.length > 0) {
- game.summary.sort()
- let last = game.summary[0]
- for (let entry of game.summary) {
- if (entry.toString() !== last.toString()) {
- text += print_move(last)
- n = 0
- }
- ++n
- last = entry
+ for (let entry of lines) {
+ if (entry !== last) {
+ logi(n + " " + last)
+ n = 0
}
- if (n > 0)
- text += print_move(last)
- else
- text += "\nnothing."
- log(text)
+ ++n
+ last = entry
}
- delete game.summary
+ if (n > 0)
+ logi(n + " " + last)
+ else
+ logi("nothing.")
}
function enemy(p) {
@@ -169,56 +198,6 @@ function remove_from_array(array, item) {
array.splice(i, 1)
}
-function deep_copy(original) {
- if (Array.isArray(original)) {
- let n = original.length
- let copy = new Array(n)
- for (let i = 0; i < n; ++i) {
- let v = original[i]
- if (typeof v === "object" && v !== null)
- copy[i] = deep_copy(v)
- else
- copy[i] = v
- }
- return copy
- } else {
- let copy = {}
- for (let i in original) {
- let v = original[i]
- if (typeof v === "object" && v !== null)
- copy[i] = deep_copy(v)
- else
- copy[i] = v
- }
- return copy
- }
-}
-
-function push_undo() {
- let copy = {}
- for (let k in game) {
- let v = game[k]
- if (k === "undo") continue
- else if (k === "log") v = v.length
- else if (typeof v === "object" && v !== null) v = deep_copy(v)
- copy[k] = v
- }
- game.undo.push(copy)
-}
-
-function pop_undo() {
- let save_log = game.log
- let save_undo = game.undo
- game = save_undo.pop()
- save_log.length = game.log
- game.log = save_log
- game.undo = save_undo
-}
-
-function clear_undo() {
- game.undo = []
-}
-
function gen_action_undo(view) {
if (!view.actions)
view.actions = {}
@@ -239,7 +218,7 @@ function gen_action(view, action, argument) {
view.actions[action].push(argument)
}
} else {
- view.actions[action] = true
+ view.actions[action] = 1
}
}
@@ -266,28 +245,30 @@ function deal_cards(deck, n) {
function select_random_block(where) {
let list = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where)
list.push(b)
if (list.length === 0)
- return null
+ return NOBODY
return list[random(list.length)]
}
function select_random_enemy_block(where) {
let list = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === enemy(game.active))
list.push(b)
if (list.length === 0)
- return null
+ return NOBODY
return list[random(list.length)]
}
+function town_name(where) {
+ return TOWNS[where].name
+}
+
function block_name(who) {
- if (BLOCKS[who].type === 'nomads')
- return BLOCKS[who].name
- return who
+ return BLOCKS[who].name
}
function block_type(who) {
@@ -295,52 +276,23 @@ function block_type(who) {
}
function block_home(who) {
- let home = BLOCKS[who].home
- if (home === "Normandy") return "England"
- if (home === "Aquitaine") return "England"
- if (home === "Bourgogne") return "France"
- if (home === "Flanders") return "France"
- return home
+ return BLOCKS[who].home
}
function list_seats(who) {
- switch (block_type(who)) {
- case 'nomads':
+ if (block_type(who) === 'nomads')
return [ block_home(who) ]
- case 'turcopoles':
- who = "Turcopoles"
- break
- case 'military_orders':
- who = BLOCKS[who].name
- break
- }
- if (is_saladin_family(who))
- who = SALADIN
- if (who === "Raymond (Tiberias)" || who === "Raymond (Tripoli)")
- who = "Raymond"
let list = []
- for (let town in SHIELDS)
- if (SHIELDS[town].includes(who))
+ for (let town = first_town; town <= last_town; ++town)
+ if (set_has(SHIELDS[town], who))
list.push(town)
return list
}
function is_home_seat(where, who) {
- if (is_saladin_family(who))
- who = SALADIN
- switch (block_type(who)) {
- case 'nomads':
+ if (block_type(who) === 'nomads')
return where === block_home(who)
- case 'turcopoles':
- who = "Turcopoles"
- break
- case 'military_orders':
- who = BLOCKS[who].name
- break
- }
- if (who === "Raymond (Tiberias)" || who === "Raymond (Tripoli)")
- who = "Raymond"
- if (SHIELDS[where] && SHIELDS[where].includes(who))
+ if (set_has(SHIELDS[where], who))
return true
return false
}
@@ -356,11 +308,11 @@ function block_owner(who) {
}
function block_initiative(who) {
- return BLOCKS[who].combat[0]
+ return BLOCKS[who].initiative
}
function block_fire_power(who) {
- return BLOCKS[who].combat[1] | 0
+ return BLOCKS[who].fire_power
}
function block_move(who) {
@@ -372,11 +324,11 @@ function block_max_steps(who) {
}
function is_saladin_family(who) {
- return who === "Saladin" || who === "Al Adil" || who === "Al Aziz" || who === "Al Afdal" || who === "Al Zahir"
+ return who === SALADIN || who === AL_ADIL || who === AL_AZIZ || who === AL_AFDAL || who === AL_ZAHIR
}
function is_english_crusader(who) {
- return (who === "Richard" || who === "Robert" || who === "Crossbows")
+ return who === RICHARD || who === ROBERT || who === CROSSBOWS
}
function are_crusaders_not_in_pool(crusaders) {
@@ -398,7 +350,7 @@ function is_block_on_land(who) {
}
function road_id(a, b) {
- return (a < b) ? a + "/" + b : b + "/" + a
+ return (a < b) ? a * 100 + b : b * 100 + a
}
function road_was_last_used_by_enemy(from, to) {
@@ -423,7 +375,7 @@ function reset_road_limits() {
function count_player(p, where) {
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === p)
++count
return count
@@ -432,7 +384,7 @@ function count_player(p, where) {
function count_friendly(where) {
let p = game.active
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === p)
++count
return count
@@ -441,7 +393,7 @@ function count_friendly(where) {
function count_enemy(where) {
let p = enemy(game.active)
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === p)
++count
return count
@@ -450,7 +402,7 @@ function count_enemy(where) {
function count_friendly_in_field(where) {
let p = game.active
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === p)
if (!is_block_in_castle(b))
++count
@@ -460,7 +412,7 @@ function count_friendly_in_field(where) {
function count_enemy_in_field(where) {
let p = enemy(game.active)
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === p)
if (!is_block_in_castle(b))
++count
@@ -470,7 +422,7 @@ function count_enemy_in_field(where) {
function count_friendly_in_field_excluding_reserves(where) {
let p = game.active
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === p)
if (!is_block_in_castle(b) && !is_reserve(b))
++count
@@ -480,7 +432,7 @@ function count_friendly_in_field_excluding_reserves(where) {
function count_enemy_in_field_excluding_reserves(where) {
let p = enemy(game.active)
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === p)
if (!is_block_in_castle(b) && !is_reserve(b))
++count
@@ -489,24 +441,24 @@ function count_enemy_in_field_excluding_reserves(where) {
function count_blocks_in_castle(where) {
let n = 0
- for (let b of BLOCKLIST)
- if (game.location[b] === where && game.castle.includes(b))
+ for (let b = 0; b <= last_block; ++b)
+ if (game.location[b] === where && set_has(game.castle, b))
++n
return n
}
function count_enemy_in_field_and_reserve(where) {
let n = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (block_owner(b) !== game.active)
- if (game.location[b] === where && !game.castle.includes(b))
+ if (game.location[b] === where && !set_has(game.castle, b))
++n
return n
}
function count_reserves(where) {
let n = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (block_owner(b) === game.active)
if (game.location[b] === where && is_reserve(b))
++n
@@ -573,7 +525,7 @@ function is_enemy_battle_field() {
}
function is_reserve(who) {
- return game.reserves1.includes(who) || game.reserves2.includes(who)
+ return set_has(game.reserves1, who) || set_has(game.reserves2, who)
}
function is_field_attacker(who) {
@@ -599,7 +551,7 @@ function is_block_in_field(who) {
}
function is_siege_attacker(who) {
- return game.storming.includes(who)
+ return set_has(game.storming, who)
}
function is_siege_defender(who) {
@@ -607,7 +559,7 @@ function is_siege_defender(who) {
}
function is_siege_combatant(who) {
- return game.storming.includes(who) || is_block_in_castle_in(who, game.where)
+ return set_has(game.storming, who) || is_block_in_castle_in(who, game.where)
}
function castle_limit(where) {
@@ -631,15 +583,15 @@ function is_under_siege(where) {
}
function is_block_in_castle(b) {
- return game.castle.includes(b)
+ return set_has(game.castle, b)
}
function is_block_in_castle_in(b, town) {
- return game.location[b] === town && game.castle.includes(b)
+ return game.location[b] === town && set_has(game.castle, b)
}
function besieged_player(where) {
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (is_block_in_castle_in(b, where))
return block_owner(b)
return null
@@ -661,13 +613,13 @@ function can_activate(who) {
return block_owner(who) === game.active &&
is_block_on_map(who) &&
!is_block_in_castle(who) &&
- !game.moved[who]
+ !set_has(game.moved, who)
}
function can_activate_for_sea_move(who) {
return block_owner(who) === game.active &&
is_block_on_map(who) &&
- !game.moved[who]
+ !set_has(game.moved, who)
}
function count_pinning(where) {
@@ -676,7 +628,7 @@ function count_pinning(where) {
function count_pinned(where) {
let count = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === where && block_owner(b) === game.active)
if (!is_reserve(b))
++count
@@ -916,7 +868,7 @@ function can_block_muster_with_3_moves(n0, muster) {
if (can_block_use_road_to_muster(n1, n2)) {
if (n2 === muster)
return true
- if (TOWNS[n2].exits.includes(muster))
+ if (set_has(TOWNS[n2].exits, muster))
if (can_block_use_road_to_muster(n2, muster))
return true
}
@@ -933,7 +885,7 @@ function can_block_muster_with_2_moves(n0, muster, avoid) {
if (can_block_use_road_to_muster(n0, n1)) {
if (n1 === muster)
return true
- if (TOWNS[n1].exits.includes(muster))
+ if (set_has(TOWNS[n1].exits, muster))
if (can_block_use_road_to_muster(n1, muster))
return true
}
@@ -942,7 +894,7 @@ function can_block_muster_with_2_moves(n0, muster, avoid) {
}
function can_block_muster_with_1_move(n0, muster) {
- if (TOWNS[n0].exits.includes(muster))
+ if (set_has(TOWNS[n0].exits, muster))
return can_block_use_road_to_muster(n0, muster)
return false
}
@@ -957,20 +909,20 @@ function can_block_muster(who, muster) {
if (block_move(who) === 3)
return can_block_muster_with_3_moves(from, muster)
else
- return can_block_muster_with_2_moves(from, muster, null)
+ return can_block_muster_with_2_moves(from, muster, NOWHERE)
}
return false
}
function can_muster_to(muster) {
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (can_block_muster(b, muster))
return true
return false
}
function can_muster_anywhere() {
- for (let where of TOWNLIST)
+ for (let where = first_town; where <= last_town; ++where)
if (is_friendly_field(where))
if (can_muster_to(where))
return true
@@ -979,21 +931,21 @@ function can_muster_anywhere() {
function lift_siege(where) {
if (is_under_siege(where) && !is_contested_town(where)) {
- log("Siege lifted in " + where + ".")
- for (let b of BLOCKLIST)
+ log("Siege lifted at #" + where + ".")
+ for (let b = 0; b <= last_block; ++b)
if (is_block_in_castle_in(b, where))
- remove_from_array(game.castle, b)
+ set_delete(game.castle, b)
}
}
function lift_all_sieges() {
- for (let t of TOWNLIST)
- lift_siege(t)
+ for (let town = first_town; town <= last_town; ++town)
+ lift_siege(town)
}
function reset_blocks() {
- for (let b of BLOCKLIST) {
- game.location[b] = ELIMINATED
+ for (let b = 0; b <= last_block; ++b) {
+ game.location[b] = NOWHERE
game.steps[b] = block_max_steps(b)
}
}
@@ -1006,19 +958,19 @@ function deploy(who, where) {
function disband(who) {
game.summary.push([game.location[who]])
if (is_saladin_family(who) || block_type(who) === 'crusaders' || block_type(who) === 'military_orders')
- game.location[who] = ELIMINATED // permanently eliminated
+ game.location[who] = NOWHERE // permanently eliminated
else
game.location[who] = DEAD // into to the pool next year
game.steps[who] = block_max_steps(who)
}
function eliminate_block(who) {
- remove_from_array(game.castle, who)
- if (game.sallying) remove_from_array(game.sallying, who)
- if (game.storming) remove_from_array(game.storming, who)
+ set_delete(game.castle, who)
+ if (game.sallying) set_delete(game.sallying, who)
+ if (game.storming) set_delete(game.storming, who)
log(block_name(who) + " was eliminated.")
if (is_saladin_family(who) || block_type(who) === 'crusaders' || block_type(who) === 'military_orders')
- game.location[who] = ELIMINATED // permanently eliminated
+ game.location[who] = NOWHERE // permanently eliminated
else
game.location[who] = DEAD // into to the pool next year
game.steps[who] = block_max_steps(who)
@@ -1036,9 +988,9 @@ function reduce_block(who) {
function is_valid_frank_deployment() {
let errors = []
- for (let town of TOWNLIST)
+ for (let town = first_town; town <= last_town; ++town)
if (!is_within_castle_limit(town))
- errors.push(town)
+ errors.push(TOWNS[town].name)
return errors
}
@@ -1055,7 +1007,7 @@ states.frank_deployment = {
let errors = is_valid_frank_deployment()
if (errors.length === 0)
gen_action(view, 'next')
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === game.active && is_block_on_land(b))
if (list_seats(b).length > 1)
gen_action(view, 'block', b)
@@ -1081,7 +1033,7 @@ states.frank_deployment_to = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Deployment: Waiting for " + game.active + "."
- view.prompt = "Deployment: Move " + game.who + " to " + join(list_seats(game.who), "or") + "."
+ view.prompt = "Deployment: Move " + game.who + " to " + join(list_seats(game.who).map(town_name), "or") + "."
gen_action_undo(view)
gen_action(view, 'block', game.who)
let from = game.location[game.who]
@@ -1091,7 +1043,7 @@ states.frank_deployment_to = {
},
town: function (where) {
game.location[game.who] = where
- game.who = null
+ game.who = NOBODY
game.state = 'frank_deployment'
},
block: pop_undo,
@@ -1101,7 +1053,7 @@ states.frank_deployment_to = {
function goto_saracen_deployment() {
for (let i = 0; i < 4; ++i) {
let nomad = select_random_block(S_POOL)
- log(BLOCKS[nomad].name + " arrived in " + block_home(nomad) + ".")
+ log(BLOCKS[nomad].name + " arrived in #" + block_home(nomad) + ".")
deploy(nomad, block_home(nomad))
}
game.active = SARACENS
@@ -1127,11 +1079,11 @@ states.saracen_deployment = {
let saladin = game.location[SALADIN]
game.location[SALADIN] = game.location[who]
game.location[who] = saladin
- game.who = null
+ game.who = NOBODY
},
next: function () {
clear_undo()
- game.who = null
+ game.who = NOBODY
start_year()
},
undo: pop_undo
@@ -1180,7 +1132,7 @@ function check_sudden_death() {
function start_year() {
log("")
- log("Start Year " + game.year)
+ log(".h1 Year " + game.year)
game.turn = 1
@@ -1195,7 +1147,7 @@ function start_year() {
function start_game_turn() {
log("")
- log("Start Turn " + game.turn + " of Year " + game.year)
+ log(".h1 Turn " + game.turn + " of Year " + game.year)
game.guide = null
game.jihad = null
@@ -1204,9 +1156,9 @@ function start_game_turn() {
reset_road_limits()
game.last_used = {}
game.attacker = {}
- game.reserves1 = []
- game.reserves2 = []
- game.moved = {}
+ set_clear(game.reserves1)
+ set_clear(game.reserves2)
+ set_clear(game.moved)
goto_card_phase()
}
@@ -1350,7 +1302,7 @@ function reveal_cards() {
function start_player_turn() {
log("")
- log("Start " + game.active)
+ log(".h2 " + game.active)
reset_road_limits()
game.main_road = {}
let card = CARDS[game.active === FRANKS ? game.f_card : game.s_card]
@@ -1393,7 +1345,7 @@ states.assassins = {
if (is_inactive_player(current))
return view.prompt = "Assassins: Waiting for " + game.active + "."
view.prompt = "Assassins: Choose one enemy block."
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (is_block_on_land(b) && block_owner(b) === enemy(game.active))
gen_action(view, 'block', b)
}
@@ -1412,7 +1364,7 @@ states.assassins_show_1 = {
view.who = ASSASSINS
if (is_inactive_player(current))
return view.prompt = "Assassins: Waiting for " + game.active + "."
- view.prompt = "Assassins: The assassins target " + block_name(game.who) + " in " + game.where + "."
+ view.prompt = "Assassins: The assassins target " + block_name(game.who) + " in " + town_name(game.where) + "."
gen_action(view, 'next')
gen_action(view, 'block', game.who)
gen_action(view, 'block', ASSASSINS)
@@ -1432,7 +1384,7 @@ states.assassins_show_2 = {
view.who = ASSASSINS
if (is_inactive_player(current))
return view.prompt = "Assassins: Waiting for " + game.active + "."
- view.prompt = "Assassins: The assassins hit " + block_name(game.who) + " in " + game.where + "."
+ view.prompt = "Assassins: The assassins hit " + block_name(game.who) + " in " + town_name(game.where) + "."
gen_action(view, 'next')
gen_action(view, 'block', ASSASSINS)
gen_action(view, 'town', MASYAF)
@@ -1445,8 +1397,8 @@ states.assassins_show_2 = {
function assassins_next_2() {
lift_siege(game.where)
game.location[ASSASSINS] = MASYAF
- game.who = null
- game.where = null
+ game.who = NOBODY
+ game.where = NOWHERE
end_player_turn()
}
@@ -1463,7 +1415,7 @@ function assassinate(who, where) {
}
}
hits = Math.min(hits, game.steps[who])
- log("Assassins hit " + block_name(who) + " in " + where + ": " + rolls.join("") + ".")
+ log("Assassins hit " + block_name(who) + " at #" + where + ": " + rolls.join("") + ".")
for (let i = 0; i < hits; ++i)
reduce_block(who)
}
@@ -1482,7 +1434,7 @@ function goto_jihad() {
function goto_select_jihad() {
game.jihad_list = []
- for (let where of TOWNLIST)
+ for (let where = first_town; where <= last_town; ++where)
if (is_contested_field(where) || besieging_player(where) === game.active)
game.jihad_list.push(where)
if (game.jihad_list.length === 0) {
@@ -1517,7 +1469,7 @@ states.select_jihad = {
function goto_manna() {
game.state = 'manna'
game.moves = 3
- game.moved = {}
+ set_clear(game.moved)
game.summary = []
}
@@ -1529,8 +1481,8 @@ states.manna = {
gen_action_undo(view)
gen_action(view, 'next')
if (game.moves > 0) {
- for (let b of BLOCKLIST) {
- if (is_block_on_land(b) && block_owner(b) === game.active && !game.moved[b])
+ for (let b = 0; b <= last_block; ++b) {
+ if (is_block_on_land(b) && block_owner(b) === game.active && !set_has(game.moved, b))
if (game.steps[b] < block_max_steps(b))
gen_action(view, 'block', b)
}
@@ -1541,12 +1493,12 @@ states.manna = {
game.summary.push([game.location[who]])
++game.steps[who]
--game.moves
- game.moved[who] = 1
+ set_add(game.moved, who)
},
next: function () {
clear_undo()
print_summary(game.active + " used Manna:")
- game.moved = {}
+ set_clear(game.moved)
end_player_turn()
},
undo: pop_undo
@@ -1558,11 +1510,11 @@ function queue_attack(who, round) {
if (round === 1)
return ATTACK_MARK
if (round === 2) {
- game.reserves1.push(who)
+ set_add(game.reserves1, who)
return RESERVE_MARK_1
}
if (round === 3) {
- game.reserves2.push(who)
+ set_add(game.reserves2, who)
return RESERVE_MARK_2
}
}
@@ -1619,8 +1571,8 @@ function end_move_phase() {
}
clear_undo()
- game.who = null
- game.where = null
+ game.who = NOBODY
+ game.where = NOWHERE
game.moves = 0
// declined to use winter campaign
@@ -1669,7 +1621,7 @@ states.move_phase = {
gen_action_undo(view)
gen_action(view, 'end_move_phase')
if (game.moves > 0) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (can_block_land_move(b))
gen_action(view, 'block', b)
if (can_block_sea_move(b))
@@ -1717,7 +1669,7 @@ states.move_phase_event = {
view.prompt = group_move_name(0) + "Choose a block to group move."
gen_action_undo(view)
gen_action(view, 'end_move_phase')
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (can_block_land_move(b))
gen_action(view, 'block', b)
},
@@ -1726,7 +1678,7 @@ states.move_phase_event = {
game.where = game.location[who]
game.who = who
game.distance = 0
- game.last_from = null
+ game.last_from = NOWHERE
game.state = 'group_move_to'
},
end_move_phase: end_move_phase,
@@ -1777,7 +1729,7 @@ states.move_phase_to = {
log_move_start(from)
let mark = move_block(game.who, from, to)
if (mark)
- log_move_continue(to + mark)
+ log_move_continue(to, mark)
else
log_move_continue(to)
lift_siege(from)
@@ -1801,7 +1753,7 @@ function group_move_name() {
}
function can_group_move_more() {
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === game.where)
if (can_block_land_move(b))
return true
@@ -1812,13 +1764,13 @@ states.group_move_who = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = group_move_name(1) + "Waiting for " + game.active + "."
- view.prompt = group_move_name(0) + "Move blocks from " + game.where + "."
+ view.prompt = group_move_name(0) + "Move blocks from " + town_name(game.where) + "."
gen_action_undo(view)
if (game.active === game.guide || game.active === game.jihad)
gen_action(view, 'end_move_phase')
else
gen_action(view, 'end_group_move')
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === game.where)
if (can_block_land_move(b))
gen_action(view, 'block', b)
@@ -1827,7 +1779,7 @@ states.group_move_who = {
push_undo()
game.who = who
game.distance = 0
- game.last_from = null
+ game.last_from = NOWHERE
game.state = 'group_move_to'
},
end_move_phase: function () {
@@ -1868,7 +1820,7 @@ states.group_move_to = {
log_move_start(from)
let mark = move_block(game.who, from, to)
if (mark)
- log_move_continue(to + mark)
+ log_move_continue(to, mark)
else
log_move_continue(to)
lift_siege(from)
@@ -1882,9 +1834,9 @@ states.group_move_to = {
function end_move() {
if (game.distance > 0)
- game.moved[game.who] = 1
+ set_add(game.moved, game.who)
log_move_end()
- game.who = null
+ game.who = NOBODY
game.distance = 0
if (can_group_move_more())
game.state = 'group_move_who'
@@ -1893,7 +1845,7 @@ function end_move() {
}
function end_group_move() {
- print_summary(game.active + " activated " + game.where + ":")
+ print_summary(game.active + " activated #" + game.where + ":")
game.state = 'move_phase'
}
@@ -1914,14 +1866,14 @@ states.german_move_to = {
--game.moves
let from = GERMANIA
game.location[game.who] = to
- game.moved[game.who] = 1
+ set_add(game.moved, game.who)
game.distance = 0
let mark = move_block(game.who, from, to)
if (mark)
- log(game.active + " moved:\n Germania \u2192 " + to + mark + ".")
+ log(game.active + " moved:\n Germania \u2192 #" + to + mark + ".")
else
- log(game.active + " moved:\n Germania \u2192 " + to + ".")
- game.who = null
+ log(game.active + " moved:\n Germania \u2192 #" + to + ".")
+ game.who = NOBODY
game.state = 'move_phase'
},
block: pop_undo,
@@ -1954,29 +1906,32 @@ states.sea_move_to = {
let from = game.where
game.location[game.who] = to
- game.moved[game.who] = 1
+ set_add(game.moved, game.who)
lift_siege(from)
- remove_from_array(game.castle, game.who)
+ set_delete(game.castle, game.who)
if (besieged_player(to) === game.active && is_more_room_in_castle(to)) {
// Move into besieged fortified port
- game.castle.push(game.who)
- log(game.active + " sea moved:\n" + from + " \u2192 " + to + " castle.")
+ set_add(game.castle, game.who)
+ log(game.active + " sea moved:")
+ logi("#" + from + " \u2192 #" + to + " castle.")
} else if (!is_friendly_port(to)) {
// English Crusaders attack!
game.attacker[to] = FRANKS
game.main_road[to] = "England"
- log(game.active + " sea moved:\n" + from + " \u2192 " + to + ATTACK_MARK + ".")
+ log(game.active + " sea moved:")
+ logi("#" + from + " \u2192 #" + to + ATTACK_MARK + ".")
} else {
// Normal move.
- log(game.active + " sea moved:\n" + from + " \u2192 " + to + ".")
+ log(game.active + " sea moved:")
+ logi("#" + from + " \u2192 #" + to + ".")
}
- game.who = null
+ game.who = NOBODY
game.state = 'move_phase'
},
block: pop_undo,
@@ -1991,7 +1946,7 @@ states.muster = {
return view.prompt = "Move Phase: Waiting for " + game.active + "."
view.prompt = "Muster: Choose a friendly muster town."
gen_action_undo(view)
- for (let where of TOWNLIST) {
+ for (let where = first_town; where <= last_town; ++where) {
// cannot start or reinforce battles in winter
if (is_winter()) {
if (is_friendly_town(where))
@@ -2017,11 +1972,11 @@ states.muster_who = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Move Phase: Waiting for " + game.active + "."
- view.prompt = "Muster: Move blocks to " + game.where + "."
+ view.prompt = "Muster: Move blocks to " + town_name(game.where) + "."
view.muster = game.where
gen_action_undo(view)
gen_action(view, 'end_muster')
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (can_block_muster(b, game.where))
gen_action(view, 'block', b)
},
@@ -2031,8 +1986,8 @@ states.muster_who = {
game.state = 'muster_move_1'
},
end_muster: function () {
- print_summary(game.active + " mustered to " + game.where + ":")
- game.where = null
+ print_summary(game.active + " mustered to #" + game.where + ":")
+ game.where = NOWHERE
game.state = 'move_phase'
},
undo: pop_undo,
@@ -2042,7 +1997,7 @@ states.muster_move_1 = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Move Phase: Waiting for " + game.active + "."
- view.prompt = "Muster: Move " + block_name(game.who) + " to " + game.where + "."
+ view.prompt = "Muster: Move " + block_name(game.who) + " to " + town_name(game.where) + "."
view.muster = game.where
gen_action_undo(view)
gen_action(view, 'block', game.who)
@@ -2084,7 +2039,7 @@ states.muster_move_2 = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Move Phase: Waiting for " + game.active + "."
- view.prompt = "Muster: Move " + block_name(game.who) + " to " + game.where + "."
+ view.prompt = "Muster: Move " + block_name(game.who) + " to " + town_name(game.where) + "."
view.muster = game.where
gen_action_undo(view)
let from = game.location[game.who]
@@ -2122,7 +2077,7 @@ states.muster_move_3 = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Move Phase: Waiting for " + game.active + "."
- view.prompt = "Muster: Move " + block_name(game.who) + " to " + game.where + "."
+ view.prompt = "Muster: Move " + block_name(game.who) + " to " + town_name(game.where) + "."
view.muster = game.where
gen_action_undo(view)
let from = game.location[game.who]
@@ -2145,8 +2100,8 @@ states.muster_move_3 = {
function end_muster_move() {
log_move_end()
- game.moved[game.who] = 1
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = NOBODY
game.state = 'muster_who'
}
@@ -2158,12 +2113,12 @@ states.winter_campaign = {
return view.prompt = "Move Phase: Waiting for " + game.active + "."
view.prompt = "Winter Campaign: Select a siege to maintain over the winter."
gen_action_undo(view)
- for (let town of TOWNLIST)
+ for (let town = first_town; town <= last_town; ++town)
if (is_friendly_field(town) && is_under_siege(town))
gen_action(view, 'town', town)
},
town: function (where) {
- log(game.active + " winter campaigned in " + where + ".")
+ log(game.active + " winter campaigned at #" + where + ".")
game.winter_campaign = where
game.state = 'move_phase'
},
@@ -2174,14 +2129,14 @@ states.winter_campaign = {
function goto_combat_phase() {
if (is_winter()) {
- game.moved = {}
+ set_clear(game.moved)
return end_game_turn()
}
game.combat_list = []
- for (let where of TOWNLIST)
+ for (let where = first_town; where <= last_town; ++where)
if (is_contested_town(where))
- game.combat_list.push(where)
+ set_add(game.combat_list, where)
resume_combat_phase()
}
@@ -2206,7 +2161,7 @@ states.combat_phase = {
gen_action(view, 'town', where)
},
town: function (where) {
- remove_from_array(game.combat_list, where)
+ set_delete(game.combat_list, where)
game.where = where
start_combat()
},
@@ -2215,9 +2170,9 @@ states.combat_phase = {
function start_combat() {
game.flash = ""
log("")
- log("Battle in " + game.where)
+ log(".h3 Battle at #" + game.where)
game.combat_round = 0
- game.halfhit = null
+ game.halfhit = NOBODY
game.storming = []
game.sallying = []
@@ -2226,7 +2181,7 @@ function start_combat() {
if (is_castle_town(game.where)) {
if (!is_under_siege(game.where)) {
- log("~ Combat Deployment ~")
+ log(".h4 Combat Deployment")
game.castle_owner = enemy(game.attacker[game.where])
game.active = game.castle_owner
game.state = 'combat_deployment'
@@ -2257,7 +2212,7 @@ function end_combat() {
delete game.sallying
delete game.show_castle
delete game.show_field
- game.where = null
+ game.where = NOWHERE
game.flash = ""
game.combat_round = 0
@@ -2276,9 +2231,9 @@ states.combat_deployment = {
let n = count_blocks_in_castle(game.where)
let have_options = false
if (n < max) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === game.active && !is_reserve(b)) {
- if (game.location[b] === game.where && !game.castle.includes(b)) {
+ if (game.location[b] === game.where && !set_has(game.castle, b)) {
gen_action(view, 'withdraw', b)
gen_action(view, 'block', b)
have_options = true
@@ -2293,11 +2248,11 @@ states.combat_deployment = {
},
withdraw: function (who) {
push_undo()
- game.castle.push(who)
+ set_add(game.castle, who)
},
block: function (who) {
push_undo()
- game.castle.push(who)
+ set_add(game.castle, who)
},
next: function () {
clear_undo()
@@ -2319,7 +2274,7 @@ states.combat_deployment = {
function print_retreat_summary() {
if (game.summary && game.summary.length > 0)
- print_summary("Retreated from " + game.where + ":")
+ print_summary("Retreated from #" + game.where + ":")
}
function goto_regroup() {
@@ -2339,7 +2294,7 @@ states.regroup = {
view.prompt = "Regroup: Choose a block to move."
gen_action_undo(view)
gen_action(view, 'end_regroup')
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (game.location[b] === game.where) {
if (can_block_regroup(b))
gen_action(view, 'block', b)
@@ -2377,13 +2332,13 @@ states.regroup_to = {
},
town: function (to) {
// We can regroup while reserves are still on the way...
- remove_from_array(game.reserves1, game.who)
- remove_from_array(game.reserves2, game.who)
+ set_delete(game.reserves1, game.who)
+ set_delete(game.reserves2, game.who)
let from = game.where
game.summary.push([from, to])
move_block(game.who, game.where, to)
- game.who = null
+ game.who = NOBODY
game.state = 'regroup'
},
block: pop_undo,
@@ -2407,14 +2362,14 @@ function next_combat_round() {
function bring_on_reserves(reserves) {
let f = 0
let s = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (game.location[b] === game.where) {
- if (reserves.includes(b)) {
+ if (set_has(reserves, b)) {
if (block_owner(b) === FRANKS)
++f
else
++s
- remove_from_array(reserves, b)
+ set_delete(reserves, b)
}
}
}
@@ -2425,18 +2380,18 @@ function bring_on_reserves(reserves) {
}
function clear_reserves(where) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (game.location[b] === where) {
- remove_from_array(game.reserves1, b)
- remove_from_array(game.reserves2, b)
+ set_delete(game.reserves1, b)
+ set_delete(game.reserves2, b)
}
}
}
function reset_moved_for_combat() {
- for (let b in game.moved) game.moved[b] = 0
- for (let b of game.reserves1) game.moved[b] = 1
- for (let b of game.reserves2) game.moved[b] = 1
+ set_clear(game.moved)
+ for (let b of game.reserves1) set_add(game.moved, b)
+ for (let b of game.reserves2) set_add(game.moved, b)
}
function goto_combat_round(new_combat_round) {
@@ -2455,7 +2410,7 @@ function goto_combat_round(new_combat_round) {
}
}
- log("~ Combat Round " + game.combat_round + " ~")
+ log(".h4 Combat Round " + game.combat_round)
if (game.combat_round === 2)
bring_on_reserves(game.reserves1)
@@ -2470,7 +2425,7 @@ function goto_combat_round(new_combat_round) {
log("Relief forces arrived!")
if (game.storming.length > 0) {
log("Storming canceled by arriving relief force.")
- game.halfhit = null
+ game.halfhit = NOBODY
game.storming.length = 0
}
let old_attacker = game.attacker[game.where]
@@ -2510,9 +2465,9 @@ states.declare_storm = {
view.prompt = "Siege Declaration: Declare which blocks should storm the castle."
let have_options = false
if (game.storming.length < castle_limit(game.where)) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === game.active && !is_reserve(b)) {
- if (game.location[b] === game.where && !game.storming.includes(b)) {
+ if (game.location[b] === game.where && !set_has(game.storming, b)) {
gen_action(view, 'storm', b)
gen_action(view, 'block', b)
have_options = true
@@ -2545,7 +2500,7 @@ states.declare_storm = {
function storm_with_block(who) {
push_undo()
- game.storming.push(who)
+ set_add(game.storming, who)
if (game.storming.length > 1)
game.flash = game.active + " stormed with " + game.storming.length + " blocks."
else
@@ -2568,9 +2523,9 @@ states.declare_sally = {
return view.prompt = "Siege Declaration: Waiting for " + game.active + " to declare sally."
view.prompt = "Siege Declaration: Declare which blocks should sally onto the field."
let have_options = false
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === game.active && !is_reserve(b) && is_block_in_castle(b)) {
- if (game.location[b] === game.where && !game.sallying.includes(b)) {
+ if (game.location[b] === game.where && !set_has(game.sallying, b)) {
gen_action(view, 'sally', b)
gen_action(view, 'block', b)
have_options = true
@@ -2608,8 +2563,8 @@ states.declare_sally = {
function sally_with_block(who) {
push_undo()
- remove_from_array(game.castle, who)
- game.sallying.push(who)
+ set_delete(game.castle, who)
+ set_add(game.sallying, who)
if (game.sallying.length > 1)
game.flash = game.active + " sallied with " + game.sallying.length + " blocks."
else
@@ -2624,17 +2579,17 @@ function goto_retreat_after_combat() {
// withdraw all sallying blocks to castle.
for (let b of game.sallying)
- game.castle.push(b)
+ set_add(game.castle, b)
game.sallying.length = 0
// TODO: 6.2 - In Sieges, the attacker /may/ retreat or stay on siege.
// withdraw all storming blocks to the field.
- game.halfhit = null
+ game.halfhit = NOBODY
game.storming.length = 0
if (is_contested_field(game.where)) {
- log("~ Retreat ~")
+ log(".h4 Retreat")
game.active = game.attacker[game.where]
game.state = 'retreat'
game.summary = []
@@ -2652,7 +2607,7 @@ states.retreat = {
view.prompt = "Retreat: Choose a block to move."
gen_action_undo(view)
let can_retreat = false
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (game.location[b] === game.where && !is_block_in_castle(b) && can_block_retreat(b)) {
gen_action(view, 'block', b)
can_retreat = true
@@ -2663,7 +2618,7 @@ states.retreat = {
},
end_retreat: function () {
clear_undo()
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === game.where && !is_block_in_castle(b) && block_owner(b) === game.active)
eliminate_block(b)
print_summary(game.active + " retreated:")
@@ -2699,12 +2654,12 @@ states.retreat_to = {
let from = game.where
game.summary.push([from, to])
move_block(game.who, game.where, to)
- game.who = null
+ game.who = NOBODY
game.state = 'retreat'
},
eliminate: function () {
eliminate_block(game.who)
- game.who = null
+ game.who = NOBODY
game.state = 'retreat'
},
block: pop_undo,
@@ -2714,13 +2669,13 @@ states.retreat_to = {
// SIEGE ATTRITION
function goto_siege_attrition() {
- log("~ Siege Attrition ~")
+ log(".h4 Siege Attrition")
game.active = besieged_player(game.where)
game.state = 'siege_attrition'
game.attrition_list = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (is_block_in_castle_in(b, game.where))
- game.attrition_list.push(b)
+ set_add(game.attrition_list, b)
}
function resume_siege_attrition() {
@@ -2728,7 +2683,7 @@ function resume_siege_attrition() {
delete game.attrition_list
if (!is_under_siege(game.where)) {
game.active = enemy(game.active)
- log(game.where + " fell to siege attrition.")
+ log("#" + game.where + " fell to siege attrition.")
goto_regroup()
} else {
log("Siege continued.")
@@ -2741,7 +2696,7 @@ states.siege_attrition = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Siege Attrition: Waiting for " + game.active + "."
- view.prompt = "Siege Attrition: Roll for siege attrition in " + game.where + "."
+ view.prompt = "Siege Attrition: Roll for siege attrition in " + town_name(game.where) + "."
for (let b of game.attrition_list)
gen_action(view, 'block', b)
},
@@ -2754,7 +2709,7 @@ states.siege_attrition = {
} else {
log("Attrition roll " + DIE_MISS[die] + ".")
}
- remove_from_array(game.attrition_list, who)
+ set_delete(game.attrition_list, who)
resume_siege_attrition()
}
}
@@ -2763,8 +2718,8 @@ states.siege_attrition = {
function filter_battle_blocks(ci, is_candidate) {
let output = null
- for (let b of BLOCKLIST) {
- if (is_candidate(b) && !game.moved[b]) {
+ for (let b = 0; b <= last_block; ++b) {
+ if (is_candidate(b) && !set_has(game.moved, b)) {
if (block_initiative(b) === ci) {
if (!output)
output = []
@@ -2870,7 +2825,7 @@ states.field_battle = {
for (let b of game.battle_list) {
gen_action(view, 'block', b) // take default action
gen_action(view, 'fire', b)
- if (game.sallying.includes(b)) {
+ if (set_has(game.sallying, b)) {
// Only sallying forces may withdraw into the castle
gen_action(view, 'withdraw', b)
} else {
@@ -2919,7 +2874,7 @@ function resume_siege_battle() {
if (is_enemy_town(game.where)) {
game.active = enemy(game.active)
- game.halfhit = null
+ game.halfhit = NOBODY
log("Siege battle won by " + game.active + ".")
return goto_regroup()
}
@@ -2935,7 +2890,7 @@ function resume_siege_battle() {
}
if (game.storming.length === 0) {
- game.halfhit = null
+ game.halfhit = NOBODY
log("Storming repulsed.")
return next_combat_round()
}
@@ -2953,7 +2908,7 @@ states.siege_battle = {
for (let b of game.battle_list) {
gen_action(view, 'block', b) // take default action
gen_action(view, 'fire', b)
- if (game.storming.includes(b))
+ if (set_has(game.storming, b))
gen_action(view, 'retreat', b)
}
},
@@ -2975,11 +2930,11 @@ function goto_field_battle_hits() {
function list_field_victims() {
let max = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (block_owner(b) === game.active && is_field_combatant(b) && game.steps[b] > max)
max = game.steps[b]
let list = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (block_owner(b) === game.active && is_field_combatant(b) && game.steps[b] === max)
list.push(b)
return list
@@ -3035,14 +2990,14 @@ function goto_siege_battle_hits() {
}
function list_siege_victims() {
- if (game.halfhit && block_owner(game.halfhit) === game.active)
+ if (game.halfhit !== NOBODY && block_owner(game.halfhit) === game.active)
return [ game.halfhit ]
let max = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (block_owner(b) === game.active && is_siege_combatant(b) && game.steps[b] > max)
max = game.steps[b]
let list = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (block_owner(b) === game.active && is_siege_combatant(b) && game.steps[b] === max)
list.push(b)
return list
@@ -3069,7 +3024,7 @@ function apply_siege_battle_hit_to(who, flash) {
msg += "hit."
log(game.active[0] + ": " + msg)
reduce_block(who)
- game.halfhit = null
+ game.halfhit = NOBODY
} else {
if (is_block_in_castle(who)) {
msg += "half-hit."
@@ -3107,7 +3062,7 @@ function roll_attack(active, b, verb, is_charge) {
let fire = block_fire_power(b, game.where) + is_charge
let rolls = []
let steps = game.steps[b]
- let name = block_name(b) + " " + BLOCKS[b].combat
+ let name = block_name(b) + " " + BLOCKS[b].initiative + BLOCKS[b].fire_power
let self = 0
for (let i = 0; i < steps; ++i) {
let die = roll_d6()
@@ -3146,7 +3101,7 @@ function roll_attack(active, b, verb, is_charge) {
}
function field_fire_with_block(b) {
- game.moved[b] = 1
+ set_add(game.moved, b)
roll_attack(game.active, b, "fired", 0)
if (game.hits > 0) {
goto_field_battle_hits()
@@ -3156,7 +3111,7 @@ function field_fire_with_block(b) {
}
function siege_fire_with_block(b) {
- game.moved[b] = 1
+ set_add(game.moved, b)
roll_attack(game.active, b, "fired", 0)
if (game.hits > 0) {
goto_siege_battle_hits()
@@ -3166,7 +3121,7 @@ function siege_fire_with_block(b) {
}
function charge_with_block(b) {
- game.moved[b] = 1
+ set_add(game.moved, b)
roll_attack(game.active, b, "charged", 1)
if (game.hits > 0) {
goto_field_battle_hits()
@@ -3176,19 +3131,19 @@ function charge_with_block(b) {
}
function field_withdraw_with_block(b) {
- game.flash = b + " withdrew."
+ game.flash = block_name(b) + " withdrew."
log(game.active[0] + ": " + game.flash)
- game.moved[b] = 1
- remove_from_array(game.sallying, b)
- game.castle.push(b)
+ set_add(game.moved, b)
+ set_delete(game.sallying, b)
+ set_add(game.castle, b)
resume_field_battle()
}
function siege_withdraw_with_block(b) {
- game.flash = b + " withdrew."
+ game.flash = block_name(b) + " withdrew."
log(game.active[0] + ": " + game.flash)
- game.moved[b] = 1
- remove_from_array(game.storming, b)
+ set_add(game.moved, b)
+ set_delete(game.storming, b)
resume_siege_battle()
}
@@ -3217,7 +3172,7 @@ states.harry = {
game.summary.push([game.active, to])
game.location[game.who] = to
move_block(game.who, game.where, to)
- game.who = null
+ game.who = NOBODY
resume_field_battle()
},
}
@@ -3244,15 +3199,15 @@ states.retreat_in_battle = {
game.summary.push([game.active, to])
game.location[game.who] = to
move_block(game.who, game.where, to)
- game.who = null
+ game.who = NOBODY
resume_field_battle()
},
block: function () {
- game.who = null
+ game.who = NOBODY
resume_field_battle()
},
undo: function () {
- game.who = null
+ game.who = NOBODY
resume_field_battle()
}
}
@@ -3274,11 +3229,11 @@ function start_draw_phase() {
game.state = 'draw_phase'
if (game.active === FRANKS) {
game.who = select_random_block(F_POOL)
- if (!game.who)
+ if (game.who === NOBODY)
end_draw_phase()
} else {
game.who = select_random_block(S_POOL)
- if (!game.who)
+ if (game.who === NOBODY)
end_draw_phase()
}
}
@@ -3296,7 +3251,7 @@ states.draw_phase = {
break
case 'pilgrims':
view.prompt = "Draw Phase: Place " + block_name(game.who) + " in a friendly port."
- for (let town of TOWNLIST) {
+ for (let town = first_town; town <= last_town; ++town) {
if (is_friendly_port(town) || can_enter_besieged_port(town)) {
gen_action(view, 'town', town)
can_place = true
@@ -3307,9 +3262,9 @@ states.draw_phase = {
case 'outremers':
case 'emirs':
case 'nomads':
- view.prompt = "Draw Phase: Place " + BLOCKS[game.who].name + " at full strength in "
- + list_seats(game.who).join(", ") + " or at strength 1 in any friendly town."
- for (let town of TOWNLIST) {
+ view.prompt = "Draw Phase: Place " + block_name(game.who) + " at full strength in "
+ + list_seats(game.who).map(town_name).join(", ") + " or at strength 1 in any friendly town."
+ for (let town = first_town; town <= last_town; ++town) {
if (town === ENGLAND || town === FRANCE || town === GERMANIA)
continue
// FAQ claims besieger controls town for draw purposes
@@ -3326,7 +3281,7 @@ states.draw_phase = {
town: function (where) {
let type = block_type(game.who)
- log(game.active + " placed drawn block in " + where + ".")
+ log(game.active + " deployed at #" + where + ".")
game.location[game.who] = where
if (type === 'turcopoles' || type === 'outremers' || type === 'emirs' || type === 'nomads') {
@@ -3337,10 +3292,10 @@ states.draw_phase = {
} else {
game.steps[game.who] = block_max_steps(game.who)
if (can_enter_besieged_port(where))
- game.castle.push(game.who)
+ set_add(game.castle, game.who)
}
- game.who = null
+ game.who = NOBODY
end_draw_phase()
},
next: function () {
@@ -3372,7 +3327,7 @@ function end_game_turn() {
function goto_winter_1() {
log("")
- log("Start Winter of " + game.year)
+ log(".h1 Winter of " + game.year)
log("")
if (game.winter_campaign)
goto_winter_siege_attrition()
@@ -3381,15 +3336,15 @@ function goto_winter_1() {
}
function goto_winter_siege_attrition() {
- log(game.active + " winter campaigned in " + game.winter_campaign + ".")
+ log(game.active + " winter campaigned at #" + game.winter_campaign + ".")
game.where = game.winter_campaign
game.active = besieged_player(game.where)
game.state = 'winter_siege_attrition'
game.attrition_list = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (is_block_in_castle_in(b, game.where))
- game.attrition_list.push(b)
+ set_add(game.attrition_list, b)
}
function resume_winter_siege_attrition() {
@@ -3397,7 +3352,7 @@ function resume_winter_siege_attrition() {
delete game.attrition_list
if (!is_under_siege(game.where)) {
game.active = enemy(game.active)
- log(game.where + " fell to siege attrition.")
+ log("#" + game.where + " fell to siege attrition.")
goto_regroup()
} else {
log("Siege continued.")
@@ -3410,7 +3365,7 @@ states.winter_siege_attrition = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Winter Siege Attrition: Waiting for " + game.active + "."
- view.prompt = "Winter Siege Attrition: Roll for siege attrition in " + game.where + "."
+ view.prompt = "Winter Siege Attrition: Roll for siege attrition in " + town_name(game.where) + "."
for (let b of game.attrition_list)
gen_action(view, 'block', b)
},
@@ -3423,13 +3378,13 @@ states.winter_siege_attrition = {
} else {
log("Attrition roll " + DIE_MISS[die] + ".")
}
- remove_from_array(game.attrition_list, who)
+ set_delete(game.attrition_list, who)
resume_winter_siege_attrition()
}
}
function goto_winter_2() {
- game.where = null
+ game.where = NOWHERE
eliminate_besieging_blocks(FRANKS)
eliminate_besieging_blocks(SARACENS)
lift_all_sieges()
@@ -3442,7 +3397,7 @@ function goto_winter_2() {
function eliminate_besieging_blocks(owner) {
game.summary = []
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === owner) {
let where = game.location[b]
if (where === game.winter_campaign)
@@ -3459,7 +3414,7 @@ function eliminate_besieging_blocks(owner) {
}
function need_winter_supply_check() {
- for (let town of TOWNLIST) {
+ for (let town = first_town; town <= last_town; ++town) {
if (town === game.winter_campaign)
continue
if (is_friendly_town(town) && !is_within_castle_limit(town))
@@ -3491,7 +3446,7 @@ states.winter_supply = {
return view.prompt = "Winter Supply: Waiting for " + game.active + "."
gen_action_undo(view)
let okay_to_end = true
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === game.active) {
if (is_block_on_land(b)) {
let where = game.location[b]
@@ -3535,7 +3490,7 @@ states.winter_supply = {
function goto_winter_replacements() {
game.rp = {}
- for (let town of TOWNLIST)
+ for (let town = first_town; town <= last_town; ++town)
if (is_under_siege(town))
game.rp[town] = 0
else
@@ -3559,7 +3514,7 @@ states.winter_replacements = {
view.prompt = "Winter Replacements: Distribute replacement points."
gen_action_undo(view)
let okay_to_end = true
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === game.active && is_block_on_land(b)) {
let where = game.location[b]
let cost = replacement_cost(where)
@@ -3616,7 +3571,7 @@ function goto_year_end() {
game.result = SARACENS
} else {
game.victory = "The game ended in a draw."
- game.result = null
+ game.result = "None"
}
log("")
log(game.victory)
@@ -3624,7 +3579,7 @@ function goto_year_end() {
}
// Return eliminated blocks to pool.
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === DEAD)
game.location[b] = block_pool(b)
@@ -3646,7 +3601,7 @@ function setup_game() {
reset_blocks()
game.year = 1187
game.turn = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b <= last_block; ++b) {
if (block_owner(b) === FRANKS) {
switch (block_type(b)) {
case 'pilgrims':
@@ -3666,10 +3621,8 @@ function setup_game() {
if (block_type(b) === 'nomads')
deploy(b, block_pool(b))
}
- if (block_owner(b) === ASSASSINS) {
- deploy(b, block_home(b))
- }
}
+ deploy(ASSASSINS, MASYAF)
goto_frank_deployment()
}
@@ -3692,9 +3645,9 @@ function make_battle_view() {
}
if (is_under_siege(game.where) && !is_contested_battle_field(game.where))
- battle.title = enemy(game.castle_owner) + " besiege " + game.where
+ battle.title = enemy(game.castle_owner) + " besiege " + town_name(game.where)
else
- battle.title = game.attacker[game.where] + " attack " + game.where
+ battle.title = game.attacker[game.where] + " attack " + town_name(game.where)
if (game.combat_round === 0)
battle.title += " \u2014 Combat Deployment"
else
@@ -3703,17 +3656,17 @@ function make_battle_view() {
battle.title += " \u2014 Jihad!"
function fill_cell(cell, owner, fn) {
- for (let b of BLOCKLIST)
+ for (let b = 0; b <= last_block; ++b)
if (game.location[b] === game.where & block_owner(b) === owner && fn(b))
cell.push(b)
}
fill_cell(battle.FR, FRANKS, b => is_reserve(b))
fill_cell(battle.FC, FRANKS, b => is_block_in_castle(b))
- fill_cell(battle.FF, FRANKS, b => is_block_in_field(b) && !game.storming.includes(b))
- fill_cell(battle.FF, SARACENS, b => is_block_in_field(b) && game.storming.includes(b))
- fill_cell(battle.SF, FRANKS, b => is_block_in_field(b) && game.storming.includes(b))
- fill_cell(battle.SF, SARACENS, b => is_block_in_field(b) && !game.storming.includes(b))
+ fill_cell(battle.FF, FRANKS, b => is_block_in_field(b) && !set_has(game.storming, b))
+ fill_cell(battle.FF, SARACENS, b => is_block_in_field(b) && set_has(game.storming, b))
+ fill_cell(battle.SF, FRANKS, b => is_block_in_field(b) && set_has(game.storming, b))
+ fill_cell(battle.SF, SARACENS, b => is_block_in_field(b) && !set_has(game.storming, b))
fill_cell(battle.SC, SARACENS, b => is_block_in_castle(b))
fill_cell(battle.SR, SARACENS, b => is_reserve(b))
@@ -3726,31 +3679,39 @@ exports.setup = function (seed, scenario, options) {
log: [],
undo: [],
+ active: null,
+ moves: 0,
+ who: NOBODY,
+ where: NOWHERE,
+
+ show_cards: false,
s_hand: [],
f_hand: [],
s_card: 0,
f_card: 0,
+
+ location: [],
+ steps: [],
+
attacker: {},
road_limit: {},
last_used: {},
- location: {},
+
castle: [],
main_road: {},
- moved: {},
- moves: 0,
- prompt: null,
+ moved: [],
reserves1: [],
reserves2: [],
- show_cards: false,
- steps: {},
- who: null,
- where: null,
}
// Old RNG for ancient replays
if (options.rng)
game.rng = options.rng
+ log(".h1 Crusader Rex")
+ log("")
+
+
if (options && options.iron_bridge) {
game.iron_bridge = 1
log("Iron Bridge:\nThe road between Antioch and Harim has a move limit of 3.")
@@ -3786,9 +3747,9 @@ exports.resign = function (state, current) {
function make_siege_view() {
let list = {}
- for (let t of TOWNLIST)
- if (is_under_siege(t))
- list[t] = besieging_player(t)
+ for (let town = first_town; town <= last_town; ++town)
+ if (is_under_siege(town))
+ list[town] = besieging_player(town)
return list
}
@@ -3815,7 +3776,7 @@ exports.view = function(state, current) {
f_card: (game.show_cards || current === FRANKS) ? game.f_card : 0,
s_card: (game.show_cards || current === SARACENS) ? game.s_card : 0,
hand: (current === FRANKS) ? game.f_hand : (current === SARACENS) ? game.s_hand : observer_hand(),
- who: (game.active === current) ? game.who : null,
+ who: (game.active === current) ? game.who : NOBODY,
location: game.location,
castle: game.castle,
steps: game.steps,
@@ -3823,7 +3784,6 @@ exports.view = function(state, current) {
sieges: make_siege_view(),
battle: null,
prompt: null,
- actions: null,
}
if (game.jihad && game.jihad !== game.p1)
@@ -3838,3 +3798,146 @@ exports.view = function(state, current) {
return view
}
+
+
+// === COMMON LIBRARY ===
+
+// remove item at index (faster than splice)
+function array_remove(array, index) {
+ let n = array.length
+ for (let i = index + 1; i < n; ++i)
+ array[i - 1] = array[i]
+ array.length = n - 1
+ return array
+}
+
+// insert item at index (faster than splice)
+function array_insert(array, index, item) {
+ for (let i = array.length; i > index; --i)
+ array[i] = array[i - 1]
+ array[index] = item
+ return array
+}
+
+function set_clear(set) {
+ set.length = 0
+}
+
+function set_has(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 true
+ }
+ return false
+}
+
+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 set
+ }
+ return array_insert(set, a, item)
+}
+
+function set_delete(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_remove(set, m)
+ }
+ return set
+}
+
+function set_toggle(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_remove(set, m)
+ }
+ return array_insert(set, a, item)
+}
+
+// Fast deep copy for objects without cycles
+function object_copy(original) {
+ if (Array.isArray(original)) {
+ let n = original.length
+ let copy = new Array(n)
+ for (let i = 0; i < n; ++i) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = object_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ } else {
+ let copy = {}
+ for (let i in original) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = object_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ }
+}
+
+function clear_undo() {
+ if (game.undo.length > 0)
+ game.undo = []
+}
+
+function push_undo() {
+ let copy = {}
+ for (let k in game) {
+ let v = game[k]
+ if (k === "undo")
+ continue
+ else if (k === "log")
+ v = v.length
+ else if (typeof v === "object" && v !== null)
+ v = object_copy(v)
+ copy[k] = v
+ }
+ game.undo.push(copy)
+}
+
+function pop_undo() {
+ let save_log = game.log
+ let save_undo = game.undo
+ game = save_undo.pop()
+ save_log.length = game.log
+ game.log = save_log
+ game.undo = save_undo
+}