"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",
"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",
"Luise 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",
"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",
]
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,786,126,126],
"Social Movements": [490,786,126,126],
"Fort d'Issy": [844,793,126,126],
"Butte-Aux-Cailles": [1038,660,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],
"Red Crisis Track Start": [982,61,90,112],
"Red Crisis Track Escalation": [1072,61,84,112],
"Red Crisis Track Tension": [1156,61,83,112],
"Red Crisis Track Final Crisis": [1239,61,83,112],
"Blue Crisis Track Start": [432,61,88,112],
"Blue Crisis Track Escalation": [348,61,84,112],
"Blue Crisis Track Tension": [265,61,83,112],
"Blue Crisis Track Final Crisis": [182,61,83,112],
"Blue Objective Card": [120,996,272,54],
"Red Objective Card": [1114,996,272,54],
"Blue Cube Pool": [180,198,198,55],
"Red Bonus Cubes 1": [1072,22,84,39],
"Red Bonus Cubes 2": [1156,22,83,39],
"Red Bonus Cubes 3": [1239,22,83,39],
"Blue Bonus Cubes 1": [348,22,84,39],
"Blue Bonus Cubes 2": [265,22,83,39],
"Blue Bonus Cubes 3": [182,22,83,39],
"Red Cube Pool 1": [787,330,115,39],
"Red Cube Pool 2": [902,330,136,39],
"Red Cube Pool 3": [1038,330,157,40],
"Prussian Collaboration 1": [600,330,115,39],
"Prussian Collaboration 2": [463,330,136,39],
"Prussian Collaboration 3": [306,330,157,39],
}
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_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
}
function build_user_interface() {
let elt
document.getElementById("red_momentum").addEventListener("mousedown", on_click_red_momentum)
document.getElementById("blue_momentum").addEventListener("mousedown", on_click_blue_momentum)
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; ++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 i = 0; i < space_count; ++i) {
let name = space_names[i]
let [x, y, w, h] = boxes[name]
elt = ui.spaces[i] = create("div", {
className: "space",
my_space: i,
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`
})
space_layout_cube[i] = { x: x + Math.round(w/2), y: y + Math.round(h*1/2) }
space_layout_disc[i] = { x: x + w, y: y + h }
document.getElementById("spaces").appendChild(elt)
}
}
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) {
if (typeof objective === "object") {
for (let c of objective)
parent.appendChild(ui.cards[c])
} else if (objective === 1) {
parent.appendChild(ui.objective_back[0])
} else if (objective === 2) {
parent.appendChild(ui.objective_back[0])
} 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.querySelector("body").classList.toggle("censorship", view.censorship === 1)
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)
on_update_objective(document.getElementById("red_objective_panel"), document.getElementById("red_objective"), view.red_objective)
if (view.discard)
document.getElementById("discard").appendChild(ui.cards[view.discard])
if (view.hand)
for (let c of view.hand)
document.getElementById("hand").appendChild(ui.cards[c])
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")
}
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)
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()
scroll_with_middle_mouse("main")