diff options
Diffstat (limited to 'play.js')
-rw-r--r-- | play.js | 665 |
1 files changed, 665 insertions, 0 deletions
@@ -0,0 +1,665 @@ +"use strict"; + +const ENGLAND = "England"; +const SCOTLAND = "Scotland"; +const ENEMY = { Scotland: "England", England: "Scotland" } +const ENGLAND_BAG = "E. Bag"; +const SCOTLAND_BAG = "S. Bag"; + +const NOBLES = [ + "Angus", "Argyll", "Atholl", "Bruce", "Buchan", "Comyn", "Dunbar", + "Galloway", "Lennox", "Mar", "Mentieth", "Ross", "Steward" +]; + +let block_style = window.localStorage['hammer-of-the-scots/block-style'] || 'oldblocks'; +document.querySelector("body").classList.remove("oldblocks"); +document.querySelector("body").classList.remove("newblocks"); +document.querySelector("body").classList.add(block_style); + +function old_block_style() { + block_style = 'oldblocks'; + document.querySelector("body").classList.remove("oldblocks"); + document.querySelector("body").classList.remove("newblocks"); + document.querySelector("body").classList.add(block_style); + window.localStorage['hammer-of-the-scots/block-style'] = block_style; + update_map(); +} + +function new_block_style() { + block_style = 'newblocks'; + document.querySelector("body").classList.remove("oldblocks"); + document.querySelector("body").classList.remove("newblocks"); + document.querySelector("body").classList.add(block_style); + window.localStorage['hammer-of-the-scots/block-style'] = block_style; + update_map(); +} + +function toggle_blocks() { + document.getElementById("map").classList.toggle("hide_blocks"); +} + +let ui = { + cards: {}, + card_backs: {}, + areas: {}, + blocks: {}, + battle_menu: {}, + battle_block: {}, + present: new Set(), +} + +create_log_entry = function (text) { + let p = document.createElement("div"); + text = text.replace(/&/g, "&"); + text = text.replace(/</g, "<"); + text = text.replace(/>/g, ">"); + + text = text.replace(/\u2192 /g, "\u2192\xa0"); + + text = text.replace(/^([A-Z]):/, '<span class="$1"> $1 </span>'); + + if (text.match(/^~ .* ~$/)) + p.className = 'br', text = text.substring(2, text.length-2); + else if (text.match(/^Start England turn/)) + p.className = 'E'; + else if (text.match(/^Start Scotland turn/)) + p.className = 'S'; + else if (text.match(/^Start /)) + p.className = 'st', text = text.replace(/\.$/, ""); + else if (text.match(/^(Battle in|Defection battle in)/)) + p.className = 'bs'; + + if (text.match(/^Start /)) + text = text.substring(6); + + p.innerHTML = text; + return p; +} + +function on_focus_area(evt) { + let where = evt.target.area; + document.getElementById("status").textContent = where; +} + +function on_blur_area(evt) { + document.getElementById("status").textContent = ""; +} + +function on_click_area(evt) { + let where = evt.target.area; + send_action('area', where); +} + +const STEP_TEXT = [ 0, "I", "II", "III", "IIII" ]; + +function block_name(who) { + if (who === "Edward") + return view.edward === 1 ? "Edward I" : "Edward II"; + if (who === "King") + return "Scottish King"; + return BLOCKS[who].name; +} + +function is_known_block(b) { + return (view.game_over || BLOCKS[b].owner === player); +} + +function on_focus_map_block(evt) { + let b = evt.target.block; + let text; + if (is_known_block(b)) { + let s = BLOCKS[b].steps; + text = block_name(b); + text += " " + BLOCKS[b].move + "-" + STEP_TEXT[s] + "-" + BLOCKS[b].combat; + if (BLOCKS[b].mortal) + text += ' \u271d'; + } else { + text = (BLOCKS[b].owner === ENGLAND) ? "English" : "Scottish"; + } + document.getElementById("status").textContent = text; +} + +function on_blur_map_block(evt) { + document.getElementById("status").textContent = ""; +} + +function on_click_map_block(evt) { + let b = evt.target.block; + if (!view.battle) + send_action('block', b); +} + +function is_battle_reserve(who, list) { + for (let [b, s, m] of list) + if (who === b) + return true; + return false; +} + +function on_focus_battle_block(evt) { + let b = evt.target.block; + let msg = block_name(b); + if (is_battle_reserve(b, view.battle.ER)) + msg = "English Reserve"; + if (is_battle_reserve(b, view.battle.SR)) + msg = "Scottish Reserve"; + if (view.actions && view.actions.battle_fire && view.actions.battle_fire.includes(b)) + msg = "Fire with " + msg; + if (view.actions && view.actions.battle_hit && view.actions.battle_hit.includes(b)) + msg = "Take hit on " + msg; + document.getElementById("status").textContent = msg; +} + +function on_blur_battle_block(evt) { + document.getElementById("status").textContent = ""; +} + +function on_click_battle_block(evt) { + let b = evt.target.block; + send_action('block', b); +} + +function on_focus_battle_fire(evt) { + document.getElementById("status").textContent = + "Fire with " + block_name(evt.target.block); +} + +function on_focus_battle_retreat(evt) { + document.getElementById("status").textContent = + "Retreat with " + block_name(evt.target.block); +} + +function on_focus_battle_pass(evt) { + document.getElementById("status").textContent = + "Pass with " + block_name(evt.target.block); +} + +function on_focus_battle_hit(evt) { + document.getElementById("status").textContent = + "Take hit on " + block_name(evt.target.block); +} + +function on_blur_battle_button(evt) { + document.getElementById("status").textContent = ""; +} + +function on_click_battle_hit(evt) { send_action('battle_hit', evt.target.block); } +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_pass(evt) { + if (window.confirm("Are you sure that you want to PASS with " + block_name(evt.target.block) + "?")) + send_action('battle_pass', evt.target.block); +} + +function on_click_card(evt) { + let c = evt.target.id.split("+")[1] | 0; + send_action('play', c); +} + +function on_herald(noble) { + send_action('noble', noble); +} + +function build_battle_button(menu, b, c, click, enter, img_src) { + let img = new Image(); + img.draggable = false; + img.classList.add("action"); + img.classList.add(c); + img.setAttribute("src", img_src); + img.addEventListener("click", click); + img.addEventListener("mouseenter", enter); + img.addEventListener("mouseleave", on_blur_battle_button); + img.block = b; + menu.appendChild(img); +} + +function build_battle_block(b, block) { + let element = document.createElement("div"); + element.classList.add("block"); + element.classList.add("known"); + element.classList.add(BLOCKS[b].owner); + element.classList.add("block_" + block.image); + element.addEventListener("mouseenter", on_focus_battle_block); + element.addEventListener("mouseleave", on_blur_battle_block); + element.addEventListener("click", on_click_battle_block); + element.block = b; + ui.battle_block[b] = element; + + let menu_list = document.createElement("div"); + menu_list.classList.add("battle_menu_list"); + build_battle_button(menu_list, b, "hit", + on_click_battle_hit, on_focus_battle_hit, + "/images/cross-mark.svg"); + build_battle_button(menu_list, b, "fire", + on_click_battle_fire, on_focus_battle_fire, + "/images/pointy-sword.svg"); + 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, "pass", + on_click_battle_pass, on_focus_battle_pass, + "/images/sands-of-time.svg"); + + let menu = document.createElement("div"); + menu.classList.add("battle_menu"); + menu.appendChild(element); + menu.appendChild(menu_list); + menu.block = b; + ui.battle_menu[b] = menu; +} + +function build_map_block(b, block) { + let element = document.createElement("div"); + element.classList.add("block"); + element.classList.add("known"); + element.classList.add(BLOCKS[b].owner); + element.classList.add("block_" + block.image); + element.addEventListener("mouseenter", on_focus_map_block); + element.addEventListener("mouseleave", on_blur_map_block); + element.addEventListener("click", on_click_map_block); + element.block = b; + ui.blocks[b] = element; +} + +function build_map() { + let svgmap = document.getElementById("svgmap"); + + ui.blocks_element = document.getElementById("blocks"); + ui.offmap_element = document.getElementById("offmap"); + + for (let c = 1; c <= 25; ++c) { + ui.cards[c] = document.getElementById("card+"+c); + ui.cards[c].addEventListener("click", on_click_card); + } + + for (let c = 1; c <= 5; ++c) + ui.card_backs[c] = document.getElementById("back+"+c); + + for (let name in AREAS) { + let area = AREAS[name]; + let element = svgmap.getElementById("area+"+name); + if (element) { + element.area = name; + element.addEventListener("mouseenter", on_focus_area); + element.addEventListener("mouseleave", on_blur_area); + element.addEventListener("click", on_click_area); + ui.areas[name] = element; + } + } + + for (let b in BLOCKS) { + let block = BLOCKS[b]; + build_battle_block(b, block); + build_map_block(b, block); + } +} + +build_map(); + +function update_steps(b, steps, element) { + element.classList.remove("r1"); + element.classList.remove("r2"); + element.classList.remove("r3"); + element.classList.add("r"+(BLOCKS[b].steps - steps)); +} + +function layout_blocks(location, north, south) { + let wrap = 4; + let s = north.length; + let k = south.length; + let n = s + k; + let row, rows = []; + let i = 0; + + switch (location) { + case ENGLAND_BAG: + case SCOTLAND_BAG: + wrap = 28; + break; + case "Selkirk": + case "Lothian": + case "Dunbar": + case "Lanark": + case "Lennox": + case "Argyll": + case "Garmoran": + case "Mentieth": + wrap = 3; + break; + case "England": + wrap = 5; + } + + function new_line() { + rows.push(row = []); + i = 0; + } + + new_line(); + + while (north.length > 0) { + if (i === wrap) + new_line(); + row.push(north.shift()); + ++i; + } + + // Break early if north and south fit in exactly two rows + if (s > 0 && s <= wrap && k > 0 && k <= wrap) + new_line(); + + while (south.length > 0) { + if (i === wrap) + new_line(); + row.push(south.shift()); + ++i; + } + + for (let j = 0; j < rows.length; ++j) + for (i = 0; i < rows[j].length; ++i) + position_block(location, j, rows.length, i, rows[j].length, rows[j][i]); +} + +function position_block(location, row, n_rows, col, n_cols, element) { + let area = AREAS[location]; + let block_size = 60+6; + let padding = 4; + let offset = block_size + padding; + let row_size = (n_rows-1) * offset; + let col_size = (n_cols-1) * offset; + let x = area.x - block_size/2; + let y = area.y - block_size/2; + + let layout_major = 0.5; + let layout_minor = 0.5; + switch (location) { + case ENGLAND_BAG: + case SCOTLAND_BAG: + layout_major = 0; + layout_minor = 0; + break; + case ENGLAND: + layout_major = 1; + layout_minor = 1; + break; + case "Argyll": + layout_major = 0.5; + layout_minor = 1.0; + break; + case "Carrick": + layout_major = 0.75; + layout_minor = 0.5; + break; + case "Dunbar": + layout_major = 0.25; + layout_minor = 0.75; + break; + case "Fife": + layout_major = 0.25; + layout_minor = 0.5; + break; + case "Lennox": + layout_major = 0.75; + layout_minor = 0.75; + break; + case "Mentieth": + layout_major = 0.5; + layout_minor = 0.25; + break; + } + + x -= col_size * layout_major; + y -= row_size * layout_minor; + + x += col * offset; + y += row * offset; + + element.style.left = (x|0)+"px"; + element.style.top = (y|0)+"px"; +} + +function show_block(element) { + if (element.parentElement !== ui.blocks_element) + ui.blocks_element.appendChild(element); +} + +function hide_block(element) { + if (element.parentElement !== ui.offmap_element) + ui.offmap_element.appendChild(element); +} + +function update_map() { + let overflow = { England: [], Scotland: [] }; + let layout = {}; + + document.getElementById("turn").setAttribute("class", "turn year_" + view.year); + + for (let area in AREAS) + layout[area] = { north: [], south: [] }; + + 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; + } + if (info.owner === SCOTLAND) + layout[area].north.push(element); + else + layout[area].south.push(element); + show_block(element); + } else { + hide_block(element); + } + } + + for (let area in AREAS) + layout_blocks(area, layout[area].north, layout[area].south); + + // Mark selections and highlights + + for (let where in AREAS) { + if (ui.areas[where]) { + ui.areas[where].classList.remove('highlight'); + ui.areas[where].classList.remove('where'); + } + } + if (view.actions && view.actions.area) + for (let where of view.actions.area) + ui.areas[where].classList.add('highlight'); + if (view.where) + ui.areas[view.where].classList.add('where'); + + for (let b in BLOCKS) { + ui.blocks[b].classList.remove('highlight'); + ui.blocks[b].classList.remove('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 = view.hand; + for (let c = 1; c <= 25; ++c) { + ui.cards[c].classList.remove('enabled'); + if (cards && cards.includes(c)) + ui.cards[c].classList.add('show'); + else + ui.cards[c].classList.remove('show'); + } + + let n = view.hand.length; + for (let c = 1; c <= 5; ++c) + if (c <= n && player === 'Observer') + ui.card_backs[c].classList.add("show"); + else + ui.card_backs[c].classList.remove("show"); + + if (view.actions && view.actions.play) { + for (let c of view.actions.play) + ui.cards[c].classList.add('enabled'); + } + + if (!view.e_card) + document.getElementById("england_card").className = "show card card_back"; + else + document.getElementById("england_card").className = "show card " + CARDS[view.e_card].image; + if (!view.s_card) + document.getElementById("scotland_card").className = "show card card_back"; + else + document.getElementById("scotland_card").className = "show card " + CARDS[view.s_card].image; +} + +function compare_blocks(a, b) { + let aa = BLOCKS[a].combat; + let bb = BLOCKS[b].combat; + if (aa === bb) + return (a < b) ? -1 : (a > b) ? 1 : 0; + return (aa < bb) ? -1 : (aa > bb) ? 1 : 0; +} + +function insert_battle_block(root, node, block) { + for (let i = 0; i < root.children.length; ++i) { + let prev = root.children[i]; + if (compare_blocks(prev.block, block) > 0) { + root.insertBefore(node, prev); + return; + } + } + root.appendChild(node); +} + +function update_battle() { + function fill_cell(name, list, reserve) { + let cell = window[name]; + + ui.present.clear(); + + for (let block of list) { + ui.present.add(block); + + if (!cell.contains(ui.battle_menu[block])) + insert_battle_block(cell, ui.battle_menu[block], block); + + if (block === view.who) + ui.battle_block[block].classList.add("selected"); + else + ui.battle_block[block].classList.remove("selected"); + + ui.battle_block[block].classList.remove("highlight"); + ui.battle_menu[block].classList.remove('hit'); + ui.battle_menu[block].classList.remove('fire'); + ui.battle_menu[block].classList.remove('retreat'); + ui.battle_menu[block].classList.remove('pass'); + + if (view.actions && view.actions.block && view.actions.block.includes(block)) + ui.battle_block[block].classList.add("highlight"); + if (view.actions && view.actions.battle_fire && view.actions.battle_fire.includes(block)) + ui.battle_menu[block].classList.add('fire'); + if (view.actions && view.actions.battle_retreat && view.actions.battle_retreat.includes(block)) + ui.battle_menu[block].classList.add('retreat'); + if (view.actions && view.actions.battle_pass && view.actions.battle_pass.includes(block)) + ui.battle_menu[block].classList.add('pass'); + if (view.actions && view.actions.battle_hit && view.actions.battle_hit.includes(block)) + ui.battle_menu[block].classList.add('hit'); + + 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 (view.moved[block]) + ui.battle_block[block].classList.add("moved"); + else + ui.battle_block[block].classList.remove("moved"); + if (reserve) + ui.battle_block[block].classList.remove("known"); + else + ui.battle_block[block].classList.add("known"); + } + + for (let b in BLOCKS) { + if (!ui.present.has(b)) { + if (cell.contains(ui.battle_menu[b])) + cell.removeChild(ui.battle_menu[b]); + } + } + } + + if (player === ENGLAND) { + fill_cell("FR", view.battle.ER, true); + fill_cell("FF", view.battle.EF, false); + fill_cell("EF", view.battle.SF, false); + fill_cell("ER", view.battle.SR, true); + } else { + fill_cell("ER", view.battle.ER, true); + fill_cell("EF", view.battle.EF, false); + fill_cell("FF", view.battle.SF, false); + fill_cell("FR", view.battle.SR, true); + } +} + +function on_update() { + action_button("crown_bruce", "Crown Bruce"); + action_button("crown_comyn", "Crown Comyn"); + action_button("return_of_the_king", "Return of the King"); + action_button("play_event", "Play event"); + action_button("winter", "Winter"); + action_button("eliminate", "Eliminate"); + action_button("disband", "Disband"); + action_button("end_move_phase", "End move phase"); + action_button("end_regroup", "End regroup"); + action_button("end_retreat", "End retreat"); + action_button("end_disbanding", "End disbanding"); + action_button("end_builds", "End builds"); + action_button("end_pillage", "End pillage"); + action_button("pass", "Pass"); + action_button("undo", "Undo"); + + document.getElementById("england_vp").textContent = view.e_vp; + document.getElementById("scotland_vp").textContent = view.s_vp; + document.getElementById("turn_info").textContent = `Turn ${view.turn} of Year ${view.year}`; + + update_cards(); + update_map(); + + if (view.actions && view.actions.noble) { + document.getElementById("herald").classList.add("show"); + for (let noble of NOBLES) { + let element = document.getElementById("herald+" + noble); + if (view.actions.noble.includes(noble)) + element.classList.add("show"); + else + element.classList.remove("show"); + } + } else { + document.getElementById("herald").classList.remove("show"); + } + + 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 { + document.getElementById("battle").classList.remove("show"); + } +} + +drag_element_with_mouse("#battle", "#battle_header"); +drag_element_with_mouse("#herald", "#herald_header"); +scroll_with_middle_mouse("main", 2); |