summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js308
1 files changed, 199 insertions, 109 deletions
diff --git a/rules.js b/rules.js
index afb16ef..0726a42 100644
--- a/rules.js
+++ b/rules.js
@@ -638,7 +638,8 @@ function count_normal_steps_in_battle() {
let steps = [ 0, 0, 0, 0 ]
for_each_undisrupted_enemy_unit_in_hex(game.battle, u => {
if (!is_unit_elite(u))
- steps[unit_class(u)] += unit_steps(u)
+ if (!is_unit_retreating(u))
+ steps[unit_class(u)] += unit_steps(u)
})
return steps
}
@@ -647,7 +648,8 @@ function count_elite_steps_in_battle() {
let steps = [ 0, 0, 0, 0 ]
for_each_undisrupted_enemy_unit_in_hex(game.battle, u => {
if (is_unit_elite(u))
- steps[unit_class(u)] += unit_steps(u)
+ if (!is_unit_retreating(u))
+ steps[unit_class(u)] += unit_steps(u)
})
return steps
}
@@ -655,16 +657,8 @@ function count_elite_steps_in_battle() {
function count_hp_in_battle() {
let hp = [ 0, 0, 0, 0 ]
for_each_undisrupted_enemy_unit_in_hex(game.battle, u => {
- hp[unit_class(u)] += unit_hp(u)
- })
- return hp
-}
-
-function count_hp_in_battle_of_class(tc) {
- let hp = 0
- for_each_undisrupted_enemy_unit_in_hex(game.battle, u => {
- if (unit_class(u) === tc)
- hp += unit_hp(u)
+ if (!is_unit_retreating(u))
+ hp[unit_class(u)] += unit_hp(u)
})
return hp
}
@@ -988,19 +982,19 @@ function print_path(who, from, to, road) {
function search_move(start, speed) {
// Normal moves.
- search_init_enemy()
+ search_init()
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, false, null, null)
- if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, null, null)
- if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, null, null)
- if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, null, null)
+ search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, null, null)
+ search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, null, null)
+ search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, null, null)
}
function search_move_retreat(start, speed) {
- search_init_enemy()
+ search_init()
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, true, null, null)
- if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, null, null)
- if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, null, null)
- if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, null, null)
+ search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, null, null)
+ search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, null, null)
+ search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, null, null)
}
function search_withdraw(start, speed) {
@@ -1008,11 +1002,11 @@ function search_withdraw(start, speed) {
update_supply_networks()
let sline = game.active === AXIS ? game.axis_supply_line : game.allied_supply_line
let sdist = game.active === AXIS ? distance_to[EL_AGHEILA] : distance_to[ALEXANDRIA]
- search_init_enemy()
+ search_init()
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, false, sline, sdist)
- if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, sline, sdist)
- if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, sline, sdist)
- if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, sline, sdist)
+ search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, sline, sdist)
+ search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, sline, sdist)
+ search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, sline, sdist)
}
function search_withdraw_retreat(start, speed) {
@@ -1020,15 +1014,15 @@ function search_withdraw_retreat(start, speed) {
update_supply_networks()
let sline = game.active === AXIS ? game.axis_supply_line : game.allied_supply_line
let sdist = game.active === AXIS ? distance_to[EL_AGHEILA] : distance_to[ALEXANDRIA]
- search_init_enemy()
+ search_init()
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, true, sline, sdist)
- if (hex_road[start] >= 1) search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, sline, sdist)
- if (hex_road[start] >= 2) search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, sline, sdist)
- if (hex_road[start] >= 4) search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, sline, sdist)
+ search_move_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, sline, sdist)
+ search_move_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, sline, sdist)
+ search_move_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, sline, sdist)
}
// Cache enemy presence
-function search_init_enemy() {
+function search_init() {
path_enemy.fill(0)
for_each_enemy_unit(u => {
let x = unit_hex(u)
@@ -1044,12 +1038,16 @@ function search_init_enemy() {
// Breadth First Search
function search_move_bfs(from, cost, start, road, max_cost, retreat, sline, sdist) {
let friendly_sides = (game.active === AXIS) ? game.axis_sides : game.allied_sides
- let queue = [ start << 4 ]
from.fill(0)
cost.fill(15)
cost[start] = 0
+ if (hex_road[start] < road)
+ return
+
+ let queue = [ start << 4 ]
+
while (queue.length > 0) {
let item = queue.shift()
let here = item >> 4
@@ -1136,10 +1134,6 @@ function can_move_to(to, speed) {
return false
}
-function can_move_from(from, speed) {
- return can_move_to(from, speed)
-}
-
function pick_path(to, speed) {
let road = 4
let next_cost = 15, next_road = 0
@@ -1221,6 +1215,8 @@ function set_enemy_player() {
}
function end_player_turn() {
+ set_clear(game.partial_retreats)
+
// TODO: end when both pass
if (game.phasing === AXIS)
game.phasing = ALLIED
@@ -1239,6 +1235,10 @@ function goto_player_turn() {
game.from1 = game.from2 = 0
game.to1 = game.to2 = 0
+ // reset moved and fired flags
+ set_clear(game.fired)
+ set_clear(game.moved)
+
goto_initial_supply_check()
}
@@ -1268,13 +1268,8 @@ function goto_initial_supply_check() {
goto_turn_option()
}
-function clear_all_unit_moved() {
- set_clear(game.moved)
-}
-
function goto_turn_option() {
game.state = 'turn_option'
- clear_all_unit_moved()
}
states.turn_option = {
@@ -1552,32 +1547,37 @@ function apply_move(to) {
search_move(from, max(speed + rommel1, speed + rommel2))
if (!game.to1 && game.from1) {
- if (from === game.from1)
- if (can_move_to(to, speed + rommel1))
- move_unit(rommel1, who, from, to)
+ 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.to2 && game.from2) {
- if (from === game.from2)
- if (can_move_to(to, speed + rommel2))
- move_unit(rommel2, who, from, to)
+ 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.to1) {
+ if (game.to1 === to) {
if (is_hex_or_adjacent_to(from, game.from1))
if (can_move_to(game.to1, speed + rommel1))
- move_unit(rommel1, who, from, to)
+ return move_unit(who, to, speed + rommel1)
}
- if (game.to2) {
+ if (game.to2 === to) {
if (is_hex_or_adjacent_to(from, game.from2))
if (can_move_to(game.to2, speed + rommel2))
- move_unit(rommel2, who, from, to)
+ return move_unit(who, to, speed + rommel2)
}
}
-function move_unit(rommel, who, from, to) {
- let road = pick_path(to, unit_speed(who) + rommel)
+function move_unit(who, to, speed) {
+ let from = unit_hex(who)
+ let road = pick_path(to, speed)
set_unit_moved(who)
set_unit_hex(who, to)
@@ -1654,19 +1654,19 @@ states.move_who = {
// Retreat
let has_retreat_hex = false
- if (!game.to1 && game.from1 && is_battle_hex(game.from1))
+ if (!game.to1 && game.from1 && is_battle_hex(game.from1) && !set_has(game.partial_retreats, game.from1))
has_retreat_hex = true
- if (!game.to2 && game.from2 && is_battle_hex(game.from2))
+ if (!game.to2 && game.from2 && is_battle_hex(game.from2) && !set_has(game.partial_retreats, game.from2))
has_retreat_hex = true
if (game.to1 && !has_enemy_unit(game.to1)) {
for_each_hex_and_adjacent_hex(game.from1, x => {
- if (has_undisrupted_friendly_unit(x) && is_battle_hex(x))
+ if (has_undisrupted_friendly_unit(x) && is_battle_hex(x) && !set_has(game.partial_retreats, x))
has_retreat_hex = true
})
}
if (game.to2 && !has_enemy_unit(game.to2)) {
for_each_hex_and_adjacent_hex(game.from2, x => {
- if (has_undisrupted_friendly_unit(x) && is_battle_hex(x))
+ if (has_undisrupted_friendly_unit(x) && is_battle_hex(x) && !set_has(game.partial_retreats, x))
has_retreat_hex = true
})
}
@@ -1816,22 +1816,36 @@ states.retreat_who = {
states.provoke_probe_combat = {
prompt() {
view.prompt = `Retreat: You may provoke probe combat at ${hex_name[game.retreat]}.`
+ view.selected = game.retreat_units
gen_action('probe')
gen_action('pass')
},
- // TODO: probe
- pass() {
+ probe() {
set_active_player()
- 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_move()
- else
- goto_pursuit_fire_during_retreat(game.retreat)
+ game.state = 'probe_fire'
+ game.battle = game.retreat
+ game.hits = [ 0, 0, 0, 0 ]
},
+ pass() {
+ end_probe()
+ },
+}
+
+function end_probe() {
+ game.flash = ""
+ game.battle = 0
+ game.hits = 0
+
+ set_active_player()
+ 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_move()
+ else
+ goto_pursuit_fire_during_retreat(game.retreat)
}
function goto_retreat_move() {
@@ -1853,6 +1867,7 @@ states.retreat_move = {
if (done)
gen_action('end_retreat')
} else {
+ gen_action_unit(game.selected)
if (game.turn_option === 'pass')
gen_move(search_withdraw_retreat)
else
@@ -2056,18 +2071,33 @@ function end_combat_phase() {
// active fire
// passive hits
-function roll_fire(who, fp, tc) {
- let roll = random(6) + 1
- log(`${who} fired ${firepower_name[fp]} ${roll} at ${class_name[tc]}`)
- if (roll >= fp)
- return 1
- return 0
+function is_unit_retreating(u) {
+ if (game.retreat_units)
+ return set_has(game.retreat_units, u)
+ return false
+}
+
+function roll_battle_fire(who, tc) {
+ let fc = unit_class(who)
+ let cv = unit_cv(who)
+ console.log("FIRE", unit_name(who), cv)
+ let fp = FIREPOWER_MATRIX[fc][tc]
+ let result = []
+ let total = 0
+ for (let i = 0; i < cv; ++i) {
+ let roll = random(6) + 1
+ result.push(roll)
+ if (roll >= fp)
+ ++total
+ }
+ game.flash = `${unit_name(who)} fired ${firepower_name[fp]} at ${class_name[tc]}: ` + result.join(", ")
+ log(game.flash)
+ return total
}
function goto_battle(x) {
clear_undo()
game.battle = x
- game.fired = []
// goto defensive fire
set_passive_player()
@@ -2075,16 +2105,27 @@ function goto_battle(x) {
game.hits = [ 0, 0, 0, 0 ]
}
-function apply_battle_fire(tc) {
- let firing = pop_selected()
+function end_battle() {
+ set_delete(game.active_battles, game.battle)
+ set_delete(game.assault_battles, game.battle)
- let fp = FIREPOWER_MATRIX[unit_class(firing)][tc]
- let cv = unit_cv(firing)
+ game.flash = ""
+ game.battle = 0
+ game.hits = 0
- set_unit_fired(firing)
+ set_active_player()
+ if (game.active_battles.length > 0)
+ game.state = 'select_battle'
+ else
+ end_combat_phase()
+}
+
+function apply_battle_fire(tc) {
+ let who = pop_selected()
- for (let i = 0; i < cv; ++i)
- game.hits[tc] += roll_fire(firing, fp, tc)
+ set_unit_fired(who)
+
+ game.hits[tc] += roll_battle_fire(who, tc)
let hp = count_hp_in_battle()
@@ -2095,20 +2136,23 @@ function apply_battle_fire(tc) {
let done = true
if (game.hits[0] < hp[0] || game.hits[1] < hp[1] || game.hits[2] < hp[2] || game.hits[3] < hp[3]) {
for_each_undisrupted_friendly_unit_in_hex(game.battle, u => {
- if (!is_unit_fired(u))
+ if (!is_unit_fired(u) && !is_unit_retreating(u))
done = false
})
}
if (done) {
set_enemy_player()
- game.state = 'battle_hits'
+ if (game.state === 'battle_fire')
+ game.state = 'battle_hits'
+ else
+ game.state = 'probe_hits'
}
}
function gen_battle_fire() {
let arty = false
for_each_undisrupted_friendly_unit_in_hex(game.battle, u => {
- if (is_artillery_unit(u)) {
+ if (is_artillery_unit(u) && !is_unit_retreating(u)) {
if (!is_unit_fired(u)) {
gen_action_unit(u)
arty = true
@@ -2117,7 +2161,7 @@ function gen_battle_fire() {
})
if (!arty) {
for_each_undisrupted_friendly_unit_in_hex(game.battle, u => {
- if (!is_unit_fired(u))
+ if (!is_unit_fired(u) && !is_unit_retreating(u))
gen_action_unit(u)
})
}
@@ -2166,22 +2210,24 @@ function gen_battle_hits() {
let done = true
for_each_undisrupted_friendly_unit_in_hex(game.battle, u => {
- let c = unit_class(u)
- if (is_unit_elite(u)) {
- if (game.hits[c] >= 2) {
- gen_action_unit(u)
- done = false
- }
- } else {
- if (game.hits[c] >= 1) {
- // If mixed elite and non-elite: must assign ALL damage.
- if (elite_steps[c] > 0 && normal_steps[c] === 1 && (game.hits[c] & 1) === 0) {
- // Eliminating the last non-elite must not leave an odd
- // number of hits remaining.
- } else {
+ if (!is_unit_retreating(u)) {
+ let c = unit_class(u)
+ if (is_unit_elite(u)) {
+ if (game.hits[c] >= 2) {
gen_action_unit(u)
done = false
}
+ } else {
+ if (game.hits[c] >= 1) {
+ // If mixed elite and non-elite: must assign ALL damage.
+ if (elite_steps[c] > 0 && normal_steps[c] === 1 && (game.hits[c] & 1) === 0) {
+ // Eliminating the last non-elite must not leave an odd
+ // number of hits remaining.
+ } else {
+ gen_action_unit(u)
+ done = false
+ }
+ }
}
}
})
@@ -2246,16 +2292,57 @@ states.battle_hits = {
},
}
-function end_battle() {
- clear_undo()
- set_active_player()
- set_delete(game.active_battles, game.battle)
- set_delete(game.assault_battles, game.battle)
- game.battle = 0
- if (game.active_battles.length > 0)
- game.state = 'select_battle'
- else
- end_combat_phase()
+states.probe_fire = {
+ prompt() {
+ if (game.active !== game.phasing)
+ view.prompt = `Offensive Fire!`
+ else
+ view.prompt = `Defensive Fire!`
+ if (game.selected < 0)
+ gen_battle_fire()
+ else
+ gen_battle_target()
+ },
+ unit(who) {
+ apply_select(who)
+ },
+ armor() {
+ apply_battle_fire(ARMOR)
+ },
+ infantry() {
+ apply_battle_fire(INFANTRY)
+ },
+ antitank() {
+ apply_battle_fire(ANTITANK)
+ },
+ artillery() {
+ apply_battle_fire(ARTILLERY)
+ },
+}
+
+states.probe_hits = {
+ prompt() {
+ if (game.active !== game.phasing)
+ view.prompt = `Apply hits from Defensive Fire.`
+ else
+ view.prompt = `Apply hits from Offensive Fire.`
+ view.prompt = `Apply hits.`
+ gen_battle_hits()
+ },
+ unit(who) {
+ push_undo()
+ apply_battle_hit(who)
+ },
+ next() {
+ clear_undo()
+ if (game.active !== game.phasing) {
+ // goto offensive fire
+ game.state = 'probe_fire'
+ game.hits = [ 0, 0, 0, 0 ]
+ } else {
+ end_probe()
+ }
+ },
}
// === PURSUIT FIRE ===
@@ -2276,6 +2363,7 @@ function end_battle() {
// routing moves
function goto_pursuit_fire_during_retreat(where) {
+console.log("goto_pursuit_fire_during_retreat", where)
clear_undo()
set_passive_player()
game.hits = 0
@@ -2323,7 +2411,7 @@ function can_pursuit_fire(verbose) {
let result = false
let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit)
if (verbose)
- log(`Slowest enemy was ${speed_name[slowest]}.`)
+ log(`Slowest retreating 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
@@ -2335,7 +2423,8 @@ function roll_pursuit_fire(who, n) {
if (n === 2) {
let a = random(6) + 1
let b = random(6) + 1
- log(`>%${who} pursuit fired ${a}, ${b}.`)
+ game.flash = `${unit_name(who)} fired ${a}, ${b}`
+ log(game.flash)
if (a >= 4)
game.hits++
if (b >= 4)
@@ -2343,6 +2432,7 @@ function roll_pursuit_fire(who, n) {
}
if (n === 1) {
let a = random(6) + 1
+ game.flash = `${unit_name(who)} fired ${a}`
log(`>%${who} pursuit fired ${a}.`)
if (a >= 4)
game.hits++