From 399ff9adb0d6deed741100311129248ebbff612c Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Thu, 28 Jul 2022 13:00:00 +0200 Subject: Faster fortress control. Victory checks. Combined turn option. --- rules.js | 307 ++++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 215 insertions(+), 92 deletions(-) (limited to 'rules.js') diff --git a/rules.js b/rules.js index dbc4562..e50ac44 100644 --- a/rules.js +++ b/rules.js @@ -8,6 +8,8 @@ // TODO: BUILDUP // TODO: MINEFIELDS +// TOOD: reveal/hide blocks (hexes) + // TODO: setup scenario specials // TODO: when is "fired" status cleared? @@ -673,17 +675,16 @@ function claim_hex_control_for_defender(a) { }) } -function capture_fortress(fortress, capacity, control_prop, captured_prop) { - if (game[control_prop] !== game.active) { +function capture_fortress(fortress, capacity) { + if (!is_fortress_friendly_controlled(fortress)) { if (has_undisrupted_friendly_unit(fortress) && !has_enemy_unit(fortress)) { supply_axis_invalid = true supply_allied_invalid = true log(`Captured #${fortress}!`) - game[control_prop] = game.active - if (!game[captured_prop]) { - game[captured_prop] = 1 + let fresh = set_fortress_friendly_controlled(fortress) + if (fresh) { if (is_axis_player()) { let award = capacity log(`Awarded ${award} supply cards.`) @@ -698,6 +699,62 @@ function capture_fortress(fortress, capacity, control_prop, captured_prop) { } } +// === FORTRESSES === + +const FORTRESS_BIT = { + [BARDIA]: 1, + [BENGHAZI]: 2, + [TOBRUK]: 4, +} + +function is_fortress_axis_controlled(fortress) { + return (game.fortress & FORTRESS_BIT[fortress]) === 0 +} + +function set_fortress_axis_controlled(fortress) { + game.fortress &= ~FORTRESS_BIT[fortress] +} + +function set_fortress_allied_controlled(fortress) { + game.fortress |= FORTRESS_BIT[fortress] +} + +function set_fortress_captured(fortress) { + let bit = FORTRESS_BIT[fortress] << 3 + if (game.fortress & bit) + return false + game.fortress |= bit + return true +} + +function clear_fortresses_captured() { + game.fortress &= 7 +} + +function is_fortress_friendly_controlled(fortress) { + if (is_axis_player()) + return is_fortress_axis_controlled(fortress) + return !is_fortress_axis_controlled(fortress) +} + +function set_fortress_friendly_controlled(fortress) { + if (is_axis_player()) + set_fortress_axis_controlled(fortress) + else + set_fortress_allied_controlled(fortress) + return set_fortress_captured(fortress) +} + +function is_fortress_besieged(fortress) { + let result = false + let besieged = is_fortress_axis_controlled() ? has_allied_unit : has_axis_unit + for_each_adjacent_hex(fortress, x => { + if (besieged(x)) + result = true + }) + return result +} + // === ITERATORS === function for_each_adjacent_hex(here, fn) { @@ -1371,15 +1428,17 @@ function find_valid_regroup_destinations(from, rommel) { } } -// === SUPPLY COMMITMENT === +// === SUPPLY COMMITMENT & TURN OPTION === -function goto_supply_commitment() { - game.state = 'supply_commitment' +function goto_turn_option() { + game.state = 'turn_option' } -states.supply_commitment = { +states.turn_option = { + inactive: "turn option", prompt() { - view.prompt = `Supply Commitment: ${game.commit[0]} real and ${game.commit[1]} dummy.` + view.prompt = `Select Turn Option: ${game.commit[0]} real and ${game.commit[1]} dummy supply.` + let hand = is_axis_player() ? game.axis_hand : game.allied_hand if (game.commit[0] + game.commit[1] < 3) { if (hand[0] > 0) @@ -1387,47 +1446,7 @@ states.supply_commitment = { if (hand[1] > 0) gen_action('dummy_card') } - gen_action_next() - }, - real_card() { - push_undo() - let hand = is_axis_player() ? game.axis_hand : game.allied_hand - hand[0]-- - game.commit[0]++ - }, - dummy_card() { - push_undo() - let hand = is_axis_player() ? game.axis_hand : game.allied_hand - hand[1]-- - game.commit[1]++ - }, - next() { - push_undo() - goto_turn_option() - }, -} - -function goto_turn_option() { - set_active_player() - let n = game.commit[0] + game.commit[1] - if (n === 0) - log(`Played zero supply cards.`) - else if (n === 1) - log(`Played one supply card.`) - else if (n === 2) - log(`Played two supply cards.`) - else if (n === 3) - log(`Played three supply cards.`) - log_br() - - game.state = 'turn_option' -} - -states.turn_option = { - inactive: "turn option", - prompt() { - view.prompt = "Select Turn Option" if (game.commit[0] >= 1) view.actions.basic = 1 else @@ -1447,36 +1466,61 @@ states.turn_option = { }, basic() { push_undo() - game.turn_option = 'basic' - game.passed = 0 - goto_move_phase() + apply_turn_option('basic') }, offensive() { push_undo() - game.turn_option = 'offensive' - game.passed = 0 - goto_move_phase() + apply_turn_option('offensive') }, assault() { push_undo() - game.turn_option = 'assault' - game.passed = 0 - goto_move_phase() + apply_turn_option('assault') }, blitz() { push_undo() - game.turn_option = 'blitz' - game.passed = 0 - goto_move_phase() + apply_turn_option('blitz') }, pass() { push_undo() - game.turn_option = 'pass' - game.passed ++ - goto_move_phase() + apply_turn_option('pass') + }, + real_card() { + push_undo() + let hand = is_axis_player() ? game.axis_hand : game.allied_hand + hand[0]-- + game.commit[0]++ + }, + dummy_card() { + push_undo() + let hand = is_axis_player() ? game.axis_hand : game.allied_hand + hand[1]-- + game.commit[1]++ }, } +function apply_turn_option(option) { + push_undo() + + game.turn_option = option + + let n = game.commit[0] + game.commit[1] + if (n === 0) + log(`Played zero supply cards.`) + else if (n === 1) + log(`Played one supply card.`) + else if (n === 2) + log(`Played two supply cards.`) + else if (n === 3) + log(`Played three supply cards.`) + log_br() + + if (game.turn_option === 'pass') + game.passed++ + else + game.passed = 0 + goto_move_phase() +} + // === PLAYER TURN === function goto_player_turn() { @@ -1507,8 +1551,13 @@ function end_player_turn() { // Reveal supply cards log_br() log(`Supply Cards Revealed:\n${game.commit[0]} real and ${game.commit[1]} dummy.`) + log_br() + game.commit = [ 0, 0 ] + if (check_sudden_death_victory()) + return + if (game.passed === 2) return end_month() @@ -1559,7 +1608,7 @@ function goto_initial_supply_check_rout() { } } if (n === 0) - goto_supply_commitment() + goto_turn_option() else if (n === 1) goto_rout(where, false, goto_initial_supply_check_rout) else @@ -1581,9 +1630,9 @@ states.initial_supply_check_rout = { function goto_final_supply_check() { set_active_player() - capture_fortress(BARDIA, 2, "bardia", "bardia_captured") - capture_fortress(BENGHAZI, 2, "benghazi", "benghazi_captured") - capture_fortress(TOBRUK, 5, "tobruk", "tobruk_captured") + capture_fortress(BARDIA, 2) + capture_fortress(BENGHAZI, 2) + capture_fortress(TOBRUK, 5) let snet = friendly_supply_network() let ssrc = friendly_supply_base() @@ -2874,26 +2923,32 @@ function end_rout() { // ==== COMBAT PHASE === -function is_mandatory_combat(fortress, control_prop) { - return is_battle_hex(fortress) && (game[control_prop] !== game.phasing) +function is_mandatory_combat(fortress) { + if (is_battle_hex(fortress)) { + if (game.phasing === AXIS) + return is_fortress_allied_controlled() + else + return is_fortress_axis_controlled() + } + return false } function goto_combat_phase() { set_active_player() if (game.turn_option === 'pass') { - if (is_mandatory_combat(BARDIA, "bardia")) + if (is_mandatory_combat(BARDIA)) return goto_rout(BARDIA, false, goto_combat_phase) - if (is_mandatory_combat(BENGHAZI, "benghazi")) + if (is_mandatory_combat(BENGHAZI)) return goto_rout(BENGHAZI, false, goto_combat_phase) - if (is_mandatory_combat(TOBRUK, "tobruk")) + if (is_mandatory_combat(TOBRUK)) return goto_rout(TOBRUK, false, goto_combat_phase) } else { - if (is_mandatory_combat(BARDIA, "bardia")) + if (is_mandatory_combat(BARDIA)) set_add(game.active_battles, BARDIA) - if (is_mandatory_combat(BENGHAZI, "benghazi")) + if (is_mandatory_combat(BENGHAZI)) set_add(game.active_battles, BENGHAZI) - if (is_mandatory_combat(TOBRUK, "tobruk")) + if (is_mandatory_combat(TOBRUK)) set_add(game.active_battles, TOBRUK) } @@ -3615,11 +3670,78 @@ function end_rout_fire() { // === BUILD-UP === function end_month() { - delete game.bardia_captured - delete game.benghazi_captured - delete game.tobruk_captured - // TODO: check end game and victory - throw new Error("end month not done yet") + // Forget captured fortresses (for bonus cards) + clear_fortresses_captured() + + if (game.month === SCENARIOS[game.scenario].end) + return end_game() + + goto_buildup() +} + +// === VICTORY CHECK === + +const EXIT_EAST_EDGE = [ 99, 123, 148 ] +const EXIT_EAST_STASH = 49 + +function check_sudden_death_victory() { + // Supplied units that move beyond the map "edge" exit the map. + // Count the easternmost row of hexes and half-hexes. + // In the original map this would be the half-hexes and the virtual hexes beyond the edge. + for (let x of EXIT_EAST_EDGE) { + for_each_axis_unit(u => { + if (unit_hex(u) === x && is_unit_supplied(u)) { + log(`Exited the east map edge.`) + set_unit_hex(u, EXIT_EAST_STASH) + } + }) + } + + let axis_exited = 0 + for_each_axis_unit(u => { + if (unit_hex(u) === EXIT_EAST_STASH) + axis_exited++ + }) + + if (is_axis_hex(ALEXANDRIA) || axis_exited >= 3) + return goto_game_over(ALLIED, "Allied Strategic Victory!") + if (is_allied_hex(EL_AGHEILA)) + return goto_game_over(AXIS, "Axis Strategic Victory!") + + return false +} + +function end_game() { + let axis = 0 + for_each_axis_unit(u => { + if (is_map_hex(unit_hex(u))) + axis += is_german_unit(u) ? 1.5 : 1.0 + }) + + let allied = 0 + for_each_allied_unit(u => { + if (is_map_hex(unit_hex(u))) + allied += 1.0 + }) + + if (axis >= allied * 2) + return goto_game_over(AXIS, "Axis Decisive Victory!") + if (allied >= axis * 2) + return goto_game_over(AXIS, "Allied Decisive Victory!") + + if (!is_fortress_besieged(TOBRUK)) { + if (is_fortress_axis_controlled(TOBRUK)) + return goto_game_over(AXIS, "Axis Positional Victory!") + else + return goto_game_over(ALLIED, "Allied Positional Victory!") + } + + if (axis > allied) + return goto_game_over(AXIS, "Axis Attrition Victory!") + if (allied > axis) + return goto_game_over(ALLIED, "Allied Attrition Victory!") + + return goto_game_over("Draw", "No Victory!") } // === DEPLOYMENT === @@ -4159,10 +4281,9 @@ const SETUP = { function setup_fortress(scenario, fortress) { if (scenario.allied_deployment.includes(fortress)) - return ALLIED + set_fortress_allied_controlled(fortress) if (scenario.axis_deployment.includes(fortress)) - return AXIS - throw new Error("invalid setup") + set_fortress_axis_controlled(fortress) } function setup(name) { @@ -4173,9 +4294,9 @@ function setup(name) { SETUP[name](-scenario.start) - game.bardia = setup_fortress(scenario, BARDIA) - game.benghazi = setup_fortress(scenario, BENGHAZI) - game.tobruk = setup_fortress(scenario, TOBRUK) + setup_fortress(scenario, BARDIA) + setup_fortress(scenario, BENGHAZI) + setup_fortress(scenario, TOBRUK) log_h2("Axis Deployment") game.phasing = AXIS @@ -4218,9 +4339,7 @@ exports.setup = function (seed, scenario, options) { revealed_minefields: [], // fortress control - bardia: ALLIED, - benghazi: ALLIED, - tobruk: ALLIED, + fortress: 7, // battle hexes (defender) axis_hexes: [], @@ -4274,6 +4393,7 @@ exports.view = function(state, current) { month: game.month, units: game.units, moved: game.moved, + fortress: game.fortress, axis_hand: game.axis_hand[0] + game.axis_hand[1], allied_hand: game.allied_hand[0] + game.allied_hand[1], commit: game.commit[0] + game.commit[1], @@ -4507,6 +4627,7 @@ function goto_game_over(result, victory) { game.victory = victory log_br() log(game.victory) + return true } states.game_over = { @@ -4548,7 +4669,9 @@ exports.action = function (state, current, action, arg) { function common_view(current) { view.log = game.log - if (current === 'Observer' || game.active !== current) { + if (game.state === 'game_over') { + view.prompt = game.victory + } else if (current === 'Observer' || game.active !== current) { let inactive = states[game.state].inactive || game.state view.prompt = `Waiting for ${game.active} \u2014 ${inactive}...` } else { -- cgit v1.2.3