Mid refactor to stop polluting the namespace so much
This commit is contained in:
parent
7d9531f0d9
commit
cf0c9135e1
@ -1,4 +1,4 @@
|
|||||||
#players {
|
#players-div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
const ID = window.crypto.randomUUID();
|
|
||||||
// Timeout to consider a player disconnected
|
|
||||||
const TIMEOUT = 30_000;
|
|
||||||
|
|
||||||
let players = {};
|
|
||||||
let us = null;
|
|
||||||
let currentPlayer = () => Object.values(players).filter((p) => p.isPlaying)[0];
|
|
||||||
|
|
||||||
const WAITING = 0;
|
|
||||||
const PRE_GAME = 1;
|
|
||||||
const PLAYING = 2;
|
|
||||||
const POST_GAME = 3;
|
|
||||||
|
|
||||||
let gameState = WAITING;
|
|
||||||
|
|
||||||
let socket;
|
|
||||||
let random;
|
|
||||||
let barrier;
|
|
||||||
|
|
||||||
// Not totally reliable but better than nothing.
|
|
||||||
window.addEventListener("beforeunload", () => {
|
|
||||||
socket.emit("message", Packet.createDisconnect());
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
socket = io();
|
|
||||||
random = new Random();
|
|
||||||
barrier = new Barrier();
|
|
||||||
|
|
||||||
socket.on("connect", () => {
|
|
||||||
console.log("Connected!");
|
|
||||||
socket.emit("message", Packet.createAnnounce());
|
|
||||||
// Create self
|
|
||||||
players[ID] = new Player(ID, name);
|
|
||||||
us = players[ID];
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("message", async (data) => {
|
|
||||||
switch (data.type) {
|
|
||||||
case "ANNOUNCE":
|
|
||||||
if (data.author === ID) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
playerConnected(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "DISCONNECT":
|
|
||||||
playerDisconnected(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "KEEPALIVE":
|
|
||||||
if (data.author === ID) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
keepAlive(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "READY":
|
|
||||||
if (data.author === ID) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await setReady(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "RANDOM":
|
|
||||||
if (data.author === ID) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await random.processCooperativeRandom(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "BARRIER":
|
|
||||||
if (data.author === ID) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
barrier.resolve(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "ACT":
|
|
||||||
if (data.author !== currentPlayer().id) {
|
|
||||||
if (data.action === "DEFENSE") {
|
|
||||||
await players[data.author].setDefense(data.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameState === PRE_GAME) {
|
|
||||||
if (!allRegionsClaimed()) {
|
|
||||||
// Claim a region in the pregame.
|
|
||||||
if (currentPlayer().claim(data)) {
|
|
||||||
// Increment to next player.
|
|
||||||
currentPlayer().endTurn();
|
|
||||||
}
|
|
||||||
} else if (!allReinforcementsPlaced()) {
|
|
||||||
if (currentPlayer().reinforce(data)) {
|
|
||||||
currentPlayer().endTurn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allReinforcementsPlaced()) {
|
|
||||||
gameState = PLAYING;
|
|
||||||
updateDom();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (await currentPlayer().act(data)) {
|
|
||||||
currentPlayer().endTurn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDom();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emit keepalive messages to inform other players we are still here
|
|
||||||
window.setInterval(() => {
|
|
||||||
socket.emit("message", Packet.createKeepAlive());
|
|
||||||
}, TIMEOUT / 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process player connect packets: these inform that a new player has joined.
|
|
||||||
*
|
|
||||||
* @param data Packet received
|
|
||||||
*/
|
|
||||||
function playerConnected(data) {
|
|
||||||
// Block players from joining mid-game
|
|
||||||
if (gameState !== WAITING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a new player is seen, all announce to ensure they know all players.
|
|
||||||
if (players[data.author] === undefined) {
|
|
||||||
players[data.author] = new Player(data.author, data.name);
|
|
||||||
socket.emit("message", Packet.createAnnounce());
|
|
||||||
players[data.author].resetTimeout();
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDom();
|
|
||||||
}
|
|
||||||
|
|
||||||
function playerDisconnected(data) {
|
|
||||||
console.log("deleting player");
|
|
||||||
delete players[data.author];
|
|
||||||
updateDom();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process keep-alive packets: these are packets that check players are still online.
|
|
||||||
*
|
|
||||||
* @param data Packet received
|
|
||||||
*/
|
|
||||||
function keepAlive(data) {
|
|
||||||
players[data.author].resetTimeout();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process sync packets: update player details like status and name.
|
|
||||||
*
|
|
||||||
* @param data Packet received
|
|
||||||
*/
|
|
||||||
async function setReady(data) {
|
|
||||||
players[data.author].name = data.name;
|
|
||||||
players[data.author].ready = data.ready;
|
|
||||||
|
|
||||||
updateDom();
|
|
||||||
|
|
||||||
if (allPlayersReady()) {
|
|
||||||
await startPregame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function allPlayersReady() {
|
|
||||||
for (let player of Object.values(players)) {
|
|
||||||
if (!player.ready) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startPregame() {
|
|
||||||
console.log("all players ready.");
|
|
||||||
|
|
||||||
gameState = PRE_GAME;
|
|
||||||
|
|
||||||
let firstPlayerIndex = await random.get(Object.keys(players).length, "first-player");
|
|
||||||
|
|
||||||
let firstPlayer = Object.values(players).sort((a, b) => (a.id < b.id ? -1 : 1))[
|
|
||||||
firstPlayerIndex
|
|
||||||
];
|
|
||||||
|
|
||||||
firstPlayer.isPlaying = true;
|
|
||||||
await barrier.wait();
|
|
||||||
updateDom();
|
|
||||||
}
|
|
2
static/js/modules/crypto/main.js
Normal file
2
static/js/modules/crypto/main.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { generate_keypair } from "./paillier.js";
|
||||||
|
export { generate_keypair };
|
14
static/js/modules/crypto/math.js
Normal file
14
static/js/modules/crypto/math.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export function mod_exp(a, b, n) {
|
||||||
|
let res = 1n;
|
||||||
|
|
||||||
|
while (b > 0n) {
|
||||||
|
if (b % 2n === 1n) {
|
||||||
|
res = (res * a) % n;
|
||||||
|
}
|
||||||
|
|
||||||
|
b >>= 1n;
|
||||||
|
a = (a * a) % n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
|
import { random2048, generate_prime } from "./random_primes.js";
|
||||||
|
import { mod_exp } from "./math.js";
|
||||||
|
|
||||||
let p, q, pubKey, privKey;
|
let p, q, pubKey, privKey;
|
||||||
|
|
||||||
class PubKey {
|
class PubKey {
|
||||||
@ -18,7 +21,7 @@ class PubKey {
|
|||||||
// Compute g^m by binomial theorem.
|
// Compute g^m by binomial theorem.
|
||||||
let gm = (1n + this.n * m) % this.n ** 2n;
|
let gm = (1n + this.n * m) % this.n ** 2n;
|
||||||
// Compute g^m r^n from crt
|
// Compute g^m r^n from crt
|
||||||
return (gm * fastModularExponentiation(r, this.n, this.n ** 2n)) % this.n ** 2n;
|
return (gm * mod_exp(r, this.n, this.n ** 2n)) % this.n ** 2n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,19 +29,17 @@ class PrivKey {
|
|||||||
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);
|
||||||
this.mu = fastModularExponentiation(this.lambda, this.lambda - 1n, this.n);
|
this.mu = mod_exp(this.lambda, this.lambda - 1n, this.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypt(c) {
|
decrypt(c) {
|
||||||
return (
|
return (
|
||||||
(((fastModularExponentiation(c, this.lambda, this.n ** 2n) - 1n) / this.n) *
|
(((mod_exp(c, this.lambda, this.n ** 2n) - 1n) / this.n) * this.mu) % this.n
|
||||||
this.mu) %
|
|
||||||
this.n
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
export function generate_keypair() {
|
||||||
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);
|
||||||
@ -55,4 +56,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
pubKey = new PubKey(p, q);
|
pubKey = new PubKey(p, q);
|
||||||
privKey = new PrivKey(p, q);
|
privKey = new PrivKey(p, q);
|
||||||
});
|
|
||||||
|
return { pubKey, privKey };
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
function random2048() {
|
import { mod_exp } from "./math.js";
|
||||||
|
|
||||||
|
export function random2048() {
|
||||||
const byteArray = new BigUint64Array(32);
|
const byteArray = new BigUint64Array(32);
|
||||||
window.crypto.getRandomValues(byteArray);
|
window.crypto.getRandomValues(byteArray);
|
||||||
let intRepr = 0n;
|
let intRepr = 0n;
|
||||||
@ -55,7 +57,7 @@ function miller_rabin(n, k) {
|
|||||||
|
|
||||||
for (; k > 0; k--) {
|
for (; k > 0; k--) {
|
||||||
let a = random2048();
|
let a = random2048();
|
||||||
let x = fastModularExponentiation(a, d, n);
|
let x = mod_exp(a, d, n);
|
||||||
|
|
||||||
if (x === 1n || x === n - 1n) {
|
if (x === 1n || x === n - 1n) {
|
||||||
continue;
|
continue;
|
||||||
@ -77,7 +79,7 @@ function miller_rabin(n, k) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate_prime() {
|
export function generate_prime() {
|
||||||
while (true) {
|
while (true) {
|
||||||
let n = generate_bigint();
|
let n = generate_bigint();
|
||||||
if (small_prime_test(n) && miller_rabin(n, 40)) {
|
if (small_prime_test(n) && miller_rabin(n, 40)) {
|
||||||
@ -86,21 +88,6 @@ function generate_prime() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fastModularExponentiation(a, b, n) {
|
|
||||||
let res = 1n;
|
|
||||||
|
|
||||||
while (b > 0n) {
|
|
||||||
if (b % 2n === 1n) {
|
|
||||||
res = (res * a) % n;
|
|
||||||
}
|
|
||||||
|
|
||||||
b >>= 1n;
|
|
||||||
a = (a * a) % n;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SMALL_PRIMES = [
|
const SMALL_PRIMES = [
|
||||||
2n,
|
2n,
|
||||||
3n,
|
3n,
|
@ -1,9 +1,12 @@
|
|||||||
|
import { socket, players } from "./main.js";
|
||||||
|
import { Packet } from "./packet.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Typical barrier type.
|
* Typical barrier type.
|
||||||
*
|
*
|
||||||
* Block all clients until everyone has hit the barrier.
|
* Block all clients until everyone has hit the barrier.
|
||||||
*/
|
*/
|
||||||
class Barrier {
|
export class Barrier {
|
||||||
constructor() {
|
constructor() {
|
||||||
let resolver;
|
let resolver;
|
||||||
this.promise = new Promise((resolve) => {
|
this.promise = new Promise((resolve) => {
|
@ -1,4 +1,19 @@
|
|||||||
function unlockMapDom() {
|
import {
|
||||||
|
gameState,
|
||||||
|
WAITING,
|
||||||
|
PRE_GAME,
|
||||||
|
PLAYING,
|
||||||
|
us,
|
||||||
|
socket,
|
||||||
|
players,
|
||||||
|
ID,
|
||||||
|
allPlayersReady,
|
||||||
|
startPregame,
|
||||||
|
} from "./main.js";
|
||||||
|
import { Region } from "./map.js";
|
||||||
|
import { Packet } from "./packet.js";
|
||||||
|
|
||||||
|
export function unlockMapDom() {
|
||||||
Object.values(REGIONS).forEach((region) => {
|
Object.values(REGIONS).forEach((region) => {
|
||||||
if (!allRegionsClaimed() && region.owner === null) {
|
if (!allRegionsClaimed() && region.owner === null) {
|
||||||
document
|
document
|
||||||
@ -12,11 +27,11 @@ function unlockMapDom() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function lockMapDom() {
|
export function lockMapDom() {
|
||||||
document.querySelectorAll(".actions").forEach((e) => e.classList.add("hidden"));
|
document.querySelectorAll(".actions").forEach((e) => e.classList.add("hidden"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDom() {
|
export function updateDom() {
|
||||||
if (gameState !== WAITING) {
|
if (gameState !== WAITING) {
|
||||||
document.querySelector("#ready-button").style.display = "none";
|
document.querySelector("#ready-button").style.display = "none";
|
||||||
}
|
}
|
||||||
@ -49,7 +64,7 @@ function updateMapDom() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let region of Object.values(REGIONS)) {
|
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.strength || "";
|
||||||
element.style.backgroundColor =
|
element.style.backgroundColor =
|
82
static/js/modules/interface/game.js
Normal file
82
static/js/modules/interface/game.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Player } from "./player";
|
||||||
|
|
||||||
|
const WAITING = 0;
|
||||||
|
const PRE_GAME = 1;
|
||||||
|
const PLAYING = 2;
|
||||||
|
|
||||||
|
export class Game {
|
||||||
|
constructor() {
|
||||||
|
this.us = null;
|
||||||
|
this.players = {};
|
||||||
|
this.state = WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
isWaiting() {
|
||||||
|
return this.state === WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPregame() {
|
||||||
|
return this.state === PRE_GAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlaying() {
|
||||||
|
return this.state === PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementState() {
|
||||||
|
this.state += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPlayer() {
|
||||||
|
return Object.values(this.players).filter((p) => p.isPlaying)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlayer(id, name, is_us) {
|
||||||
|
let is_new = this.players[id] === undefined;
|
||||||
|
|
||||||
|
if (this.isWaiting()) {
|
||||||
|
this.players[id] = new Player(id, name, is_us);
|
||||||
|
if (is_us === true) {
|
||||||
|
this.us = this.players[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_new) {
|
||||||
|
const event = new CustomEvent("addPlayer");
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_new;
|
||||||
|
}
|
||||||
|
|
||||||
|
removePlayer(id) {
|
||||||
|
if (this.players[id] !== undefined) {
|
||||||
|
const event = new CustomEvent("removePlayer");
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
delete this.players[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keepAlive(id) {
|
||||||
|
if (id !== this.us.id) {
|
||||||
|
this.players[id].resetTimeout(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setReady(id, ready) {
|
||||||
|
this.players[id].readyState = ready;
|
||||||
|
|
||||||
|
if (this._allPlayersReady()) {
|
||||||
|
this.incrementState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_allPlayersReady() {
|
||||||
|
for (let player of Object.values(this.players)) {
|
||||||
|
if (!player.readyState) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
135
static/js/modules/interface/main.js
Normal file
135
static/js/modules/interface/main.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { generate_keypair } from "../crypto/main.js";
|
||||||
|
import { Random } from "./random.js";
|
||||||
|
import { Barrier } from "./barrier.js";
|
||||||
|
import { Packet } from "./packet.js";
|
||||||
|
import { updateDom } from "./dom.js";
|
||||||
|
|
||||||
|
export const ID = window.crypto.randomUUID();
|
||||||
|
export let us = null;
|
||||||
|
|
||||||
|
export const game = new Game();
|
||||||
|
|
||||||
|
export let socket;
|
||||||
|
let random;
|
||||||
|
let barrier;
|
||||||
|
let keys;
|
||||||
|
|
||||||
|
// Not totally reliable but better than nothing.
|
||||||
|
window.addEventListener("beforeunload", () => {
|
||||||
|
socket.emit("message", Packet.createDisconnect());
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
socket = io();
|
||||||
|
random = new Random();
|
||||||
|
barrier = new Barrier();
|
||||||
|
keys = generate_keypair();
|
||||||
|
|
||||||
|
socket.on("connect", () => {
|
||||||
|
window.console.log("Connected!");
|
||||||
|
socket.emit("message", Packet.createAnnounce());
|
||||||
|
game.addPlayer(ID, name, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("message", async (data) => {
|
||||||
|
// todo validate signature
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case "RANDOM":
|
||||||
|
if (data.author === ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await random.processCooperativeRandom(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "BARRIER":
|
||||||
|
if (data.author === ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
barrier.resolve(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
const event = new CustomEvent(data.type, { detail: data });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process player connect packets: these inform that a new player has joined.
|
||||||
|
*
|
||||||
|
* @param data Packet received
|
||||||
|
*/
|
||||||
|
document.addEventListener("ANNOUNCE", (data) => {
|
||||||
|
if (data.author === ID) return;
|
||||||
|
|
||||||
|
let is_new = game.addPlayer(data.author, data.name, false);
|
||||||
|
|
||||||
|
// When a new player is seen, all announce to ensure they know all players.
|
||||||
|
if (is_new) {
|
||||||
|
socket.emit("message", Packet.createAnnounce());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DISCONNECT", (data) => {
|
||||||
|
game.removePlayer(data.author);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process keep-alive packets: these are packets that check players are still online.
|
||||||
|
*
|
||||||
|
* @param data Packet received
|
||||||
|
*/
|
||||||
|
document.addEventListener("KEEPALIVE", (data) => {
|
||||||
|
game.keepAlive(data.author);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("ACT", async (data) => {
|
||||||
|
if (data.author !== game.currentPlayer().id) {
|
||||||
|
if (data.action === "DEFENSE") {
|
||||||
|
await game.players[data.author].setDefense(data.amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.isWaiting()) {
|
||||||
|
game.setReady(data.author, data.ready);
|
||||||
|
} else if (game.isPregame()) {
|
||||||
|
if (!Region.allRegionsClaimed()) {
|
||||||
|
// Claim a region in the pregame.
|
||||||
|
if (game.currentPlayer().claim(data)) {
|
||||||
|
// Increment to next player.
|
||||||
|
game.currentPlayer().endTurn();
|
||||||
|
}
|
||||||
|
} else if (!Region.allReinforcementsPlaced()) {
|
||||||
|
if (game.currentPlayer().reinforce(data)) {
|
||||||
|
game.currentPlayer().endTurn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Region.allReinforcementsPlaced()) {
|
||||||
|
game.incrementState();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (await game.currentPlayer().act(data)) {
|
||||||
|
game.currentPlayer().endTurn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function startPregame() {
|
||||||
|
gameState = PRE_GAME;
|
||||||
|
|
||||||
|
let firstPlayerIndex = await random.get(Object.keys(players).length, "first-player");
|
||||||
|
|
||||||
|
let firstPlayer = Object.values(players).sort((a, b) => (a.id < b.id ? -1 : 1))[
|
||||||
|
firstPlayerIndex
|
||||||
|
];
|
||||||
|
|
||||||
|
firstPlayer.isPlaying = true;
|
||||||
|
await barrier.wait();
|
||||||
|
updateDom();
|
||||||
|
}
|
@ -1,3 +1,10 @@
|
|||||||
|
let allPlaced = false;
|
||||||
|
|
||||||
|
// In standard Risk, this is 5
|
||||||
|
const _REINFORCEMENT_MULTIPLIER = 1;
|
||||||
|
|
||||||
|
export const REGIONS = {};
|
||||||
|
|
||||||
class Continent {
|
class Continent {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -5,9 +12,7 @@ class Continent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const REGIONS = {};
|
export class Region {
|
||||||
|
|
||||||
class Region {
|
|
||||||
constructor(name, continent) {
|
constructor(name, continent) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.owner = null;
|
this.owner = null;
|
||||||
@ -23,10 +28,50 @@ class Region {
|
|||||||
region2.neighbours.add(region1);
|
region2.neighbours.add(region1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static allRegionsClaimed() {
|
||||||
|
return (
|
||||||
|
Object.values(REGIONS).find((region) => region.owner === null) === undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static reinforcementsRemaining() {
|
||||||
|
if (allPlaced) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
let totalStrength = Object.values(REGIONS)
|
||||||
|
.filter((region) => region.owner === us)
|
||||||
|
.reduce((counter, region) => counter + region.strength, 0);
|
||||||
|
let numPlayers = Object.values(players).length;
|
||||||
|
|
||||||
|
return _REINFORCEMENT_MULTIPLIER * (10 - numPlayers) - totalStrength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static allReinforcementsPlaced() {
|
||||||
|
if (allPlaced) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
let totalStrength = Object.values(REGIONS).reduce(
|
||||||
|
(counter, region) => counter + region.strength,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
let numPlayers = Object.values(players).length;
|
||||||
|
|
||||||
|
allPlaced =
|
||||||
|
totalStrength >=
|
||||||
|
numPlayers * _REINFORCEMENT_MULTIPLIER * (10 - numPlayers);
|
||||||
|
return allPlaced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static getRegion(name) {
|
static getRegion(name) {
|
||||||
return REGIONS[name];
|
return REGIONS[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getAllRegions() {
|
||||||
|
return Object.values(REGIONS);
|
||||||
|
}
|
||||||
|
|
||||||
claim(player) {
|
claim(player) {
|
||||||
this.owner = player;
|
this.owner = player;
|
||||||
this.strength = 1;
|
this.strength = 1;
|
||||||
@ -66,41 +111,3 @@ Region.setNeighbours(F, G);
|
|||||||
Region.setNeighbours(G, H);
|
Region.setNeighbours(G, H);
|
||||||
Region.setNeighbours(G, I);
|
Region.setNeighbours(G, I);
|
||||||
Region.setNeighbours(H, I);
|
Region.setNeighbours(H, I);
|
||||||
|
|
||||||
function allRegionsClaimed() {
|
|
||||||
return Object.values(REGIONS).find((region) => region.owner === null) === undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let allPlaced = false;
|
|
||||||
|
|
||||||
// In standard Risk, this is 5
|
|
||||||
const _REINFORCEMENT_MULTIPLIER = 1;
|
|
||||||
|
|
||||||
function reinforcementsRemaining() {
|
|
||||||
if (allPlaced) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
let totalStrength = Object.values(REGIONS)
|
|
||||||
.filter((region) => region.owner === us)
|
|
||||||
.reduce((counter, region) => counter + region.strength, 0);
|
|
||||||
let numPlayers = Object.values(players).length;
|
|
||||||
|
|
||||||
return _REINFORCEMENT_MULTIPLIER * (10 - numPlayers) - totalStrength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function allReinforcementsPlaced() {
|
|
||||||
if (allPlaced) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
let totalStrength = Object.values(REGIONS).reduce(
|
|
||||||
(counter, region) => counter + region.strength,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
let numPlayers = Object.values(players).length;
|
|
||||||
|
|
||||||
allPlaced =
|
|
||||||
totalStrength >= numPlayers * _REINFORCEMENT_MULTIPLIER * (10 - numPlayers);
|
|
||||||
return allPlaced;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,6 @@
|
|||||||
class Packet {
|
import { ID } from "./main.js";
|
||||||
|
|
||||||
|
export class Packet {
|
||||||
static _createBase(name) {
|
static _createBase(name) {
|
||||||
return {
|
return {
|
||||||
type: name,
|
type: name,
|
||||||
@ -24,7 +26,8 @@ class Packet {
|
|||||||
|
|
||||||
static createSetReady(nowReady) {
|
static createSetReady(nowReady) {
|
||||||
return {
|
return {
|
||||||
...this._createBase("READY"),
|
...this._createBase("ACT"),
|
||||||
|
action: "READY",
|
||||||
ready: nowReady,
|
ready: nowReady,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -1,12 +1,18 @@
|
|||||||
|
import { Packet } from "./packet.js";
|
||||||
|
import { socket } from "./main.js";
|
||||||
|
import { updateDom } from "./dom.js";
|
||||||
|
|
||||||
|
// Timeout to consider a player disconnected
|
||||||
|
const TIMEOUT = 30_000;
|
||||||
|
|
||||||
const PHASE_REINFORCE = 1;
|
const PHASE_REINFORCE = 1;
|
||||||
const PHASE_ATTACK = 2;
|
const PHASE_ATTACK = 2;
|
||||||
const PHASE_FORTIFY = 3;
|
const PHASE_FORTIFY = 3;
|
||||||
|
|
||||||
let totalDice = 0;
|
let totalDice = 0;
|
||||||
|
|
||||||
class Player {
|
export class Player {
|
||||||
constructor(id, name) {
|
constructor(id, local) {
|
||||||
this.name = name;
|
|
||||||
this.timeout = null;
|
this.timeout = null;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
@ -23,18 +29,22 @@ class Player {
|
|||||||
this.defenderAmount = null;
|
this.defenderAmount = null;
|
||||||
|
|
||||||
this.resetColor();
|
this.resetColor();
|
||||||
|
|
||||||
|
if (local) {
|
||||||
|
// Emit keepalive messages to inform other players we are still here
|
||||||
|
window.setInterval(() => {
|
||||||
|
socket.emit("message", Packet.createKeepAlive());
|
||||||
|
}, TIMEOUT / 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetTimeout() {
|
resetTimeout(game) {
|
||||||
if (this.timeout !== null) {
|
if (this.timeout !== null) {
|
||||||
window.clearTimeout(this.timeout);
|
window.clearTimeout(this.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timeout = window.setTimeout(() => {
|
this.timeout = window.setTimeout(() => {
|
||||||
if (players[this.id] !== undefined) {
|
game.removePlayer(this);
|
||||||
delete players[this.id];
|
|
||||||
}
|
|
||||||
updateDom();
|
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
|||||||
|
import { socket, ID, players } from "./main.js";
|
||||||
|
|
||||||
class RandomSession {
|
class RandomSession {
|
||||||
constructor(range) {
|
constructor(range) {
|
||||||
this.range = range;
|
this.range = range;
|
||||||
@ -15,7 +17,7 @@ class RandomSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Random {
|
export class Random {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.sessions = {};
|
this.sessions = {};
|
||||||
}
|
}
|
@ -7,15 +7,8 @@
|
|||||||
|
|
||||||
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
|
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
|
||||||
<script src="{{ url_for('static', filename='js/index.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/modules/interface/main.js') }}" type="module"></script>
|
||||||
<script src="{{ url_for('static', filename='js/player.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/modules/crypto/main.js') }}" type="module"></script>
|
||||||
<script src="{{ url_for('static', filename='js/dom.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/random.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/barrier.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/packet.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/map.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/random_primes.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/paillier.js') }}"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="modal" class="hidden modal">
|
<div id="modal" class="hidden modal">
|
||||||
@ -52,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="players">
|
<div id="players-div">
|
||||||
<strong>Players</strong>
|
<strong>Players</strong>
|
||||||
<ul id="playerList">
|
<ul id="playerList">
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
Reference in New Issue
Block a user