feat(pong): detect when the client hasnt sent anything in X secs

This commit is contained in:
Maieul BOYER 2026-01-05 17:43:05 +01:00 committed by Nigel
parent 613bb31100
commit 8eac0a0d4e
5 changed files with 61 additions and 13 deletions

View file

@ -194,15 +194,15 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
showError("couldn't find your id in game");
}); // TODO: notif user of new game w "ready up" btn
socket.on("gameEnd", () => {
socket.on("gameEnd", (winner) => {
queueBtn.innerHTML = QueueState.Iddle;
queueBtn.style.color = 'white';
if (!isNullish(currentGame)) {
let new_div = document.createElement('div');
let end_txt = "";
if ((user.id === currentGame.left.id && currentGame.left.score > currentGame.right.score) ||
(user.id === currentGame.right.id && currentGame.right.score > currentGame.left.score))
if ((user.id === currentGame.left.id && winner === 'left') ||
(user.id === currentGame.right.id && winner === 'right'))
end_txt = 'won! #yippe';
else
end_txt = 'lost #sadge';

View file

@ -48,7 +48,7 @@ export interface ServerToClient {
updateInformation: (info: UpdateInfo) => void,
newGame: (initState: GameUpdate) => void, // <- consider this the gameProc eg not start of game but wait for client to "ready up"
gameUpdate: (state: GameUpdate) => void,
gameEnd: () => void;
gameEnd: (winner: 'left' | 'right') => void;
};
export type SSocket = Socket<ClientToServer, ServerToClient>;

View file

@ -98,6 +98,8 @@ function makeAngle(i: number): [number, number, number, number] {
export class Pong {
public gameUpdate: NodeJS.Timeout | null = null;
public static readonly CONCEDED_TIMEOUT: number = 1500;
public static readonly BALL_START_ANGLES: number[] = [
...makeAngle(4),
...makeAngle(6),
@ -127,6 +129,11 @@ export class Pong {
public score: [number, number] = [0, 0];
public local: boolean = false;
public rightLastSeen: number = -1;
public leftLastSeen: number = -1;
private cachedWinner: 'left' | 'right' | null = null;
public static makeLocal(owner: UserId): Pong {
const game = new Pong(owner, owner);
game.local = true;
@ -200,7 +207,9 @@ export class Pong {
if (
(Math.abs(this.ball.angle) > Math.PI / 2 && side !== 'left') ||
(Math.abs(this.ball.angle) < Math.PI / 2 && side !== 'right')
) {return false;}
) {
return false;
}
// now we check only if the ball is near enought in the y axis to permform the collision
if (
@ -212,7 +221,9 @@ export class Pong {
this.ball.y < paddle.y + paddle.height + this.ball.size
)
)
) {return false;}
) {
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 (
@ -222,14 +233,49 @@ export class Pong {
paddle.width * (side === 'left' ? 1 : 0) -
this.ball.x,
) < this.ball.size
) {return true;}
) {
return true;
}
return false;
}
public updateLastSeen(user: UserId) {
if (this.local && this.userLeft === user) {
this.leftLastSeen = Date.now();
this.rightLastSeen = Date.now();
}
else if (this.userLeft === user) {
this.leftLastSeen = Date.now();
}
else if (this.userRight === user) {
this.rightLastSeen = Date.now();
}
}
public checkWinner(): 'left' | 'right' | null {
const checkInner = () => {
if (this.score[0] >= 5) return 'left';
if (this.score[1] >= 5) return 'right';
if (
this.leftLastSeen !== -1 &&
Date.now() - this.leftLastSeen > Pong.CONCEDED_TIMEOUT
) {
return 'right';
}
if (
this.rightLastSeen !== -1 &&
Date.now() - this.rightLastSeen > Pong.CONCEDED_TIMEOUT
) {
return 'left';
}
return null;
};
if (this.cachedWinner === null) {
this.cachedWinner = checkInner();
}
return this.cachedWinner;
}
public movePaddle(user: UserId | ('left' | 'right'), dir: 'up' | 'down') {

View file

@ -47,7 +47,7 @@ export interface ServerToClient {
updateInformation: (info: UpdateInfo) => void,
newGame: (initState: GameUpdate) => void,
gameUpdate: (state: GameUpdate) => void,
gameEnd: () => void;
gameEnd: (winner: 'left' | 'right') => void;
};
export type SSocket = Socket<ClientToServer, ServerToClient>;

View file

@ -128,6 +128,7 @@ class StateI {
if (u.moveRight !== null) { game.movePaddle('right', u.moveRight); }
}
else if (u.move !== null) { game.movePaddle(user.id, u.move); }
game.updateLastSeen(user.id);
}
@ -174,14 +175,15 @@ class StateI {
private cleanupGame(gameId: GameId, game: Pong): void {
clearInterval(game.gameUpdate ?? undefined);
this.games.delete(gameId);
const winner = game.checkWinner() ?? 'left';
let player: PUser | undefined = undefined;
if ((player = this.users.get(game.userLeft)) !== undefined) {
player.currentGame = null;
player.socket.emit('gameEnd');
player.socket.emit('gameEnd', winner);
}
if ((player = this.users.get(game.userRight)) !== undefined) {
player.currentGame = null;
player.socket.emit('gameEnd');
player.socket.emit('gameEnd', winner);
}
const rOutcome = game.checkWinner();
let outcome: PongGameOutcome = 'other';