summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.js7
-rw-r--r--rules.js350
2 files changed, 320 insertions, 37 deletions
diff --git a/play.js b/play.js
index daf45aa..6f9ba86 100644
--- a/play.js
+++ b/play.js
@@ -764,7 +764,7 @@ function target_button(action) {
function on_update() {
if (!ui.loaded) {
- document.getElementById("prompt").textContent = "Waiting for map..."
+ document.getElementById("prompt").textContent = "ERROR"
return setTimeout(on_update, 500)
}
@@ -803,6 +803,11 @@ function on_update() {
action_button("retreat", "Retreat")
action_button("probe", "Probe")
+ action_button("replacement", "Replacement")
+ action_button("refit", "Refit")
+ action_button("minefield", "Minefield")
+ action_button("extra_supply_card", "Card")
+
action_button("group", "Group")
action_button("regroup", "Regroup")
diff --git a/rules.js b/rules.js
index 0bed6aa..1a0f83c 100644
--- a/rules.js
+++ b/rules.js
@@ -1,32 +1,29 @@
"use strict"
// TOOD: reveal/hide blocks (hexes)
+
// TODO: fortress supply
// TODO: oasis supply
-// TODO: raiders
-// TODO: minefields
+
+// TODO: RAIDERS
+// TODO: MINEFIELDS
+
// TODO: group move from queue holding box to base
+// TODO: 1942 malta group (reinforce, reduce supply card draw)
// TODO: legal pass withdrawal moves (reduce supply net, withdraw from fortress attack)
-// TODO: redeployment
-// TODO: return for refit
-// TODO: replacements
-// TODO: malta units
+// TODO: log summaries (deploy, rebuild, move, etc)
-// TODO: black hit outline in battles ("steploss/bad" action) and skip "apply 0 hits" step
+// RULES: for sea redeployment, can bases be "besieged"? (yes)
+// RULES: may units redeploying out of battle hex leave disrupted units behind to be routed? (no)
+// RULES: may units redeploying out of battle hex cross enemy controlled hexsides? (yes / doesn't matter)
+// RULES: if disrupted units are routed again during their "full enemy turn", can they still recover?
// RULES: may oasis supplied units refuse battle or withdraw to base?
-
-// RULES: may units redeploying out of battles cross enemy controlled hexsides?
-
-// RULES: return to refit land path -- how/why check?
-
// RULES: when is "fired" status cleared?
+// RULES: are minefields moved through (but not stopped at) revealed?
-// RULES: disrupted units routed again in second enemy turn, will they still recover?
-// assume yes, easy to change (remove from game.recover set if routed)
-
-// RULES: reveal minefields moved through (but not stopped at)?
+// TODO: black hit outline in battles ("steploss/bad" action) and skip "apply 0 hits" step
// ERRATA: forbid single-group regroup moves or convert to group moves after the fact,
// to prevent forced march abuse.
@@ -427,6 +424,11 @@ function reduce_unit(u) {
return hp
}
+function replace_unit(u) {
+ let lost = unit_lost_steps(u)
+ set_unit_lost_steps(u, lost - 1)
+}
+
// === UNIT DATA ===
function find_unit(name) {
@@ -1307,7 +1309,10 @@ function friendly_supply_network() {
// === PATHING ===
+// NOTE: we don't actually need path_from but we can use it to show paths taken in client
+// NOTE: we may need the path to reveal minefields moved through
const path_from = [ new Array(hexcount), new Array(hexcount), new Array(hexcount), null, new Array(hexcount) ]
+
const path_cost = [ new Array(hexcount), new Array(hexcount), new Array(hexcount), null, new Array(hexcount) ]
const path_valid = new Array(hexcount)
@@ -1328,19 +1333,17 @@ function print_path(who, from, to, road) {
// TODO: cache search results from previous invocation
function search_move(start, speed) {
- // Normal moves.
- search_init()
- search_move_bfs(path_from[0], path_cost[0], start, 0, speed, 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)
+ search_path_bfs(path_from[0], path_cost[0], start, 0, speed, false, null, null)
+ search_path_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, null, null)
+ search_path_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, null, null)
+ search_path_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, null, null)
}
function search_move_retreat(start, speed) {
- search_move_bfs(path_from[0], path_cost[0], start, 0, speed, 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)
+ search_path_bfs(path_from[0], path_cost[0], start, 0, speed, true, null, null)
+ search_path_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, null, null)
+ search_path_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, null, null)
+ search_path_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, null, null)
}
function search_withdraw(who, bonus) {
@@ -1349,10 +1352,10 @@ function search_withdraw(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)
- 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)
+ search_path_bfs(path_from[0], path_cost[0], start, 0, speed, false, sline, sdist)
+ search_path_bfs(path_from[1], path_cost[1], start, 1, speed + 1, false, sline, sdist)
+ search_path_bfs(path_from[2], path_cost[2], start, 2, speed + 2, false, sline, sdist)
+ search_path_bfs(path_from[4], path_cost[4], start, 4, speed + 4, false, sline, sdist)
}
function search_withdraw_retreat(who, bonus) {
@@ -1361,17 +1364,21 @@ function search_withdraw_retreat(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)
- 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)
+ search_path_bfs(path_from[0], path_cost[0], start, 0, speed, true, sline, sdist)
+ search_path_bfs(path_from[1], path_cost[1], start, 1, speed + 1, true, sline, sdist)
+ search_path_bfs(path_from[2], path_cost[2], start, 2, speed + 2, true, sline, sdist)
+ search_path_bfs(path_from[4], path_cost[4], start, 4, speed + 4, true, sline, sdist)
}
-function search_init() {
+function search_redeploy(start) {
+ search_path_redeploy_bfs(path_cost[0], start, 0, 100)
+ search_path_redeploy_bfs(path_cost[1], start, 1, 100)
+ search_path_redeploy_bfs(path_cost[2], start, 2, 100)
+ search_path_redeploy_bfs(path_cost[4], start, 4, 100)
}
// Breadth First Search
-function search_move_bfs(from, cost, start, road, max_cost, retreat, sline, sdist) {
+function search_path_bfs(from, cost, start, road, max_cost, retreat, sline, sdist) {
let path_enemy, friendly_sides
if (presence_invalid)
@@ -1467,6 +1474,75 @@ function search_move_bfs(from, cost, start, road, max_cost, retreat, sline, sdis
}
}
+function search_path_redeploy_bfs(cost, start, road) {
+ let path_enemy, friendly_network, enemy_network
+
+ if (presence_invalid)
+ update_presence()
+ if (is_axis_player()) {
+ path_enemy = presence_allied
+ friendly_network = game.buildup.axis_network
+ enemy_network = game.buildup.allied_network
+ } else {
+ path_enemy = presence_axis
+ friendly_network = game.buildup.allied_network
+ enemy_network = game.buildup.axis_network
+ }
+
+ cost.fill(63)
+ cost[start] = 0
+
+ if (hex_road[start] < road)
+ return
+
+ let queue = [ start << 6 ]
+
+ while (queue.length > 0) {
+ let item = queue.shift()
+ let here = item >> 6
+ let here_cost = item & 63
+ let next_cost = here_cost + 1
+
+ for (let s = 0; s < 6; ++s) {
+ let next = here + hexnext[s]
+
+ // can't go off-map
+ if (next < first_hex || next > last_hex || !hex_exists[next])
+ continue
+
+ // already seen
+ if (cost[next] < 63)
+ continue
+
+ let side = to_side(here, next, s)
+ let max_side = side_limit[side]
+
+ // can't cross this hexside
+ if (max_side === 0)
+ continue
+
+ // must stay on road for current bonus
+ if (side_road[side] < road)
+ continue
+
+ // must stay within supply network, and not enter enemy network
+ if (!friendly_network[next] || enemy_network[next])
+ continue
+
+ // may not move into or through battle hexes
+ let next_enemy = path_enemy[next]
+ if (next_enemy)
+ continue
+
+ cost[next] = next_cost
+
+ // don't care about distance (need to find home base for refit)
+ if (next_cost < 63)
+ queue.push(next << 6 | next_cost)
+ }
+ }
+}
+
function can_move_to(to, speed) {
if (path_cost[4][to] <= speed + 4)
return true
@@ -4007,6 +4083,8 @@ function apply_reinforcements() {
log(`>${early} early`)
}
+// === BUILDUP - SPENDING BPS ===
+
function goto_buildup_spending() {
game.state = 'spending_bps'
}
@@ -4025,11 +4103,204 @@ function pay_bps(n) {
game.allied_bps -= n
}
+function count_secret_minefields() {
+ let n = 0
+ if (is_axis_player()) {
+ let network = game.buildup.axis_network
+ for (let x of game.axis_minefields)
+ if (network[x])
+ ++n
+ } else {
+ let network = game.buildup.allied_network
+ for (let x of game.allied_minefields)
+ if (network[x])
+ ++n
+ }
+ return n
+}
+
+function replacement_cost(who) {
+ let cost = (unit_class[who] === INFANTRY) ? (unit_speed[who] > 1) ? 2 : 1 : 3
+ if (is_elite_unit(who))
+ return 2 * cost
+ return cost
+}
+
+function can_redeploy_from(from) {
+ if (is_battle_hex(from)) {
+ let n = 0
+ // TODO: can leave disrupted units behind to be routed?
+ for_each_undisrupted_friendly_unit_in_hex(from, u => {
+ n++
+ })
+ return n > 1
+ }
+ return true
+}
+
+function is_controlled_port(where) {
+ if (where === BARDIA || where === BENGHAZI || where === TOBRUK)
+ return is_fortress_friendly_controlled(where)
+ console.log("not a porT", where)
+ return true
+}
+
+function sea_redeploy_cost(from, to) {
+ if ((from === BARDIA || to === BARDIA) && game.buildup.bardia === 0)
+ return 0
+ if ((from === BENGHAZI || to === BENGHAZI) && game.buildup.benghazi === 0)
+ return 0
+ if ((from === TOBRUK || to === TOBRUK) && game.buildup.tobruk === 0)
+ return 0
+ if (is_controlled_port(from) && is_controlled_port(to)) {
+ let b_from = is_fortress_besieged(from)
+ let b_to = is_fortress_besieged(to)
+ if (b_from && b_to)
+ return 0
+ if (b_from || b_to) {
+ if (is_axis_player())
+ return 0
+ return 4
+ }
+ return 1
+ }
+ return 0
+}
+
+function gen_sea_redeployment(from, to) {
+ let cost = sea_redeploy_cost(from, to)
+ if (cost && cost <= available_bps())
+ gen_action_hex(to)
+}
+
+function gen_spending_bps() {
+ let who = game.selected
+ let bps = available_bps()
+ let base = friendly_base()
+ let from = unit_hex(who)
+
+ // Receive replacement in base
+ if (from === base) {
+ if (unit_lost_steps(who) > 0 && bps <= replacement_cost(who))
+ view.actions.replacement = 1
+ else
+ view.actions.replacement = 0
+ }
+
+ // Quick deselect
+ gen_action_hex(from)
+
+ if (can_redeploy_from(from)) {
+ search_redeploy(from)
+
+ // Return for Refit
+ if (from !== base) {
+ if (can_move_to(base, 63))
+ gen_action_hex(friendly_refit())
+ }
+
+ // Redeployment
+ if (bps > 0) {
+ for (let x of all_hexes)
+ if (x !== from && can_move_to(x, 2))
+ gen_action_hex(x)
+ }
+
+ // Sea Redeployment
+ if (from === base) {
+ gen_sea_redeployment(base, BARDIA)
+ gen_sea_redeployment(base, BENGHAZI)
+ gen_sea_redeployment(base, TOBRUK)
+ }
+ if (from === BARDIA) {
+ gen_sea_redeployment(BARDIA, BENGHAZI)
+ gen_sea_redeployment(BARDIA, TOBRUK)
+ gen_sea_redeployment(BARDIA, base)
+ }
+ if (from === BENGHAZI) {
+ gen_sea_redeployment(BENGHAZI, BARDIA)
+ gen_sea_redeployment(BENGHAZI, TOBRUK)
+ gen_sea_redeployment(BENGHAZI, base)
+ }
+ if (from === TOBRUK) {
+ gen_sea_redeployment(TOBRUK, BARDIA)
+ gen_sea_redeployment(TOBRUK, BENGHAZI)
+ gen_sea_redeployment(TOBRUK, base)
+ }
+ }
+}
+
states.spending_bps = {
prompt() {
view.prompt = `Buildup: Spend buildup points (${available_bps()} remain).`
+ if (game.selected < 0) {
+ let bps = available_bps()
+ if (count_secret_minefields() >= 2 || bps >= 15)
+ gen_action('minefield')
+ if (bps >= 10)
+ gen_action('extra_supply_card')
+ for_each_friendly_unit_on_map(u => {
+ if (!is_unit_disrupted(u))
+ gen_action_unit(u)
+ })
+ } else {
+ gen_action_unit(game.selected)
+ gen_spending_bps()
+ }
gen_action('end_buildup')
},
+ unit(who) {
+ if (game.selected < 0)
+ push_undo()
+ apply_select(who)
+ },
+ extra_supply_card() {
+ push_undo()
+ log(`Purchased extra supply card.`)
+ pay_bps(10)
+ },
+ minefield() {
+ push_undo()
+ game.state = 'minefield'
+ },
+ replacement() {
+ log(`Replaced unit.`)
+ replace_unit(game.selected)
+ pay_bps(replacement_cost(game.selected))
+ },
+ refit() {
+ log(`Returned for Refit.`)
+ set_unit_hex(pop_selected(), friendly_refit())
+ },
+ hex(to) {
+ let who = game.selected
+ let from = unit_hex(who)
+ if (to === from) {
+ game.selected = -1
+ } else if (to === friendly_refit()) {
+ log(`Returned for Refit.`)
+ set_unit_hex(pop_selected(), friendly_refit())
+ } else {
+ search_redeploy(from)
+ if (can_move_to(to, 2)) {
+ log(`Redeployed to #${to}.`)
+ pay_bps(1)
+ } else {
+ log(`Sea Redeployed to #${to}.`)
+ if (is_fortress_besieged(from) || is_fortress_besieged(to))
+ pay_bps(4)
+ else
+ pay_bps(1)
+ if (from === BARDIA || to === BARDIA)
+ game.buildup.bardia--
+ if (from === BENGHAZI || to === BENGHAZI)
+ game.buildup.benghazi--
+ if (from === TOBRUK || to === TOBRUK)
+ game.buildup.tobruk--
+ }
+ set_unit_hex(who, to)
+ }
+ },
end_buildup() {
clear_undo()
@@ -4053,6 +4324,13 @@ function end_buildup_spending() {
}
}
+states.minefield = {
+ prompt() {
+ view.prompt = `Buildup: Build a minefield. (TODO!)`
+ // Tear down two existing, or pay 15
+ },
+}
+
function goto_buildup_resupply() {
log_h2("Resupply")
log(`Shuffled supply cards.`)