diff options
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 248 |
1 files changed, 129 insertions, 119 deletions
@@ -4,7 +4,6 @@ // TODO: oasis supply // TODO: raiders // TODO: minefields -// TODO: gazala scenario // TODO: legal pass withdrawal moves (reduce supply net, withdraw from fortress attack) // TOOD: reveal/hide blocks (hexes) // TODO: group move from queue holding box to base @@ -14,6 +13,8 @@ // TODO: replacements // TODO: malta units +// TODO: sort blocks by type + // TODO: black hit outline in battles ("steploss/bad" action) // RULES: may units redeploying out of battles cross enemy controlled hexsides? @@ -38,7 +39,10 @@ var view = null var after_rout_table = {} -const { all_hexes, hex_exists, hex_road, side_road, side_limit, hex_name, units, regions } = require("./data") +const { + all_hexes, hex_exists, hex_road, side_road, side_limit, hex_name, regions, + unit_name, unit_appearance, unit_class, unit_speed, unit_start_steps, +} = require("./data") function debug_hexes3(n, list) { console.log("--", n, "--") @@ -218,9 +222,10 @@ function is_hex_or_adjacent_to(x, where) { // === STATE CACHES === const first_axis_unit = 0 -const first_allied_unit = units.findIndex(item => item.nationality === 'allied') -const last_axis_unit = first_allied_unit - 1 -const last_allied_unit = units.length - 1 +const last_axis_unit = 33 +const first_allied_unit = 34 +const last_allied_unit = 93 +const unit_count = 94 var presence_invalid = true var presence_axis = new Array(hexcount).fill(0) @@ -382,11 +387,11 @@ function set_unit_lost_steps(u, n) { } function unit_steps(u) { - return units[u].steps - unit_lost_steps(u) + return unit_start_steps[u] - unit_lost_steps(u) } function set_unit_steps(u, n) { - set_unit_lost_steps(u, units[u].steps - n) + set_unit_lost_steps(u, unit_start_steps[u] - n) } function is_unit_moved(u) { @@ -423,78 +428,46 @@ function reduce_unit(u) { // === UNIT DATA === function find_unit(name) { - for (let u = 0; u < units.length; ++u) - if (units[u].name === name) + for (let u = 0; u < unit_count; ++u) + if (unit_name[u] === name) return u - throw new Error("cannot find named block: " + name) -} - -function unit_name(u) { - return units[u].name -} - -function unit_speed(u) { - return units[u].speed + throw new Error("cannot find named block: " + name + unit_name) } -function unit_class(u) { - return units[u].class -} - -function is_artillery_unit(u) { - return units[u].class === ARTILLERY +function is_allied_unit(u) { + return u >= first_allied_unit && u <= last_allied_unit } -function is_armor_unit(u) { - return units[u].class === ARMOR +function is_axis_unit(u) { + return u >= first_axis_unit && u <= last_axis_unit } -function is_infantry_unit(u) { - return units[u].class === INFANTRY +function is_german_unit(u) { + return (u >= 14 && u <= 33) } -function is_antitank_unit(u) { - return units[u].class === ANTITANK +function is_elite_unit(u) { + return unit_elite[u] } -function is_unit_elite(u) { - return units[u].elite +function is_artillery_unit(u) { + return unit_class[u] === ARTILLERY } function unit_cv(u) { - if (is_unit_elite(u)) + if (is_elite_unit(u)) return unit_steps(u) * 2 return unit_steps(u) } function unit_hp_per_step(u) { - return is_unit_elite(u) ? 2 : 1 + return is_elite_unit(u) ? 2 : 1 } function unit_hp(u) { return unit_steps(u) * unit_hp_per_step(u) } -function is_friendly_hex(x) { - if (game.active === AXIS) - return is_axis_hex(x) - return is_allied_hex(x) -} - -function is_enemy_hex(x) { - if (game.active === ALLIED) - return is_axis_hex(x) - return is_allied_hex(x) -} - -function is_allied_unit(u) { - return u >= first_allied_unit && u <= last_allied_unit -} - -function is_axis_unit(u) { - return u >= first_axis_unit && u <= last_axis_unit -} - // === MAP STATE === function friendly_base() { @@ -616,6 +589,18 @@ function is_empty_hex(x) { return (presence_axis[x] === 0) && (presence_allied[x] === 0) } +function is_friendly_hex(x) { + if (game.active === AXIS) + return is_axis_hex(x) + return is_allied_hex(x) +} + +function is_enemy_hex(x) { + if (game.active === ALLIED) + return is_axis_hex(x) + return is_allied_hex(x) +} + function has_friendly_unit(x) { if (game.active === AXIS) return has_axis_unit(x) @@ -658,10 +643,6 @@ function has_unshielded_disrupted_friendly_unit(x) { return has_unshielded_disrupted_axis_unit(x) } -function is_overrun_hex(x) { - return has_undisrupted_friendly_unit(x) && has_unshielded_disrupted_enemy_unit(x) -} - function is_enemy_rout_hex(x) { return has_undisrupted_friendly_unit(x) && has_unshielded_disrupted_enemy_unit(x) } @@ -820,6 +801,12 @@ function for_each_hex_and_adjacent_hex(here, fn) { } } +function for_each_unit_on_map(fn) { + for (let u = 0; u < unit_count; ++u) + if (is_map_hex(unit_hex(u))) + fn(u) +} + function for_each_axis_unit(fn) { for (let u = first_axis_unit; u <= last_axis_unit; ++u) fn(u) @@ -921,9 +908,9 @@ function has_friendly_units_in_battle() { 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)) + if (!is_elite_unit(u)) if (!is_unit_retreating(u)) - steps[unit_class(u)] += unit_steps(u) + steps[unit_class[u]] += unit_steps(u) }) return steps } @@ -931,9 +918,9 @@ function count_normal_steps_in_battle() { 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)) + if (is_elite_unit(u)) if (!is_unit_retreating(u)) - steps[unit_class(u)] += unit_steps(u) + steps[unit_class[u]] += unit_steps(u) }) return steps } @@ -942,7 +929,7 @@ function count_hp_in_battle() { let hp = [ 0, 0, 0, 0 ] for_each_undisrupted_enemy_unit_in_hex(game.battle, u => { if (!is_unit_retreating(u)) - hp[unit_class(u)] += unit_hp(u) + hp[unit_class[u]] += unit_hp(u) }) return hp } @@ -950,7 +937,7 @@ function count_hp_in_battle() { function count_normal_steps_in_pursuit() { let steps = 0 for_each_undisrupted_enemy_unit_in_hex(game.pursuit, u => { - if (!is_unit_elite(u)) + if (!is_elite_unit(u)) steps += unit_steps(u) }) return steps @@ -959,7 +946,7 @@ function count_normal_steps_in_pursuit() { function count_elite_steps_in_pursuit() { let steps = 0 for_each_undisrupted_enemy_unit_in_hex(game.pursuit, u => { - if (is_unit_elite(u)) + if (is_elite_unit(u)) steps += unit_steps(u) }) return steps @@ -976,7 +963,7 @@ function count_hp_in_pursuit() { function count_normal_steps_in_rout() { let steps = 0 for_each_enemy_unit_in_hex(game.pursuit, u => { - if (!is_unit_elite(u)) + if (!is_elite_unit(u)) steps += unit_steps(u) }) return steps @@ -985,7 +972,7 @@ function count_normal_steps_in_rout() { function count_elite_steps_in_rout() { let steps = 0 for_each_enemy_unit_in_hex(game.pursuit, u => { - if (is_unit_elite(u)) + if (is_elite_unit(u)) steps += unit_steps(u) }) return steps @@ -1357,7 +1344,7 @@ function search_move_retreat(start, speed) { function search_withdraw(who, bonus) { let sline = unit_supply_line(who) let sdist = unit_supply_distance(who) - let speed = unit_speed(who) + bonus + let speed = unit_speed[who] + bonus let start = unit_hex(who) search_move_bfs(path_from[0], path_cost[0], start, 0, speed, false, sline, sdist) @@ -1369,7 +1356,7 @@ function search_withdraw(who, bonus) { function search_withdraw_retreat(who, bonus) { let sline = unit_supply_line(who) let sdist = unit_supply_distance(who) - let speed = unit_speed(who) + bonus + let speed = unit_speed[who] + bonus let start = unit_hex(who) search_move_bfs(path_from[0], path_cost[0], start, 0, speed, true, sline, sdist) @@ -1493,7 +1480,7 @@ function can_move_to(to, speed) { function max_speed_of_undisrupted_and_unmoved_friendly_unit_in_hex(from) { let max_speed = 0 for_each_undisrupted_and_unmoved_friendly_unit_in_hex(from, u => { - let s = unit_speed(u) + let s = unit_speed[u] if (s > max_speed) max_speed = s }) @@ -1989,7 +1976,7 @@ states.move = { // TODO: withdraw pass move search_move(from, speed + 1 + rommel1) for_each_undisrupted_and_unmoved_friendly_unit_in_hex(from, u => { - if (can_move_to(game.to1, unit_speed(u) + 1 + rommel1)) + if (can_move_to(game.to1, unit_speed[u] + 1 + rommel1)) gen_action_unit(u) }) } @@ -2004,7 +1991,7 @@ states.move = { // TODO: withdraw pass move search_move(from, speed + 1 + rommel2) for_each_undisrupted_and_unmoved_friendly_unit_in_hex(from, u => { - if (can_move_to(game.to2, unit_speed(u) + 1 + rommel2)) + if (can_move_to(game.to2, unit_speed[u] + 1 + rommel2)) gen_action_unit(u) }) } @@ -2018,7 +2005,7 @@ states.move = { // Overrun let has_overrun_hex = false for (let x of all_hexes) { - if (is_overrun_hex(x)) { + if (is_enemy_rout_hex(x)) { has_overrun_hex = true break } @@ -2038,7 +2025,7 @@ states.move = { if (game.turn_option === 'pass') search_withdraw(game.selected, 1 + (rommel1 | rommel2)) else - search_move(unit_hex(game.selected), unit_speed(game.selected) + 1 + (rommel1 | rommel2)) + search_move(unit_hex(game.selected), unit_speed[game.selected] + 1 + (rommel1 | rommel2)) gen_move() } @@ -2084,7 +2071,7 @@ states.overrun = { prompt() { view.prompt = `Overrun!` for (let x of all_hexes) - if (is_overrun_hex(x)) + if (is_enemy_rout_hex(x)) gen_action_hex(x) }, hex(where) { @@ -2102,7 +2089,7 @@ function goto_overrun(where) { function gen_move() { let rommel1 = (game.rommel === 1) ? 1 : 0 let rommel2 = (game.rommel === 2) ? 1 : 0 - let speed = unit_speed(game.selected) + let speed = unit_speed[game.selected] let from = unit_hex(game.selected) if (!game.to1 && game.from1 === from) { @@ -2148,7 +2135,7 @@ function apply_move(to) { let rommel2 = (game.rommel === 2) ? 1 : 0 let who = pop_selected() let from = unit_hex(who) - let speed = unit_speed(who) + let speed = unit_speed[who] push_undo() @@ -2543,12 +2530,12 @@ function can_unit_disengage_and_move(who) { function can_unit_disengage_and_withdraw_to(who, to, extra) { search_withdraw_retreat(who, extra) - return can_move_to(to, unit_speed(who) + extra) + return can_move_to(to, unit_speed[who] + extra) } function can_unit_disengage_and_move_to(who, to, extra) { - search_move_retreat(unit_hex(who), unit_speed(who) + extra) - return can_move_to(to, unit_speed(who) + extra) + search_move_retreat(unit_hex(who), unit_speed[who] + extra) + return can_move_to(to, unit_speed[who] + extra) } function can_select_retreat_hex() { @@ -2745,7 +2732,7 @@ states.retreat_move = { if (game.turn_option === 'pass') search_withdraw_retreat(game.selected, 1 + (rommel1 | rommel2)) else - search_move_retreat(unit_hex(game.selected), unit_speed(game.selected) + 1 + (rommel1 | rommel2)) + search_move_retreat(unit_hex(game.selected), unit_speed[game.selected] + 1 + (rommel1 | rommel2)) gen_move() } }, @@ -2783,8 +2770,10 @@ function end_retreat() { } function end_retreat_2() { - if (can_select_retreat_hex()) + if (can_select_retreat_hex()) { + console.log("can_select_retreat_hex") game.state = 'retreat_from' + } else end_movement() } @@ -2862,7 +2851,7 @@ states.refuse_battle_move = { if (done) gen_action('end_retreat') } else { - let speed = unit_speed(game.selected) + let speed = unit_speed[game.selected] gen_action_unit(game.selected) search_withdraw_retreat(game.selected, 0) for (let to of all_hexes) @@ -2970,7 +2959,7 @@ states.rout_move = { if (done) gen_action('end_rout') } else { - let speed = unit_speed(game.selected) + let speed = unit_speed[game.selected] let eliminate = true search_withdraw_retreat(game.selected, 0) for (let to of all_hexes) { @@ -3193,7 +3182,7 @@ function is_minefield_offensive_fire() { } function roll_battle_fire(who, tc) { - let fc = unit_class(who) + let fc = unit_class[who] let cv = unit_cv(who) // Double dice during assault and non-armor defenders in fortress! @@ -3216,7 +3205,7 @@ function roll_battle_fire(who, tc) { } // Double defense in minefields! - if (is_minefield_offensive_fire()) + if (fc !== ARTILLERY && is_minefield_offensive_fire()) total = total / 2 game.flash = `${class_name_cap[fc]} ${firepower_name[fp]} ${result.join("")} at ${class_name[tc]}` @@ -3325,7 +3314,7 @@ function gen_battle_target() { hp[i] -= game.hits[i] let who = game.selected - let fc = unit_class(who) + let fc = unit_class[who] gen_action_unit(who) // deselect @@ -3362,8 +3351,8 @@ function gen_battle_hits() { let done = true for_each_undisrupted_friendly_unit_in_hex(game.battle, u => { if (!is_unit_retreating(u)) { - let c = unit_class(u) - if (is_unit_elite(u)) { + let c = unit_class[u] + if (is_elite_unit(u)) { if (game.hits[c] >= 2) { gen_action_unit(u) done = false @@ -3388,7 +3377,7 @@ function gen_battle_hits() { } function apply_battle_hit(who) { - game.hits[unit_class(who)] -= reduce_unit(who) + game.hits[unit_class[who]] -= reduce_unit(who) } states.battle_fire = { @@ -3572,7 +3561,7 @@ function goto_pursuit_hits() { function slowest_enemy_unit_speed(where) { let r = 4 for_each_enemy_unit_in_hex(where, u => { - let s = unit_speed(u) + let s = unit_speed[u] if (s < r) r = s }) @@ -3582,7 +3571,7 @@ function slowest_enemy_unit_speed(where) { function slowest_undisrupted_enemy_unit_speed(where) { let r = 4 for_each_undisrupted_enemy_unit_in_hex(where, u => { - let s = unit_speed(u) + let s = unit_speed[u] if (s < r) r = s }) @@ -3595,7 +3584,7 @@ function can_rout_fire(verbose) { if (verbose) log(`Slowest was ${speed_name[slowest]} unit.`) for_each_undisrupted_friendly_unit_in_hex(game.pursuit, u => { - if (unit_speed(u) >= slowest && !is_unit_fired(u)) + if (unit_speed[u] >= slowest && !is_unit_fired(u)) result = true }) return result @@ -3607,14 +3596,14 @@ function can_pursuit_fire(verbose) { if (verbose) log(`Slowest was ${speed_name[slowest]} unit.`) for_each_undisrupted_friendly_unit_in_hex(game.pursuit, u => { - if (unit_speed(u) >= slowest && !is_unit_fired(u)) + if (unit_speed[u] >= slowest && !is_unit_fired(u)) result = true }) return result } function roll_pursuit_fire_imp(who, n, hp) { - let speed = unit_speed(who) + let speed = unit_speed[who] if (n === 2) { let a = roll_die() let b = roll_die() @@ -3662,7 +3651,7 @@ states.pursuit_fire = { view.prompt = `Pursuit Fire.` let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) for_each_undisrupted_friendly_unit_in_hex(game.pursuit, u => { - if (unit_speed(u) >= slowest && !is_unit_fired(u)) + if (unit_speed[u] >= slowest && !is_unit_fired(u)) gen_action_unit(u) }) // allow saving fire if there are shielded enemy units @@ -3672,7 +3661,7 @@ states.pursuit_fire = { unit(who) { let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) set_unit_fired(who) - let done = roll_pursuit_fire(who, (unit_speed(who) > slowest ? 2 : 1)) + let done = roll_pursuit_fire(who, (unit_speed[who] > slowest ? 2 : 1)) if (done || !can_pursuit_fire(false)) goto_pursuit_hits() }, @@ -3687,14 +3676,14 @@ states.rout_fire = { view.prompt = `Pursuit Fire (Rout).` let slowest = slowest_enemy_unit_speed(game.pursuit) for_each_undisrupted_friendly_unit_in_hex(game.pursuit, u => { - if (unit_speed(u) >= slowest && !is_unit_fired(u)) + if (unit_speed[u] >= slowest && !is_unit_fired(u)) gen_action_unit(u) }) }, unit(who) { let slowest = slowest_enemy_unit_speed(game.pursuit) set_unit_fired(who) - let done = roll_rout_fire(who, (unit_speed(who) > slowest ? 2 : 1)) + let done = roll_rout_fire(who, (unit_speed[who] > slowest ? 2 : 1)) if (done || !can_rout_fire(false)) goto_rout_hits() }, @@ -3703,7 +3692,7 @@ states.rout_fire = { function gen_pursuit_hits(normal_steps, elite_steps, iterate) { let done = true iterate(game.pursuit, u => { - if (is_unit_elite(u)) { + if (is_elite_unit(u)) { if (game.hits >= 2) { gen_action_unit(u) done = false @@ -3835,18 +3824,17 @@ function end_buildup_discard() { } function init_buildup() { - // TODO: fortress supply - // TODO: assign fortress supply - game.buildup = { // redeployment network axis_network: axis_supply_network().slice(), axis_line: axis_supply_line().slice(), allied_network: allied_supply_network().slice(), allied_line: allied_supply_line().slice(), + // extra cards purchased axis_cards: 0, allied_cards: 0, + // remaining port capacity for sea redeployment bardia: 2, benghazi: 2, @@ -3855,6 +3843,9 @@ function init_buildup() { } function goto_buildup_supply_check() { + // TODO: fortress supply + // TODO: assign fortress supply + init_buildup() for_each_axis_unit_on_map(u => { @@ -3873,13 +3864,20 @@ function goto_buildup_supply_check() { set_unit_supply(u, 0) }) + for_each_unit_on_map(u => { + if (is_unit_supplied(u) && is_unit_disrupted(u) && !is_battle_hex(unit_hex(u))) { + log(`Recovered at #${unit_hex(u)}.`) + clear_unit_disrupted(u) + } + }) + log_br() - resume_buildup_supply_check() + resume_buildup_eleminate_unsupplied() } -function resume_buildup_supply_check() { - game.state = 'buildup_supply_check' +function resume_buildup_eleminate_unsupplied() { + game.state = 'buildup_eleminate_unsupplied' let done = true for_each_friendly_unit_on_map(u => { if (is_unit_unsupplied(u)) @@ -3889,14 +3887,14 @@ function resume_buildup_supply_check() { if (is_axis_player()) { log_br() set_enemy_player() - resume_buildup_supply_check() + resume_buildup_eleminate_unsupplied() } else { goto_buildup_point_determination() } } } -states.buildup_supply_check = { +states.buildup_eleminate_unsupplied = { prompt() { view.prompt = `Buildup: Eliminate unsupplied units.` for_each_friendly_unit_on_map(u => { @@ -3905,9 +3903,9 @@ states.buildup_supply_check = { }) }, unit(u) { - log(`>eliminated at #${unit_hex(u)}`) + log(`Eliminated at #${unit_hex(u)}`) eliminate_unit(u) - resume_buildup_supply_check() + resume_buildup_eleminate_unsupplied() }, } @@ -3944,9 +3942,21 @@ function goto_buildup_point_determination() { goto_buildup_reinforcements() } +function have_scheduled_reinforcements() { + let refit = friendly_refit() + for (let u = first_friendly_unit; u <= last_friendly_unit; ++u) { + let x = unit_hex(u) + if (x === refit || x === game.month || x === game.month + 1) + return true + } +} + function goto_buildup_reinforcements() { log_h2(game.active + " Buildup") - game.state = 'buildup_reinforcements' + if (have_scheduled_reinforcements()) + game.state = 'buildup_reinforcements' + else + goto_buildup_spending() } states.buildup_reinforcements = { @@ -4335,7 +4345,7 @@ function begin_game() { function find_axis_units(a) { let list = [] for (let u = first_axis_unit; u <= last_axis_unit; ++u) - if (units[u].appearance === a) + if (unit_appearance[u] === a) list.push(u) return list } @@ -4343,15 +4353,15 @@ function find_axis_units(a) { function find_allied_units(a) { let list = [] for (let u = first_allied_unit; u <= last_allied_unit; ++u) - if (units[u].appearance === a) + if (unit_appearance[u] === a) list.push(u) return list } function setup_reinforcements(m) { - for (let u = 0; u < units.length; ++u) { - if (units[u].appearance === m) { - if (m === 'M') + for (let u = 0; u < unit_count; ++u) { + if (unit_appearance[u] === m) { + if (m === "M") set_unit_hex(u, MALTA) else set_unit_hex(u, hexdeploy + m) @@ -4367,7 +4377,7 @@ function setup_units(where, steps, list) { u = find_unit(u) set_unit_hex(u, where) if (steps < 0) - set_unit_steps(u, units[u].steps + steps) + set_unit_steps(u, unit_start_steps[u] + steps) else if (steps > 0) set_unit_steps(u, steps) } @@ -4799,7 +4809,7 @@ exports.setup = function (seed, scenario, options) { axis_bps: 0, allied_bps: 0, - units: new Array(units.length).fill(0), + units: new Array(unit_count).fill(0), moved: [], fired: [], recover: [], @@ -4903,7 +4913,7 @@ exports.query = function (state, current, q) { return { axis_supply: game.buildup.axis_network, axis_supply_line: game.buildup.axis_line, - allied_supply: game.buildup.allien_network, + allied_supply: game.buildup.allied_network, allied_supply_line: game.buildup.allied_line, } } else { |