diff options
-rw-r--r-- | rules.js | 225 |
1 files changed, 148 insertions, 77 deletions
@@ -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, |