From 76b763521928ff2a11c04f022d4b2d1a67357ed5 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sun, 24 Jul 2022 19:27:42 +0200 Subject: probe combat --- rules.js | 308 +++++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 199 insertions(+), 109 deletions(-) (limited to 'rules.js') diff --git a/rules.js b/rules.js index afb16ef..0726a42 100644 --- a/rules.js +++ b/rules.js @@ -638,7 +638,8 @@ function count_normal_steps_in_battle() { let steps = [ 0, 0, 0, 0 ] for_each_undisrupted_enemy_unit_in_hex(game.battle, u => { if (!is_unit_elite(u)) - steps[unit_class(u)] += unit_steps(u) + if (!is_unit_retreating(u)) + steps[unit_class(u)] += unit_steps(u) }) return steps } @@ -647,7 +648,8 @@ function count_elite_steps_in_battle() { let steps = [ 0, 0, 0, 0 ] for_each_undisrupted_enemy_unit_in_hex(game.battle, u => { if (is_unit_elite(u)) - steps[unit_class(u)] += unit_steps(u) + if (!is_unit_retreating(u)) + steps[unit_class(u)] += unit_steps(u) }) return steps } @@ -655,16 +657,8 @@ function count_elite_steps_in_battle() { function count_hp_in_battle() { let hp = [ 0, 0, 0, 0 ] for_each_undisrupted_enemy_unit_in_hex(game.battle, u => { - hp[unit_class(u)] += unit_hp(u) - }) - return hp -} - -function count_hp_in_battle_of_class(tc) { - let hp = 0 - for_each_undisrupted_enemy_unit_in_hex(game.battle, u => { - if (unit_class(u) === tc) - hp += unit_hp(u) + if (!is_unit_retreating(u)) + hp[unit_class(u)] += unit_hp(u) }) return hp } @@ -988,19 +982,19 @@ function print_path(who, from, to, road) { function search_move(start, speed) { // Normal moves. - search_init_enemy() + search_init() 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) + search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, null, null) + search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, null, null) + search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, null, null) } function search_move_retreat(start, speed) { - search_init_enemy() + search_init() 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) + search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, null, null) + search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, null, null) + search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, null, null) } function search_withdraw(start, speed) { @@ -1008,11 +1002,11 @@ function search_withdraw(start, speed) { 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_init() 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) + search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, sline, sdist) + search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, sline, sdist) + search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, sline, sdist) } function search_withdraw_retreat(start, speed) { @@ -1020,15 +1014,15 @@ function search_withdraw_retreat(start, speed) { 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_init() 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) + search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, sline, sdist) + search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, sline, sdist) + search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, sline, sdist) } // Cache enemy presence -function search_init_enemy() { +function search_init() { path_enemy.fill(0) for_each_enemy_unit(u => { let x = unit_hex(u) @@ -1044,12 +1038,16 @@ function search_init_enemy() { // Breadth First Search 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) cost.fill(15) cost[start] = 0 + if (hex_road[start] < road) + return + + let queue = [ start << 4 ] + while (queue.length > 0) { let item = queue.shift() let here = item >> 4 @@ -1136,10 +1134,6 @@ function can_move_to(to, speed) { return false } -function can_move_from(from, speed) { - return can_move_to(from, speed) -} - function pick_path(to, speed) { let road = 4 let next_cost = 15, next_road = 0 @@ -1221,6 +1215,8 @@ function set_enemy_player() { } function end_player_turn() { + set_clear(game.partial_retreats) + // TODO: end when both pass if (game.phasing === AXIS) game.phasing = ALLIED @@ -1239,6 +1235,10 @@ function goto_player_turn() { game.from1 = game.from2 = 0 game.to1 = game.to2 = 0 + // reset moved and fired flags + set_clear(game.fired) + set_clear(game.moved) + goto_initial_supply_check() } @@ -1268,13 +1268,8 @@ function goto_initial_supply_check() { goto_turn_option() } -function clear_all_unit_moved() { - set_clear(game.moved) -} - function goto_turn_option() { game.state = 'turn_option' - clear_all_unit_moved() } states.turn_option = { @@ -1552,32 +1547,37 @@ function apply_move(to) { search_move(from, max(speed + rommel1, speed + rommel2)) if (!game.to1 && game.from1) { - if (from === game.from1) - if (can_move_to(to, speed + rommel1)) - move_unit(rommel1, who, from, to) + if (from === game.from1) { + for (let to of all_hexes) + if (to != from && can_move_to(to, speed + rommel1)) + return move_unit(who, to, speed + rommel1) + } } if (!game.to2 && game.from2) { - if (from === game.from2) - if (can_move_to(to, speed + rommel2)) - move_unit(rommel2, who, from, to) + if (from === game.from2) { + for (let to of all_hexes) + if (to != from && can_move_to(to, speed + rommel2)) + return move_unit(who, to, speed + rommel2) + } } - if (game.to1) { + if (game.to1 === to) { if (is_hex_or_adjacent_to(from, game.from1)) if (can_move_to(game.to1, speed + rommel1)) - move_unit(rommel1, who, from, to) + return move_unit(who, to, speed + rommel1) } - if (game.to2) { + if (game.to2 === to) { if (is_hex_or_adjacent_to(from, game.from2)) if (can_move_to(game.to2, speed + rommel2)) - move_unit(rommel2, who, from, to) + return move_unit(who, to, speed + rommel2) } } -function move_unit(rommel, who, from, to) { - let road = pick_path(to, unit_speed(who) + rommel) +function move_unit(who, to, speed) { + let from = unit_hex(who) + let road = pick_path(to, speed) set_unit_moved(who) set_unit_hex(who, to) @@ -1654,19 +1654,19 @@ states.move_who = { // Retreat let has_retreat_hex = false - if (!game.to1 && game.from1 && is_battle_hex(game.from1)) + 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)) + 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)) + 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)) + if (has_undisrupted_friendly_unit(x) && is_battle_hex(x) && !set_has(game.partial_retreats, x)) has_retreat_hex = true }) } @@ -1816,22 +1816,36 @@ states.retreat_who = { states.provoke_probe_combat = { prompt() { view.prompt = `Retreat: You may provoke probe combat at ${hex_name[game.retreat]}.` + view.selected = game.retreat_units gen_action('probe') gen_action('pass') }, - // TODO: probe - pass() { + probe() { set_active_player() - let shielded = false - for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => { - if (!set_has(game.retreat_units, u)) - shielded = true - }) - if (shielded) - goto_retreat_move() - else - goto_pursuit_fire_during_retreat(game.retreat) + game.state = 'probe_fire' + game.battle = game.retreat + game.hits = [ 0, 0, 0, 0 ] }, + pass() { + end_probe() + }, +} + +function end_probe() { + game.flash = "" + game.battle = 0 + game.hits = 0 + + set_active_player() + let shielded = false + for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => { + if (!set_has(game.retreat_units, u)) + shielded = true + }) + if (shielded) + goto_retreat_move() + else + goto_pursuit_fire_during_retreat(game.retreat) } function goto_retreat_move() { @@ -1853,6 +1867,7 @@ states.retreat_move = { if (done) gen_action('end_retreat') } else { + gen_action_unit(game.selected) if (game.turn_option === 'pass') gen_move(search_withdraw_retreat) else @@ -2056,18 +2071,33 @@ function end_combat_phase() { // active fire // passive hits -function roll_fire(who, fp, tc) { - let roll = random(6) + 1 - log(`${who} fired ${firepower_name[fp]} ${roll} at ${class_name[tc]}`) - if (roll >= fp) - return 1 - return 0 +function is_unit_retreating(u) { + if (game.retreat_units) + return set_has(game.retreat_units, u) + return false +} + +function roll_battle_fire(who, tc) { + let fc = unit_class(who) + let cv = unit_cv(who) + console.log("FIRE", unit_name(who), cv) + let fp = FIREPOWER_MATRIX[fc][tc] + let result = [] + let total = 0 + for (let i = 0; i < cv; ++i) { + let roll = random(6) + 1 + result.push(roll) + if (roll >= fp) + ++total + } + game.flash = `${unit_name(who)} fired ${firepower_name[fp]} at ${class_name[tc]}: ` + result.join(", ") + log(game.flash) + return total } function goto_battle(x) { clear_undo() game.battle = x - game.fired = [] // goto defensive fire set_passive_player() @@ -2075,16 +2105,27 @@ function goto_battle(x) { game.hits = [ 0, 0, 0, 0 ] } -function apply_battle_fire(tc) { - let firing = pop_selected() +function end_battle() { + set_delete(game.active_battles, game.battle) + set_delete(game.assault_battles, game.battle) - let fp = FIREPOWER_MATRIX[unit_class(firing)][tc] - let cv = unit_cv(firing) + game.flash = "" + game.battle = 0 + game.hits = 0 - set_unit_fired(firing) + set_active_player() + if (game.active_battles.length > 0) + game.state = 'select_battle' + else + end_combat_phase() +} + +function apply_battle_fire(tc) { + let who = pop_selected() - for (let i = 0; i < cv; ++i) - game.hits[tc] += roll_fire(firing, fp, tc) + set_unit_fired(who) + + game.hits[tc] += roll_battle_fire(who, tc) let hp = count_hp_in_battle() @@ -2095,20 +2136,23 @@ function apply_battle_fire(tc) { let done = true 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)) + if (!is_unit_fired(u) && !is_unit_retreating(u)) done = false }) } if (done) { set_enemy_player() - game.state = 'battle_hits' + if (game.state === 'battle_fire') + game.state = 'battle_hits' + else + game.state = 'probe_hits' } } function gen_battle_fire() { let arty = false for_each_undisrupted_friendly_unit_in_hex(game.battle, u => { - if (is_artillery_unit(u)) { + if (is_artillery_unit(u) && !is_unit_retreating(u)) { if (!is_unit_fired(u)) { gen_action_unit(u) arty = true @@ -2117,7 +2161,7 @@ function gen_battle_fire() { }) if (!arty) { for_each_undisrupted_friendly_unit_in_hex(game.battle, u => { - if (!is_unit_fired(u)) + if (!is_unit_fired(u) && !is_unit_retreating(u)) gen_action_unit(u) }) } @@ -2166,22 +2210,24 @@ function gen_battle_hits() { let done = true for_each_undisrupted_friendly_unit_in_hex(game.battle, u => { - let c = unit_class(u) - if (is_unit_elite(u)) { - if (game.hits[c] >= 2) { - gen_action_unit(u) - done = false - } - } else { - if (game.hits[c] >= 1) { - // If mixed elite and non-elite: must assign ALL damage. - if (elite_steps[c] > 0 && normal_steps[c] === 1 && (game.hits[c] & 1) === 0) { - // Eliminating the last non-elite must not leave an odd - // number of hits remaining. - } else { + if (!is_unit_retreating(u)) { + let c = unit_class(u) + if (is_unit_elite(u)) { + if (game.hits[c] >= 2) { gen_action_unit(u) done = false } + } else { + if (game.hits[c] >= 1) { + // If mixed elite and non-elite: must assign ALL damage. + if (elite_steps[c] > 0 && normal_steps[c] === 1 && (game.hits[c] & 1) === 0) { + // Eliminating the last non-elite must not leave an odd + // number of hits remaining. + } else { + gen_action_unit(u) + done = false + } + } } } }) @@ -2246,16 +2292,57 @@ states.battle_hits = { }, } -function end_battle() { - clear_undo() - set_active_player() - set_delete(game.active_battles, game.battle) - set_delete(game.assault_battles, game.battle) - game.battle = 0 - if (game.active_battles.length > 0) - game.state = 'select_battle' - else - end_combat_phase() +states.probe_fire = { + prompt() { + if (game.active !== game.phasing) + view.prompt = `Offensive Fire!` + else + view.prompt = `Defensive Fire!` + if (game.selected < 0) + gen_battle_fire() + else + gen_battle_target() + }, + unit(who) { + apply_select(who) + }, + armor() { + apply_battle_fire(ARMOR) + }, + infantry() { + apply_battle_fire(INFANTRY) + }, + antitank() { + apply_battle_fire(ANTITANK) + }, + artillery() { + apply_battle_fire(ARTILLERY) + }, +} + +states.probe_hits = { + prompt() { + if (game.active !== game.phasing) + view.prompt = `Apply hits from Defensive Fire.` + else + view.prompt = `Apply hits from Offensive Fire.` + view.prompt = `Apply hits.` + gen_battle_hits() + }, + unit(who) { + push_undo() + apply_battle_hit(who) + }, + next() { + clear_undo() + if (game.active !== game.phasing) { + // goto offensive fire + game.state = 'probe_fire' + game.hits = [ 0, 0, 0, 0 ] + } else { + end_probe() + } + }, } // === PURSUIT FIRE === @@ -2276,6 +2363,7 @@ function end_battle() { // routing moves function goto_pursuit_fire_during_retreat(where) { +console.log("goto_pursuit_fire_during_retreat", where) clear_undo() set_passive_player() game.hits = 0 @@ -2323,7 +2411,7 @@ function can_pursuit_fire(verbose) { let result = false let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) if (verbose) - log(`Slowest enemy was ${speed_name[slowest]}.`) + log(`Slowest retreating unit was ${speed_name[slowest]} ${game.active} at #${game.pursuit}.`) for_each_undisrupted_friendly_unit_in_hex(game.pursuit, u => { if (unit_speed(u) >= slowest && !is_unit_fired(u)) result = true @@ -2335,7 +2423,8 @@ function roll_pursuit_fire(who, n) { if (n === 2) { let a = random(6) + 1 let b = random(6) + 1 - log(`>%${who} pursuit fired ${a}, ${b}.`) + game.flash = `${unit_name(who)} fired ${a}, ${b}` + log(game.flash) if (a >= 4) game.hits++ if (b >= 4) @@ -2343,6 +2432,7 @@ function roll_pursuit_fire(who, n) { } if (n === 1) { let a = random(6) + 1 + game.flash = `${unit_name(who)} fired ${a}` log(`>%${who} pursuit fired ${a}.`) if (a >= 4) game.hits++ -- cgit v1.2.3