diff options
-rw-r--r-- | rules.js | 708 |
1 files changed, 368 insertions, 340 deletions
@@ -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 } |