170 lines
4.0 KiB
JavaScript
170 lines
4.0 KiB
JavaScript
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.isReady)[0];
|
|
|
|
const WAITING = 0;
|
|
const PRE_GAME = 1;
|
|
const PLAYING = 2;
|
|
const POST_GAME = 3;
|
|
|
|
const COLORS = ["#FF0000", "#00FF00", "#0000FF", "#FF00FF", "#FFFF00", "#00FFFF"];
|
|
|
|
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) => {
|
|
// Ignore any messages that originate from us.
|
|
if (data.author === ID) {
|
|
return;
|
|
}
|
|
|
|
switch (data.type) {
|
|
case "ANNOUNCE":
|
|
playerConnected(data);
|
|
break;
|
|
|
|
case "DISCONNECT":
|
|
playerDisconnected(data);
|
|
break;
|
|
|
|
case "KEEPALIVE":
|
|
keepAlive(data);
|
|
break;
|
|
|
|
case "READY":
|
|
await setReady(data);
|
|
break;
|
|
|
|
case "RANDOM":
|
|
await random.processCooperativeRandom(data);
|
|
break;
|
|
|
|
case "BARRIER":
|
|
barrier.resolve(data);
|
|
break;
|
|
|
|
case "CLAIM":
|
|
// Claim a region in the pregame.
|
|
await currentPlayer().claim(data);
|
|
// Increment to next player.
|
|
currentPlayer().endTurn();
|
|
|
|
if (currentPlayer() === us) {
|
|
currentPlayer().claimRegions();
|
|
}
|
|
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 {
|
|
}
|
|
|
|
updatePlayerDom();
|
|
}
|
|
|
|
function playerDisconnected(data) {
|
|
console.log("deleting player");
|
|
delete players[data.author];
|
|
updatePlayerDom();
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
updatePlayerDom();
|
|
|
|
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();
|
|
updatePlayerDom();
|
|
|
|
// Players select starting regions.
|
|
}
|