feat(tour): added tournament broadcast

This commit is contained in:
Maieul BOYER 2026-01-14 15:55:39 +01:00 committed by Nigel
parent eccc763644
commit 8e6ff3aedc
10 changed files with 157 additions and 41 deletions

View file

@ -39,7 +39,7 @@
<div id="game-modal" class="gamePopup hidden">
<div class="popUpMessage">
<div id="game-info">
<p class="modal-messages" id="modal-message"></p>
<p class="" id="modal-message"></p>
<button id="close-modal-message" class="btn-style relative bottom-8 left-85">Close</button>
</div>
</div>

View file

@ -297,6 +297,11 @@ function initChatSocket() {
openMessagePopup(message);
});
//receives broadcast of the next GAME
socket.on("tourStatus", (message: string) => {
openMessagePopup(message);
});
socket.on("listBud", async (myBuddies: string[]) => {
const buddies = document.getElementById(
"div-buddies",

View file

@ -5,16 +5,14 @@ import { incrementCounter } from "./incrementCounter";
// return count;
// }
export async function openMessagePopup(message: any) {
export async function openMessagePopup(message: string) {
const modalmessage = document.getElementById("modal-message") ?? null;
if(!message) return
const obj = message;
if (modalmessage) {
const messageElement = document.createElement("div");
messageElement.innerHTML = `
<div id="profile-about">Game-Info: ${incrementCounter()}: ${obj.nextGame}</div>
`;
messageElement.innerHTML = `<div id="profile-about">${message}</div>`;
modalmessage.appendChild(messageElement);
modalmessage.scrollTop = modalmessage.scrollHeight;
}

View file

@ -82,6 +82,7 @@ declare module 'fastify' {
blockBtn: (data: blockedUnBlocked) => void;
isBlockdBtn: (data: blockedUnBlocked) => void;
check_Block_button: (data: blockedUnBlocked) => void;
tourStatus: (data: string) => void;
}>;
}
}

View file

@ -6,16 +6,13 @@ import { clientChat } from './app';
* @param fastify
* @param gameLink
*/
export async function broadcastNextGame(fastify: FastifyInstance, gameLink?: Promise<string>) {
const link = gameLink ? await gameLink : undefined;
export async function broadcastNextGame(fastify: FastifyInstance, gameLink: string) {
const sockets = await fastify.io.fetchSockets();
for (const socket of sockets) {
const clientInfo = clientChat.get(socket.id);
if (!clientInfo?.user) {
continue;
}
if (link) {
socket.emit('nextGame', link);
}
socket.emit('nextGame', gameLink);
}
};

View file

@ -0,0 +1,21 @@
import { FastifyInstance } from 'fastify';
import { clientChat } from './app';
/**
* function broadcast a clickable link
* @param fastify
* @param gameLink
*/
export async function broadcastTourStatus(fastify: FastifyInstance, gameLink?: string) {
const link = gameLink;
const sockets = await fastify.io.fetchSockets();
for (const socket of sockets) {
const clientInfo = clientChat.get(socket.id);
if (!clientInfo?.user) {
continue;
}
if (link) {
socket.emit('tourStatus', link);
}
}
};

View file

@ -3,7 +3,7 @@ import { Static, Type } from 'typebox';
import { broadcastNextGame } from '../broadcastNextGame';
export const ChatReq = Type.Object({
message: Type.String(),
nextGame: Type.String(),
});
export type ChatReq = Static<typeof ChatReq>;
@ -27,21 +27,14 @@ export type ChatReq = Static<typeof ChatReq>;
// export default route;
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post('/api/chat/broadcast', {
fastify.post<{ Body: ChatReq }>('/broadcastNextGame', {
schema: {
body: {
type: 'object',
required: ['nextGame'],
properties: {
nextGame: { type: 'string' },
body: ChatReq,
hide: true,
},
},
},
}, async (req, reply) => {
const gameLink: Promise<string> = Promise.resolve(req.body as string);
if (gameLink) {
broadcastNextGame(fastify, gameLink);
}
}, async function(req, reply) {
this.log.info({ msg: 'Broadcasting nextGame status', ...req.body });
broadcastNextGame(fastify, req.body.nextGame);
return reply.send({ status: 'ok' });
});
};

View file

@ -0,0 +1,44 @@
import { FastifyPluginAsync } from 'fastify';
import { Static, Type } from 'typebox';
import { broadcastTourStatus } from '../broadcastTourStatus';
export const ChatReq = Type.Object({
message: Type.String(),
});
export type ChatReq = Static<typeof ChatReq>;
// const route: FastifyPluginAsync = async (fastify): Promise<void> => {
// fastify.post<{ Body: ChatReq }>(
// '/api/chat/broadcast',
// {
// schema: {
// body: ChatReq,
// hide: true,
// },
// config: { requireAuth: false },
// },
// async function(req, res) {
// // broadcast(this, { command: '', destination: '', user: 'CMwaLeSever!!', text: req.body.message, SenderWindowID: 'server' });
// void res;
// },
// );
// };
// export default route;
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<{ Body: ChatReq }>('/broadcastTourStatus', {
schema: {
body: ChatReq,
hide: true,
},
}, async function(req, reply) {
const gameLink: string = req.body.message;
if (gameLink) {
broadcastTourStatus(fastify, gameLink);
}
return reply.send({ status: 'ok' });
});
};
export default route;

View file

@ -90,8 +90,12 @@ class StateI {
g.rdy_timer = Date.now();
this.games.set(gameId, g);
if (u1) { u1.currentGame = gameId; }
if (u2) { u2.currentGame = gameId; }
if (u1) {
u1.currentGame = gameId;
}
if (u2) {
u2.currentGame = gameId;
}
g.gameUpdate = setInterval(() => {
g.tick();
@ -193,6 +197,26 @@ class StateI {
return;
}
public async broadcastTourStatus(message: string) {
try {
const resp = await fetch('http://app-chat/broadcastTourStatus', {
method: 'POST',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify({ message }),
});
if (!resp.ok) {
throw resp;
}
else {
this.fastify.log.info('tour-status info to chat success');
}
}
catch (e: unknown) {
this.fastify.log.error(`tour-status info to chat failed: ${e}`);
}
}
private createTournament(sock: SSocket) {
const user = this.users.get(sock.authUser.id);
if (isNullish(user)) return;
@ -215,7 +239,14 @@ class StateI {
private cleanupTournament() {
if (this.tournament === null) return;
if (this.tournament.state === 'ended') { this.fastify.db.createNewTournamentById(this.tournament.owner, this.tournament.users.values().toArray(), this.tournament.games); }
if (this.tournament.state === 'ended') {
this.fastify.db.createNewTournamentById(
this.tournament.owner,
this.tournament.users.values().toArray(),
this.tournament.games,
);
this.broadcastTourStatus('The tournament has ended');
}
this.tournament = null;
this.fastify.log.info('Tournament has been ended');
}
@ -231,8 +262,15 @@ class StateI {
}
public newPausedGame(suid1: string, suid2: string): GameId | undefined {
this.fastify.log.info('new game request: suid1: ' + suid1 + '\tsuid2: ' + suid2);
if (!this.fastify.db.getUser(suid1) || !this.fastify.db.getUser(suid2)) { return undefined; }
this.fastify.log.info(
'new game request: suid1: ' + suid1 + '\tsuid2: ' + suid2,
);
if (
!this.fastify.db.getUser(suid1) ||
!this.fastify.db.getUser(suid2)
) {
return undefined;
}
this.fastify.log.info('new paused start');
const uid1: UserId = suid1 as UserId;
const uid2: UserId = suid2 as UserId;
@ -313,7 +351,9 @@ class StateI {
const user = this.users.get(sock.authUser.id);
if (!user) return;
if (this.tournament && this.tournament.users.has(sock.authUser.id)) return;
if (this.tournament && this.tournament.users.has(sock.authUser.id)) {
return;
}
const gameId = newUUID() as unknown as GameId;
const g = Pong.makeLocal(user.id);
@ -388,19 +428,28 @@ class StateI {
if (this.games.has(game_id) === false) {
this.fastify.log.warn('gameId:' + g_id + ' is unknown!');
return (JoinRes.no);
return JoinRes.no;
}
const game: Pong = this.games.get(game_id)!;
if (game.local === true || (game.userLeft !== sock.authUser.id && game.userRight !== sock.authUser.id)) {
this.fastify.log.warn('user trying to connect to a game he\'s not part of: gameId:' + g_id + ' userId:' + sock.authUser.id);
return (JoinRes.no);
if (
game.local === true ||
(game.userLeft !== sock.authUser.id &&
game.userRight !== sock.authUser.id)
) {
this.fastify.log.warn(
'user trying to connect to a game he\'s not part of: gameId:' +
g_id +
' userId:' +
sock.authUser.id,
);
return JoinRes.no;
}
game.userOnPage[game.userLeft === sock.authUser.id ? 0 : 1] = true;
if (game.userOnPage[0] === game.userOnPage[1]) {
this.fastify.log.info('Paused game start: gameId:' + g_id);
this.initGame(game, game_id, game.userLeft, game.userRight);
}
return (JoinRes.yes);
return JoinRes.yes;
}
public registerUser(socket: SSocket): void {
@ -433,7 +482,9 @@ class StateI {
socket.on('gameMove', (e) => this.gameMove(socket, e));
socket.on('localGame', () => this.newLocalGame(socket));
socket.on('joinGame', (g_id, ack) => { return (ack(this.tryJoinGame(g_id, socket))); });
socket.on('joinGame', (g_id, ack) => {
return ack(this.tryJoinGame(g_id, socket));
});
// todo: allow passing nickname
socket.on('tourRegister', () =>
this.registerForTournament(socket, null),
@ -522,7 +573,7 @@ class StateI {
if (!game.local) {
const payload = { nextGame: chat_text };
try {
const resp = await fetch('http://app-chat/api/chat/broadcast', {
const resp = await fetch('http://app-chat/broadcastNextGame', {
method: 'POST',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(payload),
@ -548,7 +599,9 @@ class StateI {
if (this.users.get(socket.authUser.id)?.currentGame !== null) return;
if (this.tournament && this.tournament.users.has(socket.authUser.id)) return;
if (this.tournament && this.tournament.users.has(socket.authUser.id)) {
return;
}
this.queue.add(socket.authUser.id);
socket.emit('queueEvent', 'registered');

View file

@ -19,7 +19,9 @@ export class Tournament {
public startTimeout: NodeJS.Timeout | undefined;
public games: PongGameId[] = [];
constructor(public owner: UserId) { }
constructor(public owner: UserId) {
State.broadcastTourStatus('A new Tournament has been created');
}
public addUser(id: UserId, name: string) {
if (this.state !== 'prestart') return;
@ -35,6 +37,7 @@ export class Tournament {
if (this.state !== 'prestart') return;
if (this.users.size < 2) {
this.state = 'canceled';
State.broadcastTourStatus('The tournament has been canceled !');
return;
}
this.state = 'playing';
@ -54,6 +57,7 @@ export class Tournament {
comb.forEach(([u1, u2]) => { result.push([u1, u2]); });
this.matchup = result;
this.setupNextGame();
State.broadcastTourStatus('The tournament Started !');
}
private setupNextGame() {