feat(tour): tournament is almost done

This commit is contained in:
Maieul BOYER 2026-01-11 16:58:39 +01:00 committed by G.C.L. Baptiste
parent 2e23943578
commit 6a55b28c2a
3 changed files with 127 additions and 28 deletions

View file

@ -249,7 +249,6 @@ async function handleLogin(
if (dOtherLoginArea) {
let styleSheetElement = document.createElement("style");
styleSheetElement.innerText = "";
// TODO: fetch all the providers from an API ?
const providersReq = await client.providerList();
const providers = providersReq.payload.list;
/*const providers: Providers[] = [

View file

@ -18,7 +18,7 @@
<div class="flex flex-col gap-2 items-end">
<span id="queue-info" class="fit-all rounded-elem gray-color text-white">?👤 ?⏳ ?▮•▮</span>
<!-- total | in queue | games-->
<span id="tour-info" class="fit-all rounded-elem gray-color text-white">⚪️ ?👤 ?▮•▮</span>
<button id="tour-info" class="blue-hover pong-btn-style fit-all rounded-elem gray-color text-white">⚪️ ?👤 ?▮•▮</button>
<!-- [Owner|Registered|NotRegisterd|nonExistant] | Player | games -->
<!-- 👑 ✅ ❌ ⚪️ -->
<button id="play-info" class="circle-8 pong-btn-style gray-color blue-hover">?</button>
@ -54,6 +54,24 @@
<kbd class="disp-key">L</kbd>
</span>
</div>
<div id="tourscore-box" class="white-color rounded-elem focus-elem text-2xl hidden">
up:
<kbd class="pong-protips-key">W</kbd>
down:
<kbd class="pong-protips-key">S</kbd>
<br />
You are <span class="text-red-500">red</span>.
<br />
Your goal is to bounce the ball back to the adversary.
<br />
<span class="text-gray-400">local games keys for the left paddle:
<br />
up:
<kbd class="disp-key">O</kbd>
down:
<kbd class="disp-key">L</kbd>
</span>
</div>
<button id="readyup-btn" class="justify-center white-color focus-elem rounded-elem">ready!</button>
<div class="pong-field">
<div id="batleft" class="pong-bat pong-batleft top-0"></div>

View file

@ -7,7 +7,7 @@ import {
} from "@app/routing";
import authHtml from "./pong.html?raw";
import io from "socket.io-client";
import type { CSocket, GameMove, GameUpdate } from "./socket";
import type { CSocket, GameMove, GameUpdate, TourInfo } from "./socket";
import { showError, showInfo, showSuccess } from "@app/toast";
import { getUser as getSelfUser, type User } from "@app/auth";
import { isNullish } from "@app/utils";
@ -53,7 +53,8 @@ enum TourInfoState {
document.addEventListener("ft:pageChange", (newUrl) => {
if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
if (window.__state.pongKeepAliveInterval !== undefined) clearInterval(window.__state.pongKeepAliveInterval);
if (window.__state.pongKeepAliveInterval !== undefined)
clearInterval(window.__state.pongKeepAliveInterval);
window.__state.pongSock = undefined;
window.__state.pongKeepAliveInterval = undefined;
});
@ -65,17 +66,24 @@ export function getSocket(): CSocket {
}) as any as CSocket;
}
if (window.__state.pongKeepAliveInterval === undefined) {
window.__state.pongKeepAliveInterval = setInterval(() => { window.__state.pongSock?.emit("hello") }, 100);
window.__state.pongKeepAliveInterval = setInterval(() => {
window.__state.pongSock?.emit("hello");
}, 100);
}
return window.__state.pongSock;
}
function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
setTitle('Pong Game Page');
function pongClient(
_url: string,
_args: RouteHandlerParams,
): RouteHandlerReturn {
setTitle("Pong Game Page");
const urlParams = new URLSearchParams(window.location.search);
const game_req_join = urlParams.get("game");
if (game_req_join) {
showError("currently not supporting the act of joining game (even as a spectator)");
showError(
"currently not supporting the act of joining game (even as a spectator)",
);
}
return {
@ -85,7 +93,12 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
const SELF_COLOR = "red";
const user = getSelfUser();
let currentGame: { game: GameUpdate, spectating: boolean, playerL: { id: string, name: string, self: boolean }, playerR: { id: string, name: string, self: boolean } } | null = null;
let currentGame: {
game: GameUpdate;
spectating: boolean;
playerL: { id: string; name: string; self: boolean };
playerR: { id: string; name: string; self: boolean };
} | null = null;
const rdy_btn =
document.querySelector<HTMLButtonElement>("#readyup-btn");
const batLeft = document.querySelector<HTMLDivElement>("#batleft");
@ -115,7 +128,9 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
const tournamentBtn =
document.querySelector<HTMLButtonElement>("#TourBtn");
const tour_infos =
document.querySelector<HTMLSpanElement>("#tour-info");
document.querySelector<HTMLButtonElement>("#tour-info");
const tour_scores =
document.querySelector<HTMLDivElement>("#tourscore-box");
let socket = getSocket();
@ -138,19 +153,20 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
!rdy_btn ||
!end_scr ||
!tournamentBtn ||
!tour_infos
!tour_infos ||
!tour_scores ||
!how_to_play_btn ||
!protips
)
// sanity check
return showError("fatal error");
if (!how_to_play_btn || !protips) showError("missing protips"); // not a fatal error
tournamentBtn.addEventListener("click", () => {
showInfo(`Button State: ${tournamentBtn.innerText}`);
switch (tournamentBtn.innerText) {
case TourBtnState.AbleToStart:
socket.emit('tourStart')
socket.emit("tourStart");
break;
case TourBtnState.AbleToJoin:
socket.emit("tourRegister");
@ -187,10 +203,15 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
keys[e.key.toLowerCase()] = false;
});
tour_infos.addEventListener("click", () => {
tour_scores.classList.toggle("hidden");
});
setInterval(() => {
// key sender
if (keys["escape"] === true && protips && how_to_play_btn) {
if (keys["escape"] === true) {
protips.classList.add("hidden");
tour_scores.classList.add("hidden");
how_to_play_btn.innerText = "?";
}
if (queueBtn.innerText !== QueueState.InGame)
@ -254,6 +275,42 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
currentGame = null;
}
const renderTournamentScores = (info: TourInfo) => {
let players = info.players.sort((l, r) => r.score - l.score);
tour_scores.innerHTML = `
<div class="overflow-x-auto">
<table class="min-w-full border border-gray-200 rounded-lg shadow-sm">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 border-b">
Name
</th>
<th class="px-4 py-2 text-right text-sm font-semibold text-gray-700 border-b">
Score
</th>
</tr>
</thead>
<tbody>
${players
.map(
(player) => `
<tr class="hover:bg-gray-50" key="${player.id}">
<td class="px-4 py-2 text-sm text-gray-800 border-b">
${player.name}
</td>
<td class="px-4 py-2 text-sm text-gray-800 text-right border-b">
${player.score}
</td>
</tr>
`,
)
.join("")}
</tbody>
</table>
</div>`;
};
const render = (state: GameUpdate) => {
batLeft.style.top = `${state.left.paddle.y}px`;
batLeft.style.left = `${state.left.paddle.x}px`;
@ -278,7 +335,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
socket.on("tourEnding", (ending) => {
showInfo(ending);
})
});
// ---
// position logic (client) end
// ---
@ -287,7 +344,9 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
// queue evt
// ---
// utils
async function getUser(user: string): Promise<{ id: string, name: string | null }> {
async function getUser(
user: string,
): Promise<{ id: string; name: string | null }> {
let t = await client.getUser({ user });
if (t.kind === "success")
@ -342,28 +401,51 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
});
const updateCurrentGame = async (state: GameUpdate) => {
const normalizeUser = (u: { id: string, name: string | null }, d: string) => {
return { id: u.id, name: u.name ?? d, self: u.id === user.id };
const normalizeUser = (
u: { id: string; name: string | null },
d: string,
) => {
return {
id: u.id,
name: u.name ?? d,
self: u.id === user.id,
};
};
if (currentGame === null)
currentGame = {
spectating: !(state.left.id === user.id || state.right.id === user.id),
spectating: !(
state.left.id === user.id ||
state.right.id === user.id
),
game: state,
playerL: normalizeUser(await getUser(state.left.id), "left"),
playerR: normalizeUser(await getUser(state.right.id), "right"),
}
playerL: normalizeUser(
await getUser(state.left.id),
"left",
),
playerR: normalizeUser(
await getUser(state.right.id),
"right",
),
};
else currentGame.game = state;
if (currentGame && currentGame?.game.local || currentGame?.playerL.self) {
if (
(currentGame && currentGame?.game.local) ||
currentGame?.playerL.self
) {
batLeft!.style.backgroundColor = SELF_COLOR;
playerL!.style.color = SELF_COLOR;
}
if (currentGame && (!currentGame?.game.local && currentGame?.playerR.self)) {
if (
currentGame &&
!currentGame?.game.local &&
currentGame?.playerR.self
) {
batRight!.style.backgroundColor = SELF_COLOR;
playerR!.style.color = SELF_COLOR;
}
playerL!.innerText = currentGame!.playerL.name;
playerR!.innerText = currentGame!.playerR.name;
}
};
socket.on("newGame", async (state) => {
currentGame = null;
@ -429,7 +511,6 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
let weIn = s.players.some((p) => p.id === user.id);
let imOwner = s.ownerId === user.id;
// TODO: fix this so the number of remaining games are correct
switch (s.state) {
case "ended":
tournamentBtn.innerText = TourBtnState.AbleToCreate;
@ -439,7 +520,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
tour_infos.innerText = `${TourInfoState.Running} ${s.players.length}👤 ${s.remainingMatches ?? "?"}▮•▮`;
break;
case "prestart":
tour_infos.innerText = `${imOwner ? TourInfoState.Owner : (weIn ? TourInfoState.Registered : TourInfoState.NotRegisted)} ${s.players.length}👤 ?▮•▮`;
tour_infos.innerText = `${imOwner ? TourInfoState.Owner : weIn ? TourInfoState.Registered : TourInfoState.NotRegisted} ${s.players.length}👤 ?▮•▮`;
if (imOwner) {
tournamentBtn.innerText = TourBtnState.AbleToStart;
} else {
@ -449,6 +530,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
}
break;
}
renderTournamentScores(s);
});
socket.on("tournamentRegister", ({ kind, msg }) => {