"use strict" function set_has(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else return true } return false } function map_get(map, key, missing) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return map[(m<<1)+1] } return missing } const LANCASTER = "Lancaster" const YORK = "York" const ENEMY = { York: "Lancaster", Lancaster: "York" } const NOWHERE = 0 const POOL = 1 const MINOR = 2 const L_POOL = area_index["LPool"] const Y_POOL = area_index["YPool"] const L_MINOR = area_index["LMinor"] const Y_MINOR = area_index["YMinor"] const NOBODY = -1 const B_YORK = block_index["York"] const B_MARCH = block_index["March"] const B_RUTLAND = block_index["Rutland"] const B_CLARENCE_Y = block_index["Clarence/Y"] const B_GLOUCESTER = block_index["Gloucester"] const B_EXETER_Y = block_index["Exeter/Y"] const B_WARWICK_Y = block_index["Warwick/Y"] const B_KENT_Y = block_index["Kent/Y"] const B_SALISBURY_Y = block_index["Salisbury/Y"] const B_IRISH_MERCENARY = block_index["Irish Mercenary"] const B_BURGUNDIAN_MERCENARY = block_index["Burgundian Mercenary"] const B_CALAIS_MERCENARY = block_index["Calais Mercenary"] const B_HENRY_VI = block_index["Henry VI"] const B_PRINCE_EDWARD = block_index["Prince Edward"] const B_EXETER_L = block_index["Exeter/L"] const B_SOMERSET = block_index["Somerset"] const B_RICHMOND = block_index["Richmond"] const B_WARWICK_L = block_index["Warwick/L"] const B_KENT_L = block_index["Kent/L"] const B_SALISBURY_L = block_index["Salisbury/L"] const B_CLARENCE_L = block_index["Clarence/L"] const B_SCOTS_MERCENARY = block_index["Scots Mercenary"] const B_WELSH_MERCENARY = block_index["Welsh Mercenary"] const B_FRENCH_MERCENARY = block_index["French Mercenary"] const B_REBEL = block_index["Rebel"] const KING_TEXT = "\u2756" const PRETENDER_TEXT = "" const LONG_NAME = { "Somerset": "Duke of Somerset", "Exeter": "Duke of Exeter", "Devon": "Earl of Devon", "Pembroke": "Earl of Pembroke", "Wiltshire": "Earl of Wiltshire", "Oxford": "Earl of Oxford", "Beaumont": "Viscount Beaumont", "Clifford": "Lord Clifford", "Buckingham": "Duke of Buckingham", "Northumberland": "Earl of Northumberland", "Shrewsbury": "Earl of Shrewsbury", "Westmoreland": "Earl of Westmoreland", "Rivers": "Lord Rivers", "Stanley": "Lord Stanley", "Richmond": "Earl of Richmond", "York": "Duke of York", "Rutland": "Earl of Rutland", "March": "Earl of March", "Warwick": "Earl of Warwick", "Salisbury": "Earl of Salisbury", "Kent": "Earl of Kent", "Norfolk": "Duke of Norfolk", "Suffolk": "Duke of Suffolk", "Arundel": "Earl of Arundel", "Essex": "Earl of Essex", "Worcester": "Earl of Worcester", "Hastings": "Lord Hastings", "Herbert": "Lord Herbert", "Clarence": "Duke of Clarence", "Gloucester": "Duke of Gloucester", } // :!r node tools/genborders.js const BORDERS_XY = { "English Channel / Calais": [1317,1792], "English Channel / France": [378,125], "English Channel / Cornwall": [382,1762], "English Channel / Dorset": [860,1660], "English Channel / Kent": [1437,1565], "English Channel / Sussex": [1090,1631], "Irish Sea / France": [185,245], "Irish Sea / Ireland": [176,396], "Irish Sea / Scotland": [575,284], "Irish Sea / Caernarvon": [524,816], "Irish Sea / Chester": [627,782], "Irish Sea / Cornwall": [286,1646], "Irish Sea / Cumbria": [570,482], "Irish Sea / Glamorgan": [459,1366], "Irish Sea / Isle of Man": [389,467], "Irish Sea / Lancaster": [645,654], "Irish Sea / Pembroke": [303,1187], "Irish Sea / Somerset": [688,1418], "North Sea / Calais": [1565,1760], "North Sea / Scotland": [844,16], "North Sea / East Anglia": [1582,972], "North Sea / East Yorks": [1224,643], "North Sea / Essex": [1504,1295], "North Sea / Kent": [1562,1445], "North Sea / Lincoln": [1305,801], "North Sea / Middlesex": [1382,1392], "North Sea / Northumbria": [952,248], "North Sea / Rutland": [1298,965], "Scotland / Cumbria": [679,248], "Scotland / Northumbria": [774,171], "Caernarvon / Chester": [677,920], "Caernarvon / Pembroke": [479,1071], "Caernarvon / Powys": [522,995], "Chester / Derby": [842,879], "Chester / Hereford": [717,1012], "Chester / Lancaster": [762,794], "Chester / Powys": [610,986], "Chester / Warwick": [793,982], "Cornwall / Dorset": [613,1627], "Cornwall / Somerset": [528,1574], "Cumbria / Lancaster": [740,517], "Cumbria / North Yorks": [819,478], "Cumbria / Northumbria": [782,342], "Derby / Lancaster": [881,784], "Derby / Leicester": [1029,943], "Derby / Lincoln": [1098,840], "Derby / South Yorks": [986,814], "Derby / Warwick": [878,981], "Dorset / Somerset": [716,1566], "Dorset / Sussex": [965,1582], "Dorset / Wilts": [879,1558], "East Anglia / Essex": [1452,1168], "East Anglia / Rutland": [1354,1042], "East Yorks / North Yorks": [971,521], "East Yorks / Northumbria": [997,431], "East Yorks / South Yorks": [1039,654], "Essex / Leicester": [1218,1165], "Essex / Middlesex": [1300,1261], "Essex / Rutland": [1287,1132], "Glamorgan / Hereford": [712,1226], "Glamorgan / Pembroke": [447,1274], "Glamorgan / Powys": [563,1195], "Gloucester / Hereford": [799,1282], "Gloucester / Oxford": [956,1254], "Gloucester / Somerset": [786,1397], "Gloucester / Warwick": [881,1208], "Gloucester / Wilts": [913,1351], "Hereford / Powys": [640,1112], "Hereford / Warwick": [793,1127], "Kent / Middlesex": [1329,1401], "Kent / Sussex": [1276,1509], "Lancaster / North Yorks": [791,623], "Lancaster / South Yorks": [880,701], "Leicester / Lincoln": [1145,937], "Leicester / Middlesex": [1157,1180], "Leicester / Oxford": [1051,1173], "Leicester / Rutland": [1172,1057], "Leicester / Warwick": [974,1048], "Lincoln / Rutland": [1211,967], "Lincoln / South Yorks": [1111,741], "Middlesex / Oxford": [1138,1298], "Middlesex / Sussex": [1152,1413], "North Yorks / Northumbria": [905,434], "North Yorks / South Yorks": [939,611], "Oxford / Sussex": [1085,1404], "Oxford / Warwick": [975,1128], "Oxford / Wilts": [1032,1363], "Pembroke / Powys": [517,1141], "Somerset / Wilts": [838,1452], "Sussex / Wilts": [1018,1495], } function toggle_blocks() { document.getElementById("map").classList.toggle("hide_blocks") } let ui = { cards: {}, card_backs: {}, areas: {}, blocks: {}, borders: [], battle_menu: {}, battle_block: {}, present: new Set(), } for (let a of AREAS) a.nbname = a.name.replace(/ /g, "\xa0") function remember_position(e) { if (e.classList.contains("show")) { let rect = e.getBoundingClientRect() e.my_parent = true e.my_x = rect.x e.my_y = rect.y } else { e.my_parent = false e.my_x = 0 e.my_y = 0 } } function animate_position(e) { if (e.parentElement) { if (e.my_parent) { let rect = e.getBoundingClientRect() let dx = e.my_x - rect.x let dy = e.my_y - rect.y if (dx !== 0 || dy !== 0) { e.animate( [ { transform: `translate(${dx}px, ${dy}px)`, }, { transform: "translate(0, 0)", }, ], { duration: 250, easing: "ease" } ) } } else { e.animate( [ { opacity: 0 }, { opacity: 1 } ], { duration: 250, easing: "ease" } ) } } } function on_focus_space_tip(x) { ui.areas[x].classList.add("tip") } function on_blur_space_tip(x) { ui.areas[x].classList.remove("tip") } function on_click_space_tip(x) { scroll_into_view(ui.areas[x]) } function sub_space_name(match, p1, offset, string) { let x = p1 | 0 let n = AREAS[x].nbname return `${n}` } function on_log(text) { let p = document.createElement("div") if (text.match(/^>/)) { text = text.substring(1) p.className = "i" } text = text.replace(/&/g, "&") text = text.replace(//g, ">") text = text.replace(/\u2192 /g, "\u2192\xa0") text = text.replace(/^([A-Z]):/, ' $1 ') text = text.replace(/#(\d+)/g, sub_space_name) if (text.match(/^\.h1 /)) p.className = 'h1', text = text.substring(4) if (text.match(/^\.h2 L/)) p.className = 'h2 L', text = text.substring(4) if (text.match(/^\.h2 Y/)) p.className = 'h2 Y', text = text.substring(4) if (text.match(/^\.h3 /)) p.className = 'h3', text = text.substring(4) if (text.match(/^\.h4 /)) p.className = 'h4', text = text.substring(4) p.innerHTML = text return p } function is_known_block(b) { if (view.game_over && player === 'Observer') return true return block_owner(b) === player } function on_focus_area(evt) { let where = evt.target.area let text = AREAS[where].name if (AREAS[where].city) text += " (" + AREAS[where].city + ")" if (AREAS[where].crown) text += " - Crown" // " \u2655" if (where === "South Yorks" || where === "Kent") text += " - Church" // " -" \u2657" if (AREAS[where].major_port) text += " - Port" if (AREAS[where].shields.length > 0) text += " - " + AREAS[where].shields.join(", ") document.getElementById("status").textContent = text } 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" ] const HEIR_TEXT = [ 0, '\u00b9', '\u00b2', '\u00b3', '\u2074', '\u2075' ] function block_name(who) { if (who === NOBODY) return "Nobody" let name = BLOCKS[who].name let long_name = LONG_NAME[name] return long_name ? long_name : name } function block_owner(who) { if (who === B_REBEL) { if (view.pretender !== NOBODY) return BLOCKS[view.pretender].owner if (view.king !== NOBODY) return ENEMY[BLOCKS[view.king].owner] return YORK } return BLOCKS[who].owner } function on_focus_map_block(evt) { let b = evt.target.block 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 === B_REBEL) owner = "Rebel" document.getElementById("status").textContent = owner } } 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 on_focus_battle_block(evt) { let b = evt.target.block let msg = block_name(b) if (view.battle.LR.includes(b)) msg = "Lancaster Reserve" if (view.battle.YR.includes(b)) msg = "York Reserve" if (view.actions && view.actions.battle_fire && view.actions.battle_fire.includes(b)) msg = "Fire with " + msg else if (view.actions && view.actions.battle_retreat && view.actions.battle_retreat.includes(b)) msg = "Retreat with " + msg else if (view.actions && view.actions.battle_charge && view.actions.battle_charge.includes(b)) msg = "Charge " + msg else if (view.actions && view.actions.battle_treachery && view.actions.battle_treachery.includes(b)) msg = "Attempt treachery on " + msg else 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_focus_battle_charge(evt) { if (block_owner(evt.target.block) === view.active) document.getElementById("status").textContent = "Charge with " + block_name(evt.target.block) else document.getElementById("status").textContent = "Charge " + block_name(evt.target.block) } function on_focus_battle_treachery(evt) { if (block_owner(evt.target.block) === view.active) document.getElementById("status").textContent = "Attempt treachery with " + block_name(evt.target.block) else document.getElementById("status").textContent = "Attempt treachery 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_charge(evt) { send_action('battle_charge', evt.target.block); } function on_click_battle_treachery(evt) { send_action('battle_treachery', 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 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, "treachery", on_click_battle_treachery, on_focus_battle_treachery, "/images/rose.svg") build_battle_button(menu_list, b, "charge", on_click_battle_charge, on_focus_battle_charge, "/images/mounted-knight.svg") build_battle_button(menu_list, b, "hit", on_click_battle_hit, on_focus_battle_hit, "/images/cross-mark.svg") // menu_list.appendChild(document.createElement("br")) 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 element 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 <= 7; ++c) ui.card_backs[c] = document.getElementById("back+"+c) for (let area = 0; area < AREAS.length; ++area) { let name = AREAS[area].name element = document.getElementById("svgmap").getElementById("area_"+name.replace(/ /g, "_")) if (element) { element.area = area element.addEventListener("mouseenter", on_focus_area) element.addEventListener("mouseleave", on_blur_area) element.addEventListener("click", on_click_area) ui.areas[area] = element } } for (let b = 0; b < BLOCKS.length; ++b) { let block = BLOCKS[b] build_battle_block(b, block) build_map_block(b, block) } for (let name in BORDERS_XY) { let [x, y] = BORDERS_XY[name] let [a, b] = name.split(" / ") let id = area_index[a] * 100 + area_index[b] let type = BORDERS[id] || "sea" if (type !== "sea") { let e = document.createElement("div") e.my_id = id e.my_show = "border " + type e.className = "hide" e.style.left = (x - 12) + "px" e.style.top = (y - 12) + "px" ui.borders.push(e) document.getElementById("borders").appendChild(e) } } } 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 compare_layout_blocks(a, b) { let ad = view.dead.includes(a.block) let bd = view.dead.includes(b.block) if (ad && !bd) return 1 if (bd && !ad) return -1 return a.block - b.block } function layout_blocks(area, secret, known) { secret.sort(compare_layout_blocks) known.sort(compare_layout_blocks) let wrap = AREAS[area].layout.wrap let s = secret.length let k = known.length let n = s + k let row, rows = [] let i = 0 function new_line() { rows.push(row = []) i = 0 } new_line() while (secret.length > 0) { if (i === wrap) new_line() row.push(secret.shift()) ++i } // Break early if secret and known fit in exactly two rows, and more than three blocks total if (s > 0 && s <= wrap && k > 0 && k <= wrap && n > 3) new_line() while (known.length > 0) { if (i === wrap) new_line() row.push(known.shift()) ++i } if (AREAS[area].layout.minor > 0.5) rows.reverse() for (let j = 0; j < rows.length; ++j) for (i = 0; i < rows[j].length; ++i) position_block(area, j, rows.length, i, rows[j].length, rows[j][i]) } function position_block(area, row, n_rows, col, n_cols, element) { let space = AREAS[area] let block_size = 62+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 = space.layout.x - block_size/2 let y = space.layout.y - block_size/2 if (space.layout.axis === 'X') { x -= col_size * space.layout.major y -= row_size * space.layout.minor x += col * offset y += row * offset } else { y -= col_size * space.layout.major x -= row_size * space.layout.minor y += col * offset x += 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 is_dead(who) { return view.location[who] === NOWHERE } function is_perma_dead(who) { if (BLOCKS[who].loyalty === 0) return true switch (who) { case B_WARWICK_Y: return is_dead(B_WARWICK_Y) && is_dead(B_WARWICK_L) case B_KENT_Y: return is_dead(B_KENT_Y) && is_dead(B_KENT_L) case B_SALISBURY_Y: return is_dead(B_SALISBURY_Y) && is_dead(B_SALISBURY_L) case B_CLARENCE_Y: return is_dead(B_CLARENCE_Y) && is_dead(B_CLARENCE_L) case B_EXETER_L: return is_dead(B_EXETER_Y) && is_dead(B_EXETER_L) } return false } function is_in_battle(b) { if (view.battle) { if (view.battle.LR.includes(b)) return true if (view.battle.YR.includes(b)) return true if (view.battle.LF.includes(b)) return true if (view.battle.YF.includes(b)) return true } return false } function update_map() { let overflow = { Lancaster: [], York: [], Rebel: [] } let layout = {} document.getElementById("turn_info").textContent = "Campaign " + view.campaign + "\nKing: " + block_name(view.king) + "\nPretender: " + block_name(view.pretender) for (let area = 0; area < AREAS.length; ++area) layout[area] = { Lancaster: [], York: [] } let is_battle_open = document.getElementById("battle").getAttribute("open") !== null for (let b = 0; b < BLOCKS.length; ++b) { let info = BLOCKS[b] let element = ui.blocks[b] let area = view.location[b] let moved = view.moved.includes(b) ? " moved" : "" let image = " block_" + info.image let steps = " r" + (info.steps - view.steps[b]) let known = is_known_block(b) if (area !== NOWHERE || is_perma_dead(b)) { // perma-dead nobles if (area === NOWHERE || view.dead.includes(b)) { moved = " moved" known = 1 steps = "" } if (known) { element.classList = info.owner + " known block" + image + steps + moved } else { element.classList = info.owner + " block" + moved } if (block_owner(b) === LANCASTER) layout[area].Lancaster.push(element) else layout[area].York.push(element) show_block(element) } else { hide_block(element) } if (is_battle_open && is_in_battle(b)) element.classList.add("battle") else element.classList.remove("battle") } for (let area = 1; area < AREAS.length; ++area) { if (area === POOL) { layout_blocks(L_POOL, layout[POOL].Lancaster, layout[NOWHERE].Lancaster) layout_blocks(Y_POOL, layout[POOL].York, layout[NOWHERE].York) } else if (area === MINOR) { layout_blocks(L_MINOR, layout[area].Lancaster, []) layout_blocks(Y_MINOR, layout[area].York, []) } else { layout_blocks(area, layout[area].Lancaster, layout[area].York) } } for (let area = 0; area < AREAS.length; ++area) { if (ui.areas[area]) { ui.areas[area].classList.remove('highlight') ui.areas[area].classList.remove('where') ui.areas[area].classList.remove('battle') } } if (view.actions && view.actions.area) for (let area of view.actions.area) ui.areas[area].classList.add('highlight') if (view.where !== NOWHERE) ui.areas[view.where].classList.add('where') if (view.battle) ui.areas[view.where].classList.add('battle') for (let b = 0; b < BLOCKS.length; ++b) { 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 !== NOBODY) ui.blocks[view.who].classList.add('selected') } for (let e of ui.borders) { let u = map_get(view.last_used, e.my_id, 0) let n = map_get(view.border_limit, e.my_id, "") if (view.main_border && set_has(view.main_border, e.my_id)) n += "*" switch (u) { case 1: e.className = "border Lancaster" e.textContent = n break case 2: e.className = "border York" e.textContent = n break case 0: if (n) { e.className = e.my_show e.textContent = n } else { e.className = "hide" e.textContent = "" } break } } } 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 <= 7; ++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.l_card) document.getElementById("lancaster_card").className = "show card card_back" else 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[view.y_card].image } function compare_blocks(a, b) { let aa = BLOCKS[a].combat let bb = BLOCKS[b].combat // Bombard if (aa === "D3" && view.battle.round <= 1) aa = "A3" if (bb === "D3" && view.battle.round <= 1) bb = "A3" if (aa === bb) return (a < b) ? -1 : (a > b) ? 1 : 0 return (aa < bb) ? -1 : (aa > bb) ? 1 : 0 } function sort_battle_row(root) { let swapped let children = root.children do { swapped = false for (let i = 1; i < children.length; ++i) { if (compare_blocks(children[i-1].block, children[i].block) > 0) { children[i].after(children[i-1]) swapped = true } } } while (swapped) } function show_battle() { let box = document.getElementById("battle") let space = AREAS[view.where].layout let sh = ui.areas[view.where].getBoundingClientRect().height >> 1 // reset position box.classList.add("show") box.style.top = null box.style.left = null box.setAttribute("open", true) // calculate size let w = box.clientWidth let h = box.clientHeight // center where possible let x = space.x - w / 2 if (x < 60) x = 60 if (x > 1688 - w - 60) x = 1688 - w - 60 let y = space.y - sh - h - 60 if (y < 60) y = space.y + sh + 60 box.style.top = y + "px" box.style.left = x + "px" scroll_into_view_if_needed(box) } 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])) cell.appendChild(ui.battle_menu[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') ui.battle_menu[block].classList.remove('charge') ui.battle_menu[block].classList.remove('treachery') 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') if (view.actions && view.actions.battle_charge && view.actions.battle_charge.includes(block)) ui.battle_menu[block].classList.add('charge') if (view.actions && view.actions.battle_treachery && view.actions.battle_treachery.includes(block)) ui.battle_menu[block].classList.add('treachery') 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.includes(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 = 0; b < BLOCKS.length; ++b) { if (!ui.present.has(b)) { if (cell.contains(ui.battle_menu[b])) cell.removeChild(ui.battle_menu[b]) } } sort_battle_row(cell) } if (player === LANCASTER) { fill_cell("FR", view.battle.LR, true) fill_cell("FF", view.battle.LF, false) fill_cell("EF", view.battle.YF, false) fill_cell("ER", view.battle.YR, true) } else { fill_cell("ER", view.battle.LR, true) fill_cell("EF", view.battle.LF, false) fill_cell("FF", view.battle.YF, false) fill_cell("FR", view.battle.YR, true) } } function on_update() { 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) action_button("eliminate", "Eliminate") action_button("execute_clarence", "Execute Clarence") action_button("execute_exeter", "Execute Exeter") action_button("end_action_phase", "End action phase") action_button("end_supply_phase", "End supply phase") action_button("end_political_turn", "End political turn") action_button("end_exile_limits", "End exile limits") action_button("end_regroup", "End regroup") action_button("end_retreat", "End retreat") action_button("pass", "Pass") action_button("undo", "Undo") for (let c = 1; c <= 25; ++c) remember_position(ui.cards[c]) update_cards() if (view.battle) { document.getElementById("battle_header").textContent = view.battle.title document.getElementById("battle_message").textContent = view.battle.flash update_battle() if (!document.getElementById("battle").classList.contains("show")) show_battle() } else { document.getElementById("battle").classList.remove("show") } update_map() for (let c = 1; c <= 25; ++c) animate_position(ui.cards[c]) } document.getElementById("battle").addEventListener("toggle", on_update) build_map()