summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.html6
-rw-r--r--play.js56
-rw-r--r--rules.js382
3 files changed, 229 insertions, 215 deletions
diff --git a/play.html b/play.html
index 07994fa..ea2057c 100644
--- a/play.html
+++ b/play.html
@@ -542,6 +542,12 @@ svg .hex.tip {
<div class="debug menu_item" onclick="send_restart('1941-42')">&#x26a0; Restart 1941-42</div>
</div>
</div>
+ <div class="menu">
+ <div class="menu_title"><img src="/images/wooden-sign.svg"></div>
+ <div class="menu_popup">
+ <div class="menu_item" onclick="send_query('supply')">Supply lines</div>
+ </div>
+ </div>
<div class="icon_button" onclick="toggle_units()"><img src="/images/earth-africa-europe.svg"></div>
<div class="icon_button" onclick="toggle_zoom()"><img src="/images/magnifying-glass.svg"></div>
<div class="icon_button" onclick="toggle_log()"><img src="/images/scroll-quill.svg"></div>
diff --git a/play.js b/play.js
index d495750..5c9601e 100644
--- a/play.js
+++ b/play.js
@@ -152,7 +152,7 @@ function is_unit_action(unit) {
}
function is_unit_selected(unit) {
- return !!(view.selected && view.selected.includes(unit))
+ return view.selected === unit
}
function is_artillery_unit(u) {
@@ -227,16 +227,18 @@ function on_blur(evt) {
function on_click_hex(evt) {
if (evt.button === 0) {
- send_action('hex', evt.target.hex)
+ hide_supply()
+ if (send_action('hex', evt.target.hex))
+ evt.stopPropagation()
}
}
function on_click_unit(evt) {
if (evt.button === 0) {
+ hide_supply()
evt.stopPropagation()
if (focus_stack(evt.target.stack))
send_action('unit', evt.target.unit)
- return true
}
}
@@ -248,6 +250,7 @@ function on_click_battle_unit(evt) {
document.getElementById("map").addEventListener("mousedown", function (evt) {
if (evt.button === 0) {
+ hide_supply()
blur_stack()
}
})
@@ -277,6 +280,38 @@ function toggle_units() {
document.getElementById("units").classList.toggle("hide")
}
+let showing_supply = false
+
+function show_supply(reply) {
+ showing_supply = true
+ view.axis_supply = reply.axis_supply
+ view.axis_supply_line = reply.axis_supply_line
+ view.allied_supply = reply.allied_supply
+ view.allied_supply_line = reply.allied_supply_line
+ for (let x of all_hexes) {
+ ui.hexes[x].classList.toggle("axis_supply", is_hex_axis_supply(x))
+ for (let s = 0; s < 3; ++s)
+ ui.sides[x*3+s].classList.toggle("axis_supply", is_side_axis_supply_line(x*3+s))
+ ui.hexes[x].classList.toggle("allied_supply", is_hex_allied_supply(x))
+ for (let s = 0; s < 3; ++s)
+ ui.sides[x*3+s].classList.toggle("allied_supply", is_side_allied_supply_line(x*3+s))
+ }
+}
+
+function hide_supply() {
+ if (showing_supply) {
+ showing_supply = false
+ for (let x of all_hexes) {
+ ui.hexes[x].classList.toggle("axis_supply", false)
+ ui.hexes[x].classList.toggle("allied_supply", false)
+ for (let s = 0; s < 3; ++s) {
+ ui.sides[x*3+s].classList.toggle("axis_supply", false)
+ ui.sides[x*3+s].classList.toggle("allied_supply", false)
+ }
+ }
+ }
+}
+
const CLEAR = 2
const PASS = 1
const ROUGH = 0
@@ -487,16 +522,6 @@ function update_map() {
ui.hexes[hex].classList.toggle("to", hex === view.to1 || hex === view.to2)
ui.hexes[hex].classList.toggle("axis_control", is_hex_axis_controlled(hex))
ui.hexes[hex].classList.toggle("allied_control", is_hex_allied_controlled(hex))
- if (view.axis_supply) {
- ui.hexes[hex].classList.toggle("axis_supply", is_hex_axis_supply(hex))
- for (let s = 0; s < 3; ++s)
- ui.sides[hex*3+s].classList.toggle("axis_supply", is_side_axis_supply_line(hex*3+s))
- }
- if (view.allied_supply) {
- ui.hexes[hex].classList.toggle("allied_supply", is_hex_allied_supply(hex))
- for (let s = 0; s < 3; ++s)
- ui.sides[hex*3+s].classList.toggle("allied_supply", is_side_allied_supply_line(hex*3+s))
- }
for (let s = 0; s < 3; ++s) {
ui.sides[hex*3+s].classList.toggle("axis_control", is_side_axis_controlled(hex*3+s))
ui.sides[hex*3+s].classList.toggle("allied_control", is_side_allied_controlled(hex*3+s))
@@ -623,6 +648,11 @@ function on_update() {
action_button("undo", "Undo")
}
+function on_reply(q, params) {
+ if (q === 'supply')
+ show_supply(params)
+}
+
function on_focus_hex_tip(x) {
ui.hexes[x].classList.add("tip")
}
diff --git a/rules.js b/rules.js
index 64c62b5..f740ab4 100644
--- a/rules.js
+++ b/rules.js
@@ -231,6 +231,19 @@ function is_hex_or_adjacent_to(x, where) {
// === UNIT STATE ===
+function apply_select(u) {
+ if (game.selected === u)
+ game.selected = -1
+ else
+ game.selected = u
+}
+
+function pop_selected() {
+ let u = game.selected
+ game.selected = -1
+ return u
+}
+
const UNIT_DISRUPTED_SHIFT = 0
const UNIT_DISRUPTED_MASK = 1 << UNIT_DISRUPTED_SHIFT
@@ -592,6 +605,13 @@ function for_each_undisrupted_friendly_unit_in_hex(x, fn) {
fn(u)
}
+function for_each_undisrupted_friendly_unit_in_hex_or_adjacent(x, fn) {
+ // TODO: first/last_enemy_unit
+ for (let u = 0; u < units.length; ++u)
+ if (is_friendly_unit(u) && !is_unit_disrupted(u) && is_hex_or_adjacent_to(unit_hex(u), x))
+ fn(u)
+}
+
function for_each_enemy_unit_in_hex(x, fn) {
// TODO: first/last_enemy_unit
for (let u = 0; u < units.length; ++u)
@@ -1478,100 +1498,12 @@ function goto_move() {
game.state = 'move_who'
}
-function slowest_speed_of_selected_units() {
- let r = 4
- for (let u of game.selected) {
- let s = unit_speed(u)
- if (s < r)
- r = s
- }
- return r
-}
-function gen_move_who() {
+function gen_move(search_fn) {
let rommel1 = (game.rommel === 1) ? 1 : 0
let rommel2 = (game.rommel === 2) ? 1 : 0
-
- // Deselect
- for (let u of game.selected)
- gen_action_unit(u)
-
- // Select Group Move 1
- if (!game.to1 && game.from1) {
- if (game.selected.length === 0 || unit_hex(game.selected[0]) === game.from1) {
- if (!is_battle_hex(game.from1)) {
- for_each_friendly_unit(u => {
- if (!is_unit_disrupted(u) && !is_unit_moved(u) && !set_has(game.selected, u))
- if (unit_hex(u) === game.from1)
- gen_action_unit(u)
- })
- }
- }
- }
-
- // Select Group Move 2
- if (!game.to2 && game.from2) {
- if (game.selected.length === 0 || unit_hex(game.selected[0]) === game.from2) {
- if (!is_battle_hex(game.from2)) {
- for_each_friendly_unit(u => {
- if (!is_unit_disrupted(u) && !is_unit_moved(u) && !set_has(game.selected, u))
- if (unit_hex(u) === game.from2)
- gen_action_unit(u)
- })
- }
- }
- }
-
- function overlap(x, f, t) {
- return t ? is_hex_or_adjacent_to(x, f) : x === f
- }
-
- // Select Regroup Move 1
- if (game.to1) {
- // Multi-select if it does not engage and is an unambigously regroup move 1.
- if (game.selected.length === 0 || (!has_enemy_unit(game.to1) && overlap(unit_hex(game.selected[0]), game.from1, game.to1) && !overlap(unit_hex(game.selected[0]), game.from2, game.to2))) {
- search_move(game.to1, 4 + rommel1)
- for_each_friendly_unit(u => {
- if (!is_unit_disrupted(u) && !is_unit_moved(u) && !set_has(game.selected, u)) {
- let x = unit_hex(u)
- if (is_hex_or_adjacent_to(x, game.from1))
- if (game.selected.length === 0 || !overlap(x, game.from2, game.to2))
- if (can_move_to(game.to1, unit_speed(u) + rommel1))
- if (!is_battle_hex(x))
- gen_action_unit(u)
- }
- })
- }
- }
-
- // Select Regroup Move 2
- if (game.to2) {
- // Multi-select if it does not engage and is unambigously regroup move 2.
- if (game.selected.length === 0 || (!has_enemy_unit(game.to2) && !overlap(unit_hex(game.selected[0]), game.from1, game.to1) && overlap(unit_hex(game.selected[0]), game.from2, game.to2))) {
- search_move(game.to2, 4 + rommel2)
- for_each_friendly_unit(u => {
- if (!is_unit_disrupted(u) && !is_unit_moved(u) && !set_has(game.selected, u)) {
- let x = unit_hex(u)
- if (is_hex_or_adjacent_to(x, game.from2))
- if (game.selected.length === 0 || !overlap(x, game.from1, game.to1))
- if (can_move_to(game.to2, unit_speed(u) + rommel2))
- if (!is_battle_hex(x))
- gen_action_unit(u)
- }
- })
- }
- }
-}
-
-function gen_move_to(search_fn) {
- let rommel1 = (game.rommel === 1) ? 1 : 0
- let rommel2 = (game.rommel === 2) ? 1 : 0
- let speed = slowest_speed_of_selected_units()
-
- // NOTE: During normal regroup moves, we check that we can reach
- // the destination before allowing multi-select.
- // During retreats, we only move from one hex at a time.
- let from = unit_hex(game.selected[0])
+ let speed = unit_speed(game.selected)
+ let from = unit_hex(game.selected)
search_fn(from, max(speed + rommel1, speed + rommel2))
@@ -1579,9 +1511,7 @@ function gen_move_to(search_fn) {
if (from === game.from1) {
for (let to of all_hexes)
if (to != from && can_move_to(to, speed + rommel1))
- // no multi-unit engagement moves
- if (game.selected.length === 1 || !has_enemy_unit(to))
- gen_action_hex(to)
+ gen_action_hex(to)
}
}
@@ -1589,9 +1519,7 @@ function gen_move_to(search_fn) {
if (from === game.from2) {
for (let to of all_hexes)
if (to != from && can_move_to(to, speed + rommel2))
- // no multi-unit engagement moves
- if (game.selected.length === 1 || !has_enemy_unit(to))
- gen_action_hex(to)
+ gen_action_hex(to)
}
}
@@ -1611,11 +1539,9 @@ function gen_move_to(search_fn) {
function apply_move(to) {
let rommel1 = (game.rommel === 1) ? 1 : 0
let rommel2 = (game.rommel === 2) ? 1 : 0
- let speed = slowest_speed_of_selected_units()
- let from = unit_hex(game.selected[0])
-
- let list = game.selected
- game.selected = []
+ let who = pop_selected()
+ let from = unit_hex(who)
+ let speed = unit_speed(who)
push_undo()
@@ -1625,28 +1551,28 @@ function apply_move(to) {
search_move(from, max(speed + rommel1, speed + rommel2))
- for (let who of list) {
- if (!game.to1 && game.from1) {
- if (from === game.from1)
+ if (!game.to1 && game.from1) {
+ if (from === game.from1)
+ if (can_move_to(to, speed + rommel1))
move_unit(rommel1, who, from, to)
- }
+ }
- if (!game.to2 && game.from2) {
- if (from === game.from2)
+ if (!game.to2 && game.from2) {
+ if (from === game.from2)
+ if (can_move_to(to, speed + rommel2))
move_unit(rommel2, who, from, to)
- }
+ }
- if (game.to1) {
- if (is_hex_or_adjacent_to(from, game.from1))
- if (can_move_to(game.to1, speed + rommel1))
- move_unit(rommel1, who, from, to)
- }
+ if (game.to1) {
+ if (is_hex_or_adjacent_to(from, game.from1))
+ if (can_move_to(game.to1, speed + rommel1))
+ move_unit(rommel1, who, from, to)
+ }
- if (game.to2) {
- if (is_hex_or_adjacent_to(from, game.from2))
- if (can_move_to(game.to2, speed + rommel2))
- move_unit(rommel2, who, from, to)
- }
+ if (game.to2) {
+ if (is_hex_or_adjacent_to(from, game.from2))
+ if (can_move_to(game.to2, speed + rommel2))
+ move_unit(rommel2, who, from, to)
}
}
@@ -1681,39 +1607,88 @@ states.move_who = {
prompt() {
view.prompt = `Move: Select unit to move.`
- gen_move_who()
+ let rommel1 = (game.rommel === 1) ? 1 : 0
+ let rommel2 = (game.rommel === 2) ? 1 : 0
- if (game.selected.length > 0) {
+ if (game.selected < 0) {
+
+ // Select Group Move 1
+ if (!game.to1 && game.from1) {
+ if (!is_battle_hex(game.from1)) {
+ for_each_undisrupted_friendly_unit_in_hex(game.from1, u => {
+ if (!is_unit_moved(u))
+ gen_action_unit(u)
+ })
+ }
+ }
+
+ // Select Group Move 2
+ if (!game.to2 && game.from2) {
+ if (!is_battle_hex(game.from2)) {
+ for_each_undisrupted_friendly_unit_in_hex(game.from2, u => {
+ if (!is_unit_moved(u))
+ gen_action_unit(u)
+ })
+ }
+ }
+
+ // Select Regroup Move 1
+ if (game.to1) {
+ search_move(game.to1, 4 + rommel1)
+ for_each_undisrupted_friendly_unit_in_hex_or_adjacent(game.from1, u => {
+ if (!is_unit_moved(u) && can_move_to(game.to1, unit_speed(u) + rommel1))
+ if (!is_battle_hex(unit_hex(u)))
+ gen_action_unit(u)
+ })
+ }
+
+ // Select Regroup Move 2
+ if (game.to1) {
+ search_move(game.to2, 4 + rommel1)
+ for_each_undisrupted_friendly_unit_in_hex_or_adjacent(game.from2, u => {
+ if (!is_unit_moved(u) && can_move_to(game.to2, unit_speed(u) + rommel2))
+ if (!is_battle_hex(unit_hex(u)))
+ gen_action_unit(u)
+ })
+ }
+
+ // Retreat
+ let has_retreat_hex = false
+ if (!game.to1 && game.from1 && is_battle_hex(game.from1))
+ has_retreat_hex = true
+ if (!game.to2 && game.from2 && is_battle_hex(game.from2))
+ has_retreat_hex = true
+ if (game.to1 && !has_enemy_unit(game.to1)) {
+ for_each_hex_and_adjacent_hex(game.from1, x => {
+ if (has_undisrupted_friendly_unit(x) && is_battle_hex(x))
+ has_retreat_hex = true
+ })
+ }
+ if (game.to2 && !has_enemy_unit(game.to2)) {
+ for_each_hex_and_adjacent_hex(game.from2, x => {
+ if (has_undisrupted_friendly_unit(x) && is_battle_hex(x))
+ has_retreat_hex = true
+ })
+ }
+ if (has_retreat_hex)
+ gen_action('retreat')
+
+ gen_action('end_move')
+ } else {
+
+ // Deselect
+ gen_action_unit(game.selected)
+
+ // Move
if (game.turn_option === 'pass')
- gen_move_to(search_withdraw_normal)
+ gen_move(search_withdraw)
else
- gen_move_to(search_move)
- }
+ gen_move(search_move)
- let has_retreat_hex = false
- if (!game.to1 && game.from1 && is_battle_hex(game.from1))
- has_retreat_hex = true
- if (!game.to2 && game.from2 && is_battle_hex(game.from2))
- has_retreat_hex = true
- if (game.to1 && !has_enemy_unit(game.to1)) {
- for_each_hex_and_adjacent_hex(game.from1, x => {
- if (has_undisrupted_friendly_unit(x) && is_battle_hex(x))
- has_retreat_hex = true
- })
}
- if (game.to2 && !has_enemy_unit(game.to2)) {
- for_each_hex_and_adjacent_hex(game.from2, x => {
- if (has_undisrupted_friendly_unit(x) && is_battle_hex(x))
- has_retreat_hex = true
- })
- }
- if (has_retreat_hex)
- gen_action('retreat')
-
- gen_action('end_move')
},
unit(who) {
- set_toggle(game.selected, who)
+ apply_select(who)
},
hex(to) {
apply_move(to)
@@ -1871,9 +1846,9 @@ states.retreat_who = {
if (game.selected.length > 0) {
if (game.turn_option === 'pass')
- gen_move_to(search_withdraw_retreat)
+ gen_move(search_withdraw_retreat)
else
- gen_move_to(search_move_retreat)
+ gen_move(search_move_retreat)
}
},
unit(who) {
@@ -1933,20 +1908,20 @@ states.refuse_battle = {
}
}
-states.refuse_battle_who = {
+states.refuse_battle_withdraw = {
inactive: "refuse battle (withdraw group move)",
prompt() {
view.prompt = `Withdraw: Select unit to move.`
- let done = true
- for_each_undisrupted_friendly_unit_in_hex(game.from1, u => {
- gen_action_unit(u)
- done = false
- })
- if (done)
- gen_action('end_retreat')
-
- if (game.selected.length > 0) {
- let speed = slowest_speed_of_selected_units()
+ if (game.selected < 0) {
+ let done = true
+ for_each_undisrupted_friendly_unit_in_hex(game.from1, u => {
+ gen_action_unit(u)
+ done = false
+ })
+ if (done)
+ gen_action('end_retreat')
+ } else {
+ let speed = unit_speed(game.selected)
search_withdraw_retreat(game.from1, speed)
for (let to of all_hexes)
if (to != game.from1 && can_move_to(to, speed))
@@ -1954,17 +1929,14 @@ states.refuse_battle_who = {
}
},
unit(who) {
- set_toggle(game.selected, who)
+ apply_select(who)
},
hex(to) {
- let list = game.selected
- game.selected = []
+ let who = pop_selected()
push_undo()
- for (let who of list) {
- log(`>to #${to}`)
- set_unit_hex(who, to)
- set_unit_disrupted(who)
- }
+ log(`>to #${to}`)
+ set_unit_hex(who, to)
+ set_unit_disrupted(who)
},
end_retreat() {
clear_undo()
@@ -2453,7 +2425,7 @@ function end_pursuit_fire() {
if (game.retreat) {
game.state = 'retreat_who'
} else {
- game.state = 'refuse_battle_who'
+ game.state = 'refuse_battle_withdraw'
}
}
@@ -2464,52 +2436,39 @@ states.free_deployment = {
prompt() {
let scenario = SCENARIOS[game.scenario]
let deploy = hexdeploy + scenario.start
+ let axis = (game.active === AXIS)
let done = true
- if (game.active === AXIS)
- view.prompt = "Axis Free Deployment"
- else
- view.prompt = "Allied Free Deployment"
+ view.prompt = axis ? "Axis Free Deployment" : "Allied Free Deployment"
- // TODO: first/last_axis_unit
- for (let u = 0; u < units.length; ++u) {
- if (is_friendly_unit(u)) {
- if (unit_hex(u) === deploy) {
- done = false
- gen_action_unit(u)
+ if (game.selected < 0) {
+ for_each_friendly_unit_in_hex(deploy, u => {
+ gen_action_unit(u)
+ done = false
+ })
+ } else {
+ for (let x of (axis ? scenario.axis_deployment : scenario.allied_deployment)) {
+ if (!is_enemy_hex(x)) {
+ let limit = scenario.deployment_limit[x] | 0
+ if (!limit || count_friendly_units_in_hex(x) < limit)
+ gen_action_hex(x)
}
}
- }
-
- if (game.selected.length > 0) {
- let list
- if (game.active === AXIS)
- list = scenario.axis_deployment
- else
- list = scenario.allied_deployment
- // TODO: scenario.deployment_limit
- for (let i = 0; i < list.length; ++i)
- if (!is_enemy_hex(list[i]))
- gen_action_hex(list[i])
+ gen_action_unit(game.selected)
+ done = false
}
if (done)
gen_action_next()
},
unit(u) {
- if (set_has(game.selected, u))
- set_delete(game.selected, u)
- else
- set_add(game.selected, u)
+ apply_select(u)
},
- hex(x) {
+ hex(to) {
+ let who = pop_selected()
push_undo()
- log(`Deployed ${game.selected.length} at #${x}.`)
- for (let i = 0; i < game.selected.length; ++i) {
- let u = game.selected[i]
- set_unit_hex(u, x)
- }
- set_clear(game.selected)
+ log(`Deployed at #${to}.`)
+ set_unit_hex(who, to)
},
next() {
clear_undo()
@@ -2593,6 +2552,7 @@ const SCENARIOS = {
allied_deployment: region_egypt,
axis_initial_supply: 6,
allied_initial_supply: 3,
+ deployment_limit: {},
special: {
no_rommel_bonus: true,
only_one_die_for_buildup: true,
@@ -2606,6 +2566,7 @@ const SCENARIOS = {
allied_deployment: region_egypt_and_libya,
axis_initial_supply: 6,
allied_initial_supply: 6,
+ deployment_limit: {},
},
"Crusader": {
year: 1941,
@@ -2656,6 +2617,7 @@ const SCENARIOS = {
allied_deployment: regions["East Line"],
axis_initial_supply: 10,
allied_initial_supply: 12,
+ deployment_limit: {},
special: {
gazala_pre_build: true,
}
@@ -2668,6 +2630,7 @@ const SCENARIOS = {
allied_deployment: regions["Egypt"],
axis_initial_supply: 8,
allied_initial_supply: 8,
+ deployment_limit: {},
},
"1941-42": {
year: 1941,
@@ -2677,6 +2640,7 @@ const SCENARIOS = {
allied_deployment: region_egypt_and_libya,
axis_initial_supply: 6,
allied_initial_supply: 6,
+ deployment_limit: {},
},
}
@@ -2979,7 +2943,7 @@ function setup(name) {
log_h2("Axis Deployment")
game.active = 'Axis'
game.state = 'free_deployment'
- game.selected = []
+ game.selected = -1
}
// === PUBLIC FUNCTIONS ===
@@ -2997,7 +2961,7 @@ exports.setup = function (seed, scenario, options) {
state: null,
phasing: AXIS,
active: AXIS,
- selected: null,
+ selected: -1,
scenario: scenario,
month: 0,
@@ -3098,6 +3062,20 @@ exports.view = function(state, current) {
return common_view(current)
}
+exports.query = function (state, current, q) {
+ if (q === 'supply') {
+ game = state
+ return {
+ axis_supply: game.axis_supply,
+ axis_supply_line: game.axis_supply_line,
+ allied_supply: game.allied_supply,
+ allied_supply_line: game.allied_supply_line,
+ }
+ }
+ return null
+}
+
+
function gen_action_next() {
gen_action('next')
}