aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/web/src/gui/maps/common
diff options
context:
space:
mode:
authornotoraptor <stevenbocco@gmail.com>2019-08-14 12:22:22 -0400
committerPhilip Paquette <pcpaquette@gmail.com>2019-08-14 12:40:01 -0400
commit5c3bd9b3802e2001a7e77baf2911386135a03839 (patch)
treee641744650b05cddc85bc60c2d7e2d6fe2d88b47 /diplomacy/web/src/gui/maps/common
parent5acb4ff23be4757a49b234f93928f13c436b60c6 (diff)
[Web] Integrated new maps on the web interface
- Fixed bug with incorrect dislodged unit on pure map - [python] Make sure dummy powers are registered only for standard maps. - Hardcoded supply centers into SVG files. - Removed supply centers CSS classes. - Update positions for units and dislodged units on all maps. - Converted SVGs to React. - Removed "sym" classes and hardcode related styles into symbol definitions. - Reordered map list (standard at top, then other ones in alphabetical order) - Displayed + button for all maps and disable it for maps without variants. - Minified generated code when converting SVG files to React. - [web] Added ability to hide/display map abbreviations.
Diffstat (limited to 'diplomacy/web/src/gui/maps/common')
-rw-r--r--diplomacy/web/src/gui/maps/common/build.js57
-rw-r--r--diplomacy/web/src/gui/maps/common/common.js83
-rw-r--r--diplomacy/web/src/gui/maps/common/convoy.js103
-rw-r--r--diplomacy/web/src/gui/maps/common/disband.js47
-rw-r--r--diplomacy/web/src/gui/maps/common/equilateralTriangle.js122
-rw-r--r--diplomacy/web/src/gui/maps/common/hold.js47
-rw-r--r--diplomacy/web/src/gui/maps/common/move.js67
-rw-r--r--diplomacy/web/src/gui/maps/common/supportHold.js72
-rw-r--r--diplomacy/web/src/gui/maps/common/supportMove.js61
-rw-r--r--diplomacy/web/src/gui/maps/common/unit.js50
10 files changed, 709 insertions, 0 deletions
diff --git a/diplomacy/web/src/gui/maps/common/build.js b/diplomacy/web/src/gui/maps/common/build.js
new file mode 100644
index 0000000..765dbad
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/build.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 <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {ARMY, centerSymbolAroundUnit, FLEET} from "./common";
+import PropTypes from "prop-types";
+
+export class Build extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const SymbolSizes = this.props.symbolSizes;
+ const loc = this.props.loc;
+ const unit_type = this.props.unitType;
+ const build_symbol = 'BuildUnit';
+ const loc_x = Coordinates[loc].unit[0];
+ const loc_y = Coordinates[loc].unit[1];
+ const [build_loc_x, build_loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, loc, false, build_symbol);
+
+ const symbol = unit_type === 'A' ? ARMY : FLEET;
+ return (
+ <g>
+ <use x={build_loc_x}
+ y={build_loc_y}
+ height={SymbolSizes[build_symbol].height}
+ width={SymbolSizes[build_symbol].width}
+ href={`#${build_symbol}`}/>
+ <use x={loc_x}
+ y={loc_y}
+ height={SymbolSizes[symbol].height}
+ width={SymbolSizes[symbol].width}
+ href={`#${symbol}`}
+ className={`unit${this.props.powerName.toLowerCase()}`}/>
+ </g>
+ );
+ }
+}
+
+Build.propTypes = {
+ unitType: PropTypes.string.isRequired,
+ loc: PropTypes.string.isRequired,
+ powerName: PropTypes.string.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired
+};
diff --git a/diplomacy/web/src/gui/maps/common/common.js b/diplomacy/web/src/gui/maps/common/common.js
new file mode 100644
index 0000000..750f786
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/common.js
@@ -0,0 +1,83 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+
+export const ARMY = 'Army';
+export const FLEET = 'Fleet';
+
+export function offset(floatString, offset) {
+ return "" + (parseFloat(floatString) + offset);
+}
+
+export function setInfluence(classes, mapData, loc, power_name) {
+ const province = mapData.getProvince(loc);
+ if (!province)
+ throw new Error(`Unable to find province ${loc}`);
+ if (!['LAND', 'COAST'].includes(province.type))
+ return;
+ const id = province.getID(classes);
+ if (!id)
+ throw new Error(`Unable to find SVG path for loc ${id}`);
+ classes[id] = power_name ? power_name.toLowerCase() : 'nopower';
+}
+
+export 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;
+}
+
+export function parseLocation(txt) {
+ if (txt.length > 2 && txt[1] === ' ' && ['A', 'F'].includes(txt[0]))
+ return txt.substr(2);
+ return txt;
+}
+
+export function centerSymbolAroundUnit(coordinates, symbolSizes, loc, isDislodged, symbol) {
+ const key = isDislodged ? 'disl' : 'unit';
+ const unitKey = ARMY;
+ const [unit_x, unit_y] = coordinates[loc][key];
+ const unit_height = symbolSizes[unitKey].height;
+ const unit_width = symbolSizes[unitKey].width;
+ const symbol_height = symbolSizes[symbol].height;
+ const symbol_width = symbolSizes[symbol].width;
+ return [
+ `${(parseFloat(unit_x) + parseFloat(unit_width) / 2 - parseFloat(symbol_width) / 2)}`,
+ `${(parseFloat(unit_y) + parseFloat(unit_height) / 2 - parseFloat(symbol_height) / 2)}`
+ ];
+}
+
+export function getUnitCenter(coordinates, symbolSizes, loc, isDislodged) {
+ const key = isDislodged ? 'disl' : 'unit';
+ const unitKey = ARMY;
+ const [unit_x, unit_y] = coordinates[loc][key];
+ const unit_height = symbolSizes[unitKey].height;
+ const unit_width = symbolSizes[unitKey].width;
+ return [
+ `${parseFloat(unit_x) + parseFloat(unit_width) / 2}`,
+ `${parseFloat(unit_y) + parseFloat(unit_height) / 2}`
+ ];
+}
+
+export function plainStrokeWidth(symbolSizes) {
+ return parseFloat(symbolSizes.Stroke.height);
+}
+
+export function coloredStrokeWidth(symbolSizes) {
+ return parseFloat(symbolSizes.Stroke.width);
+}
diff --git a/diplomacy/web/src/gui/maps/common/convoy.js b/diplomacy/web/src/gui/maps/common/convoy.js
new file mode 100644
index 0000000..c966161
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/convoy.js
@@ -0,0 +1,103 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {ARMY, centerSymbolAroundUnit, coloredStrokeWidth, getUnitCenter} from "./common";
+import {EquilateralTriangle} from "./equilateralTriangle";
+import PropTypes from "prop-types";
+
+export class Convoy extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const SymbolSizes = this.props.symbolSizes;
+ const Colors = this.props.colors;
+ const loc = this.props.loc;
+ const src_loc = this.props.srcLoc;
+ const dest_loc = this.props.dstLoc;
+
+ const symbol = 'ConvoyTriangle';
+ let [symbol_loc_x, symbol_loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, src_loc, false, symbol);
+ const symbol_height = parseFloat(SymbolSizes[symbol].height);
+ const symbol_width = parseFloat(SymbolSizes[symbol].width);
+ const triangle = new EquilateralTriangle(
+ parseFloat(symbol_loc_x) + symbol_width / 2,
+ parseFloat(symbol_loc_y),
+ parseFloat(symbol_loc_x) + symbol_width,
+ parseFloat(symbol_loc_y) + symbol_height,
+ parseFloat(symbol_loc_x),
+ parseFloat(symbol_loc_y) + symbol_height
+ );
+ symbol_loc_y = '' + (parseFloat(symbol_loc_y) - symbol_height / 6);
+ const [loc_x, loc_y] = getUnitCenter(Coordinates, SymbolSizes, loc, false);
+ const [src_loc_x, src_loc_y] = getUnitCenter(Coordinates, SymbolSizes, src_loc, false);
+ let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, false);
+
+ const [src_loc_x_1, src_loc_y_1] = triangle.intersection(loc_x, loc_y);
+ const [src_loc_x_2, src_loc_y_2] = triangle.intersection(dest_loc_x, dest_loc_y);
+
+ const dest_delta_x = dest_loc_x - src_loc_x;
+ const dest_delta_y = dest_loc_y - src_loc_y;
+ const dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y);
+ const delta_dec = parseFloat(SymbolSizes[ARMY].width) / 2 + 2 * coloredStrokeWidth(SymbolSizes);
+ dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (dest_vector_length - delta_dec) / dest_vector_length * dest_delta_x) * 100.) / 100.;
+ dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (dest_vector_length - delta_dec) / dest_vector_length * dest_delta_y) * 100.) / 100.;
+
+ return (
+ <g stroke={Colors[this.props.powerName]}>
+ <line x1={loc_x}
+ y1={loc_y}
+ x2={src_loc_x_1}
+ y2={src_loc_y_1}
+ className={'shadowdash'}/>
+ <line x1={src_loc_x_2}
+ y1={src_loc_y_2}
+ x2={dest_loc_x}
+ y2={dest_loc_y}
+ className={'shadowdash'}/>
+ <line x1={loc_x}
+ y1={loc_y}
+ x2={src_loc_x_1}
+ y2={src_loc_y_1}
+ className={'convoyorder'}
+ stroke={Colors[this.props.powerName]}/>
+ <line x1={src_loc_x_2}
+ y1={src_loc_y_2}
+ x2={dest_loc_x}
+ y2={dest_loc_y}
+ className={'convoyorder'}
+ markerEnd={'url(#arrow)'}
+ stroke={Colors[this.props.powerName]}/>
+ <use
+ x={symbol_loc_x}
+ y={symbol_loc_y}
+ width={'' + symbol_width}
+ height={'' + symbol_height}
+ href={`#${symbol}`}
+ />
+ </g>
+ );
+ }
+}
+
+Convoy.propTypes = {
+ loc: PropTypes.string.isRequired,
+ srcLoc: PropTypes.string.isRequired,
+ dstLoc: PropTypes.string.isRequired,
+ powerName: PropTypes.string.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired,
+ colors: PropTypes.object.isRequired
+};
diff --git a/diplomacy/web/src/gui/maps/common/disband.js b/diplomacy/web/src/gui/maps/common/disband.js
new file mode 100644
index 0000000..97d5c04
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/disband.js
@@ -0,0 +1,47 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {centerSymbolAroundUnit} from "./common";
+import PropTypes from "prop-types";
+
+export class Disband extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const SymbolSizes = this.props.symbolSizes;
+ const loc = this.props.loc;
+ const phaseType = this.props.phaseType;
+ const symbol = 'RemoveUnit';
+ const [loc_x, loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, loc, phaseType === 'R', symbol);
+ return (
+ <g>
+ <use x={loc_x}
+ y={loc_y}
+ height={SymbolSizes[symbol].height}
+ width={SymbolSizes[symbol].width}
+ href={`#${symbol}`}
+ />
+ </g>
+ );
+ }
+}
+
+Disband.propTypes = {
+ loc: PropTypes.string.isRequired,
+ phaseType: PropTypes.string.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired
+};
diff --git a/diplomacy/web/src/gui/maps/common/equilateralTriangle.js b/diplomacy/web/src/gui/maps/common/equilateralTriangle.js
new file mode 100644
index 0000000..8c433ea
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/equilateralTriangle.js
@@ -0,0 +1,122 @@
+export class EquilateralTriangle {
+ /** Helper class that represent an equilateral triangle.;
+ Used to compute intersection of a line with a side of convoy symbol, which is an equilateral triangle. **/
+ constructor(x_top, y_top, x_right, y_right, x_left, y_left) {
+ this.x_A = x_top;
+ this.y_A = y_top;
+ this.x_B = x_right;
+ this.y_B = y_right;
+ this.x_C = x_left;
+ this.y_C = y_left;
+ this.h = this.y_B - this.y_A;
+ this.x_O = this.x_A;
+ this.y_O = this.y_A + 2 * this.h / 3;
+ this.line_AB_a = (this.y_B - this.y_A) / (this.x_B - this.x_A);
+ this.line_AB_b = this.y_B - this.x_B * this.line_AB_a;
+ this.line_AC_a = (this.y_C - this.y_A) / (this.x_C - this.x_A);
+ this.line_AC_b = this.y_C - this.x_C * this.line_AC_a;
+ }
+
+ __line_OM(x_M, y_M) {
+ const a = (y_M - this.y_O) / (x_M - this.x_O);
+ const b = y_M - a * x_M;
+ return [a, b];
+ }
+
+ __intersection_with_AB(x_M, y_M) {
+ const [a, b] = [this.line_AB_a, this.line_AB_b];
+ let x = null;
+ if (x_M === this.x_O) {
+ x = x_M;
+ } else {
+ const [u, v] = this.__line_OM(x_M, y_M);
+ if (a === u)
+ return [null, null];
+ x = (v - b) / (a - u);
+ }
+ const y = a * x + b;
+ if (this.x_A <= x && x <= this.x_B && this.y_A <= y && y <= this.y_B)
+ return [x, y];
+ return [null, null];
+ }
+
+ __intersection_with_AC(x_M, y_M) {
+ const [a, b] = [this.line_AC_a, this.line_AC_b];
+ let x = null;
+ if (x_M === this.x_O) {
+ x = x_M;
+ } else {
+ const [u, v] = this.__line_OM(x_M, y_M);
+ if (a === u)
+ return [null, null];
+ x = (v - b) / (a - u);
+ }
+ const y = a * x + b;
+ if (this.x_C <= x && x <= this.x_A && this.y_A <= y && y <= this.y_C)
+ return [x, y];
+ return [null, null];
+ }
+
+ __intersection_with_BC(x_M, y_M) {
+ const y = this.y_C;
+ let x = null;
+ if (x_M === this.x_O) {
+ x = x_M;
+ } else {
+ const [a, b] = this.__line_OM(x_M, y_M);
+ if (a === 0)
+ return [null, null];
+ x = (y - b) / a;
+ }
+ if (this.x_C <= x && x <= this.x_A)
+ return [x, y];
+ return [null, null];
+ }
+
+ intersection(x_M, y_M) {
+ if (this.x_O === x_M && this.y_O === y_M)
+ return [x_M, y_M];
+ if (this.x_O === x_M) {
+ if (y_M < this.y_O)
+ return [x_M, this.y_A];
+ else {
+ // vertical line intersects BC;
+ return [x_M, this.y_C];
+ }
+ } else if (this.y_O === y_M) {
+ let a = null;
+ let b = null;
+ if (x_M < this.x_O) {
+ // horizontal line intersects AC;
+ [a, b] = [this.line_AC_a, this.line_AC_b];
+ } else {
+ // horizontal line intersects AB;
+ [a, b] = [this.line_AB_a, this.line_AB_b];
+ }
+ const x = (y_M - b) / a;
+ return [x, y_M];
+ } else {
+ // get nearest point in intersections with AB, AC, BC;
+ const [p1_x, p1_y] = this.__intersection_with_AB(x_M, y_M);
+ const [p2_x, p2_y] = this.__intersection_with_AC(x_M, y_M);
+ const [p3_x, p3_y] = this.__intersection_with_BC(x_M, y_M);
+ const distances = [];
+ if (p1_x !== null) {
+ const d1 = Math.sqrt((p1_x - x_M) * (p1_x - x_M) + (p1_y - y_M) * (p1_y - y_M));
+ distances.push([d1, p1_x, p1_y]);
+ }
+ if (p2_x !== null) {
+ const d2 = Math.sqrt((p2_x - x_M) * (p2_x - x_M) + (p2_y - y_M) * (p2_y - y_M));
+ distances.push([d2, p2_x, p2_y]);
+ }
+ if (p3_x !== null) {
+ const d3 = Math.sqrt((p3_x - x_M) * (p3_x - x_M) + (p3_y - y_M) * (p3_y - y_M));
+ distances.push([d3, p3_x, p3_y]);
+ }
+ distances.sort();
+ const output = distances[0];
+ output.shift();
+ return output;
+ }
+ }
+}
diff --git a/diplomacy/web/src/gui/maps/common/hold.js b/diplomacy/web/src/gui/maps/common/hold.js
new file mode 100644
index 0000000..ce77ec5
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/hold.js
@@ -0,0 +1,47 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {centerSymbolAroundUnit} from "./common";
+import PropTypes from "prop-types";
+
+export class Hold extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const Colors = this.props.colors;
+ const SymbolSizes = this.props.symbolSizes;
+ const symbol = 'HoldUnit';
+ const [loc_x, loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, this.props.loc, false, symbol);
+ return (
+ <g stroke={Colors[this.props.powerName]}>
+ <use
+ x={loc_x}
+ y={loc_y}
+ width={SymbolSizes[symbol].width}
+ height={SymbolSizes[symbol].height}
+ href={`#${symbol}`}/>
+ </g>
+ );
+ }
+}
+
+Hold.propTypes = {
+ loc: PropTypes.string.isRequired,
+ powerName: PropTypes.string.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired,
+ colors: PropTypes.object.isRequired
+};
diff --git a/diplomacy/web/src/gui/maps/common/move.js b/diplomacy/web/src/gui/maps/common/move.js
new file mode 100644
index 0000000..cefb6de
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/move.js
@@ -0,0 +1,67 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {ARMY, coloredStrokeWidth, getUnitCenter, plainStrokeWidth} from "./common";
+import PropTypes from "prop-types";
+
+export class Move extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const SymbolSizes = this.props.symbolSizes;
+ const Colors = this.props.colors;
+ const src_loc = this.props.srcLoc;
+ const dest_loc = this.props.dstLoc;
+ const is_dislodged = this.props.phaseType === 'R';
+ const [src_loc_x, src_loc_y] = getUnitCenter(Coordinates, SymbolSizes, src_loc, is_dislodged);
+ let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, is_dislodged);
+ // Adjusting destination
+ const delta_x = dest_loc_x - src_loc_x;
+ const delta_y = dest_loc_y - src_loc_y;
+ const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
+ const delta_dec = parseFloat(SymbolSizes[ARMY].width) / 2 + 2 * coloredStrokeWidth(SymbolSizes);
+ dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - delta_dec) / vector_length * delta_x) * 100.) / 100.;
+ dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - delta_dec) / vector_length * delta_y) * 100.) / 100.;
+ return (
+ <g>
+ <line x1={src_loc_x}
+ y1={src_loc_y}
+ x2={dest_loc_x}
+ y2={dest_loc_y}
+ className={'varwidthshadow'}
+ strokeWidth={'' + plainStrokeWidth(SymbolSizes)}/>
+ <line x1={src_loc_x}
+ y1={src_loc_y}
+ x2={dest_loc_x}
+ y2={dest_loc_y}
+ className={'varwidthorder'}
+ markerEnd={'url(#arrow)'}
+ stroke={Colors[this.props.powerName]}
+ strokeWidth={'' + coloredStrokeWidth(SymbolSizes)}/>
+ </g>
+ );
+ }
+}
+
+Move.propTypes = {
+ srcLoc: PropTypes.string.isRequired,
+ dstLoc: PropTypes.string.isRequired,
+ powerName: PropTypes.string.isRequired,
+ phaseType: PropTypes.string.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired,
+ colors: PropTypes.object.isRequired
+};
diff --git a/diplomacy/web/src/gui/maps/common/supportHold.js b/diplomacy/web/src/gui/maps/common/supportHold.js
new file mode 100644
index 0000000..9b350d2
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/supportHold.js
@@ -0,0 +1,72 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {centerSymbolAroundUnit, getUnitCenter} from "./common";
+import PropTypes from "prop-types";
+
+export class SupportHold extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const SymbolSizes = this.props.symbolSizes;
+ const Colors = this.props.colors;
+ const loc = this.props.loc;
+ const dest_loc = this.props.dstLoc;
+ const symbol = 'SupportHoldUnit';
+ const [symbol_loc_x, symbol_loc_y] = centerSymbolAroundUnit(Coordinates, SymbolSizes, dest_loc, false, symbol);
+ const [loc_x, loc_y] = getUnitCenter(Coordinates, SymbolSizes, loc, false);
+ let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, false);
+
+ const delta_x = dest_loc_x - loc_x;
+ const delta_y = dest_loc_y - loc_y;
+ const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
+ const delta_dec = parseFloat(SymbolSizes[symbol].height) / 2;
+ dest_loc_x = '' + Math.round((parseFloat(loc_x) + (vector_length - delta_dec) / vector_length * delta_x) * 100.) / 100.;
+ dest_loc_y = '' + Math.round((parseFloat(loc_y) + (vector_length - delta_dec) / vector_length * delta_y) * 100.) / 100.;
+
+ return (
+ <g stroke={Colors[this.props.powerName]}>
+ <line x1={loc_x}
+ y1={loc_y}
+ x2={dest_loc_x}
+ y2={dest_loc_y}
+ className={'shadowdash'}/>
+ <line x1={loc_x}
+ y1={loc_y}
+ x2={dest_loc_x}
+ y2={dest_loc_y}
+ className={'supportorder'}
+ stroke={Colors[this.props.powerName]}/>
+ <use
+ x={symbol_loc_x}
+ y={symbol_loc_y}
+ width={SymbolSizes[symbol].width}
+ height={SymbolSizes[symbol].height}
+ href={`#${symbol}`}
+ />
+ </g>
+ );
+ }
+}
+
+SupportHold.propTypes = {
+ loc: PropTypes.string.isRequired,
+ dstLoc: PropTypes.string.isRequired,
+ powerName: PropTypes.string.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired,
+ colors: PropTypes.object.isRequired
+};
diff --git a/diplomacy/web/src/gui/maps/common/supportMove.js b/diplomacy/web/src/gui/maps/common/supportMove.js
new file mode 100644
index 0000000..e52e37f
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/supportMove.js
@@ -0,0 +1,61 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {ARMY, coloredStrokeWidth, getUnitCenter} from "./common";
+import PropTypes from "prop-types";
+
+export class SupportMove extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const SymbolSizes = this.props.symbolSizes;
+ const Colors = this.props.colors;
+ const loc = this.props.loc;
+ const src_loc = this.props.srcLoc;
+ const dest_loc = this.props.dstLoc;
+ const [loc_x, loc_y] = getUnitCenter(Coordinates, SymbolSizes, loc, false);
+ const [src_loc_x, src_loc_y] = getUnitCenter(Coordinates, SymbolSizes, src_loc, false);
+ let [dest_loc_x, dest_loc_y] = getUnitCenter(Coordinates, SymbolSizes, dest_loc, false);
+
+ // Adjusting destination
+ const delta_x = dest_loc_x - src_loc_x;
+ const delta_y = dest_loc_y - src_loc_y;
+ const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
+ const delta_dec = parseFloat(SymbolSizes[ARMY].width) / 2 + 2 * coloredStrokeWidth(SymbolSizes);
+ dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - delta_dec) / vector_length * delta_x) * 100.) / 100.;
+ dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - delta_dec) / vector_length * delta_y) * 100.) / 100.;
+ return (
+ <g>
+ <path className={'shadowdash'}
+ 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 className={'supportorder'}
+ markerEnd={'url(#arrow)'}
+ stroke={Colors[this.props.powerName]}
+ 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>
+ );
+ }
+}
+
+SupportMove.propTypes = {
+ loc: PropTypes.string.isRequired,
+ srcLoc: PropTypes.string.isRequired,
+ dstLoc: PropTypes.string.isRequired,
+ powerName: PropTypes.string.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired,
+ colors: PropTypes.object.isRequired
+};
diff --git a/diplomacy/web/src/gui/maps/common/unit.js b/diplomacy/web/src/gui/maps/common/unit.js
new file mode 100644
index 0000000..277a591
--- /dev/null
+++ b/diplomacy/web/src/gui/maps/common/unit.js
@@ -0,0 +1,50 @@
+// ==============================================================================
+// Copyright (C) 2019 - Philip Paquette, Steven Bocco
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License along
+// with this program. If not, see <https://www.gnu.org/licenses/>.
+// ==============================================================================
+import React from "react";
+import {ARMY, FLEET} from "./common";
+import PropTypes from "prop-types";
+
+export class Unit extends React.Component {
+ render() {
+ const Coordinates = this.props.coordinates;
+ const SymbolSizes = this.props.symbolSizes;
+ 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 = Coordinates[loc][dislogged_type][0];
+ const loc_y = Coordinates[loc][dislogged_type][1];
+ return (
+ <use href={`#${this.props.isDislodged ? 'Dislodged' : ''}${symbol}`}
+ x={loc_x}
+ y={loc_y}
+ id={`${this.props.isDislodged ? 'dislodged_' : ''}unit_${loc}`}
+ width={SymbolSizes[symbol].width}
+ height={SymbolSizes[symbol].height}
+ className={`unit${this.props.powerName.toLowerCase()}`}/>
+ );
+ }
+}
+
+Unit.propTypes = {
+ unit: PropTypes.string.isRequired,
+ powerName: PropTypes.string.isRequired,
+ isDislodged: PropTypes.bool.isRequired,
+ coordinates: PropTypes.object.isRequired,
+ symbolSizes: PropTypes.object.isRequired
+};