diff options
Diffstat (limited to 'diplomacy/web/src/gui/map')
-rw-r--r-- | diplomacy/web/src/gui/map/dom_order_builder.js | 278 | ||||
-rw-r--r-- | diplomacy/web/src/gui/map/dom_past_map.js | 114 | ||||
-rw-r--r-- | diplomacy/web/src/gui/map/map.jsx | 94 | ||||
-rw-r--r-- | diplomacy/web/src/gui/map/renderer.js | 615 |
4 files changed, 1101 insertions, 0 deletions
diff --git a/diplomacy/web/src/gui/map/dom_order_builder.js b/diplomacy/web/src/gui/map/dom_order_builder.js new file mode 100644 index 0000000..14ba743 --- /dev/null +++ b/diplomacy/web/src/gui/map/dom_order_builder.js @@ -0,0 +1,278 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see <https://www.gnu.org/licenses/>. +// ============================================================================== +import {UTILS} from "../../diplomacy/utils/utils"; +import $ from "jquery"; +import {extendOrderBuilding} from "../utils/order_building"; +import {Diplog} from "../../diplomacy/utils/diplog"; + +function parseLocation(txt) { + if (txt.length > 2 && txt[1] === ' ' && ['A', 'F'].includes(txt[0])) + return txt.substr(2); + return txt; +} + +export class DOMOrderBuilder { + + constructor(svgElement, onOrderBuilding, onOrderBuilt, onSelectLocation, onSelectVia, onError) { + this.svg = svgElement; + this.cbOrderBuilding = onOrderBuilding; + this.cbOrderBuilt = onOrderBuilt; + this.cbSelectLocation = onSelectLocation; + this.cbSelectVia = onSelectVia; + this.cbError = onError; + + this.game = null; + this.mapData = null; + this.orderBuilding = null; + + this.provinceColors = {}; + this.clickedID = null; + this.clickedNeighbors = []; + + this.onProvinceClick = this.onProvinceClick.bind(this); + this.onLabelClick = this.onLabelClick.bind(this); + this.onUnitClick = this.onUnitClick.bind(this); + } + + saveProvinceColors() { + // Get province colors. + const elements = this.svg.getElementsByTagName('path'); + for (let element of elements) { + this.provinceColors[element.id] = element.getAttribute('class'); + } + } + + provinceNameToMapID(name) { + return `_${name.toLowerCase()}___${this.svg.parentNode.id}`; + } + + mapID(id) { + return `${id}___${this.svg.parentNode.id}`; + } + + onOrderBuilding(svgPath, powerName, orderPath) { + this.cbOrderBuilding(powerName, orderPath); + } + + onOrderBuilt(svgPath, powerName, orderString) { + this.cbOrderBuilt(powerName, orderString); + } + + onError(svgPath, error) { + this.cbError(error.toString()); + } + + handleSvgPath(svgPath) { + const orderBuilding = this.orderBuilding; + if (!orderBuilding.builder) + return this.onError(svgPath, 'No orderable locations.'); + + const province = this.mapData.getProvince(svgPath.id); + if (!province) + return; + + const stepLength = orderBuilding.builder.steps.length; + if (orderBuilding.path.length >= stepLength) + throw new Error(`Order building: current steps count (${orderBuilding.path.length}) should be less than` + + ` expected steps count (${stepLength}) (${orderBuilding.path.join(', ')}).`); + + const lengthAfterClick = orderBuilding.path.length + 1; + let validLocations = []; + const testedPath = [orderBuilding.type].concat(orderBuilding.path); + const value = UTILS.javascript.getTreeValue(this.game.ordersTree, testedPath); + if (value !== null) { + const checker = orderBuilding.builder.steps[lengthAfterClick - 1]; + try { + const possibleLocations = checker(province, orderBuilding.power); + for (let possibleLocation of possibleLocations) { + possibleLocation = possibleLocation.toUpperCase(); + if (value.includes(possibleLocation)) + validLocations.push(possibleLocation); + } + } catch (error) { + return this.onError(svgPath, error); + } + } + if (!validLocations.length) + return this.onError(svgPath, 'Disallowed.'); + + if (validLocations.length > 1 && orderBuilding.type === 'S' && orderBuilding.path.length >= 2) { + // We are building a support order and we have a multiple choice for a location. + // Let's check if next location to choose is a coast. To have a coast: + // - all possible locations must start with same 3 characters. + // - we expect at least province name in possible locations (e.g. 'SPA' for 'SPA/NC'). + // If we have a coast, we will remove province name from possible locations. + let isACoast = true; + let validLocationsNoProvinceName = []; + for (let i = 0; i < validLocations.length; ++i) { + let location = validLocations[i]; + if (i > 0) { + // Compare 3 first letters with previous location. + if (validLocations[i - 1].substring(0, 3).toUpperCase() !== validLocations[i].substring(0, 3).toUpperCase()) { + // No same prefix with previous location. We does not have a coast. + isACoast = false; + break; + } + } + if (location.length !== 3) + validLocationsNoProvinceName.push(location); + } + if (validLocations.length === validLocationsNoProvinceName.length) { + // We have not found province name. + isACoast = false; + } + if (isACoast) { + // We want to choose location in a coastal province. Let's remove province name. + validLocations = validLocationsNoProvinceName; + } + } + + if (validLocations.length > 1) { + if (this.cbSelectLocation) { + return this.cbSelectLocation(validLocations, orderBuilding.power, orderBuilding.type, orderBuilding.path); + } else { + Diplog.warn(`Forced to select first valid location.`); + validLocations = [validLocations[0]]; + } + } + let orderBuildingType = orderBuilding.type; + if (lengthAfterClick === stepLength && orderBuildingType === 'M') { + const moveOrderPath = ['M'].concat(orderBuilding.path, validLocations[0]); + const moveTypes = UTILS.javascript.getTreeValue(this.game.ordersTree, moveOrderPath); + if (moveTypes !== null) { + if (moveTypes.length === 2) { + // This move can be done either regularly or VIA a fleet. Let user choose. + return this.cbSelectVia(validLocations[0], orderBuilding.power, orderBuilding.path); + } else { + orderBuildingType = moveTypes[0]; + } + } + } + this.clickedID = svgPath.id; + + this.cleanBuildingView(); + if (lengthAfterClick < stepLength) + this.renderBuildingView(validLocations[0]); + extendOrderBuilding( + orderBuilding.power, orderBuildingType, orderBuilding.path, validLocations[0], + this.cbOrderBuilding, this.cbOrderBuilt, this.cbError + ); + + } + + getPathFromProvince(province) { + let path = this.svg.getElementById(this.provinceNameToMapID(province.name)); + if (!path) { + for (let alias of province.aliases) { + path = this.svg.getElementById(this.provinceNameToMapID(alias)); + if (path) + break; + } + } + return path; + } + + onProvinceClick(event) { + this.handleSvgPath(event.target); + } + + onLabelClick(event) { + const province = this.mapData.getProvince(event.target.textContent); + if (province) { + const path = this.getPathFromProvince(province); + if (path) + this.handleSvgPath(path); + } + } + + onUnitClick(event) { + const province = this.mapData.getProvince(event.target.getAttribute('diplomacyUnit')); + if (province) { + let path = this.getPathFromProvince(province); + if (!path && province.isCoast()) + path = this.svg.getElementById(this.provinceNameToMapID(province.parent.name)); + if (path) { + this.handleSvgPath(path); + } + } + } + + cleanBuildingView() { + if (this.clickedID) { + const path = this.svg.getElementById(this.clickedID); + if (path) + path.setAttribute('class', this.provinceColors[this.clickedID]); + } + for (let neighborName of this.clickedNeighbors) { + const province = this.mapData.getProvince(neighborName); + if (!province) + continue; + const path = this.getPathFromProvince(province); + if (path) + path.setAttribute('class', this.provinceColors[path.id]); + } + this.clickedNeighbors = []; + } + + renderBuildingView(extraLocation) { + if (this.clickedID) { + const path = this.svg.getElementById(this.clickedID); + if (path) + path.setAttribute('class', 'provinceRed'); + } + const selectedPath = [this.orderBuilding.type].concat(this.orderBuilding.path); + if (extraLocation) + selectedPath.push(extraLocation); + const possibleNeighbors = UTILS.javascript.getTreeValue(this.game.ordersTree, selectedPath); + if (!possibleNeighbors) + return; + this.clickedNeighbors = possibleNeighbors.map(neighbor => parseLocation(neighbor)); + if (this.clickedNeighbors.length) { + for (let neighbor of this.clickedNeighbors) { + let neighborProvince = this.mapData.getProvince(neighbor); + if (!neighborProvince) + throw new Error('Unknown neighbor province ' + neighbor); + let path = this.getPathFromProvince(neighborProvince); + if (!path && neighborProvince.isCoast()) + path = this.getPathFromProvince(neighborProvince.parent); + if (!path) + throw new Error(`Unable to find SVG path related to province ${neighborProvince.name}.`); + path.setAttribute('class', neighborProvince.isWater() ? 'provinceBlue' : 'provinceGreen'); + } + } + } + + update(game, mapData, orderBuilding) { + this.game = game; + this.mapData = mapData; + this.orderBuilding = orderBuilding; + this.saveProvinceColors(); + // If there is a building path, then we are building, so we don't clean anything. + this.cleanBuildingView(); + if (this.orderBuilding.path.length) + this.renderBuildingView(); + // I don't yet know why I should place this here. Maybe because unit are re-rendered manually at every reloading ? + $(`#${this.svg.parentNode.id} svg use[diplomacyUnit]`).click(this.onUnitClick); + } + + init(game, mapData, orderBuilding) { + $(`#${this.svg.parentNode.id} svg path`).click(this.onProvinceClick); + $(`#${this.mapID('BriefLabelLayer')} text`).click(this.onLabelClick); + this.update(game, mapData, orderBuilding); + } + +} diff --git a/diplomacy/web/src/gui/map/dom_past_map.js b/diplomacy/web/src/gui/map/dom_past_map.js new file mode 100644 index 0000000..eb44616 --- /dev/null +++ b/diplomacy/web/src/gui/map/dom_past_map.js @@ -0,0 +1,114 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see <https://www.gnu.org/licenses/>. +// ============================================================================== +import $ from "jquery"; + +export class DOMPastMap { + + constructor(svgElement, onHover) { + this.svg = svgElement; + this.cbHover = onHover; + this.game = null; + this.orders = null; + this.mapData = null; + this.onProvinceHover = this.onProvinceHover.bind(this); + this.onLabelHover = this.onLabelHover.bind(this); + this.onUnitHover = this.onUnitHover.bind(this); + } + + provinceNameToMapID(name) { + return `_${name.toLowerCase()}___${this.svg.parentNode.id}`; + } + + mapID(id) { + return `${id}___${this.svg.parentNode.id}`; + } + + onHover(name) { + const orders = []; + if (this.orders) { + for (let powerOrders of Object.values(this.orders)) { + if (powerOrders) { + for (let order of powerOrders) { + const pieces = order.split(/ +/); + if (pieces[1].slice(0, 3) === name.toUpperCase().slice(0, 3)) + orders.push(order); + } + } + } + } + return orders; + } + + handleSvgPath(svgPath) { + const province = this.mapData.getProvince(svgPath.id); + if (province) { + this.cbHover(province.name, this.onHover(province.name)); + } + } + + getPathFromProvince(province) { + let path = this.svg.getElementById(this.provinceNameToMapID(province.name)); + if (!path) { + for (let alias of province.aliases) { + path = this.svg.getElementById(this.provinceNameToMapID(alias)); + if (path) + break; + } + } + return path; + } + + onProvinceHover(event) { + this.handleSvgPath(event.target); + } + + onLabelHover(event) { + const province = this.mapData.getProvince(event.target.textContent); + if (province) { + const path = this.getPathFromProvince(province); + if (path) + this.handleSvgPath(path); + } + } + + onUnitHover(event) { + const province = this.mapData.getProvince(event.target.getAttribute('diplomacyUnit')); + if (province) { + let path = this.getPathFromProvince(province); + if (!path && province.isCoast()) + path = this.svg.getElementById(this.provinceNameToMapID(province.parent.name)); + if (path) { + this.handleSvgPath(path); + } + } + } + + update(game, mapData, orders) { + this.game = game; + this.mapData = mapData; + this.orders = orders; + // I don't yet know why I should place this here. Maybe because unit are re-rendered manually at every reloading ? + $(`#${this.svg.parentNode.id} svg use[diplomacyUnit]`).hover(this.onUnitHover); + } + + init(game, mapData, orders) { + $(`#${this.svg.parentNode.id} svg path`).hover(this.onProvinceHover).mouseleave(() => this.cbHover(null, null)); + $(`#${this.mapID('BriefLabelLayer')} text`).hover(this.onLabelHover); + this.update(game, mapData, orders); + } + +} diff --git a/diplomacy/web/src/gui/map/map.jsx b/diplomacy/web/src/gui/map/map.jsx new file mode 100644 index 0000000..1130563 --- /dev/null +++ b/diplomacy/web/src/gui/map/map.jsx @@ -0,0 +1,94 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see <https://www.gnu.org/licenses/>. +// ============================================================================== +import React from "react"; +import SVG from 'react-inlinesvg'; +import mapSVG from '../../standard.svg'; +import {Renderer} from "./renderer"; +import {MapData} from "../utils/map_data"; +import {DOMOrderBuilder} from "./dom_order_builder"; +import PropTypes from 'prop-types'; +import {DOMPastMap} from "./dom_past_map"; + +export class Map extends React.Component { + // id: ID of div wrapping SVG map. + // mapInfo: dict + // game: game engine + // onError: callback(error) + // showOrders: bool + + // orderBuilding: dict + // onOrderBuilding: callback(powerName, orderBuildingPath) + // onOrderBuilt: callback(powerName, orderString) + + constructor(props) { + super(props); + this.renderer = null; + this.domOrderBuilder = null; + this.domPastMap = null; + this.initSVG = this.initSVG.bind(this); + } + + initSVG() { + const svg = document.getElementById(this.props.id).getElementsByTagName('svg')[0]; + + const game = this.props.game; + const mapData = new MapData(this.props.mapInfo, game); + this.renderer = new Renderer(svg, game, mapData); + this.renderer.render(this.props.showOrders, this.props.orders); + if (this.props.orderBuilding) { + this.domOrderBuilder = new DOMOrderBuilder( + svg, + this.props.onOrderBuilding, this.props.onOrderBuilt, this.props.onSelectLocation, this.props.onSelectVia, + this.props.onError + ); + this.domOrderBuilder.init(game, mapData, this.props.orderBuilding); + } else if (this.props.onHover) { + this.domPastMap = new DOMPastMap(svg, this.props.onHover); + this.domPastMap.init(game, mapData, this.props.orders); + } + } + + render() { + if (this.renderer) { + const game = this.props.game; + const mapData = new MapData(this.props.mapInfo, game); + this.renderer.update(game, mapData, this.props.showOrders, this.props.orders); + if (this.domOrderBuilder) + this.domOrderBuilder.update(game, mapData, this.props.orderBuilding); + else if (this.domPastMap) + this.domPastMap.update(game, mapData, this.props.orders); + } + const divFactory = ((props, children) => <div id={this.props.id} {...props}>{children}</div>); + return <SVG wrapper={divFactory} uniquifyIDs={true} uniqueHash={this.props.id} src={mapSVG} + onLoad={this.initSVG} onError={err => this.props.onError(err.message)}>Game map</SVG>; + } +} + +Map.propTypes = { + id: PropTypes.string, + showOrders: PropTypes.bool, + orders: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)), + onSelectLocation: PropTypes.func, + onSelectVia: PropTypes.func, + game: PropTypes.object, + mapInfo: PropTypes.object, + orderBuilding: PropTypes.object, + onOrderBuilding: PropTypes.func, + onOrderBuilt: PropTypes.func, + onError: PropTypes.func, + onHover: PropTypes.func, +}; diff --git a/diplomacy/web/src/gui/map/renderer.js b/diplomacy/web/src/gui/map/renderer.js new file mode 100644 index 0000000..e2586af --- /dev/null +++ b/diplomacy/web/src/gui/map/renderer.js @@ -0,0 +1,615 @@ +// ============================================================================== +// Copyright (C) 2019 - Philip Paquette, Steven Bocco +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License along +// with this program. If not, see <https://www.gnu.org/licenses/>. +// ============================================================================== +import $ from "jquery"; + +const ARMY = 'Army'; +const FLEET = 'Fleet'; +// SVG tag names. +const PREFIX_TAG = 'jdipNS'.toLowerCase(); +const TAG_ORDERDRAWING = 'jdipNS:ORDERDRAWING'.toLowerCase(); +const TAG_POWERCOLORS = 'jdipNS:POWERCOLORS'.toLowerCase(); +const TAG_POWERCOLOR = 'jdipNS:POWERCOLOR'.toLowerCase(); +const TAG_SYMBOLSIZE = 'jdipNS:SYMBOLSIZE'.toLowerCase(); +const TAG_PROVINCE_DATA = 'jdipNS:PROVINCE_DATA'.toLowerCase(); +const TAG_PROVINCE = 'jdipNS:PROVINCE'.toLowerCase(); +const TAG_UNIT = 'jdipNS:UNIT'.toLowerCase(); +const TAG_DISLODGED_UNIT = 'jdipNS:DISLODGED_UNIT'.toLowerCase(); +const TAG_SUPPLY_CENTER = 'jdipNS:SUPPLY_CENTER'.toLowerCase(); +const TAG_DISPLAY = 'jdipNS:DISPLAY'.toLowerCase(); + +function attr(node, name) { + return node.attributes[name].value; +} + +function offset(floatString, offset) { + return "" + (parseFloat(floatString) + offset); +} + +export class Renderer { + constructor(svgDomElement, game, mapData) { + this.svg = svgDomElement; + this.game = game; + this.mapData = mapData; + this.metadata = { + color: {}, + symbol_size: {}, + orders: {}, + coord: {} + }; + this.initialInfluences = {}; + this.__load_metadata(); + this.__save_initial_influences(); + } + + __hashed_id(id) { + return `${id}___${this.svg.parentNode.id}`; + } + + __svg_element_from_id(id) { + const hashedID = this.__hashed_id(id); + const element = this.svg.getElementById(hashedID); + if (!element) + throw new Error(`Unable to find ID ${id} (looked for hashed ID ${hashedID})`); + return element; + } + + __load_metadata() { + // Order drawings. + const order_drawings = this.svg.getElementsByTagName(TAG_ORDERDRAWING); + if (!order_drawings.length) + throw new Error('Unable to find order drawings (tag ' + TAG_ORDERDRAWING + ') in SVG map.'); + for (let order_drawing of order_drawings) { + for (let child_node of order_drawing.childNodes) { + if (child_node.nodeName === TAG_POWERCOLORS) { + // Power colors. + for (let power_color of child_node.childNodes) { + if (power_color.nodeName === TAG_POWERCOLOR) { + this.metadata.color[attr(power_color, 'power').toUpperCase()] = attr(power_color, 'color'); + } + } + } else if (child_node.nodeName === TAG_SYMBOLSIZE) { + // Symbol size. + this.metadata.symbol_size[attr(child_node, 'name')] = [attr(child_node, 'height'), attr(child_node, 'width')]; + } else if (child_node.nodeName.startsWith(PREFIX_TAG)) { + // Order type. + const order_type = child_node.nodeName.replace(PREFIX_TAG + ':', ''); + this.metadata.orders[order_type] = {}; + for (let attribute of child_node.attributes) { + if (!attribute.name.includes(':')) { + this.metadata.orders[order_type][attribute.name] = attribute.value; + } + } + } + } + } + // Object coordinates. + const all_province_data = this.svg.getElementsByTagName(TAG_PROVINCE_DATA); + if (!all_province_data.length) + throw new Error('Unable to find province data in SVG map (tag ' + TAG_PROVINCE_DATA + ').'); + for (let province_data of all_province_data) { + for (let child_node of province_data.childNodes) { + // Province. + if (child_node.nodeName === TAG_PROVINCE) { + const province = attr(child_node, 'name').toUpperCase().replace('-', '/'); + this.metadata.coord[province] = {}; + for (let coord_node of child_node.childNodes) { + if (coord_node.nodeName === TAG_UNIT) { + this.metadata.coord[province].unit = [attr(coord_node, 'x'), attr(coord_node, 'y')]; + } else if (coord_node.nodeName === TAG_DISLODGED_UNIT) { + this.metadata.coord[province].disl = [attr(coord_node, 'x'), attr(coord_node, 'y')]; + } else if (coord_node.nodeName === TAG_SUPPLY_CENTER) { + this.metadata.coord[province].sc = [attr(coord_node, 'x'), attr(coord_node, 'y')]; + } + } + } + } + } + // Deleting. + this.svg.removeChild(this.svg.getElementsByTagName(TAG_DISPLAY)[0]); + this.svg.removeChild(this.svg.getElementsByTagName(TAG_ORDERDRAWING)[0]); + this.svg.removeChild(this.svg.getElementsByTagName(TAG_PROVINCE_DATA)[0]); + + // (this code was previously in render()) + // Removing mouse layer. + this.svg.removeChild(this.__svg_element_from_id('MouseLayer')); + } + + __save_initial_influences() { + const mapLayer = this.__svg_element_from_id('MapLayer'); + if (!mapLayer) + throw new Error('Unable to find map layer.'); + for (let element of mapLayer.childNodes) { + if (element.tagName === 'path') { + this.initialInfluences[element.id] = element.getAttribute('class'); + } + } + } + + __restore_initial_influences() { + for (let id of Object.keys(this.initialInfluences)) { + const className = this.initialInfluences[id]; + this.svg.getElementById(id).setAttribute('class', className); + } + } + + __set_current_phase() { + const current_phase = (this.game.phase[0] === '?' || this.game.phase === 'COMPLETED') ? 'FINAL' : this.game.phase; + const phase_display = this.__svg_element_from_id('CurrentPhase'); + if (phase_display) { + phase_display.childNodes[0].nodeValue = current_phase; + } + } + + __set_note(note1, note2) { + note1 = note1 || ''; + note2 = note2 || ''; + const display_note1 = this.__svg_element_from_id('CurrentNote'); + const display_note2 = this.__svg_element_from_id('CurrentNote2'); + if (display_note1) + display_note1.childNodes[0].nodeValue = note1; + if (display_note2) + display_note2.childNodes[0].nodeValue = note2; + } + + __add_unit(unit, power_name, is_dislogged) { + const split_unit = unit.split(/ +/); + const unit_type = split_unit[0]; + const loc = split_unit[1]; + const dislogged_type = is_dislogged ? 'disl' : 'unit'; + const symbol = unit_type === 'F' ? FLEET : ARMY; + const loc_x = offset(this.metadata.coord[loc][dislogged_type][0], -11.5); + const loc_y = offset(this.metadata.coord[loc][dislogged_type][1], -10.0); + // Helpful link about creating SVG elements: https://stackoverflow.com/a/25949237 + const node = document.createElementNS("http://www.w3.org/2000/svg", 'use'); + node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id((is_dislogged ? 'Dislodged' : '') + symbol)); + node.setAttribute('x', loc_x); + node.setAttribute('y', loc_y); + node.setAttribute('height', this.metadata.symbol_size[symbol][0]); + node.setAttribute('width', this.metadata.symbol_size[symbol][1]); + node.setAttribute('class', 'unit' + power_name.toLowerCase()); + node.setAttribute('diplomacyUnit', loc); + const parent_node = this.__svg_element_from_id(is_dislogged ? 'DislodgedUnitLayer' : 'UnitLayer'); + if (parent_node) + parent_node.appendChild(node); + } + + __add_supply_center(loc, power_name) { + const symbol = 'SupplyCenter'; + const loc_x = offset(this.metadata.coord[loc]['sc'][0], -8.5); + const loc_y = offset(this.metadata.coord[loc]['sc'][1], -11.0); + const node = document.createElementNS("http://www.w3.org/2000/svg", 'use'); + node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(symbol)); + node.setAttribute('x', loc_x); + node.setAttribute('y', loc_y); + node.setAttribute('height', this.metadata.symbol_size[symbol][0]); + node.setAttribute('width', this.metadata.symbol_size[symbol][1]); + node.setAttribute('class', power_name ? ('sc' + power_name.toLowerCase()) : 'scnopower'); + const parent_node = this.__svg_element_from_id('SupplyCenterLayer'); + if (parent_node) + parent_node.appendChild(node); + } + + __set_influence(loc, power_name) { + loc = loc.toUpperCase().substr(0, 3); + if (!['LAND', 'COAST'].includes(this.mapData.getProvince(loc).type)) + return; + const path = this.__svg_element_from_id('_' + loc.toLowerCase()); + if (!path || path.nodeName !== 'path') { + throw new Error(`Unable to find SVG path for loc ${loc}, got ${path ? path.nodeName : '(nothing)'}`); + } + path.setAttribute('class', power_name ? power_name.toLowerCase() : 'nopower'); + } + + issueHoldOrder(loc, power_name) { + const polygon_coord = []; + const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 8.5); + const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 9.5); + for (let ofs of [ + [13.8, -33.3], [33.3, -13.8], [33.3, 13.8], [13.8, 33.3], [-13.8, 33.3], + [-33.3, 13.8], [-33.3, -13.8], [-13.8, -33.3]] + ) { + polygon_coord.push(offset(loc_x, ofs[0]) + ',' + offset(loc_y, ofs[1])); + } + const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + const poly_1 = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + const poly_2 = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + poly_1.setAttribute('stroke-width', '10'); + poly_1.setAttribute('class', 'varwidthshadow'); + poly_1.setAttribute('points', polygon_coord.join(' ')); + poly_2.setAttribute('stroke-width', '6'); + poly_2.setAttribute('class', 'varwidthorder'); + poly_2.setAttribute('points', polygon_coord.join(' ')); + poly_2.setAttribute('stroke', this.metadata['color'][power_name]); + g_node.appendChild(poly_1); + g_node.appendChild(poly_2); + const orderLayer = this.__svg_element_from_id('Layer1'); + if (!orderLayer) + throw new Error(`Unable to find svg order layer.`); + orderLayer.appendChild(g_node); + } + + issueMoveOrder(src_loc, dest_loc, power_name) { + let src_loc_x = 0; + let src_loc_y = 0; + const phaseType = this.game.getPhaseType(); + if (phaseType === 'R') { + src_loc_x = offset(this.metadata.coord[src_loc]['unit'][0], -2.5); + src_loc_y = offset(this.metadata.coord[src_loc]['unit'][1], -2.5); + } else { + src_loc_x = offset(this.metadata.coord[src_loc]['unit'][0], 10); + src_loc_y = offset(this.metadata.coord[src_loc]['unit'][1], 10); + } + let dest_loc_x = offset(this.metadata.coord[dest_loc]['unit'][0], 10); + let dest_loc_y = offset(this.metadata.coord[dest_loc]['unit'][1], 10); + + // Adjusting destination + const delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x); + const delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y); + const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y); + dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - 30.) / vector_length * delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - 30.) / vector_length * delta_y) * 100.) / 100.; + + // Creating nodes. + const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + const line_with_shadow = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + const line_with_arrow = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + line_with_shadow.setAttribute('x1', src_loc_x); + line_with_shadow.setAttribute('y1', src_loc_y); + line_with_shadow.setAttribute('x2', dest_loc_x); + line_with_shadow.setAttribute('y2', dest_loc_y); + line_with_shadow.setAttribute('class', 'varwidthshadow'); + line_with_shadow.setAttribute('stroke-width', '10'); + line_with_arrow.setAttribute('x1', src_loc_x); + line_with_arrow.setAttribute('y1', src_loc_y); + line_with_arrow.setAttribute('x2', dest_loc_x); + line_with_arrow.setAttribute('y2', dest_loc_y); + line_with_arrow.setAttribute('class', 'varwidthorder'); + line_with_arrow.setAttribute('marker-end', 'url(#' + this.__hashed_id('arrow') + ')'); + line_with_arrow.setAttribute('stroke', this.metadata.color[power_name]); + line_with_arrow.setAttribute('stroke-width', '6'); + g_node.appendChild(line_with_shadow); + g_node.appendChild(line_with_arrow); + const orderLayer = this.__svg_element_from_id('Layer1'); + if (!orderLayer) + throw new Error(`Unable to find svg order layer.`); + orderLayer.appendChild(g_node); + } + + issueSupportMoveOrder(loc, src_loc, dest_loc, power_name) { + const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 10); + const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 10); + const src_loc_x = offset(this.metadata['coord'][src_loc]['unit'][0], 10); + const src_loc_y = offset(this.metadata['coord'][src_loc]['unit'][1], 10); + let dest_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 10); + let dest_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 10); + + // Adjusting destination + const delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x); + const delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y); + const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y); + dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - 30.) / vector_length * delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - 30.) / vector_length * delta_y) * 100.) / 100.; + + const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + const path_with_shadow = document.createElementNS("http://www.w3.org/2000/svg", 'path'); + const path_with_arrow = document.createElementNS("http://www.w3.org/2000/svg", 'path'); + path_with_shadow.setAttribute('class', 'shadowdash'); + path_with_shadow.setAttribute('d', `M ${loc_x},${loc_y} C ${src_loc_x},${src_loc_y} ${src_loc_x},${src_loc_y} ${dest_loc_x},${dest_loc_y}`); + path_with_arrow.setAttribute('class', 'supportorder'); + path_with_arrow.setAttribute('marker-end', 'url(#' + this.__hashed_id('arrow') + ')'); + path_with_arrow.setAttribute('stroke', this.metadata['color'][power_name]); + path_with_arrow.setAttribute('d', `M ${loc_x},${loc_y} C ${src_loc_x},${src_loc_y} ${src_loc_x},${src_loc_y} ${dest_loc_x},${dest_loc_y}`); + g_node.appendChild(path_with_shadow); + g_node.appendChild(path_with_arrow); + const orderLayer = this.__svg_element_from_id('Layer2'); + if (!orderLayer) + throw new Error(`Unable to find svg order layer.`); + orderLayer.appendChild(g_node); + } + + issueSupportHoldOrder(loc, dest_loc, power_name) { + const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 10); + const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 10); + let dest_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 10); + let dest_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 10); + + const delta_x = parseFloat(dest_loc_x) - parseFloat(loc_x); + const delta_y = parseFloat(dest_loc_y) - parseFloat(loc_y); + const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y); + dest_loc_x = '' + Math.round((parseFloat(loc_x) + (vector_length - 35.) / vector_length * delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(loc_y) + (vector_length - 35.) / vector_length * delta_y) * 100.) / 100.; + + const polygon_coord = []; + const poly_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 8.5); + const poly_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 9.5); + for (let ofs of [ + [15.9, -38.3], [38.3, -15.9], [38.3, 15.9], [15.9, 38.3], [-15.9, 38.3], [-38.3, 15.9], + [-38.3, -15.9], [-15.9, -38.3] + ]) { + polygon_coord.push(offset(poly_loc_x, ofs[0]) + ',' + offset(poly_loc_y, ofs[1])); + } + const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + const shadow_line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + const support_line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + const shadow_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + const support_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + shadow_line.setAttribute('x1', loc_x); + shadow_line.setAttribute('y1', loc_y); + shadow_line.setAttribute('x2', dest_loc_x); + shadow_line.setAttribute('y2', dest_loc_y); + shadow_line.setAttribute('class', 'shadowdash'); + support_line.setAttribute('x1', loc_x); + support_line.setAttribute('y1', loc_y); + support_line.setAttribute('x2', dest_loc_x); + support_line.setAttribute('y2', dest_loc_y); + support_line.setAttribute('class', 'supportorder'); + support_line.setAttribute('stroke', this.metadata['color'][power_name]); + shadow_poly.setAttribute('class', 'shadowdash'); + shadow_poly.setAttribute('points', polygon_coord.join(' ')); + support_poly.setAttribute('class', 'supportorder'); + support_poly.setAttribute('points', polygon_coord.join(' ')); + support_poly.setAttribute('stroke', this.metadata['color'][power_name]); + g_node.appendChild(shadow_line); + g_node.appendChild(support_line); + g_node.appendChild(shadow_poly); + g_node.appendChild(support_poly); + const orderLayer = this.__svg_element_from_id('Layer2'); + if (!orderLayer) + throw new Error(`Unable to find svg order layer.`); + orderLayer.appendChild(g_node); + } + + issueConvoyOrder(loc, src_loc, dest_loc, power_name) { + const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 10); + const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 10); + const src_loc_x = offset(this.metadata['coord'][src_loc]['unit'][0], 10); + const src_loc_y = offset(this.metadata['coord'][src_loc]['unit'][1], 10); + let dest_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 10); + let dest_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 10); + + const src_delta_x = parseFloat(src_loc_x) - parseFloat(loc_x); + const src_delta_y = parseFloat(src_loc_y) - parseFloat(loc_y); + const src_vector_length = Math.sqrt(src_delta_x * src_delta_x + src_delta_y * src_delta_y); + const src_loc_x_1 = '' + Math.round((parseFloat(loc_x) + (src_vector_length - 30.) / src_vector_length * src_delta_x) * 100.) / 100.; + const src_loc_y_1 = '' + Math.round((parseFloat(loc_y) + (src_vector_length - 30.) / src_vector_length * src_delta_y) * 100.) / 100.; + + let dest_delta_x = parseFloat(src_loc_x) - parseFloat(dest_loc_x); + let dest_delta_y = parseFloat(src_loc_y) - parseFloat(dest_loc_y); + let dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y); + const src_loc_x_2 = '' + Math.round((parseFloat(dest_loc_x) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_x) * 100.) / 100.; + const src_loc_y_2 = '' + Math.round((parseFloat(dest_loc_y) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_y) * 100.) / 100.; + + dest_delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x); + dest_delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y); + dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y); + dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_x) * 100.) / 100.; + dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_y) * 100.) / 100.; + + const triangle_coord = []; + const triangle_loc_x = offset(this.metadata['coord'][src_loc]['unit'][0], 10); + const triangle_loc_y = offset(this.metadata['coord'][src_loc]['unit'][1], 10); + for (let ofs of [[0, -38.3], [33.2, 19.1], [-33.2, 19.1]]) { + triangle_coord.push(offset(triangle_loc_x, ofs[0]) + ',' + offset(triangle_loc_y, ofs[1])); + } + + const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + const src_shadow_line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + const dest_shadow_line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + const src_convoy_line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + const dest_convoy_line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + const shadow_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + const convoy_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + src_shadow_line.setAttribute('x1', loc_x); + src_shadow_line.setAttribute('y1', loc_y); + src_shadow_line.setAttribute('x2', src_loc_x_1); + src_shadow_line.setAttribute('y2', src_loc_y_1); + src_shadow_line.setAttribute('class', 'shadowdash'); + + dest_shadow_line.setAttribute('x1', src_loc_x_2); + dest_shadow_line.setAttribute('y1', src_loc_y_2); + dest_shadow_line.setAttribute('x2', dest_loc_x); + dest_shadow_line.setAttribute('y2', dest_loc_y); + dest_shadow_line.setAttribute('class', 'shadowdash'); + + src_convoy_line.setAttribute('x1', loc_x); + src_convoy_line.setAttribute('y1', loc_y); + src_convoy_line.setAttribute('x2', src_loc_x_1); + src_convoy_line.setAttribute('y2', src_loc_y_1); + src_convoy_line.setAttribute('class', 'convoyorder'); + src_convoy_line.setAttribute('stroke', this.metadata['color'][power_name]); + + dest_convoy_line.setAttribute('x1', src_loc_x_2); + dest_convoy_line.setAttribute('y1', src_loc_y_2); + dest_convoy_line.setAttribute('x2', dest_loc_x); + dest_convoy_line.setAttribute('y2', dest_loc_y); + dest_convoy_line.setAttribute('class', 'convoyorder'); + dest_convoy_line.setAttribute('marker-end', 'url(#' + this.__hashed_id('arrow') + ')'); + + dest_convoy_line.setAttribute('stroke', this.metadata['color'][power_name]); + + shadow_poly.setAttribute('class', 'shadowdash'); + shadow_poly.setAttribute('points', triangle_coord.join(' ')); + + convoy_poly.setAttribute('class', 'convoyorder'); + convoy_poly.setAttribute('points', triangle_coord.join(' ')); + convoy_poly.setAttribute('stroke', this.metadata['color'][power_name]); + + g_node.appendChild(src_shadow_line); + g_node.appendChild(dest_shadow_line); + g_node.appendChild(src_convoy_line); + g_node.appendChild(dest_convoy_line); + g_node.appendChild(shadow_poly); + g_node.appendChild(convoy_poly); + + const orderLayer = this.__svg_element_from_id('Layer2'); + if (!orderLayer) + throw new Error(`Unable to find svg order layer.`); + orderLayer.appendChild(g_node); + } + + issueBuildOrder(unit_type, loc, power_name) { + const loc_x = offset(this.metadata['coord'][loc]['unit'][0], -11.5); + const loc_y = offset(this.metadata['coord'][loc]['unit'][1], -10.); + const build_loc_x = offset(this.metadata['coord'][loc]['unit'][0], -20.5); + const build_loc_y = offset(this.metadata['coord'][loc]['unit'][1], -20.5); + const symbol = unit_type === 'A' ? ARMY : FLEET; + const build_symbol = 'BuildUnit'; + const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + const symbol_node = document.createElementNS("http://www.w3.org/2000/svg", 'use'); + const build_node = document.createElementNS("http://www.w3.org/2000/svg", 'use'); + symbol_node.setAttribute('x', loc_x); + symbol_node.setAttribute('y', loc_y); + symbol_node.setAttribute('height', this.metadata['symbol_size'][symbol][0]); + symbol_node.setAttribute('width', this.metadata['symbol_size'][symbol][1]); + symbol_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(symbol)); + symbol_node.setAttribute('class', `unit${power_name.toLowerCase()}`); + build_node.setAttribute('x', build_loc_x); + build_node.setAttribute('y', build_loc_y); + build_node.setAttribute('height', this.metadata['symbol_size'][build_symbol][0]); + build_node.setAttribute('width', this.metadata['symbol_size'][build_symbol][1]); + build_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(build_symbol)); + g_node.appendChild(build_node); + g_node.appendChild(symbol_node); + const orderLayer = this.__svg_element_from_id('HighestOrderLayer'); + if (!orderLayer) + throw new Error(`Unable to find svg order layer.`); + orderLayer.appendChild(g_node); + } + + issueDisbandOrder(loc) { + const phaseType = this.game.getPhaseType(); + let loc_x = 0; + let loc_y = 0; + if (phaseType === 'R') { + loc_x = offset(this.metadata['coord'][loc]['unit'][0], -29.); + loc_y = offset(this.metadata['coord'][loc]['unit'][1], -27.5); + } else { + loc_x = offset(this.metadata['coord'][loc]['unit'][0], -16.5); + loc_y = offset(this.metadata['coord'][loc]['unit'][1], -15.); + } + const symbol = 'RemoveUnit'; + const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + const symbol_node = document.createElementNS("http://www.w3.org/2000/svg", 'use'); + symbol_node.setAttribute('x', loc_x); + symbol_node.setAttribute('y', loc_y); + symbol_node.setAttribute('height', this.metadata['symbol_size'][symbol][0]); + symbol_node.setAttribute('width', this.metadata['symbol_size'][symbol][1]); + symbol_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(symbol)); + g_node.appendChild(symbol_node); + const orderLayer = this.__svg_element_from_id('HighestOrderLayer'); + if (!orderLayer) + throw new Error(`Unable to find svg order layer.`); + orderLayer.appendChild(g_node); + } + + clear() { + this.__set_note('', ''); + $(`#${this.__hashed_id('DislodgedUnitLayer')} use`).remove(); + $(`#${this.__hashed_id('UnitLayer')} use`).remove(); + $(`#${this.__hashed_id('SupplyCenterLayer')} use`).remove(); + $(`#${this.__hashed_id('Layer1')} g`).remove(); + $(`#${this.__hashed_id('Layer2')} g`).remove(); + $(`#${this.__hashed_id('HighestOrderLayer')} g`).remove(); + this.__restore_initial_influences(); + } + + render(includeOrders, orders) { + // Setting phase and note. + const nb_centers = []; + for (let power of Object.values(this.game.powers)) { + if (!power.isEliminated()) + nb_centers.push([power.name.substr(0, 3), power.centers.length]); + } + // Sort nb_centers by descending number of centers. + nb_centers.sort((a, b) => { + return -(a[1] - b[1]) || a[0].localeCompare(b[0]); + }); + const nb_centers_per_power = nb_centers.map((couple) => (couple[0] + ': ' + couple[1])).join(' '); + this.__set_current_phase(); + this.__set_note(nb_centers_per_power, this.game.note); + + // Adding units, supply centers, influence and orders. + const scs = new Set(this.mapData.supplyCenters); + for (let power of Object.values(this.game.powers)) { + for (let unit of power.units) + this.__add_unit(unit, power.name, false); + for (let unit of Object.keys(power.retreats)) + this.__add_unit(unit, power.name, true); + for (let center of power.centers) { + this.__add_supply_center(center, power.name); + this.__set_influence(center, power.name); + scs.delete(center); + } + if (!power.isEliminated()) { + for (let loc of power.influence) { + if (!this.mapData.supplyCenters.has(loc)) + this.__set_influence(loc, power.name); + } + } + + if (includeOrders) { + const powerOrders = (orders && orders.hasOwnProperty(power.name) && orders[power.name]) || []; + for (let order of powerOrders) { + const tokens = order.split(/ +/); + if (!tokens || tokens.length < 3) + continue; + const unit_loc = tokens[1]; + if (tokens[2] === 'H') + this.issueHoldOrder(unit_loc, power.name); + else if (tokens[2] === '-') { + const destLoc = tokens[tokens.length - (tokens[tokens.length - 1] === 'VIA' ? 2 : 1)]; + this.issueMoveOrder(unit_loc, destLoc, power.name); + } else if (tokens[2] === 'S') { + const destLoc = tokens[tokens.length - 1]; + if (tokens.includes('-')) { + const srcLoc = tokens[4]; + this.issueSupportMoveOrder(unit_loc, srcLoc, destLoc, power.name); + } else { + this.issueSupportHoldOrder(unit_loc, destLoc, power.name); + } + } else if (tokens[2] === 'C') { + const srcLoc = tokens[4]; + const destLoc = tokens[tokens.length - 1]; + if ((srcLoc !== destLoc) && (tokens.includes('-'))) { + this.issueConvoyOrder(unit_loc, srcLoc, destLoc, power.name); + } + } else if (tokens[2] === 'B') { + this.issueBuildOrder(tokens[0], unit_loc, power.name); + } else if (tokens[2] === 'D') { + this.issueDisbandOrder(unit_loc); + } else if (tokens[2] === 'R') { + const srcLoc = tokens[1]; + const destLoc = tokens[3]; + this.issueMoveOrder(srcLoc, destLoc, power.name); + } else { + throw new Error(`Unknown error to render (${order}).`); + } + } + } + } + // Adding remaining supply centers. + for (let remainingCenter of scs) + this.__add_supply_center(remainingCenter, null); + } + + update(game, mapData, showOrders, orders) { + this.game = game; + this.mapData = mapData; + this.clear(); + this.render(showOrders, orders); + } +} |