diff options
-rw-r--r-- | play.html | 11 | ||||
-rw-r--r-- | play.js | 333 | ||||
-rw-r--r-- | rules.js | 42 |
3 files changed, 139 insertions, 247 deletions
@@ -6,11 +6,10 @@ <title>RICHARD III</title> <link rel="icon" href="icons/Red_Rose_Badge_of_Lancaster.svg"> <link rel="stylesheet" href="/fonts/fonts.css"> -<link rel="stylesheet" href="/common/grid.css"> -<link rel="stylesheet" href="/common/battle_abc.css"> +<link rel="stylesheet" href="/common/play.css"> +<link rel="stylesheet" href="/common/columbia.css"> <link rel="stylesheet" href="play.css"> -<script defer src="/socket.io/socket.io.min.js"></script> -<script defer src="/common/client.js"></script> +<script defer src="/common/play.js"></script> <script defer src="data.js"></script> <script defer src="play.js"></script> </head> @@ -62,7 +61,7 @@ <aside> <div id="roles"> - <div class="role one"> + <div class="role" id="role_Lancaster"> <div class="role_name"> Lancaster <div class="role_vp" id="lancaster_vp"></div> @@ -70,7 +69,7 @@ </div> <div class="role_info"><div class="show card card_back" id="lancaster_card"></div></div> </div> - <div class="role two"> + <div class="role" id="role_York"> <div class="role_name"> York <div class="role_vp" id="york_vp"></div> @@ -52,8 +52,7 @@ let ui = { cards: {}, card_backs: {}, areas: {}, - known: {}, - secret: { Lancaster: {}, York: {}, Rebel: {} }, + blocks: {}, battle_menu: {}, battle_block: {}, present: new Set(), @@ -87,6 +86,10 @@ create_log_entry = function (text) { return p; } +function is_known_block(b) { + return view.game_over || block_owner(b) === player; +} + function on_focus_area(evt) { let where = evt.target.area; let text = where; @@ -124,40 +127,34 @@ function block_name(who) { function block_owner(who) { if (who === REBEL) { - if (game.pretender) - return BLOCKS[game.pretender].owner; - if (game.king) - return ENEMY[BLOCKS[game.king].owner]; + if (view.pretender) + return BLOCKS[view.pretender].owner; + if (view.king) + return ENEMY[BLOCKS[view.king].owner]; return YORK; } return BLOCKS[who].owner; } -function on_focus_secret_block(evt) { - let owner = evt.target.owner; - let text = owner; - document.getElementById("status").textContent = text; -} - -function on_blur_secret_block(evt) { - document.getElementById("status").textContent = ""; -} - -function on_click_secret_block(evt) { -} - function on_focus_map_block(evt) { let b = evt.target.block; - let s = game.known[b][1]; - let text = block_name(b) + " "; - if (BLOCKS[b].type === 'heir') - text += "H" + HEIR_TEXT[BLOCKS[b].heir] + "-"; - if (BLOCKS[b].loyalty) - text += BLOCKS[b].loyalty + "-"; - else if (BLOCKS[b].type === 'nobles') - text += "\u2740-"; - text += STEP_TEXT[s] + "-" + BLOCKS[b].combat; - document.getElementById("status").textContent = text; + if (is_known_block(b)) { + let s = BLOCKS[b].steps; + let text = block_name(b) + " "; + if (BLOCKS[b].type === 'heir') + text += "H" + HEIR_TEXT[BLOCKS[b].heir] + "-"; + if (BLOCKS[b].loyalty) + text += BLOCKS[b].loyalty + "-"; + else if (BLOCKS[b].type === 'nobles') + text += "\u2740-"; + text += STEP_TEXT[s] + "-" + BLOCKS[b].combat; + document.getElementById("status").textContent = text; + } else { + let owner = block_owner(b); + if (b === REBEL) + owner = "Rebel"; + document.getElementById("status").textContent = owner; + } } function on_blur_map_block(evt) { @@ -166,7 +163,7 @@ function on_blur_map_block(evt) { function on_click_map_block(evt) { let b = evt.target.block; - if (!game.battle) + if (!view.battle) send_action('block', b); } @@ -180,20 +177,20 @@ function is_battle_reserve(who, list) { function on_focus_battle_block(evt) { let b = evt.target.block; let msg = block_name(b); - if (is_battle_reserve(b, game.battle.LR)) + if (is_battle_reserve(b, view.battle.LR)) msg = "Lancaster Reserve"; - if (is_battle_reserve(b, game.battle.YR)) + if (is_battle_reserve(b, view.battle.YR)) msg = "York Reserve"; - if (game.actions && game.actions.battle_fire && game.actions.battle_fire.includes(b)) + if (view.actions && view.actions.battle_fire && view.actions.battle_fire.includes(b)) msg = "Fire with " + msg; - else if (game.actions && game.actions.battle_retreat && game.actions.battle_retreat.includes(b)) + else if (view.actions && view.actions.battle_retreat && view.actions.battle_retreat.includes(b)) msg = "Retreat with " + msg; - else if (game.actions && game.actions.battle_charge && game.actions.battle_charge.includes(b)) + else if (view.actions && view.actions.battle_charge && view.actions.battle_charge.includes(b)) msg = "Charge " + msg; - else if (game.actions && game.actions.battle_treachery && game.actions.battle_treachery.includes(b)) + else if (view.actions && view.actions.battle_treachery && view.actions.battle_treachery.includes(b)) msg = "Attempt treachery on " + msg; - else if (game.actions && game.actions.battle_hit && game.actions.battle_hit.includes(b)) + else if (view.actions && view.actions.battle_hit && view.actions.battle_hit.includes(b)) msg = "Take hit on " + msg; document.getElementById("status").textContent = msg; @@ -229,7 +226,7 @@ function on_focus_battle_hit(evt) { } function on_focus_battle_charge(evt) { - if (block_owner(evt.target.block) === game.active) + if (block_owner(evt.target.block) === view.active) document.getElementById("status").textContent = "Charge with " + block_name(evt.target.block); else @@ -238,7 +235,7 @@ function on_focus_battle_charge(evt) { } function on_focus_battle_treachery(evt) { - if (block_owner(evt.target.block) === game.active) + if (block_owner(evt.target.block) === view.active) document.getElementById("status").textContent = "Attempt treachery with " + block_name(evt.target.block); else @@ -323,7 +320,7 @@ function build_battle_block(b, block) { ui.battle_menu[b] = menu; } -function build_known_block(b, block) { +function build_map_block(b, block) { let element = document.createElement("div"); element.classList.add("block"); element.classList.add("known"); @@ -333,19 +330,7 @@ function build_known_block(b, block) { element.addEventListener("mouseleave", on_blur_map_block); element.addEventListener("click", on_click_map_block); element.block = b; - return element; -} - -function build_secret_block(b, block) { - let element = document.createElement("div"); - element.classList.add("block"); - element.classList.add("secret"); - element.classList.add(BLOCKS[b].owner); - element.addEventListener("mouseenter", on_focus_secret_block); - element.addEventListener("mouseleave", on_blur_secret_block); - element.addEventListener("click", on_click_secret_block); - element.owner = BLOCKS[b].owner; - return element; + ui.blocks[b] = element; } function build_map() { @@ -372,19 +357,12 @@ function build_map() { element.addEventListener("click", on_click_area); ui.areas[name] = element; } - ui.secret.Lancaster[name] = []; - ui.secret.York[name] = []; - ui.secret.Rebel[name] = []; } - ui.secret.Lancaster.offmap = []; - ui.secret.York.offmap = []; - ui.secret.Rebel.offmap = []; for (let b in BLOCKS) { 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)); + build_map_block(b, block); } } @@ -477,101 +455,37 @@ function update_map() { let layout = {}; document.getElementById("turn_info").textContent = - "Campaign " + game.campaign + - "\nKing: " + block_name(game.king) + - "\nPretender: " + block_name(game.pretender); + "Campaign " + view.campaign + + "\nKing: " + block_name(view.king) + + "\nPretender: " + block_name(view.pretender); for (let area in AREAS) layout[area] = { Lancaster: [], York: [] }; - // Move secret blocks to overflow queue if there are too many in a area - for (let area in AREAS) { - for (let color of [LANCASTER, YORK, REBEL]) { - if (game.secret[color]) { - let max = game.secret[color][area] ? game.secret[color][area][0] : 0; - while (ui.secret[color][area].length > max) { - overflow[color].push(ui.secret[color][area].pop()); - } - } - } - } - - // Add secret blocks if there are too few in a location - for (let area in AREAS) { - for (let color of [LANCASTER, YORK, REBEL]) { - if (game.secret[color]) { - let max = game.secret[color][area] ? game.secret[color][area][0] : 0; - while (ui.secret[color][area].length < max) { - if (overflow[color].length > 0) { - ui.secret[color][area].push(overflow[color].pop()); - } else { - let element = ui.secret[color].offmap.pop(); - show_block(element); - ui.secret[color][area].push(element); - } - } - } - } - } - - // Remove any blocks left in the overflow queue - for (let color of [LANCASTER, YORK, REBEL]) { - while (overflow[color].length > 0) { - let element = overflow[color].pop(); - hide_block(element); - ui.secret[color].offmap.push(element); - } - } - - // Hide formerly known blocks - for (let b in BLOCKS) { - if (!(b in game.known)) { - hide_block(ui.known[b]); - } - } - - // Add secret blocks to layout - for (let area in AREAS) { - for (let color of [LANCASTER, YORK, REBEL]) { - let i = 0, n = 0, m = 0; - if (game.secret[color] && game.secret[color][area]) { - n = game.secret[color][area][0]; - m = game.secret[color][area][1]; - } - for (let element of ui.secret[color][area]) { - if (i++ < n - m) - element.classList.remove("moved"); - else - element.classList.add("moved"); - if (color === REBEL) - layout[area][BLOCKS[game.pretender].owner].push(element); - else - layout[area][color].push(element); + for (let b in view.location) { + let info = BLOCKS[b]; + let element = ui.blocks[b]; + let area = view.location[b]; + if (area in AREAS) { + let moved = view.moved[b] ? " moved" : ""; + if (is_known_block(b)) { + let image = " block_" + info.image; + let steps = " r" + (info.steps - view.steps[b]); + let known = " known"; + element.classList = info.owner + known + " block" + image + steps + moved; + } else { + element.classList = info.owner + " block" + moved; } - } - } - - // Add known blocks to layout - for (let b in game.known) { - let area = game.known[b][0]; - if (area) { - let steps = game.known[b][1]; - let moved = game.known[b][2]; - let element = ui.known[b]; - let color = block_owner(b); - - show_block(element); - layout[area][color].push(element); - update_steps(b, steps, element); - - if (moved) - element.classList.add("moved"); + if (block_owner(b) === LANCASTER) + layout[area].Lancaster.push(element); else - element.classList.remove("moved"); + layout[area].York.push(element); + show_block(element); + } else { + hide_block(element); } } - // Layout blocks on map for (let area in AREAS) { if (area === POOL) { layout_blocks("LPool", layout[area].Lancaster, []); @@ -590,27 +504,27 @@ function update_map() { ui.areas[where].classList.remove('where'); } } - if (game.actions && game.actions.area) - for (let where of game.actions.area) + if (view.actions && view.actions.area) + for (let where of view.actions.area) ui.areas[where].classList.add('highlight'); - if (game.where) - ui.areas[game.where].classList.add('where'); + if (view.where) + ui.areas[view.where].classList.add('where'); for (let b in BLOCKS) { - ui.known[b].classList.remove('highlight'); - ui.known[b].classList.remove('selected'); + ui.blocks[b].classList.remove('highlight'); + ui.blocks[b].classList.remove('selected'); } - if (!game.battle) { - if (game.actions && game.actions.block) - for (let b of game.actions.block) - ui.known[b].classList.add('highlight'); - if (game.who) - ui.known[game.who].classList.add('selected'); + if (!view.battle) { + if (view.actions && view.actions.block) + for (let b of view.actions.block) + ui.blocks[b].classList.add('highlight'); + if (view.who) + ui.blocks[view.who].classList.add('selected'); } } function update_cards() { - let cards = game.hand; + let cards = view.hand; for (let c = 1; c <= 25; ++c) { ui.cards[c].classList.remove('enabled'); if (cards && cards.includes(c)) @@ -619,28 +533,26 @@ function update_cards() { ui.cards[c].classList.remove('show'); } - if (player === 'Observer') { - let n = game.hand.length; - for (let c = 1; c <= 7; ++c) - if (c <= n) - ui.card_backs[c].classList.add("show"); - else - ui.card_backs[c].classList.remove("show"); - } + let n = view.hand.length; + for (let c = 1; c <= 7; ++c) + if (c <= n && player === 'Observer') + ui.card_backs[c].classList.add("show"); + else + ui.card_backs[c].classList.remove("show"); - if (game.actions && game.actions.play) { - for (let c of game.actions.play) + if (view.actions && view.actions.play) { + for (let c of view.actions.play) ui.cards[c].classList.add('enabled'); } - if (!game.l_card) + if (!view.l_card) document.getElementById("lancaster_card").className = "show card card_back"; else - document.getElementById("lancaster_card").className = "show card " + CARDS[game.l_card].image; - if (!game.y_card) + document.getElementById("lancaster_card").className = "show card " + CARDS[view.l_card].image; + if (!view.y_card) document.getElementById("york_card").className = "show card card_back"; else - document.getElementById("york_card").className = "show card " + CARDS[game.y_card].image; + document.getElementById("york_card").className = "show card " + CARDS[view.y_card].image; } function update_battle() { @@ -649,10 +561,10 @@ function update_battle() { ui.present.clear(); - for (let [block, steps, moved] of list) { + for (let block of list) { ui.present.add(block); - if (block === game.who) + if (block === view.who) ui.battle_block[block].classList.add("selected"); else ui.battle_block[block].classList.remove("selected"); @@ -665,27 +577,27 @@ function update_battle() { ui.battle_menu[block].classList.remove('charge'); ui.battle_menu[block].classList.remove('treachery'); - if (game.actions && game.actions.block && game.actions.block.includes(block)) + if (view.actions && view.actions.block && view.actions.block.includes(block)) ui.battle_block[block].classList.add("highlight"); - if (game.actions && game.actions.battle_fire && game.actions.battle_fire.includes(block)) + if (view.actions && view.actions.battle_fire && view.actions.battle_fire.includes(block)) ui.battle_menu[block].classList.add('fire'); - if (game.actions && game.actions.battle_retreat && game.actions.battle_retreat.includes(block)) + if (view.actions && view.actions.battle_retreat && view.actions.battle_retreat.includes(block)) ui.battle_menu[block].classList.add('retreat'); - if (game.actions && game.actions.battle_pass && game.actions.battle_pass.includes(block)) + if (view.actions && view.actions.battle_pass && view.actions.battle_pass.includes(block)) ui.battle_menu[block].classList.add('pass'); - if (game.actions && game.actions.battle_hit && game.actions.battle_hit.includes(block)) + if (view.actions && view.actions.battle_hit && view.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)) + if (view.actions && view.actions.battle_charge && view.actions.battle_charge.includes(block)) ui.battle_menu[block].classList.add('charge'); - if (game.actions && game.actions.battle_treachery && game.actions.battle_treachery.includes(block)) + if (view.actions && view.actions.battle_treachery && view.actions.battle_treachery.includes(block)) ui.battle_menu[block].classList.add('treachery'); - update_steps(block, steps, ui.battle_block[block], false); + update_steps(block, view.steps[block], ui.battle_block[block]); if (reserve) ui.battle_block[block].classList.add("secret"); else ui.battle_block[block].classList.remove("secret"); - if (moved) + if (view.moved[block]) ui.battle_block[block].classList.add("moved"); else ui.battle_block[block].classList.remove("moved"); @@ -707,32 +619,32 @@ function update_battle() { } if (player === LANCASTER) { - fill_cell("FR", game.battle.LR, true); - fill_cell("FA", game.battle.LA, false); - fill_cell("FB", game.battle.LB, false); - fill_cell("FC", game.battle.LC, false); - fill_cell("FD", game.battle.LD, false); - fill_cell("EA", game.battle.YA, false); - fill_cell("EB", game.battle.YB, false); - fill_cell("EC", game.battle.YC, false); - fill_cell("ED", game.battle.YD, false); - fill_cell("ER", game.battle.YR, true); + fill_cell("FR", view.battle.LR, true); + fill_cell("FA", view.battle.LA, false); + fill_cell("FB", view.battle.LB, false); + fill_cell("FC", view.battle.LC, false); + fill_cell("FD", view.battle.LD, false); + fill_cell("EA", view.battle.YA, false); + fill_cell("EB", view.battle.YB, false); + fill_cell("EC", view.battle.YC, false); + fill_cell("ED", view.battle.YD, false); + fill_cell("ER", view.battle.YR, true); } else { - fill_cell("ER", game.battle.LR, true); - fill_cell("EA", game.battle.LA, false); - fill_cell("EB", game.battle.LB, false); - fill_cell("EC", game.battle.LC, false); - fill_cell("ED", game.battle.LD, false); - fill_cell("FA", game.battle.YA, false); - fill_cell("FB", game.battle.YB, false); - fill_cell("FC", game.battle.YC, false); - fill_cell("FD", game.battle.YD, false); - fill_cell("FR", game.battle.YR, true); + fill_cell("ER", view.battle.LR, true); + fill_cell("EA", view.battle.LA, false); + fill_cell("EB", view.battle.LB, false); + fill_cell("EC", view.battle.LC, false); + fill_cell("ED", view.battle.LD, false); + fill_cell("FA", view.battle.YA, false); + fill_cell("FB", view.battle.YB, false); + fill_cell("FC", view.battle.YC, false); + fill_cell("FD", view.battle.YD, false); + fill_cell("FR", view.battle.YR, true); } } function on_update() { - let king = block_owner(game.king); + let king = block_owner(view.king); document.getElementById("lancaster_vp").textContent = (king === LANCASTER ? KING_TEXT : PRETENDER_TEXT); document.getElementById("york_vp").textContent = (king === YORK ? KING_TEXT : PRETENDER_TEXT); @@ -751,9 +663,9 @@ function on_update() { update_cards(); update_map(); - if (game.battle) { - document.getElementById("battle_header").textContent = game.battle.title; - document.getElementById("battle_message").textContent = game.battle.flash; + if (view.battle) { + document.getElementById("battle_header").textContent = view.battle.title; + document.getElementById("battle_message").textContent = view.battle.flash; document.getElementById("battle").classList.add("show"); update_battle(); } else { @@ -765,6 +677,3 @@ build_map(); drag_element_with_mouse("#battle", "#battle_header"); scroll_with_middle_mouse("main", 2); -init_map_zoom(); -init_shift_zoom(); -init_client(["Lancaster", "York"]); @@ -129,7 +129,7 @@ function clear_undo() { function push_undo() { game.undo.push(JSON.stringify(game, (k,v) => { - if (k === 'undo') return undefined; + if (k === 'undo') return 0; if (k === 'log') return v.length; return v; })); @@ -138,7 +138,7 @@ function push_undo() { function pop_undo() { let undo = game.undo; let save_log = game.log; - Object.assign(game, JSON.parse(undo.pop())); + game = JSON.parse(undo.pop()); game.undo = undo; save_log.length = game.log; game.log = save_log; @@ -1332,8 +1332,10 @@ function start_campaign() { } function start_game_turn() { + game.turn = 8 - game.l_hand.length; + log(""); - log("Start Turn " + (8-game.l_hand.length) + " of campaign " + game.campaign + "."); + log("Start Turn " + game.turn + " of campaign " + game.campaign + "."); // Reset movement and attack tracking state reset_border_limits(); @@ -3336,7 +3338,7 @@ function make_battle_view() { function fill_cell(cell, owner, fn) { for (let b in BLOCKS) if (game.location[b] === game.where & block_owner(b) === owner && !game.dead[b] && fn(b)) - cell.push([b, game.steps[b], game.moved[b]?1:0]) + cell.push(b); } fill_cell(battle.LR, LANCASTER, b => is_battle_reserve(b)); @@ -3397,7 +3399,7 @@ exports.action = function (state, current, action, arg) { S[action](arg, current); else throw new Error("Invalid action: " + action); - return state; + return game; } exports.resign = function (state, current) { @@ -3410,7 +3412,7 @@ exports.resign = function (state, current) { game.victory = current + " resigned."; game.result = ENEMY[current]; } - return state; + return game; } function observer_hand() { @@ -3420,6 +3422,8 @@ function observer_hand() { return hand; } +exports.is_checkpoint = (a, b) => a.turn !== b.turn; + exports.view = function(state, current) { game = state; @@ -3434,8 +3438,9 @@ exports.view = function(state, current) { hand: (current === LANCASTER) ? game.l_hand : (current === YORK) ? game.y_hand : observer_hand(), who: (game.active === current) ? game.who : null, where: game.where, - known: {}, - secret: { York: {}, Lancaster: {}, Rebel: {} }, + location: game.location, + steps: game.steps, + moved: game.moved, battle: null, prompt: null, actions: null, @@ -3446,26 +3451,5 @@ exports.view = function(state, current) { if (states[game.state].show_battle) view.battle = make_battle_view(); - for (let b in BLOCKS) { - let a = game.location[b]; - if (!a) - continue; - - let is_known = false; - if (current === block_owner(b) || (game.dead[b] && is_block_on_map(b)) || game.state === 'game_over') - is_known = true; - - if (is_known) { - view.known[b] = [a, game.steps[b], (game.moved[b] || game.dead[b]) ? 1 : 0]; - } else { - let list = view.secret[BLOCKS[b].owner]; - if (!(a in list)) - list[a] = [0, 0]; - list[a][0]++; - if (game.moved[b] || game.dead[b]) - list[a][1]++; - } - } - return view; } |