diff options
-rw-r--r-- | play.html | 96 | ||||
-rw-r--r-- | play.js | 52 | ||||
-rw-r--r-- | rules.js | 199 |
3 files changed, 278 insertions, 69 deletions
@@ -105,6 +105,48 @@ header.your_turn { background-color: orange; } border-top: 1px solid black; } +/* PURSUIT FIRE DIALOG */ + +#pursuit { background-color: #d6c4a9; background: url(texture_mountain.png); } +#pursuit_header { background-color: brown; color: gold } +#pursuit_hits { background-color: #c4ab8b; } +#pursuit_message { background-color: #d6c4a9; } + +#pursuit { + position: fixed; + min-width: 524px; /* 6 blocks wide */ + left: 12px; + top: 56px; + z-index: 100; + box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.5); + border: 1px solid black; + user-select: none; +} + +#pursuit_header { + cursor: move; + padding: 2px 8px; + line-height: 24px; + min-height: 24px; + text-align: center; + font-weight: bold; + border-bottom: 1px solid black; +} + +#pursuit_hits { + padding: 4px; + text-align: center; + border-bottom: 1px solid black; +} + +#pursuit_message { + padding: 2px 8px; + line-height: 24px; + min-height: 24px; + text-align: center; + border-top: 1px solid black; +} + /* TABLES */ table { border-collapse: collapse; font-size: 12px; } @@ -435,30 +477,38 @@ svg .hex.allied_control { </head> <body> - <div id="battle" class="hide"> - <div id="battle_header"></div> - <div id="battle_hits"> - <img class="hits_icon" src="icons/armor.svg"> - <div class="hits_text" id="hits_armor">0</div> - <img class="hits_icon" src="icons/infantry.svg"> - <div class="hits_text" id="hits_infantry">0</div> - <img class="hits_icon" src="icons/motorized_antitank_old.svg"> - <div class="hits_text" id="hits_antitank">0</div> - <img class="hits_icon" src="icons/artillery.svg"> - <div class="hits_text" id="hits_artillery">0</div> - </div> - <div class="battle_line" id="battle_line_1"></div> - <div class="battle_line" id="battle_line_2"></div> - <div class="battle_line" id="battle_line_3"></div> - <div class="battle_line" id="battle_line_4"></div> - <div id="battle_buttons"> - <button id="target_armor_button" onclick="send_action('armor')">Armor</button> - <button id="target_infantry_button" onclick="send_action('infantry')">Infantry</button> - <button id="target_antitank_button" onclick="send_action('antitank')">Anti-tank</button> - <button id="target_artillery_button" onclick="send_action('artillery')">Artillery</button> - </div> - <div id="battle_message"></div> +<div id="battle" class="hide"> + <div id="battle_header"></div> + <div id="battle_hits"> + <img class="hits_icon" src="icons/armor.svg"> + <div class="hits_text" id="hits_armor">0</div> + <img class="hits_icon" src="icons/infantry.svg"> + <div class="hits_text" id="hits_infantry">0</div> + <img class="hits_icon" src="icons/motorized_antitank_old.svg"> + <div class="hits_text" id="hits_antitank">0</div> + <img class="hits_icon" src="icons/artillery.svg"> + <div class="hits_text" id="hits_artillery">0</div> </div> + <div class="battle_line" id="battle_line_1"></div> + <div class="battle_line" id="battle_line_2"></div> + <div class="battle_line" id="battle_line_3"></div> + <div class="battle_line" id="battle_line_4"></div> + <div id="battle_buttons"> + <button id="target_armor_button" onclick="send_action('armor')">Armor</button> + <button id="target_infantry_button" onclick="send_action('infantry')">Infantry</button> + <button id="target_antitank_button" onclick="send_action('antitank')">Anti-tank</button> + <button id="target_artillery_button" onclick="send_action('artillery')">Artillery</button> + </div> + <div id="battle_message"></div> +</div> + +<div id="pursuit" class="hide"> + <div id="pursuit_header">Pursuit Fire: $NAME</div> + <div id="pursuit_hits">0 hits</div> + <div class="battle_line" id="pursuit_line_1"></div> + <div class="battle_line" id="pursuit_line_2"></div> + <div id="pursuit_message"></div> +</div> <header> <div id="toolbar"> @@ -38,6 +38,7 @@ let ui = { hex_y: [], units: [], battle_units: [], + battle: document.getElementById("battle"), battle_hits: [ document.getElementById("hits_armor"), @@ -57,6 +58,14 @@ let ui = { battle_line_2: document.getElementById("battle_line_2"), battle_line_3: document.getElementById("battle_line_3"), battle_line_4: document.getElementById("battle_line_4"), + + pursuit: document.getElementById("pursuit"), + pursuit_hits: document.getElementById("pursuit_hits"), + pursuit_header: document.getElementById("pursuit_header"), + pursuit_message: document.getElementById("pursuit_message"), + pursuit_line_1: document.getElementById("pursuit_line_1"), + pursuit_line_2: document.getElementById("pursuit_line_2"), + onmap: document.getElementById("units"), focus: null, } @@ -446,10 +455,10 @@ function update_map() { } } -function update_battle_line(line, test) { +function update_battle_line(hex, line, test) { for (let u = 0; u < units.length; ++u) { let e = ui.battle_units[u] - if (unit_hex(u) === view.battle && test(u)) { + if (unit_hex(u) === hex && test(u)) { if (!line.contains(e)) line.appendChild(e) @@ -475,15 +484,15 @@ function update_battle() { ui.battle_header.textContent = hex_name[view.battle] ui.battle_message.textContent = view.flash if (player === ALLIED) { - update_battle_line(ui.battle_line_1, u => is_axis_unit(u) && is_artillery_unit(u)) - update_battle_line(ui.battle_line_2, u => is_axis_unit(u) && !is_artillery_unit(u)) - update_battle_line(ui.battle_line_3, u => is_allied_unit(u) && !is_artillery_unit(u)) - update_battle_line(ui.battle_line_4, u => is_allied_unit(u) && is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_1, u => is_axis_unit(u) && is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_2, u => is_axis_unit(u) && !is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_3, u => is_allied_unit(u) && !is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_4, u => is_allied_unit(u) && is_artillery_unit(u)) } else { - update_battle_line(ui.battle_line_1, u => is_allied_unit(u) && is_artillery_unit(u)) - update_battle_line(ui.battle_line_2, u => is_allied_unit(u) && !is_artillery_unit(u)) - update_battle_line(ui.battle_line_3, u => is_axis_unit(u) && !is_artillery_unit(u)) - update_battle_line(ui.battle_line_4, u => is_axis_unit(u) && is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_1, u => is_allied_unit(u) && is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_2, u => is_allied_unit(u) && !is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_3, u => is_axis_unit(u) && !is_artillery_unit(u)) + update_battle_line(view.battle, ui.battle_line_4, u => is_axis_unit(u) && is_artillery_unit(u)) } target_button("armor") target_button("infantry") @@ -493,6 +502,24 @@ function update_battle() { ui.battle_hits[i].textContent = view.hits[i] } +function update_pursuit() { + ui.pursuit.classList.remove("hide") + ui.pursuit_header.textContent = "Pursuit Fire at " + hex_name[view.pursuit] + ui.pursuit_message.textContent = view.flash + if (player === ALLIED) { + let slowest = + update_battle_line(view.pursuit, ui.pursuit_line_1, u => is_axis_unit(u)) + update_battle_line(view.pursuit, ui.pursuit_line_2, u => is_allied_unit(u)) + } else { + update_battle_line(view.pursuit, ui.pursuit_line_1, u => is_allied_unit(u)) + update_battle_line(view.pursuit, ui.pursuit_line_2, u => is_axis_unit(u)) + } + if (view.hits === 1) + ui.pursuit_hits.textContent = view.hits + " hit" + else + ui.pursuit_hits.textContent = view.hits + " hits" +} + function target_button(action) { let button = document.getElementById("target_" + action + "_button") if (view.actions) { @@ -514,6 +541,11 @@ function on_update() { else ui.battle.classList.add("hide") + if (view.pursuit) + update_pursuit() + else + ui.pursuit.classList.add("hide") + action_button("overrun", "Overrun") action_button("rommel", "Rommel") action_button("stop", "Stop") @@ -1471,11 +1471,6 @@ function stop_move(who) { game.state = 'move_who' } -// === PURSUIT FIRE === - -function goto_pursuit_fire() { -} - // === REFUSE BATTLE === function gen_withdraw_group_move(who, from) { @@ -1485,6 +1480,7 @@ function gen_withdraw_group_move(who, from) { } function goto_refuse_battle() { + clear_undo() if (game.active_battles.length > 0) { set_passive_player() game.state = 'refuse_battle' @@ -1504,13 +1500,10 @@ states.refuse_battle = { hex(x) { push_undo() set_delete(game.active_battles, x) - game.battle = x - game.from1 = x + game.pursuit = x goto_pursuit_fire(x) - } + }, next() { - clear_undo() - set_active_player() goto_combat_phase() } } @@ -1561,6 +1554,8 @@ states.refuse_battle_to = { // ==== COMBAT PHASE === function goto_combat_phase() { + clear_undo() + set_active_player() game.state = 'select_active_battles' } @@ -1667,7 +1662,7 @@ const xxx_fire = { }, } -function count_normal_steps() { +function count_normal_steps_in_battle() { let steps = [ 0, 0, 0, 0 ] for (let u = 0; u < units.length; ++u) if (is_enemy_unit(u) && unit_hex(u) === game.battle) @@ -1675,7 +1670,7 @@ function count_normal_steps() { return steps } -function count_elite_steps() { +function count_elite_steps_in_battle() { let steps = [ 0, 0, 0, 0 ] for (let u = 0; u < units.length; ++u) if (is_enemy_unit(u) && unit_hex(u) === game.battle) @@ -1683,7 +1678,7 @@ function count_elite_steps() { return steps } -function count_hp() { +function count_hp_in_battle() { let hp = [ 0, 0, 0, 0 ] for (let u = 0; u < units.length; ++u) if (is_enemy_unit(u) && unit_hex(u) === game.battle) @@ -1691,11 +1686,35 @@ function count_hp() { return hp } +function count_normal_steps_in_pursuit() { + let steps = 0 + for (let u = 0; u < units.length; ++u) + if (is_enemy_unit(u) && unit_hex(u) === game.pursuit) + steps += unit_steps(u) + return steps +} + +function count_elite_steps_in_pursuit() { + let steps = 0 + for (let u = 0; u < units.length; ++u) + if (is_enemy_unit(u) && unit_hex(u) === game.pursuit) + steps += unit_steps(u) + return steps +} + +function count_hp_in_pursuit() { + let hp = 0 + for (let u = 0; u < units.length; ++u) + if (is_enemy_unit(u) && unit_hex(u) === game.pursuit) + hp += unit_hp(u) + return hp +} + const xxx_fire_target = { prompt() { view.prompt = `Select a target class.` - let hp = count_hp() + let hp = count_hp_in_battle() for (let i = 0; i < 4; ++i) hp[i] -= game.hits[i] @@ -1760,32 +1779,30 @@ const xxx_fire_hits = { prompt() { view.prompt = `Apply hits.` - let normal_steps = count_normal_steps() - let elite_steps = count_elite_steps() + let normal_steps = count_normal_steps_in_battle() + let elite_steps = count_elite_steps_in_battle() let done = true - for (let u = 0; u < units.length; ++u) { - if (is_friendly_unit(u) && unit_hex(u) === game.battle) { - let c = unit_class(u) - if (is_unit_elite(u)) { - if (game.hits[c] >= 2) { + for_each_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 { 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 - } - } } } - } + }) if (done) gen_action_next() }, @@ -1875,6 +1892,113 @@ function end_combat_phase() { end_player_turn() } +// === PURSUIT FIRE === + +function goto_pursuit_fire() { + clear_undo() + set_active_player() + game.state = 'pursuit_fire' + game.hits = 0 +} + +function slowest_enemy_unit_speed(where) { + let r = 4 + for (let u = 0; u < units.length; ++u) { + if (is_enemy_unit(u) && unit_hex(u) === where) { + let s = unit_speed(u) + if (s < r) + r = s + } + } + return r +} + +function roll_pursuit_fire(n) { + for (let i = 0; i < n; ++i) { + let roll = random(6) + 1 + log(`Pursuit fire ${roll}.`) + if (roll >= 4) + game.hits++ + } +} + +states.pursuit_fire = { + inactive: "pursuit fire (fire)", + prompt() { + view.prompt = `Pursuit Fire.` + let slowest = slowest_enemy_unit_speed(game.pursuit) + for_each_friendly_unit_in_hex(game.pursuit, u => { + if (unit_speed(u) >= slowest && !is_unit_fired(u)) + gen_action_unit(u) + }) + gen_action('next') + }, + unit(who) { + let slowest = slowest_enemy_unit_speed(game.pursuit) + if (unit_speed(who) > slowest) + roll_pursuit_fire(2) + else + roll_pursuit_fire(1) + set_unit_fired(who) + }, + next() { + goto_pursuit_hits() + } +} + +function goto_pursuit_hits() { + if (game.hits > 0) { + set_passive_player() + let hp = count_hp_in_pursuit() + if (game.hits > hp) + game.hits = hp + game.state = 'pursuit_hits' + } else { + end_pursuit_fire() + } +} + +states.pursuit_hits = { + inactive: "pursuit fire (hits)", + prompt() { + view.prompt = `Pursuit Fire: Apply ${game.hits} hits.` + + let normal_steps = count_normal_steps_in_pursuit() + let elite_steps = count_elite_steps_in_pursuit() + + let done = true + for_each_friendly_unit_in_hex(game.pursuit, u => { + if (is_unit_elite(u)) { + if (game.hits >= 2) { + gen_action_unit(u) + done = false + } + } else { + if (game.hits >= 1) { + // If mixed elite and non-elite: must assign ALL damage. + if (elite_steps > 0 && normal_steps === 1 && (game.hits & 1) === 0) { + // Eliminating the last non-elite must not leave an odd + // number of hits remaining. + } else { + gen_action_unit(u) + done = false + } + } + } + }) + if (done) + gen_action('next') + }, + unit(who) { + push_undo() + game.hits -= reduce_unit(who) + }, + next() { + clear_undo() + end_pursuit_fire() + }, +} + // === DEPLOYMENT === states.free_deployment = { @@ -2454,8 +2578,10 @@ exports.setup = function (seed, scenario, options) { move_road: 4, // combat + partial_retreats: [], // remember partial retreats to forbid initiating combat active_battles: [], assault_battles: [], + pursuit: 0, battle: 0, fired: [], hits: null, @@ -2492,10 +2618,11 @@ exports.view = function(state, current) { if (game.to1) view.to1 = game.to1 if (game.to2) view.to2 = game.to2 + if (game.pursuit) view.pursuit = game.pursuit if (game.battle) view.battle = game.battle - if (game.fired) view.fired = game.fired - if (game.hits) view.hits = game.hits - if (game.flash) view.flash = game.flash + if (game.fired !== undefined) view.fired = game.fired + if (game.hits !== undefined) view.hits = game.hits + if (game.flash !== undefined) view.flash = game.flash return common_view(current) } |