From dc5b4b75d58ff827638bcee2a66b8b2f0a9c60a9 Mon Sep 17 00:00:00 2001 From: Ostaptsov Danil <64201643+OstaptsovDanil@users.noreply.github.com> Date: Wed, 27 Nov 2024 06:25:43 +0300 Subject: [PATCH 1/2] feat(rating): add k/d column to rating table (#82) ## How does this PR impact the user? before: ![image](https://github.com/user-attachments/assets/c27d7e62-d9fe-4175-8a11-bb83fa2bab82) after: ![image](https://github.com/user-attachments/assets/5e1d357f-17f7-4e78-ac26-4dd44df7ae4d) ## Description Added K/D column to rating table ## Limitations ## Checklist - [x] my PR is focused and contains one wholistic change - [x] I have added screenshots or screen recordings to show the changes --------- Co-authored-by: Yurij Mikhalevich --- components/RatingTable.vue | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/components/RatingTable.vue b/components/RatingTable.vue index f99d3e0..c59ece3 100644 --- a/components/RatingTable.vue +++ b/components/RatingTable.vue @@ -81,17 +81,23 @@ const isModalOpen = ref(false); scope="col" class="px-6 py-3 bg-gray-50 dark:bg-gray-800" > - food eaten + k/d - max endgame size + food eaten + max endgame size + + avg endgame size @@ -137,12 +143,15 @@ const isModalOpen = ref(false); {{ userRating.deaths }} - {{ userRating.foodEaten }} + {{ userRating.deaths ? (userRating.kills / userRating.deaths).toFixed(2) : "n/a" }} - {{ userRating.maxEndgameSize }} + {{ userRating.foodEaten }} + {{ userRating.maxEndgameSize }} + + {{ userRating.avgEndgameSize.toFixed(2) }} From 01455cd3d0a24f21d079a0cb83ec68a2754643ae Mon Sep 17 00:00:00 2001 From: aldrin312 Date: Wed, 27 Nov 2024 00:26:27 -0500 Subject: [PATCH 2/2] feat: zoom in on the game screen to show the players bot more closely and panning on game screen with mouse drag (#81) ## How does this PR impact the user? ![Recording 2024-11-22 at 12 06 16](https://github.com/user-attachments/assets/c4017c94-6487-4656-9f79-3cfeaf12ab76) ## Description - Issue #48 - Allows users to zoom in in the game screen using mouse wheel on the mouse location. - Allows user to pan through the game screen with mouse dragging. - Max zoom in is 3 times. ## Limitations - Currently panning outside the predefined spawn locations of the food doesn't look good. - Same issue with zooming out ## Checklist - [x] my PR is focused and contains one wholistic change - [x] I have added screenshots or screen recordings to show the changes --------- Co-authored-by: aldrin312 --- components/GameScreen.vue | 131 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/components/GameScreen.vue b/components/GameScreen.vue index ce25955..5234b1f 100644 --- a/components/GameScreen.vue +++ b/components/GameScreen.vue @@ -2,6 +2,9 @@ import { Application, Graphics, Text, FillGradient } from "pixi.js"; const refreshIntervalMs = 1000; +const zoomSpeed = 0.05; +const minZoom = 1; +const maxZoom = 3; const { data: gameState, refresh } = await useFetch("/api/state"); const intervalRef = ref(null); @@ -20,6 +23,7 @@ const canvas = ref(null); const appRef = ref(null); const foodRef = ref<{ x: number; y: number; graphics: Graphics }[]>([]); const botSpawnsRef = ref>({}); +const gameScreen = ref(null); const tickFnRef = ref<() => void>(); @@ -106,6 +110,132 @@ watch(gameState, async (newState, prevState) => { autoDensity: true, }); + let isZoomingOut = false; + const zoomDuration = 500; // Duration of the zoom-out effect in milliseconds + let startZoomTime: number | null = null; + let startMousePos = { x: 0, y: 0 }; + + // Functions to handle smooth zoom out + function smoothZoomOut(mousePos: { x: number; y: number }) { + if (!appRef.value) { + return; + } + + isZoomingOut = true; + startZoomTime = performance.now(); + startMousePos = mousePos; + requestAnimationFrame(animateZoomOut); + } + + function animateZoomOut(currentTime: number) { + if (!appRef.value || !startZoomTime) { + return; + }; + + const elapsedTime = currentTime - startZoomTime; + const progress = Math.min(elapsedTime / zoomDuration, 1); + const newScale = minZoom + (appRef.value.stage.scale.x - minZoom) * (1 - progress); + + const worldPos = { + x: (startMousePos.x - appRef.value.stage.position.x) / appRef.value.stage.scale.x, + y: (startMousePos.y - appRef.value.stage.position.y) / appRef.value.stage.scale.y, + }; + + appRef.value.stage.scale.set(newScale); + + const newScreenPos = { + x: worldPos.x * newScale + appRef.value.stage.position.x, + y: worldPos.y * newScale + appRef.value.stage.position.y, + }; + + appRef.value.stage.position.set( + Math.min(0, Math.max(appRef.value.stage.position.x - (newScreenPos.x - startMousePos.x), appRef.value.screen.width - appRef.value.screen.width * newScale)), + Math.min(0, Math.max(appRef.value.stage.position.y - (newScreenPos.y - startMousePos.y), appRef.value.screen.height - appRef.value.screen.height * newScale)), + ); + if (progress < 1) { + requestAnimationFrame(animateZoomOut); + } else { + isZoomingOut = false; + } + } + + // Mouse wheel event listener + // Event listeners can be potentially called multiple times. + // TODO: add .removeEventListener later for refactoring. + canvas.value?.addEventListener("wheel", (event) => { + event.preventDefault(); + const mousePos = { x: event.offsetX, y: event.offsetY }; + if (event.deltaY > 0) { + // Zoom out when scrolling down + if (!isZoomingOut) { + smoothZoomOut(mousePos); + } + } else { + // Existing zoom-in functionality + const zoomFactor = event.deltaY * -zoomSpeed; + const newScale = Math.max(minZoom, Math.min(maxZoom, app.stage.scale.x + zoomFactor)); + + const worldPos = { + x: (mousePos.x - app.stage.position.x) / app.stage.scale.x, + y: (mousePos.y - app.stage.position.y) / app.stage.scale.y, + }; + + app.stage.scale.set(newScale); + + const newScreenPos = { + x: worldPos.x * newScale + app.stage.position.x, + y: worldPos.y * newScale + app.stage.position.y, + }; + + app.stage.position.set( + Math.min(0, Math.max(app.stage.position.x - (newScreenPos.x - mousePos.x), app.screen.width - app.screen.width * newScale)), + Math.min(0, Math.max(app.stage.position.y - (newScreenPos.y - mousePos.y), app.screen.height - app.screen.height * newScale)), + ); + if (newScale > 1) { + gameScreen.value?.classList.add("cursor-grab"); + } else { + gameScreen.value?.classList.remove("cursor-grab"); + } + } + }); + + // Panning functionality + let isDragging = false; + let startDragPos = { x: 0, y: 0 }; + + canvas.value?.addEventListener("mousedown", (event) => { + if (app.stage.scale.x > 1) { + gameScreen.value?.classList.add("cursor-grabbing"); + } + isDragging = true; + startDragPos = { x: event.offsetX, y: event.offsetY }; + }); + + canvas.value?.addEventListener("mousemove", (event) => { + if (isDragging && canvas.value) { + const dx = event.offsetX - startDragPos.x; + const dy = event.offsetY - startDragPos.y; + const newPosX = app.stage.position.x + dx; + const newPosY = app.stage.position.y + dy; + + // Constrain the new position to within map boundaries + app.stage.position.x = Math.min(0, Math.max(newPosX, app.screen.width - app.screen.width * app.stage.scale.x)); + app.stage.position.y = Math.min(0, Math.max(newPosY, app.screen.height - app.screen.height * app.stage.scale.y)); + + startDragPos = { x: event.offsetX, y: event.offsetY }; + } + }); + + canvas.value?.addEventListener("mouseup", () => { + gameScreen.value?.classList.remove("cursor-grabbing"); + isDragging = false; + }); + + canvas.value?.addEventListener("mouseleave", () => { + gameScreen.value?.classList.remove("cursor-grabbing"); + isDragging = false; + }); + // render food for (const food of prevState.food) { const graphics = new Graphics(); @@ -212,6 +342,7 @@ watch(gameState, async (newState, prevState) => { data-testid="game-screen" >