diff options
-rw-r--r-- | play.html | 129 | ||||
-rw-r--r-- | play.js | 112 | ||||
-rw-r--r-- | rules.js | 246 |
3 files changed, 398 insertions, 89 deletions
@@ -146,43 +146,62 @@ body.Teutons #plan_actions .russian { display: none } #battle.defender { background-image: url(images/mat_battle_defender.png); } #battle.attacker { background-image: url(images/mat_battle_attacker.png); } -#battle.defender #battle_attacker_reserves { width: 356px !important; top: 4px; left: 8px; } -#battle.defender #battle_attacker_left { top: 96px; left: 32px; } -#battle.defender #battle_attacker_center { top: 96px; left: 162px; } -#battle.defender #battle_attacker_right { top: 96px; right: 32px; } -#battle.defender #battle_defender_garrison { width: 356px !important; top: 172px; left: 8px; } -#battle.defender #battle_defender_left { top: 220px; left: 32px; } -#battle.defender #battle_defender_center { top: 220px; left: 162px; } -#battle.defender #battle_defender_right { top: 220px; right: 32px; } -#battle.defender #battle_sally_left { bottom: 60px; left: 4px; } -#battle.defender #battle_sally_center { bottom: 60px; right: 200px; } -#battle.defender #battle_sally_right { bottom: 60px; right: 4px; } -#battle.defender #battle_defender_reserves { width: 356px !important; bottom: 4px; left: 8px; } - -#battle.attacker #battle_attacker_reserves { width: 356px !important; bottom: 4px; left: 8px; } -#battle.attacker #battle_attacker_left { bottom: 96px; right: 32px; } -#battle.attacker #battle_attacker_center { bottom: 96px; left: 162px; } -#battle.attacker #battle_attacker_right { bottom: 96px; left: 32px; } -#battle.attacker #battle_defender_garrison { width: 356px !important; bottom: 172px; left: 8px; } -#battle.attacker #battle_defender_left { bottom: 220px; right: 32px; } -#battle.attacker #battle_defender_center { bottom: 220px; left: 162px; } -#battle.attacker #battle_defender_right { bottom: 220px; left: 32px; } -#battle.attacker #battle_sally_left { top: 60px; right: 4px; } -#battle.attacker #battle_sally_center { top: 60px; left: 200px; } -#battle.attacker #battle_sally_right { top: 60px; left: 4px; } -#battle.attacker #battle_defender_reserves { width: 356px !important; top: 4px; left: 8px; } +#battle.defender #array_attacker_reserves { top: 4px; left: 8px; } +#battle.defender #array_attacker_left { top: 96px; left: 32px; } +#battle.defender #array_attacker_center { top: 96px; left: 162px; } +#battle.defender #array_attacker_right { top: 96px; right: 32px; } +#battle.defender #array_defender_garrison { top: 172px; left: 8px; } +#battle.defender #array_defender_left { top: 220px; left: 32px; } +#battle.defender #array_defender_center { top: 220px; left: 162px; } +#battle.defender #array_defender_right { top: 220px; right: 32px; } +#battle.defender #array_sally_left { bottom: 60px; left: 4px; } +#battle.defender #array_sally_center { bottom: 60px; right: 200px; } +#battle.defender #array_sally_right { bottom: 60px; right: 4px; } +#battle.defender #array_defender_reserves { bottom: 4px; left: 8px; } + +#battle.attacker #array_attacker_reserves { bottom: 4px; left: 8px; } +#battle.attacker #array_attacker_left { bottom: 96px; right: 32px; } +#battle.attacker #array_attacker_center { bottom: 96px; left: 162px; } +#battle.attacker #array_attacker_right { bottom: 96px; left: 32px; } +#battle.attacker #array_defender_garrison { bottom: 172px; left: 8px; } +#battle.attacker #array_defender_left { bottom: 220px; right: 32px; } +#battle.attacker #array_defender_center { bottom: 220px; left: 162px; } +#battle.attacker #array_defender_right { bottom: 220px; left: 32px; } +#battle.attacker #array_sally_left { top: 60px; right: 4px; } +#battle.attacker #array_sally_center { top: 60px; left: 200px; } +#battle.attacker #array_sally_right { top: 60px; left: 4px; } +#battle.attacker #array_defender_reserves { top: 4px; left: 8px; } #battle > div { position: absolute; - width: 48px; - height: 48px; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; +} + +#battle .reserves { + width: 356px; + height: 48px; + gap: 8px; +} + +#battle .garrison { + width: 356px; + height: 48px; gap: 2px; } +#battle .array { + width: 48px; + height: 48px; +} + +#battle .array.action { + border-radius: 50%; + box-shadow: 0 0 0 3px white; +} + #battle .cylinder, #battle .unit { position: static } @@ -1208,49 +1227,19 @@ body.shift .mustered_vassals { <div id="hand" class="hand"></div> -<div id="battle" class="defender"> -<div id="battle_attacker_reserves"> -<div class="cylinder lord teutonic knud_and_abel"></div> -<div class="cylinder lord teutonic rudolf"></div> -</div> -<div id="battle_attacker_right"> -<div class="cylinder lord teutonic andreas"></div> -</div> -<div id="battle_attacker_center"> -<div class="cylinder lord teutonic heinrich"></div> -</div> -<div id="battle_attacker_left"> -<div class="cylinder lord teutonic hermann"></div> -</div> -<div id="battle_defender_garrison"> -<div class="unit knights"></div> -<div class="unit men_at_arms"></div> -<div class="unit men_at_arms"></div> -<div class="unit men_at_arms"></div> -</div> -<div id="battle_defender_left"> -<div class="cylinder lord russian aleksandr"></div> -</div> -<div id="battle_defender_center"> -<div class="cylinder lord russian andrey"></div> -</div> -<div id="battle_defender_right"> -<div class="cylinder lord russian domash"></div> -</div> -<div id="battle_defender_reserves"> -<div class="cylinder lord russian gavrilo"></div> -<div class="cylinder lord russian karelians"></div> -<div class="cylinder lord russian vladislav"></div> -</div> -<div id="battle_sally_left"> -<div class="cylinder lord teutonic yaroslav"></div> -</div> -<div id="battle_sally_center"> -<div class="cylinder lord teutonic yaroslav"></div> -</div> -<div id="battle_sally_right"> -<div class="cylinder lord teutonic yaroslav"></div> -</div> +<div id="battle" class="attacker"> +<div class="garrison" id="array_defender_garrison"></div> +<div class="reserves" id="array_attacker_reserves"></div> +<div class="reserves" id="array_defender_reserves"></div> +<div class="array" id="array_attacker_right"></div> +<div class="array" id="array_attacker_center"></div> +<div class="array" id="array_attacker_left"></div> +<div class="array" id="array_defender_left"></div> +<div class="array" id="array_defender_center"></div> +<div class="array" id="array_defender_right"></div> +<div class="array" id="array_sally_left"></div> +<div class="array" id="array_sally_center"></div> +<div class="array" id="array_sally_right"></div> </div> </div> @@ -55,6 +55,17 @@ const SLED = 4 const BOAT = 5 const SHIP = 6 +// battle array +const ARRAY_ATK_C = 0 +const ARRAY_DEF_C = 1 +const ARRAY_SALLY_C = 2 +const ARRAY_ATK_L = 3 +const ARRAY_DEF_R = 4 +const ARRAY_SALLY_R = 5 +const ARRAY_ATK_R = 6 +const ARRAY_DEF_L = 7 +const ARRAY_SALLY_L = 8 + const VECHE = 100 const on_click_asset = [ @@ -167,6 +178,14 @@ function is_lord_action(lord) { return !!(view.actions && view.actions.lord && set_has(view.actions.lord, lord)) } +function is_battle_lord_action(lord) { + return !!(view.actions && view.actions.battle_lord && set_has(view.actions.battle_lord, lord)) +} + +function is_battle_array_action(ix) { + return !!(view.actions && view.actions.array && set_has(view.actions.array, ix)) +} + function is_asset_action(lord, action) { return !!(view.actions && view.actions[action] && set_has(view.actions[action], lord)) } @@ -248,6 +267,13 @@ function restart_cache() { } } +function is_attacking_lord(lord) { + if (view.battle.attacker === "Teutons") + return lord < 6 + else + return lord >= 6 +} + function is_p1_locale(loc) { return loc >= first_p1_locale && loc <= last_p1_locale } @@ -419,6 +445,7 @@ const ui = { locale_name: [], locale_markers: [], lord_cylinder: [], + battle_cylinder: [], lord_service: [], lord_mat: [], lord_buttons: [], @@ -450,6 +477,21 @@ const ui = { turn: document.getElementById("turn"), vp1: document.getElementById("vp1"), vp2: document.getElementById("vp2"), + battle_attacker_reserves: document.getElementById("array_attacker_reserves"), + battle_defender_reserves: document.getElementById("array_defender_reserves"), + battle_garrison: document.getElementById("array_defender_garrison"), + battle: document.getElementById("battle"), + battle_array: [ + document.getElementById("array_attacker_center"), + document.getElementById("array_defender_center"), + document.getElementById("array_sally_center"), + document.getElementById("array_attacker_left"), + document.getElementById("array_defender_right"), + document.getElementById("array_sally_right"), + document.getElementById("array_attacker_right"), + document.getElementById("array_defender_left"), + document.getElementById("array_sally_left"), + ], } let locale_layout = [] @@ -501,6 +543,13 @@ function on_click_cylinder(evt) { } } +function on_click_battle_cylinder(evt) { + if (evt.button === 0) { + let id = evt.target.my_id + send_action('battle_lord', id) + } +} + function on_click_card(evt) { if (evt.button === 0) { let id = evt.target.my_id @@ -616,6 +665,11 @@ function on_click_legate(evt) { send_action('legate') } +function on_click_array(evt) { + if (evt.button === 0) + send_action('array', evt.target.my_id) +} + function on_blur(evt) { document.getElementById("status").textContent = "" } @@ -1221,6 +1275,29 @@ function update_cards() { } } +function update_battle() { + let array = view.battle.array + ui.battle_attacker_reserves.replaceChildren() + ui.battle_defender_reserves.replaceChildren() + for (let i = 0; i < array.length; ++i) { + let lord = array[i] + ui.battle_array[i].replaceChildren() + if (lord >= 0) + ui.battle_array[i].appendChild(ui.battle_cylinder[lord]) + ui.battle_array[i].classList.toggle("action", is_battle_array_action(i)) + } + for (let lord of view.battle.reserves) { + if (is_attacking_lord(lord)) + ui.battle_attacker_reserves.appendChild(ui.battle_cylinder[lord]) + else + ui.battle_defender_reserves.appendChild(ui.battle_cylinder[lord]) + } + for (let lord = 0; lord < 12; ++lord) { + ui.battle_cylinder[lord].classList.toggle("action", is_battle_lord_action(lord)) + ui.battle_cylinder[lord].classList.toggle("selected", view.who === lord) + } +} + function on_update() { restart_cache() @@ -1293,6 +1370,16 @@ function on_update() { ui.veche.classList.toggle("action", is_veche_action()) + if (view.battle) { + if (view.battle.attacker === player) + ui.battle.className = "attacker" + else + ui.battle.className = "defender" + update_battle() + } else { + ui.battle.className = "hide" + } + // Misc action_button("left", "Left") action_button("right", "Right") @@ -1337,22 +1424,27 @@ function on_update() { action_button("hold", "Hold") action_button("play", "Play") + action_button("concede", "Concede") + action_button("battle", "Battle") + action_button("end_actions", "End actions") - action_button("end_wastage", "End wastage") - action_button("end_plow_and_reap", "End plow and reap") + action_button("end_array", "End array") action_button("end_avoid_battle", "End avoid battle") action_button("end_call_to_arms", "End call to arms") action_button("end_disband", "End disband") - action_button("end_discard", "End discard") + action_button("end_disband", "End disband") action_button("end_feed", "End feed") action_button("end_levy", "End levy") action_button("end_muster", "End muster") action_button("end_pay", "End pay") action_button("end_plan", "End plan") + action_button("end_plow_and_reap", "End plow and reap") action_button("end_ransom", "End ransom") + action_button("end_remove", "End remove") action_button("end_setup", "End setup") action_button("end_spoils", "End spoils") action_button("end_supply", "End supply") + action_button("end_wastage", "End wastage") action_button("end_withdraw", "End withdraw") action_button("pass", "Pass") @@ -1536,6 +1628,13 @@ function build_map() { e.addEventListener("mouseleave", on_blur) document.getElementById("pieces").appendChild(e) + e = ui.battle_cylinder[ix] = document.createElement("div") + e.className = "cylinder lord " + clean_name(lord.side) + " " + clean_name(lord.name) + e.my_id = ix + e.addEventListener("mousedown", on_click_battle_cylinder) + e.addEventListener("mouseenter", on_focus_cylinder) + e.addEventListener("mouseleave", on_blur) + e = ui.lord_service[ix] = document.createElement("div") e.className = "service_marker lord image" + lord.image + " " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide" e.my_id = ix @@ -1584,6 +1683,11 @@ function build_map() { build_plan() + for (let i = 0; i < ui.battle_array.length; ++i) { + ui.battle_array[i].my_id = i + ui.battle_array[i].addEventListener("mousedown", on_click_array) + } + for (let c = 0; c < 21; ++c) build_card("teutonic", c) for (let c = 21; c < 42; ++c) @@ -1591,6 +1695,4 @@ function build_map() { } build_map() -// drag_element_with_mouse("#battle", "#battle_header") -drag_element_with_mouse("#arts_of_war", "#arts_of_war_header") scroll_with_middle_mouse("main") @@ -1,6 +1,8 @@ "use strict" // clean up game.who (use only in muster / events, not for command) +// TODO: remove push_state/pop_state stuff - use explicit substates with common functions instead +// game.levy/command instead of game.who for levy (like game.command for campaign) // TEST: legate removal during battle and retreats etc @@ -17,8 +19,6 @@ // TODO: SALLY // TODO: STORM -// TODO: remove push_state/pop_state stuff - use explicit substates with common functions instead - const data = require("./data.js") const TODO = false @@ -89,6 +89,17 @@ const SLED = 4 const BOAT = 5 const SHIP = 6 +// battle array +const ARRAY_AC = 0 +const ARRAY_DC = 1 +const ARRAY_SC = 2 +const ARRAY_AL = 3 +const ARRAY_DR = 4 +const ARRAY_SR = 5 +const ARRAY_AR = 6 +const ARRAY_DL = 7 +const ARRAY_SL = 8 + const asset_type_name = [ "prov", "coin", "loot", "cart", "sled", "boat", "ship" ] function find_card(name) { @@ -3450,6 +3461,15 @@ function remove_legate_if_endangered(here) { function march_with_group_3() { let here = get_lord_locale(game.command) + // Disbanded in battle! + if (here === NOWHERE) { + game.march = 0 + spend_all_actions() + resume_actions() + update_supply() + return + } + remove_legate_if_endangered(here) if (is_besieged_friendly_stronghold(here)) { @@ -4678,6 +4698,17 @@ function goto_smerdi() { // === BATTLE === +function set_active_attacker() { + set_active(game.battle.attacker) +} + +function set_active_defender() { + if (game.battle.attacker === P1) + set_active(P2) + else + set_active(P1) +} + function goto_battle() { if (has_unbesieged_enemy_lord(game.march.to)) start_battle() @@ -4695,9 +4726,10 @@ function start_battle() { attacker: game.active, conceded: 0, array: [ - NOBODY, NOBODY, NOBODY, // attacker - NOBODY, NOBODY, NOBODY, // defender - NOBODY, NOBODY, NOBODY, // sally + game.command, + NOBODY, NOBODY, + NOBODY, NOBODY, NOBODY, + NOBODY, NOBODY, NOBODY, ], garrison: 0, reserves: [], @@ -4707,7 +4739,8 @@ function start_battle() { for (let lord = first_lord; lord <= last_lord; ++lord) if (get_lord_locale(lord) === here && !is_lord_besieged(lord)) - set_add(game.battle.reserves, lord) + if (lord !== game.command) + set_add(game.battle.reserves, lord) // battle array // events @@ -4721,11 +4754,182 @@ function start_battle() { // roll by hit // end battle - log("TODO: Battle...") + goto_attacker_battle_array() +} + +// === BATTLE: BATTLE ARRAY === - game.battle.conceded = P2 - game.battle.loser = P2 - end_battle() +function has_reserves() { + for (let lord of game.battle.reserves) + if (is_friendly_lord(lord)) + return true + return false +} + +function prompt_array_lord() { + for (let lord of game.battle.reserves) + if (is_friendly_lord(lord)) + if (lord !== game.who) + gen_action_battle_lord(lord) +} + +function action_array_lord(pos) { + push_undo_without_who() + game.battle.array[pos] = game.who + set_delete(game.battle.reserves, game.who) + game.who = NOBODY +} + +function action_select_lord(lord) { + game.who = lord +} + +function goto_attacker_battle_array() { + set_active_attacker() + game.state = "attacker_battle_array" + game.who = NOBODY + if (!has_reserves()) + goto_defender_battle_array() +} + +states.attacker_battle_array = { + prompt() { + view.prompt = "Battle Array: Position your lords." + + prompt_array_lord() + + if (game.who !== NOBODY) { + if (array[ARRAY_AC] === NOBODY) { + gen_action_array(ARRAY_AC) + } else { + if (array[ARRAY_AL] === NOBODY) + gen_action_array(ARRAY_AL) + if (array[ARRAY_AR] === NOBODY) + gen_action_array(ARRAY_AR) + } + } + + if (!has_reserves() || (array[ARRAY_AC] >= 0 && array[ARRAY_AL] >= 0 && array[ARRAY_AR] >= 0)) + view.actions.end_array = 1 + }, + battle_lord: action_select_lord, + array: action_array_lord, + end_array() { + clear_undo() + goto_defender_battle_array() + }, +} + +function goto_defender_battle_array() { + set_active_defender() + game.state = "defender_battle_array" + game.who = NOBODY + if (!has_reserves()) + goto_attacker_events() +} + +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_AL] !== NOBODY && array[ARRAY_AR] === NOBODY && array[ARRAY_DL] === NOBODY) { + gen_action_array(ARRAY_DL) + } else if (array[ARRAY_AR] !== NOBODY && array[ARRAY_AL] === NOBODY && array[ARRAY_DR] === NOBODY) { + gen_action_array(ARRAY_DR) + } else { + if (array[ARRAY_DL] !== NOBODY) + gen_action_array(ARRAY_DL) + if (array[ARRAY_DR] === NOBODY) + gen_action_array(ARRAY_DR) + } + } + + if (!has_reserves() || (array[ARRAY_DC] >= 0 && array[ARRAY_DL] >= 0 && array[ARRAY_DR] >= 0)) + view.actions.end_array = 1 + }, + battle_lord: action_select_lord, + array: action_array_lord, + end_array() { + clear_undo() + goto_attacker_events() + }, +} + +function goto_attacker_events() { + log("TODO attacker events") + goto_defender_events() +} + +function goto_defender_events() { + log("TODO defender events") + goto_relief_sally() +} + +function goto_relief_sally() { + log("TODO relief sally") + goto_battle_rounds() +} + +function goto_battle_rounds() { + set_active_attacker() + goto_concede() +} + +// === BATTLE: CONCEDE THE FIELD === + +function goto_concede() { + game.state = "concede" +} + +states.concede = { + prompt() { + view.prompt = "Battle: Concede the Field?" + view.actions.concede = 1 + view.actions.battle = 1 + }, + concede() { + log(game.active + " concede.") + game.battle.conceded = game.active + goto_reposition() + }, + battle() { + if (game.battle.storm) { + goto_reposition() + } else { + set_active_enemy() + if (game.active === game.attacker) + goto_reposition() + } + } +} + +// === BATTLE: REPOSITION === + +function goto_reposition() { + log("TODO reposition") + goto_strike() +} + +function goto_strike() { + log("TODO strike") + goto_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() + } } // === ENDING THE BATTLE === @@ -4882,6 +5086,7 @@ function can_retreat() { function goto_retreat() { let here = game.march.to + console.log("goto_retreat", here, count_unbesieged_friendly_lords(here), can_retreat()) if (count_unbesieged_friendly_lords(here) > 0 && can_retreat()) { game.battle.retreated = [] for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) @@ -5057,6 +5262,7 @@ states.battle_remove = { push_undo() transfer_assets_except_ships(lord) disband_lord(lord, true) + remove_legate_if_endangered(game.battle.where) }, end_remove() { push_undo() @@ -5175,14 +5381,17 @@ states.battle_service = { function goto_battle_aftermath() { set_active(game.battle.attacker) - // Moved/Fought + // Moved/Fought - TODO: mark at start of battle instead for (let lord of game.battle.array) if (lord !== NOBODY) - set_lord_moved(lord, 1) + if (is_lord_on_map(lord)) + set_lord_moved(lord, 1) for (let lord of game.battle.reserves) - set_lord_moved(lord, 1) + if (is_lord_on_map(lord)) + set_lord_moved(lord, 1) for (let lord of game.battle.routed) - set_lord_moved(lord, 1) + if (is_lord_on_map(lord)) + set_lord_moved(lord, 1) game.battle = 0 @@ -6100,6 +6309,14 @@ function gen_action_lord(lord) { gen_action("lord", lord) } +function gen_action_battle_lord(lord) { + gen_action("battle_lord", lord) +} + +function gen_action_array(pos) { + gen_action("array", pos) +} + function gen_action_service(service) { gen_action("service", service) } @@ -6156,6 +6373,7 @@ exports.view = function (state, current) { events: game.events, capabilities: game.capabilities, pieces: game.pieces, + battle: game.battle, command: game.command, hand: null, |