summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-08-12 10:00:15 +0200
committerTor Andersson <tor@ccxvii.net>2023-10-01 16:11:21 +0200
commit644d5b7e72e7ac00b5a95951b5430b519e93f146 (patch)
treed6461e70cc21faf614a5ba5cd027ff7e46881642
parent22be520ec9a59b5ea5f7b9bd2c82181bdc0d7e3d (diff)
downloadwaterloo-campaign-1815-644d5b7e72e7ac00b5a95951b5430b519e93f146.tar.gz
Stuff.
-rw-r--r--about.html4
-rw-r--r--data.js6
-rw-r--r--play.html69
-rw-r--r--play.js24
-rw-r--r--rules.js630
5 files changed, 612 insertions, 121 deletions
diff --git a/about.html b/about.html
index d6c2d3d..1417df9 100644
--- a/about.html
+++ b/about.html
@@ -14,6 +14,6 @@ Designer: Mark Herman.
Copyright &copy; 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>
diff --git a/data.js b/data.js
index fab4aa3..e02c440 100644
--- a/data.js
+++ b/data.js
@@ -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)" },
diff --git a/play.html b/play.html
index 14a1c24..ecf6c9b 100644
--- a/play.html
+++ b/play.html
@@ -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>
diff --git a/play.js b/play.js
index c269c8e..cd27c84 100644
--- a/play.js
+++ b/play.js
@@ -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")
}
diff --git a/rules.js b/rules.js
index 92ea536..d7e2bd6 100644
--- a/rules.js
+++ b/rules.js
@@ -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
+ }
+ }
+}