From 736097bab6ce3d9b59f791b8dad0d7cd98c9d584 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Wed, 27 Jul 2022 19:12:49 +0200 Subject: Forced march. --- play.html | 7 +- play.js | 26 ++++-- rules.js | 272 ++++++++++++++++++++++++++++++++++++++------------------------ 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() { -- cgit v1.2.3