From 0cee28075504d3354e5c327200d472c760452b94 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sun, 12 Nov 2023 14:34:56 +0100 Subject: Show border limits. --- play.css | 20 ++ play.html | 1 + play.js | 170 ++++++++++++++ rules.js | 142 ++++++++++-- tools/borders.svg | 626 +++++++++++++++++++++++++++++++++++++++++++++++++++ tools/coord.js | 7 + tools/genborders.js | 53 +++++ tools/makeborders.js | 29 +++ tools/slice.sh | 16 ++ 9 files changed, 1044 insertions(+), 20 deletions(-) create mode 100644 tools/borders.svg create mode 100644 tools/coord.js create mode 100644 tools/genborders.js create mode 100644 tools/makeborders.js create mode 100644 tools/slice.sh diff --git a/play.css b/play.css index 19731a1..d6aac5a 100644 --- a/play.css +++ b/play.css @@ -147,6 +147,26 @@ header.your_turn { background-color: orange; } visibility: hidden; } +.border { + position: absolute; + width: 24px; + height: 24px; + border-radius: 50%; + text-align: center; + line-height: 24px; + font-size: 16px; + font-weight: bold; + color: #000c; + background-color: #535a26; +} + +.border.sea { background-color: #ecb30c; } +.border.major { background-color: #cbc183; } +.border.minor { background-color: #946127; } +.border.river { background-color: #91a1a9; } +.border.Lancaster { background-color: brown; color: #fed; } +.border.York { background-color: #fdfefc; color: #ae2e24; } + /* BLOCKS */ body.shift .block.known:hover { diff --git a/play.html b/play.html index 538aeb6..01194aa 100644 --- a/play.html +++ b/play.html @@ -529,6 +529,7 @@ l0 -3 539 0 540 0 2 2 3 3 -1 78 c0 60 0 79 1 83 3 11 14 23 25 27 5 1 24 1 +
diff --git a/play.js b/play.js index 2870dff..7071231 100644 --- a/play.js +++ b/play.js @@ -1,5 +1,37 @@ "use strict" +function set_has(set, item) { + let a = 0 + let b = set.length - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = set[m] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else + return true + } + return false +} + +function 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 LANCASTER = "Lancaster" const YORK = "York" const ENEMY = { York: "Lancaster", Lancaster: "York" } @@ -76,6 +108,100 @@ const LONG_NAME = { "Gloucester": "Duke of Gloucester", } +// :!r node tools/genborders.js +const BORDERS_XY = { + "English Channel / Calais": [1317,1792], + "English Channel / France": [378,125], + "English Channel / Cornwall": [382,1762], + "English Channel / Dorset": [860,1660], + "English Channel / Kent": [1437,1565], + "English Channel / Sussex": [1090,1631], + "Irish Sea / France": [185,245], + "Irish Sea / Ireland": [176,396], + "Irish Sea / Scotland": [575,284], + "Irish Sea / Caernarvon": [524,816], + "Irish Sea / Chester": [627,782], + "Irish Sea / Cornwall": [286,1646], + "Irish Sea / Cumbria": [570,482], + "Irish Sea / Glamorgan": [459,1366], + "Irish Sea / Isle of Man": [389,467], + "Irish Sea / Lancaster": [645,654], + "Irish Sea / Pembroke": [303,1187], + "Irish Sea / Somerset": [688,1418], + "North Sea / Calais": [1565,1760], + "North Sea / Scotland": [844,16], + "North Sea / East Anglia": [1582,972], + "North Sea / East Yorks": [1224,643], + "North Sea / Essex": [1504,1295], + "North Sea / Kent": [1562,1445], + "North Sea / Lincoln": [1305,801], + "North Sea / Middlesex": [1382,1392], + "North Sea / Northumbria": [952,248], + "North Sea / Rutland": [1298,965], + "Scotland / Cumbria": [679,248], + "Scotland / Northumbria": [774,171], + "Caernarvon / Chester": [677,920], + "Caernarvon / Pembroke": [479,1071], + "Caernarvon / Powys": [522,995], + "Chester / Derby": [842,879], + "Chester / Hereford": [717,1012], + "Chester / Lancaster": [762,794], + "Chester / Powys": [610,986], + "Chester / Warwick": [793,982], + "Cornwall / Dorset": [613,1627], + "Cornwall / Somerset": [528,1574], + "Cumbria / Lancaster": [740,517], + "Cumbria / North Yorks": [819,478], + "Cumbria / Northumbria": [782,342], + "Derby / Lancaster": [881,784], + "Derby / Leicester": [1029,943], + "Derby / Lincoln": [1098,840], + "Derby / South Yorks": [986,814], + "Derby / Warwick": [878,981], + "Dorset / Somerset": [716,1566], + "Dorset / Sussex": [965,1582], + "Dorset / Wilts": [879,1558], + "East Anglia / Essex": [1452,1168], + "East Anglia / Rutland": [1354,1042], + "East Yorks / North Yorks": [971,521], + "East Yorks / Northumbria": [997,431], + "East Yorks / South Yorks": [1039,654], + "Essex / Leicester": [1218,1165], + "Essex / Middlesex": [1300,1261], + "Essex / Rutland": [1287,1132], + "Glamorgan / Hereford": [712,1226], + "Glamorgan / Pembroke": [447,1274], + "Glamorgan / Powys": [563,1195], + "Gloucester / Hereford": [799,1282], + "Gloucester / Oxford": [956,1254], + "Gloucester / Somerset": [786,1397], + "Gloucester / Warwick": [881,1208], + "Gloucester / Wilts": [913,1351], + "Hereford / Powys": [640,1112], + "Hereford / Warwick": [793,1127], + "Kent / Middlesex": [1329,1401], + "Kent / Sussex": [1276,1509], + "Lancaster / North Yorks": [791,623], + "Lancaster / South Yorks": [880,701], + "Leicester / Lincoln": [1145,937], + "Leicester / Middlesex": [1157,1180], + "Leicester / Oxford": [1051,1173], + "Leicester / Rutland": [1172,1057], + "Leicester / Warwick": [974,1048], + "Lincoln / Rutland": [1211,967], + "Lincoln / South Yorks": [1111,741], + "Middlesex / Oxford": [1138,1298], + "Middlesex / Sussex": [1152,1413], + "North Yorks / Northumbria": [905,434], + "North Yorks / South Yorks": [939,611], + "Oxford / Sussex": [1085,1404], + "Oxford / Warwick": [975,1128], + "Oxford / Wilts": [1032,1363], + "Pembroke / Powys": [517,1141], + "Somerset / Wilts": [838,1452], + "Sussex / Wilts": [1018,1495], +} + function toggle_blocks() { document.getElementById("map").classList.toggle("hide_blocks") } @@ -85,6 +211,7 @@ let ui = { card_backs: {}, areas: {}, blocks: {}, + borders: [], battle_menu: {}, battle_block: {}, present: new Set(), @@ -458,6 +585,23 @@ function build_map() { build_battle_block(b, block) build_map_block(b, block) } + + for (let name in BORDERS_XY) { + let [x, y] = BORDERS_XY[name] + let [a, b] = name.split(" / ") + let id = area_index[a] * 100 + area_index[b] + let type = BORDERS[id] || "sea" + if (type !== "sea") { + let e = document.createElement("div") + e.my_id = id + e.my_show = "border " + type + e.className = "hide" + e.style.left = (x - 12) + "px" + e.style.top = (y - 12) + "px" + ui.borders.push(e) + document.getElementById("borders").appendChild(e) + } + } } function update_steps(b, steps, element) { @@ -672,6 +816,32 @@ function update_map() { if (view.who !== NOBODY) ui.blocks[view.who].classList.add('selected') } + + 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 Lancaster" + e.textContent = n + break + case 2: + e.className = "border York" + e.textContent = n + break + case 0: + if (n) { + e.className = e.my_show + e.textContent = n + } else { + e.className = "hide" + e.textContent = "" + } + break + } + } } function update_cards() { diff --git a/rules.js b/rules.js index 5efce8c..9bca996 100644 --- a/rules.js +++ b/rules.js @@ -27,6 +27,9 @@ const ENEMY = { Lancaster: "York", York: "Lancaster" } const OBSERVER = "Observer" const BOTH = "Both" +const PLAYER_ID = { "": 0, Lancaster: 1, York: 2 } +const ID_PLAYER = [ "", "Lancaster", "York" ] + // areas const NOWHERE = 0 const POOL = 1 @@ -608,11 +611,11 @@ 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) === PLAYER_ID[ENEMY[game.active]] } function border_was_last_used_by_active(from, to) { - return game.last_used[border_id(from, to)] === game.active + return map_get(game.last_used, border_id(from, to), 0) === PLAYER_ID[game.active] } function border_type(a, b) { @@ -620,11 +623,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) { @@ -1399,7 +1406,7 @@ function start_game_turn() { // Reset movement and attack tracking state reset_border_limits() - game.last_used = {} + game.last_used = [] game.attacker = {} set_clear(game.reserves) set_clear(game.moved) @@ -1733,7 +1740,7 @@ states.muster_move_2 = { // ACTION PHASE function use_border(from, to) { - game.border_limit[border_id(from, to)] = border_limit(from, to) + 1 + set_border_limit(from, to, border_limit(from, to) + 1) } function move_block(who, from, to) { @@ -1741,12 +1748,12 @@ function move_block(who, from, to) { use_border(from, to) game.distance ++ if (is_contested_area(to)) { - game.last_used[border_id(from, to)] = game.active + map_set(game.last_used, border_id(from, to), PLAYER_ID[game.active]) if (!game.attacker[to]) { game.attacker[to] = game.active - game.main_border[to] = from + map_set(game.main_border, to, from) } else { - if (game.attacker[to] !== game.active || game.main_border[to] !== from) { + if (game.attacker[to] !== game.active || map_get(game.main_border, to, 0) !== from) { set_add(game.reserves, who) return RESERVE_MARK } @@ -1760,8 +1767,8 @@ function goto_action_phase(moves) { game.state = 'action_phase' game.moves = moves game.activated = [] - game.move_port = {} - game.main_border = {} + game.move_port = [] + game.main_border = [] game.turn_log = [] game.recruit_log = [] clear_undo() @@ -1812,7 +1819,7 @@ states.action_phase = { } if (can_block_sea_move(b)) { if (game.moves === 0) { - if (game.move_port[game.location[b]] !== undefined) + if (map_has(game.move_port, game.location[b])) gen_action(view, 'block', b) } else { gen_action(view, 'block', b) @@ -1892,7 +1899,7 @@ states.move_to = { let has_destination_port = false if (game.moves === 0) { for (let port of AREAS[to].exits) - if (game.move_port[game.origin] === port) + if (map_get(game.move_port, game.origin) === port) has_destination_port = true } else { if (game.active === game.piracy) @@ -1923,6 +1930,7 @@ states.move_to = { log_move_start(from) game.last_from = from if (is_sea_area(to)) { + // XXX use_border(from, to) // if displaying sea moves log_move_continue(to) game.location[game.who] = to game.state = 'sea_move_to' @@ -1953,7 +1961,7 @@ states.sea_move_to = { continue if (is_friendly_or_vacant_area(to)) { if (game.moves === 0) { - if (game.move_port[game.origin] === to) + if (map_get(game.move_port, game.origin) === to) gen_action(view, 'area', to) } else { gen_action(view, 'area', to) @@ -1965,6 +1973,8 @@ states.sea_move_to = { } }, area: function (to) { + // XXX use_border(game.location[game.who], to) // if displaying sea moves + game.location[game.who] = to set_add(game.moved, game.who) @@ -1979,13 +1989,13 @@ states.sea_move_to = { } else { // Can sea move two blocks between same major ports for 1 AP. log_move_continue(to) - if (game.move_port[game.origin] === to) { - delete game.move_port[game.origin] + if (map_get(game.move_port, game.origin) === to) { + map_delete(game.move_port, game.origin) } else { logp("sea moved.") --game.moves if (is_major_port(game.origin) && is_major_port(to)) - game.move_port[game.origin] = to + map_set(game.move_port, game.origin, to) } } @@ -2020,6 +2030,7 @@ function end_action() { // BATTLE PHASE function goto_battle_phase() { + reset_border_limits() if (have_contested_areas()) { game.active = game.p1 game.state = 'battle_phase' @@ -3463,9 +3474,9 @@ exports.setup = function (seed, scenario, options) { reserves: [], attacker: {}, - border_limit: {}, - last_used: {}, - main_border: {}, + border_limit: [], + last_used: [], + main_border: [], } // Old RNG for ancient replays @@ -3537,10 +3548,18 @@ exports.view = function(state, current) { steps: game.steps, moved: game.moved, dead: game.dead, + last_used: game.last_used, + border_limit: game.border_limit, battle: null, prompt: null, } + 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) @@ -3560,6 +3579,13 @@ function array_remove(array, index) { return array } +function array_remove_pair(array, index) { + let n = array.length + for (let i = index + 2; i < n; ++i) + array[i - 2] = array[i] + array.length = n - 2 +} + // insert item at index (faster than splice) function array_insert(array, index, item) { for (let i = array.length; i > index; --i) @@ -3568,6 +3594,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 } @@ -3620,6 +3655,73 @@ function set_delete(set, item) { return set } +function map_has(map, key) { + 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 true + } + 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 +} + +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) +} + +function map_delete(map, item) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else { + array_remove_pair(map, m<<1) + return + } + } +} + // 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..af5d4a5 --- /dev/null +++ b/tools/borders.svg @@ -0,0 +1,626 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/coord.js b/tools/coord.js new file mode 100644 index 0000000..5776990 --- /dev/null +++ b/tools/coord.js @@ -0,0 +1,7 @@ +for (let row=1; row<=9; ++row) { + let y = -(row-1) * 62 + for (let col=1; col<=7; ++col) { + let x = -(col-1) * 62 + console.log(`.known.block_${row}${col}{background-position:${x}px ${y}px}`) + } +} diff --git a/tools/genborders.js b/tools/genborders.js new file mode 100644 index 0000000..f639e8a --- /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] = [ cx, 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 in data.BORDERS) { + let a = (id / 100) | 0 + let b = id % 100 + let x = (data.AREAS[a].layout.x + data.AREAS[b].layout.x) >> 1 + let y = (data.AREAS[a].layout.y + data.AREAS[b].layout.y) >> 1 + let label = data.AREAS[a].name + " / " + data.AREAS[b].name + print(``) +} + +print(``) diff --git a/tools/slice.sh b/tools/slice.sh new file mode 100644 index 0000000..8053af7 --- /dev/null +++ b/tools/slice.sh @@ -0,0 +1,16 @@ +# white ffffff +# red eb2127 + +pnmcut -left 247 -top 394 -width 1736 -height 2232 tools/labels.ppm | pnmtopng > blocks300.png + +exit + +i=1 +for y in 394 642 890 1138 1386 1634 1882 2130 2378 +do + for x in 247 495 743 991 1239 1487 1735 + do + pnmcut -left $x -top $y -width 248 -height 248 tools/labels.ppm > tools/b$i.ppm + i=$(expr $i + 1) + done +done -- cgit v1.2.3