diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-08-12 10:00:15 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2023-10-01 16:11:21 +0200 |
commit | 644d5b7e72e7ac00b5a95951b5430b519e93f146 (patch) | |
tree | d6461e70cc21faf614a5ba5cd027ff7e46881642 | |
parent | 22be520ec9a59b5ea5f7b9bd2c82181bdc0d7e3d (diff) | |
download | waterloo-campaign-1815-644d5b7e72e7ac00b5a95951b5430b519e93f146.tar.gz |
Stuff.
-rw-r--r-- | about.html | 4 | ||||
-rw-r--r-- | data.js | 6 | ||||
-rw-r--r-- | play.html | 69 | ||||
-rw-r--r-- | play.js | 24 | ||||
-rw-r--r-- | rules.js | 630 |
5 files changed, 612 insertions, 121 deletions
@@ -14,6 +14,6 @@ Designer: Mark Herman. Copyright © 2019 Rodger B. MacGowan and Studiolo Designs. <ul> -<li><a href="/waterloo-campaign/info/rules.html">Rules</a> -<li><a href="/waterloo-campaign/info/charts.html">Charts</a> +<li><a href="/waterloo-campaign-1815/info/rules.html">Rules</a> +<li><a href="/waterloo-campaign-1815/info/charts.html">Charts</a> </ul> @@ -171,7 +171,7 @@ data.pieces = [ // 13 { side: "Anglo", type: "inf", stars: 2, mp1: 4, mp2: 1, name: "I Corps (Orange)" }, { side: "Anglo", type: "inf", stars: 2, mp1: 4, mp2: 1, name: "Reserve Corps (Wellington)" }, - { side: "Anglo", type: "inf", stars: 2, mp1: 4, mp2: 1, name: "II Corps (Hill)" }, + { side: "Anglo", type: "inf", stars: 2, mp1: 4, mp2: 1, name: "II Corps (Hill**)" }, { side: "Anglo", type: "cav", stars: 1, mp1: 6, mp2: 4, name: "Cav Corps (Uxbridge)" }, { side: "Anglo", type: "inf", stars: 1, mp1: 4, mp2: 1, name: "II Corps (Hill*)" }, @@ -183,7 +183,7 @@ data.pieces = [ { side: "Prussian", type: "cav", stars: 0, mp1: 6, mp2: 4, name: "Cav Corps (Gneisenau)" }, // 23 - { side: "French", type: "det", stars: 0, parent: 5, name: "I Detachment (Jaquinot)" }, + { side: "French", type: "det", stars: 0, parent: 5, name: "I Detachment (Jacquinot)" }, { side: "French", type: "det", stars: 0, parent: 6, name: "II Detachment (Pire)" }, { side: "French", type: "det", stars: 2, parent: 0, name: "Old Guard" }, { side: "French", type: "det", stars: 0, parent: 10, name: "Res Cav Detachment (Pajol)" }, @@ -192,7 +192,7 @@ data.pieces = [ { side: "Anglo", type: "det", stars: 0, parent: 13, name: "I Detachment (Perponcher)" }, { side: "Anglo", type: "det", stars: 0, parent: 14, name: "Res Detachment (KGL)" }, - { side: "Anglo", type: "det", stars: 0, parent: 15, name: "II Detachment (Frederick)" }, // Hill * or ** ? + { side: "Anglo", type: "det", stars: 0, parent: [15,17], name: "II Detachment (Frederick)" }, // Hill * or ** ? { side: "Anglo", type: "det", stars: 0, parent: 16, name: "Cav Detachment (Collaert)" }, { side: "Prussian", type: "det", stars: 0, parent: 18, name: "I Detachment (Steinmetz)" }, @@ -21,16 +21,39 @@ main { #mapwrap { margin: 0 auto; width: 2550px; - height: 1650px; - box-shadow: 0 0 8px #0008; + height: 1900px; } #map { + position: absolute; + width: 2550px; + height: 1900px; +} + +#board, #tracks, #hexes, #pieces { + position: absolute; +} + +#board { + top: 0px; + left: 0px; width: 2550px; height: 1650px; - background-color: white; + background-color: #803a3b; background-image: url(map75.png); background-size: 2550px 1650px; + box-shadow: 0 0 8px #0008; +} + +#tracks { + top: 1650px; + left: 0px; + width: 600px; + height: 250px; + background-color: #d0c5b1; + background-size: 600px 250px; + background-image: url(tracks75.png); + box-shadow: 0 0 8px #0008; } #hexes, #pieces { position: absolute } @@ -87,7 +110,6 @@ main { background-size: 84px auto; width: 42px; height: 42px; - border-radius: 6px; } .small { @@ -95,7 +117,6 @@ main { background-size: 68px auto; width: 34px; height: 34px; - border-radius: 3px; } .large, .small { @@ -104,24 +125,32 @@ main { box-shadow: 0 0 0 1px #444, 0 0 4px #0008; } +.marker { border-color: hsl(199,65%,85%) hsl(199,55%,50%) hsl(199,55%,50%) hsl(199,65%,85%) } .large.french, .small.french { border-color: hsl(199,85%,90%) hsl(199,75%,70%) hsl(199,75%,70%) hsl(199,85%,90%) } .large.anglo, .small.anglo { border-color: hsl(0,0%,90%) hsl(0,0%,70%) hsl(0,0%,70%) hsl(0,0%,90%) } .large.prussian, .small.prussian { border-color: hsl(202,10%,70%) hsl(202,10%,50%) hsl(202,10%,50%) hsl(202,10%,70%) } -.large.french { background-image: url(sheet_french1_75.png) } -.large.anglo { background-image: url(sheet_anglo1_75.png) } -.large.prussian { background-image: url(sheet_prussian1_75.png) } -.small.french { background-image: url(sheet_french2_75.png) } -.small.anglo { background-image: url(sheet_anglo2_75.png) } -.small.prussian { background-image: url(sheet_prussian2_75.png) } +.marker.turn { background-image: url(images.2x/game_turn.png) } +.marker.turn.rain { background-image: url(images.2x/rain.png) } +.marker.remain { background-image: url(images.2x/move_attacks_remaining.png) } +.marker.remain.p10 { background-image: url(images.2x/move_attacks_remaining_10.png) } + +.large.french { background-image: url(images.1x/sheet_french1.png) } +.large.anglo { background-image: url(images.1x/sheet_anglo1.png) } +.large.prussian { background-image: url(images.1x/sheet_prussian1.png) } +.large.marker { background-image: url(images.1x/sheet_misc.png) } +.small.french { background-image: url(images.1x/sheet_french2.png) } +.small.anglo { background-image: url(images.1x/sheet_anglo2.png) } +.small.prussian { background-image: url(images.1x/sheet_prussian2.png) } @media (min-resolution:97dpi) { -.large.french { background-image: url(sheet_french1_150.png) } -.large.anglo { background-image: url(sheet_anglo1_150.png) } -.large.prussian { background-image: url(sheet_prussian1_150.png) } -.small.french { background-image: url(sheet_french2_150.png) } -.small.anglo { background-image: url(sheet_anglo2_150.png) } -.small.prussian { background-image: url(sheet_prussian2_150.png) } +.large.french { background-image: url(images.2x/sheet_french1.png) } +.large.anglo { background-image: url(images.2x/sheet_anglo1.png) } +.large.prussian { background-image: url(images.2x/sheet_prussian1.png) } +.large.marker { background-image: url(images.2x/sheet_misc.png) } +.small.french { background-image: url(images.2x/sheet_french2.png) } +.small.anglo { background-image: url(images.2x/sheet_anglo2.png) } +.small.prussian { background-image: url(images.2x/sheet_prussian2.png) } } .large.y1 { background-position: -0px -0px } @@ -209,11 +238,17 @@ main { </aside> <main> + <div id="mapwrap"> <div id="map"> +<div id="board"></div> +<div id="tracks"></div> <div id="hexes"></div> <div id="pieces"> +<div id="marker_turn" class="marker large y1" style="top:1660px;left:25px"></div> +<div id="marker_remain" class="marker large y3" style="top:1837px;left:109px"></div> + <div id="french_hq_1" class="french large y1"></div> <div id="french_hq_2" class="french large y2"></div> <div id="french_hq_3" class="french large y3"></div> @@ -27,6 +27,10 @@ function set_has(set, item) { const FRENCH = "French" const COALITION = "Coalition" +const TURN_X = 20 - 70 + 35 + 8 +const TURN_Y = 1745 +const TURN_DX = 70 + let ui = { hexes: new Array(last_hex+1).fill(null), sides: new Array((last_hex+1)*3).fill(null), @@ -176,10 +180,10 @@ function on_update() { } for (let id = 0; id < piece_count; ++id) { - let hex = view.pieces[id] + let hex = view.pieces[id] >> 1 if (hex >= first_hex) { ui.pieces[id].classList.remove("hide") - ui.pieces[id].classList.toggle("flip", !!view.mode[id]) + 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 @@ -192,6 +196,21 @@ function on_update() { } ui.pieces[id].style.top = y + "px" ui.pieces[id].style.left = x + "px" + } else if (hex >= 1) { + ui.pieces[id].classList.remove("hide") + ui.pieces[id].classList.remove("flip") + let x = TURN_X + hex * TURN_DX - ui.stack[hex] * 18 + let y = TURN_Y + ui.stack[hex] * 12 + ui.stack[hex] += 1 + if (id <= last_corps) { + x -= (46>>1) + y -= (46>>1) + } else { + x -= (38>>1) + y -= (38>>1) + } + ui.pieces[id].style.top = y + "px" + ui.pieces[id].style.left = x + "px" } else { ui.pieces[id].classList.add("hide") } @@ -212,6 +231,7 @@ function on_update() { action_button("edit_stream", "Stream") action_button("edit_road", "Road") + action_button("next", "Next") action_button("pass", "Pass") action_button("undo", "Undo") } @@ -8,7 +8,7 @@ const P2 = COALITION exports.roles = [ P1, P2 ] -exports.scenarios = [ "June 16-18", "June 15-18" ] +exports.scenarios = [ "June 16", "June 15", "June 15 (no special rules)" ] const data = require("./data") @@ -16,12 +16,10 @@ var game = null var view = null var states = {} -const OPEN = 0 -const TOWN = 1 -const STREAM = 2 - -const OLD_GUARD = 25 -const GRAND_BATTERY = 28 +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**)") function make_piece_list(f) { let list = [] @@ -58,45 +56,77 @@ function friendly_units() { return (game.active === P1) ? p1_units : p2_units } function enemy_units() { return (game.active !== P1) ? p1_units : p2_units } function set_piece_hex(p, hex) { - game.hex[p] = hex + game.pieces[p] &= 1 + game.pieces[p] |= hex << 1 } function set_piece_mode(p, mode) { - game.mode[p] = mode + game.pieces[p] &= ~1 + game.pieces[p] |= mode } function piece_hex(p) { - return game.hex[p] + return game.pieces[p] >> 1 } function piece_mode(p) { - return game.mode[p] + return game.pieces[p] & 1 +} + +const data_rivers = [] +const data_bridges = [] + +for (let [a, b] of data.map.rivers) { + set_add(data_rivers, a * 10000 + b) + set_add(data_rivers, b * 10000 + a) +} + +for (let [a, b] of data.map.bridges) { + set_delete(data_rivers, a * 10000 + b) + set_delete(data_rivers, b * 10000 + a) + set_add(data_bridges, a * 10000 + b) + set_add(data_bridges, b * 10000 + a) +} + +function is_river(a, b) { + return set_has(data_rivers, a * 10000 + b) +} + +function is_bridge(a, b) { + return set_has(data_bridges, a * 10000 + b) } // === ZONE OF CONTROL / INFLUENCE === var zoc_valid = false -var p1_zoc = new Array(data.map.rows * 100).fill(0) -var p1_zoi = new Array(data.map.rows * 100).fill(0) -var p2_zoc = new Array(data.map.rows * 100).fill(0) -var p2_zoi = new Array(data.map.rows * 100).fill(0) +var zoc_cache = new Array(data.map.rows * 100).fill(0) -function is_friendly_zoc(x) { return game.active === P1 ? p1_zoc[x] : p2_zoc[x] } -function is_friendly_zoi(x) { return game.active === P1 ? p1_zoi[x] : p2_zoi[x] } -function is_enemy_zoc(x) { return game.active !== P1 ? p1_zoc[x] : p2_zoc[x] } -function is_enemy_zoi(x) { return game.active !== P1 ? p1_zoi[x] : p2_zoi[x] } +function is_friendly_zoc(x) { return game.active === P1 ? zoc_cache[x] & 1 : zoc_cache[x] & 4 } +function is_friendly_zoi(x) { return game.active === P1 ? zoc_cache[x] & 2 : zoc_cache[x] & 8 } +function is_friendly_zoc_zoi(x) { return game.active === P1 ? zoc_cache[x] & 3 : zoc_cache[x] & 12 } +function is_enemy_zoc(x) { return game.active !== P1 ? zoc_cache[x] & 1 : zoc_cache[x] & 4 } +function is_enemy_zoi(x) { return game.active !== P1 ? zoc_cache[x] & 2 : zoc_cache[x] & 8 } +function is_enemy_zoc_zoi(x) { return game.active !== P1 ? zoc_cache[x] & 3 : zoc_cache[x] & 12 } function update_zoc_imp(zoc, zoi, units) { - zoc.fill(0) - zoi.fill(0) + zoc_cache.fill(0) for (let p of units) { - for_each_adjacent(piece_hex(p), x => { - // TODO: river - zoc[x - 1000] = 1 - for_each_adjacent(x, y => { - // TODO: bridge - zoi[y - 1000] = 1 - }) + let a = piece_hex(p) + let aa = a - 1000 + if (zoc_cache[aa] & zoc) + continue + zoc_cache[aa] = zoc | zoi + for_each_adjacent(a, b => { + let bb = b - 1000 + if (!(zoc_cache[bb] & zoc) && !is_river(a, b)) { + zoc_cache[bb] |= zoc + for_each_adjacent(b, c => { + let cc = c - 1000 + if (!is_bridge(b, c)) { + zoc_cache[cc] |= zoi + } + }) + } }) } } @@ -104,14 +134,24 @@ function update_zoc_imp(zoc, zoi, units) { function update_zoc() { if (!zoc_valid) { zoc_valid = true - update_zoc_imp(p1_zoc, p1_zoi, p1_units) - update_zoc_imp(p2_zoc, p2_zoi, p2_units) + update_zoc_imp(1, 2, p1_units) + update_zoc_imp(4, 8, p2_units) } } -function is_not_in_enemy_zoc_or_zoi(p) { +function piece_is_not_in_enemy_zoc_or_zoi(p) { + let x = piece_hex(p) + return is_map_hex(x) && !is_enemy_zoc_zoi(x) +} + +function piece_is_not_in_enemy_zoc(p) { + let x = piece_hex(p) + return is_map_hex(x) && !is_enemy_zoc(x) +} + +function piece_is_in_enemy_zoc(p) { let x = piece_hex(p) - return !is_enemy_zoc(x) && !is_enemy_zoi(x) + return is_map_hex(x) && is_enemy_zoc(x) } function is_map_hex(row, col) { @@ -162,10 +202,45 @@ function for_each_adjacent(hex, fn) { } } +function set_next_player() { + game.active = (game.active === P1) ? P2 : P1 +} + function prompt(str) { view.prompt = str } +// === SEQUENCE OF PLAY === + +/* + +command phase: + remove hq + place hq + return up to 2 blown corps + flip exhausted cav to fresh (move to organization?) + place 1 detachment per hq + recall all, some, or no detachments + angst: substitute Hill unit + +organization + advance formation: flip infantry corps to advance + battle formation: flip infantry corps to battle + alternate withdrawal: retreat or pass (3 remain) + +movement + alternate corps movement: move corps or pass + +attack + alternate corps to attack in zoc or pass + +end phase + if last turn - victory + recall french grand battery + new turn + +*/ + // === === COMMAND PHASE === === function goto_command_phase() { @@ -174,11 +249,32 @@ function goto_command_phase() { goto_hq_placement_step() } +// === A: HQ PLACEMENT STEP === + function goto_hq_placement_step() { game.active = P1 game.state = "hq_placement_step" } +function end_hq_placement_step() { + if (game.active === P1) + game.active = P2 + else + goto_blown_unit_return_step() +} + +states.hq_placement_step = { + prompt() { + prompt("HQ Placement Step.") + view.actions.next = 1 + }, + next() { + end_hq_placement_step() + }, +} + +// === B: BLOWN UNIT RETURN STEP === + function goto_blown_unit_return_step() { game.active = P1 game.state = "blown_unit_return_step" @@ -194,6 +290,20 @@ function end_blown_unit_return_step() { } } +states.blown_unit_return_step = { + prompt() { + prompt("Blown Unit Return Step.") + view.actions.next = 1 + }, + next() { + end_blown_unit_return_step() + }, +} + +// === C: CAVALRY CORPS RECOVERY STEP === + +// TODO: merge with steps F and G to save time + function goto_cavalry_corps_recovery_step() { game.active = P1 game.state = "cavalry_corps_recovery_step" @@ -203,7 +313,7 @@ function goto_cavalry_corps_recovery_step() { function resume_cavalry_corps_recovery_step() { update_zoc() for (let p of friendly_cavalry_corps()) - if (is_not_in_enemy_zoc_or_zoi(p)) + if (piece_mode(p) && piece_is_not_in_enemy_zoc_or_zoi(p)) return end_cavalry_corps_recovery_step() } @@ -217,6 +327,21 @@ function end_cavalry_corps_recovery_step() { } } +states.cavalry_corps_recovery_step = { + prompt() { + prompt("Cavalry Corps Recovery Step.") + for (let p of friendly_cavalry_corps()) + if (piece_mode(p) && piece_is_not_in_enemy_zoc_or_zoi(p)) + gen_action_piece(p) + }, + piece(p) { + set_piece_mode(p, 0) + resume_cavalry_corps_recovery_step() + }, +} + +// === D: DETACHMENT PLACEMENT STEP === + function goto_detachment_placement_step() { game.active = P1 game.state = "detachment_placement_step" @@ -232,6 +357,18 @@ function end_detachment_placement_step() { } } +states.detachment_placement_step = { + prompt() { + prompt("Detachment Placement Step.") + view.actions.next = 1 + }, + next() { + end_detachment_placement_step() + }, +} + +// === E: DETACHMENT RECALL STEP === + function goto_detachment_recall_step() { game.active = P1 game.state = "detachment_recall_step" @@ -245,60 +382,231 @@ function end_detachment_recall_step() { } } +states.detachment_recall_step = { + prompt() { + prompt("Detachment Recall Step.") + view.actions.next = 1 + }, + next() { + end_detachment_recall_step() + }, +} + function goto_british_line_of_communication_angst() { game.active = P2 - game.state = "british_line_of_communication_angst" + game.state = "british_line_of_communication_angst_1" + // TODO + goto_advance_formation() } -/* +states.british_line_of_communication_angst_1 = { + prompt() { + prompt("British Line of Communication Angst.") + gen_action_piece(HILL_1) + }, + piece(p) { + set_piece_hex(HILL_2, piece_hex(HILL_1)) + set_piece_mode(HILL_2, piece_mode(HILL_1)) + set_piece_hex(HILL_1, 0) + set_piece_mode(HILL_1, 0) + goto_advance_formation() + }, +} -command phase: +states.british_line_of_communication_angst_2 = { + prompt() { + prompt("British Line of Communication Angst.") + gen_action_piece(HILL_2) + }, + piece(p) { + set_piece_hex(HILL_1, piece_hex(HILL_2)) + set_piece_mode(HILL_1, piece_mode(HILL_2)) + set_piece_hex(HILL_2, 0) + set_piece_mode(HILL_2, 0) + goto_advance_formation() + }, +} - remove hq - place hq - return up to 2 blown corps - flip exhausted cav to fresh (move to organization?) - place 1 detachment per hq - recall all, some, or no detachments - angst: substitute Hill unit +// === === ORGANIZATION PHASE === === -organization - advance formation: flip infantry corps to advance - battle formation: flip infantry corps to battle - alternate withdrawal: retreat or pass (3 remain) +// === F: ADVANCE FORMATION === +// === G: BATTLE FORMATION === -movement - alternate corps movement: move corps or pass +// NOTE: merged step F and step G to save time +// TODO: move step C here -attack - alternate corps to attack in zoc or pass +function goto_advance_formation() { + game.active = P1 + resume_advance_formation() +} -end phase - if last turn - victory - recall french grand battery - new turn +function resume_advance_formation() { + game.state = "advance_formation" + update_zoc() + for (let p of friendly_infantry_corps()) + if (piece_mode(p) && piece_is_not_in_enemy_zoc(p)) + return + resume_battle_formation() +} -*/ +function resume_battle_formation() { + game.state = "battle_formation" + update_zoc() + for (let p of friendly_infantry_corps()) + if (piece_mode(p) && piece_is_in_enemy_zoc(p)) + return + end_battle_formation() +} + +function end_battle_formation() { + if (game.active === P1) { + game.active = P2 + resume_advance_formation() + } else { + goto_withdrawal() + } +} +states.advance_formation = { + prompt() { + prompt("Advance Formation.") + for (let p of friendly_infantry_corps()) + if (piece_mode(p) && piece_is_not_in_enemy_zoc(p)) + gen_action_piece(p) + }, + piece(p) { + set_piece_mode(p, 0) + resume_advance_formation() + }, +} -// === A: HQ PLACEMENT STEP === +states.battle_formation = { + prompt() { + prompt("Battle Formation.") + for (let p of friendly_infantry_corps()) + if (piece_mode(p) && piece_is_in_enemy_zoc(p)) + gen_action_piece(p) + }, + piece(p) { + set_piece_mode(p, 0) + resume_battle_formation() + }, +} +// === H: WITHDRAWAL === -states.hq_placement_step = { +function goto_withdrawal() { + game.active = P1 + game.state = "withdrawal" + game.remain = 0 +} + +function next_withdrawal() { + game.state = "withdrawal" + if (game.remain === 0) + set_next_player() + else if (--game.remain === 0) + end_withdrawal() +} + +function end_withdrawal() { + goto_movement_phase() +} + +states.withdrawal = { prompt() { - prompt("HQ Placement") + prompt("Withdrawal.") + view.actions.pass = 1 + }, + piece(p) { + push_undo() + game.who = p + game.state = "withdrawal_to" + }, + pass() { + clear_undo() + if (game.remain > 0) { + end_withdrawal() + } else { + set_next_player() + game.remain = 3 + } }, } -states.setup = { +states.withdrawal_to = { prompt() { + prompt("Withdrawal to.") + view.actions.next = 1 + }, + next() { + next_withdrawal() }, } -states.edit_town = { +// === === MOVEMENT PHASE === === + +function goto_movement_phase() { + log("") + log("Movement Phase") + log("") + game.active = P1 + game.state = "movement" + game.remain = 0 +} + +function next_movement() { + game.state = "movement" + if (game.remain === 0) + set_next_player() + else if (--game.remain === 0) + end_movement() +} + +function end_movement() { + goto_attack_phase() +} + +states.movement = { + prompt() { + prompt("Movement.") + view.actions.pass = 1 + }, + piece(p) { + push_undo() + game.who = p + game.state = "movement_to" + }, + pass() { + clear_undo() + if (game.remain > 0) { + end_movement() + } else { + set_next_player() + game.remain = roll_die() + } + }, +} + +states.movement_to = { prompt() { - view.roads = data.map.roads + prompt("Movement to.") + view.actions.next = 1 }, + next() { + next_movement() + }, +} + +// === === ATTACK PHASE === === + +function goto_attack_phase() { + log("") + log("Attack Phase") + log("") + game.active = P1 + game.state = "attack" + game.remain = 0 } // === SETUP === @@ -311,48 +619,91 @@ function setup_piece(side, name, hex, mode = 0) { set_piece_mode(id, mode) } +function setup_june_15() { + game.turn = 1 + + setup_piece("French", "Napoleon HQ", 1017) + setup_piece("French", "II Corps (Reille)", 1) + setup_piece("French", "I Corps (d'Erlon)", 1) + setup_piece("French", "III Corps (Vandamme)", 1) + setup_piece("French", "VI Corps (Lobau)", 1) + setup_piece("French", "Guard Corps (Drouot)", 1) + setup_piece("French", "Guard Cav Corps (Guyot)", 1) + setup_piece("French", "Res Cav Corps (Grouchy)", 1) + setup_piece("French", "IV Corps (Gerard)", 1) + setup_piece("French", "Grouchy HQ", 2) + setup_piece("French", "Ney HQ", 2) + + setup_piece("Anglo", "Wellington HQ", 3715) + setup_piece("Anglo", "Reserve Corps (Wellington)", 3715) + setup_piece("Anglo", "I Corps (Orange)", 3002) + setup_piece("Anglo", "II Corps (Hill*)", 3) + setup_piece("Anglo", "Cav Corps (Uxbridge)", 4) + setup_piece("Anglo", "Cav Detachment (Collaert)", 1211) + setup_piece("Anglo", "I Detachment (Perponcher)", 2618) + + setup_piece("Prussian", "Blucher HQ", 1737) + setup_piece("Prussian", "Cav Corps (Gneisenau)", 1737) + setup_piece("Prussian", "I Corps (Ziethen)", 1716) + setup_piece("Prussian", "II Corps (Pirch)", 2840) + setup_piece("Prussian", "III Corps (Thielmann)", 1340) + setup_piece("Prussian", "IV Corps (Bulow)", 3) + setup_piece("Prussian", "I Detachment (Steinmetz)", 1215) + setup_piece("Prussian", "I Detachment (Pirch)", 1217) + setup_piece("Prussian", "I Detachment (Lutzow)", 1221) +} + +function setup_june_16() { + game.turn = 3 + + setup_piece("French", "Napoleon HQ", 1217) + setup_piece("French", "Guard Corps (Drouot)", 1217) + setup_piece("French", "Grouchy HQ", 1621) + setup_piece("French", "Ney HQ", 2218) + setup_piece("French", "II Corps (Reille)", 2218) + setup_piece("French", "I Corps (d'Erlon)", 1617) + setup_piece("French", "III Corps (Vandamme)", 1721) + setup_piece("French", "IV Corps (Gerard)", 1221) + setup_piece("French", "VI Corps (Lobau)", 1117) + setup_piece("French", "Guard Cav Corps (Guyot)", 2317) + setup_piece("French", "Res Cav Corps (Grouchy)", 1822) + setup_piece("French", "I Detachment (Jacquinot)", 1314) + + setup_piece("Anglo", "Wellington HQ", 2818, 1) + setup_piece("Anglo", "Reserve Corps (Wellington)", 3715) + setup_piece("Anglo", "I Corps (Orange)", 3002) + setup_piece("Anglo", "II Corps (Hill*)", 3) + setup_piece("Anglo", "Cav Corps (Uxbridge)", 4) + setup_piece("Anglo", "Cav Detachment (Collaert)", 1211) + setup_piece("Anglo", "I Detachment (Perponcher)", 2618) + + setup_piece("Prussian", "Blucher HQ", 2324, 1) + setup_piece("Prussian", "Cav Corps (Gneisenau)", 2324) + setup_piece("Prussian", "I Corps (Ziethen)", 1922, 1) + setup_piece("Prussian", "II Corps (Pirch)", 1928) + setup_piece("Prussian", "III Corps (Thielmann)", 1737) + setup_piece("Prussian", "IV Corps (Bulow)", 3) + setup_piece("Prussian", "I Detachment (Lutzow)", 1623) +} + exports.setup = function (seed, scenario, options) { game = { seed, - scenario, - undo: [], log: [], + undo: [], active: P1, state: null, turn: 3, - pieces: new Array(piece_count).fill(0), - mode: new Array(piece_count).fill(0), remain: 0, + pieces: new Array(data.pieces.length).fill(0), + who: -1, + count: 0, } - setup("French", "Napoleon HQ", 1217) - setup("French", "Guard Corps (Drouot)", 1217) - setup("French", "Grouchy HQ", 1621) - setup("French", "Ney HQ", 2218) - setup("French", "II Corps (Reille)", 2218) - setup("French", "I Corps (d'Erlon)", 1617) - setup("French", "III Corps (Vandamme)", 1721) - setup("French", "IV Corps (Gerard)", 1221) - setup("French", "VI Corps (Lobau)", 1117) - setup("French", "Guard Cav Corps (Guyot)", 2317) - setup("French", "Res Cav Corps (Grouchy)", 1822) - setup("French", "I Detachment (Jacquinot)", 1314) - - setup("Anglo", "Wellington HQ", 2818, 1) - setup("Anglo", "Reserve Corps (Wellington)", 3715) - setup("Anglo", "I Corps (Orange)", 3002) - setup("Anglo", "II Corps (Hill*)", 3) - setup("Anglo", "Cav Corps (Uxbridge)", 4) - setup("Anglo", "Cav Detachment (Collaert)", 1211) - setup("Anglo", "I Detachment (Perponcher)", 2618) - - setup("Prussian", "Blucher HQ", 2324) - setup("Prussian", "Cav Corps (Gneisenau)", 2324, 1) - setup("Prussian", "I Corps (Ziethen)", 1922, 1) - setup("Prussian", "II Corps (Pirch)", 1928) - setup("Prussian", "III Corps (Thielmann)", 1737) - setup("Prussian", "IV Corps (Bulow)", 3) - setup("Prussian", "I Detachment (Lutzow)", 1623) + if (scenario === "June 15" || scenario === "June 15 (no special rules)") + setup_june_15() + else + setup_june_16() goto_command_phase() @@ -361,13 +712,28 @@ exports.setup = function (seed, scenario, options) { // === COMMON === +function gen_action(action, argument) { + if (!(action in view.actions)) + view.actions[action] = [] + view.actions[action].push(argument) +} + +function gen_action_piece(piece) { + gen_action("piece", piece) +} + +function gen_action_hex(hex) { + gen_action("hex", hex) +} + exports.view = function (state, player) { + game = state + view = { prompt: null, actions: null, log: game.log, - hex: game.hex, - mode: game.mode, + pieces: game.pieces, } if (game.state === "game_over") { @@ -379,7 +745,7 @@ exports.view = function (state, player) { view.actions = {} view.who = game.who if (states[game.state]) - states[game.state].prompt(current) + states[game.state].prompt() else view.prompt = "Unknown state: " + game.state if (view.actions.undo === undefined) { @@ -457,6 +823,10 @@ function random(range) { return (game.seed = game.seed * 200105 % 34359738337) % range } +function roll_die() { + return random(6) + 1 +} + // Fast deep copy for objects without cycles function object_copy(original) { if (Array.isArray(original)) { @@ -482,3 +852,69 @@ function object_copy(original) { return copy } } + +// Array remove and insert (faster than splice) + +function array_remove(array, index) { + let n = array.length + for (let i = index + 1; i < n; ++i) + array[i - 1] = array[i] + array.length = n - 1 +} + +function array_insert(array, index, item) { + for (let i = array.length; i > index; --i) + array[i] = array[i - 1] + array[index] = item +} + +// Set as plain sorted array + +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 set_add(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 + } + array_insert(set, a, item) +} + +function set_delete(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 { + array_remove(set, m) + return + } + } +} |