...
This commit is contained in:
parent
4a55f5c11f
commit
a6961e1900
@ -1,11 +1,9 @@
|
|||||||
import { random2048, generate_prime } from "./random_primes.js";
|
import { random2048, generate_prime } from "./random_primes.js";
|
||||||
import { mod_exp } from "./math.js";
|
import { mod_exp } from "./math.js";
|
||||||
|
|
||||||
let p, q, pubKey, privKey;
|
export class PaillierPubKey {
|
||||||
|
constructor(n) {
|
||||||
class PubKey {
|
this.n = n;
|
||||||
constructor(p, q) {
|
|
||||||
this.n = p * q;
|
|
||||||
// this.g = this.n + 1n;
|
// this.g = this.n + 1n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,9 +21,19 @@ class PubKey {
|
|||||||
// Compute g^m r^n from crt
|
// Compute g^m r^n from crt
|
||||||
return (gm * mod_exp(r, this.n, this.n ** 2n)) % this.n ** 2n;
|
return (gm * mod_exp(r, this.n, this.n ** 2n)) % this.n ** 2n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
n: "0x" + this.n.toString(16),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(data) {
|
||||||
|
return new PaillierPubKey(BigInt(data.n));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrivKey {
|
class PaillierPrivKey {
|
||||||
constructor(p, q) {
|
constructor(p, q) {
|
||||||
this.n = p * q;
|
this.n = p * q;
|
||||||
this.lambda = (p - 1n) * (q - 1n);
|
this.lambda = (p - 1n) * (q - 1n);
|
||||||
@ -40,6 +48,8 @@ class PrivKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generate_keypair() {
|
export function generate_keypair() {
|
||||||
|
let p, q, pubKey, privKey;
|
||||||
|
|
||||||
if (window.sessionStorage.getItem("p") === null) {
|
if (window.sessionStorage.getItem("p") === null) {
|
||||||
p = generate_prime();
|
p = generate_prime();
|
||||||
window.sessionStorage.setItem("p", p);
|
window.sessionStorage.setItem("p", p);
|
||||||
@ -54,8 +64,8 @@ export function generate_keypair() {
|
|||||||
q = BigInt(window.sessionStorage.getItem("q"));
|
q = BigInt(window.sessionStorage.getItem("q"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKey = new PubKey(p, q);
|
pubKey = new PaillierPubKey(p * q);
|
||||||
privKey = new PrivKey(p, q);
|
privKey = new PaillierPrivKey(p, q);
|
||||||
|
|
||||||
return { pubKey, privKey };
|
return { pubKey, privKey };
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { generate_prime } from "./random_primes.js";
|
import { generate_prime } from "./random_primes.js";
|
||||||
import { mod_exp, mod_inv } from "./math.js";
|
import { mod_exp, mod_inv } from "./math.js";
|
||||||
|
|
||||||
let p, q, pubKey, privKey;
|
|
||||||
|
|
||||||
export class RsaPubKey {
|
export class RsaPubKey {
|
||||||
constructor(n, e) {
|
constructor(n, e) {
|
||||||
this.n = n;
|
this.n = n;
|
||||||
@ -37,6 +35,8 @@ class RsaPrivKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generate_rsa_keypair() {
|
export function generate_rsa_keypair() {
|
||||||
|
let p, q, pubKey, privKey;
|
||||||
|
|
||||||
if (window.sessionStorage.getItem("rsa_p") === null) {
|
if (window.sessionStorage.getItem("rsa_p") === null) {
|
||||||
p = generate_prime();
|
p = generate_prime();
|
||||||
window.sessionStorage.setItem("rsa_p", p);
|
window.sessionStorage.setItem("rsa_p", p);
|
||||||
|
@ -31,8 +31,21 @@ function updateMapDom() {
|
|||||||
document.querySelectorAll(".fortify, .attack").forEach((e) => {
|
document.querySelectorAll(".fortify, .attack").forEach((e) => {
|
||||||
e.classList.add("hidden");
|
e.classList.add("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Region.allRegionsClaimed()) {
|
||||||
|
document.querySelectorAll(".claim").forEach((e) => {
|
||||||
|
e.classList.add("hidden");
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".reinforce").forEach((e) => {
|
||||||
|
e.classList.remove("hidden");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll(".reinforce").forEach((e) => {
|
||||||
|
e.classList.add("hidden");
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (game.isPlaying()) {
|
} else if (game.isPlaying()) {
|
||||||
document.querySelectorAll(".node button").forEach((e) => {
|
document.querySelectorAll(".node button:not(.claim)").forEach((e) => {
|
||||||
e.classList.remove("hidden");
|
e.classList.remove("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,7 +58,7 @@ function updateMapDom() {
|
|||||||
|
|
||||||
for (let region of Region.getAllRegions()) {
|
for (let region of Region.getAllRegions()) {
|
||||||
const element = document.querySelector(`.node[data-name=${region.name}]`);
|
const element = document.querySelector(`.node[data-name=${region.name}]`);
|
||||||
element.querySelector(".strength").textContent = region.strength || "";
|
element.querySelector(".strength").textContent = region.displayStrength();
|
||||||
element.style.backgroundColor =
|
element.style.backgroundColor =
|
||||||
region.owner === null ? "white" : region.owner.getColor();
|
region.owner === null ? "white" : region.owner.getColor();
|
||||||
}
|
}
|
||||||
@ -119,13 +132,20 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
socket.emit("message", Packet.createEndTurn());
|
socket.emit("message", Packet.createEndTurn());
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll(".reinforce").forEach((el) =>
|
document.querySelectorAll(".claim").forEach((el) =>
|
||||||
el.addEventListener("click", (ev) => {
|
el.addEventListener("click", (ev) => {
|
||||||
let region = ev.target.closest(".node").dataset.name;
|
let region = ev.target.closest(".node").dataset.name;
|
||||||
socket.emit("message", Packet.createRegionClaim(region));
|
socket.emit("message", Packet.createRegionClaim(region));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
document.querySelectorAll(".reinforce").forEach((el) =>
|
||||||
|
el.addEventListener("click", (ev) => {
|
||||||
|
let region = ev.target.closest(".node").dataset.name;
|
||||||
|
socket.emit("message", Packet.createReinforce(region));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
document.querySelectorAll(".attack").forEach((el) =>
|
document.querySelectorAll(".attack").forEach((el) =>
|
||||||
el.addEventListener("click", (ev) => {
|
el.addEventListener("click", (ev) => {
|
||||||
let modal = document.querySelector("#modal");
|
let modal = document.querySelector("#modal");
|
||||||
|
@ -40,11 +40,11 @@ export class Game {
|
|||||||
return Object.values(this.players).filter((p) => p.isPlaying)[0];
|
return Object.values(this.players).filter((p) => p.isPlaying)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlayer(id, is_us, pubkey) {
|
addPlayer(id, is_us, rsa_key, paillier_key) {
|
||||||
let is_new = this.players[id] === undefined;
|
let is_new = this.players[id] === undefined;
|
||||||
|
|
||||||
if (this.isWaiting()) {
|
if (this.isWaiting()) {
|
||||||
this.players[id] = new Player(id, is_us, pubkey);
|
this.players[id] = new Player(id, is_us, rsa_key, paillier_key);
|
||||||
if (is_us) {
|
if (is_us) {
|
||||||
this.us = this.players[id];
|
this.us = this.players[id];
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import "./dom.js";
|
|||||||
|
|
||||||
export const ID = window.crypto.randomUUID();
|
export const ID = window.crypto.randomUUID();
|
||||||
export const game = new Game();
|
export const game = new Game();
|
||||||
|
window.game = game;
|
||||||
export let socket;
|
export let socket;
|
||||||
export let random;
|
export let random;
|
||||||
let barrier;
|
let barrier;
|
||||||
@ -27,13 +28,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
window.console.log("Connected!");
|
window.console.log("Connected!");
|
||||||
window.console.log(`We are: ${ID}`);
|
window.console.log(`We are: ${ID}`);
|
||||||
socket.emit("message", Packet.createAnnounce(window.rsa));
|
socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier));
|
||||||
game.addPlayer(ID, true, window.rsa.pubKey);
|
game.addPlayer(ID, true, window.rsa.pubKey, window.paillier.pubKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("message", async (packet) => {
|
socket.on("message", async (packet) => {
|
||||||
let data = packet.payload;
|
let data = packet.payload;
|
||||||
window.console.log(data);
|
if (data.type !== "KEEPALIVE") window.console.log(data);
|
||||||
|
|
||||||
const sender = game.players[data.author];
|
const sender = game.players[data.author];
|
||||||
if (sender === undefined) {
|
if (sender === undefined) {
|
||||||
@ -91,11 +92,11 @@ document.addEventListener("ANNOUNCE", (ev) => {
|
|||||||
const data = ev.detail;
|
const data = ev.detail;
|
||||||
if (data.author === ID) return;
|
if (data.author === ID) return;
|
||||||
|
|
||||||
let is_new = game.addPlayer(data.author, false, data.rsaPubKey);
|
let is_new = game.addPlayer(data.author, false, data.rsaPubKey, data.paillierPubKey);
|
||||||
|
|
||||||
// When a new player is seen, all announce to ensure they know all players.
|
// When a new player is seen, all announce to ensure they know all players.
|
||||||
if (is_new) {
|
if (is_new) {
|
||||||
socket.emit("message", Packet.createAnnounce(window.rsa));
|
socket.emit("message", Packet.createAnnounce(window.rsa, window.paillier));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,11 +14,28 @@ class Continent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Strength {
|
||||||
|
constructor(cyphertext) {
|
||||||
|
this.cyphertext = cyphertext;
|
||||||
|
this.assumedStrength = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(amount) {
|
||||||
|
if (this.cyphertext === null) {
|
||||||
|
this.cyphertext = amount;
|
||||||
|
} else {
|
||||||
|
this.cyphertext *= amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.assumedStrength = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Region {
|
export class Region {
|
||||||
constructor(name, continent) {
|
constructor(name, continent) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.owner = null;
|
this.owner = null;
|
||||||
this.strength = 0;
|
this.strength = new Strength(null);
|
||||||
this.neighbours = new Set();
|
this.neighbours = new Set();
|
||||||
this.continent = continent;
|
this.continent = continent;
|
||||||
|
|
||||||
@ -36,13 +53,17 @@ export class Region {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo fix
|
||||||
static reinforcementsRemaining() {
|
static reinforcementsRemaining() {
|
||||||
if (allPlaced) {
|
if (allPlaced) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
let totalStrength = Object.values(REGIONS)
|
let totalStrength = Object.values(REGIONS)
|
||||||
.filter((region) => region.owner === game.us)
|
.filter((region) => region.owner === game.us)
|
||||||
.reduce((counter, region) => counter + region.strength, 0);
|
.reduce(
|
||||||
|
(counter, region) => counter + region.strength.assumedStrength,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
return _REINFORCEMENT_MULTIPLIER * (10 - game.playerCount()) - totalStrength;
|
return _REINFORCEMENT_MULTIPLIER * (10 - game.playerCount()) - totalStrength;
|
||||||
}
|
}
|
||||||
@ -53,7 +74,7 @@ export class Region {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
let totalStrength = Object.values(REGIONS).reduce(
|
let totalStrength = Object.values(REGIONS).reduce(
|
||||||
(counter, region) => counter + region.strength,
|
(counter, region) => counter + region.strength.assumedStrength,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -76,11 +97,28 @@ export class Region {
|
|||||||
|
|
||||||
claim(player) {
|
claim(player) {
|
||||||
this.owner = player;
|
this.owner = player;
|
||||||
this.strength = 1;
|
this.strength.update(player.paillierPubKey.encrypt(1n));
|
||||||
|
this.strength.assumedStrength = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
reinforce(amount) {
|
reinforce(amount) {
|
||||||
this.strength += amount;
|
this.strength.update(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
isClaimed() {
|
||||||
|
return this.strength.cyphertext !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayStrength() {
|
||||||
|
if (this.owner === null) {
|
||||||
|
return "";
|
||||||
|
} else if (this.owner === game.us) {
|
||||||
|
return window.paillier.privKey.decrypt(this.strength.cyphertext).toString();
|
||||||
|
} else if (this.strength.assumedStrength !== null) {
|
||||||
|
return `${this.strength.assumedStrength}`;
|
||||||
|
} else {
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ID } from "./main.js";
|
import { ID, game } from "./main.js";
|
||||||
|
|
||||||
export class Packet {
|
export class Packet {
|
||||||
static _createBase(name) {
|
static _createBase(name) {
|
||||||
@ -30,10 +30,11 @@ export class Packet {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static createAnnounce(rsaKeyPair) {
|
static createAnnounce(rsaKeyPair, paillierKeyPair) {
|
||||||
return this._sign({
|
return this._sign({
|
||||||
...this._createBase("ANNOUNCE"),
|
...this._createBase("ANNOUNCE"),
|
||||||
rsaPubKey: rsaKeyPair.pubKey.toJSON(),
|
rsaPubKey: rsaKeyPair.pubKey.toJSON(),
|
||||||
|
paillierPubKey: paillierKeyPair.pubKey.toJSON(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +84,25 @@ export class Packet {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createReinforce(region) {
|
||||||
|
// todo cache some of these, possibly by pregenerating cyphertexts in a web worker
|
||||||
|
let regions = {};
|
||||||
|
for (let ourRegion of game.us.getRegions()) {
|
||||||
|
if (ourRegion.name !== region) {
|
||||||
|
regions[ourRegion.name] =
|
||||||
|
"0x" + game.us.paillierPubKey.encrypt(0n).toString(16);
|
||||||
|
} else {
|
||||||
|
regions[ourRegion.name] =
|
||||||
|
"0x" + game.us.paillierPubKey.encrypt(1n).toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._sign({
|
||||||
|
...this._createBase("ACT"),
|
||||||
|
regions: regions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static createAction(action, startRegion, endRegion, amount) {
|
static createAction(action, startRegion, endRegion, amount) {
|
||||||
return this._sign({
|
return this._sign({
|
||||||
...this._createBase("ACT"),
|
...this._createBase("ACT"),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Packet } from "./packet.js";
|
import { Packet } from "./packet.js";
|
||||||
import { socket, game, random } from "./main.js";
|
import { socket, game, random } from "./main.js";
|
||||||
import { RsaPubKey } from "../crypto/rsa.js";
|
import { RsaPubKey } from "../crypto/rsa.js";
|
||||||
|
import { PaillierPubKey } from "../crypto/paillier.js";
|
||||||
import { Region } from "./map.js";
|
import { Region } from "./map.js";
|
||||||
import { showDefenseDom } from "./dom.js";
|
import { showDefenseDom } from "./dom.js";
|
||||||
|
|
||||||
@ -14,11 +15,12 @@ const PHASE_FORTIFY = 3;
|
|||||||
let totalDice = 0;
|
let totalDice = 0;
|
||||||
|
|
||||||
export class Player {
|
export class Player {
|
||||||
constructor(id, local, pubkey) {
|
constructor(id, local, rsa_key, paillier_key) {
|
||||||
this.timeout = null;
|
this.timeout = null;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.rsaPubKey = RsaPubKey.fromJSON(pubkey);
|
this.rsaPubKey = RsaPubKey.fromJSON(rsa_key);
|
||||||
|
this.paillierPubKey = PaillierPubKey.fromJSON(paillier_key);
|
||||||
this.lastPacket = 0;
|
this.lastPacket = 0;
|
||||||
|
|
||||||
// Data which is reset every turn
|
// Data which is reset every turn
|
||||||
@ -64,6 +66,13 @@ export class Player {
|
|||||||
this.color = `hsl(${randomColor} 57% 50%)`;
|
this.color = `hsl(${randomColor} 57% 50%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all regions controlled by this player.
|
||||||
|
*/
|
||||||
|
getRegions() {
|
||||||
|
return Region.getAllRegions().filter((r) => r.owner === this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Claim a region of the map.
|
* Claim a region of the map.
|
||||||
*
|
*
|
||||||
@ -83,17 +92,20 @@ export class Player {
|
|||||||
/**
|
/**
|
||||||
* Reinforce a region of the map.
|
* Reinforce a region of the map.
|
||||||
*
|
*
|
||||||
|
* Todo. Check object before committing changes.
|
||||||
|
*
|
||||||
* @param data Data received via socket.
|
* @param data Data received via socket.
|
||||||
*/
|
*/
|
||||||
reinforce(data) {
|
reinforce(data) {
|
||||||
let region = Region.getRegion(data.region);
|
for (let regionName of Object.keys(data.regions)) {
|
||||||
|
let region = Region.getRegion(regionName);
|
||||||
|
|
||||||
if (region.owner === this) {
|
if (region.owner === this) {
|
||||||
region.reinforce(1);
|
region.reinforce(BigInt(data.regions[regionName]));
|
||||||
return true;
|
}
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,6 +79,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">A</div>
|
<div class="label">A</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -88,6 +89,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">B</div>
|
<div class="label">B</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -97,6 +99,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">C</div>
|
<div class="label">C</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -106,6 +109,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">D</div>
|
<div class="label">D</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -115,6 +119,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">E</div>
|
<div class="label">E</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -124,6 +129,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">F</div>
|
<div class="label">F</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -133,6 +139,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">G</div>
|
<div class="label">G</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -142,6 +149,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">H</div>
|
<div class="label">H</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -151,6 +159,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">I</div>
|
<div class="label">I</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
@ -160,6 +169,7 @@
|
|||||||
<div class="strength"></div>
|
<div class="strength"></div>
|
||||||
<div class="label">J</div>
|
<div class="label">J</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="claim">Claim</button>
|
||||||
<button class="reinforce">Reinf.</button>
|
<button class="reinforce">Reinf.</button>
|
||||||
<button class="attack">Attack</button>
|
<button class="attack">Attack</button>
|
||||||
<button class="fortify">Fortify</button>
|
<button class="fortify">Fortify</button>
|
||||||
|
Loading…
Reference in New Issue
Block a user