From 2cd898d379ed6a5fc8bfe60b1d8e3a20f343ab51 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Fri, 23 Sep 2022 17:24:31 +0200 Subject: Use numeric indexing for blocks and spaces. Use sets for moved, sea moved, last used, attacker and main road. Map old name indexing in action handler to allow old replays to work. --- data.js | 42 ++++-- play.js | 43 +++--- rules.js | 484 ++++++++++++++++++++++++++++++++++++--------------------------- 3 files changed, 330 insertions(+), 239 deletions(-) diff --git a/data.js b/data.js index 137ad39..4743c37 100644 --- a/data.js +++ b/data.js @@ -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 } diff --git a/play.js b/play.js index c5ef4a0..0214247 100644 --- a/play.js +++ b/play.js @@ -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]) 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, -- cgit v1.2.3