summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js248
1 files changed, 129 insertions, 119 deletions
diff --git a/rules.js b/rules.js
index 4d93492..f28a8ab 100644
--- a/rules.js
+++ b/rules.js
@@ -4,7 +4,6 @@
// TODO: oasis supply
// TODO: raiders
// TODO: minefields
-// TODO: gazala scenario
// TODO: legal pass withdrawal moves (reduce supply net, withdraw from fortress attack)
// TOOD: reveal/hide blocks (hexes)
// TODO: group move from queue holding box to base
@@ -14,6 +13,8 @@
// TODO: replacements
// TODO: malta units
+// TODO: sort blocks by type
+
// TODO: black hit outline in battles ("steploss/bad" action)
// RULES: may units redeploying out of battles cross enemy controlled hexsides?
@@ -38,7 +39,10 @@ var view = null
var after_rout_table = {}
-const { all_hexes, hex_exists, hex_road, side_road, side_limit, hex_name, units, regions } = require("./data")
+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,
+} = require("./data")
function debug_hexes3(n, list) {
console.log("--", n, "--")
@@ -218,9 +222,10 @@ function is_hex_or_adjacent_to(x, where) {
// === STATE CACHES ===
const first_axis_unit = 0
-const first_allied_unit = units.findIndex(item => item.nationality === 'allied')
-const last_axis_unit = first_allied_unit - 1
-const last_allied_unit = units.length - 1
+const last_axis_unit = 33
+const first_allied_unit = 34
+const last_allied_unit = 93
+const unit_count = 94
var presence_invalid = true
var presence_axis = new Array(hexcount).fill(0)
@@ -382,11 +387,11 @@ function set_unit_lost_steps(u, n) {
}
function unit_steps(u) {
- return units[u].steps - unit_lost_steps(u)
+ return unit_start_steps[u] - unit_lost_steps(u)
}
function set_unit_steps(u, n) {
- set_unit_lost_steps(u, units[u].steps - n)
+ set_unit_lost_steps(u, unit_start_steps[u] - n)
}
function is_unit_moved(u) {
@@ -423,78 +428,46 @@ function reduce_unit(u) {
// === UNIT DATA ===
function find_unit(name) {
- for (let u = 0; u < units.length; ++u)
- if (units[u].name === name)
+ for (let u = 0; u < unit_count; ++u)
+ if (unit_name[u] === name)
return u
- throw new Error("cannot find named block: " + name)
-}
-
-function unit_name(u) {
- return units[u].name
-}
-
-function unit_speed(u) {
- return units[u].speed
+ throw new Error("cannot find named block: " + name + unit_name)
}
-function unit_class(u) {
- return units[u].class
-}
-
-function is_artillery_unit(u) {
- return units[u].class === ARTILLERY
+function is_allied_unit(u) {
+ return u >= first_allied_unit && u <= last_allied_unit
}
-function is_armor_unit(u) {
- return units[u].class === ARMOR
+function is_axis_unit(u) {
+ return u >= first_axis_unit && u <= last_axis_unit
}
-function is_infantry_unit(u) {
- return units[u].class === INFANTRY
+function is_german_unit(u) {
+ return (u >= 14 && u <= 33)
}
-function is_antitank_unit(u) {
- return units[u].class === ANTITANK
+function is_elite_unit(u) {
+ return unit_elite[u]
}
-function is_unit_elite(u) {
- return units[u].elite
+function is_artillery_unit(u) {
+ return unit_class[u] === ARTILLERY
}
function unit_cv(u) {
- if (is_unit_elite(u))
+ if (is_elite_unit(u))
return unit_steps(u) * 2
return unit_steps(u)
}
function unit_hp_per_step(u) {
- return is_unit_elite(u) ? 2 : 1
+ return is_elite_unit(u) ? 2 : 1
}
function unit_hp(u) {
return unit_steps(u) * unit_hp_per_step(u)
}
-function is_friendly_hex(x) {
- if (game.active === AXIS)
- return is_axis_hex(x)
- return is_allied_hex(x)
-}
-
-function is_enemy_hex(x) {
- if (game.active === ALLIED)
- return is_axis_hex(x)
- return is_allied_hex(x)
-}
-
-function is_allied_unit(u) {
- return u >= first_allied_unit && u <= last_allied_unit
-}
-
-function is_axis_unit(u) {
- return u >= first_axis_unit && u <= last_axis_unit
-}
-
// === MAP STATE ===
function friendly_base() {
@@ -616,6 +589,18 @@ function is_empty_hex(x) {
return (presence_axis[x] === 0) && (presence_allied[x] === 0)
}
+function is_friendly_hex(x) {
+ if (game.active === AXIS)
+ return is_axis_hex(x)
+ return is_allied_hex(x)
+}
+
+function is_enemy_hex(x) {
+ if (game.active === ALLIED)
+ return is_axis_hex(x)
+ return is_allied_hex(x)
+}
+
function has_friendly_unit(x) {
if (game.active === AXIS)
return has_axis_unit(x)
@@ -658,10 +643,6 @@ function has_unshielded_disrupted_friendly_unit(x) {
return has_unshielded_disrupted_axis_unit(x)
}
-function is_overrun_hex(x) {
- return has_undisrupted_friendly_unit(x) && has_unshielded_disrupted_enemy_unit(x)
-}
-
function is_enemy_rout_hex(x) {
return has_undisrupted_friendly_unit(x) && has_unshielded_disrupted_enemy_unit(x)
}
@@ -820,6 +801,12 @@ function for_each_hex_and_adjacent_hex(here, fn) {
}
}
+function for_each_unit_on_map(fn) {
+ for (let u = 0; u < unit_count; ++u)
+ if (is_map_hex(unit_hex(u)))
+ fn(u)
+}
+
function for_each_axis_unit(fn) {
for (let u = first_axis_unit; u <= last_axis_unit; ++u)
fn(u)
@@ -921,9 +908,9 @@ function has_friendly_units_in_battle() {
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))
+ if (!is_elite_unit(u))
if (!is_unit_retreating(u))
- steps[unit_class(u)] += unit_steps(u)
+ steps[unit_class[u]] += unit_steps(u)
})
return steps
}
@@ -931,9 +918,9 @@ function count_normal_steps_in_battle() {
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))
+ if (is_elite_unit(u))
if (!is_unit_retreating(u))
- steps[unit_class(u)] += unit_steps(u)
+ steps[unit_class[u]] += unit_steps(u)
})
return steps
}
@@ -942,7 +929,7 @@ function count_hp_in_battle() {
let hp = [ 0, 0, 0, 0 ]
for_each_undisrupted_enemy_unit_in_hex(game.battle, u => {
if (!is_unit_retreating(u))
- hp[unit_class(u)] += unit_hp(u)
+ hp[unit_class[u]] += unit_hp(u)
})
return hp
}
@@ -950,7 +937,7 @@ function count_hp_in_battle() {
function count_normal_steps_in_pursuit() {
let steps = 0
for_each_undisrupted_enemy_unit_in_hex(game.pursuit, u => {
- if (!is_unit_elite(u))
+ if (!is_elite_unit(u))
steps += unit_steps(u)
})
return steps
@@ -959,7 +946,7 @@ function count_normal_steps_in_pursuit() {
function count_elite_steps_in_pursuit() {
let steps = 0
for_each_undisrupted_enemy_unit_in_hex(game.pursuit, u => {
- if (is_unit_elite(u))
+ if (is_elite_unit(u))
steps += unit_steps(u)
})
return steps
@@ -976,7 +963,7 @@ function count_hp_in_pursuit() {
function count_normal_steps_in_rout() {
let steps = 0
for_each_enemy_unit_in_hex(game.pursuit, u => {
- if (!is_unit_elite(u))
+ if (!is_elite_unit(u))
steps += unit_steps(u)
})
return steps
@@ -985,7 +972,7 @@ function count_normal_steps_in_rout() {
function count_elite_steps_in_rout() {
let steps = 0
for_each_enemy_unit_in_hex(game.pursuit, u => {
- if (is_unit_elite(u))
+ if (is_elite_unit(u))
steps += unit_steps(u)
})
return steps
@@ -1357,7 +1344,7 @@ function search_move_retreat(start, speed) {
function search_withdraw(who, bonus) {
let sline = unit_supply_line(who)
let sdist = unit_supply_distance(who)
- let speed = unit_speed(who) + bonus
+ let speed = unit_speed[who] + bonus
let start = unit_hex(who)
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, false, sline, sdist)
@@ -1369,7 +1356,7 @@ function search_withdraw(who, bonus) {
function search_withdraw_retreat(who, bonus) {
let sline = unit_supply_line(who)
let sdist = unit_supply_distance(who)
- let speed = unit_speed(who) + bonus
+ let speed = unit_speed[who] + bonus
let start = unit_hex(who)
search_move_bfs(path_from[0], path_cost[0], start, 0, speed, true, sline, sdist)
@@ -1493,7 +1480,7 @@ function can_move_to(to, speed) {
function max_speed_of_undisrupted_and_unmoved_friendly_unit_in_hex(from) {
let max_speed = 0
for_each_undisrupted_and_unmoved_friendly_unit_in_hex(from, u => {
- let s = unit_speed(u)
+ let s = unit_speed[u]
if (s > max_speed)
max_speed = s
})
@@ -1989,7 +1976,7 @@ states.move = {
// TODO: withdraw pass move
search_move(from, speed + 1 + rommel1)
for_each_undisrupted_and_unmoved_friendly_unit_in_hex(from, u => {
- if (can_move_to(game.to1, unit_speed(u) + 1 + rommel1))
+ if (can_move_to(game.to1, unit_speed[u] + 1 + rommel1))
gen_action_unit(u)
})
}
@@ -2004,7 +1991,7 @@ states.move = {
// TODO: withdraw pass move
search_move(from, speed + 1 + rommel2)
for_each_undisrupted_and_unmoved_friendly_unit_in_hex(from, u => {
- if (can_move_to(game.to2, unit_speed(u) + 1 + rommel2))
+ if (can_move_to(game.to2, unit_speed[u] + 1 + rommel2))
gen_action_unit(u)
})
}
@@ -2018,7 +2005,7 @@ states.move = {
// Overrun
let has_overrun_hex = false
for (let x of all_hexes) {
- if (is_overrun_hex(x)) {
+ if (is_enemy_rout_hex(x)) {
has_overrun_hex = true
break
}
@@ -2038,7 +2025,7 @@ states.move = {
if (game.turn_option === 'pass')
search_withdraw(game.selected, 1 + (rommel1 | rommel2))
else
- search_move(unit_hex(game.selected), unit_speed(game.selected) + 1 + (rommel1 | rommel2))
+ search_move(unit_hex(game.selected), unit_speed[game.selected] + 1 + (rommel1 | rommel2))
gen_move()
}
@@ -2084,7 +2071,7 @@ states.overrun = {
prompt() {
view.prompt = `Overrun!`
for (let x of all_hexes)
- if (is_overrun_hex(x))
+ if (is_enemy_rout_hex(x))
gen_action_hex(x)
},
hex(where) {
@@ -2102,7 +2089,7 @@ function goto_overrun(where) {
function gen_move() {
let rommel1 = (game.rommel === 1) ? 1 : 0
let rommel2 = (game.rommel === 2) ? 1 : 0
- let speed = unit_speed(game.selected)
+ let speed = unit_speed[game.selected]
let from = unit_hex(game.selected)
if (!game.to1 && game.from1 === from) {
@@ -2148,7 +2135,7 @@ 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)
+ let speed = unit_speed[who]
push_undo()
@@ -2543,12 +2530,12 @@ function can_unit_disengage_and_move(who) {
function can_unit_disengage_and_withdraw_to(who, to, extra) {
search_withdraw_retreat(who, extra)
- return can_move_to(to, unit_speed(who) + extra)
+ return can_move_to(to, unit_speed[who] + extra)
}
function can_unit_disengage_and_move_to(who, to, extra) {
- search_move_retreat(unit_hex(who), unit_speed(who) + extra)
- return can_move_to(to, unit_speed(who) + extra)
+ search_move_retreat(unit_hex(who), unit_speed[who] + extra)
+ return can_move_to(to, unit_speed[who] + extra)
}
function can_select_retreat_hex() {
@@ -2745,7 +2732,7 @@ states.retreat_move = {
if (game.turn_option === 'pass')
search_withdraw_retreat(game.selected, 1 + (rommel1 | rommel2))
else
- search_move_retreat(unit_hex(game.selected), unit_speed(game.selected) + 1 + (rommel1 | rommel2))
+ search_move_retreat(unit_hex(game.selected), unit_speed[game.selected] + 1 + (rommel1 | rommel2))
gen_move()
}
},
@@ -2783,8 +2770,10 @@ function end_retreat() {
}
function end_retreat_2() {
- if (can_select_retreat_hex())
+ if (can_select_retreat_hex()) {
+ console.log("can_select_retreat_hex")
game.state = 'retreat_from'
+ }
else
end_movement()
}
@@ -2862,7 +2851,7 @@ states.refuse_battle_move = {
if (done)
gen_action('end_retreat')
} else {
- let speed = unit_speed(game.selected)
+ let speed = unit_speed[game.selected]
gen_action_unit(game.selected)
search_withdraw_retreat(game.selected, 0)
for (let to of all_hexes)
@@ -2970,7 +2959,7 @@ states.rout_move = {
if (done)
gen_action('end_rout')
} else {
- let speed = unit_speed(game.selected)
+ let speed = unit_speed[game.selected]
let eliminate = true
search_withdraw_retreat(game.selected, 0)
for (let to of all_hexes) {
@@ -3193,7 +3182,7 @@ function is_minefield_offensive_fire() {
}
function roll_battle_fire(who, tc) {
- let fc = unit_class(who)
+ let fc = unit_class[who]
let cv = unit_cv(who)
// Double dice during assault and non-armor defenders in fortress!
@@ -3216,7 +3205,7 @@ function roll_battle_fire(who, tc) {
}
// Double defense in minefields!
- if (is_minefield_offensive_fire())
+ if (fc !== ARTILLERY && is_minefield_offensive_fire())
total = total / 2
game.flash = `${class_name_cap[fc]} ${firepower_name[fp]} ${result.join("")} at ${class_name[tc]}`
@@ -3325,7 +3314,7 @@ function gen_battle_target() {
hp[i] -= game.hits[i]
let who = game.selected
- let fc = unit_class(who)
+ let fc = unit_class[who]
gen_action_unit(who) // deselect
@@ -3362,8 +3351,8 @@ function gen_battle_hits() {
let done = true
for_each_undisrupted_friendly_unit_in_hex(game.battle, u => {
if (!is_unit_retreating(u)) {
- let c = unit_class(u)
- if (is_unit_elite(u)) {
+ let c = unit_class[u]
+ if (is_elite_unit(u)) {
if (game.hits[c] >= 2) {
gen_action_unit(u)
done = false
@@ -3388,7 +3377,7 @@ function gen_battle_hits() {
}
function apply_battle_hit(who) {
- game.hits[unit_class(who)] -= reduce_unit(who)
+ game.hits[unit_class[who]] -= reduce_unit(who)
}
states.battle_fire = {
@@ -3572,7 +3561,7 @@ function goto_pursuit_hits() {
function slowest_enemy_unit_speed(where) {
let r = 4
for_each_enemy_unit_in_hex(where, u => {
- let s = unit_speed(u)
+ let s = unit_speed[u]
if (s < r)
r = s
})
@@ -3582,7 +3571,7 @@ function slowest_enemy_unit_speed(where) {
function slowest_undisrupted_enemy_unit_speed(where) {
let r = 4
for_each_undisrupted_enemy_unit_in_hex(where, u => {
- let s = unit_speed(u)
+ let s = unit_speed[u]
if (s < r)
r = s
})
@@ -3595,7 +3584,7 @@ function can_rout_fire(verbose) {
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))
+ if (unit_speed[u] >= slowest && !is_unit_fired(u))
result = true
})
return result
@@ -3607,14 +3596,14 @@ function can_pursuit_fire(verbose) {
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))
+ 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)
+ let speed = unit_speed[who]
if (n === 2) {
let a = roll_die()
let b = roll_die()
@@ -3662,7 +3651,7 @@ states.pursuit_fire = {
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))
+ if (unit_speed[u] >= slowest && !is_unit_fired(u))
gen_action_unit(u)
})
// allow saving fire if there are shielded enemy units
@@ -3672,7 +3661,7 @@ states.pursuit_fire = {
unit(who) {
let slowest = slowest_undisrupted_enemy_unit_speed(game.pursuit)
set_unit_fired(who)
- let done = roll_pursuit_fire(who, (unit_speed(who) > slowest ? 2 : 1))
+ let done = roll_pursuit_fire(who, (unit_speed[who] > slowest ? 2 : 1))
if (done || !can_pursuit_fire(false))
goto_pursuit_hits()
},
@@ -3687,14 +3676,14 @@ states.rout_fire = {
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))
+ 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))
+ let done = roll_rout_fire(who, (unit_speed[who] > slowest ? 2 : 1))
if (done || !can_rout_fire(false))
goto_rout_hits()
},
@@ -3703,7 +3692,7 @@ states.rout_fire = {
function gen_pursuit_hits(normal_steps, elite_steps, iterate) {
let done = true
iterate(game.pursuit, u => {
- if (is_unit_elite(u)) {
+ if (is_elite_unit(u)) {
if (game.hits >= 2) {
gen_action_unit(u)
done = false
@@ -3835,18 +3824,17 @@ function end_buildup_discard() {
}
function init_buildup() {
- // TODO: fortress supply
- // TODO: assign fortress supply
-
game.buildup = {
// redeployment network
axis_network: axis_supply_network().slice(),
axis_line: axis_supply_line().slice(),
allied_network: allied_supply_network().slice(),
allied_line: allied_supply_line().slice(),
+
// extra cards purchased
axis_cards: 0,
allied_cards: 0,
+
// remaining port capacity for sea redeployment
bardia: 2,
benghazi: 2,
@@ -3855,6 +3843,9 @@ function init_buildup() {
}
function goto_buildup_supply_check() {
+ // TODO: fortress supply
+ // TODO: assign fortress supply
+
init_buildup()
for_each_axis_unit_on_map(u => {
@@ -3873,13 +3864,20 @@ function goto_buildup_supply_check() {
set_unit_supply(u, 0)
})
+ for_each_unit_on_map(u => {
+ if (is_unit_supplied(u) && is_unit_disrupted(u) && !is_battle_hex(unit_hex(u))) {
+ log(`Recovered at #${unit_hex(u)}.`)
+ clear_unit_disrupted(u)
+ }
+ })
+
log_br()
- resume_buildup_supply_check()
+ resume_buildup_eleminate_unsupplied()
}
-function resume_buildup_supply_check() {
- game.state = 'buildup_supply_check'
+function resume_buildup_eleminate_unsupplied() {
+ game.state = 'buildup_eleminate_unsupplied'
let done = true
for_each_friendly_unit_on_map(u => {
if (is_unit_unsupplied(u))
@@ -3889,14 +3887,14 @@ function resume_buildup_supply_check() {
if (is_axis_player()) {
log_br()
set_enemy_player()
- resume_buildup_supply_check()
+ resume_buildup_eleminate_unsupplied()
} else {
goto_buildup_point_determination()
}
}
}
-states.buildup_supply_check = {
+states.buildup_eleminate_unsupplied = {
prompt() {
view.prompt = `Buildup: Eliminate unsupplied units.`
for_each_friendly_unit_on_map(u => {
@@ -3905,9 +3903,9 @@ states.buildup_supply_check = {
})
},
unit(u) {
- log(`>eliminated at #${unit_hex(u)}`)
+ log(`Eliminated at #${unit_hex(u)}`)
eliminate_unit(u)
- resume_buildup_supply_check()
+ resume_buildup_eleminate_unsupplied()
},
}
@@ -3944,9 +3942,21 @@ function goto_buildup_point_determination() {
goto_buildup_reinforcements()
}
+function have_scheduled_reinforcements() {
+ let refit = friendly_refit()
+ for (let u = first_friendly_unit; u <= last_friendly_unit; ++u) {
+ let x = unit_hex(u)
+ if (x === refit || x === game.month || x === game.month + 1)
+ return true
+ }
+}
+
function goto_buildup_reinforcements() {
log_h2(game.active + " Buildup")
- game.state = 'buildup_reinforcements'
+ if (have_scheduled_reinforcements())
+ game.state = 'buildup_reinforcements'
+ else
+ goto_buildup_spending()
}
states.buildup_reinforcements = {
@@ -4335,7 +4345,7 @@ function begin_game() {
function find_axis_units(a) {
let list = []
for (let u = first_axis_unit; u <= last_axis_unit; ++u)
- if (units[u].appearance === a)
+ if (unit_appearance[u] === a)
list.push(u)
return list
}
@@ -4343,15 +4353,15 @@ function find_axis_units(a) {
function find_allied_units(a) {
let list = []
for (let u = first_allied_unit; u <= last_allied_unit; ++u)
- if (units[u].appearance === a)
+ if (unit_appearance[u] === a)
list.push(u)
return list
}
function setup_reinforcements(m) {
- for (let u = 0; u < units.length; ++u) {
- if (units[u].appearance === m) {
- if (m === 'M')
+ for (let u = 0; u < unit_count; ++u) {
+ if (unit_appearance[u] === m) {
+ if (m === "M")
set_unit_hex(u, MALTA)
else
set_unit_hex(u, hexdeploy + m)
@@ -4367,7 +4377,7 @@ function setup_units(where, steps, list) {
u = find_unit(u)
set_unit_hex(u, where)
if (steps < 0)
- set_unit_steps(u, units[u].steps + steps)
+ set_unit_steps(u, unit_start_steps[u] + steps)
else if (steps > 0)
set_unit_steps(u, steps)
}
@@ -4799,7 +4809,7 @@ exports.setup = function (seed, scenario, options) {
axis_bps: 0,
allied_bps: 0,
- units: new Array(units.length).fill(0),
+ units: new Array(unit_count).fill(0),
moved: [],
fired: [],
recover: [],
@@ -4903,7 +4913,7 @@ exports.query = function (state, current, q) {
return {
axis_supply: game.buildup.axis_network,
axis_supply_line: game.buildup.axis_line,
- allied_supply: game.buildup.allien_network,
+ allied_supply: game.buildup.allied_network,
allied_supply_line: game.buildup.allied_line,
}
} else {