From 891fb62a77b9a86f2bc71cc02a82089412982b2f Mon Sep 17 00:00:00 2001 From: notoraptor Date: Thu, 1 Aug 2019 15:53:23 -0400 Subject: Refactored SVG map into a React component - Create link to diplomacy map folder into web/src/diplomacy/maps - Remove old web/src/gui/map folder. - [web] Handle click only on current map. - [web/game] Remove useless `wait` state. - Remove unused nodejs modules. - [web] Use queue to handle game notifications in sequential order. - Make all calls to setState() asynchronous in Page and ContentGame components. - Make sure notifications are handled in the order in which they come. --- diplomacy/web/src/diplomacy/maps | 1 + diplomacy/web/src/diplomacy/utils/queue.js | 71 ++ diplomacy/web/src/gui/components/button.jsx | 16 + diplomacy/web/src/gui/components/delete_button.jsx | 16 + diplomacy/web/src/gui/components/help.jsx | 16 + diplomacy/web/src/gui/components/navigation.jsx | 16 + diplomacy/web/src/gui/components/page_context.jsx | 16 + .../src/gui/components/power_orders_actions_bar.js | 16 + diplomacy/web/src/gui/components/tab.jsx | 16 + diplomacy/web/src/gui/forms/select_via_form.jsx | 1 + diplomacy/web/src/gui/map/dom_order_builder.js | 278 ----- diplomacy/web/src/gui/map/dom_past_map.js | 114 -- diplomacy/web/src/gui/map/map.jsx | 94 -- diplomacy/web/src/gui/map/renderer.js | 615 ---------- .../web/src/gui/maps/standard/SvgStandard.css | 106 ++ diplomacy/web/src/gui/maps/standard/SvgStandard.js | 743 ++++++++++++ .../web/src/gui/maps/standard/SvgStandardExtra.js | 1279 ++++++++++++++++++++ diplomacy/web/src/gui/maps/standard/build.js | 53 + diplomacy/web/src/gui/maps/standard/common.js | 75 ++ diplomacy/web/src/gui/maps/standard/convoy.js | 98 ++ diplomacy/web/src/gui/maps/standard/disband.js | 51 + diplomacy/web/src/gui/maps/standard/hold.js | 45 + diplomacy/web/src/gui/maps/standard/move.js | 69 ++ .../web/src/gui/maps/standard/supplyCenter.js | 40 + diplomacy/web/src/gui/maps/standard/supportHold.js | 73 ++ diplomacy/web/src/gui/maps/standard/supportMove.js | 57 + diplomacy/web/src/gui/maps/standard/unit.js | 45 + diplomacy/web/src/gui/pages/content_game.jsx | 249 ++-- diplomacy/web/src/gui/pages/content_games.jsx | 5 - diplomacy/web/src/gui/pages/page.jsx | 84 +- diplomacy/web/src/gui/utils/load_game_from_disk.js | 158 +-- diplomacy/web/src/gui/utils/province.js | 18 + diplomacy/web/src/gui/utils/saveGameToDisk.js | 16 + .../gui/wizards/gameCreation/gameCreationWizard.js | 16 + .../web/src/gui/wizards/gameCreation/mapList.js | 16 + .../src/gui/wizards/gameCreation/panelChooseMap.js | 18 +- .../gui/wizards/gameCreation/panelChoosePlayers.js | 16 + .../gui/wizards/gameCreation/panelChoosePower.js | 16 + .../wizards/gameCreation/panelChooseSettings.js | 16 + .../web/src/gui/wizards/gameCreation/panelList.js | 16 + diplomacy/web/src/maps | 1 - diplomacy/web/src/standard.svg | 1 - 42 files changed, 3339 insertions(+), 1327 deletions(-) create mode 120000 diplomacy/web/src/diplomacy/maps create mode 100644 diplomacy/web/src/diplomacy/utils/queue.js delete mode 100644 diplomacy/web/src/gui/map/dom_order_builder.js delete mode 100644 diplomacy/web/src/gui/map/dom_past_map.js delete mode 100644 diplomacy/web/src/gui/map/map.jsx delete mode 100644 diplomacy/web/src/gui/map/renderer.js create mode 100644 diplomacy/web/src/gui/maps/standard/SvgStandard.css create mode 100644 diplomacy/web/src/gui/maps/standard/SvgStandard.js create mode 100644 diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js create mode 100644 diplomacy/web/src/gui/maps/standard/build.js create mode 100644 diplomacy/web/src/gui/maps/standard/common.js create mode 100644 diplomacy/web/src/gui/maps/standard/convoy.js create mode 100644 diplomacy/web/src/gui/maps/standard/disband.js create mode 100644 diplomacy/web/src/gui/maps/standard/hold.js create mode 100644 diplomacy/web/src/gui/maps/standard/move.js create mode 100644 diplomacy/web/src/gui/maps/standard/supplyCenter.js create mode 100644 diplomacy/web/src/gui/maps/standard/supportHold.js create mode 100644 diplomacy/web/src/gui/maps/standard/supportMove.js create mode 100644 diplomacy/web/src/gui/maps/standard/unit.js delete mode 120000 diplomacy/web/src/maps delete mode 120000 diplomacy/web/src/standard.svg (limited to 'diplomacy/web/src') diff --git a/diplomacy/web/src/diplomacy/maps b/diplomacy/web/src/diplomacy/maps new file mode 120000 index 0000000..e8fa922 --- /dev/null +++ b/diplomacy/web/src/diplomacy/maps @@ -0,0 +1 @@ +../../../maps/ \ No newline at end of file diff --git a/diplomacy/web/src/diplomacy/utils/queue.js b/diplomacy/web/src/diplomacy/utils/queue.js new file mode 100644 index 0000000..a955ebb --- /dev/null +++ b/diplomacy/web/src/diplomacy/utils/queue.js @@ -0,0 +1,71 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import {Future} from "./future"; + +export class Queue { + constructor() { + this.future = new Future(); + this.queue = []; + this.append = this.append.bind(this); + this.get = this.get.bind(this); + this.consume = this.consume.bind(this); + } + + append(value) { + this.queue.push(value); + if (this.queue.length - 1 === 0) { + const previousFuture = this.future; + previousFuture.setResult(null); + this.future = new Future(); + } + } + + __next_value() { + return this.queue.length ? this.queue.shift() : null; + } + + get() { + return new Promise(resolve => { + if (this.queue.length) { + resolve(this.__next_value()); + } else { + this.future.promise().then(() => resolve(this.__next_value())); + } + }); + } + + consume(valueConsumer) { + const recursiveConsumer = (value) => { + if (value !== null) { + valueConsumer(value); + this.get().then(recursiveConsumer); + } + }; + this.get().then(recursiveConsumer); + } + + consumeAsync(valueConsumer) { + const recursiveConsumer = (value) => { + if (value !== null) { + valueConsumer(value) + .then(() => this.get()) + .then(recursiveConsumer); + } + }; + this.get().then(recursiveConsumer); + } +} diff --git a/diplomacy/web/src/gui/components/button.jsx b/diplomacy/web/src/gui/components/button.jsx index 0d5dadd..6852ee1 100644 --- a/diplomacy/web/src/gui/components/button.jsx +++ b/diplomacy/web/src/gui/components/button.jsx @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import PropTypes from "prop-types"; diff --git a/diplomacy/web/src/gui/components/delete_button.jsx b/diplomacy/web/src/gui/components/delete_button.jsx index 59141fd..82e40df 100644 --- a/diplomacy/web/src/gui/components/delete_button.jsx +++ b/diplomacy/web/src/gui/components/delete_button.jsx @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import {Button} from "./button"; import PropTypes from "prop-types"; diff --git a/diplomacy/web/src/gui/components/help.jsx b/diplomacy/web/src/gui/components/help.jsx index f3f469f..b46d41a 100644 --- a/diplomacy/web/src/gui/components/help.jsx +++ b/diplomacy/web/src/gui/components/help.jsx @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import PropTypes from 'prop-types'; import {FancyBox} from "./fancyBox"; diff --git a/diplomacy/web/src/gui/components/navigation.jsx b/diplomacy/web/src/gui/components/navigation.jsx index 051f8ea..ba2bf28 100644 --- a/diplomacy/web/src/gui/components/navigation.jsx +++ b/diplomacy/web/src/gui/components/navigation.jsx @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import Octicon, {Person} from "@primer/octicons-react"; import PropTypes from "prop-types"; diff --git a/diplomacy/web/src/gui/components/page_context.jsx b/diplomacy/web/src/gui/components/page_context.jsx index cfb8252..936891f 100644 --- a/diplomacy/web/src/gui/components/page_context.jsx +++ b/diplomacy/web/src/gui/components/page_context.jsx @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; export const PageContext = React.createContext(null); diff --git a/diplomacy/web/src/gui/components/power_orders_actions_bar.js b/diplomacy/web/src/gui/components/power_orders_actions_bar.js index 3ffef63..2c93133 100644 --- a/diplomacy/web/src/gui/components/power_orders_actions_bar.js +++ b/diplomacy/web/src/gui/components/power_orders_actions_bar.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from 'react'; import {Button} from "./button"; import {Bar} from "./layouts"; diff --git a/diplomacy/web/src/gui/components/tab.jsx b/diplomacy/web/src/gui/components/tab.jsx index f1ad4aa..cdc8097 100644 --- a/diplomacy/web/src/gui/components/tab.jsx +++ b/diplomacy/web/src/gui/components/tab.jsx @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import PropTypes from "prop-types"; diff --git a/diplomacy/web/src/gui/forms/select_via_form.jsx b/diplomacy/web/src/gui/forms/select_via_form.jsx index c0f13a5..c97d392 100644 --- a/diplomacy/web/src/gui/forms/select_via_form.jsx +++ b/diplomacy/web/src/gui/forms/select_via_form.jsx @@ -18,6 +18,7 @@ import React from "react"; import PropTypes from "prop-types"; import {Button} from "../components/button"; import {FancyBox} from "../components/fancyBox"; + const HotKey = require('react-shortcut'); export class SelectViaForm extends React.Component { diff --git a/diplomacy/web/src/gui/map/dom_order_builder.js b/diplomacy/web/src/gui/map/dom_order_builder.js deleted file mode 100644 index 14ba743..0000000 --- a/diplomacy/web/src/gui/map/dom_order_builder.js +++ /dev/null @@ -1,278 +0,0 @@ -// ============================================================================== -// 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 . -// ============================================================================== -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 deleted file mode 100644 index eb44616..0000000 --- a/diplomacy/web/src/gui/map/dom_past_map.js +++ /dev/null @@ -1,114 +0,0 @@ -// ============================================================================== -// 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 . -// ============================================================================== -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 deleted file mode 100644 index 1130563..0000000 --- a/diplomacy/web/src/gui/map/map.jsx +++ /dev/null @@ -1,94 +0,0 @@ -// ============================================================================== -// 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 . -// ============================================================================== -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) =>
{children}
); - return this.props.onError(err.message)}>Game map; - } -} - -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 deleted file mode 100644 index e2586af..0000000 --- a/diplomacy/web/src/gui/map/renderer.js +++ /dev/null @@ -1,615 +0,0 @@ -// ============================================================================== -// 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 . -// ============================================================================== -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); - } -} diff --git a/diplomacy/web/src/gui/maps/standard/SvgStandard.css b/diplomacy/web/src/gui/maps/standard/SvgStandard.css new file mode 100644 index 0000000..3aeebaa --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/SvgStandard.css @@ -0,0 +1,106 @@ +/* text */ + svg { font-size: 100% } + .titletext {text-anchor:middle; stroke-width:0.3; font-family:sans-serif; font-size:0.7em; stroke:black; fill:black;} + .provtext {text-anchor:middle; stroke-width:0.3; font-family:sans-serif; font-size:0.7em; stroke:black; fill:black;} + .labeltext {stroke-width:0.1; stroke:black; fill:black;} + .unordered {fill:red; stroke:black; stroke-width:1; fill-opacity:0.90;} + .labeltext24 {text-anchor:middle; stroke-width:0.1; stroke:black; fill:black; font-family:serif,sans-serif; font-style:italic; font-size:1.4em;} + .labeltext18 {text-anchor:middle; stroke-width:0.1; stroke:black; fill:black; font-family:serif,sans-serif; font-style:italic; font-size:1.1em;} + .fulllabeltext {font-family:serif,sans-serif; font-style:italic; font-size:1.2em; fill:black; stroke:black;} + .currentnotetext {font-family:serif,sans-serif; font-size:1.5em; fill:black; stroke:black;} + .currentnoterect {fill:#c5dfea;} + .currentphasetext {font-family:serif,sans-serif; font-size:2.5em; fill:black; stroke:black;} + + .labeltext24 text {cursor:default;} + + /* NB: this style is not yet used. */ + .labeltext24 text.allowed { + font-weight:bold; + fill:blue; + font-size: 1.5em; + stroke: red; + stroke-width:2; + text-decoration: underline; + background-color: green; + } + + /* map and object features */ + + .seapoly {stroke:#000000; stroke-width:1; fill:#B5DEF8} + .dashline {stroke:darkslateblue; stroke-width:3; stroke-linecap:round; stroke-dasharray:5,6;} + .impassable {fill:#353433; stroke:#000000; stroke-width:1} + .sealine {stroke:#B5DEFF; stroke-width:3;} + + /* invisible click rects fill:none does not work */ + + .invisibleContent {stroke:#000000; fill:#000000; fill-opacity:0.0; opacity:0.0} + + /* default region coloring, by power */ + + .provinceRed {fill:url(#patternRed)} + .provinceBrown {fill:url(#patternBrown)} + .provinceGreen {fill:url(#patternGreen)} + .provinceBlack {fill:url(#patternBlack)} + .provinceBlue {fill:url(#patternBlue)} + + .nopower {fill:antiquewhite; stroke:#000000; stroke-width:1} + .water {fill:#c5dfea; stroke:#000000; stroke-width:1} + + .neutral {fill:lightgray; stroke:#000000; stroke-width:1} + + .austria {fill:#c48f85; stroke:#000000; stroke-width:1} + .england {fill:darkviolet; stroke:#000000; stroke-width:1} + .france {fill:royalblue; stroke:#000000; stroke-width:1} + .germany {fill:#a08a75; stroke:#000000; stroke-width:1} + .italy {fill:forestgreen; stroke:#000000; stroke-width:1} + .russia {fill:#757d91; stroke:#000000; stroke-width:1} + .turkey {fill:#b9a61c; stroke:#000000; stroke-width:1} + + /* unit colors, by power note that underscores are not supported */ + + .unitaustria {fill:red; fill-opacity:0.85} + .unitengland {fill:mediumpurple; fill-opacity:0.85} + .unitfrance {fill:deepskyblue; fill-opacity:0.85} + .unitgermany {fill:dimgray; fill-opacity:0.85} + .unititaly {fill:olive; fill-opacity:0.85} + .unitrussia {fill:white; fill-opacity:1.0} + .unitturkey {fill:yellow; fill-opacity:0.85} + + /* supply center styles */ + + .scnopower {fill:black; stroke:black;} + .scaustria {fill:black; stroke:black;} + .scengland {fill:black; stroke:black;} + .scfrance {fill:black; stroke:black;} + .scgermany {fill:black; stroke:black;} + .scitaly {fill:black; stroke:black;} + .scrussia {fill:black; stroke:black;} + .scturkey {fill:black; stroke:black;} + + /* order drawing styles, stroke and fill colors should not be specified */ + + .defaultorder {stroke-width:6; fill:none;} + .supportorder {stroke-width:6; fill:none; stroke-dasharray:5,5;} + .convoyorder {stroke-dasharray:15,5; stroke-width:6; fill:none;} + + .shadoworder {stroke-width:10; fill:none; stroke:black;} + .shadowdash {stroke-width:10; fill:none; stroke:black; opacity:0.45;} + + .varwidthorder {fill:none;} + .varwidthshadow {fill:none; stroke:black;} + + /* Symbol private styles. Always start with "sym" to avoid name collisions! */ + + .symBuildShadow {fill:none;stroke:black;opacity:0.5;stroke-width:7;} + .symBuild {stroke:yellow;stroke-width:7;fill:none;} + .symRemove {stroke:red;stroke-width:1;fill:none;} + + .symShadow {stroke:black;fill:black;stroke-width:1;opacity:0.40;} + .symDislodgedShadow {stroke:red;fill:red;stroke-width:1;opacity:0.50;} + .symDislodgedBorder {stroke:red;stroke-width:3%;} + + .symDarkener {fill:black;opacity:0.45;fill-opacity:0.45;} + .symCenterHub {fill:black; stroke:black; opacity:0.60; stroke-width:0.5px;} + .symBorder {stroke:black;stroke-width:3%;} + .symThinBorder {stroke:black;stroke-width:0.4;} + .symSilhouette {stroke:black;fill:black;stroke-width:1;} diff --git a/diplomacy/web/src/gui/maps/standard/SvgStandard.js b/diplomacy/web/src/gui/maps/standard/SvgStandard.js new file mode 100644 index 0000000..87c6217 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/SvgStandard.js @@ -0,0 +1,743 @@ +// ============================================================================== +// 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 . +// ============================================================================== +/** Generated using svg_to_react.py with parameters: +Namespace(actionable=['MouseLayer'], input='src/diplomacy/maps/svg/standard.svg', name='SvgStandard', output='src/gui/maps/', remove=None) +**/ +import React from 'react'; +import PropTypes from 'prop-types'; +import "./SvgStandard.css"; +import {Game} from "../../../diplomacy/engine/game"; +import {MapData} from "../../utils/map_data"; +import {Unit} from "./unit"; +import {SupplyCenter} from "./supplyCenter"; +import {Hold} from "./hold"; +import {Move} from "./move"; +import {SupportMove} from "./supportMove"; +import {SupportHold} from "./supportHold"; +import {Convoy} from "./convoy"; +import {Build} from "./build"; +import {Disband} from "./disband"; +import {UTILS} from "../../../diplomacy/utils/utils"; +import {Diplog} from "../../../diplomacy/utils/diplog"; +import {extendOrderBuilding} from "../../utils/order_building"; + +function setInfluence(classes, mapData, loc, power_name) { + loc = loc.toUpperCase().substr(0, 3); + if (!['LAND', 'COAST'].includes(mapData.getProvince(loc).type)) + return; + const id = '_' + loc.toLowerCase(); + if (!classes.hasOwnProperty(id)) + throw new Error(`Unable to find SVG path for loc ${id}`); + classes[id] = power_name ? power_name.toLowerCase() : 'nopower'; +} + +function getClickedID(event) { + let node = event.target; + if (!node.id && node.parentNode.id && node.parentNode.tagName === 'g') + node = node.parentNode; + let id = node.id; + return id ? id.substr(0, 3) : null; +} + +function parseLocation(txt) { + if (txt.length > 2 && txt[1] === ' ' && ['A', 'F'].includes(txt[0])) + return txt.substr(2); + return txt; +} + +export class SvgStandard extends React.Component { + constructor(props) { + super(props); + this.onClick = this.onClick.bind(this); + this.onHover = this.onHover.bind(this); + } + onClick(event) { + if (this.props.orderBuilding) + return this.handleClickedID(getClickedID(event)); + } + onHover(event) { + return this.handleHoverID(getClickedID(event)); + } + handleClickedID(id) { + const orderBuilding = this.props.orderBuilding; + if (!orderBuilding.builder) + return this.props.onError('No orderable locations.'); + const province = this.props.mapData.getProvince(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.props.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.props.onError(error); + } + } + if (!validLocations.length) + return this.props.onError('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.props.onSelectLocation) { + return this.props.onSelectLocation(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.props.game.ordersTree, moveOrderPath); + if (moveTypes !== null) { + if (moveTypes.length === 2 && this.props.onSelectVia) { + // This move can be done either regularly or VIA a fleet. Let user choose. + return this.props.onSelectVia(validLocations[0], orderBuilding.power, orderBuilding.path); + } else { + orderBuildingType = moveTypes[0]; + } + } + } + extendOrderBuilding( + orderBuilding.power, orderBuildingType, orderBuilding.path, validLocations[0], + this.props.onOrderBuilding, this.props.onOrderBuilt, this.props.onError + ); + } + handleHoverID(id) { + if (this.props.onHover) { + const province = this.props.mapData.getProvince(id); + if (province) { + this.props.onHover(province.name, this.getRelatedOrders(province.name)); + } + } + } + getRelatedOrders(name) { + const orders = []; + if (this.props.orders) { + for (let powerOrders of Object.values(this.props.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; + } + getNeighbors(extraLocation) { + const selectedPath = [this.props.orderBuilding.type].concat(this.props.orderBuilding.path); + if (extraLocation) + selectedPath.push(extraLocation); + const possibleNeighbors = UTILS.javascript.getTreeValue(this.props.game.ordersTree, selectedPath); + const neighbors = possibleNeighbors ? possibleNeighbors.map(neighbor => parseLocation(neighbor)) : []; + return neighbors.length ? neighbors: null; + } + render() { + const classes = {"_ank":"nopower","_arm":"nopower","_con":"nopower","_mos":"nopower","_sev":"nopower","_stp":"nopower","_syr":"nopower","_ukr":"nopower","_lvn":"nopower","_war":"nopower","_pru":"nopower","_sil":"nopower","_ber":"nopower","_kie":"nopower","_ruh":"nopower","_mun":"nopower","_rum":"nopower","_bul":"nopower","_gre":"nopower","_smy":"nopower","_alb":"nopower","_ser":"nopower","_bud":"nopower","_gal":"nopower","_vie":"nopower","_boh":"nopower","_tyr":"nopower","_tri":"nopower","_fin":"nopower","_swe":"nopower","_nwy":"nopower","_den":"nopower","_hol":"nopower","_bel":"nopower","_swi":"impassable","_ven":"nopower","_pie":"nopower","_tus":"nopower","_rom":"nopower","_apu":"nopower","_nap":"nopower","_bur":"nopower","_mar":"nopower","_gas":"nopower","_pic":"nopower","_par":"nopower","_bre":"nopower","_spa":"nopower","_por":"nopower","_naf":"nopower","_tun":"nopower","_lon":"nopower","_wal":"nopower","_lvp":"nopower","_yor":"nopower","_edi":"nopower","_cly":"nopower","unplayable":"neutral","unplayable_water":"water","_nat":"water","_nrg":"water","_bar":"water","_bot":"water","_bal":"water","denmark_water":"water","_ska":"water","_hel":"water","_nth":"water","_eng":"water","_iri":"water","_mid":"water","_wes":"water","_gol":"water","_tyn":"water","_adr":"water","_ion":"water","_aeg":"water","_eas":"water","constantinople_water":"water","_bla":"water","BriefLabelLayer":"labeltext24","CurrentNote":"currentnotetext","CurrentNote2":"currentnotetext","CurrentPhase":"currentphasetext","MouseLayer":"invisibleContent"}; + const game = this.props.game; + const mapData = this.props.mapData; + const orders = this.props.orders; + + //// Current phase. + const current_phase = (game.phase[0] === '?' || game.phase === 'COMPLETED') ? 'FINAL' : game.phase; + + //// Notes. + const nb_centers = []; + for (let power of Object.values(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(' '); + const note = game.note; + + //// Adding units, supply centers, influence and orders. + const scs = new Set(mapData.supplyCenters); + const renderedUnits = []; + const renderedDislodgedUnits = []; + const renderedSupplyCenters = []; + const renderedOrders = []; + const renderedOrders2 = []; + const renderedHighestOrders = []; + for (let power of Object.values(game.powers)) { + for (let unit of power.units) { + renderedUnits.push(); + } + for (let unit of Object.keys(power.retreats)) { + renderedDislodgedUnits.push(); + } + for (let center of power.centers) { + renderedSupplyCenters.push(); + setInfluence(classes, mapData, center, power.name); + scs.delete(center); + } + if (!power.isEliminated()) { + for (let loc of power.influence) { + if (!mapData.supplyCenters.has(loc)) + setInfluence(classes, mapData, loc, power.name); + } + } + + if (orders) { + 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') { + renderedOrders.push(); + } else if (tokens[2] === '-') { + const destLoc = tokens[tokens.length - (tokens[tokens.length - 1] === 'VIA' ? 2 : 1)]; + renderedOrders.push(); + } else if (tokens[2] === 'S') { + const destLoc = tokens[tokens.length - 1]; + if (tokens.includes('-')) { + const srcLoc = tokens[4]; + renderedOrders2.push(); + } else { + renderedOrders2.push(); + } + } else if (tokens[2] === 'C') { + const srcLoc = tokens[4]; + const destLoc = tokens[tokens.length - 1]; + if ((srcLoc !== destLoc) && (tokens.includes('-'))) { + renderedOrders2.push(); + } + } else if (tokens[2] === 'B') { + renderedHighestOrders.push(); + } else if (tokens[2] === 'D') { + renderedHighestOrders.push(); + } else if (tokens[2] === 'R') { + const srcLoc = tokens[1]; + const destLoc = tokens[3]; + renderedOrders.push(); + } else { + throw new Error(`Unknown error to render (${order}).`); + } + } + } + } + // Adding remaining supply centers. + for (let remainingCenter of scs) { + renderedSupplyCenters.push(); + } + + if (this.props.orderBuilding && this.props.orderBuilding.path.length) { + const clicked = parseLocation(this.props.orderBuilding.path[0]); + const province = this.props.mapData.getProvince(clicked); + if (!province) + throw new Error(('Unknown clicked province ' + clicked)); + const clickedID = province.getID(classes); + if (!clicked) + throw new Error(`Unknown path (${clickedID}) for province (${clicked}).`); + classes[clickedID] = 'provinceRed'; + const neighbors = this.getNeighbors(); + if (neighbors) { + for (let neighbor of neighbors) { + const neighborProvince = this.props.mapData.getProvince(neighbor); + if (!neighborProvince) + throw new Error('Unknown neighbor province ' + neighbor); + const neighborID = neighborProvince.getID(classes); + if (!neighborID) + throw new Error(`Unknown neoghbor path (${neighborID}) for province (${neighbor}).`); + classes[neighborID] = neighborProvince.isWater() ? 'provinceBlue' : 'provinceGreen'; + } + } + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {renderedSupplyCenters} + + + + {renderedOrders2} + + + {renderedOrders} + + + + {renderedUnits} + + + {renderedDislodgedUnits} + + + {renderedHighestOrders} + + + SWI + ADR + AEG + ALB + ANK + APU + ARM + BAL + BAR + BEL + BER + BLA + BOH + BRE + BUD + BUL + BUR + CLY + CON + DEN + EAS + EDI + ENG + FIN + GAL + GAS + GRE + BOT + LYO + HEL + HOL + ION + IRI + KIE + LON + LVN + LVP + MAR + MAO + MOS + MUN + NAF + NAP + NAO + NTH + NWY + NWG + PAR + PIC + PIE + POR + PRU + ROM + RUH + RUM + SER + SEV + SIL + SKA + SMY + SPA + STP + SWE + SYR + TRI + TUN + TUS + TYR + TYS + UKR + VEN + VIE + WAL + WAR + WES + YOR + + + + {nb_centers_per_power ? nb_centers_per_power : ''} + + + {note ? note : ''} + + + {current_phase} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} +SvgStandard.propTypes = { + game: PropTypes.instanceOf(Game).isRequired, + mapData: PropTypes.instanceOf(MapData).isRequired, + orders: PropTypes.object, + onHover: PropTypes.func, + onError: PropTypes.func.isRequired, + onSelectLocation: PropTypes.func, + onSelectVia: PropTypes.func, + onOrderBuilding: PropTypes.func, + onOrderBuilt: PropTypes.func, + orderBuilding: PropTypes.object +}; diff --git a/diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js b/diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js new file mode 100644 index 0000000..be4e02e --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/SvgStandardExtra.js @@ -0,0 +1,1279 @@ +// ============================================================================== +// 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 . +// ============================================================================== +export const SvgStandardExtra = { + "jdipNS:DISPLAY": { + "jdipNS:ZOOM": { + "min": "5", + "max": "2200", + "factor": "1.2" + }, + "jdipNS:LABELS": { + "brief": "true", + "full": "true" + } + }, + "jdipNS:ORDERDRAWING": { + "jdipNS:POWERCOLORS": { + "jdipNS:POWERCOLOR": [ + { + "power": "austria", + "color": "#c48f85" + }, + { + "power": "england", + "color": "darkviolet" + }, + { + "power": "france", + "color": "royalblue" + }, + { + "power": "germany", + "color": "#a08a75" + }, + { + "power": "italy", + "color": "forestgreen" + }, + { + "power": "russia", + "color": "#757d91" + }, + { + "power": "turkey", + "color": "#b9a61c" + } + ] + }, + "jdipNS:SYMBOLSIZE": [ + { + "name": "Fleet", + "width": "40", + "height": "40" + }, + { + "name": "Army", + "width": "40", + "height": "40" + }, + { + "name": "Wing", + "width": "40", + "height": "40" + }, + { + "name": "DislodgedFleet", + "width": "40", + "height": "40" + }, + { + "name": "DislodgedArmy", + "width": "40", + "height": "40" + }, + { + "name": "DislodgedWing", + "width": "40", + "height": "40" + }, + { + "name": "FailedOrder", + "width": "30", + "height": "30" + }, + { + "name": "SupplyCenter", + "width": "20", + "height": "20" + }, + { + "name": "BuildUnit", + "width": "60", + "height": "60" + }, + { + "name": "RemoveUnit", + "width": "50", + "height": "50" + }, + { + "name": "WaivedBuild", + "width": "40", + "height": "40" + } + ], + "jdipNS:BUILD": { + "deltaRadius": "0" + }, + "jdipNS:REMOVE": { + "deltaRadius": "5" + }, + "jdipNS:DISBAND": { + "deltaRadius": "5" + }, + "jdipNS:WAIVE": { + "deltaRadius": "0" + }, + "jdipNS:HOLD": { + "deltaRadius": "5", + "strokeCSSStyle": "varwidthorder", + "highlightOffset": "0", + "highlightCSSClass": "varwidthshadow", + "widths": "6,9,12,18", + "shadowWidths": "10,15,20,25" + }, + "jdipNS:MOVE": { + "deltaRadius": "5", + "strokeCSSStyle": "varwidthorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "varwidthshadow", + "widths": "6,9,12,18", + "shadowWidths": "10,15,20,25" + }, + "jdipNS:RETREAT": { + "deltaRadius": "5", + "strokeCSSStyle": "defaultorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "shadoworder" + }, + "jdipNS:SUPPORT": { + "deltaRadius": "10", + "strokeCSSStyle": "supportorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "shadowdash" + }, + "jdipNS:CONVOY": { + "deltaRadius": "10", + "strokeCSSStyle": "convoyorder", + "markerID": "arrow", + "highlightOffset": "0", + "highlightCSSClass": "shadowdash" + } + }, + "jdipNS:PROVINCE_DATA": { + "jdipNS:PROVINCE": [ + { + "jdipNS:UNIT": { + "x": "805", + "y": "1058" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "792.5", + "y": "1045.5" + }, + "name": "adr" + }, + { + "jdipNS:UNIT": { + "x": "1055", + "y": "1240" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1042.5", + "y": "1227.5" + }, + "name": "aeg" + }, + { + "jdipNS:UNIT": { + "x": "918", + "y": "1123" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "905.5", + "y": "1110.5" + }, + "name": "alb" + }, + { + "jdipNS:UNIT": { + "x": "1313", + "y": "1120" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1300.5", + "y": "1107.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1271", + "y": "1144" + }, + "name": "ank" + }, + { + "jdipNS:UNIT": { + "x": "803", + "y": "1116" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "790.5", + "y": "1103.5" + }, + "name": "apu" + }, + { + "jdipNS:UNIT": { + "x": "1496", + "y": "1100" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1483.5", + "y": "1087.5" + }, + "name": "arm" + }, + { + "jdipNS:UNIT": { + "x": "890", + "y": "620" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "877.5", + "y": "607.5" + }, + "name": "bal" + }, + { + "jdipNS:UNIT": { + "x": "1174", + "y": "83" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1161.5", + "y": "70.5" + }, + "name": "bar" + }, + { + "jdipNS:UNIT": { + "x": "573", + "y": "763" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "560.5", + "y": "750.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "575", + "y": "755" + }, + "name": "bel" + }, + { + "jdipNS:UNIT": { + "x": "783", + "y": "700" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "770.5", + "y": "687.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "783", + "y": "736" + }, + "name": "ber" + }, + { + "jdipNS:UNIT": { + "x": "1245", + "y": "1010" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1232.5", + "y": "997.5" + }, + "name": "bla" + }, + { + "jdipNS:UNIT": { + "x": "818", + "y": "824" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "805.5", + "y": "811.5" + }, + "name": "boh" + }, + { + "jdipNS:UNIT": { + "x": "953", + "y": "495" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "940.5", + "y": "482.5" + }, + "name": "bot" + }, + { + "jdipNS:UNIT": { + "x": "416", + "y": "829" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "403.5", + "y": "816.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "369", + "y": "799" + }, + "name": "bre" + }, + { + "jdipNS:UNIT": { + "x": "962", + "y": "914" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "949.5", + "y": "901.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "900", + "y": "916" + }, + "name": "bud" + }, + { + "jdipNS:UNIT": { + "x": "1060", + "y": "1078" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1047.5", + "y": "1065.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1013", + "y": "1078" + }, + "name": "bul" + }, + { + "jdipNS:UNIT": { + "x": "571", + "y": "881" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "558.5", + "y": "868.5" + }, + "name": "bur" + }, + { + "jdipNS:UNIT": { + "x": "448", + "y": "502" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "435.5", + "y": "489.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "446", + "y": "480" + }, + "name": "cly" + }, + { + "jdipNS:UNIT": { + "x": "1157", + "y": "1147" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1144.5", + "y": "1134.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1141", + "y": "1119" + }, + "name": "con" + }, + { + "jdipNS:UNIT": { + "x": "715", + "y": "597" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "702.5", + "y": "584.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "767", + "y": "626" + }, + "name": "den" + }, + { + "jdipNS:UNIT": { + "x": "1230", + "y": "1321" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1217.5", + "y": "1308.5" + }, + "name": "eas" + }, + { + "jdipNS:UNIT": { + "x": "485", + "y": "524" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "472.5", + "y": "511.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "489", + "y": "565" + }, + "name": "edi" + }, + { + "jdipNS:UNIT": { + "x": "406", + "y": "761" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "393.5", + "y": "748.5" + }, + "name": "eng" + }, + { + "jdipNS:UNIT": { + "x": "1000", + "y": "390" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "987.5", + "y": "377.5" + }, + "name": "fin" + }, + { + "jdipNS:UNIT": { + "x": "1011", + "y": "841" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "998.5", + "y": "828.5" + }, + "name": "gal" + }, + { + "jdipNS:UNIT": { + "x": "434", + "y": "922" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "421.5", + "y": "909.5" + }, + "name": "gas" + }, + { + "jdipNS:UNIT": { + "x": "556", + "y": "1060" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "543.5", + "y": "1047.5" + }, + "name": "gol" + }, + { + "jdipNS:UNIT": { + "x": "978", + "y": "1200" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "965.5", + "y": "1187.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1023", + "y": "1237" + }, + "name": "gre" + }, + { + "jdipNS:UNIT": { + "x": "663", + "y": "641" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "650.5", + "y": "628.5" + }, + "name": "hel" + }, + { + "jdipNS:UNIT": { + "x": "608", + "y": "721" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "595.5", + "y": "708.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "630", + "y": "692" + }, + "name": "hol" + }, + { + "jdipNS:UNIT": { + "x": "858", + "y": "1296" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "845.5", + "y": "1283.5" + }, + "name": "ion" + }, + { + "jdipNS:UNIT": { + "x": "347", + "y": "671" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "334.5", + "y": "658.5" + }, + "name": "iri" + }, + { + "jdipNS:UNIT": { + "x": "695", + "y": "711" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "682.5", + "y": "698.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "724", + "y": "685" + }, + "name": "kie" + }, + { + "jdipNS:UNIT": { + "x": "500", + "y": "685" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "487.5", + "y": "672.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "489", + "y": "717" + }, + "name": "lon" + }, + { + "jdipNS:UNIT": { + "x": "1037", + "y": "577" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1024.5", + "y": "564.5" + }, + "name": "lvn" + }, + { + "jdipNS:UNIT": { + "x": "462", + "y": "586" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "449.5", + "y": "573.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "458", + "y": "638" + }, + "name": "lvp" + }, + { + "jdipNS:UNIT": { + "x": "525.8", + "y": "1065" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "513.3", + "y": "1052.5" + }, + "name": "lyo" + }, + { + "jdipNS:UNIT": { + "x": "536", + "y": "985" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "523.5", + "y": "972.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "562", + "y": "1018" + }, + "name": "mar" + }, + { + "jdipNS:UNIT": { + "x": "153.3", + "y": "845.3" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "140.8", + "y": "832.8" + }, + "name": "mao" + }, + { + "jdipNS:UNIT": { + "x": "126", + "y": "902" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "113.5", + "y": "889.5" + }, + "name": "mid" + }, + { + "jdipNS:UNIT": { + "x": "1212", + "y": "600" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1199.5", + "y": "587.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1267", + "y": "584" + }, + "name": "mos" + }, + { + "jdipNS:UNIT": { + "x": "705", + "y": "838" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "692.5", + "y": "825.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "730", + "y": "878" + }, + "name": "mun" + }, + { + "jdipNS:UNIT": { + "x": "337", + "y": "1291" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "324.5", + "y": "1278.5" + }, + "name": "naf" + }, + { + "jdipNS:UNIT": { + "x": "191.6", + "y": "298.2" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "179.1", + "y": "285.7" + }, + "name": "nao" + }, + { + "jdipNS:UNIT": { + "x": "818", + "y": "1180" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "815.5", + "y": "1167.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "778", + "y": "1143" + }, + "name": "nap" + }, + { + "jdipNS:UNIT": { + "x": "238", + "y": "427" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "225.5", + "y": "414.5" + }, + "name": "nat" + }, + { + "jdipNS:UNIT": { + "x": "605", + "y": "250" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "592.5", + "y": "237.5" + }, + "name": "nrg" + }, + { + "jdipNS:UNIT": { + "x": "565", + "y": "570" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "552.5", + "y": "557.5" + }, + "name": "nth" + }, + { + "jdipNS:UNIT": { + "x": "664.2", + "y": "191.8" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "651.7", + "y": "179.3" + }, + "name": "nwg" + }, + { + "jdipNS:UNIT": { + "x": "715", + "y": "420" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "702.5", + "y": "407.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "761", + "y": "463" + }, + "name": "nwy" + }, + { + "jdipNS:UNIT": { + "x": "500", + "y": "855" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "487.5", + "y": "842.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "518", + "y": "819" + }, + "name": "par" + }, + { + "jdipNS:UNIT": { + "x": "535", + "y": "791" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "522.5", + "y": "778.5" + }, + "name": "pic" + }, + { + "jdipNS:UNIT": { + "x": "642", + "y": "978" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "629.5", + "y": "965.5" + }, + "name": "pie" + }, + { + "jdipNS:UNIT": { + "x": "193", + "y": "1023" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "180.5", + "y": "1010.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "151", + "y": "1060" + }, + "name": "por" + }, + { + "jdipNS:UNIT": { + "x": "877", + "y": "700" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "864.5", + "y": "687.5" + }, + "name": "pru" + }, + { + "jdipNS:UNIT": { + "x": "743", + "y": "1112" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "730.5", + "y": "1099.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "719", + "y": "1087" + }, + "name": "rom" + }, + { + "jdipNS:UNIT": { + "x": "648", + "y": "789" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "635.5", + "y": "776.5" + }, + "name": "ruh" + }, + { + "jdipNS:UNIT": { + "x": "1108", + "y": "977" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1095.5", + "y": "964.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1083", + "y": "1014" + }, + "name": "rum" + }, + { + "jdipNS:UNIT": { + "x": "945", + "y": "1060" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "932.5", + "y": "1047.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "936", + "y": "1021" + }, + "name": "ser" + }, + { + "jdipNS:UNIT": { + "x": "1296", + "y": "855" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1283.5", + "y": "842.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1267", + "y": "973" + }, + "name": "sev" + }, + { + "jdipNS:UNIT": { + "x": "844", + "y": "779" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "831.5", + "y": "766.5" + }, + "name": "sil" + }, + { + "jdipNS:UNIT": { + "x": "747", + "y": "528" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "734.5", + "y": "515.5" + }, + "name": "ska" + }, + { + "jdipNS:UNIT": { + "x": "1265", + "y": "1220" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1252.5", + "y": "1207.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1132", + "y": "1222" + }, + "name": "smy" + }, + { + "jdipNS:UNIT": { + "x": "347", + "y": "1049" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "334.5", + "y": "1036.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "302", + "y": "1056" + }, + "name": "spa" + }, + { + "jdipNS:UNIT": { + "x": "1178", + "y": "415" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1165.5", + "y": "402.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "1113", + "y": "469" + }, + "name": "stp" + }, + { + "jdipNS:UNIT": { + "x": "841", + "y": "469" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "828.5", + "y": "456.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "890", + "y": "489" + }, + "name": "swe" + }, + { + "jdipNS:UNIT": { + "x": "642", + "y": "928" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "629", + "y": "915.5" + }, + "name": "swi" + }, + { + "jdipNS:UNIT": { + "x": "1464", + "y": "1216" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1451.5", + "y": "1203.5" + }, + "name": "syr" + }, + { + "jdipNS:UNIT": { + "x": "837", + "y": "1006" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "824.5", + "y": "993.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "794", + "y": "975" + }, + "name": "tri" + }, + { + "jdipNS:UNIT": { + "x": "634", + "y": "1310" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "621.5", + "y": "1297.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "647", + "y": "1282" + }, + "name": "tun" + }, + { + "jdipNS:UNIT": { + "x": "698", + "y": "1044" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "685.5", + "y": "1031.5" + }, + "name": "tus" + }, + { + "jdipNS:UNIT": { + "x": "720", + "y": "1160" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "707.5", + "y": "1147.5" + }, + "name": "tyn" + }, + { + "jdipNS:UNIT": { + "x": "754", + "y": "914" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "741.5", + "y": "901.5" + }, + "name": "tyr" + }, + { + "jdipNS:UNIT": { + "x": "710.0", + "y": "1159.1" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "697.5", + "y": "1146.6" + }, + "name": "tys" + }, + { + "jdipNS:UNIT": { + "x": "1136", + "y": "810" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1123.5", + "y": "797.5" + }, + "name": "ukr" + }, + { + "jdipNS:UNIT": { + "x": "719", + "y": "1004" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "706.5", + "y": "991.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "733", + "y": "971" + }, + "name": "ven" + }, + { + "jdipNS:UNIT": { + "x": "867", + "y": "874" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "854.5", + "y": "861.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "835", + "y": "887" + }, + "name": "vie" + }, + { + "jdipNS:UNIT": { + "x": "440", + "y": "668" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "427.5", + "y": "655.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "440", + "y": "712" + }, + "name": "wal" + }, + { + "jdipNS:UNIT": { + "x": "995", + "y": "750" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "982.5", + "y": "737.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "937", + "y": "748" + }, + "name": "war" + }, + { + "jdipNS:UNIT": { + "x": "474", + "y": "1173" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "461.5", + "y": "1160.5" + }, + "name": "wes" + }, + { + "jdipNS:UNIT": { + "x": "504", + "y": "626" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "491.5", + "y": "613.5" + }, + "jdipNS:SUPPLY_CENTER": { + "x": "506", + "y": "647" + }, + "name": "yor" + }, + { + "jdipNS:UNIT": { + "x": "1218", + "y": "222" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1205.5", + "y": "209.5" + }, + "name": "stp-nc" + }, + { + "jdipNS:UNIT": { + "x": "1066", + "y": "487" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1053.5", + "y": "474.5" + }, + "name": "stp-sc" + }, + { + "jdipNS:UNIT": { + "x": "1127", + "y": "1067" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1114.5", + "y": "1054.5" + }, + "name": "bul-ec" + }, + { + "jdipNS:UNIT": { + "x": "1070", + "y": "1140" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "1057.5", + "y": "1127.5" + }, + "name": "bul-sc" + }, + { + "jdipNS:UNIT": { + "x": "289", + "y": "965" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "276.5", + "y": "952.5" + }, + "name": "spa-nc" + }, + { + "jdipNS:UNIT": { + "x": "291", + "y": "1166" + }, + "jdipNS:DISLODGED_UNIT": { + "x": "278.5", + "y": "1153.5" + }, + "name": "spa-sc" + } + ] + } +}; diff --git a/diplomacy/web/src/gui/maps/standard/build.js b/diplomacy/web/src/gui/maps/standard/build.js new file mode 100644 index 0000000..cf28f19 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/build.js @@ -0,0 +1,53 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {ARMY, Coordinates, FLEET, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class Build extends React.Component { + render() { + const loc = this.props.loc; + const unit_type = this.props.unitType; + const loc_x = offset(Coordinates[loc].unit[0], -11.5); + const loc_y = offset(Coordinates[loc].unit[1], -10.); + const build_loc_x = offset(Coordinates[loc].unit[0], -20.5); + const build_loc_y = offset(Coordinates[loc].unit[1], -20.5); + const symbol = unit_type === 'A' ? ARMY : FLEET; + const build_symbol = 'BuildUnit'; + return ( + + + + + ); + } +} + +Build.propTypes = { + unitType: PropTypes.string.isRequired, + loc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/common.js b/diplomacy/web/src/gui/maps/standard/common.js new file mode 100644 index 0000000..cb768c2 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/common.js @@ -0,0 +1,75 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import {SvgStandardExtra} from "./SvgStandardExtra"; + +const TAG_ORDERDRAWING = 'jdipNS:ORDERDRAWING'; +const TAG_POWERCOLORS = 'jdipNS:POWERCOLORS'; +const TAG_POWERCOLOR = 'jdipNS:POWERCOLOR'; +const TAG_SYMBOLSIZE = 'jdipNS:SYMBOLSIZE'; +const TAG_PROVINCE_DATA = 'jdipNS:PROVINCE_DATA'; +const TAG_PROVINCE = 'jdipNS:PROVINCE'; +const TAG_UNIT = 'jdipNS:UNIT'; +const TAG_DISLODGED_UNIT = 'jdipNS:DISLODGED_UNIT'; +const TAG_SUPPLY_CENTER = 'jdipNS:SUPPLY_CENTER'; + +export const ARMY = 'Army'; +export const FLEET = 'Fleet'; + +function getCoordinates() { + const coordinates = {}; + for (let provinceDefiniton of SvgStandardExtra[TAG_PROVINCE_DATA][TAG_PROVINCE]) { + const name = provinceDefiniton.name.toUpperCase().replace('-', '/'); + coordinates[name] = {}; + if (provinceDefiniton.hasOwnProperty(TAG_UNIT)) { + coordinates[name].unit = [provinceDefiniton[TAG_UNIT]['x'], provinceDefiniton[TAG_UNIT]['y']]; + } + if (provinceDefiniton.hasOwnProperty(TAG_DISLODGED_UNIT)) { + coordinates[name].disl = [provinceDefiniton[TAG_DISLODGED_UNIT]['x'], provinceDefiniton[TAG_DISLODGED_UNIT]['y']]; + } + if (provinceDefiniton.hasOwnProperty(TAG_SUPPLY_CENTER)) { + coordinates[name].sc = [provinceDefiniton[TAG_SUPPLY_CENTER]['x'], provinceDefiniton[TAG_SUPPLY_CENTER]['y']]; + } + } + return coordinates; +} + +function getSymbolSizes() { + const sizes = {}; + for (let definition of SvgStandardExtra[TAG_ORDERDRAWING][TAG_SYMBOLSIZE]) { + sizes[definition.name] = { + width: parseInt(definition.width), + height: parseInt(definition.height) + }; + } + return sizes; +} + +function getColors() { + const colors = {}; + for (let definition of SvgStandardExtra[TAG_ORDERDRAWING][TAG_POWERCOLORS][TAG_POWERCOLOR]) { + colors[definition.power.toUpperCase()] = definition.color; + } + return colors; +} + +export const Coordinates = getCoordinates(); +export const SymbolSizes = getSymbolSizes(); +export const Colors = getColors(); + +export function offset(floatString, offset) { + return "" + (parseFloat(floatString) + offset); +} diff --git a/diplomacy/web/src/gui/maps/standard/convoy.js b/diplomacy/web/src/gui/maps/standard/convoy.js new file mode 100644 index 0000000..03b4749 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/convoy.js @@ -0,0 +1,98 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class Convoy extends React.Component { + render() { + const loc = this.props.loc; + const src_loc = this.props.srcLoc; + const dest_loc = this.props.dstLoc; + const loc_x = offset(Coordinates[loc].unit[0], 10); + const loc_y = offset(Coordinates[loc].unit[1], 10); + const src_loc_x = offset(Coordinates[src_loc].unit[0], 10); + const src_loc_y = offset(Coordinates[src_loc].unit[1], 10); + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[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(Coordinates[src_loc].unit[0], 10); + const triangle_loc_y = offset(Coordinates[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])); + } + + return ( + + + + + + + + + ); + } +} + +Convoy.propTypes = { + loc: PropTypes.string.isRequired, + srcLoc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/disband.js b/diplomacy/web/src/gui/maps/standard/disband.js new file mode 100644 index 0000000..857ba70 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/disband.js @@ -0,0 +1,51 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Coordinates, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class Disband extends React.Component { + render() { + const loc = this.props.loc; + const phaseType = this.props.phaseType; + let loc_x = 0; + let loc_y = 0; + if (phaseType === 'R') { + loc_x = offset(Coordinates[loc].unit[0], -29.); + loc_y = offset(Coordinates[loc].unit[1], -27.5); + } else { + loc_x = offset(Coordinates[loc].unit[0], -16.5); + loc_y = offset(Coordinates[loc].unit[1], -15.); + } + const symbol = 'RemoveUnit'; + return ( + + + + ); + } +} + +Disband.propTypes = { + loc: PropTypes.string.isRequired, + phaseType: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/hold.js b/diplomacy/web/src/gui/maps/standard/hold.js new file mode 100644 index 0000000..4a79deb --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/hold.js @@ -0,0 +1,45 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class Hold extends React.Component { + render() { + const polygon_coord = []; + const loc_x = offset(Coordinates[this.props.loc].unit[0], 8.5); + const loc_y = offset(Coordinates[this.props.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])); + } + return ( + + + + + ); + } +} + +Hold.propTypes = { + loc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/move.js b/diplomacy/web/src/gui/maps/standard/move.js new file mode 100644 index 0000000..8351a96 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/move.js @@ -0,0 +1,69 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class Move extends React.Component { + render() { + const src_loc = this.props.srcLoc; + const dest_loc = this.props.dstLoc; + let src_loc_x = 0; + let src_loc_y = 0; + if (this.props.phaseType === 'R') { + src_loc_x = offset(Coordinates[src_loc].unit[0], -2.5); + src_loc_y = offset(Coordinates[src_loc].unit[1], -2.5); + } else { + src_loc_x = offset(Coordinates[src_loc].unit[0], 10); + src_loc_y = offset(Coordinates[src_loc].unit[1], 10); + } + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[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.; + return ( + + + + + ); + } +} + +Move.propTypes = { + srcLoc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, + phaseType: PropTypes.string.isRequired +}; diff --git a/diplomacy/web/src/gui/maps/standard/supplyCenter.js b/diplomacy/web/src/gui/maps/standard/supplyCenter.js new file mode 100644 index 0000000..0663ede --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/supplyCenter.js @@ -0,0 +1,40 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Coordinates, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class SupplyCenter extends React.Component { + render() { + const symbol = 'SupplyCenter'; + const loc_x = offset(Coordinates[this.props.loc].sc[0], -8.5); + const loc_y = offset(Coordinates[this.props.loc].sc[1], -11.0); + return ( + + ); + } +} + +SupplyCenter.propTypes = { + loc: PropTypes.string.isRequired, + powerName: PropTypes.string +}; diff --git a/diplomacy/web/src/gui/maps/standard/supportHold.js b/diplomacy/web/src/gui/maps/standard/supportHold.js new file mode 100644 index 0000000..bfa2656 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/supportHold.js @@ -0,0 +1,73 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class SupportHold extends React.Component { + render() { + const loc = this.props.loc; + const dest_loc = this.props.dstLoc; + const loc_x = offset(Coordinates[loc].unit[0], 10); + const loc_y = offset(Coordinates[loc].unit[1], 10); + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[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(Coordinates[dest_loc].unit[0], 8.5); + const poly_loc_y = offset(Coordinates[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])); + } + return ( + + + + + + + ); + } +} + +SupportHold.propTypes = { + loc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, +}; diff --git a/diplomacy/web/src/gui/maps/standard/supportMove.js b/diplomacy/web/src/gui/maps/standard/supportMove.js new file mode 100644 index 0000000..e46abc3 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/supportMove.js @@ -0,0 +1,57 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {Colors, Coordinates, offset} from "./common"; +import PropTypes from "prop-types"; + +export class SupportMove extends React.Component { + render() { + const loc = this.props.loc; + const src_loc = this.props.srcLoc; + const dest_loc = this.props.dstLoc; + const loc_x = offset(Coordinates[loc].unit[0], 10); + const loc_y = offset(Coordinates[loc].unit[1], 10); + const src_loc_x = offset(Coordinates[src_loc].unit[0], 10); + const src_loc_y = offset(Coordinates[src_loc].unit[1], 10); + let dest_loc_x = offset(Coordinates[dest_loc].unit[0], 10); + let dest_loc_y = offset(Coordinates[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.; + return ( + + + + + ); + } +} + +SupportMove.propTypes = { + loc: PropTypes.string.isRequired, + srcLoc: PropTypes.string.isRequired, + dstLoc: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, +}; diff --git a/diplomacy/web/src/gui/maps/standard/unit.js b/diplomacy/web/src/gui/maps/standard/unit.js new file mode 100644 index 0000000..f32a678 --- /dev/null +++ b/diplomacy/web/src/gui/maps/standard/unit.js @@ -0,0 +1,45 @@ +// ============================================================================== +// 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 . +// ============================================================================== +import React from "react"; +import {ARMY, Coordinates, FLEET, offset, SymbolSizes} from "./common"; +import PropTypes from "prop-types"; + +export class Unit extends React.Component { + render() { + const split_unit = this.props.unit.split(/ +/); + const unit_type = split_unit[0]; + const loc = split_unit[1]; + const dislogged_type = this.props.isDislodged ? 'disl' : 'unit'; + const symbol = unit_type === 'F' ? FLEET : ARMY; + const loc_x = offset(Coordinates[loc][dislogged_type][0], -11.5); + const loc_y = offset(Coordinates[loc][dislogged_type][1], -10.0); + return ( + + ); + } +} + +Unit.propTypes = { + unit: PropTypes.string.isRequired, + powerName: PropTypes.string.isRequired, + isDislodged: PropTypes.bool.isRequired, +}; diff --git a/diplomacy/web/src/gui/pages/content_game.jsx b/diplomacy/web/src/gui/pages/content_game.jsx index a98c739..bb7d41e 100644 --- a/diplomacy/web/src/gui/pages/content_game.jsx +++ b/diplomacy/web/src/gui/pages/content_game.jsx @@ -21,7 +21,6 @@ import {SelectViaForm} from "../forms/select_via_form"; import {Order} from "../utils/order"; import {Row} from "../components/layouts"; import {Tabs} from "../components/tabs"; -import {Map} from "../map/map"; import {extendOrderBuilding, ORDER_BUILDER, POSSIBLE_ORDERS} from "../utils/order_building"; import {PowerOrderCreationForm} from "../forms/power_order_creation_form"; import {MessageForm} from "../forms/message_form"; @@ -44,6 +43,9 @@ import {Button} from "../components/button"; import {saveGameToDisk} from "../utils/saveGameToDisk"; import {Game} from '../../diplomacy/engine/game'; import {PowerOrdersActionBar} from "../components/power_orders_actions_bar"; +import {SvgStandard} from "../maps/standard/SvgStandard"; +import {MapData} from "../utils/map_data"; +import {Queue} from "../../diplomacy/utils/queue"; const HotKey = require('react-shortcut'); @@ -75,6 +77,10 @@ const PRETTY_ROLES = { [STRINGS.OBSERVER_TYPE]: 'Observer' }; +function noPromise() { + return new Promise(resolve => resolve()); +} + export class ContentGame extends React.Component { constructor(props) { @@ -111,7 +117,6 @@ export class ContentGame extends React.Component { historyShowOrders: true, historyCurrentLoc: null, historyCurrentOrders: null, - wait: null, // {power name => bool} orders: orders, // {power name => {loc => {local: bool, order: str}}} power: null, orderBuildingType: null, @@ -156,6 +161,7 @@ export class ContentGame extends React.Component { this.setSelectedVia = this.setSelectedVia.bind(this); this.setWaitFlag = this.setWaitFlag.bind(this); this.vote = this.vote.bind(this); + this.updateDeadlineTimer = this.updateDeadlineTimer.bind(this); } static prettyRole(role) { @@ -194,12 +200,24 @@ export class ContentGame extends React.Component { }; } + setState(state) { + return new Promise(resolve => super.setState(state, resolve)); + } + + forceUpdate() { + return new Promise(resolve => super.forceUpdate(resolve)); + } + + /** + * Return current page object displaying this content. + * @returns {Page} + */ getPage() { return this.context; } clearOrderBuildingPath() { - this.setState({ + return this.setState({ orderBuildingPath: [] }); } @@ -275,7 +293,8 @@ export class ContentGame extends React.Component { engine.deadline_timer = 0; this.clearScheduleTimeout(); } - this.getPage().load(`game: ${engine.game_id}`, ); + if (this.networkGameIsDisplayed(engine.client)) + this.forceUpdate(); } reloadDeadlineTimer(networkGame) { @@ -287,13 +306,12 @@ export class ContentGame extends React.Component { const server_remaining = server_end - server_current; this.props.data.deadline_timer = server_remaining * schedule.time_unit; if (!this.schedule_timeout_id) - this.schedule_timeout_id = setInterval(() => this.updateDeadlineTimer(), schedule.time_unit * 1000); + this.schedule_timeout_id = setInterval(this.updateDeadlineTimer, schedule.time_unit * 1000); }) .catch(() => { if (this.props.data.hasOwnProperty('deadline_timer')) delete this.props.data.deadline_timer; this.clearScheduleTimeout(); - // this.getPage().error(`Error while updating deadline timer: ${error.toString()}`); }); } @@ -311,13 +329,10 @@ export class ContentGame extends React.Component { notifiedNetworkGame(networkGame, notification) { if (this.networkGameIsDisplayed(networkGame)) { const msg = `Game (${networkGame.local.game_id}) received notification ${notification.name}.`; - this.getPage().load( - `game: ${networkGame.local.game_id}`, - , - {info: msg} - ); this.reloadDeadlineTimer(networkGame); + return this.forceUpdate().then(() => this.getPage().info(msg)); } + return noPromise(); } notifiedPowersControllers(networkGame, notification) { @@ -326,51 +341,46 @@ export class ContentGame extends React.Component { || !networkGame.channel.game_id_to_instances[networkGame.local.game_id].has(networkGame.local.role) )) { // This power game is now invalid. - this.getPage().disconnectGame(networkGame.local.game_id) + return this.getPage().disconnectGame(networkGame.local.game_id) .then(() => { if (this.networkGameIsDisplayed(networkGame)) { - const page = this.getPage(); - page.loadGames( + return this.getPage().loadGames( {error: `${networkGame.local.game_id}/${networkGame.local.role} was kicked. Deadline over?`}); } }); } else { - this.notifiedNetworkGame(networkGame, notification); + return this.notifiedNetworkGame(networkGame, notification); } } notifiedGamePhaseUpdated(networkGame, notification) { - networkGame.getAllPossibleOrders() + return networkGame.getAllPossibleOrders() .then(allPossibleOrders => { networkGame.local.setPossibleOrders(allPossibleOrders); if (this.networkGameIsDisplayed(networkGame)) { - this.getPage().load( - `game: ${networkGame.local.game_id}`, - , - {info: `Game update (${notification.name}) to ${networkGame.local.phase}.`} - ); this.__store_orders(null); - this.setState({orders: null, wait: null, messageHighlights: {}}); this.reloadDeadlineTimer(networkGame); + return this.setState({orders: null, messageHighlights: {}}) + .then(() => this.getPage().info( + `Game update (${notification.name}) to ${networkGame.local.phase}.`)); } }) .catch(error => this.getPage().error('Error when updating possible orders: ' + error.toString())); } notifiedLocalStateChange(networkGame, notification) { - networkGame.getAllPossibleOrders() + return networkGame.getAllPossibleOrders() .then(allPossibleOrders => { networkGame.local.setPossibleOrders(allPossibleOrders); if (this.networkGameIsDisplayed(networkGame)) { - this.getPage().load( - `game: ${networkGame.local.game_id}`, - , - {info: `Possible orders re-loaded.`} - ); + this.reloadDeadlineTimer(networkGame); + let result = null; if (notification.power_name) { - this.reloadPowerServerOrders(notification.power_name); + result = this.reloadPowerServerOrders(notification.power_name); + } else { + result = this.forceUpdate(); } - this.reloadDeadlineTimer(networkGame); + return result.then(() => this.getPage().info(`Possible orders re-loaded.`)); } }) .catch(error => this.getPage().error('Error when updating possible orders: ' + error.toString())); @@ -385,48 +395,79 @@ export class ContentGame extends React.Component { messageHighlights[protagonist] = 1; else ++messageHighlights[protagonist]; - this.setState({messageHighlights: messageHighlights}); - this.notifiedNetworkGame(networkGame, notification); + return this.setState({messageHighlights: messageHighlights}) + .then(() => this.notifiedNetworkGame(networkGame, notification)); } bindCallbacks(networkGame) { + const collector = (game, notification) => { + game.queue.append(notification); + }; + const consumer = (notification) => { + switch (notification.name) { + case 'powers_controllers': + return this.notifiedPowersControllers(networkGame, notification); + case 'game_message_received': + return this.notifiedNewGameMessage(networkGame, notification); + case 'game_processed': + case 'game_phase_update': + return this.notifiedGamePhaseUpdated(networkGame, notification); + case 'cleared_centers': + case 'cleared_orders': + case 'cleared_units': + case 'power_orders_update': + case 'power_orders_flag': + return this.notifiedLocalStateChange(networkGame, notification); + case 'game_status_update': + case 'omniscient_updated': + case 'power_vote_updated': + case 'power_wait_flag': + case 'vote_count_updated': + case 'vote_updated': + return this.notifiedNetworkGame(networkGame, notification); + default: + throw new Error(`Unhandled notification: ${notification.name}`); + } + }; if (!networkGame.callbacksBound) { - networkGame.addOnClearedCenters(this.notifiedLocalStateChange); - networkGame.addOnClearedOrders(this.notifiedLocalStateChange); - networkGame.addOnClearedUnits(this.notifiedLocalStateChange); - networkGame.addOnPowersControllers(this.notifiedPowersControllers); - networkGame.addOnGameMessageReceived(this.notifiedNewGameMessage); - networkGame.addOnGameProcessed(this.notifiedGamePhaseUpdated); - networkGame.addOnGamePhaseUpdate(this.notifiedGamePhaseUpdated); - networkGame.addOnGameStatusUpdate(this.notifiedNetworkGame); - networkGame.addOnOmniscientUpdated(this.notifiedNetworkGame); - networkGame.addOnPowerOrdersUpdate(this.notifiedLocalStateChange); - networkGame.addOnPowerOrdersFlag(this.notifiedLocalStateChange); - networkGame.addOnPowerVoteUpdated(this.notifiedNetworkGame); - networkGame.addOnPowerWaitFlag(this.notifiedNetworkGame); - networkGame.addOnVoteCountUpdated(this.notifiedNetworkGame); - networkGame.addOnVoteUpdated(this.notifiedNetworkGame); + networkGame.queue = new Queue(); + networkGame.addOnClearedCenters(collector); + networkGame.addOnClearedOrders(collector); + networkGame.addOnClearedUnits(collector); + networkGame.addOnPowerOrdersUpdate(collector); + networkGame.addOnPowerOrdersFlag(collector); + networkGame.addOnPowersControllers(collector); + networkGame.addOnGameMessageReceived(collector); + networkGame.addOnGameProcessed(collector); + networkGame.addOnGamePhaseUpdate(collector); + networkGame.addOnGameStatusUpdate(collector); + networkGame.addOnOmniscientUpdated(collector); + networkGame.addOnPowerVoteUpdated(collector); + networkGame.addOnPowerWaitFlag(collector); + networkGame.addOnVoteCountUpdated(collector); + networkGame.addOnVoteUpdated(collector); networkGame.callbacksBound = true; networkGame.local.markAllMessagesRead(); + networkGame.queue.consumeAsync(consumer); } } // ] onChangeCurrentPower(event) { - this.setState({power: event.target.value, tabPastMessages: null, tabCurrentMessages: null}); + return this.setState({power: event.target.value, tabPastMessages: null, tabCurrentMessages: null}); } onChangeMainTab(tab) { - this.setState({tabMain: tab}); + return this.setState({tabMain: tab}); } onChangeTabCurrentMessages(tab) { - this.setState({tabCurrentMessages: tab}); + return this.setState({tabCurrentMessages: tab}); } onChangeTabPastMessages(tab) { - this.setState({tabPastMessages: tab}); + return this.setState({tabPastMessages: tab}); } sendMessage(networkGame, recipient, body) { @@ -468,10 +509,6 @@ export class ContentGame extends React.Component { return this.state.power || (controllablePowers.length && controllablePowers[0]); } - __get_wait(engine) { - return this.state.wait ? this.state.wait : ContentGame.getServerWaitFlags(engine); - } - // [ Methods involved in orders management. /** @@ -535,12 +572,11 @@ export class ContentGame extends React.Component { const engine = this.props.data; const allOrders = this.__get_orders(engine); if (!allOrders.hasOwnProperty(powerName)) { - this.getPage().error(`Unknown power ${powerName}.`); - return; + return this.getPage().error(`Unknown power ${powerName}.`); } allOrders[powerName] = serverOrders[powerName]; this.__store_orders(allOrders); - this.setState({orders: allOrders}); + return this.setState({orders: allOrders}); } /** @@ -598,7 +634,7 @@ export class ContentGame extends React.Component { const orders = this.__get_orders(this.props.data); orders[powerName] = {}; this.__store_orders(orders); - this.setState({orders: orders}); + return this.setState({orders: orders}); } /** @@ -667,8 +703,8 @@ export class ContentGame extends React.Component { onOrderBuilding(powerName, path) { const pathToSave = path.slice(1); - this.getPage().success(`Building order ${pathToSave.join(' ')} ...`); - this.setState({orderBuildingPath: pathToSave}); + return this.setState({orderBuildingPath: pathToSave}) + .then(() => this.getPage().success(`Building order ${pathToSave.join(' ')} ...`)); } onOrderBuilt(powerName, orderString) { @@ -676,16 +712,14 @@ export class ContentGame extends React.Component { state.orderBuildingPath = []; if (!orderString) { Diplog.warn('No order built.'); - this.setState(state); - return; + return this.setState(state); } const engine = this.props.data; const localOrder = new Order(orderString, true); const allOrders = this.__get_orders(engine); if (!allOrders.hasOwnProperty(powerName)) { Diplog.warn(`Unknown power ${powerName}.`); - this.setState(state); - return; + return this.setState(state); } if (!allOrders[powerName]) @@ -694,11 +728,11 @@ export class ContentGame extends React.Component { state.orders = allOrders; this.getPage().success(`Built order: ${orderString}`); this.__store_orders(allOrders); - this.setState(state); + return this.setState(state); } onChangeOrderType(form) { - this.setState({ + return this.setState({ orderBuildingType: form.order_type, orderBuildingPath: [], }); @@ -727,7 +761,9 @@ export class ContentGame extends React.Component { if (!currentPowerName) throw new Error(`Internal error: unable to detect current selected power name.`); networkGame.setWait(waitFlag, {power_name: currentPowerName}) - .then(() => this.getPage().success(`Wait flag set to ${waitFlag} for ${currentPowerName}`)) + .then(() => { + this.forceUpdate(() => this.getPage().success(`Wait flag set to ${waitFlag} for ${currentPowerName}`)); + }) .catch(error => { Diplog.error(error.stack); this.getPage().error(`Error while setting wait flag for ${currentPowerName}: ${error.toString()}`); @@ -735,7 +771,7 @@ export class ContentGame extends React.Component { } __change_past_phase(newPhaseIndex) { - this.setState({ + return this.setState({ historyPhaseIndex: newPhaseIndex, historyCurrentLoc: null, historyCurrentOrders: null @@ -780,7 +816,7 @@ export class ContentGame extends React.Component { } onChangeShowPastOrders(event) { - this.setState({historyShowOrders: event.target.checked}); + return this.setState({historyShowOrders: event.target.checked}); } onClickMessage(message) { @@ -799,7 +835,7 @@ export class ContentGame extends React.Component { } displayLocationOrders(loc, orders) { - this.setState({ + return this.setState({ historyCurrentLoc: loc || null, historyCurrentOrders: orders && orders.length ? orders : null }); @@ -810,7 +846,7 @@ export class ContentGame extends React.Component { renderOrders(engine, currentPowerName) { const serverOrders = this.props.data.getServerOrders(); const orders = this.__get_orders(engine); - const wait = this.__get_wait(engine); + const wait = ContentGame.getServerWaitFlags(engine); const render = []; render.push(; + return ( +
+ +
+ ); } renderMapForMessages(gameEngine, showOrders) { - return ; + return ( +
+ +
+ ); } renderMapForCurrent(gameEngine, powerName, orderType, orderPath) { @@ -941,18 +979,19 @@ export class ContentGame extends React.Component { orders[entry[0]].push(orderObject.order); } } - return ; + return ( +
+ +
+ ); } __get_engine_to_display(initialEngine) { @@ -1265,10 +1304,10 @@ export class ContentGame extends React.Component { navigation={navigation}/> {/* Tab Phase history. */} - {(hasTabPhaseHistory && this.renderTabResults(mainTab === 'phase_history', engine)) || ''} - {this.renderTabMessages(mainTab === 'messages', engine, currentPowerName)} + {(hasTabPhaseHistory && mainTab === 'phase_history' && this.renderTabResults(mainTab === 'phase_history', engine)) || ''} + {mainTab === 'messages' && this.renderTabMessages(mainTab === 'messages', engine, currentPowerName)} {/* Tab Current phase. */} - {(hasTabCurrentPhase && this.renderTabCurrentPhase( + {(mainTab === 'current_phase' && hasTabCurrentPhase && this.renderTabCurrentPhase( mainTab === 'current_phase', engine, currentPowerName, diff --git a/diplomacy/web/src/gui/pages/content_games.jsx b/diplomacy/web/src/gui/pages/content_games.jsx index 5250f03..ef79b58 100644 --- a/diplomacy/web/src/gui/pages/content_games.jsx +++ b/diplomacy/web/src/gui/pages/content_games.jsx @@ -26,7 +26,6 @@ import {ContentGame} from "./content_game"; import PropTypes from 'prop-types'; import {Tab} from "../components/tab"; import {GameCreationWizard} from "../wizards/gameCreation/gameCreationWizard"; -import {Diplog} from "../../diplomacy/utils/diplog"; const TABLE_LOCAL_GAMES = { game_id: ['Game ID', 0], @@ -109,10 +108,6 @@ export class ContentGames extends React.Component { username={this.getPage().channel.username} onSubmit={(form) => { onClose(); - Diplog.info(`Creating game:`); - for (let entry of Object.entries(form)) { - Diplog.info(`${entry[0]}: ${entry[1] ? entry[1].toString() : entry[1]}`); - } this.onCreate(form); }}/> ))}> diff --git a/diplomacy/web/src/gui/pages/page.jsx b/diplomacy/web/src/gui/pages/page.jsx index a9ff9ac..95b4482 100644 --- a/diplomacy/web/src/gui/pages/page.jsx +++ b/diplomacy/web/src/gui/pages/page.jsx @@ -73,6 +73,10 @@ export class Page extends React.Component { return ; } + setState(state) { + return new Promise(resolve => super.setState(state, resolve)); + } + onReconnectionError(error) { this.__disconnect(error); } @@ -106,11 +110,11 @@ export class Page extends React.Component { Diplog.printMessages(newState); newState.name = name; newState.body = body; - this.setState(newState); + return this.setState(newState); } loadGames(messages) { - this.load( + return this.load( 'games', , messages @@ -118,14 +122,13 @@ export class Page extends React.Component { } loadGameFromDisk() { - loadGameFromDisk( - (game) => this.load( + return loadGameFromDisk() + .then((game) => this.load( `game: ${game.game_id}`, , {success: `Game loaded from disk: ${game.game_id}`} - ), - this.error - ); + )) + .catch(this.error); } getName() { @@ -142,7 +145,7 @@ export class Page extends React.Component { this.availableMaps = null; const message = Page.wrapMessage(error ? `${error.toString()}` : `Disconnected from channel and server.`); Diplog.success(message); - this.setState({ + return this.setState({ error: error ? message : null, info: null, success: error ? null : message, @@ -157,11 +160,11 @@ export class Page extends React.Component { logout() { // Disconnect channel and go back to connection page. if (this.channel) { - this.channel.logout() + return this.channel.logout() .then(() => this.__disconnect()) .catch(error => this.error(`Error while disconnecting: ${error.toString()}.`)); } else { - this.__disconnect(); + return this.__disconnect(); } } @@ -170,23 +173,23 @@ export class Page extends React.Component { error(message) { message = Page.wrapMessage(message); Diplog.error(message); - this.setState({error: message}); + return this.setState({error: message}); } info(message) { message = Page.wrapMessage(message); Diplog.info(message); - this.setState({info: message}); + return this.setState({info: message}); } success(message) { message = Page.wrapMessage(message); Diplog.success(message); - this.setState({success: message}); + return this.setState({success: message}); } warn(message) { - this.info(message); + return this.info(message); } //// Methods to manage games. @@ -205,7 +208,7 @@ export class Page extends React.Component { } if (!gamesFound) gamesFound = this.state.games; - this.setState({myGames: myGames, games: gamesFound}); + return this.setState({myGames: myGames, games: gamesFound}); } getGame(gameID) { @@ -230,52 +233,46 @@ export class Page extends React.Component { this.state.myGames[game.game_id] : game ); } - this.setState({games: gamesFound}); + return this.setState({games: gamesFound}); } leaveGame(gameID) { if (this.state.myGames.hasOwnProperty(gameID)) { const game = this.state.myGames[gameID]; if (game.client) { - game.client.leave() - .then(() => { - this.disconnectGame(gameID).then(() => { - this.loadGames({info: `Game ${gameID} left.`}); - }); - }) + return game.client.leave() + .then(() => this.disconnectGame(gameID)) + .then(() => this.loadGames({info: `Game ${gameID} left.`})) .catch(error => this.error(`Error when leaving game ${gameID}: ${error.toString()}`)); } } else { - this.loadGames({info: `No game to left.`}); + return this.loadGames({info: `No game to left.`}); } + return null; } _post_remove(gameID) { - this.disconnectGame(gameID) + return this.disconnectGame(gameID) .then(() => { const myGames = this._remove_from_my_games(gameID); const games = this._remove_from_games(gameID); - this.setState( - {games, myGames}, - () => this.loadGames({info: `Game ${gameID} deleted.`})); - }); + return this.setState({games, myGames}); + }) + .then(() => this.loadGames({info: `Game ${gameID} deleted.`})); } removeGame(gameID) { const game = this.getGame(gameID); if (game) { if (game.client) { - game.client.remove() + return game.client.remove() .then(() => this._post_remove(gameID)) .catch(error => this.error(`Error when deleting game ${gameID}: ${error.toString()}`)); } else { - this.channel.joinGame({game_id: gameID}) - .then(networkGame => { - networkGame.remove() - .then(() => this._post_remove(gameID)) - .catch(error => this.error(`Error when deleting game ${gameID}: ${error.toString()}`)); - }) - .catch(error => this.error(`Error when connecting to game to delete (${gameID}): ${error.toString()}`)); + return this.channel.joinGame({game_id: gameID}) + .then(networkGame => networkGame.remove()) + .then(() => this._post_remove(gameID)) + .catch(error => this.error(`Error when deleting game after joining it (${gameID}): ${error.toString()}`)); } } } @@ -283,12 +280,14 @@ export class Page extends React.Component { disconnectGame(gameID) { const game = this.getGame(gameID); if (game) { - if (game.client) + if (game.client) { game.client.clearAllCallbacks(); + game.client.callbacksBound = false; + if (game.client.queue) + game.client.queue.append(null); + } return this.channel.getGamesInfo({games: [gameID]}) - .then(gamesInfo => { - this.updateMyGames(gamesInfo); - }) + .then(gamesInfo => this.updateMyGames(gamesInfo)) .catch(error => this.error(`Error while leaving game ${gameID}: ${error.toString()}`)); } return null; @@ -327,13 +326,12 @@ export class Page extends React.Component { addToMyGames(game) { // Update state myGames with given game **and** update local storage. DipStorage.addUserGame(this.channel.username, game.game_id); - this.setState(this._add_to_my_games(game), () => this.loadGames()); + return this.setState(this._add_to_my_games(game)).then(() => this.loadGames()); } removeFromMyGames(gameID) { const myGames = this._remove_from_my_games(gameID); - if (myGames !== this.state.myGames) - this.setState({myGames}, () => this.loadGames()); + return this.setState({myGames}).then(() => this.loadGames()); } hasMyGame(gameID) { diff --git a/diplomacy/web/src/gui/utils/load_game_from_disk.js b/diplomacy/web/src/gui/utils/load_game_from_disk.js index ca49aa0..a65e1df 100644 --- a/diplomacy/web/src/gui/utils/load_game_from_disk.js +++ b/diplomacy/web/src/gui/utils/load_game_from_disk.js @@ -1,83 +1,101 @@ +// ============================================================================== +// 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 . +// ============================================================================== import $ from "jquery"; import {STRINGS} from "../../diplomacy/utils/strings"; import {Game} from "../../diplomacy/engine/game"; -export function loadGameFromDisk(onLoad, onError) { - const input = $(document.createElement('input')); - input.attr("type", "file"); - input.trigger('click'); - input.change(event => { - const file = event.target.files[0]; - if (!file.name.match(/\.json$/i)) { - onError(`Invalid JSON filename ${file.name}`); - return; - } - const reader = new FileReader(); - reader.onload = () => { - const savedData = JSON.parse(reader.result); - const gameObject = {}; - gameObject.game_id = `(local) ${savedData.id}`; - gameObject.map_name = savedData.map; - gameObject.rules = savedData.rules; - gameObject.state_history = {}; - gameObject.message_history = {}; - gameObject.order_history = {}; - gameObject.result_history = {}; +export function loadGameFromDisk() { + return new Promise((onLoad, onError) => { + const input = $(document.createElement('input')); + input.attr("type", "file"); + input.trigger('click'); + input.change(event => { + const file = event.target.files[0]; + if (!file.name.match(/\.json$/i)) { + onError(`Invalid JSON filename ${file.name}`); + return; + } + const reader = new FileReader(); + reader.onload = () => { + const savedData = JSON.parse(reader.result); + const gameObject = {}; + gameObject.game_id = `(local) ${savedData.id}`; + gameObject.map_name = savedData.map; + gameObject.rules = savedData.rules; + gameObject.state_history = {}; + gameObject.message_history = {}; + gameObject.order_history = {}; + gameObject.result_history = {}; - // Load all saved phases (expect the latest one) to history fields. - for (let i = 0; i < savedData.phases.length - 1; ++i) { - const savedPhase = savedData.phases[i]; - const gameState = savedPhase.state; - const phaseOrders = savedPhase.orders || {}; - const phaseResults = savedPhase.results || {}; - const phaseMessages = {}; - if (savedPhase.messages) { - for (let message of savedPhase.messages) { - phaseMessages[message.time_sent] = message; + // Load all saved phases (expect the latest one) to history fields. + for (let i = 0; i < savedData.phases.length - 1; ++i) { + const savedPhase = savedData.phases[i]; + const gameState = savedPhase.state; + const phaseOrders = savedPhase.orders || {}; + const phaseResults = savedPhase.results || {}; + const phaseMessages = {}; + if (savedPhase.messages) { + for (let message of savedPhase.messages) { + phaseMessages[message.time_sent] = message; + } } + if (!gameState.name) + gameState.name = savedPhase.name; + gameObject.state_history[gameState.name] = gameState; + gameObject.message_history[gameState.name] = phaseMessages; + gameObject.order_history[gameState.name] = phaseOrders; + gameObject.result_history[gameState.name] = phaseResults; } - if (!gameState.name) - gameState.name = savedPhase.name; - gameObject.state_history[gameState.name] = gameState; - gameObject.message_history[gameState.name] = phaseMessages; - gameObject.order_history[gameState.name] = phaseOrders; - gameObject.result_history[gameState.name] = phaseResults; - } - // Load latest phase separately and use it later to define the current game phase. - const latestPhase = savedData.phases[savedData.phases.length - 1]; - const latestGameState = latestPhase.state; - const latestPhaseOrders = latestPhase.orders || {}; - const latestPhaseResults = latestPhase.results || {}; - const latestPhaseMessages = {}; - if (latestPhase.messages) { - for (let message of latestPhase.messages) { - latestPhaseMessages[message.time_sent] = message; + // Load latest phase separately and use it later to define the current game phase. + const latestPhase = savedData.phases[savedData.phases.length - 1]; + const latestGameState = latestPhase.state; + const latestPhaseOrders = latestPhase.orders || {}; + const latestPhaseResults = latestPhase.results || {}; + const latestPhaseMessages = {}; + if (latestPhase.messages) { + for (let message of latestPhase.messages) { + latestPhaseMessages[message.time_sent] = message; + } } - } - if (!latestGameState.name) - latestGameState.name = latestPhase.name; - // TODO: NB: What is latest phase in loaded JSON contains order results? Not sure if it is well handled. - gameObject.result_history[latestGameState.name] = latestPhaseResults; + if (!latestGameState.name) + latestGameState.name = latestPhase.name; + // TODO: NB: What if latest phase in loaded JSON contains order results? Not sure if it is well handled. + gameObject.result_history[latestGameState.name] = latestPhaseResults; - gameObject.messages = []; - gameObject.role = STRINGS.OBSERVER_TYPE; - gameObject.status = STRINGS.COMPLETED; - gameObject.timestamp_created = 0; - gameObject.deadline = 0; - gameObject.n_controls = 0; - gameObject.registration_password = ''; - const game = new Game(gameObject); + gameObject.messages = []; + gameObject.role = STRINGS.OBSERVER_TYPE; + gameObject.status = STRINGS.COMPLETED; + gameObject.timestamp_created = 0; + gameObject.deadline = 0; + gameObject.n_controls = 0; + gameObject.registration_password = ''; + const game = new Game(gameObject); - // Set game current phase and state using latest phase found in JSON file. - game.setPhaseData({ - name: latestGameState.name, - state: latestGameState, - orders: latestPhaseOrders, - messages: latestPhaseMessages - }); - onLoad(game); - }; - reader.readAsText(file); + // Set game current phase and state using latest phase found in JSON file. + game.setPhaseData({ + name: latestGameState.name, + state: latestGameState, + orders: latestPhaseOrders, + messages: latestPhaseMessages + }); + onLoad(game); + }; + reader.readAsText(file); + }); }); } diff --git a/diplomacy/web/src/gui/utils/province.js b/diplomacy/web/src/gui/utils/province.js index fe54a82..8e26c4b 100644 --- a/diplomacy/web/src/gui/utils/province.js +++ b/diplomacy/web/src/gui/utils/province.js @@ -114,4 +114,22 @@ export class Province { isWater() { return this.type === ProvinceType.WATER; } + + _id(id) { + return `_${id.toLowerCase()}`; + } + + getID(identifiers) { + let id = this._id(this.name); + if (!identifiers[id]) { + for (let alias of this.aliases) { + id = this._id(alias); + if (identifiers[id]) + break; + } + } + if (!identifiers[id] && this.isCoast()) + id = this.parent.getID(identifiers); + return id; + } } diff --git a/diplomacy/web/src/gui/utils/saveGameToDisk.js b/diplomacy/web/src/gui/utils/saveGameToDisk.js index aae69a4..3b7ff48 100644 --- a/diplomacy/web/src/gui/utils/saveGameToDisk.js +++ b/diplomacy/web/src/gui/utils/saveGameToDisk.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== export function saveGameToDisk(game, onError) { if (game.client) { game.client.save() diff --git a/diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js b/diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js index daaa461..e6477d2 100644 --- a/diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js +++ b/diplomacy/web/src/gui/wizards/gameCreation/gameCreationWizard.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from 'react'; import PropTypes from 'prop-types'; import {Panels} from "./panelList"; diff --git a/diplomacy/web/src/gui/wizards/gameCreation/mapList.js b/diplomacy/web/src/gui/wizards/gameCreation/mapList.js index 5d2c00a..388066f 100644 --- a/diplomacy/web/src/gui/wizards/gameCreation/mapList.js +++ b/diplomacy/web/src/gui/wizards/gameCreation/mapList.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== class VariantInfo { constructor(variantName, variantTitle) { this.name = variantName; diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js index 5c40f1c..432d1a8 100644 --- a/diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js +++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseMap.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import {Maps} from "./mapList"; import {FancyBox} from "../../components/fancyBox"; @@ -5,7 +21,7 @@ import PropTypes from "prop-types"; export class PanelChooseMap extends React.Component { render() { - const mapImg = require(`../../../maps/svg/${this.props.params.map.svgName()}.svg`); + const mapImg = require(`../../../diplomacy/maps/svg/${this.props.params.map.svgName()}.svg`); const mapEntries = []; let count = 0; for (let mapInfo of Maps) { diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js index 84a47a0..53ddd64 100644 --- a/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js +++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePlayers.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import {FancyBox} from "../../components/fancyBox"; import PropTypes from "prop-types"; diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js index 05154e6..1463805 100644 --- a/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js +++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChoosePower.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import {FancyBox} from "../../components/fancyBox"; import PropTypes from "prop-types"; diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js index e509158..a34a1d1 100644 --- a/diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js +++ b/diplomacy/web/src/gui/wizards/gameCreation/panelChooseSettings.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== import React from "react"; import {FancyBox} from "../../components/fancyBox"; import PropTypes from "prop-types"; diff --git a/diplomacy/web/src/gui/wizards/gameCreation/panelList.js b/diplomacy/web/src/gui/wizards/gameCreation/panelList.js index 0b6100c..d48749d 100644 --- a/diplomacy/web/src/gui/wizards/gameCreation/panelList.js +++ b/diplomacy/web/src/gui/wizards/gameCreation/panelList.js @@ -1,3 +1,19 @@ +// ============================================================================== +// 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 . +// ============================================================================== export const Panels = { CHOOSE_MAP: 0, CHOOSE_PLAYERS: 1, diff --git a/diplomacy/web/src/maps b/diplomacy/web/src/maps deleted file mode 120000 index 0aaf789..0000000 --- a/diplomacy/web/src/maps +++ /dev/null @@ -1 +0,0 @@ -../../maps/ \ No newline at end of file diff --git a/diplomacy/web/src/standard.svg b/diplomacy/web/src/standard.svg deleted file mode 120000 index abb544e..0000000 --- a/diplomacy/web/src/standard.svg +++ /dev/null @@ -1 +0,0 @@ -../../maps/svg/standard.svg \ No newline at end of file -- cgit v1.2.3