feat(tour): tournament is almost done
This commit is contained in:
parent
2e23943578
commit
6a55b28c2a
3 changed files with 127 additions and 28 deletions
|
|
@ -249,7 +249,6 @@ async function handleLogin(
|
||||||
if (dOtherLoginArea) {
|
if (dOtherLoginArea) {
|
||||||
let styleSheetElement = document.createElement("style");
|
let styleSheetElement = document.createElement("style");
|
||||||
styleSheetElement.innerText = "";
|
styleSheetElement.innerText = "";
|
||||||
// TODO: fetch all the providers from an API ?
|
|
||||||
const providersReq = await client.providerList();
|
const providersReq = await client.providerList();
|
||||||
const providers = providersReq.payload.list;
|
const providers = providersReq.payload.list;
|
||||||
/*const providers: Providers[] = [
|
/*const providers: Providers[] = [
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<div class="flex flex-col gap-2 items-end">
|
<div class="flex flex-col gap-2 items-end">
|
||||||
<span id="queue-info" class="fit-all rounded-elem gray-color text-white">?👤 ?⏳ ?▮•▮</span>
|
<span id="queue-info" class="fit-all rounded-elem gray-color text-white">?👤 ?⏳ ?▮•▮</span>
|
||||||
<!-- total | in queue | games-->
|
<!-- 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 -->
|
<!-- [Owner|Registered|NotRegisterd|nonExistant] | Player | games -->
|
||||||
<!-- 👑 ✅ ❌ ⚪️ -->
|
<!-- 👑 ✅ ❌ ⚪️ -->
|
||||||
<button id="play-info" class="circle-8 pong-btn-style gray-color blue-hover">?</button>
|
<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>
|
<kbd class="disp-key">L</kbd>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
<button id="readyup-btn" class="justify-center white-color focus-elem rounded-elem">ready!</button>
|
||||||
<div class="pong-field">
|
<div class="pong-field">
|
||||||
<div id="batleft" class="pong-bat pong-batleft top-0"></div>
|
<div id="batleft" class="pong-bat pong-batleft top-0"></div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from "@app/routing";
|
} from "@app/routing";
|
||||||
import authHtml from "./pong.html?raw";
|
import authHtml from "./pong.html?raw";
|
||||||
import io from "socket.io-client";
|
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 { showError, showInfo, showSuccess } from "@app/toast";
|
||||||
import { getUser as getSelfUser, type User } from "@app/auth";
|
import { getUser as getSelfUser, type User } from "@app/auth";
|
||||||
import { isNullish } from "@app/utils";
|
import { isNullish } from "@app/utils";
|
||||||
|
|
@ -53,7 +53,8 @@ enum TourInfoState {
|
||||||
|
|
||||||
document.addEventListener("ft:pageChange", (newUrl) => {
|
document.addEventListener("ft:pageChange", (newUrl) => {
|
||||||
if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
|
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.pongSock = undefined;
|
||||||
window.__state.pongKeepAliveInterval = undefined;
|
window.__state.pongKeepAliveInterval = undefined;
|
||||||
});
|
});
|
||||||
|
|
@ -65,17 +66,24 @@ export function getSocket(): CSocket {
|
||||||
}) as any as CSocket;
|
}) as any as CSocket;
|
||||||
}
|
}
|
||||||
if (window.__state.pongKeepAliveInterval === undefined) {
|
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;
|
return window.__state.pongSock;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
|
function pongClient(
|
||||||
setTitle('Pong Game Page');
|
_url: string,
|
||||||
|
_args: RouteHandlerParams,
|
||||||
|
): RouteHandlerReturn {
|
||||||
|
setTitle("Pong Game Page");
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const game_req_join = urlParams.get("game");
|
const game_req_join = urlParams.get("game");
|
||||||
if (game_req_join) {
|
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 {
|
return {
|
||||||
|
|
@ -85,7 +93,12 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
const SELF_COLOR = "red";
|
const SELF_COLOR = "red";
|
||||||
|
|
||||||
const user = getSelfUser();
|
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 =
|
const rdy_btn =
|
||||||
document.querySelector<HTMLButtonElement>("#readyup-btn");
|
document.querySelector<HTMLButtonElement>("#readyup-btn");
|
||||||
const batLeft = document.querySelector<HTMLDivElement>("#batleft");
|
const batLeft = document.querySelector<HTMLDivElement>("#batleft");
|
||||||
|
|
@ -115,7 +128,9 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
const tournamentBtn =
|
const tournamentBtn =
|
||||||
document.querySelector<HTMLButtonElement>("#TourBtn");
|
document.querySelector<HTMLButtonElement>("#TourBtn");
|
||||||
const tour_infos =
|
const tour_infos =
|
||||||
document.querySelector<HTMLSpanElement>("#tour-info");
|
document.querySelector<HTMLButtonElement>("#tour-info");
|
||||||
|
const tour_scores =
|
||||||
|
document.querySelector<HTMLDivElement>("#tourscore-box");
|
||||||
|
|
||||||
let socket = getSocket();
|
let socket = getSocket();
|
||||||
|
|
||||||
|
|
@ -138,19 +153,20 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
!rdy_btn ||
|
!rdy_btn ||
|
||||||
!end_scr ||
|
!end_scr ||
|
||||||
!tournamentBtn ||
|
!tournamentBtn ||
|
||||||
!tour_infos
|
!tour_infos ||
|
||||||
|
!tour_scores ||
|
||||||
|
!how_to_play_btn ||
|
||||||
|
!protips
|
||||||
)
|
)
|
||||||
// sanity check
|
// sanity check
|
||||||
return showError("fatal error");
|
return showError("fatal error");
|
||||||
if (!how_to_play_btn || !protips) showError("missing protips"); // not a fatal error
|
|
||||||
|
|
||||||
|
|
||||||
tournamentBtn.addEventListener("click", () => {
|
tournamentBtn.addEventListener("click", () => {
|
||||||
showInfo(`Button State: ${tournamentBtn.innerText}`);
|
showInfo(`Button State: ${tournamentBtn.innerText}`);
|
||||||
|
|
||||||
switch (tournamentBtn.innerText) {
|
switch (tournamentBtn.innerText) {
|
||||||
case TourBtnState.AbleToStart:
|
case TourBtnState.AbleToStart:
|
||||||
socket.emit('tourStart')
|
socket.emit("tourStart");
|
||||||
break;
|
break;
|
||||||
case TourBtnState.AbleToJoin:
|
case TourBtnState.AbleToJoin:
|
||||||
socket.emit("tourRegister");
|
socket.emit("tourRegister");
|
||||||
|
|
@ -187,10 +203,15 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
keys[e.key.toLowerCase()] = false;
|
keys[e.key.toLowerCase()] = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tour_infos.addEventListener("click", () => {
|
||||||
|
tour_scores.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
// key sender
|
// key sender
|
||||||
if (keys["escape"] === true && protips && how_to_play_btn) {
|
if (keys["escape"] === true) {
|
||||||
protips.classList.add("hidden");
|
protips.classList.add("hidden");
|
||||||
|
tour_scores.classList.add("hidden");
|
||||||
how_to_play_btn.innerText = "?";
|
how_to_play_btn.innerText = "?";
|
||||||
}
|
}
|
||||||
if (queueBtn.innerText !== QueueState.InGame)
|
if (queueBtn.innerText !== QueueState.InGame)
|
||||||
|
|
@ -254,6 +275,42 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
currentGame = null;
|
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) => {
|
const render = (state: GameUpdate) => {
|
||||||
batLeft.style.top = `${state.left.paddle.y}px`;
|
batLeft.style.top = `${state.left.paddle.y}px`;
|
||||||
batLeft.style.left = `${state.left.paddle.x}px`;
|
batLeft.style.left = `${state.left.paddle.x}px`;
|
||||||
|
|
@ -278,7 +335,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
|
|
||||||
socket.on("tourEnding", (ending) => {
|
socket.on("tourEnding", (ending) => {
|
||||||
showInfo(ending);
|
showInfo(ending);
|
||||||
})
|
});
|
||||||
// ---
|
// ---
|
||||||
// position logic (client) end
|
// position logic (client) end
|
||||||
// ---
|
// ---
|
||||||
|
|
@ -287,7 +344,9 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
// queue evt
|
// queue evt
|
||||||
// ---
|
// ---
|
||||||
// utils
|
// 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 });
|
let t = await client.getUser({ user });
|
||||||
|
|
||||||
if (t.kind === "success")
|
if (t.kind === "success")
|
||||||
|
|
@ -342,28 +401,51 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateCurrentGame = async (state: GameUpdate) => {
|
const updateCurrentGame = async (state: GameUpdate) => {
|
||||||
const normalizeUser = (u: { id: string, name: string | null }, d: string) => {
|
const normalizeUser = (
|
||||||
return { id: u.id, name: u.name ?? d, self: u.id === user.id };
|
u: { id: string; name: string | null },
|
||||||
|
d: string,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
id: u.id,
|
||||||
|
name: u.name ?? d,
|
||||||
|
self: u.id === user.id,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
if (currentGame === null)
|
if (currentGame === null)
|
||||||
currentGame = {
|
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,
|
game: state,
|
||||||
playerL: normalizeUser(await getUser(state.left.id), "left"),
|
playerL: normalizeUser(
|
||||||
playerR: normalizeUser(await getUser(state.right.id), "right"),
|
await getUser(state.left.id),
|
||||||
}
|
"left",
|
||||||
|
),
|
||||||
|
playerR: normalizeUser(
|
||||||
|
await getUser(state.right.id),
|
||||||
|
"right",
|
||||||
|
),
|
||||||
|
};
|
||||||
else currentGame.game = state;
|
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;
|
batLeft!.style.backgroundColor = SELF_COLOR;
|
||||||
playerL!.style.color = 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;
|
batRight!.style.backgroundColor = SELF_COLOR;
|
||||||
playerR!.style.color = SELF_COLOR;
|
playerR!.style.color = SELF_COLOR;
|
||||||
}
|
}
|
||||||
playerL!.innerText = currentGame!.playerL.name;
|
playerL!.innerText = currentGame!.playerL.name;
|
||||||
playerR!.innerText = currentGame!.playerR.name;
|
playerR!.innerText = currentGame!.playerR.name;
|
||||||
}
|
};
|
||||||
|
|
||||||
socket.on("newGame", async (state) => {
|
socket.on("newGame", async (state) => {
|
||||||
currentGame = null;
|
currentGame = null;
|
||||||
|
|
@ -429,7 +511,6 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
|
|
||||||
let weIn = s.players.some((p) => p.id === user.id);
|
let weIn = s.players.some((p) => p.id === user.id);
|
||||||
let imOwner = s.ownerId === user.id;
|
let imOwner = s.ownerId === user.id;
|
||||||
// TODO: fix this so the number of remaining games are correct
|
|
||||||
switch (s.state) {
|
switch (s.state) {
|
||||||
case "ended":
|
case "ended":
|
||||||
tournamentBtn.innerText = TourBtnState.AbleToCreate;
|
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 ?? "?"}▮•▮`;
|
tour_infos.innerText = `${TourInfoState.Running} ${s.players.length}👤 ${s.remainingMatches ?? "?"}▮•▮`;
|
||||||
break;
|
break;
|
||||||
case "prestart":
|
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) {
|
if (imOwner) {
|
||||||
tournamentBtn.innerText = TourBtnState.AbleToStart;
|
tournamentBtn.innerText = TourBtnState.AbleToStart;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -449,6 +530,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
renderTournamentScores(s);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("tournamentRegister", ({ kind, msg }) => {
|
socket.on("tournamentRegister", ({ kind, msg }) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue