diff options
author | Tor Andersson <tor@ccxvii.net> | 2021-06-20 00:19:57 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2022-11-16 19:19:38 +0100 |
commit | 4605ba9ebd7a63bbf2cc3bc14d72fb84d3dc1b64 (patch) | |
tree | 87917605a6237e523e3848f698dd7201b0eea5b1 | |
parent | ce43a7e11f7d69427fb397c14b332e96ab47cef8 (diff) | |
download | crusader-rex-4605ba9ebd7a63bbf2cc3bc14d72fb84d3dc1b64.tar.gz |
crusader: Stacked layout.
-rw-r--r-- | data.js | 2 | ||||
-rw-r--r-- | play.html | 6 | ||||
-rw-r--r-- | rules.js | 102 | ||||
-rw-r--r-- | ui.js | 60 |
4 files changed, 124 insertions, 46 deletions
@@ -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"); @@ -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> @@ -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) @@ -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"); |