diff options
-rw-r--r-- | play.html | 34 | ||||
-rw-r--r-- | rules.js | 126 | ||||
-rw-r--r-- | ui.js | 24 |
3 files changed, 148 insertions, 36 deletions
@@ -163,6 +163,8 @@ body.shift .block.known:hover { .block.moved { filter: brightness(80%) grayscale(40%); } .block.highlight.moved { filter: brightness(95%) grayscale(40%); } +.map .block.castle { border-color: #444; } + .block.r0 { transform: rotate(0deg); } .block.r1 { transform: rotate(-90deg); } .block.r2 { transform: rotate(-180deg); } @@ -184,12 +186,13 @@ body.shift .block.known:hover { .battle .battle_message { background-color: gainsboro; } .battle .battle_header { background-color: #224467; color: white; font-weight: bold; } .battle .battle_separator { background-color: #224467; } - .battle_line.enemy .battle_menu_list { min-height: 0; } -.battle_reserves > td > div { height: 75px; padding: 5px; } -.battle_a_cell > div { min-width: 270px; padding: 5px 5px; } -.battle_b_cell > div { min-width: 270px; padding: 5px 5px; } -.battle_c_cell > div { min-width: 270px; padding: 5px 5px; } +.battle_line > td > div { min-width: 800px; } + +.battle td { border: none; } +#FA, #FC, #FD, #FR, #EA, #EC, #ER { margin: 0px; } +#FC, #EC { background-color: gray; } +.battle .battle_menu { margin: 10px 5px; } /* CARD AND BLOCK IMAGES */ @@ -283,20 +286,12 @@ body.shift .block.known:hover { <table class="battle"> <tr> <th class="battle_header" colspan=4></th> -<tr class="battle_reserves enemy"> -<td colspan=4><div id="ER"></div></td> -<tr class="battle_line enemy"> -<td class="battle_a_cell"><div id="EA"></div></td> -<td class="battle_b_cell"><div id="EB"></div></td> -<td class="battle_c_cell"><div id="EC"></div></td> -<tr class="battle_separator"> -<td colspan=4> -<tr class="battle_line friendly"> -<td class="battle_a_cell"><div id="FA"></div></td> -<td class="battle_b_cell"><div id="FB"></div></td> -<td class="battle_c_cell"><div id="FC"></div></td> -<tr class="battle_reserves friendly"> -<td colspan=4><div id="FR"></div></td> +<tr class="battle_reserves enemy"><td><div id="ER"></div></td> +<tr class="battle_line enemy"><td><div id="EC"></div></td> +<tr class="battle_line enemy"><td><div id="EA"></div></td> +<tr class="battle_line friendly"><td><div id="FA"></div></td> +<tr class="battle_line friendly"><td><div id="FC"></div></td> +<tr class="battle_reserves friendly"><td><div id="FR"></div></td> <tr> <th class="battle_message" colspan=4></th> </table> @@ -344,6 +339,7 @@ body.shift .block.known:hover { <button id="end_regroup_button" class="hide" onclick="on_button_end_regroup()">End regroup</button> <button id="end_move_phase_button" class="hide" onclick="on_button_end_move_phase()">End move phase</button> <button id="pass_button" class="hide" onclick="on_button_pass()">Pass</button> + <button id="next_button" class="hide" onclick="on_button_next()">Next</button> <button id="undo_button" class="hide" onclick="on_button_undo()">Undo</button> </div> @@ -299,6 +299,10 @@ function is_vacant_town(where) { return count_friendly(where) == 0 && count_enem function is_contested_town(where) { return count_friendly(where) > 0 && count_enemy(where) > 0; } function is_friendly_or_vacant_town(where) { return is_friendly_town(where) || is_vacant_town(where); } +function castle_limit(where) { + return TOWNS[where].rating; +} + function is_fortified_port(where) { return TOWNS[where].fortified_port; } @@ -1136,6 +1140,44 @@ function end_muster_move() { // BATTLE PHASE +function is_siege_able(where) { + return castle_limit(where) > 0; +} + +function count_blocks_in_castle(where) { + let n = 0; + for (let b in BLOCKS) + if (game.location[b] == where && game.castle.includes(b)) + ++n; + return n; +} + +function count_enemies_in_field_and_reserve(where) { + let n = 0; + for (let b in BLOCKS) + if (block_owner(b) != game.active) + if (game.location[b] == where && !game.castle.includes(b)) + ++n; + return n; +} + +function count_friends_in_field_and_reserve(where) { + let n = 0; + for (let b in BLOCKS) + if (block_owner(b) == game.active) + if (game.location[b] == where && !game.castle.includes(b)) + ++n; + return n; +} + +function is_under_siege(where) { + return count_blocks_in_castle(where) > 0; +} + +function is_block_in_castle(b) { + return game.castle.includes(b); +} + function goto_battle_phase() { game.moved = {}; if (have_contested_towns()) { @@ -1166,8 +1208,67 @@ function start_battle(where) { log("Battle in " + where + "."); game.where = where; game.battle_round = 0; - game.state = 'battle_round'; - start_battle_round(); + + if (is_siege_able(where)) { + if (!is_under_siege(where)) { + log("~ Combat Deployment ~"); + game.active = ENEMY[game.attacker[game.where]]; + game.state = 'combat_deployment'; + } else { + log("Siege continues from previous turn."); + game.state = 'battle_round'; + start_battle_round(); + } + } else { + game.state = 'battle_round'; + start_battle_round(); + } +} + +states.combat_deployment = { + show_battle: true, + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to deploy units."; + view.prompt = "Deploy blocks on the field and in the castle."; + let max = castle_limit(game.where); + let n = count_blocks_in_castle(game.where); + if (n < max) { + for (let b in BLOCKS) { + if (block_owner(b) == game.active && !is_battle_reserve(b)) { + if (game.location[b] == game.where && !game.castle.includes(b)) { + gen_action(view, 'battle_withdraw', b); + gen_action(view, 'block', b); + } + } + } + } + gen_action_undo(view); + gen_action(view, 'next'); + }, + battle_withdraw: function (who) { + push_undo(); + game.castle.push(who); + }, + block: function (who) { + push_undo(); + game.castle.push(who); + }, + next: function () { + clear_undo(); + let n = count_blocks_in_castle(game.where); + if (n == 1) + log("1 unit withdraws."); + else + log(n + " units withdraw."); + game.active = game.attacker[game.where]; + if (count_enemies_in_field_and_reserve(game.where) == 0) { + return goto_regroup(); + } + game.state = 'battle_round'; + start_battle_round(); + }, + undo: pop_undo } function resume_battle() { @@ -1660,13 +1761,16 @@ function setup_game() { function make_battle_view() { let battle = { - FA: [], FB: [], FC: [], FR: [], - SA: [], SB: [], SC: [], SR: [], + FA: [], FC: [], FR: [], + SA: [], SC: [], SR: [], flash: game.flash }; battle.title = game.attacker[game.where] + " attacks " + game.where; - battle.title += " \u2014 round " + game.battle_round + " of 3"; + if (game.battle_round == 0) { + battle.title += " \u2014 combat deployment"; + } else { + } function fill_cell(cell, owner, fn) { for (let b in BLOCKS) @@ -1675,14 +1779,12 @@ function make_battle_view() { } fill_cell(battle.FR, FRANK, b => is_battle_reserve(b)); - fill_cell(battle.FA, FRANK, b => !is_battle_reserve(b) && block_initiative(b) == 'A'); - fill_cell(battle.FB, FRANK, b => !is_battle_reserve(b) && block_initiative(b) == 'B'); - fill_cell(battle.FC, FRANK, b => !is_battle_reserve(b) && block_initiative(b) == 'C'); + fill_cell(battle.FA, FRANK, b => !is_battle_reserve(b) && !is_block_in_castle(b)); + fill_cell(battle.FC, FRANK, b => !is_battle_reserve(b) && is_block_in_castle(b)); fill_cell(battle.SR, SARACEN, b => is_battle_reserve(b)); - fill_cell(battle.SA, SARACEN, b => !is_battle_reserve(b) && block_initiative(b) == 'A'); - fill_cell(battle.SB, SARACEN, b => !is_battle_reserve(b) && block_initiative(b) == 'B'); - fill_cell(battle.SC, SARACEN, b => !is_battle_reserve(b) && block_initiative(b) == 'C'); + fill_cell(battle.SA, SARACEN, b => !is_battle_reserve(b) && !is_block_in_castle(b)); + fill_cell(battle.SC, SARACEN, b => !is_battle_reserve(b) && is_block_in_castle(b)); return battle; } @@ -1697,7 +1799,7 @@ exports.setup = function (scenario, players) { road_limit: {}, last_used: {}, location: {}, - castle: {}, + castle: [], log: [], main_road: {}, moved: {}, @@ -156,6 +156,11 @@ function on_focus_battle_charge(evt) { "Charge with " + block_name(evt.target.block); } +function on_focus_battle_withdraw(evt) { + document.getElementById("status").textContent = + "Withdraw with " + block_name(evt.target.block); +} + function on_focus_battle_hit(evt) { document.getElementById("status").textContent = "Take hit on " + block_name(evt.target.block); @@ -170,14 +175,16 @@ function on_click_battle_fire(evt) { send_action('battle_fire', evt.target.block function on_click_battle_retreat(evt) { send_action('battle_retreat', evt.target.block); } function on_click_battle_charge(evt) { send_action('battle_charge', evt.target.block); } function on_click_battle_harry(evt) { send_action('battle_harry', evt.target.block); } +function on_click_battle_withdraw(evt) { send_action('battle_withdraw', evt.target.block); } function on_click_card(evt) { let c = evt.target.id.split("+")[1] | 0; send_action('play', c); } -function on_button_undo(evt) { send_action('undo'); } +function on_button_next(evt) { send_action('next'); } function on_button_pass(evt) { send_action('pass'); } +function on_button_undo(evt) { send_action('undo'); } function on_button_sea_move(evt) { send_action('sea_move'); } function on_button_muster(evt) { send_action('muster'); } function on_button_end_muster(evt) { send_action('end_muster'); } @@ -230,6 +237,9 @@ function build_battle_block(b, block) { build_battle_button(menu_list, b, "retreat", on_click_battle_retreat, on_focus_battle_retreat, "/images/flying-flag.svg"); + build_battle_button(menu_list, b, "withdraw", + on_click_battle_withdraw, on_focus_battle_withdraw, + "/images/stone-tower.svg"); let menu = document.createElement("div"); menu.classList.add("battle_menu"); @@ -535,6 +545,10 @@ function update_map() { if (game.who) ui.blocks[game.who].classList.add('selected'); } + for (let b of game.castle) { + ui.blocks[b].classList.add('castle'); + ui.battle_block[b].classList.add('castle'); + } } function update_cards() { @@ -581,6 +595,7 @@ function update_battle() { ui.battle_menu[block].classList.remove('charge'); ui.battle_menu[block].classList.remove('fire'); ui.battle_menu[block].classList.remove('harry'); + ui.battle_menu[block].classList.remove('withdraw'); ui.battle_menu[block].classList.remove('retreat'); if (game.actions && game.actions.block && game.actions.block.includes(block)) @@ -593,6 +608,8 @@ function update_battle() { ui.battle_menu[block].classList.add('harry'); if (game.actions && game.actions.battle_charge && game.actions.battle_charge.includes(block)) ui.battle_menu[block].classList.add('charge'); + if (game.actions && game.actions.battle_withdraw && game.actions.battle_withdraw.includes(block)) + ui.battle_menu[block].classList.add('withdraw'); if (game.actions && game.actions.battle_hit && game.actions.battle_hit.includes(block)) ui.battle_menu[block].classList.add('hit'); if (game.actions && game.actions.battle_charge && game.actions.battle_charge.includes(block)) @@ -629,25 +646,22 @@ function update_battle() { if (player == FRANK) { fill_cell("FR", game.battle.FR, true); fill_cell("FA", game.battle.FA, false); - fill_cell("FB", game.battle.FB, false); fill_cell("FC", game.battle.FC, false); fill_cell("EA", game.battle.SA, false); - fill_cell("EB", game.battle.SB, false); fill_cell("EC", game.battle.SC, false); fill_cell("ER", game.battle.SR, true); } else { fill_cell("ER", game.battle.FR, true); fill_cell("EA", game.battle.FA, false); - fill_cell("EB", game.battle.FB, false); fill_cell("EC", game.battle.FC, false); fill_cell("FA", game.battle.SA, false); - fill_cell("FB", game.battle.SB, false); fill_cell("FC", game.battle.SC, false); fill_cell("FR", game.battle.SR, true); } } function on_update() { + show_action_button("#next_button", "next"); show_action_button("#pass_button", "pass"); show_action_button("#undo_button", "undo"); show_action_button("#sea_move_button", "sea_move"); |