summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rules.js225
1 files changed, 148 insertions, 77 deletions
diff --git a/rules.js b/rules.js
index b1fea97..7cd0ac4 100644
--- a/rules.js
+++ b/rules.js
@@ -2,15 +2,15 @@
// TODO: first_friendly_unit / for_each_friendly_unit
-// RULES: can refuse battle from hex with mixed undisrupted and disrupted units?
-// assume yes, followed by rout
+// TODO: be smart about caching update_supply_networks calls
+
// 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: forced marches (force march at first move, remember where to for later, or separate step)
// TODO: raider
// unit state: location (8 bits), supply source (3 bits), steps (2 bits), disrupted (1 bit)
@@ -32,9 +32,6 @@
// pursuit fire
// withdraw
// forced marches
-// rout
-// pursuit fire
-// withdraw
// refuse battle
// pursuit fire
// withdraw
@@ -539,6 +536,12 @@ function has_enemy_unit(x) {
return has_allied_unit(x)
}
+function has_disrupted_enemy_unit(x) {
+ if (game.active === ALLIED)
+ return has_disrupted_axis_unit(x)
+ return has_disrupted_allied_unit(x)
+}
+
function has_undisrupted_enemy_unit(x) {
if (game.active === ALLIED)
return has_undisrupted_axis_unit(x)
@@ -678,6 +681,13 @@ function for_each_undisrupted_and_unmoved_friendly_unit_in_hex(x, fn) {
fn(u)
}
+function has_undisrupted_and_moved_friendly_unit_in_hex(x) {
+ // TODO: first/last_enemy_unit
+ for (let u = 0; u < units.length; ++u)
+ if (is_friendly_unit(u) && !is_unit_disrupted(u) && unit_hex(u) === x && is_unit_moved(u))
+ return true
+}
+
function for_each_undisrupted_friendly_unit_in_hex_or_adjacent(x, fn) {
// TODO: first/last_enemy_unit
@@ -1113,10 +1123,10 @@ function search_move_retreat(start, speed) {
}
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()
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, false, sline, sdist)
search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, sline, sdist)
@@ -1125,10 +1135,10 @@ function search_withdraw(start, speed) {
}
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()
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, true, sline, sdist)
search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, sline, sdist)
@@ -1665,24 +1675,7 @@ states.move = {
}
// Retreat
- let has_retreat_hex = false
- if (!game.to1 && game.from1 && is_battle_hex(game.from1) && !set_has(game.partial_retreats, game.from1))
- has_retreat_hex = true
- if (!game.to2 && game.from2 && is_battle_hex(game.from2) && !set_has(game.partial_retreats, 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) && !set_has(game.partial_retreats, 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) && !set_has(game.partial_retreats, x))
- has_retreat_hex = true
- })
- }
- if (has_retreat_hex)
+ if (can_select_retreat_hex())
gen_action('retreat')
// Overrun
@@ -1864,68 +1857,139 @@ function move_unit(who, to, speed) {
// === RETREAT ===
-function can_select_retreat_hex() {
+function can_disengage_and_withdraw(from, sline, sdist) {
let result = false
- if (game.from1) {
- if (game.to1) {
- for_each_hex_and_adjacent_hex(game.from1, x => {
- if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
- result = true
- })
- } else {
- if (is_battle_hex(game.from1) && !set_has(game.partial_retreats, game.from1))
+ for_each_adjacent_hex(from, to => {
+ let side = to_side_id(from, to)
+ if (is_friendly_hexside(side) && !has_enemy_unit(to))
+ if (sline[side] && sdist[to] <= sdist[from])
result = true
- }
+ })
+ return result
+}
+
+function can_disengage_and_move(from) {
+ let result = false
+ for_each_adjacent_hex(from, to => {
+ let side = to_side_id(from, to)
+ if (is_friendly_hexside(side) && !has_enemy_unit(to))
+ result = true
+ })
+ return result
+}
+
+function can_retreat_with_group_move(from, sline, sdist) {
+ if (!is_battle_hex(from) || set_has(game.partial_retreats, from))
+ return false
+ if (game.turn_option === 'pass') {
+ // no partial retreats!
+ if (has_undisrupted_and_moved_friendly_unit_in_hex(from))
+ return false
+ return can_disengage_and_withdraw(from, sline, sdist)
+ } else {
+ return can_disengage_and_move(from)
}
- if (game.from2) {
- if (game.to2) {
- for_each_hex_and_adjacent_hex(game.from2, x => {
- if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
- result = true
- })
- } else {
- if (is_battle_hex(game.from2) && !set_has(game.partial_retreats, game.from2))
+}
+
+function can_retreat_with_regroup_move(from, to, rommel) {
+ if (!is_battle_hex(from) || set_has(game.partial_retreats, from))
+ return false
+ let speed = max_speed_of_undisrupted_and_unmoved_friendly_unit_in_hex(from)
+ if (game.turn_option === 'pass')
+ search_withdraw_retreat(from, speed + rommel)
+ else
+ search_move_retreat(from, speed + rommel)
+
+ let result = 0
+ for_each_undisrupted_friendly_unit_in_hex(from, u => {
+ if (can_move_to(to, unit_speed(u) + rommel) && !is_unit_moved(u))
+ result |= 1
+ else
+ result |= 2
+ })
+
+ if (game.turn_option === 'pass')
+ return result === 1
+ else
+ return result === 1 || result === 3
+}
+
+function can_select_retreat_hex() {
+ if (!game.to1 && game.from1)
+ if (can_retreat_with_group_move(game.from1, sline, sdist))
+ return true
+
+ if (!game.to2 && game.from2)
+ if (can_retreat_with_group_move(game.from2, sline, sdist))
+ return true
+
+ if (game.to1) {
+ let result = false
+ for_each_hex_and_adjacent_hex(game.from1, x => {
+ if (can_retreat_with_regroup_move(x, game.to1, rommel1))
result = true
- }
+ })
+ if (result)
+ return true
}
- return result
+
+ if (game.to2) {
+ let result = false
+ for_each_hex_and_adjacent_hex(game.from2, x => {
+ if (can_retreat_with_regroup_move(x, game.to2, rommel2))
+ result = true
+ })
+ if (result)
+ return true
+ }
+
+ return false
}
states.retreat_from = {
prompt() {
view.prompt = `Retreat: Select hex to retreat from.`
- if (game.from1) {
- if (game.to1) {
- // TODO: only hexes with units that can reach regroup destination
- // TODO: if full retreat required, only allow hexes where all can reach regroup destination
- for_each_hex_and_adjacent_hex(game.from1, x => {
- if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
- gen_action_hex(x)
- })
- } else {
- if (is_battle_hex(game.from1) && !set_has(game.partial_retreats, game.from1))
- gen_action_hex(game.from1)
- }
+
+ 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]
+
+ if (!game.to1 && game.from1) {
+ if (can_retreat_with_group_move(game.from1, sline, sdist))
+ gen_action_hex(game.from1)
}
- if (game.from2) {
- if (game.to2) {
- // TODO: only hexes with units that can reach regroup destination
- // TODO: if full retreat required, only allow hexes where all can reach regroup destination
- for_each_hex_and_adjacent_hex(game.from2, x => {
- if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
- gen_action_hex(x)
- })
- } else {
- if (is_battle_hex(game.from2) && !set_has(game.partial_retreats, game.from2))
- gen_action_hex(game.from2)
- }
+
+ if (!game.to2 && game.from2) {
+ if (can_retreat_with_group_move(game.from2, sline, sdist))
+ gen_action_hex(game.from2)
+ }
+
+ if (game.to1) {
+ for_each_hex_and_adjacent_hex(game.from1, x => {
+ if (can_retreat_with_regroup_move(x, game.to1, rommel1))
+ gen_action_hex(x)
+ })
+ }
+
+ if (game.to2) {
+ for_each_hex_and_adjacent_hex(game.from2, x => {
+ if (can_retreat_with_regroup_move(x, game.to2, rommel2))
+ gen_action_hex(x)
+ })
}
+
+ gen_action('end_move')
},
hex(x) {
push_undo()
game.retreat = x
game.state = 'retreat_who'
game.retreat_units = []
+ },
+ end_move() {
+ clear_undo()
+ log_br()
+ end_move_phase()
}
}
@@ -1934,9 +1998,10 @@ states.retreat_who = {
view.prompt = `Retreat: Select units to retreat.`
let full_retreat = true
for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => {
- // TODO: can reach regroup destination?
if (!set_has(game.retreat_units, u))
full_retreat = false
+ // TODO: has available retreat path XXX
+ // TODO: can reach regroup destination?
gen_action_unit(u)
})
if (full_retreat) {
@@ -1955,6 +2020,8 @@ states.retreat_who = {
},
select_all() {
for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => {
+ // TODO: has available retreat path XXX
+ // TODO: can reach regroup destination?
set_add(game.retreat_units, u)
})
},
@@ -2104,9 +2171,14 @@ states.refuse_battle = {
inactive: "refuse battle",
prompt() {
view.prompt = `You may Refuse Battle.`
+
+ 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]
+
for (let x of game.active_battles)
- // TODO: check if there are possible retreat paths
- gen_action_hex(x)
+ if (can_disengage_and_withdraw(x, sline, sdist))
+ gen_action_hex(x)
gen_action('next')
},
hex(x) {
@@ -2826,8 +2898,9 @@ states.pursuit_fire = {
if (unit_speed(u) >= slowest && !is_unit_fired(u))
gen_action_unit(u)
})
- // TODO: only save fire if there are shielded enemy units?
- gen_action('next')
+ // allow saving fire if there are shielded enemy units
+ if (has_disrupted_enemy_unit(game.pursuit))
+ gen_action('next')
},
unit(who) {
let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit)
@@ -3543,8 +3616,6 @@ exports.setup = function (seed, scenario, options) {
exports.view = function(state, current) {
game = state
- // update_supply_networks()
-
view = {
month: game.month,
units: game.units,