From 830d733f1bb423af2a688bee449fc820b1f2dba5 Mon Sep 17 00:00:00 2001 From: Alessandro Petitcollin Date: Sat, 10 Jan 2026 18:12:04 +0100 Subject: [PATCH] Initial commit --- frontend/src/pages/ttt/ttt.ts | 330 +++++++++++++++++----------------- 1 file changed, 165 insertions(+), 165 deletions(-) diff --git a/frontend/src/pages/ttt/ttt.ts b/frontend/src/pages/ttt/ttt.ts index 71f9e5b..2c22add 100644 --- a/frontend/src/pages/ttt/ttt.ts +++ b/frontend/src/pages/ttt/ttt.ts @@ -1,39 +1,39 @@ import "./ttt.css" -import { addRoute, type RouteHandlerReturn } from "@app/routing"; +import {addRoute, type RouteHandlerReturn} from "@app/routing"; import tttPage from "./ttt.html?raw"; -import { showError, showInfo, showSuccess, showWarn } from "@app/toast"; -import { io } from "socket.io-client"; -import type { GameUpdate, CSocket as Socket } from "./socket"; -import { updateUser } from "@app/auth"; +import {showError, showInfo, showSuccess, showWarn} from "@app/toast"; +import {io} from "socket.io-client"; +import type {CSocket as Socket, GameUpdate} from "./socket"; +import {updateUser} from "@app/auth"; import client from "@app/api"; declare module 'ft_state' { - interface State { - tttSock?: Socket; - tttkeepAliveInterval?: ReturnType; - } + interface State { + tttSock?: Socket; + tttkeepAliveInterval?: ReturnType; + } } enum QueueState { - InQueue = "In Queue", - InGame = "In Game", - Idle = "Join Queue", -}; + InQueue = "In Queue", + InGame = "In Game", + Idle = "Join Queue", +} document.addEventListener("ft:pageChange", () => { - if (window.__state.tttSock !== undefined) window.__state.tttSock.close(); - if (window.__state.tttkeepAliveInterval !== undefined) clearInterval(window.__state.tttkeepAliveInterval); - window.__state.tttSock = undefined; - window.__state.tttkeepAliveInterval = undefined; + if (window.__state.tttSock !== undefined) window.__state.tttSock.close(); + if (window.__state.tttkeepAliveInterval !== undefined) clearInterval(window.__state.tttkeepAliveInterval); + window.__state.tttSock = undefined; + window.__state.tttkeepAliveInterval = undefined; }); export function getSocket(): Socket { - if (window.__state.tttSock === undefined) - window.__state.tttSock = io(window.location.host, { path: "/api/ttt/socket.io/" }) as any as Socket; - if (window.__state.tttkeepAliveInterval === undefined) - window.__state.tttkeepAliveInterval = setInterval(() => window.__state.tttSock?.emit('keepalive'), 100); - return window.__state.tttSock; + if (window.__state.tttSock === undefined) + window.__state.tttSock = io(window.location.host, {path: "/api/ttt/socket.io/"}) as any as Socket; + if (window.__state.tttkeepAliveInterval === undefined) + window.__state.tttkeepAliveInterval = setInterval(() => window.__state.tttSock?.emit('keepalive'), 100); + return window.__state.tttSock; } type CurrentGameInfo = GameUpdate & { lastState: GameUpdate['gameState'] | null }; @@ -41,164 +41,164 @@ type CurrentGameInfo = GameUpdate & { lastState: GameUpdate['gameState'] | null // Route handler for the Tic-Tac-Toe page. // Instantiates the game logic and binds UI events. async function handleTTT(): Promise { - const socket: Socket = getSocket(); - void socket; - return { - html: tttPage, postInsert: async (app) => { - if (!app) { - return; - } - let user = await updateUser(); - if (user === null) - return; + const socket: Socket = getSocket(); + void socket; + return { + html: tttPage, postInsert: async (app) => { + if (!app) { + return; + } + let user = await updateUser(); + if (user === null) + return; - const userXString = document.getElementById("playerX-name"); - const userOString = document.getElementById("playerO-name"); - const currentPlayerIndicator = document.getElementById("currentPlayer"); - const joinQueueBtn = document.getElementById("JoinQueueBtn"); - const currentPlayerTimer = document.getElementById("currentPlayerTimer") - if (!userXString || !userOString || !currentPlayerIndicator || !joinQueueBtn || !currentPlayerTimer) { - return showError('fatal error'); - } - - joinQueueBtn.addEventListener("click", () => { - console.log('===== JOIN QUEUE BUTTON PRESSED ====='); - if (joinQueueBtn.innerText !== QueueState.Idle) { - console.log("== Entering in first if =="); - if (joinQueueBtn.innerText === QueueState.InQueue) { - console.log("== Entering in second if =="); - socket.emit("dequeue"); - joinQueueBtn.innerText = QueueState.Idle; - } - return; - } - joinQueueBtn.innerText = QueueState.InQueue; - socket.emit("enqueue"); - }); + const userXString = document.getElementById("playerX-name"); + const userOString = document.getElementById("playerO-name"); + const currentPlayerIndicator = document.getElementById("currentPlayer"); + const joinQueueBtn = document.getElementById("JoinQueueBtn"); + const currentPlayerTimer = document.getElementById("currentPlayerTimer") + if (!userXString || !userOString || !currentPlayerIndicator || !joinQueueBtn || !currentPlayerTimer) { + return showError('fatal error'); + } - let curGame: CurrentGameInfo | null = null; - let curGameX: {id: string, name: string} | null = null; - let curGameO: {id: string, name: string} | null = null; - - socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`)); - socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`)); - socket.on('newGame', async (gameState) => { - showInfo(`newGame: ${gameState.gameId}`) + joinQueueBtn.addEventListener("click", () => { + console.log('===== JOIN QUEUE BUTTON PRESSED ====='); + if (joinQueueBtn.innerText !== QueueState.Idle) { + console.log("== Entering in first if =="); + if (joinQueueBtn.innerText === QueueState.InQueue) { + console.log("== Entering in second if =="); + socket.emit("dequeue"); + joinQueueBtn.innerText = QueueState.Idle; + } + return; + } + joinQueueBtn.innerText = QueueState.InQueue; + socket.emit("enqueue"); + }); - currentPlayerTimer.innerText = ""; - joinQueueBtn.innerText = QueueState.InGame; - curGame = { ...gameState, lastState: null }; - - let resX = await client.getUser({user: curGame.playerX}); - - curGameX = {id: curGame.playerX, name: 'Player X'}; - if (resX.kind === 'success') - curGameX.name = resX.payload.name; - else - showError(`Unable to get player information: ${resX.msg}`); - let resO = await client.getUser({user: curGame.playerO}); - - curGameO = {id: curGame.playerO, name: 'Player O'}; - if (resO.kind === 'success') - curGameO.name = resO.payload.name; - else - showError(`Unable to get player information: ${resO.msg}`); + let curGame: CurrentGameInfo | null = null; + let curGameX: { id: string, name: string } | null = null; + let curGameO: { id: string, name: string } | null = null; - if (user.id === curGameO.id) { - userOString.classList.add('text-red-800'); - userOString.classList.remove('text-gray-800'); - userXString.classList.remove('text-red-800'); - userXString.classList.add('text-gray-800'); - } - else if (user.id === curGameX.id) { - userXString.classList.add('text-red-800'); - userXString.classList.remove('text-gray-800'); - userOString.classList.remove('text-red-800'); - userOString.classList.add('text-gray-800'); - } - userXString.innerText = curGameX.name; - userOString.innerText = curGameO.name; - }); + socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`)); + socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`)); + socket.on('newGame', async (gameState) => { + showInfo(`newGame: ${gameState.gameId}`) - const cells = app.querySelectorAll(".ttt-cell"); + currentPlayerTimer.innerText = ""; + joinQueueBtn.innerText = QueueState.InGame; + curGame = {...gameState, lastState: null}; - const updateUI = (boardState: (string | null)[]) => { - boardState.forEach((state, idx) => { - cells[idx].innerText = state || " "; - }); - }; + let resX = await client.getUser({user: curGame.playerX}); - const makeEnd = (type: 'win' | 'conceded' | 'draw', player: 'X' | 'O') => { - if (type === 'draw') { - showWarn('It\'s a draw !') - } + curGameX = {id: curGame.playerX, name: 'Player X'}; + if (resX.kind === 'success') + curGameX.name = resX.payload.name; + else + showError(`Unable to get player information: ${resX.msg}`); + let resO = await client.getUser({user: curGame.playerO}); - if (type === 'win') { - let youWin: boolean; - switch (player) { - case 'X': - youWin = (curGame?.playerX === user.id); - break; - case 'O': - youWin = (curGame?.playerO === user.id); - break; - default: - return; - } - if (youWin) - showSuccess('You won the game !'); - else - showError('You lost the game :('); - } - }; + curGameO = {id: curGame.playerO, name: 'Player O'}; + if (resO.kind === 'success') + curGameO.name = resO.payload.name; + else + showError(`Unable to get player information: ${resO.msg}`); - socket.on('gameEnd', () => { - curGame = null; - currentPlayerTimer.innerText = "Waiting for match..."; - joinQueueBtn.innerText = QueueState.Idle; - }) + if (user.id === curGameO.id) { + userOString.classList.add('text-red-800'); + userOString.classList.remove('text-gray-800'); + userXString.classList.remove('text-red-800'); + userXString.classList.add('text-gray-800'); + } else if (user.id === curGameX.id) { + userXString.classList.add('text-red-800'); + userXString.classList.remove('text-gray-800'); + userOString.classList.remove('text-red-800'); + userOString.classList.add('text-gray-800'); + } + userXString.innerText = curGameX.name; + userOString.innerText = curGameO.name; + }); - socket.on('gameBoard', (u) => { - if (curGame === null) { - return showError('Got game State, but no in a game ?'); - } + const cells = app.querySelectorAll(".ttt-cell"); - curGame = {...u, lastState: curGame.lastState}; + const updateUI = (boardState: (string | null)[]) => { + boardState.forEach((state, idx) => { + cells[idx].innerText = state || " "; + }); + }; - if (curGame.currentPlayer === 'X') - { - currentPlayerIndicator.innerText = "<"; - } else if (curGame.currentPlayer === 'O') { - currentPlayerIndicator.innerText = ">"; - } + const makeEnd = (type: 'win' | 'conceded' | 'draw', player: 'X' | 'O') => { + // TODO: Enhance the draw notification + if (type === 'draw') { + showWarn('It\'s a draw !') + } - updateUI(u.boardState); + if (type === 'win') { + let youWin: boolean; + switch (player) { + case 'X': + youWin = (curGame?.playerX === user.id); + break; + case 'O': + youWin = (curGame?.playerO === user.id); + break; + default: + return; + } + // TODO: Enhance the win/loss notification + if (youWin) + showSuccess('You won the game !'); + else + showError('You lost the game :('); + } + }; - if (u.gameState && u.gameState !== "ongoing") { - if (u.gameState !== curGame.lastState) { - curGame.lastState = u.gameState; - switch (u.gameState) { - case 'winX': - case 'winO': - makeEnd('win', u.gameState[3] as 'X' | 'O'); - break; - default: - makeEnd('draw', 'X'); - break; - } - } - } - }); + socket.on('gameEnd', () => { + curGame = null; + currentPlayerTimer.innerText = "Waiting for match..."; + joinQueueBtn.innerText = QueueState.Idle; + }) - cells?.forEach(function(c, idx) { - c.addEventListener("click", () => { - if (socket) { - socket.emit("gameMove", { index: idx }); - } - }); - }); - }, - }; + socket.on('gameBoard', (u) => { + if (curGame === null) { + return showError('Got game State, but no in a game ?'); + } + + curGame = {...u, lastState: curGame.lastState}; + + if (curGame.currentPlayer === 'X') { + currentPlayerIndicator.innerText = "<"; + } else if (curGame.currentPlayer === 'O') { + currentPlayerIndicator.innerText = ">"; + } + + updateUI(u.boardState); + + if (u.gameState && u.gameState !== "ongoing") { + if (u.gameState !== curGame.lastState) { + curGame.lastState = u.gameState; + switch (u.gameState) { + case 'winX': + case 'winO': + makeEnd('win', u.gameState[3] as 'X' | 'O'); + break; + default: + makeEnd('draw', 'X'); + break; + } + } + } + }); + + cells?.forEach(function (c, idx) { + c.addEventListener("click", () => { + if (socket) { + socket.emit("gameMove", {index: idx}); + } + }); + }); + }, + }; } addRoute("/ttt", handleTTT);