From e8b02e17173feec741bd4475a299aa664346a41f Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sun, 12 Nov 2023 01:53:17 +0100 Subject: Show last used border, border limits, and main attack. Optimize border state representation (use maps instead of objects). --- data.js | 52 ++++++++ play.css | 20 ++- play.html | 1 + play.js | 58 +++++++++ rules.js | 160 +++++++++++++++++------ tools/borders.svg | 359 +++++++++++++++++++++++++++++++++++++++++++++++++++ tools/genborders.js | 53 ++++++++ tools/makeborders.js | 32 +++++ 8 files changed, 696 insertions(+), 39 deletions(-) create mode 100644 tools/borders.svg create mode 100644 tools/genborders.js create mode 100644 tools/makeborders.js diff --git a/data.js b/data.js index 920e8c6..d1d6f8b 100644 --- a/data.js +++ b/data.js @@ -90,6 +90,58 @@ let AREAS = [] let BORDERS = [] +const BORDERS_XY = { + "England / Dunbar": {"x":1285,"y":1320}, + "England / Annan": {"x":1065,"y":1630}, + "England / Teviot": {"x":1210,"y":1495}, + "Ross / Garmoran": {"x":505,"y":450}, + "Ross / Moray": {"x":665,"y":455}, + "Garmoran / Moray": {"x":550,"y":590}, + "Garmoran / Lochaber": {"x":445,"y":670}, + "Moray / Strathspey": {"x":860,"y":460}, + "Moray / Lochaber": {"x":565,"y":665}, + "Moray / Badenoch": {"x":715,"y":610}, + "Strathspey / Buchan": {"x":1110,"y":430}, + "Strathspey / Badenoch": {"x":880,"y":530}, + "Buchan / Badenoch": {"x":990,"y":565}, + "Buchan / Mar": {"x":1095,"y":605}, + "Buchan / Angus": {"x":1240,"y":645}, + "Lochaber / Badenoch": {"x":675,"y":730}, + "Lochaber / Argyll": {"x":530,"y":860}, + "Lochaber / Atholl": {"x":635,"y":855}, + "Badenoch / Mar": {"x":904,"y":672}, + "Badenoch / Atholl": {"x":730,"y":790}, + "Mar / Angus": {"x":1035,"y":750}, + "Mar / Atholl": {"x":835,"y":785}, + "Angus / Atholl": {"x":880,"y":855}, + "Angus / Fife": {"x":965,"y":900}, + "Argyll / Atholl": {"x":585,"y":950}, + "Argyll / Lennox": {"x":545,"y":1065}, + "Atholl / Lennox": {"x":615,"y":1025}, + "Atholl / Mentieth": {"x":690,"y":980}, + "Atholl / Fife": {"x":845,"y":905}, + "Lennox / Mentieth": {"x":725,"y":1185}, + "Lennox / Carrick": {"x":625,"y":1310}, + "Lennox / Lanark": {"x":725,"y":1260}, + "Mentieth / Fife": {"x":880,"y":1060}, + "Mentieth / Lanark": {"x":810,"y":1235}, + "Mentieth / Lothian": {"x":900,"y":1215}, + "Carrick / Lanark": {"x":790,"y":1450}, + "Carrick / Galloway": {"x":680,"y":1556}, + "Carrick / Annan": {"x":850,"y":1540}, + "Lanark / Lothian": {"x":905,"y":1275}, + "Lanark / Selkirk": {"x":922,"y":1377}, + "Lanark / Annan": {"x":888,"y":1470}, + "Lothian / Selkirk": {"x":1010,"y":1300}, + "Lothian / Dunbar": {"x":1100,"y":1235}, + "Selkirk / Dunbar": {"x":1115,"y":1310}, + "Selkirk / Annan": {"x":980,"y":1472}, + "Selkirk / Teviot": {"x":1080,"y":1405}, + "Dunbar / Teviot": {"x":1195,"y":1335}, + "Galloway / Annan": {"x":860,"y":1625}, + "Annan / Teviot": {"x":1070,"y":1525}, +} + ;(function () { function border(A,B,T) { A = area_index[A] diff --git a/play.css b/play.css index 8ac1672..e5a39f0 100644 --- a/play.css +++ b/play.css @@ -186,6 +186,25 @@ body.shift .block.known:hover { z-index: 100; } +.border { + position: absolute; + width: 24px; + height: 24px; + border-radius: 50%; + text-align: center; + line-height: 24px; + font-size: 16px; + font-weight: bold; + color: white; + background-color: #654; +} + +.oldblocks .border.England { background-color: brown; } +.oldblocks .border.Scotland { background-color: #06a; } + +.newblocks .border.England { background-color: #a12; } +.newblocks .border.Scotland { background-color: #059; } + #blocks > .block { position: absolute; } @@ -202,7 +221,6 @@ body.shift .block.known:hover { box-shadow: 0 0 2px 1px #0002; } - .oldblocks .block.England { border: 4px solid brown; background-color: brown; } .oldblocks .block.Scotland { border: 4px solid #06a; background-color: #06a; } diff --git a/play.html b/play.html index bae7d80..52dd012 100644 --- a/play.html +++ b/play.html @@ -565,6 +565,7 @@ c50 53 55 80 28 143 -18 42 -21 62 -16 107 17 147 18 179 6 245 -15 91 -56
+
diff --git a/play.js b/play.js index 09493cd..e65c7d6 100644 --- a/play.js +++ b/play.js @@ -16,6 +16,22 @@ function set_has(set, item) { return false } +function map_get(map, key, missing) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else + return map[(m<<1)+1] + } + return missing +} + const ENEMY = { Scotland: "England", England: "Scotland" } const ENGLAND_BAG = area_index["E. Bag"] @@ -70,6 +86,7 @@ let ui = { cards: {}, card_backs: {}, areas: [], + borders: [], blocks: [], battle_menu: [], battle_block: [], @@ -383,6 +400,21 @@ function build_map() { build_battle_block(b, block) build_map_block(b, block) } + + for (let name in BORDERS_XY) { + let xy = BORDERS_XY[name] + let [a, b] = name.split(" / ") + a = area_index[a] + b = area_index[b] + let id = a * 100 + b + let e = document.createElement("div") + e.my_id = id + e.className = "hide" + e.style.left = (xy.x - 12) + "px" + e.style.top = (xy.y - 12) + "px" + ui.borders.push(e) + document.getElementById("borders").appendChild(e) + } } build_map() @@ -612,6 +644,32 @@ function update_map() { } else { ui.areas[view.where].classList.add('battle') } + + for (let e of ui.borders) { + let u = map_get(view.last_used, e.my_id, 0) + let n = map_get(view.border_limit, e.my_id, "") + if (view.main_border && set_has(view.main_border, e.my_id)) + n += "*" + switch (u) { + case 1: + e.className = "border Scotland" + e.textContent = n + break + case 2: + e.className = "border England" + e.textContent = n + break + case 0: + if (n) { + e.className = "border" + e.textContent = n + } else { + e.className = "hide" + e.textContent = "" + } + break + } + } } function update_cards() { diff --git a/rules.js b/rules.js index 0412cb8..392e5e0 100644 --- a/rules.js +++ b/rules.js @@ -19,6 +19,9 @@ const first_map_area = 3 const ENEMY = { Scotland: "England", England: "Scotland" } +const PID = { "": 0, Scotland: 1, England: 2 } +const UNPID = [ "", "Scotland", "England" ] + const OBSERVER = "Observer" const BOTH = "Both" const ENGLAND = "England" @@ -389,7 +392,7 @@ function border_id(a, b) { } function border_was_last_used_by_enemy(from, to) { - return game.last_used[border_id(from, to)] === ENEMY[game.active] + return map_get(game.last_used, border_id(from, to), 0) === PID[ENEMY[game.active]] } function border_type(a, b) { @@ -397,11 +400,15 @@ function border_type(a, b) { } function border_limit(a, b) { - return game.border_limit[border_id(a,b)] || 0 + return map_get(game.border_limit, border_id(a,b), 0) +} + +function set_border_limit(a, b, n) { + map_set(game.border_limit, border_id(a,b), n) } function reset_border_limits() { - game.border_limit = {} + game.border_limit.length = 0 } function count_friendly(where) { @@ -576,13 +583,13 @@ function is_battle_reserve(b) { } 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 !set_has(game.reserves, 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 !set_has(game.reserves, b) return false } @@ -870,8 +877,8 @@ function start_game_turn() { // Reset movement and attack tracking state game.truce = false reset_border_limits() - game.last_used = {} - game.attacker = {} + game.last_used = [] + game.attacker = [] game.reserves = [] game.moved = [] @@ -1108,7 +1115,7 @@ function defect_nobles(list) { log(name + " defected.") who = swap_blocks(who) if (is_contested_area(where)) - game.attacker[where] = block_owner(who) + set_attacker(where, block_owner(who)) } } resume_coronation() @@ -1182,7 +1189,7 @@ states.herald = { let where = game.location[who] who = swap_blocks(who) if (is_contested_area(where)) { - game.attacker[where] = game.active + set_attacker(where, game.active) start_battle(where, 'herald') return } @@ -1373,7 +1380,7 @@ function end_pillage(where) { game.where = NOWHERE delete game.pillage if (is_contested_area(where)) { - game.attacker[where] = ENEMY[game.active] + set_attacker(where, ENEMY[game.active]) start_battle(where, 'pillage') } else { end_player_turn() @@ -1455,12 +1462,36 @@ states.sea_move_to = { // MOVE PHASE +function get_attacker(x) { + return UNPID[map_get(game.attacker, x, 0)] +} + +function set_attacker(x, who) { + return map_set(game.attacker, x, PID[who]) +} + +function main_border(to) { + return map_get(game.main_border, to, 0) +} + +function main_origin(to) { + return map_get(game.main_origin, to, 0) +} + +function set_main_border(to, x) { + map_set(game.main_border, to, x) +} + +function set_main_origin(to, x) { + map_set(game.main_origin, to, x) +} + function goto_move_phase(moves) { game.state = 'move_who' game.moves = moves game.activated = [] - game.main_origin = {} - game.main_border = {} + game.main_origin = [] + game.main_border = [] game.turn_log = [] clear_undo() } @@ -1507,17 +1538,17 @@ states.move_who = { function move_block(who, from, to) { game.location[who] = to - game.border_limit[border_id(from, to)] = border_limit(from, to) + 1 + set_border_limit(from, to, border_limit(from, to) + 1) game.distance ++ if (is_contested_area(to)) { - game.last_used[border_id(from, to)] = game.active - if (!game.attacker[to]) { - game.attacker[to] = game.active - game.main_border[to] = from - game.main_origin[to] = game.origin + map_set(game.last_used, border_id(from, to), PID[game.active]) + if (!get_attacker(to)) { + set_attacker(to, game.active) + set_main_border(to, from) + set_main_origin(to, game.origin) return ATTACK_MARK } else { - if (game.attacker[to] !== game.active || game.main_border[to] !== from || game.main_origin[to] !== game.origin) { + if (get_attacker(to) !== game.active || main_border(to) !== from || main_origin(to) !== game.origin) { set_add(game.reserves, who) return RESERVE_MARK } else { @@ -1567,9 +1598,9 @@ states.move_where = { game.location[game.who] = to set_add(game.moved, game.who) if (is_contested_area(to)) { - if (!game.attacker[to]) { + if (!get_attacker(to)) { game.turn_log.push([area_tag(from), area_tag(to) + ATTACK_MARK + " (Norse)"]) - game.attacker[to] = game.active + set_attacker(to, game.active) } else { game.turn_log.push([area_tag(from), area_tag(to) + RESERVE_MARK + " (Norse)"]) set_add(game.reserves, game.who) @@ -1627,6 +1658,7 @@ function bring_on_reserves() { } function goto_battle_phase() { + reset_border_limits() if (have_contested_areas()) { game.active = game.p1 game.state = 'battle_phase' @@ -1679,7 +1711,7 @@ function end_battle() { reset_border_limits() game.moved = [] - game.active = game.attacker[game.where] + game.active = get_attacker(game.where) let victor = game.active if (is_contested_area(game.where)) victor = ENEMY[game.active] @@ -1719,7 +1751,7 @@ function goto_battle_round(new_battle_round) { if (count_defenders() === 0) { log("Defending main force was eliminated.") log("Battlefield control changed.") - game.attacker[game.where] = ENEMY[game.attacker[game.where]] + set_attacker(game.where, ENEMY[get_attacker(game.where)]) } else if (count_attackers() === 0) { log("Attacking main force was eliminated.") } @@ -1780,7 +1812,7 @@ function battle_step(active, initiative, candidate) { } function pump_battle_step() { - let attacker = game.attacker[game.where] + let attacker = get_attacker(game.where) let defender = ENEMY[attacker] if (battle_step(defender, 'A', is_defender)) return @@ -1849,7 +1881,7 @@ function pass_with_block(b) { } function count_enemy_hp_in_battle() { - let is_candidate = (game.active === game.attacker[game.where]) ? is_defender : is_attacker + let is_candidate = (game.active === get_attacker(game.where)) ? is_defender : is_attacker let n = 0 for (let b = 0; b < block_count; ++b) if (is_candidate(b)) @@ -1962,7 +1994,7 @@ function apply_hit(who) { } 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 = 0; b < block_count; ++b) if (is_candidate(b) && game.steps[b] > max) @@ -1994,7 +2026,7 @@ states.battle_hits = { } function goto_retreat() { - game.active = game.attacker[game.where] + game.active = get_attacker(game.where) if (is_contested_area(game.where)) { game.state = 'retreat' game.turn_log = [] @@ -2131,7 +2163,7 @@ states.retreat_in_battle = { } function goto_regroup() { - game.active = game.attacker[game.where] + game.active = get_attacker(game.where) if (is_enemy_area(game.where)) game.active = ENEMY[game.active] game.state = 'regroup' @@ -2157,22 +2189,22 @@ states.regroup = { }, end_regroup: function () { print_turn_log("regrouped") - game.attacker[game.where] = null // XXX ??? + set_attacker(game.where, "") // XXX ??? game.where = NOWHERE clear_undo() game.active = game.battle_active delete game.battle_active if (game.battle_reason === 'herald') { delete game.battle_reason - game.last_used = {} + game.last_used = [] end_player_turn() } else if (game.battle_reason === 'pillage') { delete game.battle_reason - game.last_used = {} + game.last_used = [] end_player_turn() } else if (game.battle_reason === 'coronation') { delete game.battle_reason - game.last_used = {} + game.last_used = [] resume_coronation() } else { delete game.battle_reason @@ -3044,7 +3076,7 @@ function make_battle_view() { flash: game.flash } - battle.title = game.attacker[game.where] + " attacks " + area_name(game.where) + battle.title = get_attacker(game.where) + " attacks " + area_name(game.where) battle.title += " \u2014 round " + game.battle_round + " of 3" function fill_cell(cell, owner, fn) { @@ -3074,11 +3106,12 @@ exports.setup = function (seed, scenario, options) { moved: [], reserves: [], - attacker: {}, - border_limit: {}, - last_used: {}, - main_border: {}, - main_origin: {}, + attacker: [], + border_limit: [], + last_used: [], + main_border: [], + main_origin: [], + show_cards: 0, who: NOBODY, where: NOWHERE, @@ -3167,9 +3200,17 @@ exports.view = function(state, current) { location: game.location, steps: game.steps, moved: game.moved, + last_used: game.last_used, + border_limit: game.border_limit, active: game.active, } + if (game.main_border && game.main_border.length > 0) { + view.main_border = [] + for (let i = 0; i < game.main_border.length; i += 2) + set_add(view.main_border, border_id(game.main_border[i+0], game.main_border[i+1])) + } + states[game.state].prompt(view, current) if (states[game.state].show_battle) @@ -3197,6 +3238,15 @@ function array_insert(array, index, item) { return array } +function array_insert_pair(array, index, key, value) { + for (let i = array.length; i > index; i -= 2) { + array[i] = array[i-2] + array[i+1] = array[i-1] + } + array[index] = key + array[index+1] = value +} + function set_clear(set) { set.length = 0 } @@ -3265,6 +3315,40 @@ function set_toggle(set, item) { return array_insert(set, a, item) } +function map_get(map, key, missing) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else + return map[(m<<1)+1] + } + return missing +} + +function map_set(map, key, value) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else { + map[(m<<1)+1] = value + return + } + } + array_insert_pair(map, a<<1, key, value) +} + // Fast deep copy for objects without cycles function object_copy(original) { if (Array.isArray(original)) { diff --git a/tools/borders.svg b/tools/borders.svg new file mode 100644 index 0000000..0e05b74 --- /dev/null +++ b/tools/borders.svg @@ -0,0 +1,359 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/genborders.js b/tools/genborders.js new file mode 100644 index 0000000..9c50721 --- /dev/null +++ b/tools/genborders.js @@ -0,0 +1,53 @@ +const fs = require("fs") + +const { round, floor, ceil } = Math + +let output = {} +let mode, name, x, y, w, h, cx, cy, rx, ry + +function flush() { + if (mode === 'circle') { + output[name] = { x: cx, y: cy } + } + x = y = w = h = cx = cy = rx = ry = 0 + name = null +} + +for (let line of fs.readFileSync("tools/borders.svg", "utf-8").split("\n")) { + line = line.trim() + if (line.startsWith(" + +`) + +for (let id = 0; id < data.BORDERS.length; ++id) { + if (data.BORDERS[id]) { + let a = (id / 100) | 0 + let b = id % 100 + let x = (data.AREAS[a].x + data.AREAS[b].x) >> 1 + let y = (data.AREAS[a].y + data.AREAS[b].y) >> 1 + let label = data.AREAS[a].name + " / " + data.AREAS[b].name + print(``) + + } +} + +print(``) -- cgit v1.2.3