diff --git a/frontend/src/pages/pong/addPongMessage.ts b/frontend/src/pages/pong/addPongMessage.ts
deleted file mode 100644
index 13fae4b..0000000
--- a/frontend/src/pages/pong/addPongMessage.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { color } from './pong';
-
-/**
- * function adds a message to the frontend pongMessage
- * ATTENTION send inner HTML ******
- * @param text
- * @returns
- */
-
-export function addPongMessage(text: string) {
- const pongMessage = document.getElementById("system-box") as HTMLDivElement;
- if (!pongMessage) return;
- const messageElement = document.createElement("div-test");
- messageElement.innerHTML = text;
- pongMessage.appendChild(messageElement);
- pongMessage.scrollTop = pongMessage.scrollHeight;
- console.log(`%c DEBUG LOG: Added PONG new message:%c ${text}`, color.red, color.reset);
- return ;
-};
\ No newline at end of file
diff --git a/frontend/src/pages/pong/broadcastMsg.ts b/frontend/src/pages/pong/broadcastMsg.ts
deleted file mode 100644
index e17644d..0000000
--- a/frontend/src/pages/pong/broadcastMsg.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { addPongMessage } from "./addPongMessage";
-import { Socket } from 'socket.io-client';
-import { getUser } from "@app/auth";
-
-/**
- * function sends socket.emit to the backend to active and a broadcast message to all sockets
- * echos addPongMessage() the message with addMessage to the sender
- * @param socket
- * @param msgCommand
- */
-export function broadcastMsg (socket: Socket, msgCommand: string[]): void {
- let msgText = msgCommand[1] ?? "";
- addPongMessage(msgText);
- const user = getUser();
- if (user && socket?.connected) {
- const message = {
- command: msgCommand,
- destination: '',
- type: "chat",
- user: user.name,
- token: document.cookie,
- text: msgText,
- timestamp: Date.now(),
- SenderWindowID: socket.id,
- };
- socket.emit('message', JSON.stringify(message));
- }
-};
diff --git a/frontend/src/pages/pong/isLoggedIn.ts b/frontend/src/pages/pong/isLoggedIn.ts
deleted file mode 100644
index 2f01067..0000000
--- a/frontend/src/pages/pong/isLoggedIn.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { getUser } from "@app/auth";
-import type { User } from '@app/auth'
-/**
- * function checks if logged in
- * @returns either user | null
- */
-export function isLoggedIn(): User | null {
- return getUser() || null;
-};
\ No newline at end of file
diff --git a/frontend/src/pages/pong/pong.html b/frontend/src/pages/pong/pong.html
index 7684f61..7b896af 100644
--- a/frontend/src/pages/pong/pong.html
+++ b/frontend/src/pages/pong/pong.html
@@ -30,7 +30,7 @@
-
+
diff --git a/frontend/src/pages/pong/pong.ts b/frontend/src/pages/pong/pong.ts
index c22633c..eacd711 100644
--- a/frontend/src/pages/pong/pong.ts
+++ b/frontend/src/pages/pong/pong.ts
@@ -1,276 +1,36 @@
import { addRoute, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
-import { showError } from "@app/toast";
import authHtml from './pong.html?raw';
-import client from '@app/api'
-import { getUser, updateUser } from "@app/auth";
-import io, { Socket } from 'socket.io-client';
-import { addPongMessage } from './addPongMessage';
-import { isLoggedIn } from './isLoggedIn';
-import type { ClientMessage, ClientProfil } from './types_front';
-import { isNullish } from "@app/utils";
-
-export const color = {
- red: 'color: red;',
- green: 'color: green;',
- yellow: 'color: orange;',
- blue: 'color: blue;',
- reset: '',
-};
+import io from 'socket.io-client';
+import type { CSocket, GameMove, GameUpdate } from "./socket";
+import { showError, showInfo } from "@app/toast";
// TODO: local game (2player -> server -> 2player : current setup)
// TODO: tournament via remote (dedicated queu? idk)
//
// get the name of the machine used to connect
-const machineHostName = window.location.hostname;
-console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow);
+declare module 'ft_state' {
+ interface State {
+ pongSock?: CSocket;
+ }
+}
-export let __socket: Socket | undefined = undefined;
-
-document.addEventListener('ft:pageChange', () => { // dont regen socket on page change from forward/backward navigation arrows
- if (__socket !== undefined)
- __socket.close();
- __socket = undefined;
- console.log("Page changed");
+document.addEventListener("ft:pageChange", () => {
+ if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
+ window.__state.pongSock = undefined;
});
-/**
- * @returns the initialized socket
- */
-export function getSocket(): Socket {
- let addressHost = `wss://${machineHostName}:8888`;
-
- if (__socket === undefined)
- __socket = io(addressHost, {
- path: "/api/pong/socket.io/",
- secure: false,
- transports: ["websocket"],
- });
- return __socket;
-};
-
-/**
- *
- * @param socket The socket to wait for
- * @returns voir or a promise
- */
-function waitSocketConnected(socket: Socket): Promise {
- return new Promise(resolve => {
- if (socket.connected) return resolve();
- socket.on("connect", () => resolve());
- });
-};
-
-/**
- *
- * @param socket The socket to communicat
- * @returns nothing
- */
-async function whoami(socket: Socket) {
- try {
- const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
-
- const res = await client.guestLogin();
- switch (res.kind) {
- case 'success': {
- let user = await updateUser();
- if (chatWindow) {
- socket.emit('updateClientName', {
- oldUser: '',
- user: user?.name
- });
- }
- if (user === null)
- return showError('Failed to get user: no user ?');
- setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`);
- break;
- }
- case 'failed': {
- showError(`Failed to login: ${res.msg}`);
- }
- }
- } catch (e) {
- console.error("Login error:", e);
- showError('Failed to login: Unknown error');
- }
-};
+export function getSocket(): CSocket {
+ if (window.__state.pongSock === undefined)
+ window.__state.pongSock = io(window.location.host, { path: "/api/pong/socket.io/" }) as any as CSocket;
+ return window.__state.pongSock;
+}
function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
- let socket = getSocket();
-
- socket.on("connect", async () => {
- const systemWindow = document.getElementById('system-box') as HTMLDivElement;
- await waitSocketConnected(socket);
- console.log("I AM Connected to the server:", socket.id);
- // const message = {
- // command: "",
- // destination: 'system-info',
- // type: "chat",
- // user: getUser()?.name,
- // token: document.cookie ?? "",
- // text: " has Just ARRIVED in the chat",
- // timestamp: Date.now(),
- // SenderWindowID: socket.id,
- // };
- // socket.emit('message', JSON.stringify(message));
- const messageElement = document.createElement("div");
- // messageElement.textContent = `${message.user}: is connected au server`;
- messageElement.textContent = `${getUser()?.name ?? "unkown user"}: is connected au server`;
- systemWindow.appendChild(messageElement);
- systemWindow.scrollTop = systemWindow.scrollHeight;
- });
-
- // Queu handler
- async function joinQueu(socket : Socket) {
- try {
- const res = await client.guestLogin();
-
- switch (res.kind) {
- case 'success': {
- let user = await updateUser();
-
- if (user === null)
- return showError('Failed to get user: no user ?');
- socket.emit('queuJoin', user.id);
- console.log('queu join sent for : ', user.id);
- break;
- }
- case 'failed': {
- showError(`Failed to Join Game Queu: ${res.msg}`);
- console.log('Failed to Join Game Queu');
- }
- }
- } catch (err ) {
- showError(`Failed to Join Game Queu`);
- console.log('Failed to Join Game Queu');
- }
- }
-
- // keys handler
- const keys: Record = {};
-
- document.addEventListener("keydown", (e) => {
- keys[e.key.toLowerCase()] = true;
- });
- document.addEventListener("keyup", (e) => {
- keys[e.key.toLowerCase()] = false;
- });
- setInterval(() => { // key sender
- if ((keys['w'] || keys['s']) && !(keys['w'] && keys['s'])) { // exclusive or to filter requests
- if (keys['w']) {
- socket.emit("batmove_Left", "up");
- console.log('north key pressed - emit batmove_Left up');
- }
- if (keys['s']) {
- socket.emit("batmove_Left", "down");
- console.log('south key pressed - emit batmove_Left down');
- }
- }
- if ((keys['p'] || keys['l']) && !(keys['p'] && keys['l'])) { // exclusive or to filter requests
- if (keys['p']) {
- socket.emit("batmove_Right", "up");
- console.log('north key pressed - emit batmove_Right up');
- }
- if (keys['l']) {
- socket.emit("batmove_Right", "down");
- console.log('south key pressed - emit batmove_Right down');
- }
- }
- }, 16);
-
- // Pong Objects updators
- socket.on("batLeft_update", (y: number) => {
- console.log('batLeft_update received y: ', y);
- const bat = document.getElementById("batleft") as HTMLDivElement | null;
- if (!bat) {
- console.error("FATAL ERROR: Bat element with ID 'bat-left' not found. Check HTML.");
- return ;
- }
- if (typeof y === 'number' && !isNaN(y)) {
- bat.style.transform = `translateY(${y}px)`;
- } else {
- console.warn(`Received invalid Y value: ${y}`);
- }
- });
- socket.on("batRight_update", (y: number) => {
- console.log('batRight_update received y: ', y);
- const bat = document.getElementById("batright") as HTMLDivElement | null;
- if (!bat) {
- console.error("FATAL ERROR: Bat element with ID 'bat-Right' not found. Check HTML.");
- return ;
- }
- if (typeof y === 'number' && !isNaN(y)) {
- bat.style.transform = `translateY(${y}px)`;
- } else {
- console.warn(`Received invalid Y value: ${y}`);
- }
- });
- socket.on("ballPos_update", (x:number, y : number) => {
- console.log('ballPos_update recieved');
- const ball = document.getElementById("ball") as HTMLDivElement | null;
-
- if (!ball) {
- console.error("FATAL ERROR: Bat element with ID 'bat-Right' not found. Check HTML.");
- return ;
- }
- if (typeof y !== 'number' || isNaN(y) || typeof x !== 'number' || isNaN(x)) {
- console.warn(`Received invalid X/Y value: ${x} / ${y}`);
- return ;
- }
- ball.style.transform = `translateY(${y}px)`;
- ball.style.transform += `translateX(${x}px)`;
- });
-
- // socket.once('welcome', (data) => {
- // console.log('%cWelcome PONG PAGE', color.yellow );
- // addPongMessage('socket.once \'Welcome\' called')
- // });
-
- // Listen for messages from the server "MsgObjectServer"
- socket.on("MsgObjectServer", (data: { message: ClientMessage}) => {
- // Display the message in the chat window
- console.log("message recieved : ", data.message.text);
- const systemWindow = document.getElementById('system-box') as HTMLDivElement;
- const MAX_SYSTEM_MESSAGES = 10;
-
- if (systemWindow && data.message.destination === "system-info") {
- const messageElement = document.createElement("div");
- messageElement.textContent = `${data.message.user}: ${data.message.text}`;
- systemWindow.appendChild(messageElement);
-
- // keep only last 10
- while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
- systemWindow.removeChild(systemWindow.firstChild!);
- }
- systemWindow.scrollTop = systemWindow.scrollHeight;
- }
- if (systemWindow && data.message.destination === "score-info") {
- console.log("score update:", data.message.text);
- const scoreboard = document.getElementById('score-board') as HTMLHeadingElement ;
-
- if (!scoreboard) {
- console.log("update score failed :(");
- return ;
- }
- scoreboard.textContent = `${data.message.text}`;
- }
- // console.log("Getuser():", getUser());
- });
-
setTitle('Pong Game Page');
return {
html: authHtml, postInsert: async (app) => {
- const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement;
- const bqueu = document.getElementById('b-joinQueu') as HTMLButtonElement;
-
- bwhoami?.addEventListener('click', async () => {
- whoami(socket);
- });
- bqueu?.addEventListener('click', async () => {
- joinQueu(socket);
- });
-
const checkbox = document.getElementById("modeToggle") as HTMLInputElement;
const label = document.getElementById("toggleLabel") as HTMLSpanElement;
const track = document.getElementById("toggleTrack") as HTMLDivElement;
@@ -287,7 +47,67 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
knob.classList.remove("translate-x-7");
}
});
+
+
+
+ const batLeft = document.querySelector("#batleft");
+ const batRight = document.querySelector("#batright");
+ const ball = document.querySelector("#ball");
+ const score = document.querySelector("#score-board");
+ if (!batLeft || !batRight || !ball || !score)
+ return showError('fatal error');
+
+ let socket = getSocket();
+
+ // keys handler
+ const keys: Record = {};
+
+ document.addEventListener("keydown", (e) => {
+ keys[e.key.toLowerCase()] = true;
+ });
+ document.addEventListener("keyup", (e) => {
+ keys[e.key.toLowerCase()] = false;
+ });
+
+ setInterval(() => { // key sender
+ let packet: GameMove = {
+ move: null,
+ }
+ if ((keys['w'] !== keys['s'])) {
+ packet.move = keys['w'] ? 'up' : 'down';
+ }
+
+ socket.emit('gameMove', packet);
+ }, 1000 / 60);
+
+ const render = (state: GameUpdate) => {
+ //batLeft.style.transform = `translateY(${state.left.paddle.y}px) translateX(${state.left.paddle.x}px)`;
+ batLeft.style.top = `${state.left.paddle.y}px`;
+ batLeft.style.left = `${state.left.paddle.x}px`;
+ batLeft.style.width = `${state.left.paddle.width}px`;
+ batLeft.style.height = `${state.left.paddle.height}px`;
+
+ //batRight.style.transform = `translateY(${state.right.paddle.y}px) translateX(-${state.left.paddle.x}px)`;
+ batRight.style.top = `${state.right.paddle.y}px`;
+ batRight.style.left = `${state.right.paddle.x}px`;
+ batRight.style.width = `${state.right.paddle.width}px`;
+ batRight.style.height = `${state.right.paddle.height}px`;
+
+ ball.style.transform = `translateX(${state.ball.x - state.ball.size}px) translateY(${state.ball.y - state.ball.size}px)`;
+ ball.style.height = `${state.ball.size * 2}px`;
+ ball.style.width = `${state.ball.size * 2}px`;
+
+
+ score.innerText = `${state.left.score} | ${state.right.score}`
+ }
+
+ socket.on('gameUpdate', (state: GameUpdate) => render(state));
+ socket.on('newGame', (state) => render(state));
+
+ socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`));
+ socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`));
+ socket.emit('enqueue');
}
}
};
-addRoute('/pong', pongClient, { bypass_auth: true });
\ No newline at end of file
+addRoute('/pong', pongClient);
diff --git a/frontend/src/pages/pong/socket.ts b/frontend/src/pages/pong/socket.ts
new file mode 100644
index 0000000..044fb8b
--- /dev/null
+++ b/frontend/src/pages/pong/socket.ts
@@ -0,0 +1,47 @@
+import { Socket } from 'socket.io-client';
+
+export type UpdateInfo = {
+ inQueue: number,
+ totalUser: number,
+}
+
+export type PaddleData = {
+ x: number,
+ y: number,
+
+ width: number,
+ height: number,
+};
+
+export type GameUpdate = {
+ gameId: string;
+
+ left: { id: string, paddle: PaddleData, score: number };
+ right: { id: string, paddle: PaddleData, score: number };
+
+ ball: { x: number, y: number, size: number };
+}
+
+export type GameMove = {
+ move: 'up' | 'down' | null,
+}
+
+export interface ClientToServer {
+ enqueue: () => void;
+ dequeue: () => void;
+ debugInfo: () => void;
+ gameMove: (up: GameMove) => void;
+ connectedToGame: (gameId: string) => void;
+};
+
+export interface ServerToClient {
+ forceDisconnect: (reason: string) => void;
+ queueEvent: (msg: 'registered' | 'unregistered') => void;
+ updateInformation: (info: UpdateInfo) => void,
+ newGame: (initState: GameUpdate) => void,
+ gameUpdate: (state: GameUpdate) => void,
+ gameEnd: () => void;
+};
+
+export type SSocket = Socket;
+export type CSocket = Socket;
diff --git a/frontend/src/pages/pong/types_front.ts b/frontend/src/pages/pong/types_front.ts
deleted file mode 100644
index 792bdea..0000000
--- a/frontend/src/pages/pong/types_front.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-export type ClientMessage = {
- command: string
- destination: string;
- user: string;
- text: string;
- SenderWindowID: string;
-};
-
-export type ClientProfil = {
- command: string,
- destination: string,
- type: string,
- user: string,
- loginName: string,
- userID: string,
- text: string,
- timestamp: number,
- SenderWindowID:string,
- SenderName: string,
- Sendertext: string,
- innerHtml?: string,
-};
\ No newline at end of file
diff --git a/src/pong/entrypoint.sh b/src/pong/entrypoint.sh
deleted file mode 100644
index 2dcab02..0000000
--- a/src/pong/entrypoint.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-
-set -e
-set -x
-# do anything here
-
-# run the CMD [ ... ] from the dockerfile
-exec "$@"
diff --git a/src/pong/extra/.gitkeep b/src/pong/extra/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pong/src/@types/socket.io.d.ts b/src/pong/src/@types/socket.io.d.ts
new file mode 100644
index 0000000..38dedaa
--- /dev/null
+++ b/src/pong/src/@types/socket.io.d.ts
@@ -0,0 +1,12 @@
+import { type UserId } from '@shared/database/mixin/user';
+
+declare module 'socket.io'
+{
+ interface Socket {
+ authUser: {
+ id: UserId;
+ name: string;
+ guest: boolean;
+ }
+ }
+};
diff --git a/src/pong/src/app.ts b/src/pong/src/app.ts
index d4dc42c..8c1ef49 100644
--- a/src/pong/src/app.ts
+++ b/src/pong/src/app.ts
@@ -6,32 +6,11 @@ import * as auth from '@shared/auth';
import * as swagger from '@shared/swagger';
import * as utils from '@shared/utils';
import { Server, Socket } from 'socket.io';
-import { broadcast } from './broadcast';
-import type { ClientProfil, ClientMessage } from './chat_types';
-import { sendInvite } from './sendInvite';
-import { setGameLink } from './setGameLink';
-import { UserId } from '@shared/database/mixin/user';
-
-// colors for console.log
-export const color = {
- red: '\x1b[31m',
- green: '\x1b[32m',
- yellow: '\x1b[33m',
- blue: '\x1b[34m',
- reset: '\x1b[0m',
-};
+import { newState, State } from './state';
+import { ClientToServer, ServerToClient } from './socket';
declare const __SERVICE_NAME: string;
-// Global map of clients
-// key = socket, value = clientname
-interface ClientInfo {
- user: string;
- lastSeen: number;
-}
-
-export const clientChat = new Map();
-
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true });
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
@@ -60,6 +39,7 @@ const app: FastifyPluginAsync = async (fastify, opts): Promise => {
fastify.ready((err) => {
if (err) throw err;
+ newState(fastify);
onReady(fastify);
});
};
@@ -69,266 +49,13 @@ export { app };
// When using .decorate you have to specify added properties for Typescript
declare module 'fastify' {
interface FastifyInstance {
- io: Server<{
- inviteGame: (data: ClientProfil) => void;
- message: (msg: string) => void;
- batmove_Left: (direction: 'up' | 'down') => void;
- batmove_Right: (direction: 'up' | 'down') => void;
- batLeft_update: (y: number) => void;
- batRight_update: (y: number) => void;
- ballPos_update: (x: number, y: number) => void;
- MsgObjectServer: (data: { message: ClientMessage }) => void;
- queuJoin: (userID: UserId) => void;
- }>;
+ io: Server;
}
}
-function isInRange(x: number, low: number, high: number) {
- if (x >= low && x <= high) return true;
- return false;
-}
-
-async function sendScore(
- socket: Socket,
- scoreLeft: number,
- scoreRight: number,
-) {
- // idk why, sometimes... it fails?
- const msg: ClientMessage = {
- destination: 'score-info',
- command: '',
- user: '',
- text: scoreLeft.toString() + ':' + scoreRight.toString(),
- SenderWindowID: '',
- };
-
- socket.emit('MsgObjectServer', { message: msg });
-}
-
async function onReady(fastify: FastifyInstance) {
- // shows address for connection au server transcendance
- const session = process.env.SESSION_MANAGER ?? '';
- if (session) {
- const part = session.split('/')[1];
- const machineName = part.split('.')[0];
- console.log(
- color.yellow,
- 'Connect at : https://' + machineName + ':8888/app/login',
- );
- }
-
- // DRAW AREA
- // top edge of the field
- const TOP_EDGE = 0;
- // bottom edge of the field;
- const BOTTOM_EDGE = 450;
- const LEFT_EDGE = 0;
- const RIGHT_EDGE = 800;
-
- void LEFT_EDGE;
-
- // PADDLEs
- const PADDLE_HEIGHT = 80;
- const PADDLE_WIDTH = 12;
-
- const PADDLE_SPEED = 20;
- const PADDLE_X_OFFSET = 4;
-
- // 370
- const MAX_PADDLE_Y = BOTTOM_EDGE - PADDLE_HEIGHT;
- // 185
- const PADDLE_START = BOTTOM_EDGE / 2 - PADDLE_HEIGHT / 2;
-
- // BALL
- // widht times 2 bc rounded on moth sides + 4 for border
- const BALL_SIZE = 8 * 2 + 4;
- const START_BALLX = RIGHT_EDGE / 2 - BALL_SIZE;
- const START_BALLY = BOTTOM_EDGE / 2 - BALL_SIZE;
-
- const ACCELERATION_FACTOR = 1.15;
- const ABS_MAX_BALL_SPEED = 3;
-
- // val inits
- // shared start bat position
- let paddleLeft = PADDLE_START;
- // shared start bat position
- let paddleRight = PADDLE_START;
-
- let ballPosX = START_BALLX;
- let ballPosY = START_BALLY;
- let ballSpeedX = -1;
- let ballSpeedY = -1;
- let scoreL = 0;
- let scoreR = 0;
-
- // uuid, game uid - if not in game empty string
- const games: Record = {};
-
fastify.io.on('connection', (socket: Socket) => {
- socket.emit('batLeft_update', paddleLeft);
- socket.emit('batRight_update', paddleRight);
- socket.emit('ballPos_update', ballPosX, ballPosY);
- sendScore(socket, scoreL, scoreR);
-
- // GAME
- // paddle handling
- socket.on('batmove_Left', (direction: 'up' | 'down') => {
- if (direction === 'up') {
- paddleLeft -= PADDLE_SPEED;
- }
- if (direction === 'down') {
- paddleLeft += PADDLE_SPEED;
- }
- // position of bat leftplokoplpl
- paddleLeft = Math.max(TOP_EDGE, Math.min(MAX_PADDLE_Y, paddleLeft));
- console.log('batLeft_update:', paddleLeft);
- socket.emit('batLeft_update', paddleLeft);
- });
- socket.on('batmove_Right', (direction: 'up' | 'down') => {
- if (direction === 'up') {
- paddleRight -= PADDLE_SPEED;
- }
- if (direction === 'down') {
- paddleRight += PADDLE_SPEED;
- }
- // position of bat left
- paddleRight = Math.max(
- TOP_EDGE,
- Math.min(MAX_PADDLE_Y, paddleRight),
- );
- socket.emit('batRight_update', paddleRight);
- });
- // ball handling:
- setInterval(async () => {
- const new_ballPosX = ballPosX + ballSpeedX;
- const new_ballPosY = ballPosY + ballSpeedY;
-
- if (
- ((isInRange(
- new_ballPosY,
- paddleLeft,
- paddleLeft + PADDLE_HEIGHT,
- ) ||
- isInRange(
- new_ballPosY + BALL_SIZE * 2,
- paddleLeft,
- paddleLeft + PADDLE_HEIGHT,
- )) &&
- // y ok ?
- isInRange(
- new_ballPosX,
- PADDLE_X_OFFSET,
- PADDLE_X_OFFSET + PADDLE_WIDTH,
- ) &&
- ballSpeedX < 0) ||
- // x ok? && ball going toward paddle?
- ((isInRange(
- new_ballPosY,
- paddleRight,
- paddleRight + PADDLE_HEIGHT,
- ) ||
- isInRange(
- new_ballPosY + BALL_SIZE * 2,
- paddleRight,
- paddleRight + PADDLE_HEIGHT,
- )) &&
- // right side equations
- isInRange(
- new_ballPosX + BALL_SIZE * 2,
- RIGHT_EDGE - PADDLE_X_OFFSET - PADDLE_WIDTH,
- RIGHT_EDGE - PADDLE_X_OFFSET,
- ) &&
- ballSpeedX > 0)
- ) {
- ballSpeedX *= -1;
- ballSpeedX *= ACCELERATION_FACTOR;
- ballSpeedY *= ACCELERATION_FACTOR;
- console.log('bat colision');
- }
- else if (
- new_ballPosX < 0 ||
- new_ballPosX + BALL_SIZE * 2 > RIGHT_EDGE
- ) {
- ballPosX = START_BALLX;
- ballPosY = START_BALLY;
- ballSpeedX = Math.random() - 0.5 < 0 ? -1 : 1;
-
- if (new_ballPosX < 0) {
- scoreR += 1;
- ballSpeedY = -1;
- }
- else {
- scoreL += 1;
- ballSpeedY = 1;
- }
- if (scoreL >= 5 || scoreR >= 5) {
- console.log('game should stop + board reset');
- // temp solution
- ballSpeedX = 0;
- ballSpeedY = 0;
- // reset board :D
- }
- console.log('point scored');
- sendScore(socket, scoreL, scoreR);
- // TODO: score point + ball reset + spd reset
- }
- else if (
- new_ballPosY < 0 ||
- new_ballPosY + BALL_SIZE * 2 > BOTTOM_EDGE
- ) {
- ballSpeedY *= -1;
- ballSpeedX *= ACCELERATION_FACTOR;
- ballSpeedY *= ACCELERATION_FACTOR;
- }
- ballSpeedX = Math.max(
- -ABS_MAX_BALL_SPEED,
- Math.min(ballSpeedX, ABS_MAX_BALL_SPEED),
- );
- ballSpeedY = Math.max(
- -ABS_MAX_BALL_SPEED,
- Math.min(ballSpeedY, ABS_MAX_BALL_SPEED),
- );
-
- ballPosX += ballSpeedX;
- ballPosY += ballSpeedY;
-
- socket.emit('ballPos_update', ballPosX, ballPosY);
- }, 16);
-
- // QUEUE HANDL
- socket.on('queuJoin', async (uuid: UserId) => {
- console.log('queu join recieved for : ', uuid);
- if (!(uuid in games.hasOwnProperty)) {
- console.log('new user in game search queu');
- games[uuid] = '';
- }
- else if (uuid in games && games[uuid] == '') {
- console.log('already searching for game');
- }
- else {
- // (games.hasOwnProperty(uuid) && games[uuid] != "") {
- console.log('user alredy in game');
- return;
- }
- // TODO: step2 : sesrch in record<> find guid w/ "" &/ pair them up
- // TODO: step3 : move game logic to lifecycle of queu'ed game
- });
-
- // other:
- socket.on('message', (message: string) => {
- const obj: ClientMessage = JSON.parse(message) as ClientMessage;
- clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() });
- socket.emit('welcome', { msg: 'Welcome to the chat! : ' });
- broadcast(fastify, obj, obj.SenderWindowID);
- });
- socket.on('inviteGame', async (data: string) => {
- const clientName: string = clientChat.get(socket.id)?.user || '';
- const profilInvite: ClientProfil = JSON.parse(data) || '';
- const inviteHtml: string =
- 'invites you to a game ' + setGameLink('');
- if (clientName !== null) {
- sendInvite(fastify, inviteHtml, profilInvite);
- }
- });
+ fastify.log.info(`Client connected: ${socket.id}`);
+ State.registerUser(socket);
});
}
diff --git a/src/pong/src/broadcast.ts b/src/pong/src/broadcast.ts
deleted file mode 100644
index ac93f1f..0000000
--- a/src/pong/src/broadcast.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { ClientMessage } from './chat_types';
-import { clientChat, color } from './app';
-import { FastifyInstance } from 'fastify';
-
-export function broadcast(fastify: FastifyInstance, data: ClientMessage, sender?: string) {
- fastify.io.fetchSockets().then((sockets) => {
- for (const socket of sockets) {
- // Skip sender's own socket
- if (socket.id === sender) continue;
- // Get client name from map
- const clientInfo = clientChat.get(socket.id);
- if (!clientInfo?.user) {
- console.log(color.yellow, `Skipping socket ${socket.id} (no user found)`);
- continue;
- }
- // Emit structured JSON object
- socket.emit('MsgObjectServer', { message: data });
- // Debug logs
- // console.log(color.green, `'DEBUG LOG: Broadcast to:', ${data.command} message: ${data.text}`);
- }
- });
-}
diff --git a/src/pong/src/chat_types.ts b/src/pong/src/chat_types.ts
deleted file mode 100644
index 73ceeeb..0000000
--- a/src/pong/src/chat_types.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-export type ClientMessage = {
- command: string
- destination: string;
- user: string;
- text: string;
- SenderWindowID: string;
-};
-
-export type ClientProfil = {
- command: string,
- destination: string,
- type: string,
- user: string,
- loginName: string,
- userID: string,
- text: string,
- timestamp: number,
- SenderWindowID: string,
- SenderName: string,
- Sendertext: string,
- innerHtml?: string,
-
-};
diff --git a/src/pong/src/game.ts b/src/pong/src/game.ts
new file mode 100644
index 0000000..b7f9974
--- /dev/null
+++ b/src/pong/src/game.ts
@@ -0,0 +1,216 @@
+import { UserId } from '@shared/database/mixin/user';
+
+export class Paddle {
+ public static readonly DEFAULT_SPEED = 20;
+ public static readonly DEFAULT_HEIGHT = 80;
+ public static readonly DEFAULT_WIDTH = 12;
+
+ public height: number = Paddle.DEFAULT_HEIGHT;
+ public width: number = Paddle.DEFAULT_WIDTH;
+ public speed: number = Paddle.DEFAULT_SPEED;
+
+ constructor(
+ // these coordiantes are the topleft corordinates
+ public x: number,
+ public y: number,
+ ) { }
+
+ public move(dir: 'up' | 'down') {
+ this.y += (dir === 'up' ? -1 : 1) * this.speed;
+ }
+
+ public clamp(bottom: number, top: number) {
+ if (this.y <= bottom) this.y = bottom;
+ if (this.y + this.height >= top) this.y = top - this.height;
+ }
+}
+
+class Ball {
+ public static readonly DEFAULT_SPEED = 1;
+ public static readonly DEFAULT_SIZE = 16;
+ public static readonly DEFAULT_MAX_SPEED = 30;
+ public static readonly DEFAULT_MIN_SPEED = Ball.DEFAULT_SPEED;
+ public static readonly DEFAULT_ACCEL_FACTOR = 1.2;
+
+ public speed: number = Ball.DEFAULT_SPEED;
+ public size: number = Ball.DEFAULT_SIZE;
+ public accel_factor: number = Ball.DEFAULT_ACCEL_FACTOR;
+
+ public max_speed: number = Ball.DEFAULT_MAX_SPEED;
+ public min_speed: number = Ball.DEFAULT_MIN_SPEED;
+
+ constructor(
+ // these coordiantes are the center coordinates
+ public x: number,
+ public y: number,
+ public angle: number,
+ ) { }
+
+ public collided(
+ side: 'left' | 'right' | 'top' | 'bottom',
+ walls: { [k in typeof side]: number },
+ ) {
+ // this.speed *= this.accel_factor;
+ this.speed = Math.max(
+ Math.min(this.speed, this.max_speed),
+ this.min_speed,
+ );
+
+ let c: 'x' | 'y' = 'x';
+ if (side === 'top' || side === 'bottom') {
+ this.angle = -this.angle;
+ c = 'y';
+ }
+ else {
+ this.angle = -this.angle + Math.PI;
+ c = 'x';
+ }
+ this[c] =
+ walls[side] +
+ this.size * (side === 'right' || side === 'bottom' ? -1 : 1);
+
+ while (this.angle >= Math.PI) {
+ this.angle -= 2 * Math.PI;
+ }
+ while (this.angle < -Math.PI) {
+ this.angle += 2 * Math.PI;
+ }
+ }
+
+ public tick() {
+ this.x += Math.cos(this.angle) * this.speed;
+ this.y += Math.sin(this.angle) * this.speed;
+ }
+}
+
+function makeAngle(i: number): [number, number, number, number] {
+ return [
+ Math.PI / i,
+ Math.PI / i + Math.PI,
+ -Math.PI / i,
+ -Math.PI / i + Math.PI,
+ ];
+}
+
+export class Pong {
+ public gameUpdate: NodeJS.Timeout | null = null;
+
+
+ public static readonly BALL_START_ANGLES: number[] = [
+ ...makeAngle(4),
+ ...makeAngle(6),
+ ];
+
+ public ballAngleIdx: number = 0;
+
+ public static readonly GAME_WIDTH: number = 800;
+ public static readonly GAME_HEIGHT: number = 450;
+
+ public static readonly PADDLE_OFFSET: number = 40;
+
+ public leftPaddle: Paddle = new Paddle(
+ Pong.PADDLE_OFFSET,
+ (Pong.GAME_HEIGHT - Paddle.DEFAULT_HEIGHT) / 2,
+ );
+ public rightPaddle: Paddle = new Paddle(
+ Pong.GAME_WIDTH - Pong.PADDLE_OFFSET - Paddle.DEFAULT_WIDTH,
+ (Pong.GAME_HEIGHT - Paddle.DEFAULT_HEIGHT) / 2,
+ );
+ public ball: Ball = new Ball(Pong.GAME_WIDTH / 2, Pong.GAME_HEIGHT / 2, Pong.BALL_START_ANGLES[this.ballAngleIdx++]);
+
+ public score: [number, number] = [0, 0];
+
+ constructor(
+ public userLeft: UserId,
+ public userRight: UserId,
+ ) {
+ }
+
+ public tick() {
+ if (this.paddleCollision(this.leftPaddle, 'left')) {
+ this.ball.collided('left', {
+ left: this.leftPaddle.x + this.leftPaddle.width,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ });
+ return;
+ }
+ if (this.paddleCollision(this.rightPaddle, 'right')) {
+ this.ball.collided('right', {
+ right: this.rightPaddle.x,
+ left: 0,
+ top: 0,
+ bottom: 0,
+ });
+ return;
+ }
+ const wallCollision = this.boxCollision();
+ if (wallCollision === 'top' || wallCollision === 'bottom') {
+ this.ball.collided(wallCollision, {
+ left: 0,
+ top: 0,
+ bottom: Pong.GAME_HEIGHT,
+ right: Pong.GAME_WIDTH,
+ });
+ }
+ else if (wallCollision !== null) {
+ const idx = wallCollision === 'left' ? 1 : 0;
+ this.score[idx] += 1;
+ this.ball = new Ball(
+ Pong.GAME_WIDTH / 2,
+ Pong.GAME_HEIGHT / 2,
+ Pong.BALL_START_ANGLES[this.ballAngleIdx++],
+ );
+ this.ballAngleIdx %= Pong.BALL_START_ANGLES.length;
+ }
+ this.ball.tick();
+ }
+
+ // This function will return which side the ball collided, if any
+ private boxCollision(): 'top' | 'bottom' | 'left' | 'right' | null {
+ if (this.ball.y - this.ball.size <= 0) return 'top';
+ if (this.ball.y + this.ball.size >= Pong.GAME_HEIGHT) return 'bottom';
+ if (this.ball.x - this.ball.size <= 0) return 'left';
+ if (this.ball.x + this.ball.size >= Pong.GAME_WIDTH) return 'right';
+ return null;
+ }
+
+ private paddleCollision(paddle: Paddle, side: 'left' | 'right'): boolean {
+ // now we check only if the ball is near enought in the y axis to permform the collision
+ if (!(
+ // check if ball is bellow the top of the paddle
+ paddle.y - this.ball.size < this.ball.y &&
+ // check if ball is above the bottom of the paddle
+ this.ball.y < paddle.y + paddle.height + this.ball.size)) return false;
+
+ // so we know that the y is close enougth to be a bit, so we check the X. are we closer than the ball size ? if yes -> hit
+ if (
+ // check if the paddle.x is at most ball.size away from the center of the ball => we have a hit houston
+ // call he pentagon, 9 11
+ Math.abs(
+ paddle.x + paddle.width * (side === 'left' ? 1 : 0)
+ - this.ball.x)
+ < this.ball.size
+ ) return true;
+ return false;
+ }
+
+ public checkWinner(): 'left' | 'right' | null {
+ if (this.score[0] >= 5) return 'left';
+ if (this.score[1] >= 5) return 'right';
+ return null;
+ }
+
+ public movePaddle(user: UserId, dir: 'up' | 'down') {
+ const paddle =
+ user === this.userLeft
+ ? this.leftPaddle
+ : user == this.userRight
+ ? this.rightPaddle
+ : null;
+ if (paddle === null) return;
+ paddle.move(dir);
+ paddle.clamp(0, Pong.GAME_HEIGHT);
+ }
+}
diff --git a/src/pong/src/plugins/socket.ts b/src/pong/src/plugins/socket.ts
index ed248ff..c9705ba 100644
--- a/src/pong/src/plugins/socket.ts
+++ b/src/pong/src/plugins/socket.ts
@@ -1,3 +1,7 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { JwtType } from '@shared/auth';
+import { UserId } from '@shared/database/mixin/user';
+import { isNullish } from '@shared/utils';
import type {
FastifyInstance,
FastifyPluginAsync,
@@ -11,6 +15,21 @@ const F: (
) => Omit & { io: Server } = (f) =>
f as Omit & { io: Server };
+function authenticateToken(
+ fastify: FastifyInstance,
+ token: string,
+): { id: UserId; name: string; guest: boolean } {
+ const tok = fastify.jwt.verify(token);
+ if (tok.kind != 'auth') {
+ throw new Error('Token isn\'t correct type');
+ }
+ const user = fastify.db.getUser(tok.who);
+ if (isNullish(user)) {
+ throw new Error('User not found');
+ }
+ return { id: user.id, name: user.name, guest: user.guest };
+}
+
const fastifySocketIO: FastifyPluginAsync = fp(async (fastify) => {
function defaultPreClose(done: HookHandlerDoneFunction) {
F(fastify).io.local.disconnectSockets(true);
@@ -20,6 +39,36 @@ const fastifySocketIO: FastifyPluginAsync = fp(async (fastify) => {
'io',
new Server(fastify.server, { path: '/api/pong/socket.io' }),
);
+ F(fastify).io.use((socket, next) => {
+ const cookieHeader = socket.request.headers.cookie;
+
+ if (!cookieHeader) {
+ throw new Error('Missing token cookie');
+ }
+
+ const cookies = Object.fromEntries(
+ cookieHeader.split(';').map((c) => {
+ const [k, v] = c.trim().split('=');
+ return [k, v];
+ }),
+ );
+
+ if (!cookies.token) {
+ throw new Error('Missing token cookie');
+ }
+ try {
+ socket.authUser = authenticateToken(fastify, cookies.token);
+ next();
+ }
+ catch (e: any) {
+ next({
+ name: 'Unauthorized',
+ message: e.message,
+ data: { status: 401 },
+ });
+ }
+ });
+
fastify.addHook('preClose', defaultPreClose);
fastify.addHook('onClose', (instance: FastifyInstance, done) => {
F(instance).io.close();
@@ -28,4 +77,3 @@ const fastifySocketIO: FastifyPluginAsync = fp(async (fastify) => {
});
export default fastifySocketIO;
-
diff --git a/src/pong/src/routes/broadcast.ts b/src/pong/src/routes/broadcast.ts
index 8f4b533..a37b460 100644
--- a/src/pong/src/routes/broadcast.ts
+++ b/src/pong/src/routes/broadcast.ts
@@ -1,6 +1,5 @@
import { FastifyPluginAsync } from 'fastify';
import { Static, Type } from 'typebox';
-import { broadcast } from '../broadcast';
export const PongReq = Type.Object({
message: Type.String(),
@@ -19,7 +18,6 @@ const route: FastifyPluginAsync = async (fastify): Promise => {
config: { requireAuth: false },
},
async function(req, res) {
- broadcast(this, { command: '', destination: 'system-info', user: 'CMwaLeSever!!', text: req.body.message, SenderWindowID: 'server' });
void res;
},
);
diff --git a/src/pong/src/sendInvite.ts b/src/pong/src/sendInvite.ts
deleted file mode 100644
index 489dc89..0000000
--- a/src/pong/src/sendInvite.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { ClientProfil } from './chat_types';
-import { clientChat, color } from './app';
-import { FastifyInstance } from 'fastify';
-
-/**
- * function looks for the user online in the chat
- * and sends emit to invite - format HTML to make clickable
- * message appears in chat window text area
- * @param fastify
- * @param innerHtml
- * @param profil
- */
-
-export function sendInvite(fastify: FastifyInstance, innerHtml: string, profil: ClientProfil) {
- fastify.io.fetchSockets().then((sockets) => {
- let targetSocket;
- for (const socket of sockets) {
- const clientInfo: string = clientChat.get(socket.id)?.user || '';
- if (clientInfo === profil.user) {
- console.log(color.yellow, 'DEBUG LOG: user online found', profil.user);
- targetSocket = socket || '';
- break;
- }
- }
- profil.innerHtml = innerHtml ?? '';
- if (targetSocket) {
- targetSocket.emit('inviteGame', profil);
- }
- });
-}
diff --git a/src/pong/src/setGameLink.ts b/src/pong/src/setGameLink.ts
deleted file mode 100644
index e0cb51b..0000000
--- a/src/pong/src/setGameLink.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export function setGameLink(link: string): string {
- if (!link) {
- link = 'Click me';
- }
- return link;
-};
diff --git a/src/pong/src/socket.ts b/src/pong/src/socket.ts
new file mode 100644
index 0000000..05e674c
--- /dev/null
+++ b/src/pong/src/socket.ts
@@ -0,0 +1,47 @@
+import { Socket } from 'socket.io';
+
+export type UpdateInfo = {
+ inQueue: number,
+ totalUser: number,
+}
+
+export type PaddleData = {
+ x: number,
+ y: number,
+
+ width: number,
+ height: number,
+};
+
+export type GameUpdate = {
+ gameId: string;
+
+ left: { id: string, paddle: PaddleData, score: number };
+ right: { id: string, paddle: PaddleData, score: number };
+
+ ball: { x: number, y: number, size: number };
+}
+
+export type GameMove = {
+ move: 'up' | 'down' | null,
+}
+
+export interface ClientToServer {
+ enqueue: () => void;
+ dequeue: () => void;
+ debugInfo: () => void;
+ gameMove: (up: GameMove) => void;
+ connectedToGame: (gameId: string) => void;
+};
+
+export interface ServerToClient {
+ forceDisconnect: (reason: string) => void;
+ queueEvent: (msg: 'registered' | 'unregistered') => void;
+ updateInformation: (info: UpdateInfo) => void,
+ newGame: (initState: GameUpdate) => void,
+ gameUpdate: (state: GameUpdate) => void,
+ gameEnd: () => void;
+};
+
+export type SSocket = Socket;
+export type CSocket = Socket;
diff --git a/src/pong/src/state.ts b/src/pong/src/state.ts
new file mode 100644
index 0000000..7fd7cff
--- /dev/null
+++ b/src/pong/src/state.ts
@@ -0,0 +1,182 @@
+import { UserId } from '@shared/database/mixin/user';
+import { newUUID } from '@shared/utils/uuid';
+import { FastifyInstance } from 'fastify';
+import { Pong } from './game';
+import { GameMove, GameUpdate, SSocket } from './socket';
+import { isNullish } from '@shared/utils';
+
+type PUser = {
+ id: UserId;
+ currentGame: null | GameId;
+ socket: SSocket,
+ windowId: string,
+ updateInterval: NodeJS.Timeout,
+};
+
+type GameId = string & { readonly __brand: unique symbol };
+
+class StateI {
+ public static readonly UPDATE_INTERVAL_FRAMES: number = 60;
+
+ private users: Map = new Map();
+ private queue: Set = new Set();
+ private queueInterval: NodeJS.Timeout;
+ private games: Map = new Map();
+
+ public constructor(private fastify: FastifyInstance) {
+ this.queueInterval = setInterval(() => this.queuerFunction());
+ void this.queueInterval;
+ }
+
+ private static getGameUpdateData(id: GameId, g: Pong): GameUpdate {
+ return {
+ gameId: id,
+ left: { id: g.userLeft, score: g.score[0], paddle: { x: g.leftPaddle.x, y: g.leftPaddle.y, width: g.leftPaddle.width, height: g.leftPaddle.height } },
+ right: { id: g.userRight, score: g.score[1], paddle: { x: g.rightPaddle.x, y: g.rightPaddle.y, width: g.rightPaddle.width, height: g.rightPaddle.height } },
+ ball: { x: g.ball.x, y: g.ball.y, size: g.ball.size },
+ };
+ }
+
+ private queuerFunction(): void {
+ const values = Array.from(this.queue.values());
+ while (values.length >= 2) {
+ const id1 = values.pop();
+ const id2 = values.pop();
+
+ if (isNullish(id1) || isNullish(id2)) {
+ continue;
+ }
+
+ const u1 = this.users.get(id1);
+ const u2 = this.users.get(id2);
+
+ if (isNullish(u1) || isNullish(u2)) {
+ continue;
+ }
+ this.queue.delete(id1);
+ this.queue.delete(id2);
+
+ const gameId = newUUID() as unknown as GameId;
+ const g = new Pong(u1.id, u2.id);
+ const iState: GameUpdate = StateI.getGameUpdateData(gameId, g);
+
+ u1.socket.emit('newGame', iState);
+ u2.socket.emit('newGame', iState);
+ this.games.set(gameId, g);
+
+ u1.currentGame = gameId;
+ u2.currentGame = gameId;
+
+ g.gameUpdate = setInterval(() => {
+ g.tick();
+ this.gameUpdate(gameId, u1.socket);
+ this.gameUpdate(gameId, u2.socket);
+ if (g.checkWinner() !== null) { this.cleanupGame(gameId, g); }
+ }, 1000 / StateI.UPDATE_INTERVAL_FRAMES);
+ }
+ }
+
+ private gameUpdate(id: GameId, sock: SSocket) {
+ // does the game we want to update the client exists ?
+ if (!this.games.has(id)) return;
+ // is the client someone we know ?
+ if (!this.users.has(sock.authUser.id)) return;
+ // is the client associated with that game ?
+ if (this.users.get(sock.authUser.id)!.currentGame !== id) return;
+ sock.emit('gameUpdate', StateI.getGameUpdateData(id, this.games.get(id)!));
+ }
+
+ private gameMove(socket: SSocket, u: GameMove) {
+ // do we know this user ?
+ if (!this.users.has(socket.authUser.id)) return;
+ const user = this.users.get(socket.authUser.id)!;
+ // does the user have a game and do we know such game ?
+ if (user.currentGame === null || !this.games.has(user.currentGame)) return;
+ const game = this.games.get(user.currentGame)!;
+
+ if (u.move !== null) { game.movePaddle(user.id, u.move); }
+ }
+
+
+ public registerUser(socket: SSocket): void {
+ this.fastify.log.info('Registering new user');
+ if (this.users.has(socket.authUser.id)) {
+ socket.emit('forceDisconnect', 'Already Connected');
+ socket.disconnect();
+ return;
+ }
+ this.users.set(socket.authUser.id, {
+ socket,
+ id: socket.authUser.id,
+ windowId: socket.id,
+ updateInterval: setInterval(() => this.updateClient(socket), 3000),
+ currentGame: null,
+ });
+ this.fastify.log.info('Registered new user');
+
+ socket.on('disconnect', () => this.cleanupUser(socket));
+ socket.on('enqueue', () => this.enqueueUser(socket));
+ socket.on('dequeue', () => this.dequeueUser(socket));
+
+ socket.on('gameMove', (e) => this.gameMove(socket, e));
+ }
+
+ private updateClient(socket: SSocket): void {
+ socket.emit('updateInformation', {
+ inQueue: this.queue.size,
+ totalUser: this.users.size,
+ });
+ }
+
+ private cleanupUser(socket: SSocket): void {
+ if (!this.users.has(socket.authUser.id)) return;
+
+ clearInterval(this.users.get(socket.authUser.id)?.updateInterval);
+ this.users.delete(socket.authUser.id);
+ this.queue.delete(socket.authUser.id);
+ }
+
+ private cleanupGame(gameId: GameId, game: Pong): void {
+ clearInterval(game.gameUpdate ?? undefined);
+ this.games.delete(gameId);
+ let player: PUser | undefined = undefined;
+ if ((player = this.users.get(game.userLeft)) !== undefined) {
+ player.currentGame = null;
+ player.socket.emit('gameEnd');
+ }
+ if ((player = this.users.get(game.userRight)) !== undefined) {
+ player.currentGame = null;
+ player.socket.emit('gameEnd');
+ }
+ // do something here with the game result before deleting the game at the end
+ }
+
+
+ private enqueueUser(socket: SSocket): void {
+ if (!this.users.has(socket.authUser.id)) return;
+
+ if (this.queue.has(socket.authUser.id)) return;
+
+ if (this.users.get(socket.authUser.id)?.currentGame !== null) return;
+
+ this.queue.add(socket.authUser.id);
+ socket.emit('queueEvent', 'registered');
+ }
+
+ private dequeueUser(socket: SSocket): void {
+ if (!this.users.has(socket.authUser.id)) return;
+
+ if (!this.queue.has(socket.authUser.id)) return;
+
+ this.queue.delete(socket.authUser.id);
+ socket.emit('queueEvent', 'unregistered');
+ }
+
+
+}
+
+export let State: StateI = undefined as unknown as StateI;
+
+export function newState(f: FastifyInstance) {
+ State = new StateI(f);
+}