diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-08-13 19:43:43 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2023-10-01 16:11:22 +0200 |
commit | 4afafd6752f3034da84f9ae2bd82e5f1763793ea (patch) | |
tree | 2c0ada939626ab9222d3c59a6d31b184372cbede | |
parent | 74d2e6efd24db8570cd64f74c7de109c5b094c70 (diff) | |
download | waterloo-campaign-1815-4afafd6752f3034da84f9ae2bd82e5f1763793ea.tar.gz |
Reinforcements!
-rw-r--r-- | data.js | 63 | ||||
-rw-r--r-- | play.html | 2 | ||||
-rw-r--r-- | play.js | 101 | ||||
-rw-r--r-- | rules.js | 68 |
4 files changed, 193 insertions, 41 deletions
@@ -22,6 +22,13 @@ data.map = { towns: [1015,1018,1021,1024,1026,1100,1117,1118,1129,1201,1204,1209,1211,1215,1217,1221,1239,1340,1401,1407,1423,1433,1516,1526,1528,1534,1601,1603,1605,1623,1631,1716,1728,1737,1800,1810,1821,1825,1830,1903,1911,1915,1916,1919,1922,1928,1932,2001,2027,2035,2119,2122,2123,2219,2222,2223,2230,2308,2315,2317,2324,2327,2333,2337,2404,2500,2521,2529,2537,2604,2609,2618,2623,2715,2721,2723,2725,2730,2733,2736,2739,2827,2829,2840,2911,2936,3002,3013,3018,3020,3031,3125,3129,3135,3138,3204,3206,3226,3231,3233,3234,3240,3313,3327,3328,3402,3408,3417,3418,3438,3441,3512,3514,3523,3528,3614,3616,3617,3631,3636,3705,3708,3715,3718,3719,3723,3803,3828,3832,3915,3919,3925,3933,4006,4038], streams: [1021,1024,1120,1124,1224,1300,1314,1324,1401,1415,1501,1502,1514,1600,1601,1603,1604,1704,1837,1937,2038,2138,2407,2507,2524,2540,2604,2608,2609,2620,2621,2624,2625,2637,2641,2704,2708,2718,2719,2721,2724,2725,2737,2740,2741,2805,2808,2817,2820,2821,2825,2838,2840,2905,2906,2907,2915,2916,2917,2920,2921,2925,2938,2940,3006,3017,3018,3019,3020,3021,3022,3025,3039,3040,3041,3106,3117,3118,3121,3122,3125,3141,3205,3207,3219,3220,3221,3222,3223,3225,3226,3305,3306,3320,3323,3324,3325,3406,3423,3502,3503,3506,3517,3518,3520,3521,3523,3534,3535,3536,3604,3605,3606,3607,3619,3622,3623,3624,3635,3637,3703,3704,3705,3706,3707,3708,3709,3710,3711,3712,3713,3714,3719,3723,3735,3736,3739,3802,3803,3806,3813,3820,3824,3825,3836,3837,3840,3906,3920,3925,3937,3938,3939,4007,4021,4026,4027,4038,4039], brussels_couillet_road: [1018,1117,1217,1218,1317,1417,1516,1617,1716,1817,1917,2018,2117,2218,2317,2418,2517,2618,2717,2818,2917,3018,3116,3117,3216,3316,3416,3515,3616,3616,3715,3815,3915,4015], + forbidden: { + 1015: [ 1014, 1015, 1016, 1114, 1115 ], + 1018: [ 1017, 1018, 1019, 1117 ], + 1020: [ 1019, 1020, 1021, 1120 ], + 3000: [ 2900, 3000, 3001, 3100 ], + 4015: [ 4014, 4015, 4016, 3914, 3915 ], + }, names: { 1015: "Mortigny", 1018: "Couillet", @@ -213,5 +220,61 @@ data.pieces = [ { side: "Prussian", type: "det", stars: 0, parent: [21], name: "IV Detachment (Schwerin)" }, ] +data.reinforcements = [ + { + turn: 1, + side: "French", + hex: 1015, + list: [ + "II Corps (Reille)", + "I Corps (d'Erlon)", + ] + }, + { + turn: 1, + side: "French", + hex: 1018, + list: [ + "III Corps (Vandamme)", + "VI Corps (Lobau)", + "Guard Corps (Drouot)", + "Guard Cav Corps (Guyot)", + ] + }, + { + turn: 1, + side: "French", + hex: 1020, + list: [ + "Res Cav Corps (Grouchy)", + "IV Corps (Gerard)", + ] + }, + { + turn: 3, + side: "Coalition", + hex: 3000, + list: [ + "II Corps (Hill*)", + ], + }, + { + turn: 3, + side: "Coalition", + hex: 3241, + list: [ + "IV Corps (Bulow)", + ] + }, + { + turn: 4, + side: "Coalition", + hex: 4015, + list: [ + "Cav Corps (Uxbridge)", + ] + } +] + if (typeof module !== "undefined") module.exports = data @@ -135,12 +135,10 @@ main { .large.selected, .small.selected { box-shadow: 0 0 0 1px #444, 0 0 0 4px yellow; - z-index: 102; } .large.target, .small.target { box-shadow: 0 0 0 1px #444, 0 0 0 4px red; - z-index: 101; } .marker { border-color: hsl(199,65%,85%) hsl(199,55%,50%) hsl(199,55%,50%) hsl(199,65%,85%) } @@ -6,6 +6,22 @@ const piece_count = 39 const first_hex = 1000 const last_hex = 4041 +function find_piece(name) { + let id = data.pieces.findIndex(pc => pc.name === name) + if (id < 0) + throw new Error("PIECE NOT FOUND: " + name) + return id +} + +for (let info of data.reinforcements) + info.list = info.list.map(name => find_piece(name)) + +let yoff = 1555 +let xoff = 36 +let hex_dx = 58.67 +let hex_dy = 68 +let hex_r = 56 >> 1 + function set_has(set, item) { if (!set) return false @@ -31,6 +47,14 @@ const TURN_X = 20 - 70 + 35 + 8 const TURN_Y = 1745 const TURN_DX = 70 +const REINF_OFFSET = { + 1015: [ hex_dx/2, hex_dy * 3/4 ], + 1018: [ -hex_dx/2, hex_dy * 3/4 ], + 1020: [ -hex_dx/2, hex_dy * 3/4 ], + 3000: [ -hex_dx/2, 0 ], + 4015: [ 0, -hex_dy * 3/8 ], +} + let ui = { hexes: new Array(last_hex+1).fill(null), sides: new Array((last_hex+1)*3).fill(null), @@ -111,17 +135,11 @@ function toggle_pieces() { } function build_hexes() { - let yoff = 1555 - let xoff = 36 - let hex_dx = 58.67 - let hex_dy = 68 - let hex_r = 56 >> 1 - - for (let y = 0; y < data.map.rows; ++y) { - for (let x = 0; x < data.map.cols; ++x) { - let hex_id = first_hex + 100 * y + x - let hex_x = ui.hex_x[hex_id] = Math.floor(xoff + hex_dx * (x + (y & 1) * 0.5 + 0.5)) - let hex_y = ui.hex_y[hex_id] = Math.floor(yoff - hex_dy * 3 / 4 * y + hex_dy/2) + for (let row = 0; row < data.map.rows; ++row) { + for (let col = 0; col < data.map.cols; ++col) { + let hex_id = first_hex + 100 * row + col + let hex_x = ui.hex_x[hex_id] = Math.floor(xoff + hex_dx * (col + (row & 1) * 0.5 + 0.5)) + let hex_y = ui.hex_y[hex_id] = Math.floor(yoff - hex_dy * 3 / 4 * row + hex_dy/2) let hex = ui.hexes[hex_id] = document.createElement("div") hex.className = "hex" @@ -189,6 +207,27 @@ function find_hex_side(a, b) { return -1 } +function find_reinforcement_hex(who) { + for (let info of data.reinforcements) + for (let p of info.list) + if (p === who) + return info.hex + return 102 +} + +function find_reinforcement_z(who) { + for (let info of data.reinforcements) { + let n = 0 + for (let p of info.list) { + if (p === who) + return n + if ((view.pieces[p] >> 1) === 102) + ++n + } + } + return 0 +} + function on_update() { ui.stack.fill(0) @@ -202,13 +241,28 @@ function on_update() { for (let id = 0; id < piece_count; ++id) { let hex = view.pieces[id] >> 1 - if (hex >= first_hex) { + let z = 0 + let s = 0 + if (hex >= first_hex || hex === 102) { // ON MAP ui.pieces[id].classList.remove("hide") ui.pieces[id].classList.toggle("flip", (view.pieces[id] & 1) === 1) - let x = ui.hex_x[hex] - ui.stack[hex] * 18 - let y = ui.hex_y[hex] + ui.stack[hex] * 12 - ui.stack[hex] += 1 + let x, y + if (hex === 102) { + hex = find_reinforcement_hex(id) + s = find_reinforcement_z(id) + z = 4 - s + x = ui.hex_x[hex] + s * 24 + y = ui.hex_y[hex] + s * 18 + if (REINF_OFFSET[hex]) { + x += REINF_OFFSET[hex][0] + y += REINF_OFFSET[hex][1] + } + } else { + s = z = ui.stack[hex]++ + x = ui.hex_x[hex] - s * 18 + y = ui.hex_y[hex] + s * 12 + } if (id <= last_corps) { x -= (46>>1) y -= (46>>1) @@ -218,15 +272,17 @@ function on_update() { } ui.pieces[id].style.top = y + "px" ui.pieces[id].style.left = x + "px" - } else if (hex === 100 || hex === 101) { - // AVAILABLE DETACHMENTS + ui.pieces[id].style.zIndex = z + } else if (hex >= 100) { + // OFF MAP DETACHMENTS / LEADERS / REINFORCEMENTS ui.pieces[id].classList.remove("hide") ui.pieces[id].classList.toggle("flip", (view.pieces[id] & 1) === 1) - let x = 600 + 20 + ui.stack[hex] * 60 - let y = 1650 + 20 + 60 * (hex-100) + let x = 600 + 40 + ui.stack[hex] * 60 + let y = 1650 + 40 + 60 * (hex-100) ui.stack[hex] += 1 ui.pieces[id].style.top = y + "px" ui.pieces[id].style.left = x + "px" + ui.pieces[id].style.zIndex = 0 } else if (hex >= 1) { // ON TURN TRACK ui.pieces[id].classList.remove("hide") @@ -248,6 +304,12 @@ function on_update() { // ELIMINATED ui.pieces[id].classList.add("hide") } + //if (is_action("piece", id)) z = 101 + if (view.target === id) + z = 102 + if (view.who === id) + z = 103 + ui.pieces[id].style.zIndex = z ui.pieces[id].classList.toggle("action", is_action("piece", id)) ui.pieces[id].classList.toggle("selected", view.who === id) ui.pieces[id].classList.toggle("target", view.target === id) @@ -263,6 +325,7 @@ function on_update() { } } + action_button("roll", "Roll") action_button("next", "Next") action_button("done", "Done") action_button("pass", "Pass") @@ -23,23 +23,23 @@ var states = {} const ELIMINATED = 0 const AVAILABLE_P1 = 100 const AVAILABLE_P2 = 101 +const REINFORCEMENTS = 102 -const ENTRY_A = 4006 -const ENTRY_B = 4015 -const ENTRY_C = 4025 -const ENTRY_D1 = 1017 -const ENTRY_D2 = 1018 +function find_piece(name) { + let id = data.pieces.findIndex(pc => pc.name === name) + if (id < 0) + throw new Error("PIECE NOT FOUND: " + name) + return id +} -const OFFMAP_A = 4106 -const OFFMAP_B = 4115 -const OFFMAP_C = 4125 -const OFFMAP_D = 917 +for (let info of data.reinforcements) + info.list = info.list.map(name => find_piece(name)) -const NAPOLEON_HQ = data.pieces.findIndex(pc => pc.name === "Napoleon HQ") -const OLD_GUARD = data.pieces.findIndex(pc => pc.name === "Old Guard") -const GRAND_BATTERY = data.pieces.findIndex(pc => pc.name === "Grand Battery") -const HILL_1 = data.pieces.findIndex(pc => pc.name === "II Corps (Hill*)") -const HILL_2 = data.pieces.findIndex(pc => pc.name === "II Corps (Hill**)") +const NAPOLEON_HQ = find_piece("Napoleon HQ") +const OLD_GUARD = find_piece("Old Guard") +const GRAND_BATTERY = find_piece("Grand Battery") +const HILL_1 = find_piece("II Corps (Hill*)") +const HILL_2 = find_piece("II Corps (Hill**)") const brussels_couillet_road_x3 = [] for (let a of data.map.brussels_couillet_road) { @@ -936,6 +936,11 @@ function goto_movement_phase() { game.active = P1 game.state = "movement" game.remain = 0 + + for (let info of data.reinforcements) + if (info.turn === game.turn) + for (let p of info.list) + set_piece_hex(p, REINFORCEMENTS) } function next_movement() { @@ -964,6 +969,17 @@ states.movement = { if (piece_is_not_in_enemy_zoc(p)) gen_action_piece(p) + for (let info of data.reinforcements) { + if (info.turn === game.turn && info.side === game.active) { + for (let p of info.list) { + if (!piece_is_on_map(p)) { + gen_action_piece(p) + break + } + } + } + } + view.actions.pass = 1 }, piece(p) { @@ -1094,17 +1110,31 @@ function must_flip_zoc(here, next, is_cav) { const move_seen = new Array(last_hex - 999).fill(0) const move_cost = new Array(last_hex - 999).fill(0) +function find_reinforcement_hex(who) { + for (let info of data.reinforcements) + for (let p of info.list) + if (p === who) + return info.hex + return 0 +} + function search_move(p) { move_seen.fill(0) let x = piece_hex(p) let m = piece_movement_allowance(p) + let u = 0 + if (x === REINFORCEMENTS) { + x = find_reinforcement_hex(p) + u = 1 + move_seen[x-1000] = 3 + } for (let hq of data.pieces[p].hq) { let hq_hex = piece_hex(hq) if (is_map_hex(hq_hex)) { - search_move_offroad(x, m, hq_hex, piece_command_range(hq), piece_is_cavalry(p)) + search_move_offroad(x, m - u, hq_hex, piece_command_range(hq), piece_is_cavalry(p)) if (!(piece_is_infantry(game.who) && piece_mode(game.who))) if (is_road_hex(x)) - search_move_road(x, m * 2, hq_hex, piece_command_range(hq), piece_is_cavalry(p)) + search_move_road(x, m * 2 - u, hq_hex, piece_command_range(hq), piece_is_cavalry(p)) } } } @@ -1189,7 +1219,7 @@ function search_move_road_segment(queue, road, cur, dir, hq_hex, hq_range, is_ca let here = road[cur] let mp = move_cost[here-1000] cur += dir - while (mp > 0 && cur >= 0 && cur < road.length) { + while (mp >= 0 && cur >= 0 && cur < road.length) { let next = road[cur] if (!can_move_into(here, next, hq_hex, hq_range, is_cav)) break @@ -1330,9 +1360,7 @@ function roll_attack() { // === SETUP === function setup_piece(side, name, hex, mode = 0) { - let id = data.pieces.findIndex(pc => pc.side === side && pc.name === name) - if (id < 0) - throw new Error("INVALID PIECE NAME: " + name) + let id = find_piece(name) set_piece_hex(id, hex) set_piece_mode(id, mode) } |