const ID = window.crypto.randomUUID(); // Timeout to consider a player disconnected const TIMEOUT = 30_000; let players = {}; let us = null; const WAITING = 0; const PRE_GAME = 1; const PLAYING = 2; const POST_GAME = 3; let game_state = WAITING; let socket; let random; // Not totally reliable but better than nothing. window.addEventListener("beforeunload", () => { socket.emit("message", { type: "DISCONNECT", id: window.crypto.randomUUID(), author: ID, name: "", }); }); document.addEventListener("DOMContentLoaded", () => { socket = io(); random = new Random(); socket.on("connect", () => { console.log("Connected!"); socket.emit("message", { type: "ANNOUNCE", id: window.crypto.randomUUID(), author: ID, name: "", }); // 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 "SYNC": await sync(data); break; case "RANDOM": await random.processCooperativeRandom(data); break; } }); // Emit keepalive messages to inform other players we are still here window.setInterval(() => { socket.emit("message", { type: "KEEPALIVE", id: window.crypto.randomUUID(), author: ID, }); }, 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 (game_state !== 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", { type: "ANNOUNCE", id: window.crypto.randomUUID(), author: ID, name: "", }); 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 sync(data) { players[data.author].name = data.name; players[data.author].ready = data.ready; 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."); game_state = PRE_GAME; let player1 = await random.get(Object.keys(players).length, "first-player"); console.log(player1); }