summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-07-22 23:51:21 +0200
committerTor Andersson <tor@ccxvii.net>2022-11-17 13:11:26 +0100
commit9d272f13ac290e331c3f167f85d695228cee56ce (patch)
tree5675dc43be99cb9520d7c358e645746145f88b15
parent90c24bbb0bc3d461a8239f60ca50f1e8eb486d5d (diff)
downloadrommel-in-the-desert-9d272f13ac290e331c3f167f85d695228cee56ce.tar.gz
Fancy multi moves.
-rw-r--r--rules.js708
1 files changed, 368 insertions, 340 deletions
diff --git a/rules.js b/rules.js
index ad38289..64c62b5 100644
--- a/rules.js
+++ b/rules.js
@@ -1,8 +1,5 @@
"use strict"
-// TODO: search_path with actual unit speed
-
-// TODO: partial moves during regroup (to allow deciding entry hex-side)
// TODO: first_friendly_unit / for_each_friendly_unit
// RULES: can refuse battle from hex with mixed undisrupted and disrupted units?
@@ -10,6 +7,12 @@
// RULES: disrupted units routed again in second enemy turn, will they still recover?
// assume yes, easy to change (remove from game.recover set if routed)
+// TODO: check that full retreat of all units is possible for pass regroup moves!
+// TODO: pass withdrawal moves must reduce supply net
+
+// TODO: supply of disrupted units (no chaining, no blocking enemy lines)
+// TODO: raider
+
// unit state: location (8 bits), supply source (3 bits), steps (2 bits), disrupted (1 bit)
// SEQUENCE
@@ -218,7 +221,8 @@ function is_map_hex(x) {
}
function is_hex_or_adjacent_to(x, where) {
- if (x === where) return true
+ if (x === where)
+ return true
for (let s = 0; s < 6; ++s)
if (x === where + hexnext[s])
return true
@@ -405,11 +409,11 @@ function is_enemy_hex(x) {
}
function is_allied_unit(u) {
- return units[u].nationality === 'allied'
+ return u >= first_allied_unit && u <= last_allied_unit
}
function is_axis_unit(u) {
- return units[u].nationality !== 'allied'
+ return u >= first_axis_unit && u <= last_axis_unit
}
// === MAP STATE ===
@@ -957,112 +961,69 @@ function print_path(who, from, to, road) {
log(">" + p.join(" - ") + ".")
}
-function search_init() {
- path_enemy.fill(0)
- for_each_enemy_unit(u => {
- let x = unit_hex(u)
- if (x >= first_hex && x <= last_hex)
- path_enemy[x] = 1
- })
-}
+// normal move: may not leave battle hex. may engage any enemy. may move freely.
+// normal withdrawal: may not leave battle hex. may engage disrupted enemy. must follow supply lines.
+// retreat move: must leave battle hex via friendly side. may ignore disrupted enemy. may move freely.
+// retreat withdrawal: must leave battle hex via friendly side. may ignore disrupted enemy. must follow supply lines.
-function search_move(start, limit) {
+function search_move(start, speed) {
// Normal moves.
- search_init()
- search_move_bfs(path_from[0], path_cost[0], start, 0, limit)
- if (hex_road[start] >= 1)
- search_move_bfs(path_from[1], path_cost[1], start, 1, limit + 1)
- if (hex_road[start] >= 2)
- search_move_bfs(path_from[2], path_cost[2], start, 2, limit + 2)
- if (hex_road[start] >= 4)
- search_move_bfs(path_from[4], path_cost[4], start, 4, limit + 4)
-}
-
-function search_retreat(start, limit) {
- // (Non-withdrawal) Retreat moves may not cross enemy hex-sides, and may not engage.
- search_init()
- search_retreat_bfs(path_from[0], path_cost[0], start, 0, limit)
- if (hex_road[start] >= 1)
- search_retreat_bfs(path_from[1], path_cost[1], start, 1, limit + 1)
- if (hex_road[start] >= 2)
- search_retreat_bfs(path_from[2], path_cost[2], start, 2, limit + 2)
- if (hex_road[start] >= 4)
- search_retreat_bfs(path_from[4], path_cost[4], start, 4, limit + 4)
-}
-
-function search_withdraw(start, limit) {
- // Withdrawal moves may not cross enemy hex-sides,
- // and must follow a supply line,
- // and must move closer to the supply source,
- // and may not engage.
- let sline = game.active === AXIS ? game.axis_supply_line : game.allied_supply_line
- let sdist = game.active === AXIS ? distance_to[EL_AGHEILA] : distance_to[ALEXANDRIA]
- search_init()
- search_withdraw_bfs(path_from[0], path_cost[0], start, 0, limit, sline, sdist)
- if (hex_road[start] >= 1)
- search_withdraw_bfs(path_from[1], path_cost[1], start, 1, limit + 1, sline, sdist)
- if (hex_road[start] >= 2)
- search_withdraw_bfs(path_from[2], path_cost[2], start, 2, limit + 2, sline, sdist)
- if (hex_road[start] >= 4)
- search_withdraw_bfs(path_from[4], path_cost[4], start, 4, limit + 4, sline, sdist)
+ search_init_enemy()
+ search_move_bfs(path_from[0], path_cost[0], start, 0, speed, false, null, null)
+ if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, null, null)
+ if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, null, null)
+ if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, null, null)
}
-// Breadth First Search
-function search_move_bfs(from, cost, start, road, max_cost) {
- let queue = [ start << 4 ]
-
- from.fill(0)
- cost.fill(15)
- cost[start] = 0
-
- while (queue.length > 0) {
- let item = queue.shift()
- let here = item >> 4
- let here_cost = item & 15
- let next_cost = here_cost + 1
-
- for (let s = 0; s < 6; ++s) {
- let next = here + hexnext[s]
-
- // can't go off-map
- if (next < first_hex || next > last_hex || !hex_exists[next])
- continue
-
- // already seen
- if (cost[next] < 15)
- continue
-
- let side = to_side(here, next, s)
- let max_side = side_limit[side]
-
- // can't cross this hexside
- if (max_side === 0)
- continue
-
- // must stay on road for current bonus
- if (side_road[side] < road)
- continue
-
- // check hexside limit
- if (path_enemy[next] && (game.side_limit[side] | 0) >= max_side)
- continue
+function search_move_retreat(start, speed) {
+ search_init_enemy()
+ search_move_bfs(path_from[0], path_cost[0], start, 0, speed, true, null, null)
+ if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, null, null)
+ if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, null, null)
+ if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, null, null)
+}
- from[next] = here
- cost[next] = next_cost
+function search_withdraw(start, speed) {
+ // FIXME? Define supply lines at the moment of withdrawal.
+ update_supply_networks()
+ let sline = game.active === AXIS ? game.axis_supply_line : game.allied_supply_line
+ let sdist = game.active === AXIS ? distance_to[EL_AGHEILA] : distance_to[ALEXANDRIA]
+ search_init_enemy()
+ search_move_bfs(path_from[0], path_cost[0], start, 0, speed, false, sline, sdist)
+ if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, sline, sdist)
+ if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, sline, sdist)
+ if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, sline, sdist)
+}
- // must stop
- if (path_enemy[next])
- continue
+function search_withdraw_retreat(start, speed) {
+ // FIXME? Define supply lines at the moment of withdrawal.
+ update_supply_networks()
+ let sline = game.active === AXIS ? game.axis_supply_line : game.allied_supply_line
+ let sdist = game.active === AXIS ? distance_to[EL_AGHEILA] : distance_to[ALEXANDRIA]
+ search_init_enemy()
+ search_move_bfs(path_from[0], path_cost[0], start, 0, speed, true, sline, sdist)
+ if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, sline, sdist)
+ if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, sline, sdist)
+ if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, sline, sdist)
+}
- // enough movement allowance to keep going
- if (next_cost < max_cost)
- queue.push(next << 4 | next_cost)
+// Cache enemy presence
+function search_init_enemy() {
+ path_enemy.fill(0)
+ for_each_enemy_unit(u => {
+ let x = unit_hex(u)
+ if (x >= first_hex && x <= last_hex) {
+ if (is_unit_disrupted(u))
+ path_enemy[x] |= 1
+ else
+ path_enemy[x] |= 2
}
- }
+ })
}
// Breadth First Search
-function search_withdraw_bfs(from, cost, start, road, max_cost, sline, sdist) {
+function search_move_bfs(from, cost, start, road, max_cost, retreat, sline, sdist) {
+ let friendly_sides = (game.active === AXIS) ? game.axis_sides : game.allied_sides
let queue = [ start << 4 ]
from.fill(0)
@@ -1089,25 +1050,53 @@ function search_withdraw_bfs(from, cost, start, road, max_cost, sline, sdist) {
let side = to_side(here, next, s)
let max_side = side_limit[side]
- // must follow supply line
- if (sline[side] === 0)
- continue
-
- // may not increase distance to supply source (except Bardia/Ft. Capuzzo)
- if (sdist[next] > sdist[here] && side !== BARDIA_FT_CAPUZZO)
+ // can't cross this hexside
+ if (max_side === 0)
continue
// must stay on road for current bonus
if (side_road[side] < road)
continue
- // cannot enter enemy hex
- if (path_enemy[next])
- continue
+ if (sline) {
+ // must follow supply line
+ if (sline[side] === 0)
+ continue
+
+ // may not increase distance to supply source (except Bardia/Ft. Capuzzo)
+ if (sdist[next] > sdist[here] && side !== BARDIA_FT_CAPUZZO)
+ continue
+ }
+
+ let next_enemy = path_enemy[next]
+ if (retreat) {
+ // must cross friendly hex-side to disengage
+ if (here === start && !set_has(friendly_sides, side))
+ continue
+ // may only ignore unshielded disrupted units
+ if (next_enemy & 2) // has undisrupted enemy
+ continue
+ } else {
+ if (sline) {
+ // may only engage unshielded disrupted units
+ if (next_enemy & 2) // has undisrupted enemy
+ continue
+ }
+ // check hexside limit when engaging enemy units
+ if (next_enemy)
+ if ((game.side_limit[side] | 0) >= max_side)
+ continue
+ }
from[next] = here
cost[next] = next_cost
+ if (!retreat) {
+ // must stop when engaging enemy units
+ if (next_enemy)
+ continue
+ }
+
// enough movement allowance to keep going
if (next_cost < max_cost)
queue.push(next << 4 | next_cost)
@@ -1328,7 +1317,8 @@ states.select_moves = {
view.prompt = `Designate ${game.turn_option} move.`
}
gen_action('group')
- gen_action('regroup')
+ if (game.turn_option !== 'pass')
+ gen_action('regroup') // TODO: needs work...
if (game.turn_option === 'pass')
gen_action('end_turn')
},
@@ -1379,7 +1369,7 @@ states.group_move_from = {
if (game.turn_option === 'offensive' && !game.from2)
game.state = 'select_moves'
else
- goto_move_who()
+ goto_move()
},
}
@@ -1447,7 +1437,7 @@ states.regroup_move_destination = {
if (game.turn_option === 'offensive' && !game.from2)
game.state = 'select_moves'
else
- goto_move_who()
+ goto_move()
},
}
@@ -1461,38 +1451,7 @@ function end_move_phase() {
// === GROUP AND REGROUP MOVEMENT ===
-function unit_speed_1(who) {
- return unit_speed(who) + (game.rommel === 1 ? 1 : 0)
-}
-
-function unit_speed_2(who) {
- return unit_speed(who) + (game.rommel === 2 ? 1 : 0)
-}
-
-function gen_group_move_who(may_retreat, from) {
- if (may_retreat !== is_battle_hex(from))
- return
- for_each_undisrupted_friendly_unit_in_hex(from, u => {
- if (!is_unit_moved(u))
- gen_action_unit(u)
- })
-}
-
-function gen_regroup_move_who(may_retreat, command_point, destination, rommel) {
- search_move(destination, 4 + rommel)
- for_each_hex_and_adjacent_hex(command_point, x => {
- if (x !== destination) {
- if (may_retreat !== is_battle_hex(x))
- return
- for_each_undisrupted_friendly_unit_in_hex(x, u => {
- if (!is_unit_moved(u) && can_move_from(x, unit_speed(u) + rommel))
- gen_action_unit(u)
- })
- }
- })
-}
-
-function goto_move_who() {
+function goto_move() {
if (game.rommel === 1) {
if (game.from1 && game.to1)
log(`Regroup move from #${game.from1} to #${game.to1} (Rommel).`)
@@ -1519,45 +1478,179 @@ function goto_move_who() {
game.state = 'move_who'
}
-states.move_who = {
- inactive: "move (who)",
- prompt() {
- view.prompt = `Move: Select unit to move.`
- if (game.from1) {
- if (game.to1)
- gen_regroup_move_who(false, game.from1, game.to1, game.rommel === 1 ? 1 : 0)
- else
- gen_group_move_who(false, game.from1)
+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() {
+ 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)
+ })
+ }
}
- if (game.from2) {
- if (game.to2)
- gen_regroup_move_who(false, game.from2, game.to2, game.rommel === 2 ? 1 : 0)
- else
- gen_group_move_who(false, game.from2)
+ }
+
+ // 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])
+
+ search_fn(from, max(speed + rommel1, speed + rommel2))
+
+ if (!game.to1 && game.from1) {
+ 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)
+ }
+ }
+
+ if (!game.to2 && game.from2) {
+ 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)
+ }
+ }
+
+ if (game.to1) {
+ if (is_hex_or_adjacent_to(from, game.from1))
+ if (can_move_to(game.to1, speed + rommel1))
+ gen_action_hex(game.to1)
+ }
+
+ if (game.to2) {
+ if (is_hex_or_adjacent_to(from, game.from2))
+ if (can_move_to(game.to2, speed + rommel2))
+ gen_action_hex(game.to2)
+ }
+}
+
+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 = []
+
+ push_undo()
+
+ if (has_enemy_unit(to)) {
+ // TODO: pick hex-side manually when engaging
+ }
+
+ search_move(from, max(speed + rommel1, speed + rommel2))
+
+ for (let who of list) {
+ if (!game.to1 && game.from1) {
+ if (from === game.from1)
+ move_unit(rommel1, who, from, to)
+ }
+
+ if (!game.to2 && game.from2) {
+ if (from === game.from2)
+ 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.to2) {
+ if (is_hex_or_adjacent_to(from, game.from2))
+ if (can_move_to(game.to2, speed + rommel2))
+ move_unit(rommel2, who, from, to)
}
- // TODO: only retreat if possible
- gen_action('retreat')
- gen_action('end_move')
- },
- unit(who) {
- push_undo()
- game.selected = [ who ]
- game.state = 'move_to'
- },
- retreat() {
- push_undo()
- log_br()
- game.state = 'retreat_select_from'
- },
- end_move() {
- clear_undo()
- log_br()
- end_move_phase()
}
}
-function apply_move(move, who, from, to) {
- let rommel = (game.rommel === move ? 1 : 0)
+function move_unit(rommel, who, from, to) {
let road = pick_path(to, unit_speed(who) + rommel)
set_unit_moved(who)
@@ -1565,6 +1658,7 @@ function apply_move(move, who, from, to) {
log(`>from #${from} to #${to}`)
+ // TODO: pick hex-side manually
if (is_battle_hex(to)) {
let side = to_side_id(to, path_from[road][to])
@@ -1582,89 +1676,58 @@ function apply_move(move, who, from, to) {
}
}
-function can_move_regroup_1(who, from, to) {
- if (to === game.to1 && is_hex_or_adjacent_to(from, game.from1))
- if (can_move_to(game.to1, 4, unit_speed_1(who)))
- return true
- return false
-}
-
-function can_move_regroup_2(who, from, to) {
- if (to === game.to2 && is_hex_or_adjacent_to(from, game.from2))
- if (can_move_to(game.to2, 4, unit_speed_2(who)))
- return true
- return false
-}
-
-function can_move_group_1(who, from, to) {
- if (from === game.from1 && !game.to1)
- if (can_move_to(to, unit_speed_1(who)))
- return true
- return false
-}
-
-function can_move_group_2(who, from, to) {
- if (from === game.from2 && !game.to2)
- if (can_move_to(to, unit_speed_2(who)))
- return true
- return false
-}
-
-states.move_to = {
- inactive: "move (to)",
+states.move_who = {
+ inactive: "move",
prompt() {
- view.prompt = `Move: Select where to move.`
- let who = game.selected[0]
- let from = unit_hex(who)
- let maybe_rommel = 1
+ view.prompt = `Move: Select unit to move.`
- search_move(from, unit_speed(who) + maybe_rommel)
+ gen_move_who()
- if (from === game.from1 && !game.to1)
- for (let to of all_hexes)
- if (to != from && can_move_group_1(who, from, to))
- gen_action_hex(to)
-
- if (from === game.from2 && !game.to2)
- for (let to of all_hexes)
- if (to != from && can_move_group_2(who, from, to))
- gen_action_hex(to)
-
- if (can_move_regroup_1(who, from, game.to1))
- gen_action_hex(game.to1)
+ if (game.selected.length > 0) {
+ if (game.turn_option === 'pass')
+ gen_move_to(search_withdraw_normal)
+ else
+ gen_move_to(search_move)
+ }
- if (can_move_regroup_2(who, from, game.to2))
- gen_action_hex(game.to2)
+ 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_unit(who)
+ gen_action('end_move')
},
unit(who) {
- pop_undo()
+ set_toggle(game.selected, who)
},
hex(to) {
+ apply_move(to)
+ },
+ retreat() {
push_undo()
- let who = game.selected[0]
- let from = unit_hex(who)
- let maybe_rommel = 1
-
- search_move(from, unit_speed(who) + maybe_rommel)
-
- if (has_enemy_unit(to)) {
- log("TODO: pick hex-side")
- }
-
- if (can_move_group_1(who, from, to))
- apply_move(1, who, from, to)
- if (can_move_group_2(who, from, to))
- apply_move(2, who, from, to)
- if (can_move_regroup_1(who, from, to))
- apply_move(1, who, from, to)
- if (can_move_regroup_2(who, from, to))
- apply_move(2, who, from, to)
-
- game.state = 'move_who'
- game.selected.length = 0
+ log_br()
+ game.state = 'retreat_select_from'
},
+ end_move() {
+ clear_undo()
+ log_br()
+ end_move_phase()
+ }
}
// === RETREAT ===
@@ -1794,7 +1857,8 @@ function goto_retreat_who() {
states.retreat_who = {
prompt() {
- view.prompt = `Retreat: Select unit to move.`
+ view.prompt = `Retreat!`
+
let done = true
for (let u of game.retreat_units) {
if (unit_hex(u) === game.retreat) {
@@ -1804,11 +1868,26 @@ states.retreat_who = {
}
if (done)
gen_action('end_retreat')
+
+ if (game.selected.length > 0) {
+ if (game.turn_option === 'pass')
+ gen_move_to(search_withdraw_retreat)
+ else
+ gen_move_to(search_move_retreat)
+ }
},
unit(who) {
+ set_toggle(game.selected, who)
+ },
+ hex(to) {
+ let list = game.selected
+ game.selected = []
push_undo()
- game.selected = [ who ]
- game.state = 'retreat_to'
+ for (let who of list) {
+ set_unit_hex(who, to)
+ set_unit_moved(who)
+ set_unit_disrupted(who)
+ }
},
end_retreat() {
clear_undo()
@@ -1819,55 +1898,9 @@ states.retreat_who = {
}
}
-states.retreat_to = {
- prompt() {
- view.prompt = `Retreat: Select destination.`
- let who = game.selected[0]
- let from = game.retreat
-
- gen_action_unit(who)
-
- if (game.turn_option === 'pass') {
- search_withdraw(from, true)
- } else {
- search_retreat(from)
- }
-
- if (from === game.from1 && !game.to1)
- for (let to of all_hexes)
- if (to != from && can_move_group_1(who, from, to))
- gen_action_hex(to)
-
- if (from === game.from2 && !game.to2)
- for (let to of all_hexes)
- if (to != from && can_move_group_2(who, from, to))
- gen_action_hex(to)
-
- if (can_move_regroup_1(who, from, game.to1))
- gen_action_hex(game.to1)
-
- if (can_move_regroup_2(who, from, game.to2))
- gen_action_hex(game.to2)
- },
- unit(who) {
- pop_undo()
- },
- hex(to) {
- push_undo()
- let who = game.selected[0]
- set_unit_hex(who, to)
- set_unit_disrupted(who)
- game.selected = []
- game.state = 'retreat_who'
- },
-}
-
// === REFUSE BATTLE ===
function gen_withdraw_group_move(who, from) {
- for (let to of all_hexes)
- if (to != from && can_move_to(to, 4, unit_speed(who)))
- gen_action_hex(to)
}
function goto_refuse_battle() {
@@ -1901,7 +1934,7 @@ states.refuse_battle = {
}
states.refuse_battle_who = {
- inactive: "refuse battle (withdraw group move: who)",
+ inactive: "refuse battle (withdraw group move)",
prompt() {
view.prompt = `Withdraw: Select unit to move.`
let done = true
@@ -1911,11 +1944,27 @@ states.refuse_battle_who = {
})
if (done)
gen_action('end_retreat')
+
+ if (game.selected.length > 0) {
+ let speed = slowest_speed_of_selected_units()
+ search_withdraw_retreat(game.from1, speed)
+ for (let to of all_hexes)
+ if (to != game.from1 && can_move_to(to, speed))
+ gen_action_hex(to)
+ }
},
- unit(u) {
+ unit(who) {
+ set_toggle(game.selected, who)
+ },
+ hex(to) {
+ let list = game.selected
+ game.selected = []
push_undo()
- game.selected = [ u ]
- game.state = 'refuse_battle_to'
+ for (let who of list) {
+ log(`>to #${to}`)
+ set_unit_hex(who, to)
+ set_unit_disrupted(who)
+ }
},
end_retreat() {
clear_undo()
@@ -1925,31 +1974,6 @@ states.refuse_battle_who = {
}
}
-states.refuse_battle_to = {
- inactive: "refuse battle (withdraw group move: to)",
- prompt() {
- view.prompt = `Withdraw: Select destination.`
- let who = game.selected[0]
-
- update_supply_networks()
-
- search_withdraw(game.from1, false)
- gen_withdraw_group_move(who, game.from1)
- gen_action_unit(who)
- },
- unit(who) {
- pop_undo()
- },
- hex(to) {
- let who = game.selected[0]
- log(`>to #${to}`)
- set_unit_hex(who, to)
- set_unit_disrupted(who)
- game.selected = []
- game.state = 'refuse_battle_who'
- },
-}
-
// ==== COMBAT PHASE ===
function goto_combat_phase() {
@@ -2086,15 +2110,19 @@ function apply_battle_fire(tc) {
for (let i = 0; i < cv; ++i)
game.hits[tc] += roll_fire(firing, fp, tc)
+ let hp = count_hp_in_battle()
+
// clamp to available hit points
- game.hits[tc] = min(game.hits[tc], count_hp_in_battle_of_class(tc))
+ game.hits[tc] = min(game.hits[tc], hp[tc])
- // end firing when all done
+ // end when no more units to fire or all targets destroyed
let done = true
- for_each_undisrupted_friendly_unit_in_hex(game.battle, u => {
- if (!is_unit_fired(u))
- done = false
- })
+ if (game.hits[0] < hp[0] || game.hits[1] < hp[1] || game.hits[2] < hp[2] || game.hits[3] < hp[3]) {
+ for_each_undisrupted_friendly_unit_in_hex(game.battle, u => {
+ if (!is_unit_fired(u))
+ done = false
+ })
+ }
if (done) {
set_enemy_player()
game.state = 'battle_hits'
@@ -2280,7 +2308,7 @@ function goto_pursuit_fire_during_retreat(where) {
set_passive_player()
game.hits = 0
game.pursuit = where
- if (can_pursuit_fire())
+ if (can_pursuit_fire(true))
game.state = 'pursuit_fire'
else
goto_pursuit_hits()
@@ -2517,16 +2545,16 @@ function end_free_deployment() {
function find_axis_units(a) {
let list = []
- for (let u = 0; u < units.length; ++u)
- if (units[u].nationality !== 'allied' && units[u].appearance === a)
+ for (let u = first_axis_unit; u <= last_axis_unit; ++u)
+ if (units[u].appearance === a)
list.push(u)
return list
}
function find_allied_units(a) {
let list = []
- for (let u = 0; u < units.length; ++u)
- if (units[u].nationality === 'allied' && units[u].appearance === a)
+ for (let u = first_allied_unit; u <= last_allied_unit; ++u)
+ if (units[u].appearance === a)
list.push(u)
return list
}