summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.js2
-rw-r--r--play.html6
-rw-r--r--rules.js102
-rw-r--r--ui.js60
4 files changed, 124 insertions, 46 deletions
diff --git a/data.js b/data.js
index 7b89d3f..dd77abc 100644
--- a/data.js
+++ b/data.js
@@ -154,7 +154,7 @@ const PORTS = [];
frank(33, "Leopold", "Germania", 2, 3, "B3", "Crusaders");
frank(11, "Richard", "England", 3, 4, "B4", "Crusaders");
- frank(21, "Robert", "England", 2, 3, "B3", "Crusaders");
+ frank(21, "Robert", "Normandy", 2, 3, "B3", "Crusaders");
frank(31, "Crossbows", "Aquitaine", 2, 3, "A2", "Crusaders");
frank(12, "Philippe", "France", 2, 4, "B3", "Crusaders");
diff --git a/play.html b/play.html
index 80b931b..a2d760f 100644
--- a/play.html
+++ b/play.html
@@ -133,7 +133,7 @@ body.shift .block.known:hover {
.map .block { position: absolute; z-index: 2; }
.map .block.highlight { z-index: 3; }
.map .block.selected { z-index: 4; }
-.map .block:hover { z-index: 5; }
+.map .block.known:hover { z-index: 5; }
.block.highlight { cursor: pointer; box-shadow: 0px 0px 4px 1px white; }
@@ -303,9 +303,13 @@ body.shift .block.known:hover {
<div class="menu_title"><img src="/images/cog.svg"></div>
<div class="menu_popup">
<div class="menu_item" onclick="toggle_fullscreen()">Fullscreen</div>
+ <div class="menu_separator"></div>
<div class="menu_item" onclick="wide_map()">Wide Map</div>
<div class="menu_item" onclick="tall_map()">Tall Map</div>
<div class="menu_separator"></div>
+ <div class="menu_item" onclick="set_spread_layout()">Spread blocks</div>
+ <div class="menu_item" onclick="set_stack_layout()">Stack blocks</div>
+ <div class="menu_separator"></div>
<div class="menu_item" onclick="window.open('info/notes.html', '_blank')">Notes</div>
<div class="menu_item" onclick="window.open('info/rules.html', '_blank')">Rules</div>
<div class="menu_item" onclick="window.open('info/cards.html', '_blank')">Cards</div>
diff --git a/rules.js b/rules.js
index 4cc8f8a..b8e85fd 100644
--- a/rules.js
+++ b/rules.js
@@ -3,6 +3,9 @@
// TODO: frank seat adjustment at setup
// TODO: saladin seat adjustment at setup
+// TODO: optional rule - iron bridge
+// TODO: optional rule - force marches
+
exports.scenarios = [
"Third Crusade"
];
@@ -15,6 +18,7 @@ const ASSASSINS = "Assassins";
const ENEMY = { Frank: "Saracen", Saracen: "Frank" };
const OBSERVER = "Observer";
const BOTH = "Both";
+const DEAD = "Dead";
const F_POOL = "F. Pool";
const S_POOL = "S. Pool";
@@ -185,6 +189,7 @@ function block_type(who) {
function block_home(who) {
let home = BLOCKS[who].home;
+ if (home == "Normandy") return "England";
if (home == "Aquitaine") return "England";
if (home == "Bourgogne") return "France";
if (home == "Flanders") return "France";
@@ -222,8 +227,13 @@ function block_max_steps(who) {
return BLOCKS[who].steps;
}
+function is_saladin_family(who) {
+ return who == "Saladin" || who == "Al Adil" || who == "Al Aziz" || who == "Al Afdal" || who == "Al Zahir";
+}
+
function is_block_on_map(who) {
- return game.location[who] && game.location[who] != F_POOL && game.location[who] != S_POOL;
+ let location = game.location[who];
+ return location && location != DEAD && location != F_POOL && location != S_POOL;
}
function can_activate(who) {
@@ -273,7 +283,7 @@ function count_enemy_excluding_reserves(where) {
let count = 0;
for (let b in BLOCKS)
if (game.location[b] == where && block_owner(b) == p)
- if (!game.reserves.includes(b))
+ if (!is_battle_reserve(b))
++count;
return count;
}
@@ -312,7 +322,7 @@ function count_pinned(where) {
let count = 0;
for (let b in BLOCKS)
if (game.location[b] == where && block_owner(b) == game.active)
- if (!game.reserves.includes(b))
+ if (!is_battle_reserve(b))
++count;
return count;
}
@@ -487,18 +497,18 @@ function can_muster_to(muster) {
}
function is_battle_reserve(who) {
- return game.reserves.includes(who);
+ return game.reserves1.includes(who) || game.reserves2.includes(who);
}
function is_attacker(who) {
if (game.location[who] == game.where && block_owner(who) == game.attacker[game.where])
- return !game.reserves.includes(who);
+ return !is_battle_reserve(who);
return false;
}
function is_defender(who) {
if (game.location[who] == game.where && block_owner(who) != game.attacker[game.where])
- return !game.reserves.includes(who);
+ return !is_battle_reserve(who);
return false;
}
@@ -510,7 +520,10 @@ function disband(who) {
function eliminate_block(who) {
log(block_name(who) + " is eliminated.");
- game.location[who] = null;
+ if (is_saladin_family(who) || block_type(who) == 'crusaders' || block_type(who) == 'military_orders')
+ game.location[who] = null; // permanently eliminated
+ else
+ game.location[who] = DEAD; // into to the pool next year
game.steps[who] = block_max_steps(who);
}
@@ -570,7 +583,8 @@ function start_game_turn() {
reset_road_limits();
game.last_used = {};
game.attacker = {};
- game.reserves = [];
+ game.reserves1 = [];
+ game.reserves2 = [];
game.moved = {};
goto_card_phase();
@@ -708,7 +722,7 @@ function goto_event_card(event) {
end_player_turn();
}
-// ACTION PHASE
+// MOVE PHASE
function move_block(who, from, to) {
game.location[who] = to;
@@ -724,7 +738,7 @@ function move_block(who, from, to) {
// Attacker main attack or reinforcements
if (game.attacker[to] == game.active) {
if (game.main_road[to] != from) {
- game.reserves.push(who);
+ game.reserves1.push(who);
return RESERVE_MARK_1;
}
return ATTACK_MARK;
@@ -735,10 +749,10 @@ function move_block(who, from, to) {
game.main_road[to] = from;
if (game.main_road[to] == from) {
- game.reserves.push(who);
+ game.reserves1.push(who);
return RESERVE_MARK_1;
} else {
- game.reserves.push(who);
+ game.reserves2.push(who);
return RESERVE_MARK_2;
}
}
@@ -845,6 +859,23 @@ states.group_move_to = {
undo: pop_undo
}
+function end_move() {
+ if (game.distance > 0) {
+ let to = game.location[game.who];
+ if (!game.activated.includes(game.origin)) {
+ logp("activates " + game.origin + ".");
+ game.activated.push(game.origin);
+ game.moves --;
+ }
+ game.moved[game.who] = true;
+ }
+ log_move_end();
+ game.who = null;
+ game.distance = 0;
+ game.origin = null;
+ game.state = 'group_move';
+}
+
function can_sea_move_anywhere() {
if (game.moves > 0) {
for (let b in BLOCKS)
@@ -974,19 +1005,6 @@ states.muster_who = {
undo: pop_undo,
}
-function end_muster_move() {
- let muster = game.where;
- log_move_end();
- game.moved[game.who] = true;
- game.who = null;
- game.state = 'muster_who';
- if (!game.mustered.includes(muster)) {
- logp("musters to " + muster + ".");
- game.mustered.push(muster);
- --game.moves;
- }
-}
-
states.muster_move_1 = {
prompt: function (view, current) {
if (is_inactive_player(current))
@@ -1088,26 +1106,23 @@ states.muster_move_3 = {
undo: pop_undo,
}
-function end_move() {
- if (game.distance > 0) {
- let to = game.location[game.who];
- if (!game.activated.includes(game.origin)) {
- logp("activates " + game.origin + ".");
- game.activated.push(game.origin);
- game.moves --;
- }
- game.moved[game.who] = true;
- }
+function end_muster_move() {
+ let muster = game.where;
log_move_end();
+ game.moved[game.who] = true;
game.who = null;
- game.distance = 0;
- game.origin = null;
- game.state = 'group_move';
+ game.state = 'muster_who';
+ if (!game.mustered.includes(muster)) {
+ logp("musters to " + muster + ".");
+ game.mustered.push(muster);
+ --game.moves;
+ }
}
// BATTLE PHASE
function goto_battle_phase() {
+ game.moved = {};
if (have_contested_towns()) {
game.active = game.p1;
game.state = 'battle_phase';
@@ -1166,10 +1181,12 @@ function end_battle() {
}
function bring_on_reserves(round) {
- // TODO: defender reserves in round 3...
for (let b in BLOCKS) {
if (game.location[b] == game.where) {
- remove_from_array(game.reserves, b);
+ if (round == 2)
+ remove_from_array(game.reserves1, b);
+ else if (round == 3)
+ remove_from_array(game.reserves2, b);
}
}
}
@@ -1631,7 +1648,8 @@ exports.setup = function (scenario, players) {
moved: {},
moves: 0,
prompt: null,
- reserves: [],
+ reserves1: [],
+ reserves2: [],
show_cards: false,
steps: {},
who: null,
@@ -1697,6 +1715,8 @@ exports.view = function(state, current) {
let a = game.location[b];
if (!a)
continue;
+ if (a == DEAD)
+ continue;
if (a == F_POOL) // && current != FRANK)
continue;
if (a == S_POOL) // && current != SARACEN)
diff --git a/ui.js b/ui.js
index aa48ca1..b78e413 100644
--- a/ui.js
+++ b/ui.js
@@ -5,6 +5,21 @@ const SARACEN = "Saracen";
const ASSASSINS = "Assassins";
const ENEMY = { Saracen: "Frank", Frank: "Saracen" }
const POOL = "Pool";
+const DEAD = "Dead";
+
+let label_layout = window.localStorage['crusader-rex/label-layout'] || 'spread';
+
+function set_spread_layout() {
+ label_layout = 'spread';
+ window.localStorage['crusader-rex/label-layout'] = label_layout;
+ update_map();
+}
+
+function set_stack_layout() {
+ label_layout = 'stack';
+ window.localStorage['crusader-rex/label-layout'] = label_layout;
+ update_map();
+}
function toggle_blocks() {
document.getElementById("map").classList.toggle("hide_blocks");
@@ -249,8 +264,9 @@ function build_known_block(b, block) {
return element;
}
-function build_secret_block(b, block) {
+function build_secret_block(b, block, secret_index) {
let element = document.createElement("div");
+ element.secret_index = secret_index;
element.classList.add("block");
element.classList.add("secret");
element.classList.add(BLOCKS[b].owner);
@@ -344,7 +360,8 @@ function build_map() {
let block = BLOCKS[b];
build_battle_block(b, block);
ui.known[b] = build_known_block(b, block);
- ui.secret[BLOCKS[b].owner].offmap.push(build_secret_block(b, block));
+ let e = build_secret_block(b, block, ui.secret[BLOCKS[b].owner].offmap.length);
+ ui.secret[BLOCKS[b].owner].offmap.push(e);
}
update_map_layout();
@@ -357,7 +374,14 @@ function update_steps(b, steps, element) {
element.classList.add("r"+(BLOCKS[b].steps - steps));
}
-function layout_blocks(town, secret, known) {
+function layout_blocks(location, secret, known) {
+ if (label_layout == 'spread' || (location == POOL || location == DEAD))
+ layout_blocks_spread(location, secret, known);
+ else
+ layout_blocks_stacked(location, secret, known);
+}
+
+function layout_blocks_spread(town, secret, known) {
let wrap = TOWNS[town].wrap;
let s = secret.length;
let k = known.length;
@@ -424,6 +448,33 @@ function position_block(town, row, n_rows, col, n_cols, element) {
element.style.top = ((flip_y(x,y) - block_size/2)|0)+"px";
}
+function layout_blocks_stacked(location, secret, known) {
+ let s = secret.length;
+ let k = known.length;
+ let both = secret.length > 0 && known.length > 0;
+ let i = 0;
+ while (secret.length > 0)
+ position_block_stacked(location, i++, (s-1)/2, both ? 1 : 0, secret.shift());
+ i = 0;
+ while (known.length > 0)
+ position_block_stacked(location, i++, (k-1)/2, 0, known.shift());
+}
+
+function position_block_stacked(location, i, c, k, element) {
+ let space = TOWNS[location];
+ let block_size = 60+6;
+ let x, y;
+ if (map_orientation == 'tall') {
+ x = space.x + (i - c) * 12 + k * 12;
+ y = space.y + (i - c) * 16 - k * 8;
+ } else {
+ x = space.x - (i - c) * 12 + k * 12;
+ y = space.y + (i - c) * 16 + k * 8;
+ }
+ element.style.left = ((flip_x(x,y) - block_size/2)|0)+"px";
+ element.style.top = ((flip_y(x,y) - block_size/2)|0)+"px";
+}
+
function show_block(element) {
if (element.parentElement != ui.blocks_element)
ui.blocks_element.appendChild(element);
@@ -507,6 +558,9 @@ function update_map() {
n = game.secret[color][town][0];
m = game.secret[color][town][1];
}
+ // Preserve block stacking order, but lets the user track block identities...
+ if (label_layout == 'stack')
+ ui.secret[color][town].sort((a,b) => b.secret_index - a.secret_index);
for (let element of ui.secret[color][town]) {
if (i++ < n - m)
element.classList.remove("moved");