diff options
-rw-r--r-- | play.js | 5 | ||||
-rw-r--r-- | rules.js | 342 |
2 files changed, 303 insertions, 44 deletions
@@ -601,6 +601,11 @@ function on_update() { action_button("rommel", "Rommel") action_button("stop", "Stop") + action_button("retreat", "Retreat") + action_button("partial_retreat", "Partial retreat") + action_button("full_retreat", "Full retreat") + action_button("probe_combat", "Probe combat") + action_button("group", "Group") action_button("regroup", "Regroup") @@ -222,6 +222,10 @@ function is_unit_supplied(u) { return ((game.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT) !== 0 } +function is_unit_unsupplied(u) { + return ((game.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT) === 0 +} + function unit_supply(u) { let src = (game.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT return hex_from_supply_source[src] @@ -557,6 +561,14 @@ function for_each_undisrupted_enemy_unit_in_hex(x, fn) { fn(u) } +function count_battle_hexes() { + let n = 0 + for (let x of all_hexes) + if (is_battle_hex(x)) + ++n + return n +} + function count_normal_steps_in_battle() { let steps = [ 0, 0, 0, 0 ] for_each_undisrupted_enemy_unit_in_hex(game.battle, u => { @@ -1242,7 +1254,7 @@ function goto_initial_supply_check() { if (snet[x]) { set_unit_supply(u, ssrc) if (is_unit_disrupted(u) && set_has(game.recover, u) && !is_battle_hex(x)) { - log(`${unit_name(u)} recovered in ${hex_name[x]}`) + log(`${unit_name(u)} recovered at ${hex_name[x]}`) set_delete(game.recover, u) clear_unit_disrupted(u) } @@ -1475,17 +1487,21 @@ function goto_move_who() { game.state = 'move_who' } -function gen_group_move_who(from) { +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(command_point, destination, rommel) { +function gen_regroup_move_who(may_retreat, command_point, destination, rommel) { search_move(destination, 0, 4) 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, 4, unit_speed(u) + rommel)) gen_action_unit(u) @@ -1500,17 +1516,17 @@ states.move_who = { view.prompt = `Move: Select unit to move.` if (game.from1) { if (game.to1) - gen_regroup_move_who(game.from1, game.to1, game.rommel === 1 ? 1 : 0) + gen_regroup_move_who(false, game.from1, game.to1, game.rommel === 1 ? 1 : 0) else - gen_group_move_who(game.from1) + gen_group_move_who(false, game.from1) } if (game.from2) { if (game.to2) - gen_regroup_move_who(game.from2, game.to2, game.rommel === 2 ? 1 : 0) + gen_regroup_move_who(false, game.from2, game.to2, game.rommel === 2 ? 1 : 0) else - gen_group_move_who(game.from2) + gen_group_move_who(false, game.from2) } - // TODO: retreat + // TODO: only retreat if possible gen_action('retreat') gen_action('end_move') }, @@ -1521,16 +1537,24 @@ states.move_who = { game.move_used = 0 game.move_road = 4 }, + retreat() { + push_undo() + game.state = 'retreat_select_from' + }, end_move() { clear_undo() - game.side_limit = {} - game.rommel = 0 - game.from1 = game.from2 = game.to1 = game.to2 = 0 - // TODO: forced marches - goto_refuse_battle() + end_move_phase() } } +function end_move_phase() { + game.side_limit = {} + game.rommel = 0 + game.from1 = game.from2 = game.to1 = game.to2 = 0 + // TODO: forced marches + goto_refuse_battle() +} + function print_path(who, from, to, road) { let p = [ hex_name[to] ] while (to && to !== from) { @@ -1758,6 +1782,183 @@ function stop_move(who) { game.state = 'move_who' } +// === RETREAT === + +function can_select_retreat_hex() { + 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)) + result = true + } + } + 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)) + result = true + } + } + return result +} + +states.retreat_select_from = { + prompt() { + view.prompt = `Retreat: Select hex to retreat from.` + 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)) + gen_action_hex(x) + }) + } else { + if (is_battle_hex(game.from1) && !set_has(game.partial_retreats, game.from1)) + gen_action_hex(game.from1) + } + } + 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)) + gen_action_hex(x) + }) + } else { + if (is_battle_hex(game.from2) && !set_has(game.partial_retreats, game.from2)) + gen_action_hex(game.from2) + } + } + }, + hex(x) { + push_undo() + game.retreat = x + game.state = 'retreat_select_who' + } +} + +states.retreat_select_who = { + prompt() { + view.prompt = `Retreat: Select unit to move.` + let full_retreat = true + for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => { + if (!set_has(game.selected, u)) + full_retreat = false + gen_action_unit(u) + }) + // No Partial Retreat allowed for Pass turn option. + if (game.turn_option !== 'pass' && game.selected.length > 0) + gen_action('partial_retreat') + gen_action('full_retreat') + }, + unit(u) { + console.log("set toggle", u) + set_toggle(game.selected, u) + }, + full_retreat() { + clear_undo() + for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => { + set_add(game.selected, u) + }) + game.retreat_units = game.selected + game.selected = [] + goto_pursuit_fire(game.retreat) + }, + partial_retreat() { + clear_undo() + set_add(game.partial_retreats, game.retreat) + set_passive_player() + game.state = 'provoke_probe_combat' + }, +} + +states.provoke_probe_combat = { + prompt() { + view.prompt = `Retreat: You may provoke probe combat at ${hex_name[game.retreat]}.` + gen_action('probe_combat') + gen_action('pass') + }, + // TODO: probe_combat + pass() { + set_active_player() + game.retreat_units = game.selected + game.selected = [] + 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_who() + else + goto_pursuit_fire(game.retreat) + }, +} + +function goto_retreat_who() { + set_active_player() + game.state = 'retreat_who' +} + +states.retreat_who = { + prompt() { + view.prompt = `Retreat: Select unit to move.` + let done = true + for (let u of game.retreat_units) { + if (unit_hex(u) === game.retreat) { + gen_action_unit(u) + done = false + } + } + if (done) + gen_action('end_retreat') + }, + unit(who) { + push_undo() + game.selected = [ who ] + game.state = 'retreat_to' + }, + end_retreat() { + clear_undo() + if (can_select_retreat_hex()) + game.state = 'retreat_select_from' + else + end_move_phase() + } +} + +states.retreat_to = { + prompt() { + view.prompt = `Retreat: Select destination.` + let who = game.selected[0] + gen_action_unit(who) + //if (game.turn_option === 'pass') { + { + search_withdraw(game.retreat, true) + // TODO: regroup + gen_withdraw_group_move(who, game.retreat) + } + }, + 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) { @@ -1788,7 +1989,6 @@ states.refuse_battle = { hex(x) { push_undo() set_delete(game.active_battles, x) - game.pursuit = x goto_pursuit_fire(x) }, next() { @@ -1850,18 +2050,17 @@ states.refuse_battle_to = { function goto_combat_phase() { clear_undo() set_active_player() - if (game.active_battles.length > 0) - return goto_select_battles() - for (let x of all_hexes) - if (is_battle_hex(x)) - return goto_select_battles() + let n = count_battle_hexes() + if (n > 0) { + if (n > game.active_battles.length) + return game.state = 'select_active_battles' + if (game.turn_option === 'assault') + return game.state = 'select_assault_battles' + return goto_select_battle() + } end_combat_phase() } -function goto_select_battles() { - game.state = 'select_active_battles' -} - states.select_active_battles = { inactive: "combat phase (select active battles)", prompt() { @@ -1910,6 +2109,18 @@ states.select_assault_battles = { } } +function goto_select_battle() { + if (game.active_battles.length > 0) { + if (game.active_battles.length > 1) { + game.state = 'select_battle' + } else { + goto_battle(game.active_battles[0]) + } + } else { + end_combat_phase() + } +} + states.select_battle = { inactive: "combat phase (select next battle)", prompt() { @@ -1920,12 +2131,25 @@ states.select_battle = { gen_action_hex(x) }, hex(x) { - clear_undo() - game.battle = x - goto_defensive_fire() + goto_battle(x) }, } +function end_combat_phase() { + // TODO: blitz + // TODO: final supply check + // TODO: supply cards revealed + end_player_turn() +} + +// === BATTLES === + +function goto_battle(x) { + clear_undo() + game.battle = x + goto_defensive_fire() +} + function goto_defensive_fire() { set_passive_player() game.fired = [] @@ -2073,7 +2297,7 @@ const xxx_fire_hits = { if (game.state === 'defensive_fire_hits') { goto_offensive_fire() } else { - end_combat() + end_battle() } }, } @@ -2127,7 +2351,7 @@ function goto_fire_hits() { } } -function end_combat() { +function end_battle() { clear_undo() set_active_player() set_delete(game.active_battles, game.battle) @@ -2146,19 +2370,24 @@ states.offensive_fire_target = xxx_fire_target states.defensive_fire_hits = xxx_fire_hits states.offensive_fire_hits = xxx_fire_hits -function end_combat_phase() { - // TODO: blitz - // TODO: final supply check - // TODO: supply cards revealed - end_player_turn() -} - // === PURSUIT FIRE === -function goto_pursuit_fire() { +function goto_retreat_fire(where) { + clear_undo() + set_passive_player() + game.hits = 0 + game.pursuit = where + if (can_pursuit_fire()) + game.state = 'pursuit_fire' + else + goto_pursuit_hits() +} + +function goto_pursuit_fire(where) { clear_undo() set_active_player() game.hits = 0 + game.pursuit = where if (can_pursuit_fire()) game.state = 'pursuit_fire' else @@ -2187,10 +2416,19 @@ function can_pursuit_fire() { } function roll_pursuit_fire(n) { - for (let i = 0; i < n; ++i) { - let roll = random(6) + 1 - log(`Pursuit fire ${roll}.`) - if (roll >= 4) + if (n === 2) { + let a = random(6) + 1 + let b = random(6) + 1 + log(`Pursuit fire ${a}, ${b}.`) + if (a >= 4) + game.hits++ + if (b >= 4) + game.hits++ + } + if (n === 1) { + let a = random(6) + 1 + log(`Pursuit fire ${a}.`) + if (a >= 4) game.hits++ } } @@ -2207,7 +2445,7 @@ states.pursuit_fire = { gen_action('next') }, unit(who) { - let slowest = slowest_enemy_unit_speed(game.pursuit) + let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) if (unit_speed(who) > slowest) roll_pursuit_fire(2) else @@ -2275,8 +2513,13 @@ states.pursuit_hits = { function end_pursuit_fire() { game.from1 = game.pursuit game.pursuit = 0 - set_passive_player() - game.state = 'refuse_battle_who' + if (game.retreat) { + set_active_player() + game.state = 'retreat_who' + } else { + set_passive_player() + game.state = 'refuse_battle_who' + } } // === DEPLOYMENT === @@ -2860,8 +3103,12 @@ exports.setup = function (seed, scenario, options) { move_used: 0, move_road: 4, - // combat + // retreat partial_retreats: [], // remember partial retreats to forbid initiating combat + retreat: 0, + retreat_units: null, + + // combat active_battles: [], assault_battles: [], pursuit: 0, @@ -2986,6 +3233,13 @@ function set_clear(set) { set.length = 0 } +function set_toggle(set, item) { + if (set_has(set, item)) + set_delete(set, item) + else + set_add(set, item) +} + function remove_from_array(array, item) { let i = array.indexOf(item) if (i >= 0) |