summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js293
1 files changed, 195 insertions, 98 deletions
diff --git a/rules.js b/rules.js
index 6bff7ab..3ba015c 100644
--- a/rules.js
+++ b/rules.js
@@ -5,15 +5,12 @@
// TODO: raiders
// TODO: legal pass withdrawal moves (reduce supply net, withdraw from fortress attack)
-// TODO: FORCED MARCHES
// TODO: MINEFIELDS
// TODO: SUPPLY COMMITMENT
// TODO: BUILDUP
// TODO: setup scenario specials
-// TODO: failed forced march hexside control...
-
// TODO: when is "fired" status cleared?
// RULES: disrupted units routed again in second enemy turn, will they still recover?
@@ -434,7 +431,6 @@ function is_axis_unit(u) {
// === MAP STATE ===
function update_presence() {
- console.log("UPDATE PRESENCE")
presence_invalid = false
presence_axis.fill(0)
for (let u = first_axis_unit; u <= last_axis_unit; ++u)
@@ -1383,6 +1379,7 @@ function goto_player_turn() {
game.to1 = game.to2 = 0
// reset moved and fired flags
+ game.forced = []
set_clear(game.fired)
set_clear(game.moved)
@@ -1474,10 +1471,10 @@ function goto_initial_supply_check_rout() {
else if (n === 1)
goto_rout(where, false, goto_initial_supply_check_rout)
else
- game.state = 'initial_supply_check_routs'
+ game.state = 'initial_supply_check_rout'
}
-states.initial_supply_check_routs = {
+states.initial_supply_check_rout = {
prompt() {
view.prompt = `Initial Supply Check: Rout!`
for (let x of all_hexes)
@@ -1526,22 +1523,10 @@ function goto_final_supply_check_rout() {
else if (n === 1)
goto_rout(where, false, goto_final_supply_check_rout)
else
- game.state = 'final_supply_check_routs'
+ game.state = 'final_supply_check_rout'
}
-states.final_supply_check_routs = {
- prompt() {
- view.prompt = `Final Supply Check: Rout!`
- for (let x of all_hexes)
- if (is_friendly_rout_hex(x))
- gen_action_hex(x)
- },
- hex(where) {
- goto_rout(where, false, goto_final_supply_check_rout)
- }
-}
-
-states.final_supply_check_routs = {
+states.final_supply_check_rout = {
prompt() {
view.prompt = `Final Supply Check: Rout!`
for (let x of all_hexes)
@@ -1700,11 +1685,9 @@ states.regroup_move_destination = {
},
}
-function end_move_phase() {
- game.side_limit = {}
+function end_movement() {
game.from1 = game.from2 = game.to1 = game.to2 = 0
- // TODO: forced marches
- goto_refuse_battle()
+ goto_forced_marches()
}
// === GROUP AND REGROUP MOVEMENT ===
@@ -1830,7 +1813,7 @@ states.move = {
apply_select(who)
},
forced_march(to) {
- apply_move(to)
+ this.hex(to)
},
hex(to) {
apply_move(to)
@@ -1859,7 +1842,7 @@ states.move = {
end_move() {
clear_undo()
log_br()
- end_move_phase()
+ end_movement()
}
}
@@ -1931,27 +1914,29 @@ function apply_move(to) {
let rommel2 = (game.rommel === 2) ? 1 : 0
let who = pop_selected()
let from = unit_hex(who)
- let speed = unit_speed(who) + 1 // forced march
+ let speed = unit_speed(who)
push_undo()
- search_move(from, speed + (rommel1 | rommel2))
+ search_move(from, speed + 1 + (rommel1 | rommel2))
if (!game.to1 && game.from1 === from)
- if (can_move_to(to, speed + rommel1))
- return move_unit(who, to, speed + rommel1, 1)
+ if (can_move_to(to, speed + 1 + rommel1))
+ return move_unit(who, to, speed + 1 + rommel1, 1)
if (!game.to2 && game.from2 === from)
- if (can_move_to(to, speed + rommel2))
- return move_unit(who, to, speed + rommel2, 2)
+ if (can_move_to(to, speed + 1 + rommel2))
+ return move_unit(who, to, speed + 1 + rommel2, 2)
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, 1)
+ if (can_move_to(to, speed + 1 + rommel1))
+ return move_unit(who, to, speed + 1 + rommel1, 1)
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, 2)
+ if (can_move_to(to, speed + 1 + rommel2))
+ return move_unit(who, to, speed + 1 + rommel2, 2)
+
+ throw Error("bad move")
}
// to check usable alternate paths to enter destination hex
@@ -1962,62 +1947,75 @@ function can_move_via(via, to, speed, road) {
// too far
if (cost + 1 > speed + road)
- return false
+ return 0
// can't cross this hexside
if (max_side === 0)
- return false
+ return 0
// must stay on road for current bonus
if (side_road[side] < road)
- return false
+ return 0
// must stop on enemies
if (has_enemy_unit(via))
- return false
+ return 0
// may not exceed hexside limit
if (has_enemy_unit(to))
if ((game.side_limit[side] | 0) >= max_side)
- return false
+ return 0
- return true
+ if (cost + 1 === speed + road)
+ return 2
+ return 1
}
function is_forced_march_move(from, to, speed) {
- console.log("is_forced_march_move", from, to, speed, "=>",
- path_cost[4][to],
- path_cost[2][to],
- path_cost[1][to],
- path_cost[0][to]
- )
- if (hex_road[from] >= 4 && path_cost[4][to] === speed + 4)
- return true
- if (hex_road[from] >= 2 && path_cost[2][to] === speed + 2)
- return true
- if (hex_road[from] >= 1 && path_cost[1][to] === speed + 1)
- return true
- if (hex_road[from] >= 0 && path_cost[0][to] === speed + 0)
- return true
- return false
+ let result = true
+ if (hex_road[from] >= 0 && path_cost[0][to] < speed + 0)
+ result = false
+ if (hex_road[from] >= 1 && path_cost[1][to] < speed + 1)
+ result = false
+ if (hex_road[from] >= 2 && path_cost[2][to] < speed + 2)
+ result = false
+ if (hex_road[from] >= 4 && path_cost[4][to] < speed + 4)
+ result = false
+ return result
}
function move_via(who, to, speed, move) {
let from = unit_hex(who)
game.hexside = {
who: who,
- via: [],
to: to,
+ via: [],
+ forced: [],
move: move
}
for_each_adjacent_hex(to, via => {
- if (
- (hex_road[from] >= 4 && can_move_via(via, to, speed, 4)) ||
- (hex_road[from] >= 2 && can_move_via(via, to, speed, 2)) ||
- (hex_road[from] >= 1 && can_move_via(via, to, speed, 1)) ||
- (hex_road[from] >= 0 && can_move_via(via, to, speed, 0))
- )
- game.hexside.via.push(via)
+ let forced = false
+ let unforced = false
+ function check_road(bonus) {
+ if (hex_road[from] >= bonus) {
+ let k = can_move_via(via, to, speed, bonus)
+ if (k === 2)
+ forced = true
+ else if (k === 1)
+ unforced = true
+ }
+ }
+ check_road(4)
+ check_road(2)
+ check_road(1)
+ check_road(0)
+ if (unforced) {
+ game.hexside.via.push(via)
+ game.hexside.forced.push(0)
+ } else if (forced) {
+ game.hexside.via.push(via)
+ game.hexside.forced.push(1)
+ }
})
return game.hexside.via.length === 1
}
@@ -2025,7 +2023,7 @@ function move_via(who, to, speed, move) {
function move_unit(who, to, speed, move) {
let from = unit_hex(who)
- if (is_forced_march_move(from, to, speed + 1)) {
+ if (is_forced_march_move(from, to, speed)) {
if (move_via(who, to, speed, move)) {
forced_march_via(who, game.hexside.via[0], to, move)
delete game.hexside
@@ -2036,7 +2034,10 @@ function move_unit(who, to, speed, move) {
else if (has_enemy_unit(to)) {
if (move_via(who, to, speed, move)) {
- engage_via(who, game.hexside.via[0], to)
+ if (game.hexside.forced[0])
+ forced_march_via(who, game.hexside.via[0], to, move)
+ else
+ engage_via(who, game.hexside.via[0], to)
delete game.hexside
} else {
game.state = 'engage_via'
@@ -2052,7 +2053,7 @@ function move_unit(who, to, speed, move) {
states.forced_march_via = {
prompt() {
- view.prompt = `Move: Select which hex side to cross.`
+ view.prompt = `Move: Select which path to take.`
view.selected = game.hexside.who
for (let x of game.hexside.via)
gen_action_hex(x)
@@ -2068,8 +2069,16 @@ states.engage_via = {
prompt() {
view.prompt = `Move: Select which hex side to cross.`
view.selected = game.hexside.who
- for (let x of game.hexside.via)
- gen_action_hex(x)
+ for (let i = 0; i < game.hexside.via.length; ++i)
+ if (game.hexside.forced[i])
+ gen_action_forced_march(game.hexside.via[i])
+ else
+ gen_action_hex(game.hexside.via[i])
+ },
+ forced_march(via) {
+ forced_march_via(game.hexside.who, via, game.hexside.to, game.hexside.move)
+ delete game.hexside
+ game.state = 'move'
},
hex(via) {
engage_via(game.hexside.who, via, game.hexside.to)
@@ -2085,12 +2094,12 @@ function forced_march_via(who, via, to, move) {
// remember where we should advance to / return to
if ((move === 1 && game.to1) || (move === 2 && game.to2))
- game.forced[who] = to
+ game.forced.push([who, to, from])
else
- game.forced[who] = [ to, from ]
+ game.forced.push([who, to])
// attempted force marches affect hexside limits
- if (is_enemy_hex(to)) {
+ if (has_enemy_unit(to)) {
let side = to_side_id(via, to)
if (game.side_limit[side])
game.side_limit[side] = 2
@@ -2125,6 +2134,86 @@ function engage_via(who, via, to) {
log(`>from #${from} to #${to}`)
}
+// === FORCED MARCHES ===
+
+function goto_forced_marches() {
+ if (game.forced.length > 0)
+ game.state = 'forced_marches'
+ else
+ end_forced_marches()
+}
+
+states.forced_marches = {
+ prompt() {
+ view.prompt = `Forced Marches!`
+ for (let [who, to] of game.forced)
+ gen_action_unit(who)
+ },
+ unit(who) {
+ let via = unit_hex(who)
+ let ix = game.forced.findIndex(item => who === item[0])
+ let to = game.forced[ix][1]
+ let from = game.forced[ix][2] || via
+ let roll = random(6) + 1
+ if (roll >= 4) {
+ log(`Forced March roll ${roll} success.`)
+ if (has_enemy_unit(to)) {
+ engage_via(who, via, to, false)
+ } else {
+ set_unit_hex(who, to)
+ log(`>from #${via} to #${to}`)
+ }
+ } else {
+ log(`Forced March roll ${roll} failed!`)
+ if (from !== via) {
+ log(`>returned to #${from}`)
+ set_unit_hex(who, from)
+ }
+ if (is_unit_disrupted(who))
+ reduce_unit(who) // was a retreating unit
+ else
+ set_unit_disrupted(who)
+ }
+ game.forced.splice(ix, 1)
+
+ goto_forced_marches()
+ }
+}
+
+function end_forced_marches() {
+ game.side_limit = {}
+ game.forced = null
+ goto_forced_marches_rout()
+}
+
+function goto_forced_marches_rout() {
+ let n = 0, where = 0
+ for (let x of all_hexes) {
+ if (is_friendly_rout_hex(x)) {
+ where = x
+ n++
+ }
+ }
+ if (n === 0)
+ goto_refuse_battle()
+ else if (n === 1)
+ goto_rout(where, false, goto_forced_marches_rout)
+ else
+ game.state = 'forced_marches_rout'
+}
+
+states.forced_marches_rout = {
+ prompt() {
+ view.prompt = `Forced Marches: Rout!`
+ for (let x of all_hexes)
+ if (is_friendly_hexside(x))
+ gen_action_hex(x)
+ },
+ hex(where) {
+ goto_rout(where, false, goto_forced_marches_rout)
+ }
+}
+
// === RETREAT ===
function is_valid_retreat_hex(from) {
@@ -2297,7 +2386,7 @@ states.retreat_from = {
end_move() {
clear_undo()
log_br()
- end_move_phase()
+ end_movement()
}
}
@@ -2427,11 +2516,12 @@ states.retreat_move = {
unit(who) {
apply_select(who)
},
+ forced_march(to) {
+ this.hex(to)
+ },
hex(to) {
- let who = pop_selected()
- push_undo()
- set_unit_hex(who, to)
- set_unit_moved(who)
+ let who = game.selected
+ apply_move(to)
set_unit_disrupted(who)
},
end_retreat() {
@@ -2440,7 +2530,6 @@ states.retreat_move = {
}
function end_retreat() {
- clear_undo()
if (!is_battle_hex(game.retreat))
release_hex_control(game.retreat)
game.retreat_units = null
@@ -2461,15 +2550,21 @@ function end_retreat_2() {
if (can_select_retreat_hex())
game.state = 'retreat_from'
else
- end_move_phase()
+ end_movement()
}
// === REFUSE BATTLE ===
+function can_select_refuse_battle_hex() {
+ for (let x of game.active_battles)
+ if (can_all_refuse_battle(x))
+ return true
+ return false
+}
+
function goto_refuse_battle() {
- clear_undo()
- if (game.active_battles.length > 0) {
- set_passive_player()
+ set_passive_player()
+ if (can_select_refuse_battle_hex()) {
game.state = 'refuse_battle'
} else {
goto_combat_phase()
@@ -2555,7 +2650,6 @@ states.refuse_battle_move = {
}
function end_refuse_battle_move() {
- clear_undo()
if (is_friendly_rout_hex(game.refuse))
goto_rout(game.refuse, false, end_refuse_battle_move_2)
else
@@ -2675,7 +2769,6 @@ states.rout_move = {
}
function end_rout() {
- clear_undo()
game.state = game.rout.state
release_hex_control(game.rout.from)
set_delete(game.active_battles, game.rout.from)
@@ -2694,7 +2787,6 @@ function is_mandatory_combat(fortress, control_prop) {
}
function goto_combat_phase() {
- clear_undo()
set_active_player()
if (game.turn_option === 'pass') {
@@ -2800,17 +2892,21 @@ states.select_battle = {
function end_combat_phase() {
if (game.turn_option === 'blitz') {
- log_h2(`Blitz Turn`)
- if (game.rommel)
- game.rommel = 3
- set_clear(game.fired)
- game.turn_option = 'second blitz'
- goto_move_phase()
+ goto_blitz_turn()
} else {
goto_final_supply_check()
}
}
+function goto_blitz_turn() {
+ log_h2(`Blitz Turn`)
+ if (game.rommel)
+ game.rommel = 3
+ set_clear(game.fired)
+ game.turn_option = 'second blitz'
+ goto_move_phase()
+}
+
// === BATTLES ===
// Normal Battle:
@@ -2878,15 +2974,19 @@ function roll_battle_fire(who, tc) {
if (is_minefield_offensive_fire())
total = total / 2
- game.flash = `${unit_name(who)} fired ${firepower_name[fp]} at ${class_name[tc]}: ` + result.join(", ")
+ game.flash = `${unit_name(who)} fired ${firepower_name[fp]} ${result.join(", ")} at ${class_name[tc]}`
log(game.flash)
return total
}
function goto_battle(x) {
- clear_undo()
game.battle = x
+ if (is_assault_battle())
+ log_h3(`Assault in #${x}`)
+ else
+ log_h3(`Battle in #${x}`)
+
// goto defensive fire
set_passive_player()
game.state = 'battle_fire'
@@ -3177,7 +3277,6 @@ function end_probe_hits() {
// routing moves
function goto_rout_fire(where) {
- clear_undo()
set_enemy_player()
game.hits = 0
game.pursuit = where
@@ -3188,7 +3287,6 @@ function goto_rout_fire(where) {
}
function goto_pursuit_fire_during_retreat(where) {
- clear_undo()
set_passive_player()
game.hits = 0
game.pursuit = where
@@ -3199,7 +3297,6 @@ function goto_pursuit_fire_during_retreat(where) {
}
function goto_pursuit_fire_during_refuse_battle(where) {
- clear_undo()
set_active_player()
game.hits = 0
game.pursuit = where
@@ -3996,7 +4093,7 @@ exports.setup = function (seed, scenario, options) {
// current turn option and selected moves
turn_option: null,
side_limit: {},
- forced: {},
+ forced: null,
rommel: 0,
from1: 0,
to1: 0,