summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js342
1 files changed, 298 insertions, 44 deletions
diff --git a/rules.js b/rules.js
index 2d75589..691461e 100644
--- a/rules.js
+++ b/rules.js
@@ -222,6 +222,10 @@ function is_unit_supplied(u) {
return ((game.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT) !== 0
}
+function is_unit_unsupplied(u) {
+ return ((game.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT) === 0
+}
+
function unit_supply(u) {
let src = (game.units[u] & UNIT_SUPPLY_MASK) >> UNIT_SUPPLY_SHIFT
return hex_from_supply_source[src]
@@ -557,6 +561,14 @@ function for_each_undisrupted_enemy_unit_in_hex(x, fn) {
fn(u)
}
+function count_battle_hexes() {
+ let n = 0
+ for (let x of all_hexes)
+ if (is_battle_hex(x))
+ ++n
+ return n
+}
+
function count_normal_steps_in_battle() {
let steps = [ 0, 0, 0, 0 ]
for_each_undisrupted_enemy_unit_in_hex(game.battle, u => {
@@ -1242,7 +1254,7 @@ function goto_initial_supply_check() {
if (snet[x]) {
set_unit_supply(u, ssrc)
if (is_unit_disrupted(u) && set_has(game.recover, u) && !is_battle_hex(x)) {
- log(`${unit_name(u)} recovered in ${hex_name[x]}`)
+ log(`${unit_name(u)} recovered at ${hex_name[x]}`)
set_delete(game.recover, u)
clear_unit_disrupted(u)
}
@@ -1475,17 +1487,21 @@ function goto_move_who() {
game.state = 'move_who'
}
-function gen_group_move_who(from) {
+function gen_group_move_who(may_retreat, from) {
+ if (may_retreat !== is_battle_hex(from))
+ return
for_each_undisrupted_friendly_unit_in_hex(from, u => {
if (!is_unit_moved(u))
gen_action_unit(u)
})
}
-function gen_regroup_move_who(command_point, destination, rommel) {
+function gen_regroup_move_who(may_retreat, command_point, destination, rommel) {
search_move(destination, 0, 4)
for_each_hex_and_adjacent_hex(command_point, x => {
if (x !== destination) {
+ if (may_retreat !== is_battle_hex(x))
+ return
for_each_undisrupted_friendly_unit_in_hex(x, u => {
if (!is_unit_moved(u) && can_move_from(x, 4, unit_speed(u) + rommel))
gen_action_unit(u)
@@ -1500,17 +1516,17 @@ states.move_who = {
view.prompt = `Move: Select unit to move.`
if (game.from1) {
if (game.to1)
- gen_regroup_move_who(game.from1, game.to1, game.rommel === 1 ? 1 : 0)
+ gen_regroup_move_who(false, game.from1, game.to1, game.rommel === 1 ? 1 : 0)
else
- gen_group_move_who(game.from1)
+ gen_group_move_who(false, game.from1)
}
if (game.from2) {
if (game.to2)
- gen_regroup_move_who(game.from2, game.to2, game.rommel === 2 ? 1 : 0)
+ gen_regroup_move_who(false, game.from2, game.to2, game.rommel === 2 ? 1 : 0)
else
- gen_group_move_who(game.from2)
+ gen_group_move_who(false, game.from2)
}
- // TODO: retreat
+ // TODO: only retreat if possible
gen_action('retreat')
gen_action('end_move')
},
@@ -1521,16 +1537,24 @@ states.move_who = {
game.move_used = 0
game.move_road = 4
},
+ retreat() {
+ push_undo()
+ game.state = 'retreat_select_from'
+ },
end_move() {
clear_undo()
- game.side_limit = {}
- game.rommel = 0
- game.from1 = game.from2 = game.to1 = game.to2 = 0
- // TODO: forced marches
- goto_refuse_battle()
+ end_move_phase()
}
}
+function end_move_phase() {
+ game.side_limit = {}
+ game.rommel = 0
+ game.from1 = game.from2 = game.to1 = game.to2 = 0
+ // TODO: forced marches
+ goto_refuse_battle()
+}
+
function print_path(who, from, to, road) {
let p = [ hex_name[to] ]
while (to && to !== from) {
@@ -1758,6 +1782,183 @@ function stop_move(who) {
game.state = 'move_who'
}
+// === RETREAT ===
+
+function can_select_retreat_hex() {
+ let result = false
+ if (game.from1) {
+ if (game.to1) {
+ for_each_hex_and_adjacent_hex(game.from1, x => {
+ if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
+ result = true
+ })
+ } else {
+ if (is_battle_hex(game.from1) && !set_has(game.partial_retreats, game.from1))
+ result = true
+ }
+ }
+ if (game.from2) {
+ if (game.to2) {
+ for_each_hex_and_adjacent_hex(game.from2, x => {
+ if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
+ result = true
+ })
+ } else {
+ if (is_battle_hex(game.from2) && !set_has(game.partial_retreats, game.from2))
+ result = true
+ }
+ }
+ return result
+}
+
+states.retreat_select_from = {
+ prompt() {
+ view.prompt = `Retreat: Select hex to retreat from.`
+ if (game.from1) {
+ if (game.to1) {
+ for_each_hex_and_adjacent_hex(game.from1, x => {
+ if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
+ gen_action_hex(x)
+ })
+ } else {
+ if (is_battle_hex(game.from1) && !set_has(game.partial_retreats, game.from1))
+ gen_action_hex(game.from1)
+ }
+ }
+ if (game.from2) {
+ if (game.to2) {
+ for_each_hex_and_adjacent_hex(game.from2, x => {
+ if (is_battle_hex(x) && !set_has(game.partial_retreats, x))
+ gen_action_hex(x)
+ })
+ } else {
+ if (is_battle_hex(game.from2) && !set_has(game.partial_retreats, game.from2))
+ gen_action_hex(game.from2)
+ }
+ }
+ },
+ hex(x) {
+ push_undo()
+ game.retreat = x
+ game.state = 'retreat_select_who'
+ }
+}
+
+states.retreat_select_who = {
+ prompt() {
+ view.prompt = `Retreat: Select unit to move.`
+ let full_retreat = true
+ for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => {
+ if (!set_has(game.selected, u))
+ full_retreat = false
+ gen_action_unit(u)
+ })
+ // No Partial Retreat allowed for Pass turn option.
+ if (game.turn_option !== 'pass' && game.selected.length > 0)
+ gen_action('partial_retreat')
+ gen_action('full_retreat')
+ },
+ unit(u) {
+ console.log("set toggle", u)
+ set_toggle(game.selected, u)
+ },
+ full_retreat() {
+ clear_undo()
+ for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => {
+ set_add(game.selected, u)
+ })
+ game.retreat_units = game.selected
+ game.selected = []
+ goto_pursuit_fire(game.retreat)
+ },
+ partial_retreat() {
+ clear_undo()
+ set_add(game.partial_retreats, game.retreat)
+ set_passive_player()
+ game.state = 'provoke_probe_combat'
+ },
+}
+
+states.provoke_probe_combat = {
+ prompt() {
+ view.prompt = `Retreat: You may provoke probe combat at ${hex_name[game.retreat]}.`
+ gen_action('probe_combat')
+ gen_action('pass')
+ },
+ // TODO: probe_combat
+ pass() {
+ set_active_player()
+ game.retreat_units = game.selected
+ game.selected = []
+ let shielded = false
+ for_each_undisrupted_friendly_unit_in_hex(game.retreat, u => {
+ if (!set_has(game.retreat_units, u))
+ shielded = true
+ })
+ if (shielded)
+ goto_retreat_who()
+ else
+ goto_pursuit_fire(game.retreat)
+ },
+}
+
+function goto_retreat_who() {
+ set_active_player()
+ game.state = 'retreat_who'
+}
+
+states.retreat_who = {
+ prompt() {
+ view.prompt = `Retreat: Select unit to move.`
+ let done = true
+ for (let u of game.retreat_units) {
+ if (unit_hex(u) === game.retreat) {
+ gen_action_unit(u)
+ done = false
+ }
+ }
+ if (done)
+ gen_action('end_retreat')
+ },
+ unit(who) {
+ push_undo()
+ game.selected = [ who ]
+ game.state = 'retreat_to'
+ },
+ end_retreat() {
+ clear_undo()
+ if (can_select_retreat_hex())
+ game.state = 'retreat_select_from'
+ else
+ end_move_phase()
+ }
+}
+
+states.retreat_to = {
+ prompt() {
+ view.prompt = `Retreat: Select destination.`
+ let who = game.selected[0]
+ gen_action_unit(who)
+ //if (game.turn_option === 'pass') {
+ {
+ search_withdraw(game.retreat, true)
+ // TODO: regroup
+ gen_withdraw_group_move(who, game.retreat)
+ }
+ },
+ unit(who) {
+ pop_undo()
+ },
+ hex(to) {
+ push_undo()
+ let who = game.selected[0]
+ set_unit_hex(who, to)
+ set_unit_disrupted(who)
+ game.selected = []
+ game.state = 'retreat_who'
+ },
+}
+
// === REFUSE BATTLE ===
function gen_withdraw_group_move(who, from) {
@@ -1788,7 +1989,6 @@ states.refuse_battle = {
hex(x) {
push_undo()
set_delete(game.active_battles, x)
- game.pursuit = x
goto_pursuit_fire(x)
},
next() {
@@ -1850,18 +2050,17 @@ states.refuse_battle_to = {
function goto_combat_phase() {
clear_undo()
set_active_player()
- if (game.active_battles.length > 0)
- return goto_select_battles()
- for (let x of all_hexes)
- if (is_battle_hex(x))
- return goto_select_battles()
+ let n = count_battle_hexes()
+ if (n > 0) {
+ if (n > game.active_battles.length)
+ return game.state = 'select_active_battles'
+ if (game.turn_option === 'assault')
+ return game.state = 'select_assault_battles'
+ return goto_select_battle()
+ }
end_combat_phase()
}
-function goto_select_battles() {
- game.state = 'select_active_battles'
-}
-
states.select_active_battles = {
inactive: "combat phase (select active battles)",
prompt() {
@@ -1910,6 +2109,18 @@ states.select_assault_battles = {
}
}
+function goto_select_battle() {
+ if (game.active_battles.length > 0) {
+ if (game.active_battles.length > 1) {
+ game.state = 'select_battle'
+ } else {
+ goto_battle(game.active_battles[0])
+ }
+ } else {
+ end_combat_phase()
+ }
+}
+
states.select_battle = {
inactive: "combat phase (select next battle)",
prompt() {
@@ -1920,12 +2131,25 @@ states.select_battle = {
gen_action_hex(x)
},
hex(x) {
- clear_undo()
- game.battle = x
- goto_defensive_fire()
+ goto_battle(x)
},
}
+function end_combat_phase() {
+ // TODO: blitz
+ // TODO: final supply check
+ // TODO: supply cards revealed
+ end_player_turn()
+}
+
+// === BATTLES ===
+
+function goto_battle(x) {
+ clear_undo()
+ game.battle = x
+ goto_defensive_fire()
+}
+
function goto_defensive_fire() {
set_passive_player()
game.fired = []
@@ -2073,7 +2297,7 @@ const xxx_fire_hits = {
if (game.state === 'defensive_fire_hits') {
goto_offensive_fire()
} else {
- end_combat()
+ end_battle()
}
},
}
@@ -2127,7 +2351,7 @@ function goto_fire_hits() {
}
}
-function end_combat() {
+function end_battle() {
clear_undo()
set_active_player()
set_delete(game.active_battles, game.battle)
@@ -2146,19 +2370,24 @@ states.offensive_fire_target = xxx_fire_target
states.defensive_fire_hits = xxx_fire_hits
states.offensive_fire_hits = xxx_fire_hits
-function end_combat_phase() {
- // TODO: blitz
- // TODO: final supply check
- // TODO: supply cards revealed
- end_player_turn()
-}
-
// === PURSUIT FIRE ===
-function goto_pursuit_fire() {
+function goto_retreat_fire(where) {
+ clear_undo()
+ set_passive_player()
+ game.hits = 0
+ game.pursuit = where
+ if (can_pursuit_fire())
+ game.state = 'pursuit_fire'
+ else
+ goto_pursuit_hits()
+}
+
+function goto_pursuit_fire(where) {
clear_undo()
set_active_player()
game.hits = 0
+ game.pursuit = where
if (can_pursuit_fire())
game.state = 'pursuit_fire'
else
@@ -2187,10 +2416,19 @@ function can_pursuit_fire() {
}
function roll_pursuit_fire(n) {
- for (let i = 0; i < n; ++i) {
- let roll = random(6) + 1
- log(`Pursuit fire ${roll}.`)
- if (roll >= 4)
+ if (n === 2) {
+ let a = random(6) + 1
+ let b = random(6) + 1
+ log(`Pursuit fire ${a}, ${b}.`)
+ if (a >= 4)
+ game.hits++
+ if (b >= 4)
+ game.hits++
+ }
+ if (n === 1) {
+ let a = random(6) + 1
+ log(`Pursuit fire ${a}.`)
+ if (a >= 4)
game.hits++
}
}
@@ -2207,7 +2445,7 @@ states.pursuit_fire = {
gen_action('next')
},
unit(who) {
- let slowest = slowest_enemy_unit_speed(game.pursuit)
+ let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit)
if (unit_speed(who) > slowest)
roll_pursuit_fire(2)
else
@@ -2275,8 +2513,13 @@ states.pursuit_hits = {
function end_pursuit_fire() {
game.from1 = game.pursuit
game.pursuit = 0
- set_passive_player()
- game.state = 'refuse_battle_who'
+ if (game.retreat) {
+ set_active_player()
+ game.state = 'retreat_who'
+ } else {
+ set_passive_player()
+ game.state = 'refuse_battle_who'
+ }
}
// === DEPLOYMENT ===
@@ -2860,8 +3103,12 @@ exports.setup = function (seed, scenario, options) {
move_used: 0,
move_road: 4,
- // combat
+ // retreat
partial_retreats: [], // remember partial retreats to forbid initiating combat
+ retreat: 0,
+ retreat_units: null,
+
+ // combat
active_battles: [],
assault_battles: [],
pursuit: 0,
@@ -2986,6 +3233,13 @@ function set_clear(set) {
set.length = 0
}
+function set_toggle(set, item) {
+ if (set_has(set, item))
+ set_delete(set, item)
+ else
+ set_add(set, item)
+}
+
function remove_from_array(array, item) {
let i = array.indexOf(item)
if (i >= 0)