summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js281
1 files changed, 187 insertions, 94 deletions
diff --git a/rules.js b/rules.js
index 2019300..2a6b4f6 100644
--- a/rules.js
+++ b/rules.js
@@ -1252,8 +1252,6 @@ const all_hexes_for_allied_supply = all_hexes_sorted_by_distance_to_base(ALEXAN
function trace_supply_network(start) {
let search_order = (start === EL_AGHEILA ? all_hexes_for_axis_supply : all_hexes_for_allied_supply)
- console.log("START", hex_name[start])
-
supply_visited.fill(0)
supply_src.fill(0)
supply_net.fill(0)
@@ -2011,24 +2009,78 @@ function is_valid_withdrawal_regroup_permutation(src) {
return is_network_reduced(net, new_net)
}
+function includes_mandatory_hex(from, mandatory) {
+ if (mandatory.length === 0)
+ return true
+ for (let m of mandatory)
+ if (is_hex_or_adjacent_to(from, m))
+ return true
+ return false
+}
+
function list_valid_withdrawal_regroup_command_points() {
+ let mandatory = []
let always = []
let test = []
let maybe = null
- for (let x of all_hexes) {
- let status = is_valid_withdrawal_regroup_move_from(x)
- if (status === 2)
- always.push(x)
- else if (status === 1)
- test.push(x)
+
+ for (let fortress of FORTRESS_HEX_LIST) {
+ if (is_mandatory_combat(fortress)) {
+ set_add(mandatory, fortress)
+ for_each_hex_and_adjacent_hex(fortress, x => {
+ let status = is_valid_withdrawal_regroup_move_from(x)
+ if (status === 2)
+ set_add(always, x)
+ else if (status === 1)
+ set_add(test, x)
+ })
+ }
+ }
+
+ if (mandatory.length === 0) {
+ for (let x of all_hexes) {
+ let status = is_valid_withdrawal_regroup_move_from(x)
+ if (status === 2)
+ set_add(always, x)
+ else if (status === 1)
+ set_add(test, x)
+ }
}
+
console.log("WITHDRAW REGROUP CANDIDATES", always, test)
+
if (is_axis_player())
maybe = list_withdrawal_permutations(EL_AGHEILA, test)
else
maybe = list_withdrawal_permutations(ALEXANDRIA, test)
// TODO: fortress supply
- return { always, maybe, to: null, evacuate: null }
+
+ let from = []
+ let m, n
+
+ // Usable command points
+ for (let here of all_hexes) {
+ if (!includes_mandatory_hex(here, mandatory))
+ continue
+ if (here in maybe)
+ set_add(from, here)
+ else if (!is_enemy_hex(here)) {
+ m = n = 0
+ for_each_hex_and_adjacent_hex(here, x => {
+ // Must include at least one valid withdrawal hex to evacuate fully
+ if (set_has(always, x))
+ m++
+ // TODO: allow one-hex regroup moves? (failed forced march abuse)
+ // Must include at least two hexes to qualify as a regroup move
+ if (has_undisrupted_friendly_unit(x))
+ n++
+ })
+ if (m >= 1 && n >= 2)
+ set_add(from, here)
+ }
+ }
+
+ return { from, always, maybe, to: null, evacuate: null }
}
function count_bits(v) {
@@ -2131,28 +2183,6 @@ function list_withdrawal_permutations(src, maybe) {
return result
}
-function gen_withdrawal_regroup_command_point() {
- var m, n
- for (let here of all_hexes) {
- if (here in game.withdraw.maybe)
- gen_action_hex(here)
- else if (!is_enemy_hex(here)) {
- m = n = 0
- for_each_hex_and_adjacent_hex(here, x => {
- // Must include at least one valid withdrawal hex to evacuate fully
- if (set_has(game.withdraw.always, x))
- m++
- // TODO: allow one-hex regroup moves? (failed forced march abuse)
- // Must include at least two hexes to qualify as a regroup move
- if (has_undisrupted_friendly_unit(x))
- n++
- })
- if (m >= 1 && n >= 2)
- gen_action_hex(here)
- }
- }
-}
-
function list_valid_withdrawal_regroup_destinations() {
let rommel1 = (game.rommel === 1) ? 1 : 0
@@ -2161,7 +2191,7 @@ function list_valid_withdrawal_regroup_destinations() {
let result = []
for_each_hex_and_adjacent_hex(game.from1, from => {
- if (set_has(game.withdraw.always, from)) {
+ if (set_has(game.regroup.always, from)) {
if (is_battle_hex(from)) {
let who = slowest_undisrupted_friendly_unit(from)
let speed = unit_speed[who]
@@ -2188,8 +2218,8 @@ function list_valid_withdrawal_regroup_destinations() {
})
// List hexes that can be reached by ALL units of one valid permutation of maybe-hexes.
- if (game.from1 in game.withdraw.maybe) {
- for (let to of game.withdraw.maybe[game.from1])
+ if (game.from1 in game.regroup.maybe) {
+ for (let to of game.regroup.maybe[game.from1])
set_add(result, to)
}
@@ -2802,6 +2832,8 @@ states.final_supply_check_rout = {
}
function save_withdrawal_supply_lines() {
+ game.withdraw = {}
+
let net = game.withdraw.supply_net = friendly_supply_network().slice()
game.withdraw.supply_line = friendly_supply_line().slice()
@@ -2890,6 +2922,15 @@ function goto_move_phase() {
if (game.phasing === AXIS && game.scenario !== "1940" && game.rommel === 0)
if (game.turn_option !== 'offensive' && game.turn_option !== 'blitz')
game.rommel = 1
+
+ if (game.turn_option === 'pass') {
+ save_withdrawal_supply_lines()
+ game.group = list_valid_withdrawal_group_moves()
+ game.regroup = list_valid_withdrawal_regroup_command_points()
+ } else {
+ game.group = list_valid_group_moves()
+ game.regroup = list_valid_regroup_moves()
+ }
}
function show_move_commands() {
@@ -2899,6 +2940,27 @@ function show_move_commands() {
view.selected_hexes = game.from1
}
+function has_valid_group_move_left() {
+ if (game.group.length > 1)
+ return true
+ if (game.group.length > 0) {
+ if (!game.from1)
+ return true
+ if (!game.to1)
+ return true
+ }
+ return false
+}
+
+function has_valid_regroup_move_left() {
+ if (game.regroup) {
+ if (game.turn_option === 'pass')
+ return game.regroup.from.length > 0
+ return game.regroup.length > 0
+ }
+ return false
+}
+
states.select_moves = {
inactive: "move phase",
prompt() {
@@ -2914,25 +2976,30 @@ states.select_moves = {
view.prompt = `Designate ${game.turn_option} move.`
}
+ let can_group_move = has_valid_group_move_left()
+ let can_regroup_move = has_valid_regroup_move_left()
+
if (game.phasing === AXIS && game.scenario !== "1940" && game.rommel === 0) {
if (game.turn_option === 'offensive' || game.turn_option === 'blitz') {
- gen_action('group')
- gen_action('regroup')
+ view.actions.group = can_group_move
+ view.actions.regroup = can_regroup_move
}
- gen_action('group_rommel')
- gen_action('regroup_rommel')
+ view.actions.group_rommel = can_group_move
+ view.actions.regroup_rommel = can_regroup_move
} else {
- gen_action('group')
- gen_action('regroup')
+ view.actions.group = can_group_move
+ view.actions.regroup = can_regroup_move
}
if (game.turn_option === 'pass')
gen_action('end_turn')
+ else if (!can_group_move && !can_regroup_move)
+ gen_action('end_move')
},
group_rommel() {
push_undo()
game.rommel = (game.from1 === 0) ? 1 : 2
- goto_group()
+ game.state = 'group_move_from'
},
group() {
push_undo()
@@ -2941,14 +3008,20 @@ states.select_moves = {
regroup_rommel() {
push_undo()
game.rommel = (game.from1 === 0) ? 1 : 2
- goto_regroup()
+ game.state = 'regroup_move_command_point'
},
regroup() {
push_undo()
- goto_regroup()
+ game.state = 'regroup_move_command_point'
+ },
+ end_move() {
+ end_movement()
},
end_turn() {
game.summary = null
+ game.group = null
+ game.regroup = null
+ game.withdraw = null
goto_final_supply_check()
},
confirm_end_turn() {
@@ -2956,16 +3029,54 @@ states.select_moves = {
}
}
-function goto_group() {
- game.state = 'group_move_from'
+function list_valid_group_moves() {
+ let result = []
+ for (let x of all_hexes) {
+ if (has_undisrupted_and_unmoved_friendly_unit(x))
+ set_add(result, x)
+ }
+ if (has_friendly_unit_in_raw_hex(friendly_queue()))
+ set_add(result, friendly_queue())
+ return result
+}
+
+function list_valid_withdrawal_group_moves() {
+ let result = []
+ let mandatory = false
+ if (is_mandatory_combat(BARDIA)) {
+ set_add(result, BARDIA)
+ mandatory = true
+ }
+ if (is_mandatory_combat(BENGHAZI)) {
+ set_add(result, BENGHAZI)
+ mandatory = true
+ }
+ if (is_mandatory_combat(TOBRUK)) {
+ set_add(result, TOBRUK)
+ mandatory = true
+ }
+ if (!mandatory) {
+ for (let x of all_hexes) {
+ if (has_undisrupted_friendly_unit(x)) {
+ if (is_valid_withdrawal_group_move_from(x))
+ set_add(result, x)
+ }
+ }
+ }
+ return result
}
-function goto_regroup() {
- game.state = 'regroup_move_command_point'
- if (game.turn_option === 'pass') {
- game.withdraw = list_valid_withdrawal_regroup_command_points()
- save_withdrawal_supply_lines()
+function list_valid_regroup_moves() {
+ let result = []
+ for (let x of all_hexes) {
+ if (!is_enemy_hex(x)) {
+ let n = count_hex_or_adjacent_has_undisrupted_and_unmoved_friendly_unit(x)
+ // TODO: allow one-hex regroup moves? (failed forced march abuse)
+ if (n >= 2)
+ set_add(result, x)
+ }
}
+ return result
}
states.group_move_from = {
@@ -2973,38 +3084,14 @@ states.group_move_from = {
prompt() {
show_move_commands()
view.prompt = `Group Move: Select hex to move from.`
+
+ for (let x of game.group)
+ if (x !== game.from1 || game.to1)
+ gen_action_hex(x)
+
if (game.turn_option !== 'pass') {
- for (let x of all_hexes) {
- if (x === game.from1 && !game.to1)
- continue
- if (has_undisrupted_and_unmoved_friendly_unit(x))
- gen_action_hex(x)
- }
if (has_friendly_unit_in_raw_hex(friendly_queue()))
gen_action_hex(friendly_queue())
- } else {
- // Withdrawal group move
- let mandatory = false
- if (is_mandatory_combat(BARDIA)) {
- gen_action_hex(BARDIA)
- mandatory = true
- }
- if (is_mandatory_combat(BENGHAZI)) {
- gen_action_hex(BENGHAZI)
- mandatory = true
- }
- if (is_mandatory_combat(TOBRUK)) {
- gen_action_hex(TOBRUK)
- mandatory = true
- }
- if (!mandatory) {
- for (let x of all_hexes) {
- if (has_undisrupted_friendly_unit(x)) {
- if (is_valid_withdrawal_group_move_from(x))
- gen_action_hex(x)
- }
- }
- }
}
},
hex(x) {
@@ -3042,7 +3129,7 @@ states.group_move_from = {
console.log("CALC WITHDRAWAL DESTINATIONS")
search_withdraw(who, 1 + rommel1)
- game.withdraw = list_valid_withdrawal_group_moves_to(src, net, game.from1, unit_speed[who] + 1 + rommel1)
+ game.withdraw.to = list_valid_withdrawal_group_moves_to(src, net, game.from1, unit_speed[who] + 1 + rommel1)
console.log("DONE")
} else {
console.log("CALC WITHDRAWAL SKIPPED: full retreat")
@@ -3057,16 +3144,11 @@ states.regroup_move_command_point = {
show_move_commands()
view.prompt = `Regroup Move: Designate the command point hex.`
if (game.turn_option !== 'pass') {
- for (let x of all_hexes) {
- if (!is_enemy_hex(x)) {
- let n = count_hex_or_adjacent_has_undisrupted_and_unmoved_friendly_unit(x)
- // TODO: allow one-hex regroup moves? (failed forced march abuse)
- if (n >= 2)
- gen_action_hex(x)
- }
- }
+ for (let x of game.regroup)
+ gen_action_hex(x)
} else {
- gen_withdrawal_regroup_command_point()
+ for (let x of game.regroup.from)
+ gen_action_hex(x)
}
},
hex(x) {
@@ -3121,6 +3203,9 @@ states.regroup_move_destination = {
}
function end_movement() {
+ game.group = null
+ game.regroup = null
+ game.withdraw = null
game.from1 = game.from2 = game.to1 = game.to2 = 0
goto_forced_marches()
}
@@ -3197,10 +3282,18 @@ function can_end_move() {
return true
if (game.to1) {
+ // must retreat from mandatory combat!
+ for (let fortress of FORTRESS_HEX_LIST) {
+ if (is_hex_or_adjacent_to(game.from1, fortress))
+ if (is_mandatory_combat(fortress) && set_has(fortress, game.regroup.always))
+ return !has_friendly_unit(fortress)
+ }
+
// quick check
- for (let x of game.withdraw.always)
+ for (let x of game.regroup.always)
if (!has_friendly_unit(x))
return true
+
// full check
return is_withdraw_network_reduced()
} else {
@@ -3337,8 +3430,6 @@ states.move = {
},
end_move() {
flush_move_summary()
- if (game.turn_option === 'pass')
- game.withdraw = null
end_movement()
}
}
@@ -3412,9 +3503,9 @@ function gen_withdraw() {
if (!game.to1) {
for (let to of all_hexes) {
if (to != from) {
- if (can_move_to(to, speed + rommel1) && set_has(game.withdraw, to)) {
+ if (can_move_to(to, speed + rommel1) && set_has(game.withdraw.to, to)) {
gen_action_hex(to)
- } else if (can_move_to(to, speed + 1 + rommel1) && set_has(game.withdraw, to)) {
+ } else if (can_move_to(to, speed + 1 + rommel1) && set_has(game.withdraw.to, to)) {
gen_action_forced_march(to)
}
}
@@ -6839,6 +6930,8 @@ exports.setup = function (seed, scenario, options) {
flash: null,
// misc states
+ group: null,
+ regroup: null,
disrupt: null,
withdraw: null,
hexside: null,