From 882b952e33518eac4d432ac1b9aa84209d35172a Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 10 Sep 2022 12:55:03 +0200 Subject: Calculate and save group/regroup possibilities at start of movement phase. Also ensure mandatory combat is respected during withdrawal regroups. --- rules.js | 281 ++++++++++++++++++++++++++++++++++++++++++--------------------- 1 file 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, -- cgit v1.2.3