summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.css58
-rw-r--r--play.html1
-rw-r--r--play.js202
-rw-r--r--rules.js39
4 files changed, 219 insertions, 81 deletions
diff --git a/play.css b/play.css
index 84b2a5f..420ac37 100644
--- a/play.css
+++ b/play.css
@@ -104,7 +104,6 @@ body {
transition-duration: 700ms;
transition-timing-function: ease;
background-repeat: no-repeat;
- //background-position: center;
}
TWOD.piece.cylinder {
@@ -253,9 +252,10 @@ TWOD.piece.cylinder {
background-color: #fff4;
}
-/*
- TODO: only show colors for enemy powers?
-*/
+.space.objective.action {
+ background-color: #fff4;
+}
+
.space.objective.action.prussia { background-color: #00547880; }
.space.objective.action.hanover { background-color: #91c9ed80; }
.space.objective.action.russia { background-color: #147d3680; }
@@ -264,20 +264,15 @@ TWOD.piece.cylinder {
.space.objective.action.imperial { background-color: #fbe30080; }
.space.objective.action.france { background-color: #ed1c2380; }
-.major_road {
- position: absolute;
- box-sizing: border-box;
- background-color: black;
- width: 20px;
- height: 6px;
-}
+@media (pointer: fine) {
+
+.move line { stroke: white; }
+.battle line { stroke: #c00; }
+.retreat line { stroke: #800; opacity: 33%; }
+line { stroke-linecap: round; }
+line.road { stroke-width: 6px; }
+line.major_road { stroke-width: 8px; }
-.road {
- position: absolute;
- box-sizing: border-box;
- background-color: dimgray;
- width: 20px;
- height: 4px;
}
.marker.conquest.austria { background-image: url(images/conquest_austria.2x.png) }
@@ -302,8 +297,6 @@ TWOD.piece.cylinder {
.piece.cube.russia { background-image: url(images/cube_russia.svg) }
.piece.cube.sweden { background-image: url(images/cube_sweden.svg) }
-/* 3D
-*/
.piece.cylinder.austria_1 { background-image: url(images/cylinder_austria_1.svg) }
.piece.cylinder.austria_2 { background-image: url(images/cylinder_austria_2.svg) }
.piece.cylinder.austria_3 { background-image: url(images/cylinder_austria_3.svg) }
@@ -337,33 +330,6 @@ TWOD.piece.cylinder {
.piece.cylinder.russia.oos { background-image: url(images/cylinder_russia_oos.svg) }
.piece.cylinder.sweden.oos { background-image: url(images/cylinder_sweden_oos.svg) }
-/* 2D
-.piece.cylinder.austria_1 { background-image: url(images/A1.png) }
-.piece.cylinder.austria_2 { background-image: url(images/A2.png) }
-.piece.cylinder.austria_3 { background-image: url(images/A3.png) }
-.piece.cylinder.austria_4 { background-image: url(images/A4.png) }
-.piece.cylinder.austria_5 { background-image: url(images/A5.png) }
-.piece.cylinder.france_1 { background-image: url(images/F1.png) }
-.piece.cylinder.france_2 { background-image: url(images/F2.png) }
-.piece.cylinder.france_3 { background-image: url(images/F3.png) }
-.piece.cylinder.hanover_1 { background-image: url(images/H1.png) }
-.piece.cylinder.hanover_2 { background-image: url(images/H2.png) }
-.piece.cylinder.imperial_1 { background-image: url(images/I1.png) }
-.piece.cylinder.prussia_1 { background-image: url(images/P1.png) }
-.piece.cylinder.prussia_2 { background-image: url(images/P2.png) }
-.piece.cylinder.prussia_3 { background-image: url(images/P3.png) }
-.piece.cylinder.prussia_4 { background-image: url(images/P4.png) }
-.piece.cylinder.prussia_5 { background-image: url(images/P5.png) }
-.piece.cylinder.prussia_6 { background-image: url(images/P6.png) }
-.piece.cylinder.prussia_7 { background-image: url(images/P7.png) }
-.piece.cylinder.prussia_8 { background-image: url(images/P8.png) }
-.piece.cylinder.russia_1 { background-image: url(images/R1.png) }
-.piece.cylinder.russia_2 { background-image: url(images/R2.png) }
-.piece.cylinder.russia_3 { background-image: url(images/R3.png) }
-.piece.cylinder.russia_4 { background-image: url(images/R4.png) }
-.piece.cylinder.sweden_1 { background-image: url(images/S1.png) }
-*/
-
.card {
width: 220px;
height: 342px;
diff --git a/play.html b/play.html
index 009ff21..3baac42 100644
--- a/play.html
+++ b/play.html
@@ -38,6 +38,7 @@
<div id="mapwrap">
<div id="map">
+ <svg id="roads" class="move" viewBox="0 0 2485 1654"></svg>
<div id="spaces"></div>
<div id="markers"></div>
<div id="pieces"></div>
diff --git a/play.js b/play.js
index 18053a6..0e6a0a0 100644
--- a/play.js
+++ b/play.js
@@ -2,6 +2,8 @@
// TODO: sort selected generals above deselected generals when detaching?
+const svgNS = "http://www.w3.org/2000/svg"
+
function toggle_pieces() {
document.getElementById("pieces").classList.toggle("hide")
}
@@ -118,6 +120,7 @@ function to_value(c) {
const ui = {
header: document.querySelector("header"),
+ roads_element: document.getElementById("roads"),
spaces_element: document.getElementById("spaces"),
pieces_element: document.getElementById("pieces"),
markers_element: document.getElementById("markers"),
@@ -142,6 +145,7 @@ const ui = {
document.getElementById("hand_france"),
],
cities: [],
+ roads: [],
action_register: [],
}
@@ -153,9 +157,12 @@ function register_action(target, action, id) {
}
function on_click_action(evt, target) {
- if (evt.button === 0)
- if (send_action(target.my_action, target.my_id))
+ if (evt.button === 0) {
+ if (send_action(target.my_action, target.my_id)) {
+ hide_move_path()
evt.stopPropagation()
+ }
+ }
}
function process_actions() {
@@ -169,24 +176,39 @@ function is_action(action, arg) {
return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg))
}
-function make_road(type, x, y, dx, dy) {
- let e = document.createElement("div")
- e.className = type
- e.style.left = x + "px"
- e.style.top = y + "px"
- let a = Math.atan2(dy, dx)
- let s = (Math.hypot(dx, dy) - 15) / 20
- e.style.transform =
- "rotate(" + a + "rad)" +
- "scale(" + s + ", 1)"
- // TODO: rotate to align
- ui.spaces_element.appendChild(e)
+function make_road(c1, c2, type) {
+ let e = document.createElementNS(svgNS, "line")
+ e.setAttribute("class", type)
+ let x1 = data.cities.x[c1]
+ let y1 = data.cities.y[c1]
+ let x2 = data.cities.x[c2]
+ let y2 = data.cities.y[c2]
+
+ let v = Math.hypot(x2 - x1, y2 - y1)
+ let dx = (x2 - x1) / v
+ let dy = (y2 - y1) / v
+ let r = 18
+ x1 += r * dx
+ y1 += r * dy
+ x2 -= r * dx
+ y2 -= r * dy
+
+ e.setAttribute("x1", x1)
+ e.setAttribute("y1", y1)
+ e.setAttribute("x2", x2)
+ e.setAttribute("y2", y2)
+ e.setAttribute("visibility", "hidden")
+ ui.roads[c1][c2] = e
+ ui.roads[c2][c1] = e
+ ui.roads_element.appendChild(e)
}
function create_piece(action, id, style) {
let e = document.createElement("div")
e.className = style
register_action(e, action, id)
+ e.onmouseenter = on_focus_piece
+ e.onmouseleave = on_blur_piece
return e
}
@@ -281,9 +303,6 @@ function on_init() {
create_piece("piece", 34, "piece cube france"),
]
- for (let e of ui.pieces)
- ui.pieces_element.appendChild(e)
-
ui.troops = []
for (let i = 0; i < 24; ++i)
ui.troops[i] = create_marker("hide")
@@ -334,24 +353,18 @@ function on_init() {
for (let fc = 0; fc <= 18; ++fc)
ui.fate[fc] = make_fate_card(fc)
- if (0) {
+ if (1) {
+ for (let a = 0; a <= last_city; ++a)
+ ui.roads[a] = []
for (let a = 0; a <= last_city; ++a) {
for (let b of cities.major_roads[a]) {
if (a < b) {
- let dx = cities.x[a] - cities.x[b]
- let dy = cities.y[a] - cities.y[b]
- let x = (cities.x[a] + cities.x[b]) / 2
- let y = (cities.y[a] + cities.y[b]) / 2
- make_road("major_road", x - 10, y - 3, dx, dy)
+ make_road(a, b, "major_road")
}
}
for (let b of cities.roads[a]) {
if (a < b) {
- let dx = cities.x[a] - cities.x[b]
- let dy = cities.y[a] - cities.y[b]
- let x = (cities.x[a] + cities.x[b]) / 2
- let y = (cities.y[a] + cities.y[b]) / 2
- make_road("road", x - 10, y - 1, dx, dy)
+ make_road(a, b, "road")
}
}
}
@@ -391,6 +404,9 @@ function on_init() {
register_action(e, "space", a)
+ e.onmouseenter = on_focus_city
+ e.onmouseleave = on_blur_city
+
//e.classList.add("hide")
e.style.left = x + "px"
e.style.top = y + "px"
@@ -443,6 +459,119 @@ function on_init() {
update_favicon()
}
+/* SHOW PATH FOR MOVES */
+
+function on_focus_city(evt) {
+ document.getElementById("status").textContent = evt.target.my_name
+ if (view) {
+ if (view.move_minor)
+ show_move_path(evt.target.my_id)
+ if (view.retreat)
+ show_retreat_path(evt.target.my_id)
+ }
+}
+
+function on_blur_city() {
+ document.getElementById("status").textContent = ""
+ hide_move_path()
+}
+
+function on_focus_piece(evt) {
+ document.getElementById("status").textContent = evt.target.my_name
+ if (view && view.move_minor)
+ show_move_path(view.pos[evt.target.my_id])
+}
+
+function on_blur_piece() {
+ document.getElementById("status").textContent = ""
+ hide_move_path()
+}
+
+var _move_path = []
+var _battle_road = null
+
+function hide_move_path() {
+ if (_move_path) {
+ for (let i = 1; i < _move_path.length; ++i) {
+ let x = _move_path[i-1]
+ let y = _move_path[i]
+ ui.roads[x][y].setAttribute("visibility", "hidden")
+ }
+ _move_path = null
+ }
+}
+
+function show_move_path(x) {
+ hide_move_path()
+
+ if (map_get(view.move_major, x, -1) >= 0) {
+ _move_path = []
+ while (x >= 0) {
+ _move_path.push(x)
+ x = map_get(view.move_major, x, -1)
+ }
+ }
+
+ else
+
+ if (map_get(view.move_minor, x, -1) >= 0) {
+ _move_path = []
+ while (x >= 0) {
+ _move_path.push(x)
+ x = map_get(view.move_minor, x, -1)
+ }
+ }
+
+ if (_move_path) {
+ for (let i = 1; i < _move_path.length; ++i) {
+ let x = _move_path[i-1]
+ let y = _move_path[i]
+ ui.roads[x][y].setAttribute("visibility", "visible")
+ }
+ }
+}
+
+function show_retreat_path(x) {
+ hide_move_path()
+ _move_path = map_get(view.retreat, x, null)
+ if (_move_path) {
+ _move_path = _move_path.slice()
+ _move_path.push(x)
+ for (let i = 1; i < _move_path.length; ++i) {
+ let x = _move_path[i-1]
+ let y = _move_path[i]
+ ui.roads[x][y].setAttribute("visibility", "visible")
+ }
+ }
+}
+
+function show_battle_path() {
+ _battle_road = ui.roads[view.attacker][view.defender]
+ _battle_road.setAttribute("visibility", "visible")
+}
+
+function hide_battle_path() {
+ if (_battle_road) {
+ _battle_road.setAttribute("visibility", "hidden")
+ _battle_road = null
+ }
+}
+
+function update_path() {
+ hide_move_path()
+ hide_battle_path()
+ if (view.move_major) {
+ ui.roads_element.setAttribute("class", "move")
+ } else if (view.retreat) {
+ ui.roads_element.setAttribute("class", "retreat")
+ } else if (view.attacker !== undefined) {
+ ui.roads_element.setAttribute("class", "battle")
+ show_battle_path()
+ } else {
+ ui.roads_element.setAttribute("class", null)
+ }
+}
+
/* UPDATE UI */
function layout_general_offset(g, s) {
@@ -610,6 +739,8 @@ function on_update() {
for (let t = 24; t <= 34; ++t)
layout_train(t, view.pos[t])
+ update_path()
+
let back = [ 0, 0, 0, 0 ]
for (let i = 0; i < 5; ++i)
@@ -795,3 +926,18 @@ function set_add_all(set, other) {
set_add(set, item)
}
+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
+}
diff --git a/rules.js b/rules.js
index a9e2731..c78810e 100644
--- a/rules.js
+++ b/rules.js
@@ -1268,6 +1268,9 @@ states.move_supply_train_NEW = {
if (game.move_minor)
map_for_each_key(game.move_minor, s => { if (s !== here) gen_action_space(s) })
+ view.move_major = game.move_major
+ view.move_minor = game.move_minor
+
/*
if (game.count < 2 + game.major)
for (let next of data.cities.major_roads[here])
@@ -1401,6 +1404,9 @@ states.move_general_NEW = {
view.actions.stop = 1
}
+ view.move_major = game.move_major
+ view.move_minor = game.move_minor
+
if (game.move_major)
map_for_each_key(game.move_major, s => { if (s !== here) gen_action_space_or_piece(s) })
if (game.move_minor)
@@ -1654,7 +1660,7 @@ states.recruit = {
if (av_troops > 0)
for (let p of all_power_generals[game.power])
if (game.troops[p] < 8)
- gen_action_piece(p)
+ gen_action_supreme_commander(game.pos[p])
if (av_trains > 0)
for (let p of all_power_trains[game.power])
if (game.pos[p] === ELIMINATED)
@@ -1678,7 +1684,9 @@ states.recruit = {
game.selected = [ p ]
game.state = "re_enter"
} else {
- game.troops[p]++
+ for (let x of all_power_generals[game.power])
+ if (game.pos[x] === game.pos[p] && game.troops[x] < 8)
+ game.troops[x] ++
}
},
end_recruit() {
@@ -1868,6 +1876,7 @@ function gen_play_card(suit) {
states.combat_attack = {
prompt() {
prompt("Attack: " + game.count)
+
view.selected = [
get_supreme_commander(game.attacker),
get_supreme_commander(game.defender)
@@ -1897,6 +1906,7 @@ states.combat_attack = {
states.combat_defend = {
prompt() {
prompt("Defend: " + (-game.count))
+
view.selected = [
get_supreme_commander(game.attacker),
get_supreme_commander(game.defender)
@@ -1926,6 +1936,7 @@ states.combat_defend = {
states.combat_attack_reserve = {
prompt() {
prompt("Attack: Choose value. " + game.count)
+
view.selected = [
get_supreme_commander(game.attacker),
get_supreme_commander(game.defender)
@@ -1942,6 +1953,7 @@ states.combat_attack_reserve = {
states.combat_defend_reserve = {
prompt() {
prompt("Defend: Choose value." + (-game.count))
+
view.selected = [
get_supreme_commander(game.attacker),
get_supreme_commander(game.defender)
@@ -2086,7 +2098,7 @@ function search_retreat_possible_dfs(result, seen, here, range) {
if (has_any_piece(next))
continue
if (range === 1) {
- set_add(result, next)
+ map_set(result, next, seen.slice())
} else {
seen.push(next)
search_retreat_possible_dfs(result, seen, next, range - 1)
@@ -2113,9 +2125,12 @@ function search_retreat(loser, winner, range) {
}
let result = []
- for (let s of possible)
- if (map_get(distance, s, -1) === max)
+ map_for_each(possible, (s, path) => {
+ if (map_get(distance, s, -1) === max) {
result.push(s)
+ result.push(path)
+ }
+ })
return result
}
@@ -2123,12 +2138,12 @@ states.retreat = {
prompt() {
prompt("Retreat loser " + Math.abs(game.count))
view.selected = game.selected
+ view.retreat = game.retreat
if (game.retreat.length === 0) {
prompt("Eliminate loser.")
gen_action_piece(game.selected[0])
} else {
- for (let to of game.retreat)
- gen_action_space(to)
+ map_for_each_key(game.retreat, gen_action_space)
}
},
space(to) {
@@ -2794,6 +2809,11 @@ exports.view = function (state, player) {
retro: game.retro,
}
+ if (game.attacker !== undefined && game.defender !== undefined) {
+ view.attacker = game.attacker
+ view.defender = game.defender
+ }
+
if (game.state === "game_over") {
view.prompt = game.victory
} else if (game.active !== player) {
@@ -3137,3 +3157,8 @@ function map_for_each_key(map, f) {
for (let i = 0; i < map.length; i += 2)
f(map[i])
}
+
+function map_for_each(map, f) {
+ for (let i = 0; i < map.length; i += 2)
+ f(map[i], map[i+1])
+}