diff --git a/frontend/src/pages/pong/pong.css b/frontend/src/pages/pong/pong.css index 9c46a73..7329475 100644 --- a/frontend/src/pages/pong/pong.css +++ b/frontend/src/pages/pong/pong.css @@ -141,4 +141,16 @@ text-black text-center bg-white + z-50 +} + +.pong-rdy-screen { + @apply + rounded-2xl + absolute + justify-center + text-black + text-center + bg-white + z-50 } \ No newline at end of file diff --git a/frontend/src/pages/pong/pong.html b/frontend/src/pages/pong/pong.html index 163e144..0b63dcf 100644 --- a/frontend/src/pages/pong/pong.html +++ b/frontend/src/pages/pong/pong.html @@ -8,6 +8,8 @@ Pong Box
+ +

@@ -18,6 +20,7 @@
+
diff --git a/frontend/src/pages/pong/pong.ts b/frontend/src/pages/pong/pong.ts index bbb47de..b58136b 100644 --- a/frontend/src/pages/pong/pong.ts +++ b/frontend/src/pages/pong/pong.ts @@ -24,6 +24,11 @@ enum QueueState { In_local = "In Local", }; +enum ReadyState { + readyUp = "ready up?", + readyDown = "ready down", +}; + document.addEventListener("ft:pageChange", (newUrl) => { if (newUrl.detail.startsWith('/app/pong') || newUrl.detail.startsWith('/pong')) return; if (window.__state.pongSock !== undefined) window.__state.pongSock.close(); @@ -49,6 +54,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const user = getUser(); let currentGame: GameUpdate | null = null; let opponent: User | null = null; + const rdy_btn = document.querySelector('#readyup-btn'); const batLeft = document.querySelector("#batleft"); const batRight = document.querySelector("#batright"); const ball = document.querySelector("#ball"); @@ -66,7 +72,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn navigateTo("/app"); return ; } - if (!batLeft || !batRight || !ball || !score || !queueBtn || !playerL || !playerR || !gameBoard || !queue_infos || !LocalGameBtn) // sanity check + if (!batLeft || !batRight || !ball || !score || !queueBtn || !playerL || !playerR || !gameBoard || !queue_infos || !LocalGameBtn || !rdy_btn) // sanity check return showError('fatal error'); // --- @@ -112,6 +118,18 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn local:false }; + function resetBoard(batLeft : HTMLDivElement, batRight : HTMLDivElement, playerL : HTMLDivElement, playerR : HTMLDivElement) { + render(DEFAULT_POSITIONS); + batLeft.style.backgroundColor = DEFAULT_COLOR; + batRight.style.backgroundColor = DEFAULT_COLOR; + playerR.style.color = ""; + playerL.style.color = ""; + playerR.innerText = ""; + playerL.innerText = ""; + currentGame = null; + opponent = null; + } + const render = (state: GameUpdate) => { currentGame = state; batLeft.style.top = `${state.left.paddle.y}px`; @@ -130,7 +148,10 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn score.innerText = `${state.left.score} | ${state.right.score}` } - socket.on('gameUpdate', (state: GameUpdate) => render(state)); + socket.on('gameUpdate', (state: GameUpdate) => { + // if (rdy_btn) + // rdy_btn.classList.add('hidden'); + render(state);}); // --- // position logic (client) end // --- @@ -138,35 +159,13 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // --- // queue evt // --- + // utils function set_pretty(batU : HTMLDivElement, txtU : HTMLDivElement, txtO : HTMLDivElement, colorYou : string) { batU.style.backgroundColor = colorYou; txtU.style.color = colorYou; txtU.innerText = isNullish(user) ? "you" : user.name; txtO.innerText = isNullish(opponent) ? "the mechant" : opponent.name; } - queueBtn.addEventListener("click", ()=>{ - if (queueBtn.innerText !== QueueState.Iddle) { - if (queueBtn.innerText === QueueState.InQueu) { - socket.emit("dequeue"); - queueBtn.innerText = QueueState.Iddle; - } - return ; - } - queueBtn.innerText = QueueState.InQueu; - socket.emit('enqueue'); - }); - - LocalGameBtn.addEventListener("click", () => { - if (queueBtn.innerText !== QueueState.Iddle || currentGame !== null) { - showError("cant launch a local game while in queue/in game"); - return ; - } - socket.emit("localGame"); - queueBtn.innerText = QueueState.In_local; - LocalGameBtn.innerText = "playing"; - }); - - async function get_opponent(opponent_id : string) { let t = await client.getUser({user:opponent_id}); @@ -178,10 +177,50 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn opponent = null; } } - + + // btn setup + queueBtn.addEventListener("click", ()=>{ + if (queueBtn.innerText !== QueueState.Iddle) { + if (queueBtn.innerText === QueueState.InQueu) { + socket.emit("dequeue"); + queueBtn.innerText = QueueState.Iddle; + } + return ; + } + queueBtn.innerText = QueueState.InQueu; + socket.emit('enqueue'); + }); + LocalGameBtn.addEventListener("click", () => { + if (queueBtn.innerText !== QueueState.Iddle || currentGame !== null) { + showError("cant launch a local game while in queue/in game"); + return ; + } + socket.emit("localGame"); + queueBtn.innerText = QueueState.In_local; + LocalGameBtn.innerText = "playing"; + }); + rdy_btn.addEventListener("click", ()=>{ + showInfo("rdy-evt"); + switch (rdy_btn.innerText) { + case ReadyState.readyUp: + showInfo("sent:rdyup"); + socket.emit('readyUp'); + rdy_btn.innerText = ReadyState.readyDown; + break ; + case ReadyState.readyDown: + showInfo("sent:rdydwn"); + socket.emit('readyDown'); + rdy_btn.innerText = ReadyState.readyUp; + break ; + default: + showError("error on ready btn"); + } + + }); socket.on('newGame', async (state) => { render(state); + await get_opponent(state.left.id == user.id ? state.right.id : state.left.id); queueBtn.innerText = QueueState.InGame; queueBtn.style.color = 'red'; @@ -193,7 +232,13 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn set_pretty(batRight, playerR, playerL, SELF_COLOR); } else showError("couldn't find your id in game"); - }); // TODO: notif user of new game w "ready up" btn + + rdy_btn.classList.remove('hidden'); + rdy_btn.innerText = ReadyState.readyUp; + + setTimeout(() => { + rdy_btn.classList.add('hidden');}, 2000); // 1500 : pong.CONCEDED_TIMEOUT + }); socket.on("gameEnd", (winner) => { queueBtn.innerHTML = QueueState.Iddle; @@ -218,33 +263,24 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn LocalGameBtn.innerText = "Local Game" } } - render(DEFAULT_POSITIONS); - batLeft.style.backgroundColor = DEFAULT_COLOR; - batRight.style.backgroundColor = DEFAULT_COLOR; - playerR.style.color = ""; - playerL.style.color = ""; - playerR.innerText = ""; - playerL.innerText = ""; - currentGame = null; - opponent = null; + resetBoard(batLeft, batRight, playerL, playerR); }) - // --- - // queue evt end - // --- - - queueBtn.innerText = QueueState.Iddle; - render(DEFAULT_POSITIONS); - currentGame = null; - batLeft.style.backgroundColor = DEFAULT_COLOR; - batRight.style.backgroundColor = DEFAULT_COLOR; - + // pretty info for queue :3 socket.on('updateInformation', (e) => { queue_infos.innerText = `${e.totalUser}👤 ${e.inQueue}⏳ ${e.totalGames}▮•▮`; }); socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`)); + // --- + // queue evt end + // --- + + // init + rdy_btn.classList.add('hidden'); + queueBtn.innerText = QueueState.Iddle; + rdy_btn.innerText = ReadyState.readyUp; + resetBoard(batLeft, batRight, playerL, playerR); showInfo("butter"); showInfo("butter-toast"); - // socket.emit('localGame'); } } }; diff --git a/src/pong/src/game.ts b/src/pong/src/game.ts index 8fd2a60..ad78741 100644 --- a/src/pong/src/game.ts +++ b/src/pong/src/game.ts @@ -1,5 +1,7 @@ import { UserId } from '@shared/database/mixin/user'; + + export class Paddle { public static readonly DEFAULT_SPEED = 10; public static readonly DEFAULT_HEIGHT = 80; @@ -94,8 +96,16 @@ function makeAngle(i: number): [number, number, number, number] { -Math.PI / i + Math.PI, ]; } +const LEFT :number = 0; +const RIGHT :number = 1; +enum ReadyState { + noState, + readyUp, + readyDown, +} export class Pong { + public gameUpdate: NodeJS.Timeout | null = null; public static readonly CONCEDED_TIMEOUT: number = 1500; @@ -126,6 +136,8 @@ export class Pong { Pong.BALL_START_ANGLES[this.ballAngleIdx++], ); + public ready_checks: [ReadyState, ReadyState] = [ReadyState.noState, ReadyState.noState]; + public score: [number, number] = [0, 0]; public local: boolean = false; @@ -145,7 +157,44 @@ export class Pong { public userRight: UserId, ) { } + public readyup(uid : UserId) + { + // debug + console.log(this.userLeft + " | " + this.userRight); + if (uid === this.userLeft) { + console.log("rdy.up : lft " + uid); + } else if (uid === this.userRight) { + console.log("rdy.up : rgt " + uid); + } + + if (uid === this.userLeft) { + this.ready_checks[LEFT] = ReadyState.readyUp; + } else if (uid === this.userRight) { + this.ready_checks[RIGHT] = ReadyState.readyUp; + } + } + public readydown(uid : UserId) + { + // debug + console.log(this.userLeft + " | " + this.userRight); + if (uid === this.userLeft) { + console.log("rdy.down : lft " + uid); + } else if (uid === this.userRight) { + console.log("rdy.down : rgt " + uid); + } + + // is everyone already ready? + if (this.ready_checks[LEFT] === ReadyState.readyUp && this.ready_checks[RIGHT] === ReadyState.readyUp) return ; + + if (uid === this.userLeft) + this.ready_checks[LEFT] = ReadyState.readyDown; + else if (uid === this.userRight) + this.ready_checks[RIGHT] = ReadyState.readyDown; + } + public tick() { + if (this.ready_checks[LEFT] !== ReadyState.readyUp || this.ready_checks[RIGHT] !== ReadyState.readyUp) + return; if (this.paddleCollision(this.leftPaddle, 'left')) { this.ball.collided( 'left', @@ -254,8 +303,8 @@ export class Pong { public checkWinner(): 'left' | 'right' | null { const checkInner = () => { - if (this.score[0] >= 5) return 'left'; - if (this.score[1] >= 5) return 'right'; + if (this.score[LEFT] >= 5) return 'left'; + if (this.score[RIGHT] >= 5) return 'right'; if ( this.leftLastSeen !== -1 && diff --git a/src/pong/src/state.ts b/src/pong/src/state.ts index cf7d4e5..2d44096 100644 --- a/src/pong/src/state.ts +++ b/src/pong/src/state.ts @@ -69,11 +69,10 @@ class StateI { u1.currentGame = gameId; u2.currentGame = gameId; - // --- - // wait for ready up - // --- - g.gameUpdate = setInterval(() => { + // --- + // wait for ready up + // --- g.tick(); this.gameUpdate(gameId, u1.socket); this.gameUpdate(gameId, u2.socket); @@ -152,6 +151,9 @@ class StateI { socket.on('enqueue', () => this.enqueueUser(socket)); socket.on('dequeue', () => this.dequeueUser(socket)); + socket.on('readyUp', () => this.readyupUser(socket)); + socket.on('readyDown', () => this.readydownUser(socket)); + socket.on('gameMove', (e) => this.gameMove(socket, e)); socket.on('localGame', () => this.newLocalGame(socket)); } @@ -215,6 +217,27 @@ class StateI { socket.emit('queueEvent', 'unregistered'); } + private readydownUser(socket: SSocket) : void { // + // do we know this user ? + if (!this.users.has(socket.authUser.id)) return; + const user = this.users.get(socket.authUser.id)!; + // does the user have a game and do we know such game ? + if (user.currentGame === null || !this.games.has(user.currentGame)) return; + const game = this.games.get(user.currentGame)!; + // is this a local game? + if (game.local === true) return; + game.readydown(user.id); + } + private readyupUser(socket: SSocket) : void { // + // do we know this user ? + if (!this.users.has(socket.authUser.id)) return; + const user = this.users.get(socket.authUser.id)!; + // does the user have a game and do we know such game ? + if (user.currentGame === null || !this.games.has(user.currentGame)) return; + const game = this.games.get(user.currentGame)!; + if (game.local === true) return; + game.readyup(user.id); + } }