summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.html7
-rw-r--r--play.js26
-rw-r--r--rules.js272
3 files changed, 194 insertions, 111 deletions
diff --git a/play.html b/play.html
index c22f700..58532b8 100644
--- a/play.html
+++ b/play.html
@@ -154,7 +154,7 @@ header.your_turn { background-color: orange; }
/* TABLES */
-table { border-collapse: collapse; font-size: 12px; }
+table { border-collapse: collapse; font-size: 12px; user-select: none; }
td.blank { background-color: transparent; border: none }
td,th { border: 1px solid black; text-align: center; padding: 2px 4px; }
td { background-color: white; min-width: 20px; }
@@ -262,6 +262,11 @@ svg .hex.action {
stroke-width: 2;
}
+svg .hex.action.forced_march {
+ stroke: red;
+ stroke-width: 2;
+}
+
svg .hex.from {
fill: gold;
fill-opacity: 0.2;
diff --git a/play.js b/play.js
index 7b38c9a..c2a49da 100644
--- a/play.js
+++ b/play.js
@@ -173,6 +173,10 @@ function is_hex_action(hex) {
return !!(view.actions && view.actions.hex && view.actions.hex.includes(hex))
}
+function is_hex_forced_march_action(hex) {
+ return !!(view.actions && view.actions.forced_march && view.actions.forced_march.includes(hex))
+}
+
function is_hex_axis_supply(hex) {
return view.axis_supply[hex] > 0
}
@@ -232,6 +236,8 @@ function on_click_hex(evt) {
hide_supply()
if (send_action('hex', evt.target.hex))
evt.stopPropagation()
+ if (send_action('forced_march', evt.target.hex))
+ evt.stopPropagation()
}
}
@@ -373,13 +379,18 @@ function build_hexes() {
}
function add_hex(x, y) {
+ let sm_hex_w = hex_w - 8
+ let sm_hex_h = sm_hex_w / sqrt(3) * 2
+ let ww = sm_hex_w / 2
+ let aa = sm_hex_h / 2
+ let bb = sm_hex_h / 4
return [
- [ round(x), round(y-a) ],
- [ round(x+w), round(y-b) ],
- [ round(x+w), round(y+b) ],
- [ round(x), round(y+a) ],
- [ round(x-w), round(y+b) ],
- [ round(x-w), round(y-b) ]
+ [ round(x), round(y-aa) ],
+ [ round(x+ww), round(y-bb) ],
+ [ round(x+ww), round(y+bb) ],
+ [ round(x), round(y+aa) ],
+ [ round(x-ww), round(y+bb) ],
+ [ round(x-ww), round(y-bb) ]
].join(" ")
}
@@ -519,7 +530,8 @@ function update_map() {
}
if (ui.hexes[hex]) {
- ui.hexes[hex].classList.toggle("action", is_hex_action(hex))
+ ui.hexes[hex].classList.toggle("action", is_hex_action(hex) || is_hex_forced_march_action(hex))
+ ui.hexes[hex].classList.toggle("forced_march", is_hex_forced_march_action(hex))
ui.hexes[hex].classList.toggle("from", hex === view.from1 || hex === view.from2)
ui.hexes[hex].classList.toggle("to", hex === view.to1 || hex === view.to2)
ui.hexes[hex].classList.toggle("axis_control", is_hex_axis_controlled(hex))
diff --git a/rules.js b/rules.js
index 5283d9a..6bff7ab 100644
--- a/rules.js
+++ b/rules.js
@@ -1,18 +1,19 @@
"use strict"
-// TODO: select hexside for engaging
-
// TODO: fortress supply
// TODO: oasis supply
-// TODO: legal pass withdrawal moves (reduce supply net, withdraw from fortress attack)
// TODO: raiders
+// TODO: legal pass withdrawal moves (reduce supply net, withdraw from fortress attack)
-// TODO: MINEFIELDS
// 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?
@@ -1301,34 +1302,6 @@ function can_move_to(to, speed) {
return false
}
-function pick_path(to, speed) {
- let road = 4
- let next_cost = 15, next_road = 0
- if (path_cost[0][to] <= speed) {
- next_cost = path_cost[0][to]
- next_road = 0
- }
- if (road >= 1 && path_cost[1][to] <= speed + 1) {
- if (path_cost[1][to] <= next_cost) {
- next_cost = path_cost[1][to]
- next_road = 1
- }
- }
- if (road >= 2 && path_cost[2][to] <= speed + 2) {
- if (path_cost[2][to] <= next_cost) {
- next_cost = path_cost[2][to]
- next_road = 2
- }
- }
- if (road >= 4 && path_cost[4][to] <= speed + 4) {
- if (path_cost[4][to] <= next_cost) {
- next_cost = path_cost[4][to]
- next_road = 4
- }
- }
- return next_road
-}
-
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 => {
@@ -1340,13 +1313,12 @@ function max_speed_of_undisrupted_and_unmoved_friendly_unit_in_hex(from) {
}
function find_valid_regroup_destinations(from, rommel) {
- // TODO: forced march
let speed = max_speed_of_undisrupted_and_unmoved_friendly_unit_in_hex(from)
if (speed > 0) {
- search_move(from, speed + rommel)
+ search_move(from, speed + 1 + rommel)
for (let x of all_hexes)
if (!path_valid[x])
- if (can_move_to(x, speed + rommel))
+ if (can_move_to(x, speed + 1 + rommel))
path_valid[x] = 1
}
}
@@ -1798,9 +1770,9 @@ states.move = {
let speed = max_speed_of_undisrupted_and_unmoved_friendly_unit_in_hex(from)
if (speed > 0 && !has_enemy_unit(from)) {
// TODO: withdraw pass move
- search_move(from, speed + rommel1)
+ 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) + rommel1))
+ if (can_move_to(game.to1, unit_speed(u) + 1 + rommel1))
gen_action_unit(u)
})
}
@@ -1813,9 +1785,9 @@ states.move = {
let speed = max_speed_of_undisrupted_and_unmoved_friendly_unit_in_hex(from)
if (speed > 0 && !has_enemy_unit(from)) {
// TODO: withdraw pass move
- search_move(from, speed + rommel2)
+ 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) + rommel2))
+ if (can_move_to(game.to2, unit_speed(u) + 1 + rommel2))
gen_action_unit(u)
})
}
@@ -1847,9 +1819,9 @@ states.move = {
// Move
if (game.turn_option === 'pass')
- search_withdraw(game.selected, (rommel1 | rommel2))
+ search_withdraw(game.selected, 1 + (rommel1 | rommel2))
else
- search_move(unit_hex(game.selected), unit_speed(game.selected) + (rommel1 | rommel2))
+ search_move(unit_hex(game.selected), unit_speed(game.selected) + 1 + (rommel1 | rommel2))
gen_move()
}
@@ -1857,6 +1829,9 @@ states.move = {
unit(who) {
apply_select(who)
},
+ forced_march(to) {
+ apply_move(to)
+ },
hex(to) {
apply_move(to)
},
@@ -1914,25 +1889,40 @@ function gen_move() {
let from = unit_hex(game.selected)
if (!game.to1 && game.from1 === from) {
- for (let to of all_hexes)
- if (to != from && can_move_to(to, speed + rommel1))
- gen_action_hex(to)
+ for (let to of all_hexes) {
+ if (to != from) {
+ if (can_move_to(to, speed + rommel1))
+ gen_action_hex(to)
+ else if (can_move_to(to, speed + 1 + rommel1))
+ gen_action_forced_march(to)
+ }
+ }
}
if (!game.to2 && game.from2 === from) {
- for (let to of all_hexes)
- if (to != from && can_move_to(to, speed + rommel2))
- gen_action_hex(to)
+ for (let to of all_hexes) {
+ if (to != from) {
+ if (can_move_to(to, speed + rommel2))
+ gen_action_hex(to)
+ else if (can_move_to(to, speed + 1 + rommel2))
+ gen_action_forced_march(to)
+
+ }
+ }
}
if (game.to1 && is_hex_or_adjacent_to(from, game.from1)) {
if (can_move_to(game.to1, speed + rommel1))
gen_action_hex(game.to1)
+ else if (can_move_to(game.to1, speed + 1 + rommel1))
+ gen_action_forced_march(game.to1)
}
if (game.to2 && is_hex_or_adjacent_to(from, game.from2)) {
if (can_move_to(game.to2, speed + rommel2))
gen_action_hex(game.to2)
+ else if (can_move_to(game.to2, speed + 1 + rommel2))
+ gen_action_forced_march(game.to2)
}
}
@@ -1941,7 +1931,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) + 1 // forced march
push_undo()
@@ -1949,19 +1939,19 @@ function apply_move(to) {
if (!game.to1 && game.from1 === from)
if (can_move_to(to, speed + rommel1))
- return move_unit(who, to, speed + rommel1)
+ return move_unit(who, to, speed + rommel1, 1)
if (!game.to2 && game.from2 === from)
if (can_move_to(to, speed + rommel2))
- return move_unit(who, to, speed + rommel2)
+ return move_unit(who, to, speed + 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)
+ return move_unit(who, to, speed + 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)
+ return move_unit(who, to, speed + rommel2, 2)
}
// to check usable alternate paths to enter destination hex
@@ -1970,8 +1960,6 @@ function can_move_via(via, to, speed, road) {
let side = to_side_id(via, to)
let max_side = side_limit[side]
- console.log("can_move_via", via, to, speed, road, "=", cost)
-
// too far
if (cost + 1 > speed + road)
return false
@@ -1996,72 +1984,145 @@ function can_move_via(via, to, speed, road) {
return true
}
-function move_unit(who, to, speed) {
+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
+}
+
+function move_via(who, to, speed, move) {
let from = unit_hex(who)
- let road = pick_path(to, speed)
-
- if (has_enemy_unit(to)) {
- game.engage_via = []
- 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.engage_via.push(via)
- })
- console.log("ENGAGE", to, "FROM", game.engage_via)
- if (game.engage_via.length === 1) {
- engage_via(who, game.engage_via[0], to)
+ game.hexside = {
+ who: who,
+ via: [],
+ to: to,
+ 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)
+ })
+ return game.hexside.via.length === 1
+}
+
+function move_unit(who, to, speed, move) {
+ let from = unit_hex(who)
+
+ if (is_forced_march_move(from, to, speed + 1)) {
+ if (move_via(who, to, speed, move)) {
+ forced_march_via(who, game.hexside.via[0], to, move)
+ delete game.hexside
} else {
- game.engage_who = who
- game.engage_to = to
- game.state = 'engage'
- return
+ game.state = 'forced_march_via'
}
- } else {
+ }
+
+ else if (has_enemy_unit(to)) {
+ if (move_via(who, to, speed, move)) {
+ engage_via(who, game.hexside.via[0], to)
+ delete game.hexside
+ } else {
+ game.state = 'engage_via'
+ }
+ }
+
+ else {
log(`>from #${from} to #${to}`)
set_unit_moved(who)
set_unit_hex(who, to)
}
}
-states.engage = {
+states.forced_march_via = {
prompt() {
view.prompt = `Move: Select which hex side to cross.`
- view.selected = game.engage_who
- for (let x of game.engage_via)
+ view.selected = game.hexside.who
+ for (let x of game.hexside.via)
gen_action_hex(x)
},
hex(via) {
- engage_via(game.engage_who, via, game.engage_to)
- delete game.engage_via
- delete game.engage_who
- delete game.engage_to
+ forced_march_via(game.hexside.who, via, game.hexside.to, game.hexside.move)
+ delete game.hexside
game.state = 'move'
}
}
-function engage_via(who, via, to) {
- let from = unit_hex(who)
+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)
+ },
+ hex(via) {
+ engage_via(game.hexside.who, via, game.hexside.to)
+ delete game.hexside
+ game.state = 'move'
+ }
+}
- set_unit_moved(who)
- set_unit_hex(who, to)
+function forced_march_via(who, via, to, move) {
+ let from = unit_hex(who)
+ set_unit_moved(who)
+ set_unit_hex(who, via)
+
+ // remember where we should advance to / return to
+ if ((move === 1 && game.to1) || (move === 2 && game.to2))
+ game.forced[who] = to
+ else
+ game.forced[who] = [ to, from ]
+ // attempted force marches affect hexside limits
+ if (is_enemy_hex(to)) {
let side = to_side_id(via, to)
if (game.side_limit[side])
game.side_limit[side] = 2
else
game.side_limit[side] = 1
+ }
- claim_hexside_control(side)
- if (is_new_battle_hex(to)) {
- claim_hex_control_for_defender(to)
- set_add(game.active_battles, to)
- }
+ log(`>forced march from #${from} via #${via} to #${to}`)
+}
+
+function engage_via(who, via, to) {
+ let from = unit_hex(who)
+
+ set_unit_moved(who)
+ set_unit_hex(who, to)
+ let side = to_side_id(via, to)
+ if (game.side_limit[side])
+ game.side_limit[side] = 2
+ else
+ game.side_limit[side] = 1
+
+ claim_hexside_control(side)
+ if (is_new_battle_hex(to)) {
+ claim_hex_control_for_defender(to)
+ set_add(game.active_battles, to)
+ }
+
+ if (from !== via)
log(`>from #${from} via #${via} to #${to}`)
+ else
+ log(`>from #${from} to #${to}`)
}
// === RETREAT ===
@@ -2119,9 +2180,9 @@ function can_unit_retreat_group_move(who) {
function can_unit_retreat_regroup_move(who, to, rommel) {
if (game.turn_option === 'pass')
- return can_unit_disengage_and_withdraw_to(who, to, rommel)
+ return can_unit_disengage_and_withdraw_to(who, to, 1 + rommel)
else
- return can_unit_disengage_and_move_to(who, to, rommel)
+ return can_unit_disengage_and_move_to(who, to, 1 + rommel)
}
function is_friendly_hexside(side) {
@@ -2155,14 +2216,14 @@ function can_unit_disengage_and_move(who) {
return result
}
-function can_unit_disengage_and_withdraw_to(who, to, rommel) {
- search_withdraw_retreat(who, rommel)
- return can_move_to(to, unit_speed(who) + rommel)
+function can_unit_disengage_and_withdraw_to(who, to, extra) {
+ search_withdraw_retreat(who, extra)
+ return can_move_to(to, unit_speed(who) + extra)
}
-function can_unit_disengage_and_move_to(who, to, rommel) {
- search_move_retreat(unit_hex(who), unit_speed(who) + rommel)
- return can_move_to(to, unit_speed(who) + rommel)
+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)
}
function can_select_retreat_hex() {
@@ -2357,9 +2418,9 @@ states.retreat_move = {
let rommel2 = (game.rommel === 2) ? 1 : 0
gen_action_unit(game.selected)
if (game.turn_option === 'pass')
- search_withdraw_retreat(game.selected, (rommel1 | rommel2))
+ search_withdraw_retreat(game.selected, 1 + (rommel1 | rommel2))
else
- search_move_retreat(unit_hex(game.selected), unit_speed(game.selected) + (rommel1 | rommel2))
+ search_move_retreat(unit_hex(game.selected), unit_speed(game.selected) + 1 + (rommel1 | rommel2))
gen_move()
}
},
@@ -3935,6 +3996,7 @@ exports.setup = function (seed, scenario, options) {
// current turn option and selected moves
turn_option: null,
side_limit: {},
+ forced: {},
rommel: 0,
from1: 0,
to1: 0,
@@ -4020,6 +4082,10 @@ function gen_action_hex(x) {
gen_action('hex', x)
}
+function gen_action_forced_march(x) {
+ gen_action('forced_march', x)
+}
+
// === COMMON TEMPLATE ===
function random(range) {
@@ -4144,7 +4210,7 @@ function pop_undo() {
}
function clear_undo() {
- game.undo = []
+ // game.undo = []
}
function log_br() {