"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") ], role_stat: [ document.getElementById("stat1"), document.getElementById("stat2") ], 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() let prect = e.parentElement.getBoundingClientRect() e.my_parent = e.parentElement e.my_x = rect.x e.my_y = rect.y e.my_p_x = prect.x e.my_p_y = prect.y } else { e.my_parent = null e.my_x = 0 e.my_y = 0 } } function animate_position(e) { if (e.parentElement && e.my_parent) { let dx = 0, dy = 0 let rect = e.getBoundingClientRect() if (e.parentElement === e.my_parent) { let prect = e.parentElement.getBoundingClientRect() dx = (e.my_x - e.my_p_x) - (rect.x - prect.x) dy = (e.my_y - e.my_p_y) - (rect.y - prect.y) } else { dx = e.my_x - rect.x 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, null_action=null) { e.my_id = id e.my_action = action e.my_null_action = null_action 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_null_action) if (send_action(evt.target.my_null_action, 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, "star", '★') 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, "n" + 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 if (data.scenarios[view.scenario].players[1].tactical) ui.role_stat[p1].textContent = `${view.morale[0]} (-${view.lost[0]})` else ui.role_stat[p1].textContent = view.morale[0] if (data.scenarios[view.scenario].players[0].tactical) ui.role_stat[p2].textContent = `${view.morale[1]} (-${view.lost[1]})` else ui.role_stat[p2].textContent = view.morale[1] 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_null_action) { e.classList.toggle("action", is_action(e.my_action, e.my_id) || is_action(e.my_null_action, e.my_id)) e.classList.toggle("null", is_action(e.my_null_action, 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("next", "Next") 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 }