488 lines
16 KiB
TypeScript
488 lines
16 KiB
TypeScript
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<String>;
|
|
private colors: Set<String>;
|
|
|
|
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<String>();
|
|
this.colors = new Set<String>();
|
|
|
|
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<Socket, Client> = new Map<Socket, Client>();
|
|
let availableGames: Map<String, AvailableGame> = new Map<String, AvailableGame>();
|
|
let runningGames: Map<String, Game> = new Map<String, Game>();
|
|
|
|
app.get("/", (req, res) => {
|
|
res.send("<h1>Hello world</h1>");
|
|
});
|
|
|
|
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);
|
|
}
|
|
}
|