summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.html34
-rw-r--r--rules.js126
-rw-r--r--ui.js24
3 files changed, 148 insertions, 36 deletions
diff --git a/play.html b/play.html
index 67c85f9..87339e9 100644
--- a/play.html
+++ b/play.html
@@ -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>
diff --git a/rules.js b/rules.js
index ff8d3f2..5369dc3 100644
--- a/rules.js
+++ b/rules.js
@@ -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: {},
diff --git a/ui.js b/ui.js
index aff3787..71ca0ce 100644
--- a/ui.js
+++ b/ui.js
@@ -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");