summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--card_dummy.svg2
-rw-r--r--card_real.svg4
-rw-r--r--play.html32
-rw-r--r--play.js15
-rw-r--r--rules.js233
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>
diff --git a/play.html b/play.html
index b1162c6..40b45a0 100644
--- a/play.html
+++ b/play.html
@@ -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 {
diff --git a/play.js b/play.js
index 6f9ba86..7df8968 100644
--- a/play.js
+++ b/play.js
@@ -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")
diff --git a/rules.js b/rules.js
index 1a0f83c..ee617f3 100644
--- a/rules.js
+++ b/rules.js
@@ -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],