diff options
author | Tor Andersson <tor@ccxvii.net> | 2022-07-29 23:56:00 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2022-11-17 13:11:26 +0100 |
commit | 27e1c173919d3d1506ced0350df576781dec491f (patch) | |
tree | cc4c232e174e37ce5b7453aaa50ba633b2da6383 | |
parent | 1ec5d762368bfca6e51e83e100c7de2138fae0e8 (diff) | |
download | rommel-in-the-desert-27e1c173919d3d1506ced0350df576781dec491f.tar.gz |
Hide/reveal units. Pursuit Deception.
-rw-r--r-- | card_dummy.svg | 2 | ||||
-rw-r--r-- | card_real.svg | 4 | ||||
-rw-r--r-- | play.html | 32 | ||||
-rw-r--r-- | play.js | 15 | ||||
-rw-r--r-- | rules.js | 233 |
5 files changed, 174 insertions, 112 deletions
diff --git a/card_dummy.svg b/card_dummy.svg index f14e5fb..0a163cc 100644 --- a/card_dummy.svg +++ b/card_dummy.svg @@ -1,3 +1,3 @@ <svg xmlns="http://www.w3.org/2000/svg" width="124" height="170" version="1.2" viewBox="0 0 44 60"> -<circle cx="22" cy="30" r="15" stroke-width=".5" fill="none" stroke="black"/> +<circle cx="22" cy="30" r="15" stroke-width=".5" fill="none" stroke="#222"/> </svg> diff --git a/card_real.svg b/card_real.svg index d5e6377..9e11b27 100644 --- a/card_real.svg +++ b/card_real.svg @@ -2,6 +2,6 @@ <clipPath id="half"> <rect x="22" y="0" width="22" height="60" /> </clipPath> -<circle cx="22" cy="30" r="15" fill="black" clip-path="url(#half)" /> -<circle cx="22" cy="30" r="15" stroke-width=".5" fill="none" stroke="black"/> +<circle cx="22" cy="30" r="15" fill="#222" clip-path="url(#half)" /> +<circle cx="22" cy="30" r="15" stroke-width=".5" fill="none" stroke="#222"/> </svg> @@ -365,33 +365,41 @@ svg .hex.tip { } .unit { - background-image: url(units.svg); background-size: 1000%; border-style: solid; transition: top 200ms, left 200ms, transform 200ms; } -#mapwrap.fit .unit { +.unit.revealed { + background-image: url(units.svg); +} + +#mapwrap.fit .unit.revealed { background-image: url(units-simple.svg); } -.unit.italian { - background-color: #f9e3b3; - border-color: dimgray; +.unit.italian, .unit.german { + background-color: #9aa880; + border-color: #899572; box-shadow: 0 0 2px 1px #2d292c80; } -.unit.german { +.unit.allied { + background-color: #cebc9a; + border-color: #b7a889; + box-shadow: 0 0 2px 1px #5c3a1e80; +} + +.unit.italian.revealed { + background-color: #f9e3b3; +} + +.unit.german.revealed { background-color: #abba8e; - border-color: dimgray; - box-shadow: 0 0 2px 1px #004e2f; - box-shadow: 0 0 2px 1px #2d292c80; } -.unit.allied { +.unit.allied.revealed { background-color: #e4d1ab; - border-color: tan; - box-shadow: 0 0 2px 1px #5c3a1e80; } .unit.action { @@ -179,6 +179,15 @@ function is_unit_fired(u) { return set_has(view.fired, u) } +function is_unit_revealed(u) { + if (player === AXIS) + return is_axis_unit(u) || set_has(view.revealed, u) + else if (player === ALLIED) + return is_allied_unit(u) || set_has(view.revealed, u) + else + return set_has(view.revealed, u) +} + function is_unit_action(unit) { return !!(view.actions && view.actions.unit && view.actions.unit.includes(unit)) } @@ -653,7 +662,8 @@ function update_map() { e.classList.toggle("selected", !view.battle && is_unit_selected(u)) e.classList.toggle("disrupted", is_unit_disrupted(u)) e.classList.toggle("moved", is_unit_moved(u)) - // e.classList.toggle("unsupplied", !is_unit_supplied(u)) + // e.classList.toggle("unsupplied", is_unit_unsupplied(u)) + e.classList.toggle("revealed", is_unit_revealed(u)) } if (ui.hexes[hex]) { @@ -700,6 +710,7 @@ function update_battle_line(hex, line, test) { e.classList.toggle("selected", is_unit_selected(u)) e.classList.toggle("disrupted", is_unit_disrupted(u)) e.classList.toggle("fire", is_unit_fired(u)) + e.classList.toggle("revealed", is_unit_revealed(u)) } else { if (line.contains(e)) line.removeChild(e) @@ -818,7 +829,9 @@ function on_update() { action_button("pass", "Pass") action_button("next", "Next") + action_button("withhold", "Withhold") action_button("end_move", "End move") + action_button("end_fire", "End fire") action_button("end_rout", "End rout") action_button("end_retreat", "End retreat") action_button("end_combat", "End combat") @@ -1,7 +1,5 @@ "use strict" -// TOOD: reveal/hide blocks (hexes) - // TODO: fortress supply // TODO: oasis supply @@ -17,6 +15,7 @@ // RULES: for sea redeployment, can bases be "besieged"? (yes) // RULES: may units redeploying out of battle hex leave disrupted units behind to be routed? (no) // RULES: may units redeploying out of battle hex cross enemy controlled hexsides? (yes / doesn't matter) +// RULES: may units returning for refit enter enemy supply network? (no) // RULES: if disrupted units are routed again during their "full enemy turn", can they still recover? // RULES: may oasis supplied units refuse battle or withdraw to base? @@ -38,9 +37,9 @@ var view = null var after_rout_table = {} -const { +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, + unit_name, unit_appearance, unit_elite, unit_class, unit_speed, unit_start_steps, } = require("./data") function debug_hexes3(n, list) { @@ -412,6 +411,7 @@ function set_unit_fired(u) { function eliminate_unit(u) { set_unit_hex(u, 0) set_unit_lost_steps(u, 0) + hide_unit(u) } function reduce_unit(u) { @@ -429,6 +429,22 @@ function replace_unit(u) { set_unit_lost_steps(u, lost - 1) } +function reveal_units_in_hex(x) { + for (let u = 0; u < unit_count; ++u) + if (unit_hex(u) === x) + set_add(game.revealed, u) +} + +function hide_units_in_hex(x) { + for (let u = 0; u < unit_count; ++u) + if (unit_hex(u) === x) + set_delete(game.revealed, u) +} + +function hide_unit(u) { + set_delete(game.revealed, u) +} + // === UNIT DATA === function find_unit(name) { @@ -892,11 +908,14 @@ function for_each_undisrupted_enemy_unit_in_hex(x, fn) { fn(u) } -function count_battle_hexes() { +function count_and_reveal_battle_hexes() { let n = 0 - for (let x of all_hexes) - if (is_battle_hex(x)) + for (let x of all_hexes) { + if (is_battle_hex(x)) { + reveal_units_in_hex(x) ++n + } + } return n } @@ -1371,10 +1390,10 @@ function search_withdraw_retreat(who, bonus) { } function search_redeploy(start) { - search_path_redeploy_bfs(path_cost[0], start, 0, 100) - search_path_redeploy_bfs(path_cost[1], start, 1, 100) - search_path_redeploy_bfs(path_cost[2], start, 2, 100) - search_path_redeploy_bfs(path_cost[4], start, 4, 100) + search_path_redeploy_bfs(path_cost[0], start, 0) + search_path_redeploy_bfs(path_cost[1], start, 1) + search_path_redeploy_bfs(path_cost[2], start, 2) + search_path_redeploy_bfs(path_cost[4], start, 4) } // Breadth First Search @@ -2726,6 +2745,8 @@ states.retreat_who = { retreat() { clear_undo() let full_retreat = true + for (let u of game.retreat_units) + hide_unit(u) for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => { if (!set_has(game.retreat_units, u)) full_retreat = false @@ -2831,8 +2852,10 @@ states.retreat_move = { } function end_retreat() { - if (!is_battle_hex(game.retreat)) + if (!is_battle_hex(game.retreat)) { release_hex_control(game.retreat) + hide_units_in_hex(game.retreat) + } game.retreat_units = null // no shielding units remain @@ -3114,7 +3137,7 @@ function goto_combat_phase() { set_add(game.active_battles, TOBRUK) } - let n = count_battle_hexes() + let n = count_and_reveal_battle_hexes() if (n > 0) { if (n > game.active_battles.length) return game.state = 'select_active_battles' @@ -3306,6 +3329,11 @@ function goto_battle(x) { } function end_battle() { + if (!is_battle_hex(game.battle)) { + release_hex_control(game.battle) + hide_units_in_hex(game.battle) + } + set_delete(game.active_battles, game.battle) set_delete(game.assault_battles, game.battle) @@ -3489,9 +3517,9 @@ states.battle_fire = { states.battle_hits = { prompt() { if (game.active === game.phasing) - view.prompt = `Battle: Apply hits from Defensive Fire.` + view.prompt = `Battle: ${format_allocate_hits()} from Defensive Fire.` else - view.prompt = `Battle: Apply hits from Offensive Fire.` + view.prompt = `Battle: ${format_allocate_hits()} from Offensive Fire.` gen_battle_hits() }, unit(who) { @@ -3547,9 +3575,9 @@ states.probe_fire = { states.probe_hits = { prompt() { if (game.active !== game.phasing) - view.prompt = `Probe: Apply hits from Defensive Fire.` + view.prompt = `Probe: ${format_allocate_hits()} from Defensive Fire.` else - view.prompt = `Probe: Apply hits from Offensive Fire.` + view.prompt = `Probe: ${format_allocate_hits()} from Offensive Fire.` gen_battle_hits() }, unit(who) { @@ -3589,38 +3617,70 @@ function end_probe_hits() { // routing apply hits // routing moves +function slowest_enemy_unit_speed(where) { + let r = 4 + for_each_enemy_unit_in_hex(where, u => { + let s = unit_speed[u] + if (s < r) + r = s + }) + return r +} + +function slowest_undisrupted_enemy_unit_speed(where) { + let r = 4 + for_each_undisrupted_enemy_unit_in_hex(where, u => { + let s = unit_speed[u] + if (s < r) + r = s + }) + return r +} + function goto_rout_fire(where) { set_enemy_player() game.hits = 0 game.pursuit = where - if (can_rout_fire(true)) - game.state = 'rout_fire' - else - goto_rout_hits() + let slowest = slowest_enemy_unit_speed(game.pursuit) + log(`Slowest was ${speed_name[slowest]} unit.`) + game.state = 'rout_fire' } function goto_pursuit_fire_during_retreat(where) { set_passive_player() game.hits = 0 game.pursuit = where - if (can_pursuit_fire(true)) - game.state = 'pursuit_fire' - else - end_pursuit_fire() + let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) + log(`Slowest was ${speed_name[slowest]} unit.`) + game.state = 'pursuit_fire' } function goto_pursuit_fire_during_refuse_battle(where) { set_active_player() game.hits = 0 game.pursuit = where - if (can_pursuit_fire(true)) - game.state = 'pursuit_fire' + let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) + log(`Slowest was ${speed_name[slowest]} unit.`) + game.state = 'pursuit_fire' +} + +function format_allocate_hits() { + let hits = 0 + if (typeof game.hits === 'number') + hits = game.hits else - end_pursuit_fire() + hits = game.hits[1] + game.hits[2] + game.hits[3] + if (hits === 0) + return `Allocate zero hits` + else if (hits === 1) + return `Allocate 1 hit` + else + return `Allocate ${hits} hits` } function goto_rout_hits() { set_enemy_player() + game.flash = "" if (game.hits > 0) game.state = 'rout_hits' else @@ -3629,57 +3689,13 @@ function goto_rout_hits() { function goto_pursuit_hits() { set_enemy_player() - // XXX if (true) + game.flash = "" if (game.hits > 0) game.state = 'pursuit_hits' else end_pursuit_fire() } -function slowest_enemy_unit_speed(where) { - let r = 4 - for_each_enemy_unit_in_hex(where, u => { - let s = unit_speed[u] - if (s < r) - r = s - }) - return r -} - -function slowest_undisrupted_enemy_unit_speed(where) { - let r = 4 - for_each_undisrupted_enemy_unit_in_hex(where, u => { - let s = unit_speed[u] - if (s < r) - r = s - }) - return r -} - -function can_rout_fire(verbose) { - let result = false - let slowest = slowest_enemy_unit_speed(game.pursuit) - 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)) - result = true - }) - return result -} - -function can_pursuit_fire(verbose) { - let result = false - let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) - 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)) - result = true - }) - return result -} - function roll_pursuit_fire_imp(who, n, hp) { let speed = unit_speed[who] if (n === 2) { @@ -3727,43 +3743,63 @@ states.pursuit_fire = { inactive: "pursuit fire (fire)", prompt() { 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)) - gen_action_unit(u) - }) - // allow saving fire if there are shielded enemy units - if (has_disrupted_enemy_unit(game.pursuit)) - gen_action('next') + let done = true + if (game.hits < count_hp_in_pursuit()) { + 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)) { + gen_action_unit(u) + done = false + } + }) + } + if (done) + gen_action('end_fire') + else + gen_action('withhold') }, unit(who) { let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit) + roll_pursuit_fire(who, (unit_speed[who] > slowest ? 2 : 1)) set_unit_fired(who) - let done = roll_pursuit_fire(who, (unit_speed[who] > slowest ? 2 : 1)) - if (done || !can_pursuit_fire(false)) - goto_pursuit_hits() }, - next() { + withhold() { goto_pursuit_hits() - } + }, + end_fire() { + goto_pursuit_hits() + }, } states.rout_fire = { inactive: "rout fire (fire)", prompt() { 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)) - gen_action_unit(u) - }) + let done = true + if (game.hits < count_hp_in_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)) { + gen_action_unit(u) + done = false + } + }) + } + if (done) + gen_action('end_fire') + else + gen_action('withhold') }, unit(who) { let slowest = slowest_enemy_unit_speed(game.pursuit) + roll_rout_fire(who, (unit_speed[who] > slowest ? 2 : 1)) set_unit_fired(who) - let done = roll_rout_fire(who, (unit_speed[who] > slowest ? 2 : 1)) - if (done || !can_rout_fire(false)) - goto_rout_hits() + }, + withhold() { + goto_rout_hits() + }, + end_fire() { + goto_rout_hits() }, } @@ -3795,7 +3831,7 @@ function gen_pursuit_hits(normal_steps, elite_steps, iterate) { states.pursuit_hits = { inactive: "pursuit fire (hits)", prompt() { - view.prompt = `Pursuit Fire: Apply ${game.hits} hits.` + view.prompt = "Pursuit Fire: " + format_allocate_hits() + "." let normal_steps = count_normal_steps_in_pursuit() let elite_steps = count_elite_steps_in_pursuit() gen_pursuit_hits(normal_steps, elite_steps, for_each_undisrupted_friendly_unit_in_hex) @@ -3813,7 +3849,7 @@ states.pursuit_hits = { states.rout_hits = { inactive: "rout fire (hits)", prompt() { - view.prompt = `Pursuit Fire (Rout): Apply ${game.hits} hits.` + view.prompt = "Pursuit Fire: " + format_allocate_hits() + "." let normal_steps = count_normal_steps_in_rout() let elite_steps = count_elite_steps_in_rout() gen_pursuit_hits(normal_steps, elite_steps, for_each_friendly_unit_in_hex) @@ -4270,6 +4306,7 @@ states.spending_bps = { }, refit() { log(`Returned for Refit.`) + hide_unit(game.selected) set_unit_hex(pop_selected(), friendly_refit()) }, hex(to) { @@ -4279,6 +4316,7 @@ states.spending_bps = { game.selected = -1 } else if (to === friendly_refit()) { log(`Returned for Refit.`) + hide_unit(game.selected) set_unit_hex(pop_selected(), friendly_refit()) } else { search_redeploy(from) @@ -4299,6 +4337,7 @@ states.spending_bps = { game.buildup.tobruk-- } set_unit_hex(who, to) + hide_unit(who) } }, end_buildup() { @@ -5090,6 +5129,7 @@ exports.setup = function (seed, scenario, options) { allied_bps: 0, units: new Array(unit_count).fill(0), + revealed: [], moved: [], fired: [], recover: [], @@ -5154,6 +5194,7 @@ exports.view = function(state, current) { view = { month: game.month, units: game.units, + revealed: game.revealed, moved: game.moved, fortress: game.fortress, axis_hand: game.axis_hand[REAL] + game.axis_hand[DUMMY], |