summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js484
1 files changed, 280 insertions, 204 deletions
diff --git a/rules.js b/rules.js
index abff25b..1925270 100644
--- a/rules.js
+++ b/rules.js
@@ -11,10 +11,13 @@ exports.roles = [
"Pompeius",
]
-const { CARDS, SPACES, EDGES, BLOCKS } = require('./data')
+const {
+ CARDS, SPACES, EDGES, BLOCKS, block_index, space_index,
+} = require('./data')
-const BLOCKLIST = Object.keys(BLOCKS)
-const SPACELIST = Object.keys(SPACES)
+const block_count = BLOCKS.length
+const space_count = SPACES.length
+const first_map_space = 2
const APOLLO = 1
const JUPITER = 2
@@ -28,16 +31,19 @@ const OBSERVER = "Observer"
const BOTH = "Both"
const CAESAR = "Caesar"
const POMPEIUS = "Pompeius"
-const CLEOPATRA = "Cleopatra"
-const OCTAVIAN = "Octavian"
-const BRUTUS = "Brutus"
-const ANTONIUS = "Antonius"
-const SCIPIO = "Scipio"
-const ALEXANDRIA = "Alexandria"
-const ROMA = "Roma"
-const DEAD = "Dead"
-const LEVY = "Levy"
+const B_CAESAR = block_index["Caesar"]
+const B_POMPEIUS = block_index["Pompeius"]
+const B_CLEOPATRA = block_index["Cleopatra"]
+const B_OCTAVIAN = block_index["Octavian"]
+const B_BRUTUS = block_index["Brutus"]
+const B_ANTONIUS = block_index["Antonius"]
+const B_SCIPIO = block_index["Scipio"]
+
+const ALEXANDRIA = space_index["Alexandria"]
+const ROMA = space_index["Roma"]
+const DEAD = space_index["Dead"]
+const LEVY = space_index["Levy"]
// serif cirled numbers
const DIE_HIT = [ 0, '\u2776', '\u2777', '\u2778', '\u2779', '\u277A', '\u277B' ]
@@ -74,16 +80,16 @@ function logp(s) {
function log_move_start(from, to, mark = false) {
if (mark)
- game.turn_buf = [ from, to + mark ]
+ game.turn_buf = [ space_name(from), space_name(to) + mark ]
else
- game.turn_buf = [ from, to ]
+ game.turn_buf = [ space_name(from), space_name(to) ]
}
function log_move_continue(to, mark = false) {
if (mark)
- game.turn_buf.push(to + mark)
+ game.turn_buf.push(space_name(to) + mark)
else
- game.turn_buf.push(to)
+ game.turn_buf.push(space_name(to))
}
function log_move_end() {
@@ -94,7 +100,7 @@ function log_move_end() {
}
function log_levy(where) {
- game.turn_log.push([where])
+ game.turn_log.push([space_name(where)])
}
function print_turn_log_no_active(text) {
@@ -154,14 +160,17 @@ function gen_action(view, action, argument) {
view.actions = {}
if (argument !== undefined) {
if (!(action in view.actions))
- view.actions[action] = [ argument ]
- else
- view.actions[action].push(argument)
+ view.actions[action] = []
+ set_add(view.actions[action], argument)
} else {
view.actions[action] = 1
}
}
+function gen_action_battle(view, action, b) {
+ gen_action(view, action, b)
+}
+
function gen_action_block(view, b) {
gen_action(view, 'block', b)
}
@@ -172,8 +181,8 @@ function gen_action_space(view, s) {
function edge_id(A, B) {
if (A > B)
- return B + "/" + A
- return A + "/" + B
+ return B * 100 + A
+ return A * 100 + B
}
function roll_d6() {
@@ -205,7 +214,7 @@ function deal_cards(deck, n) {
}
function reset_road_limits() {
- game.sea_moved = {}
+ game.sea_moved = []
game.sea_retreated = false
game.limits = {}
}
@@ -214,13 +223,38 @@ function road_limit(e) {
return game.limits[e]|0
}
+function set_attacker(where, side) {
+ if (side === CAESAR) {
+ set_add(game.c_attacker, where)
+ set_delete(game.p_attacker, where)
+ } else {
+ set_add(game.p_attacker, where)
+ set_delete(game.c_attacker, where)
+ }
+}
+
+function get_attacker(where) {
+ if (set_has(game.c_attacker, where))
+ return CAESAR
+ if (set_has(game.p_attacker, where))
+ return POMPEIUS
+ return null
+}
+
function move_to(who, from, to) {
let e = edge_id(from, to)
game.location[who] = to
game.last_from = from
game.limits[e] = road_limit(e) + 1
- if (is_contested_space(to))
- game.last_used[e] = game.active
+ if (is_contested_space(to)) {
+ if (game.active === CAESAR) {
+ set_add(game.c_last_used, e)
+ set_delete(game.p_last_used, e)
+ } else {
+ set_add(game.p_last_used, e)
+ set_delete(game.c_last_used, e)
+ }
+ }
}
function block_original_owner(who) {
@@ -233,6 +267,10 @@ function block_owner(who) {
return block_original_owner(who)
}
+function space_name(where) {
+ return SPACES[where].name
+}
+
function block_name(who) {
return BLOCKS[who].name
}
@@ -262,7 +300,7 @@ function is_dead(b) {
}
function eliminate_block(who) {
- if (who === CLEOPATRA) {
+ if (who === B_CLEOPATRA) {
set_toggle(game.traitor, who)
game.flash = "Cleopatra was captured."
log("Cleopatra joined " + block_owner(who) + "!")
@@ -302,7 +340,7 @@ function enemy_player() {
function count_friendly(where) {
let count = 0
let p = game.active
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === where && block_owner(b) === p)
++count
}
@@ -311,7 +349,7 @@ function count_friendly(where) {
function has_friendly(where) {
let p = game.active
- for (let b of BLOCKLIST)
+ for (let b = 0; b < block_count; ++b)
if (game.location[b] === where && block_owner(b) === p)
return true
return false
@@ -320,7 +358,7 @@ function has_friendly(where) {
function count_enemy(where) {
let count = 0
let p = enemy_player()
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === where && block_owner(b) === p)
++count
}
@@ -329,7 +367,7 @@ function count_enemy(where) {
function has_enemy(where) {
let p = enemy_player()
- for (let b of BLOCKLIST)
+ for (let b = 0; b < block_count; ++b)
if (game.location[b] === where && block_owner(b) === p)
return true
return false
@@ -339,7 +377,7 @@ function count_pinning(where) {
let count = 0
if (game.active === game.p2) {
let p = enemy_player()
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === where && block_owner(b) === p)
if (!game.reserves.includes(b))
++count
@@ -350,7 +388,7 @@ function count_pinning(where) {
function count_pinned(where) {
let count = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === where && block_owner(b) === game.active)
if (!game.reserves.includes(b))
++count
@@ -382,7 +420,7 @@ function is_sea(where) {
}
function is_map_space(where) {
- return is_city(where) || is_sea(where)
+ return where >= first_map_space
}
function is_navis(b) {
@@ -403,7 +441,10 @@ function is_vacant_sea(where) { return is_sea(where) && is_vacant_space(where) }
function is_contested_sea(where) { return is_sea(where) && is_contested_space(where) }
function have_contested_spaces() {
- return SPACELIST.some(where => is_map_space(where) && is_contested_space(where))
+ for (let s = first_map_space; s < space_count; ++s)
+ if (is_contested_space(s))
+ return true
+ return false
}
function supply_limit(where) {
@@ -414,7 +455,7 @@ function supply_limit(where) {
function is_over_supply_limit(where) {
let count = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === where)
++count
}
@@ -427,18 +468,18 @@ function count_vp() {
game.c_vp = 0
game.p_vp = 0
game.active = CAESAR
- for (let s of SPACELIST) {
+ for (let s = first_map_space; s < space_count; ++s) {
if (is_friendly_city(s))
game.c_vp += SPACES[s].value
if (is_enemy_city(s))
game.p_vp += SPACES[s].value
}
- if (is_dead(POMPEIUS)) game.c_vp += 1
- if (is_dead(SCIPIO)) game.c_vp += 1
- if (is_dead(BRUTUS)) game.c_vp += 1
- if (is_dead(CAESAR)) game.p_vp += 1
- if (is_dead(ANTONIUS)) game.p_vp += 1
- if (is_dead(OCTAVIAN)) game.p_vp += 1
+ if (is_dead(B_POMPEIUS)) game.c_vp += 1
+ if (is_dead(B_SCIPIO)) game.c_vp += 1
+ if (is_dead(B_BRUTUS)) game.c_vp += 1
+ if (is_dead(B_CAESAR)) game.p_vp += 1
+ if (is_dead(B_ANTONIUS)) game.p_vp += 1
+ if (is_dead(B_OCTAVIAN)) game.p_vp += 1
game.active = old_active
}
@@ -460,7 +501,7 @@ function can_amphibious_move_to(b, from, to) {
}
function can_amphibious_move(b) {
- if (block_owner(b) === game.active && !game.moved[b]) {
+ if (block_owner(b) === game.active && !set_has(game.moved, b)) {
if (BLOCKS[b].type === 'navis')
return false
if (is_pinned(b))
@@ -547,12 +588,24 @@ function can_block_use_road_to_retreat(b, from, to) {
return false
}
+function did_active_last_use(e) {
+ if (game.active === CAESAR)
+ return set_has(game.c_last_used, e)
+ return set_has(game.p_last_used, e)
+}
+
+function did_enemy_last_use(e) {
+ if (game.active === CAESAR)
+ return set_has(game.p_last_used, e)
+ return set_has(game.c_last_used, e)
+}
+
function can_block_move_to(b, to) {
let from = game.location[b]
if (can_block_use_road(b, from, to)) {
if (count_pinning(from) > 0) {
let e = edge_id(from, to)
- if (game.last_used[e] === enemy_player())
+ if (did_enemy_last_use(e))
return false
}
return true
@@ -569,11 +622,11 @@ function can_block_continue_to(b, to) {
}
function can_block_move(b) {
- if (block_owner(b) === game.active && !game.moved[b]) {
+ if (block_owner(b) === game.active && !set_has(game.moved, b)) {
let from = game.location[b]
if (is_pinned(b))
return false
- if (game.sea_moved[from] && count_friendly(from) <= 1)
+ if (set_has(game.sea_moved, from) && count_friendly(from) <= 1)
return false
for (let to of SPACES[from].exits) {
if (can_block_move_to(b, to)) {
@@ -614,7 +667,7 @@ function can_attacker_retreat_to(who, from, to) {
return true
if (is_vacant_space(to))
if (can_block_use_road_to_retreat(who, from, to))
- if (game.last_used[e] === game.active)
+ if (did_active_last_use(e))
return true
if (is_friendly_space(to))
if (can_block_use_road_to_retreat(who, from, to))
@@ -628,7 +681,7 @@ function can_defender_retreat_to(who, from, to) {
// Navis can only retreat to vacant seas, not ports!
if (is_vacant_sea(to)) {
if (can_block_use_road_to_retreat(who, from, to))
- if (game.last_used[e] !== enemy_player())
+ if (!did_enemy_last_use(e))
return true
}
// Navis can retreat to any friendly sea or port, even ones used by the attacker!
@@ -642,7 +695,7 @@ function can_defender_retreat_to(who, from, to) {
return true
if (is_vacant_space(to) || is_friendly_space(to)) {
if (can_block_use_road_to_retreat(who, from, to))
- if (game.last_used[e] !== enemy_player())
+ if (!did_enemy_last_use(e))
return true
}
}
@@ -686,10 +739,10 @@ function can_levy_to(b, to) {
return BLOCKS[b].levy === to
if (BLOCKS[b].type === 'navis')
return SPACES[to].type === 'major-port'
- if (b === OCTAVIAN)
- return is_dead(CAESAR) || is_dead(ANTONIUS)
- if (b === BRUTUS)
- return is_dead(POMPEIUS) || is_dead(SCIPIO)
+ if (b === B_OCTAVIAN)
+ return is_dead(B_CAESAR) || is_dead(B_ANTONIUS)
+ if (b === B_BRUTUS)
+ return is_dead(B_POMPEIUS) || is_dead(B_SCIPIO)
return true
}
return false
@@ -700,8 +753,8 @@ function can_levy(b) {
if (block_owner(b) !== game.active)
return false
if (location === LEVY) {
- for (let to of SPACELIST)
- if (can_levy_to(b, to))
+ for (let s = first_map_space; s < space_count; ++s)
+ if (can_levy_to(b, s))
return true
return false
}
@@ -716,13 +769,9 @@ let states = {}
function start_free_deployment() {
game.active = CAESAR
- game.setup_limit = {}
- for (let space of SPACELIST) {
- if (is_map_space(space)) {
- let n = count_friendly(space) + count_enemy(space)
- if (n > 0)
- game.setup_limit[space] = n
- }
+ game.setup_limit = []
+ for (let space = 0; space < space_count; ++space) {
+ game.setup_limit[space] = count_friendly(space) + count_enemy(space)
}
validate_free_deployment()
game.state = 'free_deployment'
@@ -731,7 +780,7 @@ function start_free_deployment() {
function validate_free_deployment() {
game.setup_error = []
- for (let space of SPACELIST) {
+ for (let space = first_map_space; space < space_count; ++space) {
if (is_friendly_city(space)) {
let n = count_friendly(space)
let d = n - game.setup_limit[space]
@@ -758,7 +807,7 @@ states.free_deployment = {
} else {
format_deployment_error(view)
}
- for (let b of BLOCKLIST)
+ for (let b = 0; b < block_count; ++b)
if (block_owner(b) === game.active && is_map_space(game.location[b]))
gen_action_block(view, b)
},
@@ -770,10 +819,10 @@ states.free_deployment = {
pass: function () {
if (game.active === CAESAR) {
clear_undo()
- game.moved = {}
+ game.moved = []
game.active = POMPEIUS
} else {
- game.moved = {}
+ game.moved = []
delete game.setup_limit
delete game.setup_error
start_year()
@@ -793,8 +842,8 @@ states.free_deployment_to = {
}
gen_action_undo(view)
gen_action_block(view, game.who)
- for (let space of SPACELIST) {
- if (space in game.setup_limit && space !== game.location[game.who]) {
+ for (let space = first_map_space; space < space_count; ++space) {
+ if (game.setup_limit[space] > 0 && space !== game.location[game.who]) {
if (!is_enemy_city(space)) {
if (block_type(game.who) === 'navis') {
if (is_port(space))
@@ -808,9 +857,9 @@ states.free_deployment_to = {
},
space: function (where) {
game.location[game.who] = where
- game.moved[game.who] = 1
+ set_add(game.moved, game.who)
validate_free_deployment()
- game.who = null
+ game.who = -1
game.state = 'free_deployment'
},
block: pop_undo,
@@ -926,10 +975,12 @@ states.discard_and_play_card = {
}
function start_first_turn() {
- game.last_used = {}
- game.attacker = {}
- game.main_road = {}
- game.moved = {}
+ game.c_last_used = []
+ game.p_last_used = []
+ game.c_attacker = []
+ game.p_attacker = []
+ game.main_road = []
+ game.moved = []
game.reserves = []
logbr()
log("Start Turn " + game.turn + " of Year " + game.year)
@@ -937,10 +988,12 @@ function start_first_turn() {
}
function start_turn() {
- game.last_used = {}
- game.attacker = {}
- game.main_road = {}
- game.moved = {}
+ game.c_last_used = []
+ game.p_last_used = []
+ game.c_attacker = []
+ game.p_attacker = []
+ game.main_road = []
+ game.moved = []
game.reserves = []
game.c_card = 0
game.p_card = 0
@@ -1158,7 +1211,7 @@ states.jupiter = {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + "..."
view.prompt = "Jupiter: Choose one enemy army adjacent to a friendly city."
- for (let s of SPACELIST) {
+ for (let s = first_map_space; s < space_count; ++s) {
if (is_friendly_city(s)) {
for (let to of SPACES[s].exits)
if (is_enemy_city(to) || is_contested_city(to))
@@ -1170,21 +1223,21 @@ states.jupiter = {
space: function (where) {
/* pick a random block */
let list = []
- for (let x of BLOCKLIST)
- if (game.location[x] === where)
- list.push(x)
+ for (let b = 0; b < block_count; ++b)
+ if (game.location[b] === where)
+ list.push(b)
let i = random(list.length)
jupiter_block(list[i])
},
secret: function (args) {
- let [where, owner] = args
+ let [where, color] = args
/* pick a random block of the same color as the selected block */
- if (owner === CLEOPATRA) {
- jupiter_block(CLEOPATRA)
+ if (color === "Cleopatra") {
+ jupiter_block(B_CLEOPATRA)
} else {
let list = []
- for (let b of BLOCKLIST)
- if (game.location[b] === where && BLOCKS[b].owner === owner)
+ for (let b = 0; b < block_count; ++b)
+ if (game.location[b] === where && block_original_owner(b) === color)
list.push(b)
let i = random(list.length)
jupiter_block(list[i])
@@ -1207,9 +1260,9 @@ states.jupiter_to = {
},
space: function (to) {
log(block_name(game.who) + " joined " + game.active + ":\n" +
- game.location[game.who] + " \u2192 " + to + ".")
+ game.location[game.who] + " \u2192 " + space_name(to) + ".")
game.location[game.who] = to
- game.who = null
+ game.who = -1
end_player_turn()
},
}
@@ -1219,24 +1272,24 @@ states.vulcan = {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + "..."
view.prompt = "Vulcan: Choose an enemy city to suffer a volcanic eruption."
- for (let s of SPACELIST)
+ for (let s = first_map_space; s < space_count; ++s)
if (is_enemy_city(s))
gen_action_space(view, s)
},
space: function (city) {
- log("Vulcan struck " + city + "!")
+ log("Vulcan struck " + space_name(city) + "!")
if (game.automatic_disruption) {
- for (let b of BLOCKLIST)
+ for (let b = 0; b < block_count; ++b)
if (game.location[b] === city)
reduce_block(b)
// uh-oh! cleopatra switched sides!
if (is_contested_city(city))
- game.attacker[city] = game.active
+ set_attacker(city, game.active)
end_player_turn()
} else {
game.where = city
game.vulcan = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b < block_count; ++b)
if (game.location[b] === city)
game.vulcan.push(b)
game.active = enemy(game.active)
@@ -1249,7 +1302,7 @@ states.apply_vulcan = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + "..."
- view.prompt = "Apply Vulcan hits in " + game.where + "."
+ view.prompt = "Apply Vulcan hits in " + space_name(game.where) + "."
for (let i = 0; i < game.vulcan.length; ++i)
gen_action_block(view, game.vulcan[i])
},
@@ -1257,11 +1310,11 @@ states.apply_vulcan = {
reduce_block(who)
// uh-oh! cleopatra switched sides!
if (is_contested_city(game.where))
- game.attacker[game.where] = game.active
+ set_attacker(game.where, game.active)
remove_from_array(game.vulcan, who)
if (game.vulcan.length === 0) {
delete game.vulcan
- game.where = null
+ game.where = -1
game.active = enemy(game.active)
end_player_turn()
}
@@ -1270,16 +1323,16 @@ states.apply_vulcan = {
function goto_mars_and_neptune() {
game.surprise_list = []
- for (let where of SPACELIST)
- if (is_map_space(where) && is_contested_space(where))
- game.surprise_list.push(where)
+ for (let s = first_map_space; s < space_count; ++s)
+ if (is_contested_space(s))
+ game.surprise_list.push(s)
if (game.surprise_list.length === 0) {
delete game.surprise_list
return end_player_turn()
}
if (game.surprise_list.length === 1) {
game.surprise = game.surprise_list[0]
- log("Surprise attack in " + game.surprise + ".")
+ log("Surprise attack in " + space_name(game.surprise) + ".")
delete game.surprise_list
return end_player_turn()
}
@@ -1297,7 +1350,7 @@ states.mars_and_neptune = {
},
space: function (where) {
game.surprise = where
- log("Surprise attack in " + game.surprise + ".")
+ log("Surprise attack in " + space_name(game.surprise) + ".")
delete game.surprise_list
end_player_turn()
},
@@ -1316,12 +1369,14 @@ function move_or_attack(to) {
let from = game.location[game.who]
move_to(game.who, from, to)
if (is_enemy_space(to) || is_contested_space(to)) {
- if (!game.attacker[to]) {
- game.attacker[to] = game.active
- game.main_road[to] = from
+ let attacker = get_attacker(to)
+ let e = edge_id(from, to)
+ if (!attacker) {
+ set_attacker(to, game.active)
+ set_add(game.main_road, e)
return ATTACK_MARK
} else {
- if (game.attacker[to] !== game.active || game.main_road[to] !== from) {
+ if (attacker !== game.active || !set_has(game.main_road, e)) {
game.reserves.push(game.who)
return RESERVE_MARK
}
@@ -1348,7 +1403,7 @@ states.move_who = {
else
view.prompt = "Choose an army to group move. "+game.moves+"MP left."
if (game.moves === 0) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
let from = game.location[b]
if (game.activated.includes(from))
if (can_block_move(b))
@@ -1356,7 +1411,7 @@ states.move_who = {
}
} else {
let have_amphibious = false
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
let can_move = false
if (game.amphibious_available && can_amphibious_move(b)) {
can_move = true
@@ -1415,17 +1470,17 @@ states.move_where = {
log_move_start(from, to)
logp("amphibious moved.")
if (is_sea(to)) {
- game.sea_moved[to] = true
+ set_add(game.sea_moved, to)
game.state = 'amphibious_move_to'
} else {
- game.moved[game.who] = true
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = -1
game.state = 'move_who'
log_move_end()
}
} else {
if (!game.activated.includes(from)) {
- logp("activated " + from + ".")
+ logp("activated " + space_name(from) + ".")
game.moves --
game.activated.push(from)
}
@@ -1435,8 +1490,8 @@ states.move_where = {
if (can_block_continue(game.who, game.last_from)) {
game.state = 'move_where_2'
} else {
- game.moved[game.who] = true
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = -1
game.state = 'move_who'
log_move_end()
}
@@ -1464,8 +1519,8 @@ states.move_where_2 = {
log_move_continue(to)
move_to(game.who, from, to)
}
- game.moved[game.who] = true
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = -1
game.state = 'move_who'
log_move_end()
},
@@ -1489,11 +1544,11 @@ states.amphibious_move_to = {
game.location[game.who] = to
log_move_continue(to)
if (is_sea(to)) {
- game.sea_moved[to] = true
+ set_add(game.sea_moved, to)
game.state = 'amphibious_move_to'
} else {
- game.moved[game.who] = true
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = -1
game.state = 'move_who'
log_move_end()
}
@@ -1515,7 +1570,7 @@ states.mercury_move_1 = {
space: function (to) {
let from = game.location[game.who]
if (!game.activated.includes(from)) {
- logp("activated " + from + ".")
+ logp("activated " + space_name(from) + ".")
game.moves --
game.activated.push(from)
}
@@ -1524,7 +1579,7 @@ states.mercury_move_1 = {
if (!is_contested_space(to) && can_block_move(game.who)) {
game.state = 'mercury_move_2'
} else {
- game.who = null
+ game.who = -1
game.state = 'move_who'
log_move_end()
}
@@ -1552,14 +1607,14 @@ states.mercury_move_2 = {
if (can_block_continue(game.who, game.last_from)) {
game.state = 'mercury_move_3'
} else {
- game.moved[game.who] = true
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = -1
game.state = 'move_who'
log_move_end()
}
} else {
- game.moved[game.who] = true
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = -1
game.state = 'move_who'
log_move_end()
}
@@ -1585,8 +1640,8 @@ states.mercury_move_3 = {
log_move_continue(to)
move_to(game.who, from, to)
}
- game.moved[game.who] = true
- game.who = null
+ set_add(game.moved, game.who)
+ game.who = -1
game.state = 'move_who'
log_move_end()
},
@@ -1602,7 +1657,7 @@ function end_movement() {
if (game.pluto === game.active || game.mercury === game.active)
return end_player_turn()
- game.who = null
+ game.who = -1
game.moves = 0
game.state = 'levy'
game.turn_log = []
@@ -1615,7 +1670,7 @@ states.levy = {
view.prompt = "Choose an army to levy. "+game.levies+"LP left."
let is_levy_possible = false
if (game.levies > 0) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (can_levy(b)) {
gen_action_block(view, b)
is_levy_possible = true
@@ -1657,9 +1712,9 @@ states.levy_where = {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to levy..."
view.prompt = "Choose a friendly city to levy " + block_name(game.who) + " in."
- for (let to of SPACELIST)
- if (can_levy_to(game.who, to))
- gen_action_space(view, to)
+ for (let s = first_map_space; s < space_count; ++s)
+ if (can_levy_to(game.who, s))
+ gen_action_space(view, s)
gen_action_block(view, game.who); // for canceling levy
gen_action_undo(view)
},
@@ -1668,7 +1723,7 @@ states.levy_where = {
game.levies --
game.steps[game.who] = 1
game.location[game.who] = to
- game.who = null
+ game.who = -1
game.state = 'levy'
},
block: pop_undo,
@@ -1702,7 +1757,7 @@ states.pick_battle = {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to pick a battle..."
view.prompt = "Choose the next battle to fight!"
- for (let s of SPACELIST)
+ for (let s = first_map_space; s < space_count; ++s)
if (is_contested_city(s) || is_contested_sea(s))
gen_action_space(view, s)
},
@@ -1713,20 +1768,20 @@ states.pick_battle = {
}
function is_attacker(b) {
- if (game.location[b] === game.where && block_owner(b) === game.attacker[game.where])
+ if (game.location[b] === game.where && block_owner(b) === get_attacker(game.where))
return !game.reserves.includes(b)
return false
}
function is_defender(b) {
- if (game.location[b] === game.where && block_owner(b) !== game.attacker[game.where])
+ if (game.location[b] === game.where && block_owner(b) !== get_attacker(game.where))
return !game.reserves.includes(b)
return false
}
function count_attackers() {
let count = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (is_attacker(b))
++count
}
@@ -1735,7 +1790,7 @@ function count_attackers() {
function count_defenders() {
let count = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (is_defender(b))
++count
}
@@ -1746,7 +1801,7 @@ function start_battle() {
game.battle_round = 0
game.flash = ""
logbr()
- log("Battle in " + game.where)
+ log("Battle in " + space_name(game.where))
if (game.surprise === game.where)
log("Surprise attack.")
game.state = 'battle_round'
@@ -1754,13 +1809,13 @@ function start_battle() {
}
function resume_battle() {
- game.who = null
+ game.who = -1
game.state = 'battle_round'
pump_battle_round()
}
function bring_on_reserves() {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === game.where) {
remove_from_array(game.reserves, b)
}
@@ -1770,18 +1825,18 @@ function bring_on_reserves() {
function goto_disrupt_reserves() {
game.flash = "Reserves were disrupted."
if (game.automatic_disruption) {
- for (let b of BLOCKLIST)
- if (game.location[b] === game.where && block_owner(b) === game.attacker[game.where])
+ for (let b = 0; b < block_count; ++b)
+ if (game.location[b] === game.where && block_owner(b) === get_attacker(game.where))
if (game.reserves.includes(b))
reduce_block(b)
end_disrupt_reserves()
} else {
game.disrupted = []
- for (let b of BLOCKLIST)
- if (game.location[b] === game.where && block_owner(b) === game.attacker[game.where])
+ for (let b = 0; b < block_count; ++b)
+ if (game.location[b] === game.where && block_owner(b) === get_attacker(game.where))
if (game.reserves.includes(b))
game.disrupted.push(b)
- game.active = game.attacker[game.where]
+ game.active = get_attacker(game.where)
game.state = 'disrupt_reserves'
}
}
@@ -1810,8 +1865,7 @@ states.disrupt_reserves = {
return view.prompt = "Waiting for " + game.active + " to apply disruption hits..."
view.prompt = "Apply disruption hits to reserves."
for (let b of game.disrupted) {
- gen_action(view, 'battle_hit', b)
- gen_action_block(view, b)
+ gen_action_battle(view, 'battle_hit', b)
gen_action_block(view, b)
}
},
@@ -1826,7 +1880,7 @@ function start_battle_round() {
game.turn_log = []
reset_road_limits()
- game.moved = {}
+ game.moved = []
if (game.battle_round === 2) {
if (game.surprise === game.where)
@@ -1835,7 +1889,7 @@ function start_battle_round() {
log("Defending main force was eliminated.")
log("Defending reserves were disrupted.")
log("Battlefield control changed.")
- game.attacker[game.where] = enemy(game.attacker[game.where])
+ set_attacker(game.where, enemy(get_attacker(game.where)))
return goto_disrupt_reserves()
} else if (count_attackers() === 0) {
log("Attacking main force was eliminated.")
@@ -1856,8 +1910,8 @@ function start_battle_round() {
function pump_battle_round() {
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 < block_count; ++b) {
+ if (is_candidate(b) && !set_has(game.moved, b)) {
if (block_initiative(b) === ci) {
if (!output)
output = []
@@ -1882,7 +1936,7 @@ function pump_battle_round() {
} else if (count_attackers() === 0 || count_defenders() === 0) {
start_battle_round()
} else {
- let attacker = game.attacker[game.where]
+ let attacker = get_attacker(game.where)
let defender = enemy(attacker)
if (game.surprise === game.where) {
@@ -1917,7 +1971,7 @@ function end_battle() {
game.flash = ""
game.battle_round = 0
reset_road_limits()
- game.moved = {}
+ game.moved = []
goto_regroup()
}
@@ -1930,7 +1984,7 @@ function can_fire_with_block(b) {
}
function fire_with_block(b) {
- game.moved[b] = true
+ set_add(game.moved, b)
game.hits = 0
let strength = block_strength(b)
let fire = block_fire_power(b)
@@ -1966,7 +2020,7 @@ function fire_with_block(b) {
function can_retreat_with_block(who) {
if (game.location[who] === game.where) {
if (game.battle_round > 1) {
- if (block_owner(who) === game.attacker[game.where])
+ if (block_owner(who) === get_attacker(game.where))
return can_attacker_retreat(who)
else
return can_defender_retreat(who)
@@ -1978,7 +2032,7 @@ function can_retreat_with_block(who) {
function must_retreat_with_block(who) {
if (game.location[who] === game.where)
if (game.battle_round === 4)
- return (block_owner(who) === game.attacker[game.where])
+ return (block_owner(who) === get_attacker(game.where))
return false
}
@@ -1995,7 +2049,7 @@ function retreat_with_block(who) {
function pass_with_block(who) {
game.flash = block_name(who) + " passed."
log_battle(block_name(who) + " passed.")
- game.moved[who] = true
+ set_add(game.moved, who)
resume_battle()
}
@@ -2008,7 +2062,7 @@ states.battle_round = {
let can_retreat = false
let must_retreat = false
let can_pass = false
- if (game.active === game.attacker[game.where]) {
+ if (game.active === get_attacker(game.where)) {
if (game.battle_round < 4) can_fire = true
if (game.battle_round > 1) can_retreat = true
if (game.battle_round < 4) can_pass = true
@@ -2025,10 +2079,10 @@ states.battle_round = {
else
view.prompt = "Retreat with an army."
for (let b of game.battle_list) {
- if (can_fire) gen_action(view, 'battle_fire', b)
+ if (can_fire) gen_action_battle(view, 'battle_fire', b)
if (must_retreat || (can_retreat && can_retreat_with_block(b)))
- gen_action(view, 'battle_retreat', b)
- if (can_pass) gen_action(view, 'battle_pass', b)
+ gen_action_battle(view, 'battle_retreat', b)
+ if (can_pass) gen_action_battle(view, 'battle_pass', b)
gen_action_block(view, b)
}
},
@@ -2080,13 +2134,13 @@ function goto_battle_hits() {
}
function list_victims(p) {
- let is_candidate = (p === game.attacker[game.where]) ? is_attacker : is_defender
+ let is_candidate = (p === get_attacker(game.where)) ? is_attacker : is_defender
let max = 0
- for (let b of BLOCKLIST)
+ for (let b = 0; b < block_count; ++b)
if (is_candidate(b) && block_strength(b) > max)
max = block_strength(b)
let list = []
- for (let b of BLOCKLIST)
+ for (let b = 0; b < block_count; ++b)
if (is_candidate(b) && block_strength(b) === max)
list.push(b)
return list
@@ -2115,7 +2169,7 @@ states.battle_hits = {
return view.prompt = "Waiting for " + game.active + " to apply hits..."
view.prompt = "Assign " + game.hits + (game.hits !== 1 ? " hits" : " hit") + " to your armies."
for (let b of game.battle_list) {
- gen_action(view, 'battle_hit', b)
+ gen_action_battle(view, 'battle_hit', b)
gen_action_block(view, b)
}
},
@@ -2135,7 +2189,7 @@ states.retreat = {
view.prompt = "Retreat " + block_name(game.who) + "."
let from = game.location[game.who]
for (let to of SPACES[from].exits) {
- if (block_owner(game.who) === game.attacker[from]) {
+ if (block_owner(game.who) === get_attacker(from)) {
if (can_attacker_retreat_to(game.who, from, to))
gen_action_space(view, to)
} else {
@@ -2158,7 +2212,7 @@ states.retreat = {
game.flash = block_name(game.who) + " retreated."
log_battle(game.flash)
game.turn_log.push([game.active, to])
- game.moved[game.who] = true
+ set_add(game.moved, game.who)
resume_battle()
}
},
@@ -2190,17 +2244,17 @@ states.sea_retreat = {
log_battle(game.flash)
game.turn_log.push([game.active, from, to])
move_to(game.who, from, to)
- game.moved[game.who] = true
+ set_add(game.moved, game.who)
resume_battle()
},
undo: pop_undo,
}
function goto_regroup() {
- game.active = game.attacker[game.where]
+ game.active = get_attacker(game.where)
if (is_enemy_space(game.where))
game.active = enemy(game.active)
- log(game.active + " won the battle in " + game.where + "!")
+ log(game.active + " won the battle in " + space_name(game.where) + "!")
game.state = 'regroup'
game.turn_log = []
clear_undo()
@@ -2211,7 +2265,7 @@ states.regroup = {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to regroup..."
view.prompt = "Regroup: Choose an army to move."
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === game.where) {
if (can_regroup(b))
gen_action_block(view, b)
@@ -2228,7 +2282,7 @@ states.regroup = {
pass: function () {
print_turn_log("regrouped")
clear_undo()
- game.where = null
+ game.where = -1
goto_pick_battle()
},
undo: pop_undo,
@@ -2251,7 +2305,7 @@ states.regroup_to = {
let from = game.location[game.who]
game.turn_log.push([from, to])
move_to(game.who, from, to)
- game.who = null
+ game.who = -1
game.state = 'regroup'
},
block: pop_undo,
@@ -2259,7 +2313,7 @@ states.regroup_to = {
}
function end_turn() {
- game.moved = {}
+ game.moved = []
if (game.turn === 5) {
cleopatra_goes_home()
check_victory()
@@ -2271,13 +2325,13 @@ function end_turn() {
function cleopatra_goes_home() {
game.active = CAESAR
- if (game.location[CLEOPATRA] !== ALEXANDRIA)
+ if (game.location[B_CLEOPATRA] !== ALEXANDRIA)
log("Cleopatra went home to Alexandria.")
if (is_friendly_space(ALEXANDRIA))
- set_add(game.traitor, CLEOPATRA)
+ set_add(game.traitor, B_CLEOPATRA)
else
- set_delete(game.traitor, CLEOPATRA)
- game.location[CLEOPATRA] = ALEXANDRIA
+ set_delete(game.traitor, B_CLEOPATRA)
+ game.location[B_CLEOPATRA] = ALEXANDRIA
}
function check_victory() {
@@ -2310,7 +2364,7 @@ function check_victory() {
function count_navis_to_port() {
let count = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (block_owner(b) === game.active && BLOCKS[b].type === 'navis')
if (SPACES[game.location[b]].type === 'sea')
if (can_navis_move_to_port(b))
@@ -2352,7 +2406,7 @@ states.navis_to_port = {
return view.prompt = "Waiting for " + game.active + " to move navis to port..."
view.prompt = "Move all Navis to a friendly port."
let count = 0
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (block_owner(b) === game.active && BLOCKS[b].type === 'navis') {
if (SPACES[game.location[b]].type === 'sea') {
if (can_navis_move_to_port(b)) {
@@ -2397,7 +2451,7 @@ states.navis_to_port_where = {
let from = game.location[game.who]
game.turn_log.push([from, to])
game.location[game.who] = to
- game.who = null
+ game.who = -1
game.state = 'navis_to_port'
},
block: pop_undo,
@@ -2416,8 +2470,8 @@ states.disband = {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to disband..."
let okay_to_end = true
- for (let b of BLOCKLIST) {
- if (block_owner(b) === game.active && is_map_space(game.location[b]) && b !== CLEOPATRA) {
+ for (let b = 0; b < block_count; ++b) {
+ if (block_owner(b) === game.active && is_map_space(game.location[b]) && b !== B_CLEOPATRA) {
if (is_over_supply_limit(game.location[b])) {
okay_to_end = false
gen_action_block(view, b)
@@ -2429,9 +2483,9 @@ states.disband = {
view.prompt = "Disband armies in excess of supply."
} else {
view.prompt = "You may disband armies to your levy pool."
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (is_map_space(game.location[b]))
- if (block_owner(b) === game.active && b !== CLEOPATRA)
+ if (block_owner(b) === game.active && b !== B_CLEOPATRA)
gen_action_block(view, b)
}
gen_action_pass(view, "End disbanding")
@@ -2460,7 +2514,7 @@ states.disband = {
function end_year() {
game.year ++
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === DEAD && BLOCKS[b].type !== 'leader') {
disband_block(b)
}
@@ -2515,15 +2569,17 @@ exports.setup = function (seed, scenario, options) {
show_cards: false,
year: 705,
turn: 0,
- location: {},
- steps: {},
+ location: [],
+ steps: [],
traitor: [],
- moved: {},
+ moved: [],
limits: {},
- last_used: {},
- sea_moved: {},
- attacker: {},
- main_road: {},
+ c_last_used: [],
+ p_last_used: [],
+ c_attacker: [],
+ p_attacker: [],
+ sea_moved: [],
+ main_road: [],
reserves: [],
}
@@ -2599,17 +2655,17 @@ exports.setup = function (seed, scenario, options) {
}
function deploy_block(owner, location, name) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (BLOCKS[b].owner === owner && BLOCKS[b].name === name) {
game.steps[b] = BLOCKS[b].steps
- game.location[b] = location
+ game.location[b] = space_index[location]
return
}
}
}
function setup_historical_deployment() {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
game.location[b] = LEVY
game.steps[b] = BLOCKS[b].steps
}
@@ -2650,10 +2706,29 @@ function setup_historical_deployment() {
exports.action = function (state, current, action, arg) {
game = state
let S = states[game.state]
- if (action in S)
+ if (action in S) {
+ switch (action) {
+ case 'block':
+ case 'battle_fire':
+ case 'battle_retreat':
+ case 'battle_pass':
+ case 'battle_hit':
+ if (typeof arg === 'string')
+ arg = block_index[arg]
+ break
+ case 'space':
+ if (typeof arg === 'string')
+ arg = space_index[arg]
+ break
+ case 'secret':
+ if (typeof arg[0] === 'string')
+ arg[0] = space_index[arg[0]]
+ break
+ }
S[action](arg, current)
- else
+ } else {
throw new Error("Invalid action: " + action)
+ }
return game
}
@@ -2672,19 +2747,20 @@ exports.resign = function (state, current) {
}
function make_battle_view() {
+ let attacker = get_attacker(game.where)
let bv = {
- A: game.attacker[game.where],
+ A: attacker,
CF: [], CR: [],
PF: [], PR: [],
flash: game.flash
}
- bv.title = game.attacker[game.where]
+ bv.title = attacker
if (game.surprise === game.where)
bv.title += " surprise attacks "
else
bv.title += " attacks "
- bv.title += game.where
+ bv.title += space_name(game.where)
bv.title += " \u2014 round " + game.battle_round + " of 4"
function is_battle_reserve(b) {
@@ -2692,7 +2768,7 @@ function make_battle_view() {
}
function fill_cell(name, p, fn) {
- for (let b of BLOCKLIST) {
+ for (let b = 0; b < block_count; ++b) {
if (game.location[b] === game.where & block_owner(b) === p && fn(b)) {
bv[name].push(b)
}
@@ -2731,7 +2807,7 @@ exports.view = function(state, current) {
c_card: (game.show_cards || current === CAESAR) ? game.c_card : 0,
p_card: (game.show_cards || current === POMPEIUS) ? game.p_card : 0,
hand: (current === CAESAR) ? game.c_hand : (current === POMPEIUS) ? game.p_hand : observer_hand(),
- who: (game.active === current) ? game.who : null,
+ who: (game.active === current) ? game.who : -1,
location: game.location,
traitor: game.traitor,
steps: game.steps,