summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.js2
-rw-r--r--rules.js294
2 files changed, 238 insertions, 58 deletions
diff --git a/play.js b/play.js
index 96b12a8..c11217b 100644
--- a/play.js
+++ b/play.js
@@ -629,6 +629,7 @@ function on_update() {
action_button("overrun", "Overrun")
action_button("rommel", "Rommel")
+ action_button("eliminate", "Eliminate")
action_button("retreat", "Retreat")
action_button("probe", "Probe")
@@ -643,6 +644,7 @@ function on_update() {
action_button("next", "Next")
action_button("end_move", "End move")
+ action_button("end_rout", "End rout")
action_button("end_retreat", "End retreat")
action_button("end_combat", "End combat")
action_button("end_turn", "End turn")
diff --git a/rules.js b/rules.js
index 312faaa..e0745b2 100644
--- a/rules.js
+++ b/rules.js
@@ -1656,39 +1656,29 @@ function apply_move(to) {
push_undo()
+ console.log("apply_move", hex_name[to])
+
if (has_enemy_unit(to)) {
// TODO: pick hex-side manually when engaging
}
search_move(from, max(speed + rommel1, speed + rommel2))
- if (!game.to1 && game.from1) {
- if (from === game.from1) {
- for (let to of all_hexes)
- if (to != from && can_move_to(to, speed + rommel1))
- return move_unit(who, to, speed + rommel1)
- }
- }
+ if (!game.to1 && game.from1 === from)
+ if (can_move_to(to, speed + rommel1))
+ return move_unit(who, to, speed + rommel1)
- if (!game.to2 && game.from2) {
- if (from === game.from2) {
- for (let to of all_hexes)
- if (to != from && can_move_to(to, speed + rommel2))
- return move_unit(who, to, speed + rommel2)
- }
- }
+ if (!game.to2 && game.from2 === from)
+ if (can_move_to(to, speed + rommel2))
+ return move_unit(who, to, speed + rommel2)
- if (game.to1 === to) {
- if (is_hex_or_adjacent_to(from, game.from1))
- if (can_move_to(game.to1, speed + rommel1))
- return move_unit(who, to, speed + rommel1)
- }
+ if (game.to1 === to && is_hex_or_adjacent_to(from, game.from1))
+ if (can_move_to(to, speed + rommel1))
+ return move_unit(who, to, speed + rommel1)
- if (game.to2 === to) {
- if (is_hex_or_adjacent_to(from, game.from2))
- if (can_move_to(game.to2, speed + rommel2))
- return move_unit(who, to, speed + rommel2)
- }
+ if (game.to2 === to && is_hex_or_adjacent_to(from, game.from2))
+ if (can_move_to(to, speed + rommel2))
+ return move_unit(who, to, speed + rommel2)
}
function move_unit(who, to, speed) {
@@ -1752,6 +1742,8 @@ states.retreat_from = {
view.prompt = `Retreat: Select hex to retreat from.`
if (game.from1) {
if (game.to1) {
+ // TODO: only hexes with units that can reach regroup destination
+ // TODO: if full retreat required, only allow hexes where all can reach regroup destination
for_each_hex_and_adjacent_hex(game.from1, x => {
if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
gen_action_hex(x)
@@ -1763,6 +1755,8 @@ states.retreat_from = {
}
if (game.from2) {
if (game.to2) {
+ // TODO: only hexes with units that can reach regroup destination
+ // TODO: if full retreat required, only allow hexes where all can reach regroup destination
for_each_hex_and_adjacent_hex(game.from2, x => {
if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
gen_action_hex(x)
@@ -1786,6 +1780,7 @@ states.retreat_who = {
view.prompt = `Retreat: Select units to retreat.`
let full_retreat = true
for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => {
+ // TODO: can reach regroup destination?
if (!set_has(game.retreat_units, u))
full_retreat = false
gen_action_unit(u)
@@ -1944,7 +1939,7 @@ states.refuse_battle = {
states.refuse_battle_move = {
inactive: "refuse battle (withdraw group move)",
prompt() {
- view.prompt = `Withdraw: Select unit to move.`
+ view.prompt = `Refuse Battle: Withdraw units.`
if (game.selected < 0) {
let done = true
for_each_undisrupted_friendly_unit_in_hex(game.from1, u => {
@@ -1955,6 +1950,7 @@ states.refuse_battle_move = {
gen_action('end_retreat')
} else {
let speed = unit_speed(game.selected)
+ gen_action_unit(game.selected)
search_withdraw_retreat(game.from1, speed)
for (let to of all_hexes)
if (to != game.from1 && can_move_to(to, speed))
@@ -1979,6 +1975,102 @@ states.refuse_battle_move = {
}
}
+// === ROUT ===
+
+// rout attrition
+// pursuit fire
+// withdraw by group move
+// eliminated if cannot
+
+function goto_rout(from, side) {
+ // save old state so we can recover
+ game.rout = {
+ state: game.state,
+ active: game.active,
+ from: from,
+ attrition: [],
+ }
+
+ if (side !== game.active)
+ set_enemy_player()
+
+ game.pursuit = from
+ game.state = 'rout_attrition'
+}
+
+states.rout_attrition = {
+ prompt() {
+ view.prompt = "Rout: Suffer attrition!"
+ for_each_friendly_unit_in_hex(game.rout.from, u => {
+ if (!set_has(game.rout.attrition, u))
+ gen_action_unit(u)
+ })
+ },
+ unit(who) {
+ reduce_unit(who)
+ set_add(game.rout.attrition, who)
+ let done = true
+ for_each_friendly_unit_in_hex(game.rout.from, u => {
+ if (!set_has(game.rout.attrition, u))
+ done = false
+ })
+ if (done) {
+ goto_pursuit_fire_during_rout()
+ delete game.rout.attrition
+ }
+ },
+}
+
+states.rout_move = {
+ prompt() {
+ view.prompt = `Rout: Withdraw units.`
+ if (game.selected < 0) {
+ let done = true
+ for_each_friendly_unit_in_hex(game.from1, u => {
+ gen_action_unit(u)
+ done = false
+ })
+ if (done)
+ gen_action('end_retreat')
+ } else {
+ let speed = unit_speed(game.selected)
+ let eliminate = true
+ search_withdraw_retreat(game.rout.from, speed)
+ for (let to of all_hexes) {
+ if (to != game.rout.from && can_move_to(to, speed)) {
+ gen_action_hex(to)
+ eliminate = false
+ }
+ }
+ if (eliminate)
+ gen_action('eliminate')
+ }
+ },
+ unit(who) {
+ apply_select(who)
+ },
+ eliminate() {
+ let who = pop_selected()
+ push_undo()
+ log(`>eliminated`)
+ eliminate_unit(who)
+ },
+ hex(to) {
+ let who = pop_selected()
+ push_undo()
+ log(`>to #${to}`)
+ set_unit_hex(who, to)
+ set_unit_disrupted(who)
+ },
+ end_rout() {
+ clear_undo()
+ game.state = game.rout.state
+ if (game.active !== game.rout.active)
+ set_active_enemy()
+ delete game.rout
+ }
+}
+
// ==== COMBAT PHASE ===
function goto_combat_phase() {
@@ -2375,8 +2467,17 @@ states.probe_hits = {
// routing apply hits
// routing moves
+function goto_rout_fire(where) {
+ clear_undo()
+ game.hits = 0
+ game.pursuit = where
+ if (can_rout_fire(true))
+ game.state = 'rout_fire'
+ else
+ goto_rout_hits()
+}
+
function goto_pursuit_fire_during_retreat(where) {
-console.log("goto_pursuit_fire_during_retreat", where)
clear_undo()
set_passive_player()
game.hits = 0
@@ -2398,16 +2499,30 @@ function goto_pursuit_fire_during_refuse_battle(where) {
goto_pursuit_hits()
}
+function goto_rout_hits() {
+ set_enemy_player()
+ if (game.hits > 0)
+ game.state = 'rout_hits'
+ else
+ end_rout_fire()
+}
+
function goto_pursuit_hits() {
set_enemy_player()
- if (game.hits > 0) {
- let hp = count_hp_in_pursuit()
- if (game.hits > hp)
- game.hits = hp
+ if (game.hits > 0)
game.state = 'pursuit_hits'
- } else {
+ 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) {
@@ -2420,6 +2535,18 @@ function slowest_undisrupted_enemy_unit_speed(where) {
return r
}
+function can_rout_fire(verbose) {
+ let result = false
+ let slowest = slowest_enemy_unit_speed(game.pursuit)
+ if (verbose)
+ log(`Slowest routing unit was ${speed_name[slowest]} ${game.active} at #${game.pursuit}.`)
+ 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)
@@ -2432,7 +2559,7 @@ function can_pursuit_fire(verbose) {
return result
}
-function roll_pursuit_fire(who, n) {
+function roll_pursuit_fire_imp(who, n, hp) {
if (n === 2) {
let a = random(6) + 1
let b = random(6) + 1
@@ -2450,13 +2577,19 @@ function roll_pursuit_fire(who, n) {
if (a >= 4)
game.hits++
}
-
- let hp = count_hp_in_pursuit()
if (game.hits > hp)
game.hits = hp
return game.hits === hp
}
+function roll_pursuit_fire(who, n) {
+ return roll_pursuit_fire_imp(who, n, count_hp_in_pursuit())
+}
+
+function roll_rout_fire(who, n) {
+ return roll_pursuit_fire_imp(who, n, count_hp_in_rout())
+}
+
states.pursuit_fire = {
inactive: "pursuit fire (fire)",
prompt() {
@@ -2481,36 +2614,75 @@ states.pursuit_fire = {
}
}
+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)
+ })
+ },
+ unit(who) {
+ let slowest = slowest_enemy_unit_speed(game.pursuit)
+ 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()
+ },
+}
+
+function gen_pursuit_hits(normal_steps, elite_steps, iterate) {
+ let done = true
+ iterate(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')
+}
+
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()
+ gen_pursuit_hits(normal_steps, elite_steps, for_each_undisrupted_friendly_unit_in_hex)
+ },
+ unit(who) {
+ push_undo()
+ game.hits -= reduce_unit(who)
+ },
+ next() {
+ clear_undo()
+ end_pursuit_fire()
+ },
+}
- let done = true
- for_each_undisrupted_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')
+states.rout_hits = {
+ inactive: "rout fire (hits)",
+ prompt() {
+ view.prompt = `Pursuit Fire (Rout): Apply ${game.hits} 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)
},
unit(who) {
push_undo()
@@ -2532,6 +2704,12 @@ function end_pursuit_fire() {
}
}
+function end_rout_fire() {
+ game.from1 = game.pursuit
+ game.pursuit = 0
+ game.state = 'rout_move'
+}
+
// === DEPLOYMENT ===
states.free_deployment = {