diff options
-rw-r--r-- | play.css | 58 | ||||
-rw-r--r-- | play.html | 1 | ||||
-rw-r--r-- | play.js | 202 | ||||
-rw-r--r-- | rules.js | 39 |
4 files changed, 219 insertions, 81 deletions
@@ -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; @@ -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> @@ -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 +} @@ -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]) +} |