"use strict"
/* global data, view, send_action, action_button, player */
const wing_name = [ "red", "pink", "blue", "dkblue" ]
const reactions = [ "Screen", "Counterattack", "Absorb" ]
let ui = {
main: document.querySelector("main"),
role_name: [ document.getElementById("role1"), document.getElementById("role2") ],
name: [ document.getElementById("name1"), document.getElementById("name2") ],
front: [ document.getElementById("front1"), document.getElementById("front2") ],
morale: [ document.getElementById("morale1"), document.getElementById("morale2") ],
pool: [ document.getElementById("pool1"), document.getElementById("pool2") ],
reserve: [ document.getElementById("reserve1"), document.getElementById("reserve2") ],
cards: {},
slots: {},
slot_sticks: {},
slot_cubes: {},
slot_dice: {},
dice: [],
sticks: [],
hit_sticks: [],
cubes: [],
}
let animation_registry = []
function register_animation(e, duration) {
animation_registry.push(e)
e.my_duration = duration
}
function remember_position(e) {
if (e.parentElement) {
let rect = e.getBoundingClientRect()
e.my_parent = e.parentElement
e.my_x = rect.x
e.my_y = rect.y
} else {
e.my_parent = null
e.my_x = 0
e.my_y = 0
}
}
function animate_position(e) {
// TODO: fix double-animated dice when cards are removed
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: e.my_duration, easing: "ease" }
)
}
}
}
}
let action_registry = []
function register_action(e, action, id, fizzle=null) {
e.my_id = id
e.my_action = action
e.my_fizzle = fizzle
e.onclick = on_click_action
action_registry.push(e)
return e
}
function on_click_action(evt) {
if (evt.button === 0) {
if (send_action(evt.target.my_action, evt.target.my_id))
evt.stopPropagation()
if (evt.target.my_fizzle)
if (send_action(evt.target.my_fizzle, evt.target.my_id))
evt.stopPropagation()
}
}
function is_action(action, arg) {
if (arg === undefined)
return !!(view.actions && view.actions[action] === 1)
return !!(view.actions && view.actions[action] && view.actions[action].includes(arg))
}
function create_div(className, text) {
let e = document.createElement("div")
e.className = className
if (text)
e.innerHTML = text
return e
}
function append_div(parent, className, text) {
let e = create_div(className, text)
parent.appendChild(e)
return e
}
function create_formation_slot(id, top) {
let card = data.cards[id]
let e = create_div("slot " + wing_name[card.wing])
e.dataset.card = card.number
register_animation(e, 250)
if (top) {
ui.slot_dice[id] = append_div(e, "slot_dice")
e.appendChild(ui.cards[id])
}
if (card.special || card.name === "The Fog" || card.name === "Retreat to Nivelles")
ui.slot_cubes[id] = append_div(e, "slot_cubes")
if (!card.special)
ui.slot_sticks[id] = append_div(e, "slot_sticks")
if (!top) {
e.appendChild(ui.cards[id])
ui.slot_dice[id] = append_div(e, "slot_dice")
}
return e
}
function create_formation_card(id) {
let card = data.cards[id]
let e = create_div("card formation " + wing_name[card.wing])
register_action(e, "card", id)
if (card.infantry || card.cavalry)
append_div(e, "name with_symbol " + wing_name[card.wing], card.name)
else
append_div(e, "name " + wing_name[card.wing], card.name)
if (card.infantry)
append_div(e, "symbol infantry")
if (card.cavalry)
append_div(e, "symbol cavalry")
if (card.special === 1)
append_div(e, "strength", "I")
else if (card.special === 2)
append_div(e, "strength", "II")
else if (card.special === 3)
append_div(e, "strength", "III")
else if (card.special === 4)
append_div(e, "strength", "IV")
else if (card.special === 5)
append_div(e, "strength", "V")
else
append_div(e, "strength", card.strength)
if (card.link) {
if (set_has(card.link, id - 1))
append_div(e, "link left")
if (set_has(card.link, id + 1))
append_div(e, "link right")
}
if (card.dice) {
if (card.morale === 2)
append_div(e, "dice_area", card.dice + '
★
')
else
append_div(e, "dice_area", card.dice)
}
function create_action(a, ix) {
let ee = append_div(e, "action_row")
let et
if (reactions.includes(a.type))
et = append_div(ee, "action_type reaction", a.type)
else
et = append_div(ee, "action_type", a.type)
register_action(et, "a" + ix, id, "f" + ix)
append_div(ee, "action_requirement", a.requirement)
if (Array.isArray(a.target))
append_div(ee, "action_target", a.target.map(t=>data.cards[t].name).join(", "))
else
append_div(ee, "action_target", a.target)
if (a.effect)
append_div(ee, "action_effect", a.effect)
return et
}
if (card.actions.length >= 1)
create_action(card.actions[0], 1)
if (card.actions.length >= 2)
create_action(card.actions[1], 2)
if (card.rule_text)
append_div(e, "rule_text", card.rule_text)
if (card.lore_text)
append_div(e, "lore_text", card.lore_text)
let reserve = null
if (card.reserve) {
if (Array.isArray(card.reserve) && card.reserve.length > 0) {
reserve = card.reserve.map(c => data.cards[c].alias || data.cards[c].name).join(" or ")
} else {
reserve = card.reserve
}
}
if (card.retire) {
let ee
if (card.pursuit)
ee = append_div(e, "retire", "RETIRE, PURSUIT")
else if (card.reserve)
ee = append_div(e, "retire", `RETIRE, RESERVE (${reserve})`)
else
ee = append_div(e, "retire", "RETIRE")
register_action(ee, "retire", id)
} else if (card.pursuit) {
append_div(e, "reserve", "PURSUIT")
} else if (card.reserve) {
append_div(e, "reserve", `IN RESERVE (${reserve})`)
}
append_div(e, "number", card.number)
return e
}
function fill_card_row(top, parent, list) {
parent.replaceChildren()
for (let id of list) {
let n, x
if (!ui.cards[id])
ui.cards[id] = create_formation_card(id)
if (!ui.slots[id])
ui.slots[id] = create_formation_slot(id, top)
parent.append(ui.slots[id])
ui.cards[id].classList.toggle("selected", view.selected === id)
ui.cards[id].classList.toggle("target", view.target === id)
n = map_get(view.cubes, id, 0)
for (let i = 0; i < n; ++i)
add_cube(ui.slot_cubes[id])
n = map_get(view.sticks, id, 0)
x = 0
if (view.selected === id)
x = view.self
if (view.target === id)
x = view.hits
if (view.target2 === id)
x = view.hits2
for (let i = 0; i < x && i < n; ++i)
add_hit_stick(ui.slot_sticks[id])
for (let i = x; i < n; ++i)
add_stick(ui.slot_sticks[id])
}
}
function add_cube(parent) {
for (let i = 0; i < 10; ++i) {
if (ui.cubes[i].parentElement === null) {
parent.appendChild(ui.cubes[i])
return
}
}
throw Error("OUT OF CUBES ERROR")
}
function add_stick(parent) {
for (let i = 0; i < 80; ++i) {
if (ui.sticks[i].parentElement === null) {
parent.appendChild(ui.sticks[i])
return
}
}
throw Error("OUT OF CUBES ERROR")
}
function add_hit_stick(parent) {
for (let i = 0; i < 80; ++i) {
if (ui.hit_sticks[i].parentElement === null) {
parent.appendChild(ui.hit_sticks[i])
return
}
}
throw Error("OUT OF CUBES ERROR")
}
function on_update() {
let p1 = 0, p2 = 1
if (player === "First")
p1 = 1, p2 = 0
ui.main.dataset.scenario = data.scenarios[view.scenario].number
ui.name[p1].textContent = data.scenarios[view.scenario].players[0].name
ui.name[p2].textContent = data.scenarios[view.scenario].players[1].name
ui.role_name[p1].textContent = data.scenarios[view.scenario].players[0].name
ui.role_name[p2].textContent = data.scenarios[view.scenario].players[1].name
for (let e of animation_registry)
remember_position(e)
for (let e of ui.cubes)
e.remove()
for (let e of ui.sticks)
e.remove()
for (let e of ui.hit_sticks)
e.remove()
for (let i = 0; i < view.morale[0]; ++i)
add_cube(ui.morale[p1])
for (let i = 0; i < view.morale[1]; ++i)
add_cube(ui.morale[p2])
fill_card_row(p2, ui.reserve[p1], view.reserve[0])
fill_card_row(p2, ui.front[p1], view.front[0])
fill_card_row(p1, ui.reserve[p2], view.reserve[1])
fill_card_row(p1, ui.front[p2], view.front[1])
function update_die(d, p) {
let v = view.dice[d * 2 + 0]
let c = view.dice[d * 2 + 1]
ui.dice[d].className = "die d" + v
if (c < 0)
ui.pool[p].appendChild(ui.dice[d])
else
ui.slot_dice[c].appendChild(ui.dice[d])
}
for (let i = 0; i < 6; ++i) {
update_die(i, p1)
update_die(i+6, p2)
}
for (let e of action_registry) {
if (e.my_fizzle) {
e.classList.toggle("action", is_action(e.my_action, e.my_id) || is_action(e.my_fizzle, e.my_id))
e.classList.toggle("fizzle", is_action(e.my_fizzle, e.my_id))
} else {
e.classList.toggle("action", is_action(e.my_action, e.my_id))
}
}
for (let e of animation_registry)
animate_position(e)
action_button("attack", "Attack")
action_button("bombard", "Bombard")
action_button("command", "Command")
action_button("screen", "Screen")
action_button("counterattack", "Counterattack")
action_button("absorb", "Absorb")
action_button("roll", "Roll")
action_button("pass", "Pass")
action_button("end_turn", "End turn")
action_button("undo", "Undo")
}
for (let i = 0; i < 10; ++i) {
ui.cubes[i] = create_div("cube")
// register_animation(ui.cubes[i], 500)
}
for (let i = 0; i < 80; ++i) {
ui.sticks[i] = create_div("stick")
ui.hit_sticks[i] = create_div("stick hit")
}
for (let i = 0; i < 12; ++i) {
ui.dice[i] = register_action(create_div("die d0"), "die", i)
register_animation(ui.dice[i], 250)
}
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
}