"use strict"
const ELIMINATED = 0
const SWAPPED = 200
const REINFORCEMENTS = 100
const AVAILABLE_P1 = 101
const AVAILABLE_P2 = 102
const BLOWN = 103
const last_corps = 22
const piece_count = 39
const first_hex = 1000
const last_hex = 4041
const ADJACENT = [
[-101,-100,-1,1,99,100],
[-100,-99,-1,1,100,101]
]
const DIRECTION = [ "r3", "r2", "r4", "r1", "r5", "r0" ]
function find_piece(name) {
let id = data.pieces.findIndex(pc => pc.name === name)
if (id < 0)
throw new Error("PIECE NOT FOUND: " + name)
return id
}
for (let info of data.reinforcements)
info.list = info.list.map(name => find_piece(name))
let yoff = 1555
let xoff = 36
let hex_dx = 58.67
let hex_dy = 68
let hex_r = 56 >> 1
function set_has(set, item) {
if (!set)
return false
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 FRENCH = "French"
const COALITION = "Coalition"
const TURN_X = 20 - 70 + 35 + 8 + 20
const TURN_Y = 1745 + 20
const TURN_DX = 70
const REINF_OFFSET = {
1015: [ hex_dx/2, hex_dy * 3/4 ],
1017: [ hex_dx/2, hex_dy * 3/4 ],
1018: [ -hex_dx/2, hex_dy * 3/4 ],
1020: [ -hex_dx/2, hex_dy * 3/4 ],
3000: [ -hex_dx/2, 0 ],
3241: [ hex_dx/2, 0 ],
4015: [ 0, -hex_dy * 3/8 ],
}
let ui = {
header: document.querySelector("header"),
arrow: document.getElementById("arrow"),
hexes: new Array(last_hex+1).fill(null),
sides: new Array((last_hex+1)*3).fill(null),
hex_x: new Array(last_hex+1).fill(0),
hex_y: new Array(last_hex+1).fill(0),
pieces: [
document.getElementById("french_hq_1"),
document.getElementById("french_hq_2"),
document.getElementById("french_hq_3"),
document.getElementById("anglo_hq_1"),
document.getElementById("prussian_hq_1"),
document.getElementById("french_corps_1"),
document.getElementById("french_corps_2"),
document.getElementById("french_corps_3"),
document.getElementById("french_corps_4"),
document.getElementById("french_corps_5"),
document.getElementById("french_corps_6"),
document.getElementById("french_corps_7"),
document.getElementById("french_corps_8"),
document.getElementById("anglo_corps_1"),
document.getElementById("anglo_corps_2"),
document.getElementById("anglo_corps_3"),
document.getElementById("anglo_corps_4"),
document.getElementById("anglo_corps_5"),
document.getElementById("prussian_corps_1"),
document.getElementById("prussian_corps_2"),
document.getElementById("prussian_corps_3"),
document.getElementById("prussian_corps_4"),
document.getElementById("prussian_corps_5"),
document.getElementById("french_detachment_1"),
document.getElementById("french_detachment_2"),
document.getElementById("french_detachment_3"),
document.getElementById("french_detachment_4"),
document.getElementById("french_detachment_5"),
document.getElementById("french_detachment_6"),
document.getElementById("anglo_detachment_1"),
document.getElementById("anglo_detachment_2"),
document.getElementById("anglo_detachment_3"),
document.getElementById("anglo_detachment_4"),
document.getElementById("prussian_detachment_1"),
document.getElementById("prussian_detachment_2"),
document.getElementById("prussian_detachment_3"),
document.getElementById("prussian_detachment_4"),
document.getElementById("prussian_detachment_5"),
document.getElementById("prussian_detachment_6"),
],
stack: new Array(last_hex+1).fill(0),
turn: document.getElementById("marker_turn"),
remain: document.getElementById("marker_remain"),
french_moves: document.getElementById("marker_french_moves"),
prussian_moves: document.getElementById("marker_prussian_moves"),
}
function toggle_pieces() {
document.getElementById("pieces").classList.toggle("hide")
}
function on_blur(evt) {
document.getElementById("status").textContent = ""
}
function on_blur_hex(evt) {
on_blur()
hide_move_path()
}
function on_focus_hex(evt) {
document.getElementById("status").textContent = "Hex " + evt.target.my_name
if (view && view.move_from)
show_move_path(evt.target.my_id)
}
var focused_piece = null
function on_focus_piece(evt) {
let p = evt.target.my_id
document.getElementById("status").textContent = evt.target.my_name
if (data.pieces[p].type === "hq") {
focused_piece = p
show_hq_range(p)
}
}
function on_blur_piece(evt) {
let p = evt.target.my_id
on_blur()
if (data.pieces[p].type === "hq") {
focused_piece = -1
hide_hq_range(p)
}
}
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_action_2)
if (send_action(evt.target.my_action_2, evt.target.my_id))
evt.stopPropagation()
}
}
var _move_path = []
function hide_move_path() {
if (_move_path) {
for (let x of _move_path) {
ui.hexes[x].classList.remove("move")
ui.hexes[x].classList.remove("road")
}
_move_path = null
}
}
function show_move_path(x) {
if (_move_path)
hide_move_path()
if (!is_action("hex", x) && !is_action("stop_hex", x))
return
if (view.move_from && map_get(view.move_from, x, 0)) {
_move_path = []
for (let i = 0; x && i < 100; ++i) {
_move_path.push(x)
x = map_get(view.move_from, x, 0)
}
for (let x of _move_path)
ui.hexes[x].classList.add("move")
}
else
if (view.move_from_road && map_get(view.move_from_road, x, 0)) {
_move_path = []
for (let i = 0; x && i < 100; ++i) {
_move_path.push(x)
x = map_get(view.move_from_road, x, 0)
}
for (let x of _move_path)
ui.hexes[x].classList.add("road")
}
}
function build_hexes() {
for (let row = 0; row < data.map.rows; ++row) {
for (let col = 0; col < data.map.cols; ++col) {
let hex_id = first_hex + 100 * row + col
let hex_x = ui.hex_x[hex_id] = Math.floor(xoff + hex_dx * (col + (row & 1) * 0.5 + 0.5))
let hex_y = ui.hex_y[hex_id] = Math.floor(yoff - hex_dy * 3 / 4 * row + hex_dy/2)
let hex = ui.hexes[hex_id] = document.createElement("div")
hex.className = "hex"
hex.style.left = (hex_x - hex_r) + "px"
hex.style.top = (hex_y - hex_r) + "px"
hex.style.width = (hex_r * 2) + "px"
hex.style.height = (hex_r * 2) + "px"
hex.onmousedown = on_click_action
hex.onmouseenter = on_focus_hex
hex.onmouseleave = on_blur_hex
hex.my_action = "hex"
hex.my_action_2 = "stop_hex"
hex.my_id = hex_id
if (data.map.names[hex_id])
hex.my_name = String(hex_id) + " (" + data.map.names[hex_id] + ")"
else
hex.my_name = String(hex_id)
document.getElementById("hexes").appendChild(hex)
}
}
for (let p = 0; p < ui.pieces.length; ++p) {
ui.pieces[p].onmousedown = on_click_action
ui.pieces[p].onmouseenter = on_focus_piece
ui.pieces[p].onmouseleave = on_blur_piece
ui.pieces[p].my_action = "piece"
ui.pieces[p].my_id = p
ui.pieces[p].my_name = data.pieces[p].name
}
}
function is_action(action, arg) {
if (arg === undefined)
return !!(view.actions && view.actions[action] === 1)
return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg))
}
function is_piece_support(id) {
if (view.support)
return view.support & (1 << id)
return false
}
function find_hex_side(a, b) {
if (a > b)
return find_hex_side(b, a)
if (b === a + 1)
return (a << 2) + 0
if ((a/100) & 1) {
if (b === a + 101)
return (a << 2) + 1
if (b === a + 100)
return (a << 2) + 2
} else {
if (b === a + 100)
return (a << 2) + 1
if (b === a + 99)
return (a << 2) + 2
}
return -1
}
function find_reinforcement_hex(who) {
for (let info of data.reinforcements)
for (let p of info.list)
if (p === who)
return info.hex
return REINFORCEMENTS
}
function find_reinforcement_z(who) {
for (let info of data.reinforcements) {
let n = 0
for (let p of info.list) {
if (p === who)
return n
if ((view.pieces[p] >> 1) === REINFORCEMENTS)
++n
}
}
return 0
}
function calc_distance(a, b) {
let ac = a % 100
let bc = b % 100
let ay = a / 100 | 0
let by = b / 100 | 0
let ax = ac - (ay >> 1)
let bx = bc - (by >> 1)
let az = -ax - ay
let bz = -bx - by
return Math.max(Math.abs(bx-ax), Math.abs(by-ay), Math.abs(bz-az))
}
function is_in_range(x, hq) {
let hq_x = view.pieces[hq] >> 1
if (hq_x >= 1000) {
let hq_m = view.pieces[hq] & 1
let hq_r = hq_m ? data.pieces[hq].range2 : data.pieces[hq].range1
return calc_distance(x, hq_x) === hq_r
}
return false
}
function show_hq_range(hq) {
for (let row = 0; row < data.map.rows; ++row) {
for (let col = 0; col < data.map.cols; ++col) {
let id = first_hex + row * 100 + col
ui.hexes[id].classList.toggle("range", is_in_range(id, hq))
}
}
}
function hide_hq_range() {
for (let row = 0; row < data.map.rows; ++row) {
for (let col = 0; col < data.map.cols; ++col) {
let id = first_hex + row * 100 + col
ui.hexes[id].classList.remove("range")
}
}
}
function on_update() {
ui.stack.fill(0)
if (!view.move_path)
hide_move_path()
for (let row = 0; row < data.map.rows; ++row) {
for (let col = 0; col < data.map.cols; ++col) {
let id = first_hex + row * 100 + col
ui.hexes[id].classList.toggle("action", is_action("hex", id) || is_action("stop_hex", id))
ui.hexes[id].classList.toggle("stop", is_action("stop_hex", id))
}
}
if (focused_piece <= 4)
show_hq_range(focused_piece)
if (view.who >= 0 && view.target >= 0) {
let wx = view.pieces[view.who] >> 1
let tx = view.pieces[view.target] >> 1
if (wx >= 1000 && tx >= 1000 && calc_distance(wx, tx) === 1) {
ui.arrow.style.left = (ui.hex_x[wx] - 25) + "px"
ui.arrow.style.top = (ui.hex_y[wx] - 50) + "px"
for (let i = 0; i < 6; ++i) {
let dx = ADJACENT[wx / 100 & 1][i]
if (tx - wx === dx)
ui.arrow.className = DIRECTION[i]
}
} else {
ui.arrow.className = "hide"
}
} else {
ui.arrow.className = "hide"
}
for (let id = 0; id < piece_count; ++id) {
let hex = view.pieces[id] >> 1
let z = 0
let s = 0
if (hex > BLOWN && hex < BLOWN + 20)
hex -= BLOWN
if (hex >= first_hex || hex === REINFORCEMENTS) {
// ON MAP
ui.pieces[id].classList.remove("hide")
ui.pieces[id].classList.toggle("flip", (view.pieces[id] & 1) === 1)
let x, y
if (hex === REINFORCEMENTS) {
hex = find_reinforcement_hex(id)
if (typeof hex !== "number")
hex = hex[0]
s = find_reinforcement_z(id)
z = 4 - s
x = ui.hex_x[hex] + s * 24
y = ui.hex_y[hex] + s * 18
if (REINF_OFFSET[hex]) {
x += REINF_OFFSET[hex][0]
y += REINF_OFFSET[hex][1]
}
} else {
s = z = ui.stack[hex]++
x = ui.hex_x[hex] - s * 18
y = ui.hex_y[hex] + s * 12
}
if (id <= last_corps) {
x -= (46>>1)
y -= (46>>1)
} else {
x -= (38>>1)
y -= (38>>1)
}
ui.pieces[id].style.top = y + "px"
ui.pieces[id].style.left = x + "px"
ui.pieces[id].style.zIndex = z
} else if (hex >= AVAILABLE_P1 && hex <= BLOWN) {
// OFF MAP DETACHMENTS / LEADERS / REINFORCEMENTS
ui.pieces[id].classList.remove("hide")
ui.pieces[id].classList.toggle("flip", (view.pieces[id] & 1) === 1)
let x = 600 + 40 + ui.stack[hex] * 60 + 40
let y = 1650 + 40 + 60 * (hex-AVAILABLE_P1) + 20
ui.stack[hex] += 1
ui.pieces[id].style.top = y + "px"
ui.pieces[id].style.left = x + "px"
ui.pieces[id].style.zIndex = 0
} else if (hex >= 1 && hex <= 20) {
// ON TURN TRACK
ui.pieces[id].classList.remove("hide")
ui.pieces[id].classList.remove("flip")
let x = TURN_X + hex * TURN_DX - ui.stack[hex] * 18
let y = TURN_Y + ui.stack[hex] * 12
ui.stack[hex] += 1
if (id <= last_corps) {
x -= (46>>1)
y -= (46>>1)
} else {
x -= (38>>1)
y -= (38>>1)
}
ui.pieces[id].style.top = y + "px"
ui.pieces[id].style.left = x + "px"
} else {
// ELIMINATED or SWAPPED
ui.pieces[id].classList.add("hide")
}
//if (is_action("piece", id)) z = 101
if (view.target === id) z = 102
if (view.who === id) z = 103
ui.pieces[id].style.zIndex = z
ui.pieces[id].classList.toggle("action", is_action("piece", id))
ui.pieces[id].classList.toggle("selected", view.who === id)
ui.pieces[id].classList.toggle("target", view.target === id)
ui.pieces[id].classList.toggle("support", is_piece_support(id))
}
if (view.roads) {
for (let road of view.roads) {
for (let i = 1; i < road.length; ++i) {
let id = find_hex_side(road[i-1], road[i])
console.log("id", id)
ui.sides[id].classList.add("road")
}
}
}
ui.turn.style.left = (40 + TURN_X + (view.turn-1) * TURN_DX) + "px"
ui.turn.classList.toggle("flip", view.rain === 2)
if (view.remain > 0) {
ui.remain.style.left = (20 + 109 + (view.remain % 10) * 47.5 | 0) + "px"
ui.remain.classList.toggle("flip", view.remain > 9)
ui.remain.classList.remove("hide")
} else {
ui.remain.classList.add("hide")
}
if (view.french_moves !== undefined) {
let x = (20 + 109 + (view.french_moves % 10) * 47.5 | 0)
ui.french_moves.style.left = x + "px"
ui.french_moves.classList.toggle("flip", view.french_moves > 9)
ui.french_moves.classList.remove("hide")
} else {
ui.french_moves.classList.add("hide")
}
if (view.prussian_moves !== undefined) {
let x = (20 + 109 + (view.prussian_moves % 10) * 47.5 | 0)
let y = 1857
if (view.prussian_moves === view.french_moves) {
x += 12
y -= 12
}
ui.prussian_moves.style.left = x + "px"
ui.prussian_moves.style.top = y + "px"
ui.prussian_moves.classList.toggle("flip", view.prussian_moves > 9)
ui.prussian_moves.classList.remove("hide")
} else {
ui.prussian_moves.classList.add("hide")
}
action_button("blow", "Blow")
action_button("roll", "Roll")
action_button("next", "Next")
action_button("end_step", "End step")
action_button("end_turn", "End turn")
action_button("pass", "Pass")
action_button("undo", "Undo")
}
function on_focus_hex_tip(id) {
ui.hexes[id].classList.add("tip")
}
function on_blur_hex_tip(id) {
ui.hexes[id].classList.remove("tip")
}
function on_click_hex_tip(id) {
ui.hexes[id].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
}
function on_focus_piece_tip(id) {
ui.pieces[id].classList.add("tip")
}
function on_blur_piece_tip(id) {
ui.pieces[id].classList.remove("tip")
}
function on_click_piece_tip(id) {
ui.pieces[id].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
}
const DICE_TEXT = {
D0: '[0]',
D1: '[1]',
D2: '[2]',
D3: '[3]',
D4: '[4]',
D5: '[5]',
D6: '[6]',
}
const DICE = {
D0: '',
D1: '',
D2: '',
D3: '',
D4: '',
D5: '',
D6: '',
}
function sub_dice(match) {
return DICE[match]
}
function sub_hex(match, p1) {
let x = p1 | 0
let n = data.map.names[x]
if (n)
n = x + " " + n
else
n = x
return `${n}`
}
function sub_piece(match, p1) {
let x = p1 | 0
let n = data.pieces[x].name
let c = "piece"
if (data.pieces[x].side === "Anglo")
c = "tip anglo"
else if (data.pieces[x].side === "Prussian")
c = "tip prussian"
else if (data.pieces[x].side === "French")
c = "tip french"
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(/\b(\d\d\d\d)\b/g, sub_hex)
text = text.replace(/P(\d+)/g, sub_piece)
text = text.replace(/\bD\d\b/g, sub_dice)
text = text.replace(/^French/g, 'French')
text = text.replace(/^Coalition/g, 'Coalition')
if (text.match(/^\.h1 /)) {
text = text.substring(4)
p.className = "h1"
}
else if (text.match(/^\.h2/)) {
text = text.substring(4)
p.className = "h2"
}
else if (text.match(/^\.h3/)) {
text = text.substring(4)
p.className = "h3"
}
p.innerHTML = text
return p
}
build_hexes()
scroll_with_middle_mouse("main")