diff options
-rw-r--r-- | play.html | 74 | ||||
-rw-r--r-- | play.js | 24 | ||||
-rw-r--r-- | rules.js | 549 | ||||
-rw-r--r-- | tools/genstrike.js | 239 |
4 files changed, 736 insertions, 150 deletions
@@ -149,34 +149,34 @@ body.Teutons #plan_actions .russian { display: none } #battle.defender #array_garrison { top: 172px; left: 8px; } #battle.defender #array_attacker_reserves { top: 4px; left: 8px; } #battle.defender #array_defender_reserves { bottom: 4px; left: 8px; } -#battle.defender #array_attacker_x { top: 96px; left: 32px; } -#battle.defender #array_attacker_c { top: 96px; left: 162px; } -#battle.defender #array_attacker_y { top: 96px; right: 32px; } -#battle.defender #array_defender_x { top: 220px; left: 32px; } -#battle.defender #array_defender_c { top: 220px; left: 162px; } -#battle.defender #array_defender_y { top: 220px; right: 32px; } -#battle.defender #array_reserve_x { bottom: 60px; left: 4px; } -#battle.defender #array_reserve_c { bottom: 60px; right: 200px; } -#battle.defender #array_reserve_y { bottom: 60px; right: 4px; } -#battle.defender #array_sally_x { bottom: 48px; left: 64px; } -#battle.defender #array_sally_c { bottom: 48px; left: 200px; } -#battle.defender #array_sally_y { bottom: 48px; right: 64px; } +#battle.defender #array_a1 { top: 96px; left: 32px; } +#battle.defender #array_a2 { top: 96px; left: 162px; } +#battle.defender #array_a3 { top: 96px; right: 32px; } +#battle.defender #array_d1 { top: 220px; left: 32px; } +#battle.defender #array_d2 { top: 220px; left: 162px; } +#battle.defender #array_d3 { top: 220px; right: 32px; } +#battle.defender #array_rd1 { bottom: 60px; left: 4px; } +#battle.defender #array_rd2 { bottom: 60px; right: 200px; } +#battle.defender #array_rd3 { bottom: 60px; right: 4px; } +#battle.defender #array_sa1 { bottom: 48px; left: 64px; } +#battle.defender #array_sa2 { bottom: 48px; left: 200px; } +#battle.defender #array_sa3 { bottom: 48px; right: 64px; } #battle.attacker #array_garrison { bottom: 172px; left: 8px; } #battle.attacker #array_attacker_reserves { bottom: 4px; left: 8px; } #battle.attacker #array_defender_reserves { top: 4px; left: 8px; } -#battle.attacker #array_attacker_x { bottom: 96px; right: 32px; } -#battle.attacker #array_attacker_c { bottom: 96px; left: 162px; } -#battle.attacker #array_attacker_y { bottom: 96px; left: 32px; } -#battle.attacker #array_defender_x { bottom: 220px; right: 32px; } -#battle.attacker #array_defender_c { bottom: 220px; left: 162px; } -#battle.attacker #array_defender_y { bottom: 220px; left: 32px; } -#battle.attacker #array_reserve_x { top: 60px; right: 4px; } -#battle.attacker #array_reserve_c { top: 60px; left: 200px; } -#battle.attacker #array_reserve_y { top: 60px; left: 4px; } -#battle.attacker #array_sally_x { top: 48px; right: 64px; } -#battle.attacker #array_sally_c { top: 48px; right: 200px; } -#battle.attacker #array_sally_y { top: 48px; left: 64px; } +#battle.attacker #array_a1 { bottom: 96px; right: 32px; } +#battle.attacker #array_a2 { bottom: 96px; left: 162px; } +#battle.attacker #array_a3 { bottom: 96px; left: 32px; } +#battle.attacker #array_d1 { bottom: 220px; right: 32px; } +#battle.attacker #array_d2 { bottom: 220px; left: 162px; } +#battle.attacker #array_d3 { bottom: 220px; left: 32px; } +#battle.attacker #array_rd1 { top: 60px; right: 4px; } +#battle.attacker #array_rd2 { top: 60px; left: 200px; } +#battle.attacker #array_rd3 { top: 60px; left: 4px; } +#battle.attacker #array_sa1 { top: 48px; right: 64px; } +#battle.attacker #array_sa2 { top: 48px; right: 200px; } +#battle.attacker #array_sa3 { top: 48px; left: 64px; } #battle > div { position: absolute; @@ -1239,21 +1239,21 @@ body.shift .mustered_vassals { <div id="hand" class="hand"></div> <div id="battle" class="attacker"> -<div class="garrison" id="array_defender_garrison"></div> +<div class="garrison" id="array_garrison"></div> <div class="reserves" id="array_attacker_reserves"></div> <div class="reserves" id="array_defender_reserves"></div> -<div class="array" id="array_attacker_x"></div> -<div class="array" id="array_attacker_c"></div> -<div class="array" id="array_attacker_y"></div> -<div class="array" id="array_defender_x"></div> -<div class="array" id="array_defender_c"></div> -<div class="array" id="array_defender_y"></div> -<div class="array" id="array_reserve_x"></div> -<div class="array" id="array_reserve_c"></div> -<div class="array" id="array_reserve_y"></div> -<div class="array" id="array_sally_x"></div> -<div class="array" id="array_sally_c"></div> -<div class="array" id="array_sally_y"></div> +<div class="array" id="array_a1"></div> +<div class="array" id="array_a2"></div> +<div class="array" id="array_a3"></div> +<div class="array" id="array_d1"></div> +<div class="array" id="array_d2"></div> +<div class="array" id="array_d3"></div> +<div class="array" id="array_rd1"></div> +<div class="array" id="array_rd2"></div> +<div class="array" id="array_rd3"></div> +<div class="array" id="array_sa1"></div> +<div class="array" id="array_sa2"></div> +<div class="array" id="array_sa3"></div> </div> </div> @@ -474,18 +474,18 @@ const ui = { battle_garrison: document.getElementById("array_garrison"), battle: document.getElementById("battle"), battle_array: [ - document.getElementById("array_attacker_x"), - document.getElementById("array_attacker_c"), - document.getElementById("array_attacker_y"), - document.getElementById("array_defender_x"), - document.getElementById("array_defender_c"), - document.getElementById("array_defender_y"), - document.getElementById("array_reserve_x"), - document.getElementById("array_reserve_c"), - document.getElementById("array_reserve_y"), - document.getElementById("array_sally_x"), - document.getElementById("array_sally_c"), - document.getElementById("array_sally_y"), + document.getElementById("array_a1"), + document.getElementById("array_a2"), + document.getElementById("array_a3"), + document.getElementById("array_d1"), + document.getElementById("array_d2"), + document.getElementById("array_d3"), + document.getElementById("array_rd1"), + document.getElementById("array_rd2"), + document.getElementById("array_rd3"), + document.getElementById("array_sa1"), + document.getElementById("array_sa2"), + document.getElementById("array_sa3"), ], } @@ -21,6 +21,9 @@ const data = require("./data.js") +// packed strike and hit group data +const GROUPS = [[[0,0,0,0,0,0,0,0,0,[[8,1]],[[8,2]],[[8,3]],[[8,4]],[[8,5]],[[8,2]],[[8,7]],0,[[16,1]],[[16,2]],[[16,3]],[[16,4]],[[16,1]],[[16,6]],[[16,7]],0,[[24,1]],[[24,2]],[[8,1],[16,2]],[[24,4]],[[24,1]],[[24,2]],[[8,1],[16,6]],0,[[32,1]],[[32,2]],[[32,2]],[[32,4]],[[32,5]],[[32,6]],[[32,7]],0,[[40,1]],[[40,2]],[[8,3],[32,2]],[[40,4]],[[8,1],[32,4]],[[8,2],[32,6]],[[8,3],[32,6]],0,[[48,1]],[[48,2]],[[48,2]],[[48,4]],[[16,1],[32,4]],[[16,2],[32,4]],[[16,3],[32,4]],0,[[56,1]],[[56,2]],[[8,1],[48,2]],[[56,4]],[[24,1],[32,4]],[[24,2],[32,4]],[[8,1],[16,2],[32,4]]],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[16,4]],0,0,0,0,0,0,0,[[8,1],[16,4]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[48,4]],0,0,0,0,0,0,0,[[8,1],[48,4]],0,0]],[[0,0,0,0,0,0,0,0,0,[[1,8]],[[2,8]],[[3,8]],[[4,8]],[[5,8]],[[6,8]],[[7,8]],0,[[1,16]],[[2,16]],[[3,16]],[[4,16]],[[5,16]],[[6,16]],[[7,16]],0,[[1,24]],[[2,24]],[[1,8],[2,16]],[[4,16]],[[1,24],[4,16]],[[6,16]],[[1,8],[6,16]],0,[[1,32]],[[2,32]],[[3,32]],[[4,32]],[[5,32]],[[6,32]],[[7,32]],0,[[1,40]],[[2,8]],[[3,8]],[[4,40]],[[1,8],[4,32]],[[2,8],[4,32]],[[3,8],[4,32]],0,[[1,16]],[[2,48]],[[3,16]],[[4,48]],[[1,16],[4,48]],[[2,16],[4,32]],[[3,16],[4,32]],0,[[1,56]],[[2,56]],[[1,8],[2,48]],[[4,56]],[[1,24],[4,48]],[[2,24],[4,32]],[[1,8],[2,16],[4,32]]],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[2,32]],[[1,8],[2,32]],0,0,[[6,32]],[[1,8],[6,32]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]] + const TODO = false const BOTH = "Both" @@ -92,41 +95,39 @@ const SHIP = 6 const asset_type_name = [ "prov", "coin", "loot", "cart", "sled", "boat", "ship" ] // battle array -const ARRAY_AX = 0 // attackers -const ARRAY_AC = 1 -const ARRAY_AY = 2 -const ARRAY_DX = 3 // defenders -const ARRAY_DC = 4 -const ARRAY_DY = 5 -const ARRAY_RX = 6 // reserves vs relief sallying -const ARRAY_RC = 7 -const ARRAY_RY = 8 -const ARRAY_SX = 9 // relief sallying -const ARRAY_SC = 10 -const ARRAY_SY = 11 +const A1 = 0 // attackers +const A2 = 1 +const A3 = 2 +const D1 = 3 // defenders +const D2 = 4 +const D3 = 5 +const SA1 = 6 // relief sally: attackers +const SA2 = 7 +const SA3 = 8 +const RD1 = 9 // relief sally: reserve defenders +const RD2 = 10 +const RD3 = 11 const battle_array_name = [ - "Attacking Right", - "Attacking Center", - "Attacking Left", - "Defending Left", - "Defending Center", - "Defending Right", - "Reserves Left", - "Reserves Center", - "Reserves Right", - "Sallying Left", - "Sallying Center", - "Sallying Right", + "A1", "A2", "A2", + "D1", "D2", "D3", + "SA1", "SA2", "SA3", + "RD1", "RD2", "RD3", ] // battle steps -const STEP_DEF_ARCHERY = 0 -const STEP_ATK_ARCHERY = 1 -const STEP_DEF_HORSE = 2 -const STEP_ATK_HORSE = 3 -const STEP_DEF_FOOT = 4 -const STEP_ATK_FOOT = 5 +const BATTLE_STEP_DEF_ARCHERY = 0 +const BATTLE_STEP_ATK_ARCHERY = 1 +const BATTLE_STEP_DEF_HORSE = 2 +const BATTLE_STEP_ATK_HORSE = 3 +const BATTLE_STEP_DEF_FOOT = 4 +const BATTLE_STEP_ATK_FOOT = 5 + +// storm steps +const STORM_STEP_DEF_ARCHERY = 0 +const STORM_STEP_ATK_ARCHERY = 1 +const STORM_STEP_DEF_MELEE = 2 +const STORM_STEP_ATK_MELEE = 3 const battle_step_name = [ "Defending Archery", @@ -137,6 +138,13 @@ const battle_step_name = [ "Attacking Foot", ] +const storm_step_name = [ + "Defending Archery", + "Attacking Archery", + "Defending Melee", + "Attacking Melee", +] + function find_card(name) { return data.cards.findIndex((x) => x.name === name) } @@ -2896,6 +2904,7 @@ function count_global_capabilities() { } function goto_capability_discard() { + console.log("capability_discard", game.active, count_global_capabilities(), count_mustered_lords()) if (count_global_capabilities() > count_mustered_lords()) game.state = "capability_discard" else @@ -3939,7 +3948,7 @@ function goto_surrender() { let here = get_lord_locale(game.command) if (count_besieged_lords(here) === 0) game.state = "surrender" - else + else build_siegeworks() } @@ -4777,7 +4786,6 @@ function start_battle() { reserves: [], routed: [], step: 0, - hits: 0, loser: 0, } @@ -4794,6 +4802,9 @@ function start_battle() { // === BATTLE: BATTLE ARRAY === +// TODO (option A): order - attacking array, defending array, relief sally, reserve defender +// TODO (option B): order - attacking array + relief sally, defending array + reserve defender + function has_reserves() { for (let lord of game.battle.reserves) if (is_friendly_lord(lord)) @@ -4848,21 +4859,22 @@ function goto_attacker_battle_array() { states.attacker_battle_array = { prompt() { view.prompt = "Battle Array: Position your lords." + let array = game.battle.array prompt_array_lord() if (game.who !== NOBODY) { - if (array[ARRAY_AC] === NOBODY) { - gen_action_array(ARRAY_AC) + if (array[A2] === NOBODY) { + gen_action_array(A2) } else { - if (array[ARRAY_AX] === NOBODY) - gen_action_array(ARRAY_AX) - if (array[ARRAY_AY] === NOBODY) - gen_action_array(ARRAY_AY) + if (array[A1] === NOBODY) + gen_action_array(A1) + if (array[A3] === NOBODY) + gen_action_array(A3) } } - if (!has_reserves() || (array[ARRAY_AC] >= 0 && array[ARRAY_AX] >= 0 && array[ARRAY_AY] >= 0)) + if (!has_reserves() || (array[A1] !== NOBODY && array[A2] !== NOBODY && array[A3] !== NOBODY)) view.actions.end_array = 1 }, battle_lord: action_select_lord, @@ -4879,45 +4891,44 @@ function goto_defender_battle_array() { game.who = NOBODY let n = count_reserves() if (n === 1) { - game.battle.array[ARRAY_DC] = pop_first_reserve() - goto_attacker_events() + game.battle.array[D2] = pop_first_reserve() + goto_relief_sally() } if (n === 0) { - goto_attacker_events() + goto_relief_sally() } } states.defender_battle_array = { prompt() { view.prompt = "Battle Array: Position your lords." - let array = game.battle.array prompt_array_lord() if (game.who !== NOBODY) { - if (array[ARRAY_DC] === NOBODY) { - gen_action_array(ARRAY_DC) - } else if (array[ARRAY_AX] !== NOBODY && array[ARRAY_AY] === NOBODY && array[ARRAY_DX] === NOBODY) { - gen_action_array(ARRAY_DX) - } else if (array[ARRAY_AX] === NOBODY && array[ARRAY_AY] !== NOBODY && array[ARRAY_DY] === NOBODY) { - gen_action_array(ARRAY_DY) + if (array[D2] === NOBODY) { + gen_action_array(D2) + } else if (array[A1] !== NOBODY && array[A3] === NOBODY && array[D1] === NOBODY) { + gen_action_array(D1) + } else if (array[A1] === NOBODY && array[A3] !== NOBODY && array[D3] === NOBODY) { + gen_action_array(D3) } else { - if (array[ARRAY_DX] === NOBODY) - gen_action_array(ARRAY_DX) - if (array[ARRAY_DY] === NOBODY) - gen_action_array(ARRAY_DY) + if (array[D1] === NOBODY) + gen_action_array(D1) + if (array[D3] === NOBODY) + gen_action_array(D3) } } - if (!has_reserves() || (array[ARRAY_DC] >= 0 && array[ARRAY_DX] >= 0 && array[ARRAY_DY] >= 0)) + if (!has_reserves() || (array[D1] !== NOBODY && array[D2] !== NOBODY && array[D3] !== NOBODY)) view.actions.end_array = 1 }, battle_lord: action_select_lord, array: action_array_lord, end_array() { clear_undo() - goto_attacker_events() + goto_relief_sally() }, } @@ -4932,16 +4943,17 @@ function is_lord_arrayed(lord) { function goto_relief_sally() { set_active_attacker() if (has_besieged_friendly_lord(game.battle.where)) { - game.state = "attacker_relief_sally" + game.state = "relief_sally" game.who = NOBODY } else { goto_battle_rounds() } } -states.attacker_relief_sally = { +states.relief_sally = { prompt() { view.prompt = "Battle: Relief Sally" + let array = game.battle.array // NOTE: max 3 lords stronghold so there's always room for all to sally @@ -4949,7 +4961,6 @@ states.attacker_relief_sally = { let here = game.battle.where // RULES: can lower lords sally without lieutenant? for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) { - console.log("relsal", lord, here, get_lord_locale(lord), is_lord_besieged(lord)) if (get_lord_locale(lord) === here && is_lord_besieged(lord)) { if (!is_lord_arrayed(lord)) gen_action_lord(lord) @@ -4957,18 +4968,17 @@ states.attacker_relief_sally = { } view.actions.end_sally = 1 } else { - let array = game.battle.array - if (array[ARRAY_SC] === NOBODY) { - gen_action_array(ARRAY_SC) - } else if (array[ARRAY_DX] !== NOBODY && array[ARRAY_DY] === NOBODY && array[ARRAY_SX] === NOBODY) { - gen_action_array(ARRAY_SX) - } else if (array[ARRAY_DX] === NOBODY && array[ARRAY_DY] !== NOBODY && array[ARRAY_SY] === NOBODY) { - gen_action_array(ARRAY_SY) + if (array[SA2] === NOBODY) { + gen_action_array(SA2) + } else if (array[D1] !== NOBODY && array[D3] === NOBODY && array[SA1] === NOBODY) { + gen_action_array(SA1) + } else if (array[D1] === NOBODY && array[D3] !== NOBODY && array[SA3] === NOBODY) { + gen_action_array(SA3) } else { - if (array[ARRAY_SX] === NOBODY) - gen_action_array(ARRAY_SX) - if (array[ARRAY_SY] === NOBODY) - gen_action_array(ARRAY_SY) + if (array[SA1] === NOBODY) + gen_action_array(SA1) + if (array[SA3] === NOBODY) + gen_action_array(SA3) } } }, @@ -4986,58 +4996,52 @@ states.attacker_relief_sally = { end_sally() { clear_undo() game.who = NOBODY - goto_defender_relief_sally() + goto_reserve_defenders() }, } -function goto_defender_relief_sally() { +function goto_reserve_defenders() { set_active_defender() let array = game.battle.array - if (has_reserves() && (array[ARRAY_SC] !== NOBODY || array[ARRAY_SX] !== NOBODY || array[ARRAY_SY] !== NOBODY)) - game.state = "defender_relief_sally" + if (has_reserves() && (array[SA1] !== NOBODY || array[SA2] !== NOBODY || array[SA3] !== NOBODY)) + game.state = "reserve_defenders" else - goto_battle_rounds() + goto_attacker_events() } -states.defender_relief_sally = { +states.reserve_defenders = { prompt() { view.prompt = "Battle: Array reserves against sallying lords." + let array = game.battle.array - // NOTE: max 3 in reserve (3 already deployed on front) so always room for all - - let done = true - for (let lord of game.battle.reserves) { - if (is_friendly_lord(lord) && !is_lord_arrayed(lord)) { - done = false + for (let lord of game.battle.reserves) + if (is_friendly_lord(lord) && !is_lord_arrayed(lord)) if (lord !== game.who) gen_action_battle_lord(lord) - } - } if (game.who !== NOBODY) { - let array = game.battle.array - if (array[ARRAY_RC] === NOBODY) { - gen_action_array(ARRAY_RC) - } else if (array[ARRAY_SX] !== NOBODY && array[ARRAY_SY] === NOBODY && array[ARRAY_RX] === NOBODY) { - gen_action_array(ARRAY_RX) - } else if (array[ARRAY_SX] === NOBODY && array[ARRAY_SY] !== NOBODY && array[ARRAY_RY] === NOBODY) { - gen_action_array(ARRAY_RY) + if (array[RD2] === NOBODY) { + gen_action_array(RD2) + } else if (array[SA1] !== NOBODY && array[SA3] === NOBODY && array[RD1] === NOBODY) { + gen_action_array(RD1) + } else if (array[SA1] === NOBODY && array[SA3] !== NOBODY && array[RD3] === NOBODY) { + gen_action_array(RD3) } else { - if (array[ARRAY_RX] === NOBODY) - gen_action_array(ARRAY_RX) - if (array[ARRAY_RY] === NOBODY) - gen_action_array(ARRAY_RY) + if (array[SA1] !== NOBODY) + gen_action_array(RD1) + if (array[SA3] !== NOBODY) + gen_action_array(RD3) } } - if (done) + if (!has_reserves() || (array[RD1] !== NOBODY && array[RD2] !== NOBODY && array[RD3] !== NOBODY)) view.actions.end_array = 1 }, battle_lord: action_select_lord, array: action_array_lord, end_array() { clear_undo() - goto_battle_rounds() + goto_attacker_events() }, } @@ -5052,7 +5056,7 @@ function goto_attacker_events() { function goto_defender_events() { set_active_defender() log("TODO defender events") - goto_relief_sally() + goto_battle_rounds() } // === BATTLE: CONCEDE THE FIELD === @@ -5082,7 +5086,7 @@ states.concede = { goto_reposition() } else { set_active_enemy() - if (game.active === game.attacker) + if (game.active === game.battle.attacker) goto_reposition() } } @@ -5090,23 +5094,366 @@ states.concede = { // === BATTLE: REPOSITION === +// 1 - If all SA routed, RD to reserve +// 2 - If all empty front D, all RD to front +// 3 - If any empty front D, reserve to front +// 4 - If front center empty, side to center +// 5 - If rear center empty, side to center + +// RULES: Repositioning of SA/DR units - advance / center + function goto_reposition() { log("TODO reposition") + goto_start_strike() +} + +// === BATTLE: STRIKE === + +const STEP_ARRAY = [ + [ D1, D2, D3, RD1, RD2, RD3 ], + [ A1, A2, A3, SA1, SA2, SA3 ], +] + +// Segment strikers into groups according to flanking situation. +// S picks group to strike. +// T picks lord to apply hits. +// If routed, resume hits on next lord in same group (T's choice). +// If routed and multiple groups can be next target, S's choice of target. +// If routed, S or T chooses next lord (in same group + +// interrupt for enemy to select flank to attack when center has hits left after center is routed + +// RULES: Order of applying mixed archery hits? + +/* + strike steps: + 1) calculate hits per lord + 2) combine flanking attacks and assign to section - center choose + 3) apply hits to lords - choose lord to take all hits from one group + 4) roll walls + 5) assign hits to units + 6) roll by hit + 7) if rout, reassign remaining hits (striking player chooses) + 7a) front left to front center THEN front right + 7b) front right to front center THEN front left + 7c) front center to front left OR front right + 7d) sallying attackers (as one) to any reserve defender THEN any front + + 8) goto 3 +*/ + +function pack_battle_array_front() { + let x = 0 + for (let i = 0; i < 6; ++i) + if (game.battle.array[i] >= 0) + x |= (1 << i) + return x +} + +function pack_battle_array_rear() { + let x = 0 + for (let i = 0; i < 6; ++i) + if (game.battle.array[i+6] >= 0) + x |= (1 << i) + return x +} + +function unpack_group_hits(g, offset) { + for (let i = 0; i < 6; ++i) { + if ((g >> i) & 1) { + if (game.battle.ah1[i+offset] > 0) + return true + if (game.battle.ah2[i+offset] > 0) + return true + } + } + return false +} + +function unpack_group(g, offset) { + let list = [] + for (let i = 0; i < 6; ++i) + if ((g >> i) & 1) + list.push(i + offset) + return list +} + +function unpack_group_list(flist, rlist) { + let list = [] + if (flist) { + for (let [sg, hg] of flist) { + if (unpack_group_hits(sg, 0)) + list.push([unpack_group(sg, 0), unpack_group(hg, 0)]) + } + } + if (rlist) { + for (let [sg, hg] of rlist) { + if (unpack_group_hits(sg, 6)) + list.push([unpack_group(sg, 6), unpack_group(hg, 6)]) + } + } + return list +} + +function debug_group(g) { + return g.map(p=>battle_array_name[p]).join("+") +} + +function debug_group_list(list) { + for (let [sg,hg] of list) + console.log(debug_group(sg), "strike", debug_group(hg)) +} + +function has_front_strike_choice() { + let s = game.battle.step & 1 + let f = pack_battle_array_front() + return GROUPS[s][1][f] !== 0 +} + +function has_rear_strike_choice() { + let s = game.battle.step & 1 + let r = pack_battle_array_rear() + return GROUPS[s][1][r] !== 0 +} + + +function goto_start_strike() { + game.battle.step = 0 goto_strike() } +function goto_next_strike() { + game.battle.step++ + if (game.battle.step >= 6) + goto_new_round() + else + goto_strike() +} + +function debug_battle_array(f, r) { + for (let row = 0; row < 6; row += 3) { + let x = "" + for (let col = 0; col < 3; ++col) { + let b = row + col + if ((f >> b) & 1) + x += battle_array_name[b] + " " + else + x += "--- " + } + console.log(x) + } + for (let row = 3; row >= 0; row -= 3) { + let x = "" + for (let col = 0; col < 3; ++col) { + let b = row + col + if ((r >> b) & 1) + x += battle_array_name[b+6] + " " + else + x += "--- " + } + console.log(x) + } +} + +function count_archery_hits(ix, lord) { + if (lord_has_capability(lord, AOW_RUSSIAN_LUCHNIKI)) { + game.battle.ah1[ix] += get_lord_forces(lord, LIGHT_HORSE) + game.battle.ah1[ix] += get_lord_forces(lord, MILITIA) + } + if (lord_has_capability(lord, AOW_TEUTONIC_BALISTARII)) { + game.battle.ah2[ix] += get_lord_forces(lord, MEN_AT_ARMS) + } + game.battle.ah1[ix] += get_lord_forces(lord, ASIATIC_HORSE) +} + +function count_horse_hits(ix, lord, storm) { + if (storm) + game.battle.ah1[ix] += get_lord_forces(lord, KNIGHTS) << 1 + else + game.battle.ah1[ix] += get_lord_forces(lord, KNIGHTS) << 2 + game.battle.ah1[ix] += get_lord_forces(lord, SERGEANTS) << 1 + game.battle.ah1[ix] += get_lord_forces(lord, LIGHT_HORSE) +} + +function count_foot_hits(ix, lord) { + game.battle.ah1[ix] += get_lord_forces(lord, MEN_AT_ARMS) << 1 + game.battle.ah1[ix] += get_lord_forces(lord, MILITIA) + game.battle.ah1[ix] += get_lord_forces(lord, SERFS) +} + +function count_battle_hits(ix, lord, step) { + switch (step) { + case BATTLE_STEP_DEF_ARCHERY: + case BATTLE_STEP_ATK_ARCHERY: + count_archery_hits(ix, lord) + break + case BATTLE_STEP_DEF_HORSE: + case BATTLE_STEP_ATK_HORSE: + count_horse_hits(ix, lord, false) + break + case BATTLE_STEP_DEF_FOOT: + case BATTLE_STEP_ATK_FOOT: + count_foot_hits(ix, lord) + break + } +} + +function count_storm_hits(ix, lord, step) { + switch (step) { + case STORM_STEP_DEF_ARCHERY: + case STORM_STEP_ATK_ARCHERY: + count_archery_hits(ix, lord) + break + case STORM_STEP_DEF_MELEE: + case STORM_STEP_ATK_MELEE: + count_horse_hits(ix, lord, false) + count_foot_hits(ix, lord) + break + } +} + function goto_strike() { - log("TODO strike") - goto_new_round() + let s = game.battle.step & 1 + + if (s) + set_active_attacker() + else + set_active_defender() + + // TODO: garrison hits + game.battle.ah1 = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + game.battle.ah2 = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + for (let p of STEP_ARRAY[s]) + if (game.battle.array[p] !== NOBODY) + count_battle_hits(p, game.battle.array[p], game.battle.step) + + // TODO: select choice + // TODO: if SA and no RD + // TODO: garrison groups + let front = pack_battle_array_front() + let rear = pack_battle_array_rear() + let front_choice = 0 + let rear_choice = 0 + + game.battle.groups = unpack_group_list(GROUPS[s][front_choice][front], GROUPS[s][rear_choice][rear]) + + console.log("STRIKE") + console.log("hits", game.battle.ah1) + console.log("crossbow hits", game.battle.ah2) + debug_battle_array(front, rear) + debug_group_list(game.battle.groups) + + goto_select_strike_group() +} + +function goto_select_strike_group() { + if (game.battle.groups.length === 0) + goto_next_strike() + else + game.state = "select_strike_group" +} + +states.select_strike_group = { + prompt() { + view.prompt = `${battle_step_name[game.battle.step]}: Select Striking Lord or Group.` + for (let [sg, hg] of game.battle.groups) { + for (let p of sg) { + let lord = game.battle.array[p] + if (game.battle.ah1[p] > 0 || game.battle.ah2[p] > 0) + gen_action_battle_lord(lord) + } + } + }, + battle_lord(lord) { + for (let i = 0; i < game.battle.groups.length; ++i) { + for (let p of game.battle.groups[i][0]) + if (game.battle.array[p] === lord) + select_strike_group(i) + } + }, } +function select_strike_group(i) { + game.battle.sg = game.battle.groups[i][0] + game.battle.hg = game.battle.groups[i][1] + array_remove(game.battle.groups, i) + + // Total hits from striking lords + game.battle.h1 = 0 + game.battle.h2 = 0 + for (let p of game.battle.sg) { + game.battle.h1 += game.battle.ah1[p] + game.battle.h2 += game.battle.ah2[p] + } + + // Round in favor of crossbow hits + if (game.battle.h2 & 1) { + game.battle.h1 = (game.battle.h1 >> 1) + game.battle.h2 = (game.battle.h2 >> 1) + 1 + } else { + if (game.battle.h1 & 1) + game.battle.h1 = (game.battle.h1 >> 1) + 1 + else + game.battle.h1 = (game.battle.h1 >> 1) + game.battle.h2 = (game.battle.h2 >> 1) + } + + set_active_enemy() + goto_select_hit_group() + return +} + +function goto_select_hit_group() { + if (game.battle.hg.length > 0) { + game.state = "select_hit_group" + } else { + game.who = game.battle.array[game.battle.hg[0]] + game.state = "hit_lord" + } +} + +function format_hits() { + if (game.battle.h2 > 0 && game.battle.h1 > 0) + return `${game.battle.h2} crossbow hits and ${game.battle.h1} hits` + else if (game.battle.h2 > 0) + return `${game.battle.h2} crossbow hits` + else + return `${game.battle.h1} hits` +} + +states.select_hit_group = { + prompt() { + view.prompt = `${battle_step_name[game.battle.step]}: Select Lord to take ${format_hits()}.` + for (let pos of game.battle.hg) + gen_action_battle_lord(game.battle.array[pos]) + }, + battle_lord(lord) { + game.who = lord + game.state = "hit_lord" + }, +} + +states.hit_lord = { + prompt() { + view.prompt = `${battle_step_name[game.battle.step]}: Assign ${format_hits()}.` + view.actions.pass = 1 + }, + pass() { + game.who = NOBODY + set_active_enemy() + goto_select_strike_group() + }, +} + +// === BATTLE: NEW ROUND === + function goto_new_round() { // TODO: no unrouted lords if (game.battle.conceded) { game.battle.loser = game.battle.conceded end_battle() } else { - goto_concede_the_field() + goto_concede() } } diff --git a/tools/genstrike.js b/tools/genstrike.js new file mode 100644 index 0000000..d81bdf8 --- /dev/null +++ b/tools/genstrike.js @@ -0,0 +1,239 @@ +"use strict" + +const A1 = 0, A2 = 1, A3 = 2 +const D1 = 3, D2 = 4, D3 = 5 + +const NAMES = [ + "A1", "A2", "A3", + "D1", "D2", "D3", +] + +const OPPOSE = [ + D1, D2, D3, + A1, A2, A3, +] + +const strike_steps = [ + [ D1, D2, D3 ], + [ A1, A2, A3 ], +] + +const receive_steps = [ + [ A1, A2, A3 ], + [ D1, D2, D3 ], +] + +function pack_group2(grp) { + let p = 0 + for (let i of grp) + p |= (1 << i) + return p +} + +function pack_group1(grp) { + return grp.sort((a,b)=>(a-b)) +} + +let pack_group = pack_group2 + +function show_group(p) { + let grp = [] + for (let i = 0; i < 6; ++i) + if ((p >> i) & 1 === 1) + grp.push(NAMES[i]) + return grp.join("+") +} + +// who can strike who theoretically (both left and right) +// used to detect who is flanking who +function list_flanking_groups(array) { + function oppose_then_near_then_far(result, o, a, b) { + if (array[o]) return result.push(o) + if (array[a]) return result.push(a) + if (array[b]) return result.push(b) + } + function oppose_then_left_and_right(result, o, a, b) { + if (array[o]) return result.push(o) + if (array[a]) result.push(a) + if (array[b]) result.push(b) + } + function oppose_or_flanking(result, pos) { + switch (pos) { + case A1: return oppose_then_near_then_far(result, D1, D2, D3) + case A2: return oppose_then_left_and_right(result, D2, D1, D3) + case A3: return oppose_then_near_then_far(result, D3, D2, D1) + case D1: return oppose_then_near_then_far(result, A1, A2, A3) + case D2: return oppose_then_left_and_right(result, A2, A1, A3) + case D3: return oppose_then_near_then_far(result, A3, A2, A1) + } + } + let groups = [ [], [], [], [], [], [] ] + for (let pos = 0; pos < 6; ++pos) + if (array[pos]) + oppose_or_flanking(groups[pos], pos) + return groups +} + +// who can strike who (having chosen left or right) +// used to create the actual strike groups +function list_strike_groups(array, striking, choice) { + function target_oppose_then_near_then_far(o, a, b) { + if (array[o]) return o + if (array[a]) return a + if (array[b]) return b + return -1 + } + function target_oppose_then_left_or_right(o, a, b) { + if (choice === 0) { + if (array[o]) return o + if (array[a]) return a + if (array[b]) return b + } else { + if (array[o]) return o + if (array[b]) return b + if (array[a]) return a + } + return -1 + } + function target_oppose_or_flanking(pos) { + switch (pos) { + case A1: return target_oppose_then_near_then_far(D1, D2, D3) + case A2: return target_oppose_then_left_or_right(D2, D1, D3) + case A3: return target_oppose_then_near_then_far(D3, D2, D1) + case D1: return target_oppose_then_near_then_far(A1, A2, A3) + case D2: return target_oppose_then_left_or_right(A2, A1, A3) + case D3: return target_oppose_then_near_then_far(A3, A2, A1) + } + return -1 + } + let groups = [ [], [], [], [], [], [] ] + for (let pos of striking) { + if (array[pos]) { + let tgt = target_oppose_or_flanking(pos) + if (tgt >= 0) + groups[tgt].push(pos) + } + } + return groups +} + + +function show_array(array) { + for (let row = 0; row < 6; row += 3) { + let s = [] + for (let col = 0; col < 3; ++col) { + if (array[row+col]) + s.push(NAMES[row+col].padEnd(3, ' ')) + else + s.push("-- ") + } + console.log(s.join(" ")) + } + console.log("") +} + +function print_strikes(grp) { + for (let i = 0; i < 6; ++i) { + if (grp[i].length > 0) { + console.log(NAMES[i] + ":", show_group(grp[i])) + } + } + console.log("") +} + +function list_hit_groups(array, flanking, strikers, step) { + function is_flanking_target(target, pos) { + return array[OPPOSE[pos]] === 0 && flanking[pos].includes(OPPOSE[target]) + } + function is_flanking_striker(list) { + for (let pos of list) + if (array[OPPOSE[pos]] === 0) + return true + return false + } + function list_flanking_2(target, list1, list2) { + let result = [ target ] + function list_flanking_3(list) { + for (let pos of list) + if (array[pos] && is_flanking_target(target, pos)) + result.push(pos) + } + list_flanking_3(list1) + list_flanking_3(list2) + return result + } + function list_flanking_1(target) { + switch (target) { + case A1: return list_flanking_2(target, [ A2, A3 ], []) + case A3: return list_flanking_2(target, [ A2, A1 ], []) + case D1: return list_flanking_2(target, [ D2, D3 ], []) + case D3: return list_flanking_2(target, [ D2, D1 ], []) + case A2: return list_flanking_2(target, [ A1 ], [ A3 ]) + case D2: return list_flanking_2(target, [ D1 ], [ D3 ]) + } + } + + let result = [] + let sg, hg + for (let pos of step) { + if (strikers[pos].length > 0) { + sg = pack_group(strikers[pos]) + if (is_flanking_striker(strikers[pos])) + hg = pack_group([pos]) + else + hg = pack_group(list_flanking_1(pos)) + result.push([sg,hg]) + } + } + return result +} + +function run_step(bits, array, i, output_a, output_b) { + let a, b, strikers, flanking + + flanking = list_flanking_groups(array) + + strikers = list_strike_groups(array, strike_steps[i], 0) + a = list_hit_groups(array, flanking, strikers, receive_steps[i]) + + strikers = list_strike_groups(array, strike_steps[i], 1) + b = list_hit_groups(array, flanking, strikers, receive_steps[i]) + + output_a[bits] = a.length > 0 ? a : 0 + if (JSON.stringify(a) !== JSON.stringify(b)) + output_b[bits] = b.length > 0 ? b : 0 + else + output_b[bits] = 0 +} + +let group_defending_a = [] +let group_defending_b = [] +let group_attacking_a = [] +let group_attacking_b = [] + +function run(bits, array, step) { + run_step(bits, array, 0, group_defending_a, group_defending_b) + run_step(bits, array, 1, group_attacking_a, group_attacking_b) +} + +function runall() { + for (let x = 0; x < 64; ++x) { + run(x, [ + (x>>0)&1, + (x>>1)&1, + (x>>2)&1, + (x>>3)&1, + (x>>4)&1, + (x>>5)&1, + ]) + } +} + +runall() + +let GROUPS = [ + [ group_defending_a, group_defending_b ], + [ group_attacking_a, group_attacking_b ], +] + +console.log("const GROUPS = " + JSON.stringify(GROUPS)) |