summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.html1
-rw-r--r--play.js10
-rw-r--r--rules.js366
3 files changed, 194 insertions, 183 deletions
diff --git a/play.html b/play.html
index e5f6228..350979e 100644
--- a/play.html
+++ b/play.html
@@ -241,7 +241,6 @@ main {
<div class="role_user">-</div>
</div>
</div>
- <div id="turn_info">June 15</div>
</div>
<div id="log"></div>
</aside>
diff --git a/play.js b/play.js
index 55db288..39d9893 100644
--- a/play.js
+++ b/play.js
@@ -108,6 +108,8 @@ let ui = {
document.getElementById("prussian_detachment_6"),
],
stack: new Array(last_hex+1).fill(0),
+ turn: document.getElementById("marker_turn"),
+ remain: document.getElementById("marker_remain"),
}
function toggle_pieces() {
@@ -333,9 +335,15 @@ function on_update() {
}
}
+ ui.turn.style.left = (40 + TURN_X + (view.turn-1) * TURN_DX) + "px"
+ ui.turn.classList.toggle("flip", view.rain > 0)
+
+ ui.remain.style.left = (109 + (view.remain % 10) * 47.5 | 0) + "px"
+ ui.remain.classList.toggle("flip", view.remain > 9)
+
action_button("roll", "Roll")
action_button("next", "Next")
- action_button("done", "Done")
+ action_button("end_step", "End step")
action_button("pass", "Pass")
action_button("undo", "Undo")
}
diff --git a/rules.js b/rules.js
index 928b086..be13692 100644
--- a/rules.js
+++ b/rules.js
@@ -1,7 +1,7 @@
"use strict"
// TODO: recall grand battery at end of turn
-// TODO: recall grand battery if alon
+// TODO: recall grand battery if alone
const FRENCH = "French"
const COALITION = "Coalition"
@@ -45,20 +45,70 @@ 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) {
- set_add(brussels_couillet_road_x3, a)
- for_each_adjacent(a, (b) => {
- set_add(brussels_couillet_road_x3, b)
- for_each_adjacent(b, (c) => {
- set_add(brussels_couillet_road_x3, c)
- for_each_adjacent(c, (d) => {
- set_add(brussels_couillet_road_x3, d)
- })
- })
- })
+function is_map_hex(x) {
+ if (x >= 1000 && x <= 4041)
+ return x % 100 <= 41
+ return false
+}
+
+function calc_distance(a, b) {
+ let ac = a % 100
+ let bc = b % 100
+ let ay = a / 100 | 0
+ let by = b / 100 | 0
+ let ax = ac - (ay >> 1)
+ let bx = bc - (by >> 1)
+ let az = -ax - ay
+ let bz = -bx - by
+ return max(abs(bx-ax), abs(by-ay), abs(bz-az))
+}
+
+const adjacent_x1 = [
+ [-101,-100,-1,1,99,100],
+ [-100,-99,-1,1,100,101]
+]
+
+function for_each_adjacent(x, f) {
+ for (let dx of adjacent_x1[x / 100 & 1]) {
+ let nx = x + dx
+ if (is_map_hex(nx))
+ f(nx)
+ }
}
+const within_x3 = [
+ [
+ -302,-301,-300,-299,
+ -202,-201,-200,-199,-198,
+ -103,-102,-101,-100,-99,-98,
+ -3,-2,-1,0,1,2,3,
+ 97,98,99,100,101,102,
+ 198,199,200,201,202,
+ 298,299,300,301
+ ],
+ [
+ -301,-300,-299,-298,
+ -202,-201,-200,-199,-198,
+ -102,-101,-100,-99,-98,-97,
+ -3,-2,-1,0,1,2,3,
+ 98,99,100,101,102,103,
+ 198,199,200,201,202,
+ 299,300,301,302
+ ]
+]
+
+function for_each_within_x3(x, f) {
+ for (let dx of within_x3[x / 100 & 1]) {
+ let nx = x + dx
+ if (is_map_hex(nx))
+ f(nx)
+ }
+}
+
+const brussels_couillet_road_x3 = []
+for (let a of data.map.brussels_couillet_road)
+ for_each_within_x3(a, b => set_add(brussels_couillet_road_x3, b))
+
function make_piece_list(f) {
let list = []
for (let p = 0; p < data.pieces.length; ++p)
@@ -314,56 +364,6 @@ function piece_is_on_map(p) {
return is_map_hex(x)
}
-function is_map_hex(x) {
- if (x >= 1000 && x <= last_hex)
- return x % 100 <= 41
- return false
-}
-
-function calc_distance(a, b) {
- let ac = a % 100
- let bc = b % 100
- let ay = a / 100 | 0
- let by = b / 100 | 0
- let ax = ac - (ay >> 1)
- let bx = bc - (by >> 1)
- let az = -ax - ay
- let bz = -bx - by
- return max(abs(bx-ax), abs(by-ay), abs(bz-az))
-}
-
-function for_each_adjacent(hex, fn) {
- let row = hex / 100 | 0
- let col = hex % 100
- if (col < 41)
- fn(hex + 1)
- if (col > 0)
- fn(hex - 1)
- if (row & 1) {
- if (row < 40) {
- if (col < 41)
- fn(hex + 101)
- fn(hex + 100)
- }
- if (row > 10) {
- fn(hex - 100)
- if (col < 41)
- fn(hex - 99)
- }
- } else {
- if (row < 40) {
- fn(hex + 100)
- if (col > 0)
- fn(hex + 99)
- }
- if (row > 10) {
- if (col > 0)
- fn(hex - 101)
- fn(hex - 100)
- }
- }
-}
-
function set_next_player() {
game.active = (game.active === P1) ? P2 : P1
}
@@ -444,7 +444,7 @@ states.place_hq = {
done = false
}
if (done)
- view.actions.next = 1
+ view.actions.end_step = 1
},
piece(p) {
if (piece_is_on_map(p)) {
@@ -455,24 +455,11 @@ states.place_hq = {
game.state = "place_hq_where"
}
},
- next() {
+ end_step() {
end_hq_placement_step()
},
}
-function gen_place_hq(from, here, n) {
- for_each_adjacent(here, next => {
- if (calc_distance(next, from) <= calc_distance(here, from))
- return
- if (n > 1)
- gen_place_hq(from, next, n - 1)
- // TODO RULES: as the crow flies or must trace path?
- if (is_enemy_zoc_or_zoi(next) || hex_has_any_piece(next, friendly_hqs()))
- return
- gen_action_hex(next)
- })
-}
-
states.place_hq_where = {
prompt() {
prompt("HQ Placement Step.")
@@ -486,9 +473,10 @@ states.place_hq_where = {
for (let p of friendly_units()) {
let x = piece_hex(p)
if (is_map_hex(x) && pieces_are_same_side(p, game.who)) {
- if (!is_enemy_zoc_or_zoi(x) && !hex_has_any_piece(x, friendly_hqs()))
- gen_action_hex(x)
- gen_place_hq(x, x, 3)
+ for_each_within_x3(x, next => {
+ if (!is_enemy_zoc_or_zoi(next) && !hex_has_any_piece(next, friendly_hqs()))
+ gen_action_hex(next)
+ })
}
}
@@ -547,7 +535,7 @@ states.return_blown_unit = {
}
}
if (done)
- view.actions.next = 1
+ view.actions.end_step = 1
},
piece(p) {
push_undo()
@@ -561,7 +549,7 @@ states.return_blown_unit = {
set_piece_hex(p, ELIMINATED)
}
},
- next() {
+ end_step() {
end_blown_unit_return_step()
},
}
@@ -680,10 +668,7 @@ states.place_detachment_hq = {
for (let p of friendly_hqs())
if (game.count & (1 << p))
gen_action_piece(p)
- if (game.count === 0)
- view.actions.next = 1
- else
- view.actions.pass = 1
+ view.actions.end_step = 1
},
piece(p) {
push_undo()
@@ -691,10 +676,7 @@ states.place_detachment_hq = {
game.count ^= (1 << p)
game.state = "place_detachment_who"
},
- next() {
- end_detachment_placement_step()
- },
- pass() {
+ end_step() {
end_detachment_placement_step()
},
}
@@ -730,20 +712,6 @@ states.place_detachment_who = {
game.who = p
game.state = "place_detachment_where"
},
- next() {
- end_detachment_placement_step()
- },
-}
-
-function gen_place_old_guard(from, here, n) {
- for_each_adjacent(here, next => {
- if (calc_distance(next, from) <= calc_distance(here, from))
- return
- if (n > 1)
- gen_place_old_guard(from, next, n - 1)
- if (!is_enemy_zoc(next) && is_empty_hex(next))
- gen_action_hex(next)
- })
}
states.place_detachment_where = {
@@ -762,29 +730,21 @@ states.place_detachment_where = {
}
if (game.who === OLD_GUARD) {
- gen_place_old_guard(piece_hex(NAPOLEON_HQ), piece_hex(NAPOLEON_HQ), 3)
+ for_each_within_x3(piece_hex(NAPOLEON_HQ), next => {
+ if (!is_enemy_zoc(next) && is_empty_hex(next))
+ gen_action_hex(next)
+ })
return
}
update_zoc()
- move_seen.fill(0)
- search_detachment(piece_hex(game.target), piece_command_range(game.target))
- if (!piece_mode(game.target))
- search_detachment_road(piece_hex(game.target), piece_command_range(game.target) * 2)
-
- for (let p of data.pieces[game.who].parent)
- if (piece_is_on_map(p))
- search_detachment(piece_hex(p), 4)
+ search_detachment(game.who, game.target)
for (let row = 0; row < data.map.rows; ++row) {
for (let col = 0; col < data.map.cols; ++col) {
let x = 1000 + row * 100 + col
- if (
- move_seen[x-1000] &&
- !is_friendly_zoc_or_zoi(x) &&
- !hex_has_any_piece(x, friendly_detachments())
- )
+ if (move_seen[x-1000] && !is_friendly_zoc_or_zoi(x) && !hex_has_any_piece(x, friendly_detachments()))
gen_action_hex(x)
}
}
@@ -799,9 +759,6 @@ states.place_detachment_where = {
game.who = -1
game.state = "place_detachment_hq"
},
- next() {
- end_detachment_placement_step()
- },
}
// === E: DETACHMENT RECALL STEP ===
@@ -950,26 +907,36 @@ states.battle_formation = {
// === H: WITHDRAWAL ===
function goto_withdrawal() {
+ log("Withdrawal.")
game.active = P1
game.state = "withdrawal"
game.remain = 0
+ resume_withdrawal()
}
-/*
function resume_withdrawal() {
game.state = "withdrawal"
update_zoc()
for (let p of friendly_corps())
if (piece_is_in_enemy_zoc(p))
return
- withdrawal_pass()
+ pass_withdrawal()
+}
+
+function pass_withdrawal() {
+ log(game.active + " passed.")
+ if (game.remain > 0) {
+ end_withdrawal()
+ } else {
+ set_next_player()
+ game.remain = 3
+ }
}
-*/
function next_withdrawal() {
- game.state = "withdrawal"
+ set_next_player()
if (game.remain === 0)
- set_next_player()
+ resume_withdrawal()
else if (--game.remain === 0)
end_withdrawal()
}
@@ -981,13 +948,10 @@ function end_withdrawal() {
states.withdrawal = {
prompt() {
prompt("Withdrawal.")
-
update_zoc()
-
for (let p of friendly_corps())
if (piece_is_in_enemy_zoc(p))
gen_action_piece(p)
-
view.actions.pass = 1
},
piece(p) {
@@ -997,27 +961,19 @@ states.withdrawal = {
},
pass() {
clear_undo()
- if (game.remain > 0) {
- end_withdrawal()
- } else {
- set_next_player()
- game.remain = 3
- }
+ pass_withdrawal()
},
}
states.withdrawal_to = {
prompt() {
- prompt("Withdrawal to.")
-
+ prompt("Withdraw " + data.pieces[game.who].name + ".")
update_zoc()
-
let list = search_withdrawal(piece_hex(game.who))
if (list.length > 0)
view.actions.hex = list
else
view.actions.blow = 1
-
gen_action_piece(game.who)
},
piece(p) {
@@ -1191,8 +1147,7 @@ function can_move_into(here, next, hq_hex, hq_range, is_cav) {
return false
if (is_cav) {
- // Cavalry beginning move in Infantry ZoC may only move to empty hex not in ZoC
- // TODO: starting in detachment zoc?
+ // Cavalry beginning move in non-Cavalry ZoC may only move to empty hex not in ZoC
if (is_enemy_zoc(here) && (is_enemy_zoc(next) || !is_empty_hex(next)))
return false
}
@@ -1231,37 +1186,29 @@ function find_reinforcement_hex(who) {
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 - 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 - u, hq_hex, piece_command_range(hq), piece_is_cavalry(p))
- }
- }
-}
-
function can_trace_detachment(here, next) {
if (is_enemy_zoc_or_zoi(next))
return false
- // TODO RULES - rivers block detachment placement?
if (is_river(here, next))
return false
return true
}
-function search_detachment(start, range) {
+function search_detachment(who, hq) {
+ move_seen.fill(0)
+
+ search_detachment_normal(piece_hex(hq), piece_command_range(hq))
+
+ if (!piece_mode(hq))
+ if (is_road_hex(piece_hex(hq)))
+ search_detachment_road(piece_hex(hq), piece_command_range(hq) * 2)
+
+ for (let pp of data.pieces[who].parent)
+ if (piece_is_on_map(pp))
+ search_detachment_normal(piece_hex(pp), 4)
+}
+
+function search_detachment_normal(start, range) {
move_cost.fill(0)
move_cost[start-1000] = range
move_seen[start-1000] = 1
@@ -1270,10 +1217,10 @@ function search_detachment(start, range) {
let here = queue.shift()
for_each_adjacent(here, next => {
if (can_trace_detachment(here, next)) {
- let range = move_cost[here-1000] - 1
+ let mp = move_cost[here-1000] - 1
move_seen[next-1000] = 1
- if (range > move_cost[next-1000]) {
- move_cost[next-1000] = range
+ if (mp > move_cost[next-1000]) {
+ move_cost[next-1000] = mp
queue.push(next)
}
}
@@ -1282,10 +1229,63 @@ function search_detachment(start, range) {
}
function search_detachment_road(start, range) {
- // TODO
+ move_cost.fill(0)
+ move_cost[start-1000] = range
+ move_seen[start-1000] = 1
+ let queue = [ start ]
+ while (queue.length > 0) {
+ let here = queue.shift()
+ for (let [road_id, k] of data_roads[here-1000]) {
+ let road = data.map.roads[road_id]
+ if (k + 1 < road.length)
+ search_detachment_road_segment(queue, road, k, 1)
+ if (k > 0)
+ search_detachment_road_segment(queue, road, k, -1)
+ }
+ }
+}
+
+function search_detachment_road_segment(queue, road, cur, dir) {
+ let here = road[cur]
+ let mp = move_cost[here-1000]
+ cur += dir
+ while (mp > 0 && cur >= 0 && cur < road.length) {
+ let next = road[cur]
+ if (!can_trace_detachment(here, next))
+ return
+ move_seen[next-1000] = 1
+ here = next
+ cur += dir
+ mp --
+ }
+ if (mp > move_cost[here-1000]) {
+ move_cost[here-1000] = mp
+ queue.push(here)
+ }
+}
+
+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_normal(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 - u, hq_hex, piece_command_range(hq), piece_is_cavalry(p))
+ }
+ }
}
-function search_move_offroad(start, ma, hq_hex, hq_range, is_cav) {
+function search_move_normal(start, ma, hq_hex, hq_range, is_cav) {
move_cost.fill(0)
move_cost[start-1000] = ma
let queue = [ start ]
@@ -1332,10 +1332,10 @@ 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
+ return
move_seen[next-1000] |= 1
if (!must_flip_zoc(here, next, is_cav))
move_seen[next-1000] |= 2
@@ -1352,20 +1352,21 @@ function search_move_road_segment(queue, road, cur, dir, hq_hex, hq_range, is_ca
}
function search_withdrawal(here) {
- // Withdraw from ANY enemy unit.
- let result = []
+ // Withdraw from ALL enemy units.
+ let from_list = []
for_each_adjacent(here, from => {
if (hex_has_any_piece(from, enemy_units()))
- search_retreat(result, here, from, 3)
+ from_list.push(here)
})
- return result
+ return search_retreat(result, here, from_list, 3)
}
-function search_retreat(result, here, from, n) {
+function search_retreat(result, here, from_list, n) {
for_each_adjacent(here, next => {
// must move further away
- if (calc_distance(next, from) <= calc_distance(here, from))
- return
+ for (let from of from_list)
+ if (calc_distance(next, from) <= calc_distance(here, from))
+ return
// can't enter zoc
if (is_enemy_zoc(next))
@@ -1384,7 +1385,7 @@ function search_retreat(result, here, from, n) {
return
if (n > 1)
- search_retreat(result, next, from, n - 1)
+ search_retreat(result, next, from_list, n - 1)
else
set_add(result, next)
})
@@ -1512,7 +1513,8 @@ function setup_june_15() {
setup_piece("Prussian", "I Detachment (Lutzow)", 1221)
bring_on_reinforcements()
- goto_movement_phase()
+ // TODO goto_movement_phase()
+ goto_command_phase()
}
function setup_june_16() {
@@ -1559,6 +1561,7 @@ exports.setup = function (seed, scenario, options) {
active: P1,
state: null,
turn: 3,
+ rain: 0,
remain: 0,
pieces: new Array(data.pieces.length).fill(0),
who: -1,
@@ -1607,6 +1610,7 @@ exports.view = function (state, player) {
actions: null,
log: game.log,
turn: game.turn,
+ rain: game.rain,
remain: game.remain,
pieces: game.pieces,
who: game.who,