make requeuing and other stuff better

This commit is contained in:
Maieul BOYER 2026-01-01 18:04:02 +01:00 committed by Maix0
parent 972b78a1e2
commit b86d50f9f0
4 changed files with 101 additions and 32 deletions

View file

@ -20,21 +20,22 @@ export type GameUpdate = {
export type GameMove = {
index: number;
}
export type GameMoveResponse = 'success' | 'invalidMove' | 'unknownError';
export interface ClientToServer {
enqueue: () => void;
dequeue: () => void;
debugInfo: () => void;
gameMove: (up: GameMove) => GameMoveResponse;
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: (gameId: string) => void,
newGame: (initState: GameUpdate) => void,
gameBoard: (state: GameUpdate) => void,
gameEnd: () => void;
};
export type SSocket = Socket<ClientToServer, ServerToClient>;

View file

@ -1,8 +1,9 @@
import { addRoute, type RouteHandlerReturn } from "@app/routing";
import tttPage from "./ttt.html?raw";
import { showError, showInfo, showSuccess } from "@app/toast";
import { showError, showInfo, showSuccess, showWarn } from "@app/toast";
import { io } from "socket.io-client";
import type { CSocket as Socket } from "./socket";
import type { GameUpdate, CSocket as Socket } from "./socket";
import { updateUser } from "@app/auth";
declare module 'ft_state' {
@ -22,6 +23,8 @@ export function getSocket(): Socket {
return window.__state.tttSock;
}
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<RouteHandlerReturn> {
@ -33,10 +36,17 @@ async function handleTTT(): Promise<RouteHandlerReturn> {
if (!app) {
return;
}
let user = await updateUser();
if (user === null)
return;
let curGame: CurrentGameInfo | null = null;
socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`));
socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`));
socket.on('newGame', (e) => showInfo(`newGame: ${e}`));
socket.on('newGame', (gameState) => {
showInfo(`newGame: ${gameState.gameId}`)
curGame = { ...gameState, lastState: null };
});
socket.emit('enqueue');
const cells = app.querySelectorAll<HTMLDivElement>(".ttt-grid-cell");
@ -48,27 +58,59 @@ async function handleTTT(): Promise<RouteHandlerReturn> {
});
};
const makeEnd = (type: 'win' | 'conceded' | 'draw', player: 'X' | 'O') => {
if (type === 'draw') {
showWarn('It\'s a draw !')
}
// if the other player conceded, switch the side so you won
if (type === 'conceded') {
player = player === 'X' ? 'O' : 'X';
const youWin = (curGame?.playerX === user.id);
if (youWin)
showSuccess('The other player Conceded !');
else
showError('You Conceded :(');
}
if (type === 'win') {
const youWin = (curGame?.playerX === user.id);
if (youWin)
showSuccess('You won the game !');
else
showError('You lost the game :(');
}
};
socket.on('gameEnd', () => {
curGame = null;
socket.emit('enqueue');
showInfo('Game is finished, enqueuing directly')
})
socket.on('gameBoard', (u) => {
if (curGame === null) {
return showError('Got game State, but no in a game ?');
}
updateUI(u.boardState);
if (u.gameState && u.gameState !== "ongoing") {
grid?.classList.add("pointer-events-none");
if (u.gameState === "winX") {
showSuccess("X won !");
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;
case 'concededX':
case 'concededO':
makeEnd('conceded', u.gameState[8] as 'X' | 'O');
break;
case 'draw':
makeEnd('draw', 'X');
break;
}
if (u.gameState === "winO") {
showSuccess("O won !");
}
if (u.gameState === "draw") {
showInfo("Draw !");
}
if (u.gameState === 'concededX' )
{
showInfo("concededX");
}
if (u.gameState === 'concededO' )
{
showInfo("concededO");
}
}
});

View file

@ -20,21 +20,22 @@ export type GameUpdate = {
export type GameMove = {
index: number;
}
export type GameMoveResponse = 'success' | 'invalidMove' | 'unknownError';
export interface ClientToServer {
enqueue: () => void;
dequeue: () => void;
debugInfo: () => void;
gameMove: (up: GameMove) => GameMoveResponse;
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: (gameId: string) => void,
newGame: (initState: GameUpdate) => void,
gameBoard: (state: GameUpdate) => void,
gameEnd: () => void;
};
export type SSocket = Socket<ClientToServer, ServerToClient>;

View file

@ -1,6 +1,6 @@
import { UserId } from '@shared/database/mixin/user';
import { FastifyInstance } from 'fastify';
import { GameMove, GameMoveResponse, SSocket } from './socket';
import { GameMove, SSocket } from './socket';
import { isNullish } from '@shared/utils';
import { newUUID } from '@shared/utils/uuid';
import { GameId } from '@shared/database/mixin/tictactoe';
@ -48,17 +48,27 @@ export class StateI {
this.queue.delete(id2);
const gameId = newUUID() as unknown as GameId;
u1.socket.emit('newGame', gameId);
u2.socket.emit('newGame', gameId);
const g = new TTC(u1.userId, u2.userId);
const iState = {
boardState: g.board,
currentPlayer: g.getCurrentState(),
playerX: g.playerX,
playerO: g.playerO,
gameState: g.checkState(),
gameId: gameId,
};
this.games.set(gameId, new TTC(u1.userId, u2.userId));
u1.socket.emit('newGame', iState);
u2.socket.emit('newGame', iState);
this.games.set(gameId, g);
u1.currentGame = gameId;
u2.currentGame = gameId;
this.games.get(gameId)!.gameUpdate = setInterval(() => {
g.gameUpdate = setInterval(() => {
this.gameUpdate(gameId, u1.socket);
this.gameUpdate(gameId, u2.socket);
if (g.checkState() !== 'ongoing') { this.cleanupGame(gameId, g); }
}, 100);
}
}
@ -102,6 +112,21 @@ export class StateI {
this.queue.delete(socket.authUser.id);
}
private cleanupGame(gameId: GameId, game: TTC): void {
clearInterval(game.gameUpdate ?? undefined);
this.games.delete(gameId);
let player: TTTUser | undefined = undefined;
if ((player = this.users.get(game.playerO)) !== undefined) {
player.currentGame = null;
player.socket.emit('gameEnd');
}
if ((player = this.users.get(game.playerX)) !== 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;
@ -149,14 +174,14 @@ export class StateI {
});
}
private gameMove(socket: SSocket, update: GameMove): GameMoveResponse {
private gameMove(socket: SSocket, update: GameMove) {
if (!this.users.has(socket.authUser.id)) return 'unknownError';
const user = this.users.get(socket.authUser.id)!;
if (user.currentGame !== null && !this.games.has(user.currentGame)) return 'unknownError';
const game = this.games.get(user.currentGame!)!;
return game.makeMove(socket.authUser.id, update.index);
game.makeMove(socket.authUser.id, update.index);
}
}