summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js863
1 files changed, 483 insertions, 380 deletions
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
+}