summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-07-14 20:29:37 +0200
committerTor Andersson <tor@ccxvii.net>2022-11-17 13:11:25 +0100
commit107df9df212cc7d67791618e1b8c34ebbf97aeaa (patch)
tree69ae83ffe901ab01621cee4a8816aa98eedebedd
parent397bea71ffd9f30180bf6e446740e8a5e06d3dc6 (diff)
downloadrommel-in-the-desert-107df9df212cc7d67791618e1b8c34ebbf97aeaa.tar.gz
pursuit fire
-rw-r--r--play.html96
-rw-r--r--play.js52
-rw-r--r--rules.js199
3 files changed, 278 insertions, 69 deletions
diff --git a/play.html b/play.html
index dedbca5..63b1fe6 100644
--- a/play.html
+++ b/play.html
@@ -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">
diff --git a/play.js b/play.js
index dd7874e..63c0a91 100644
--- a/play.js
+++ b/play.js
@@ -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")
diff --git a/rules.js b/rules.js
index 4bdecee..6df7316 100644
--- a/rules.js
+++ b/rules.js
@@ -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)
}