"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()