import express, { Express, Request, Response } from "express"; import http from "http"; import { v4 as uuidv4 } from "uuid"; import { Server, Socket } from "socket.io"; import { instrument } from "@socket.io/admin-ui"; import { pallette, availableColors } from "./pallette"; const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: ["https://admin.socket.io"], credentials: true, }, }); class Client { private _login: String; private _password: String; private _inGame: Boolean; public get login() { return this._login; } public get password() { return this._password; } public get inGame() { return this._inGame; } constructor(login: String, password: String) { this._login = login; this._password = password; this._inGame = false; } public setInGame(inGame: Boolean) { this._inGame = inGame; } public simplify() { return { login: this.login, inGame: this.inGame, }; } } class Game { private id: String; private guesser: Client; private suggester: Client; private tries: number; private guesses: Set; private colors: Set; public get Id() { return this.id; } public get Guesser() { return this.guesser; } public get Suggester() { return this.suggester; } public get Tries() { return this.tries; } public get Guesses() { return this.guesses; } public get Colors() { return this.colors; } constructor(id: String, guesser: Client, suggester: Client, tries: number) { this.id = id; this.guesser = guesser; this.suggester = suggester; this.tries = tries; this.guesses = new Set(); this.colors = new Set(); while (this.colors.size < 6) { const key = availableColors[Math.floor(Math.random() * availableColors.length)]; this.colors.add(key); } } public Opponent(player: Client): Client { if (this.guesser === player) { return this.suggester; } return this.guesser; } public Guess(player: Client, guess: String) { if (this.guesses.has(guess)) { return true; } this.guesses.add(guess); const opponent = this.Opponent(player); const opponentSocket = Array.from(onlineClients.keys()).find((socket) => onlineClients.get(socket) === opponent); if (opponentSocket) { opponentSocket.emit("guess", guess); } else { console.log("opponent socket not found"); return false; } return true; } public GameWon() { for (const color of this.colors) { if (!this.guesses.has(color)) { return false; } } return true; } public GameLost() { const countOfGuesses = this.guesses.size; const countOfCorrectGuesses = Array.from(this.guesses).filter((guess) => this.colors.has(guess)).length; return countOfGuesses - countOfCorrectGuesses >= this.tries; } public simplify() { return { id: this.id, guesser: this.guesser.login, suggester: this.suggester.login, tries: this.tries, }; } } class AvailableGame { private _id: String; private _player: Client; private _tries: number; private _neededRole: String; public get id(): String { return this._id; } public get player(): Client { return this._player; } public get tries(): number { return this._tries; } public get neededRole(): String { return this._neededRole; } constructor(id: String, player: Client, tries: number, neededRole: String) { this._id = id; this._player = player; this._tries = tries; this._neededRole = neededRole; } simplify() { return { id: this.id, player: this.player.login, tries: this.tries, neededRole: this.neededRole, }; } } let registeredClients: Client[] = []; let onlineClients: Map = new Map(); let availableGames: Map = new Map(); let runningGames: Map = new Map(); app.get("/", (req, res) => { res.send("

Hello world

"); }); app.get("/pallette", (req, res) => { res.json(pallette); }); io.on("connection", (socket) => { console.log("someone connected"); socket.emit("hello", "I don't know you"); socket.on("register", (login, password) => { console.log("user send register with login: " + login + " and password: " + password); if (login.length < 3 || login.length > 20 || password.length < 3 || password.length > 20) { socket.emit("register", false, "Login or password is too short or too long"); console.log("user tried to register with too short or too long login or password"); return; } if (registeredClients.find((client) => client.login === login)) { socket.emit("register", false, "User with this login already exists"); console.log("user tried to register with existing login"); return; } registeredClients.push(new Client(login, password)); onlineClients.set(socket, new Client(login, password)); socket.emit("register", true, "User registered successfully"); console.log("user registered successfully"); }); socket.on("login", (login, password) => { console.log("user send login with login: " + login + " and password: " + password); if (login.length < 3 || login.length > 20 || password.length < 3 || password.length > 20) { socket.emit("login", false, "Login or password is too short or too long"); console.log("user tried to login with too short or too long login or password"); return; } if (!registeredClients.find((client) => client.login === login)) { socket.emit("login", false, "User with this login does not exist"); console.log("user tried to login with non existing login"); return; } if (registeredClients.find((client) => client.login === login && client.password !== password)) { socket.emit("login", false, "Wrong password"); console.log("user tried to login with wrong password"); return; } onlineClients.set(socket, new Client(login, password)); socket.emit("login", true, "User logged in successfully"); registerSocketLoggedInFunctions(socket); io.emit("updateNeeded"); }); socket.on("disconnect", () => { const client = onlineClients.get(socket); if (client !== undefined) { console.log("user " + client.login + " disconnected"); for (const game of Array.from(availableGames.values())) { if (game.player === client) { availableGames.delete(game.id); return; } } if (client.inGame) { const game = Array.from(runningGames.values()).find((game) => game.Guesser === client || game.Suggester === client); if (game !== undefined) { const opponentPlayer = game.Opponent(client); const otherClientSocket = Array.from(onlineClients.keys()).find((key) => onlineClients.get(key)?.login === opponentPlayer.login); if (otherClientSocket !== undefined) { otherClientSocket.emit("leaveGameResponse", true, 410); } client.setInGame(false); opponentPlayer.setInGame(false); runningGames.delete(game.Id); } } onlineClients.delete(socket); } else { console.log("anonymous disconnected"); } io.emit("updateNeeded"); }); }); instrument(io, { mode: "development", auth: { type: "basic", username: "admin", password: "$2a$12$84FqmSh7yVv46tdygZ2rNuJYqWPXYtYC3JxjSJBY8PyXB0Oe8qCfO", }, }); server.listen(9800, () => { console.log("⚡️ listening on *:9800"); }); function registerSocketLoggedInFunctions(socket: Socket) { socket.on("getUpdate", getUpdateEventHandler); socket.on("createGame", createGameEventHandler); socket.on("joinGame", joinGameEventHandler); socket.on("removeGame", removeGameEventHandler); socket.on("chat", chatEventHandler); socket.on("guess", guessEventHandler); socket.on("leaveGame", leaveGameEventHandler); const client = onlineClients.get(socket)!; function getUpdateEventHandler(): void { socket.emit("update", true, { availableGames: Array.from(availableGames.values()).map((e) => e.simplify()), }); } function createGameEventHandler(tries: number, role: String): void { if (client.inGame) { socket.emit("createGameResponse", false, "Player is already in game"); return; } if (tries < 20 || tries > 50) { socket.emit("createGameResponse", false, "Tries must be between 20 and 50"); return; } if (role !== "guesser" && role !== "suggester") { socket.emit("createGameResponse", false, "Role must be 'guesser' or 'suggester'"); return; } for (const game of Array.from(availableGames.values())) { if (game.player.login === client.login) { socket.emit("createGameResponse", false, "You already created a game"); return; } } const game = new AvailableGame(uuidv4(), client, tries, role); availableGames.set(game.id, game); io.emit("updateNeeded"); } function joinGameEventHandler(gameId: String): void { const game = availableGames.get(gameId); if (game === undefined) { socket.emit("joinGameResponse", false, "Game does not exist"); return; } if (game.player.login === client.login) { socket.emit("joinGameResponse", false, "You cannot join your own game"); return; } if (game.player.inGame) { socket.emit("joinGameResponse", false, "Player is already in game"); return; } const gameSocket = Array.from(onlineClients.keys()).find((key) => onlineClients.get(key)?.login === game.player.login); if (gameSocket === undefined) { socket.emit("joinGameResponse", false, "Player is not online"); return; } availableGames.delete(gameId); io.emit("updateNeeded"); let suggester: Client; let guesser: Client; if (game.neededRole === "suggester") { suggester = client; guesser = game.player; } else { suggester = game.player; guesser = client; } suggester.setInGame(true); guesser.setInGame(true); const gameInfo = new Game(uuidv4(), guesser, suggester, game.tries); const suggesterSocket = Array.from(onlineClients.keys()).find((key) => onlineClients.get(key)?.login === suggester.login)!; const guesserSocket = Array.from(onlineClients.keys()).find((key) => onlineClients.get(key)?.login === guesser.login)!; suggesterSocket.emit("joinGameResponse", true, gameInfo.simplify(), Array.from(gameInfo.Colors)); guesserSocket.emit("joinGameResponse", true, gameInfo.simplify(), []); runningGames.set(gameInfo.Id, gameInfo); } function removeGameEventHandler(gameId: String): void { const game = availableGames.get(gameId); if (game === undefined) { socket.emit("removeGameResponse", false, "Game does not exist"); return; } if (game.player.login !== client.login) { socket.emit("removeGameResponse", false, "You cannot remove game that is not yours"); return; } availableGames.delete(gameId); io.emit("updateNeeded"); } function chatEventHandler(gameId: String, message: String): void { const game = runningGames.get(gameId); if (game === undefined || !client.inGame) { socket.emit("chatResponse", false, 400); console.log("user " + client.login + " tried to chat in non existing game"); return; } const opponentPlayer = game.Opponent(client); const otherClientSocket = Array.from(onlineClients.keys()).find((key) => onlineClients.get(key)?.login === opponentPlayer.login); if (otherClientSocket === undefined) { socket.emit("chatResponse", false, 404); console.log("user " + client.login + " tried to chat with non existing user"); return; } socket.emit("chatResponse", true, { from: client.login, message, }); otherClientSocket.emit("chatResponse", true, { from: client.login, message, }); } function guessEventHandler(gameId: String, guess: String): void { const game = runningGames.get(gameId); if (game === undefined || !client.inGame) { socket.emit("guessResponse", false, 400); console.log("user " + client.login + " tried to guess in non existing game"); return; } if (game.Guesser.login !== client.login) { socket.emit("guessResponse", false, 400); console.log("user " + client.login + " tried to guess in game he is not guessing in"); return; } if (!game.Guess(client, guess)) { socket.emit("guessResponse", false, 400); console.log("user " + client.login + " tried to guess but something gone terribly wrong"); return; } const opponentPlayer = game.Opponent(client); const otherClientSocket = Array.from(onlineClients.keys()).find((key) => onlineClients.get(key)?.login === opponentPlayer.login)!; if (game.GameWon()) { console.log(`game ${gameId} won by ${client.login} and ${opponentPlayer.login}`); socket.emit("gameStatus", [true]); otherClientSocket.emit("gameStatus", [true]); client.setInGame(false); opponentPlayer.setInGame(false); runningGames.delete(gameId); return; } if (game.GameLost()) { console.log(`game ${gameId} lost by ${client.login} and ${opponentPlayer.login}`); socket.emit("gameStatus", [false]); otherClientSocket.emit("gameStatus", [false]); client.setInGame(false); opponentPlayer.setInGame(false); runningGames.delete(gameId); } } function leaveGameEventHandler(gameId: String): void { const game = runningGames.get(gameId); if (game === undefined || !client.inGame) { socket.emit("guessResponse", false, 400); console.log("user " + client.login + " tried to guess in non existing game"); return; } socket.emit("leaveGameResponse", true, 200); const opponentPlayer = game.Opponent(client); const otherClientSocket = Array.from(onlineClients.keys()).find((key) => onlineClients.get(key)?.login === opponentPlayer.login); if (otherClientSocket !== undefined) { otherClientSocket.emit("leaveGameResponse", true, 410); } client.setInGame(false); opponentPlayer.setInGame(false); runningGames.delete(game.Id); } }