diff options
-rw-r--r-- | data.js | 42 | ||||
-rw-r--r-- | play.js | 43 | ||||
-rw-r--r-- | rules.js | 484 |
3 files changed, 330 insertions, 239 deletions
@@ -33,10 +33,14 @@ const CARDS = { 27: { name: "1/2", move: 1, levy: 2, image: "12" }, }; -const BLOCKS = {}; +const block_index = {} +const BLOCKS = [] +const space_index = {} +const SPACES = [] + const EDGES = {}; -const SPACES = { +const SPACE_XY = { Dead: { x: 300, y: 236 }, Levy: { x: 767, y: 1191 }, Aenos: { x: 1872, y: 463 }, @@ -119,13 +123,16 @@ const SPACES = { (function () { function space(axis, major_align, minor_align, wrap, name, type, value) { - SPACES[name].type = type; - SPACES[name].value = value | 0; - SPACES[name].exits = []; - SPACES[name].layout_axis = axis; - SPACES[name].layout_major = (1 - major_align) / 2; - SPACES[name].layout_minor = (1 - minor_align) / 2; - SPACES[name].wrap = wrap; + let id = space_index[name] = SPACES.length + SPACES[id] = SPACE_XY[name] + SPACES[id].name = name; + SPACES[id].type = type; + SPACES[id].value = value | 0; + SPACES[id].exits = []; + SPACES[id].layout_axis = axis; + SPACES[id].layout_major = (1 - major_align) / 2; + SPACES[id].layout_minor = (1 - minor_align) / 2; + SPACES[id].wrap = wrap; } space('X', 0, 0, 3, "Dead", "pool"); @@ -210,9 +217,11 @@ const SPACES = { space('X', 0, 0, 5, "Propontis", "sea"); function edge(a, b, type) { + a = space_index[a] + b = space_index[b] if (a > b) [a, b] = [b, a]; - let AB = a + "/" + b; + let AB = a * 100 + b; EDGES[AB] = type; SPACES[a].exits.push(b); SPACES[b].exits.push(a); @@ -393,10 +402,10 @@ const SPACES = { let index = 0; function block(owner, CR, steps, type, name, levy) { - let id = name; + let sid = name; let [initiative, firepower] = CR; if (type !== 'leader' && type !== 'cleopatra' && type !== 'legio') - id = owner[0] + " " + id; + sid = owner[0] + " " + sid; let descr = name; if (type !== 'leader' && type !== 'cleopatra' && type !== 'legio') descr = owner[0] + ". " + descr; @@ -406,14 +415,17 @@ const SPACES = { else descr += " (" + levy + ")"; } + + let id = block_index[sid] = BLOCKS.length BLOCKS[id] = { owner: owner, + sid: sid, name: name, steps: steps, initiative: initiative, - firepower: firepower, + firepower: firepower | 0, type: type, - levy: levy, + levy: space_index[levy], description: descr, label: index++, } @@ -502,4 +514,4 @@ const SPACES = { })(); if (typeof module !== 'undefined') - module.exports = { CARDS, BLOCKS, SPACES, EDGES } + module.exports = { CARDS, BLOCKS, SPACES, EDGES, block_index, space_index } @@ -16,11 +16,14 @@ function set_has(set, item) { return false } -const CLEOPATRA = "Cleopatra" -const DEAD = "Dead" -const LEVY = "Levy" +const DEAD = space_index["Dead"] +const LEVY = space_index["Levy"] + const ENEMY = { "Caesar": "Pompeius", "Pompeius": "Caesar" } +const block_count = BLOCKS.length +const space_count = SPACES.length + let label_style = window.localStorage['julius-caesar/label-style'] || 'columbia' let label_layout = window.localStorage['julius-caesar/label-layout'] || 'spread' @@ -145,7 +148,7 @@ function block_original_owner(who) { function block_owner(who) { if (set_has(view.traitor, who)) - return enemy(block_original_owner(who)) + return ENEMY[block_original_owner(who)] return block_original_owner(who) } @@ -233,7 +236,7 @@ function build_map() { ui.offmap_element = document.getElementById("offmap") ui.spaces_element = document.getElementById("spaces") - for (let s in SPACES) { + for (let s = 0; s < space_count; ++s) { let space = SPACES[s] let element = document.createElement("div") element.classList.add("space") @@ -315,7 +318,7 @@ function build_map() { ui.battle_menu[b] = menu } - for (let b in BLOCKS) { + for (let b = 0; b < block_count; ++b) { let block = BLOCKS[b] block.color = (block.name === "Cleopatra" ? "Cleopatra" : block.owner) build_map_block(b, block) @@ -468,7 +471,7 @@ function is_known_block(who) { function is_visible_block(where, who) { if (view.game_over && player === 'Observer') return true - if (where === "Levy") + if (where === LEVY) return block_owner(who) === player return true } @@ -476,7 +479,7 @@ function is_visible_block(where, who) { function update_map() { let layout = {} - for (let s in SPACES) + for (let s = 0; s < space_count; ++s) layout[s] = { north: [], south: [] } for (let b in view.location) { @@ -484,7 +487,7 @@ function update_map() { let element = ui.blocks[b] let space = view.location[b] if (is_visible_block(space, b)) { - let moved = view.moved[b] ? " moved" : "" + let moved = set_has(view.moved, b) ? " moved" : "" if (space === DEAD && info.type !== 'leader') moved = " moved" if (is_known_block(b)) { @@ -517,22 +520,22 @@ function update_map() { } } - for (let space in SPACES) - layout_blocks(space, layout[space].north, layout[space].south) + for (let s = 0; s < space_count; ++s) + layout_blocks(s, layout[s].north, layout[s].south) // Mark selections and highlights - for (let where in SPACES) { - if (ui.spaces[where]) { - ui.spaces[where].classList.remove('highlight') - ui.spaces[where].classList.remove('where') + for (let s = 0; s < space_count; ++s) { + if (ui.spaces[s]) { + ui.spaces[s].classList.remove('highlight') + ui.spaces[s].classList.remove('where') } } if (view.actions && view.actions.space) for (let where of view.actions.space) ui.spaces[where].classList.add('highlight') - for (let b in BLOCKS) { + for (let b = 0; b < block_count; ++b) { ui.blocks[b].classList.remove('highlight') ui.blocks[b].classList.remove('selected') } @@ -540,11 +543,11 @@ function update_map() { if (view.actions && view.actions.block) for (let b of view.actions.block) ui.blocks[b].classList.add('highlight') - if (view.who) + if (view.who >= 0) ui.blocks[view.who].classList.add('selected') } - for (let b in BLOCKS) { + for (let b = 0; b < block_count; ++b) { let s = view.location[b] if (view.actions && view.actions.secret && view.actions.secret.includes(s)) ui.blocks[b].classList.add('highlight') @@ -616,7 +619,7 @@ function update_battle() { ui.battle_block[block].classList.add("secret") else ui.battle_block[block].classList.remove("secret") - if (view.moved[block] || reserve) + if (set_has(view.moved, block) || reserve) ui.battle_block[block].classList.add("moved") else ui.battle_block[block].classList.remove("moved") @@ -626,7 +629,7 @@ function update_battle() { ui.battle_block[block].classList.add("known") } - for (let b in BLOCKS) { + for (let b = 0; b < block_count; ++b) { if (!ui.present.has(b)) { if (cell.contains(ui.battle_menu[b])) cell.removeChild(ui.battle_menu[b]) @@ -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, |