"use strict" // TODO: show red out of play cubes on board let layout = [] let space_layout_cube = [] let space_layout_disc = [] let ui = { cards: [ null ], cubes: [], discs: [], spaces: [], red_momentum: document.getElementById("red_momentum"), blue_momentum: document.getElementById("blue_momentum"), political_vp: document.getElementById("political_vp"), military_vp: document.getElementById("military_vp"), round_marker: document.getElementById("round_marker"), } const card_names = [ "Initiative", // Strategy "Jules Ducatel", "The Murder of Vincenzini", "Brassardiers", "Jules Ferry", "Le Figaro", "Général Louis Valentin", "Général Espivent", "Les Amis de l'Ordre", "Socialist Newspaper Ban", "Fortification of Mont-Valérien", "Adolphe Thiers", "Otto von Bismarck", "Général Ernest de Cissey", "Colonel de Lochner", "Jules Favre", "Hostage Decree", "Maréchal Macmahon", "Paule Minck", "Walery Wroblewski", "Banque de France", "Le Réveil", "Execution of Generals", "Les Cantinières", "Eugène Protot", "Paul Cluseret", "Gaston Crémieux", "Louise Michel", "Jaroslav Dombrowski", "Raoul Rigault", "Karl Marx", "Blanquists", "Général Lullier", "Jules Vallès", "Charles Delescluze", "Conciliation", "Georges Clemenceau", "Archbishop Georges Darboy", "Victor Hugo", "Léon Gambetta", "Elihu Washburne", "Freemason Parade", // Objective "Paris Cannons", "Aux Barricades!", "Commune's Stronghold", "Fighting in Issy Village", "Battle of Mont-Valérien", "Raid on Château de Vincennes", "Revolution in the Press", "Pius IX", "Socialist International", "Royalists Dissension", "Rise of Republicanism", "Legitimacy", // Bonus Strategy "Louis Rossel", "Gustave Flourens", "Jean-Baptiste Clément", "Elizabeth Dimitrieff", "Paris Bombarded", "Général Gallifet", "Sapper Tactics", "Georges Vaysset", "Colonne Vendôme", "Versailles' Left", ] const space_names = [ "National Assembly", "Royalists", "Republicans", "Press", "Catholic Church", "Social Movements", "Mont-Valérien", "Fort d'Issy", "Château de Vincennes", "Butte Montmartre", "Butte-aux-Cailles", "Père Lachaise", "Prussian Occupied Territory", "Versailles HQ", "Red Cube Pool 1", "Red Cube Pool 2", "Red Cube Pool 3", "Red Crisis Track Start", "Red Crisis Track Escalation", "Red Crisis Track Tension", "Red Crisis Track Final Crisis", "Red Bonus Cubes 1", "Red Bonus Cubes 2", "Red Bonus Cubes 3", "Blue Cube Pool", "Blue Crisis Track Start", "Blue Crisis Track Escalation", "Blue Crisis Track Tension", "Blue Crisis Track Final Crisis", "Blue Bonus Cubes 1", "Blue Bonus Cubes 2", "Blue Bonus Cubes 3", "Prussian Collaboration 1", "Prussian Collaboration 2", "Prussian Collaboration 3", ] const space_count = space_names.length // :r !python3 tools/genboxes.py const boxes = { "Royalists": [80,428,126,126], "Republicans": [490,428,126,126], "Catholic Church": [80,787,126,126], "Social Movements": [490,787,126,126], "Fort d'Issy": [844,793,126,126], "Butte-aux-Cailles": [1038,661,126,126], "Père Lachaise": [1206,591,126,126], "Château de Vincennes": [1342,650,126,127], "Press": [294,727,112,112], "National Assembly": [292,496,112,112], "Butte Montmartre": [1052,460,112,111], "Mont-Valérien": [717,507,112,112], "Versailles HQ": [662,850,101,95], "Prussian Occupied Territory": [1298,353,180,86], "Blue Objective Card": [120,996,272,54], "Red Objective Card": [1114,996,272,54], "Blue Cube Pool": [180,198,198,55], "Red Crisis Track Start": [982,70,90,112], "Red Crisis Track Escalation": [1072,70,84,112], "Red Crisis Track Tension": [1156,70,84,112], "Red Crisis Track Final Crisis": [1239,70,84,112], "Blue Crisis Track Start": [432,70,88,112], "Blue Crisis Track Escalation": [348,70,84,112], "Blue Crisis Track Tension": [265,70,84,112], "Blue Crisis Track Final Crisis": [182,70,84,112], "Red Bonus Cubes 1": [1072,20,84,40], "Red Bonus Cubes 2": [1156,20,84,40], "Red Bonus Cubes 3": [1239,20,84,40], "Blue Bonus Cubes 1": [348,23,84,40], "Blue Bonus Cubes 2": [265,23,84,40], "Blue Bonus Cubes 3": [182,20,84,40], "Red Cube Pool 1": [787,340,115,40], "Red Cube Pool 2": [902,340,100,40], "Red Cube Pool 3": [1038,340,157,40], "Prussian Collaboration 1": [600,340,115,40], "Prussian Collaboration 2": [463,340,100,40], "Prussian Collaboration 3": [306,340,140,40], } function is_action(action) { if (view.actions && view.actions[action]) return true return false } function is_card_action(action, card) { if (view.actions && view.actions[action] && view.actions[action].includes(card)) return true return false } function is_piece_action(i) { if (view.actions && view.actions.piece && view.actions.piece.includes(i)) return true return false } function is_space_action(i) { if (view.actions && view.actions.space && view.actions.space.includes(i)) return true return false } function on_blur(evt) { document.getElementById("status").textContent = "" } function on_focus_space(evt) { document.getElementById("status").textContent = evt.target.my_name } function on_focus_piece(evt) { if (evt.target.my_name) document.getElementById("status").textContent = evt.target.my_name } function on_click_red_momentum(evt) { if (evt.button === 0) { if (send_action('red_momentum')) evt.stopPropagation() } } function on_click_blue_momentum(evt) { if (evt.button === 0) { if (send_action('blue_momentum')) evt.stopPropagation() } } function on_click_card(evt) { if (evt.button === 0) { if (send_action('card', evt.target.my_card)) evt.stopPropagation() } } function on_click_space(evt) { if (evt.button === 0) { if (send_action('space', evt.target.my_space)) evt.stopPropagation() } } function on_click_cube(evt) { if (evt.button === 0) { if (send_action('piece', evt.target.my_cube)) evt.stopPropagation() } } function on_click_disc(evt) { if (evt.button === 0) { if (send_action('piece', evt.target.my_disc)) evt.stopPropagation() } } function create(t, p, ...c) { let e = document.createElement(t) Object.assign(e, p) e.append(c) return e } const DIMENSION_CLASS = [ "institutional", "institutional", "institutional", "public_opinion", "public_opinion", "public_opinion", "forts", "forts", "forts", "paris", "paris", "paris", ] function build_user_interface() { let elt ui.red_momentum.my_name = "Revolutionary Momentum" ui.red_momentum.onmousedown = on_click_red_momentum ui.red_momentum.onmouseenter = on_focus_piece ui.red_momentum.onmouseleave = on_blur ui.blue_momentum.my_name = "Prussian Collaboration" ui.blue_momentum.onmousedown = on_click_blue_momentum ui.blue_momentum.onmouseenter = on_focus_piece ui.blue_momentum.onmouseleave = on_blur ui.political_vp.my_name = "Political VP" ui.political_vp.onmouseenter = on_focus_piece ui.political_vp.onmouseleave = on_blur ui.military_vp.my_name = "Military VP" ui.military_vp.onmouseenter = on_focus_piece ui.military_vp.onmouseleave = on_blur ui.objective_back = [ create("div", { className: "card card_objective_back" }), create("div", { className: "card card_objective_back" }), create("div", { className: "card card_objective_back" }), create("div", { className: "card card_objective_back" }), ] for (let c = 1; c <= 41 + 12 + 10; ++c) { elt = ui.cards[c] = create("div", { className: `card card_${c}`, my_card: c, onmousedown: on_click_card }) } for (let i = 0; i < 36; ++i) { elt = ui.cubes[i] = create("div", { className: (i < 18) ? "piece cube red" : "piece cube blue", my_cube: i, onmousedown: on_click_cube, }) document.getElementById("pieces").appendChild(elt) } for (let i = 0; i < 4; ++i) { elt = ui.discs[i] = create("div", { className: (i<2) ? "piece disc red" : "piece disc blue", my_disc: i + 36, onmousedown: on_click_disc }) document.getElementById("pieces").appendChild(elt) } for (let s = 0; s < space_count; ++s) { let name = space_names[s] let [x, y, w, h] = boxes[name] let cn = "space" if (s === 0 || s === 3 || s === 6 || s === 9) { cn += " pivotal" x -= 22 y -= 23 w += 46 h += 46 } else { x += 5 y += 5 w -= 10 h -= 10 } if (s < 12) { cn += " " + DIMENSION_CLASS[s] elt = ui.spaces[s] = create("div", { className: cn, my_space: s, my_name: name, onmousedown: on_click_space, onmouseenter: on_focus_space, onmouseleave: on_blur, style: `top: ${y-1}px;left:${x-1}px;width:${w+2}px;height:${h+2}px` }) document.getElementById("spaces").appendChild(elt) } space_layout_cube[s] = { x: x + Math.round(w/2), y: y + Math.round(h*1/2) } space_layout_disc[s] = { x: x + w, y: y + h } } } function layout_cubes(list, xorig, yorig) { const dx = 20 const dy = 11 if (list.length > 0) { let ncol = Math.round(Math.sqrt(list.length)) let nrow = Math.ceil(list.length / ncol) function place_cube(row, col, e, z) { let x = xorig - (row * dx - col * dx) - 18 + (nrow-ncol) * 6 let y = yorig - (row * dy + col * dy) - 28 + (nrow-1) * 8 e.style.left = x + "px" e.style.top = y + "px" e.style.zIndex = z } let z = 50 let i = 0 for (let row = 0; row < nrow; ++row) for (let col = 0; col < ncol && i < list.length; ++col) place_cube(row, col, list[list.length-(++i)], z--) } } function layout_disc(s, disc) { if (s > 0) { disc.classList.remove("hide") disc.style.left = (space_layout_disc[s].x - 50 - 12) + "px" disc.style.top = (space_layout_disc[s].y - 20 - 8) + "px" disc.style.zIndex = 51 } else disc.classList.add("hide") } function on_focus_card_tip(card_number) { document.getElementById("tooltip").className = "card card_" + card_number } function on_blur_card_tip() { document.getElementById("tooltip").classList = "card hide" } function sub_card_name(match, p1, offset, string) { let c = p1 | 0 let n = card_names[c] return `${n}` } function sub_space_name(match, p1, offset, string) { let c = p1 | 0 let n = space_names[c] if (true) { if (c <= 2) return '' + n + "" if (c <= 5) return '' + n + "" if (c <= 8) return '' + n + "" if (c <= 11) return '' + n + "" } if (true) { if (c <= 2) return '' + n if (c <= 5) return '' + n if (c <= 8) return '' + n if (c <= 11) return '' + n } if (true) { if (c <= 2) return '' + n + "" if (c <= 5) return '' + n + "" if (c <= 8) return '' + n + "" if (c <= 11) return '' + n + "" } return n } const IMG_RC = '' const IMG_BC = '' const IMG_RD = '' const IMG_BD = '' const IMG_RM = '' const IMG_BM = '' const IMG_MV = '' const IMG_PV = '' 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(/C(\d+)/g, sub_card_name) text = text.replace(/S(\d+)/g, sub_space_name) text = text.replace(/\bRC\b/g, IMG_RC) text = text.replace(/\bBC\b/g, IMG_BC) text = text.replace(/\bRD\b/g, IMG_RD) text = text.replace(/\bBD\b/g, IMG_BD) text = text.replace(/\bRM\b/g, IMG_RM) text = text.replace(/\bBM\b/g, IMG_BM) // text = text.replace(/\bMilitary VP\b/g, IMG_MV) // text = text.replace(/\bPolitical VP\b/g, IMG_PV) if (text.match(/^\.h1/)) { text = text.substring(4) p.className = 'h1' } if (text.match(/^\.h2/)) { text = text.substring(4) p.className = 'h2' } if (text.match(/^\.h3/)) { text = text.substring(4) p.className = 'h3' } p.innerHTML = text return p } function on_update_objective(panel, parent, objective, i) { if (typeof objective === "object") { for (let c of objective) parent.appendChild(ui.cards[c]) } else if (objective === 1) { parent.appendChild(ui.objective_back[i]) } else if (objective === 2) { parent.appendChild(ui.objective_back[i]) } else if (objective > 2) { parent.appendChild(ui.cards[objective]) } } function on_update() { if (view.initiative === "Commune") document.getElementById("commune_info").textContent = "\u2756" else document.getElementById("commune_info").textContent = "" if (view.initiative === "Versailles") document.getElementById("versailles_info").textContent = "\u2756" else document.getElementById("versailles_info").textContent = "" ui.round_marker.className = `piece pawn round${view.round}` if (is_action("red_momentum")) ui.red_momentum.className = `piece cylinder red m${view.red_momentum} action` else ui.red_momentum.className = `piece cylinder red m${view.red_momentum}` if (is_action("blue_momentum")) ui.blue_momentum.className = `piece cylinder blue m${view.blue_momentum} action` else ui.blue_momentum.className = `piece cylinder blue m${view.blue_momentum}` ui.military_vp.className = `piece cylinder purple vp${5+view.military_vp}` ui.political_vp.className = `piece cylinder orange vp${5+view.political_vp}` document.getElementById("hand").replaceChildren() document.getElementById("final").replaceChildren() document.getElementById("discard").replaceChildren() document.getElementById("set_aside").replaceChildren() document.getElementById("red_objective").replaceChildren() document.getElementById("blue_objective").replaceChildren() if (view.blue_final) document.getElementById("final").appendChild(ui.cards[view.blue_final]) if (view.red_final) document.getElementById("final").appendChild(ui.cards[view.red_final]) on_update_objective(document.getElementById("blue_objective_panel"), document.getElementById("blue_objective"), view.blue_objective, 0) on_update_objective(document.getElementById("red_objective_panel"), document.getElementById("red_objective"), view.red_objective, 1) if (view.discard) document.getElementById("discard").appendChild(ui.cards[view.discard]) if (view.hand) { document.getElementById("hand_panel").classList.remove("hide") for (let c of view.hand) document.getElementById("hand").appendChild(ui.cards[c]) } else { document.getElementById("hand_panel").classList.add("hide") } if (view.set_aside) { document.getElementById("set_aside_panel").classList.remove("hide") for (let c of view.set_aside) document.getElementById("set_aside").appendChild(ui.cards[c]) } else { document.getElementById("set_aside_panel").classList.add("hide") } if (view.hand && view.hand.length === 5) document.getElementById("discard_panel").classList.add("hide") else document.getElementById("discard_panel").classList.remove("hide") for (let i = 0; i < space_names.length; ++i) layout[i] = [] for (let i = 0; i < 36; ++i) { if (view.pieces[i] >= 0) { layout[view.pieces[i]].push(ui.cubes[i]) ui.cubes[i].classList.remove("hide") } else if (i >= 18) { ui.cubes[i].classList.add("hide") } ui.cubes[i].classList.toggle("action", is_piece_action(i)) ui.cubes[i].classList.toggle("selected", i === view.selected_cube) } let red_out_of_play = [] for (let i = 0; i <= 17; ++i) if (view.pieces[i] < 0) red_out_of_play.push(ui.cubes[i]) layout_cubes(red_out_of_play, 1225, 225) for (let i = 0; i < space_count; ++i) { layout_cubes(layout[i], space_layout_cube[i].x, space_layout_cube[i].y) if (i < 12) { ui.spaces[i].classList.toggle("action", is_space_action(i)) ui.spaces[i].classList.toggle("selected", i === view.where) } } for (let i = 0; i < 4; ++i) { layout_disc(view.pieces[36+i], ui.discs[i]) ui.discs[i].classList.toggle("action", is_piece_action(36+i)) } for (let i = 1; i < ui.cards.length; ++i) { ui.cards[i].classList.toggle("action", is_card_action('card', i)) ui.cards[i].classList.toggle("selected", i === view.selected_card) } action_button("commune", "Commune") action_button("versailles", "Versailles") action_button("spend", "Spend") action_button("draw", "Draw") action_button("momentum", "Momentum") action_button("event", "Event") action_button("political", "Political") action_button("military", "Military") action_button("institutional", "Institutional") action_button("public_opinion", "Public Opinion") action_button("paris", "Paris") action_button("forts", "Forts") action_button("de_escalate", "De-escalate") action_button("spread_influence", "Spread Influence") action_button("turncoat", "Turncoat") action_button("ops", "Operations") action_button("place", "Place") action_button("replace", "Replace") action_button("remove", "Remove") action_button("end_ops", "End Operations") action_button("end_event", "End Event") action_button("skip", "Skip") action_button("pass", "Pass") action_button("done", "Done") action_button("undo", "Undo") } build_user_interface()