From d882c670ed9ce145463ddaf2be2c04402927fad8 Mon Sep 17 00:00:00 2001 From: Mihkel Martin Kasterpalu Date: Wed, 12 Feb 2025 00:01:51 +0200 Subject: [PATCH] Optimize pakubiiti player state Add cleanup, if user identified with session token hasn't interacted with the game in 24h Store the the previously separate AlbumState inside PlayerState as well. This means it's better connected to the actual player and will also be cleaned up --- src/lib/server/pakubiiti/AlbumState.svelte.ts | 45 ------ .../server/pakubiiti/PlayerState.svelte.ts | 143 ++++++++++++------ src/lib/types.ts | 1 + .../pakubiiti/getAlbums/[count]/+server.ts | 5 - src/routes/vinge/pakubiiti/+page.server.ts | 7 +- 5 files changed, 100 insertions(+), 101 deletions(-) delete mode 100644 src/lib/server/pakubiiti/AlbumState.svelte.ts diff --git a/src/lib/server/pakubiiti/AlbumState.svelte.ts b/src/lib/server/pakubiiti/AlbumState.svelte.ts deleted file mode 100644 index 5fb7c85..0000000 --- a/src/lib/server/pakubiiti/AlbumState.svelte.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { AlbumSolveState } from '$lib/types'; - -class AlbumState { - private albums: AlbumSolveState[] | undefined = undefined; - - setAlbums(data: AlbumSolveState[]) { - if (!data) { - return; - } - - this.albums = data; - } - - checkSolve(data: AlbumSolveState[]) { - if (!data || !this.albums) { - return false; - } - - for (const solve of data) { - const search = this.albums.filter((album) => album.name === solve.name); - - if (!search) { - return false; - } - - const matching = search.at(0); - - if (!matching) { - return false; - } - - if (matching.image !== solve.image) { - return false; - } - - if (matching.artists !== solve.artists) { - return false; - } - } - - return true; - } -} - -export const albumState = new AlbumState(); diff --git a/src/lib/server/pakubiiti/PlayerState.svelte.ts b/src/lib/server/pakubiiti/PlayerState.svelte.ts index 2d3448d..3b6d8f4 100644 --- a/src/lib/server/pakubiiti/PlayerState.svelte.ts +++ b/src/lib/server/pakubiiti/PlayerState.svelte.ts @@ -1,90 +1,139 @@ -import type { Player } from '$lib/types'; +import type { AlbumSolveState, Player } from '$lib/types'; export class PlayerState { players = $state([]); + private lastAccessed: Map = new Map(); - newPlayer(id: string) { - this.players.push({ id: id, stage: 0, highscore: 0, playing: true }); + constructor() { + // Clean up expired sessions every hour + setInterval(() => this.cleanup(), 1000 * 60 * 60); } - getStage(id: string) { - const player = this.players.find((player) => player.id === id); + private updateLastAccessed(id: string) { + this.lastAccessed.set(id, Date.now()); + } - if (!player) { - return undefined; + private findPlayer(id: string): Player | undefined { + this.updateLastAccessed(id); + return this.players.find((player) => player.id === id); + } + + private checkSolution(solution: AlbumSolveState[], submission: AlbumSolveState[]) { + if (!solution || !submission) { + return false; } - return player.stage; + for (const solve of solution) { + const search = submission.filter((album) => album.name === solve.name); + + if (!search) { + return false; + } + + const matching = search.at(0); + + if (!matching) { + return false; + } + + if (matching.image !== solve.image) { + return false; + } + + if (matching.artists !== solve.artists) { + return false; + } + } + + return true; + } + + newPlayer(id: string) { + this.updateLastAccessed(id); + this.players.push({ id: id, stage: 0, highscore: 0, playing: true, albums: [] }); } nextStage(id: string) { - const player = this.players.find((player) => player.id === id); - - if (!player) { - return; + const player = this.findPlayer(id); + if (player) { + player.stage += 1; } - - player.stage += 1; } restart(id: string) { - const player = this.players.find((player) => player.id === id); - - if (!player) { - return; + const player = this.findPlayer(id); + if (player) { + player.stage = 0; + player.playing = true; } - - player.stage = 0; - player.playing = true; } - score(id: string, won: boolean) { - const player = this.players.find((player) => player.id === id); + score(id: string, submission: AlbumSolveState[]) { + if (!submission) return false; - if (!player) { - return; - } + const player = this.findPlayer(id); + if (!player) return false; - if (won) { + if (this.checkSolution(player.albums, submission)) { player.stage += 1; - return; + return true; } player.playing = false; - if (player.stage > player.highscore) { player.highscore = player.stage; } + + return true; } getHighscore(id: string) { - const player = this.players.find((player) => player.id === id); + const player = this.findPlayer(id); + return player?.highscore; + } - if (!player) { - return undefined; - } - - return player.highscore; + getStage(id: string) { + const player = this.findPlayer(id); + return player?.stage; } getPlaying(id: string) { - const player = this.players.find((player) => player.id === id); - - if (!player) { - return undefined; - } - - return player.playing; + const player = this.findPlayer(id); + return player?.playing; } setPlaying(id: string, playing: boolean) { - const player = this.players.find((player) => player.id === id); - - if (!player) { - return; + const player = this.findPlayer(id); + if (player) { + player.playing = playing; } + } - player.playing = playing; + getAlbums(id: string) { + const player = this.findPlayer(id); + return player?.albums; + } + + setAlbums(id: string, albums: AlbumSolveState[]) { + const player = this.findPlayer(id); + if (player) { + player.albums = albums; + } + } + + // Clean up players that haven't been accessed in 24 hours + private cleanup(): void { + const now = Date.now(); + const expiryTime = 24 * 60 * 60 * 1000; // 24 hours + + this.players = this.players.filter((player) => { + const lastAccess = this.lastAccessed.get(player.id); + if (!lastAccess || now - lastAccess > expiryTime) { + this.lastAccessed.delete(player.id); + return false; + } + return true; + }); } } diff --git a/src/lib/types.ts b/src/lib/types.ts index 29e7df1..a3b8db0 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -41,6 +41,7 @@ export type Player = { stage: number; highscore: number; playing: boolean; + albums: AlbumSolveState[]; }; export interface TimeRemaining { diff --git a/src/routes/api/pakubiiti/getAlbums/[count]/+server.ts b/src/routes/api/pakubiiti/getAlbums/[count]/+server.ts index 3e37e80..6d23b9e 100644 --- a/src/routes/api/pakubiiti/getAlbums/[count]/+server.ts +++ b/src/routes/api/pakubiiti/getAlbums/[count]/+server.ts @@ -1,7 +1,6 @@ import type { AlbumSolveState } from '$lib/types'; import { error, json } from '@sveltejs/kit'; -import { albumState } from '$lib/server/pakubiiti/AlbumState.svelte'; import { spotifyAPI } from '$lib/server/pakubiiti/Spotify.svelte'; const maxTries = 10; @@ -30,12 +29,8 @@ export async function GET({ params }) { } if (albums.length !== count) { - albumState.setAlbums([]); - return error(500, "Couldn't get albums from Spotify."); } - albumState.setAlbums(albums); - return json({ albums: albums }); } diff --git a/src/routes/vinge/pakubiiti/+page.server.ts b/src/routes/vinge/pakubiiti/+page.server.ts index ba8f98f..bcb38ea 100644 --- a/src/routes/vinge/pakubiiti/+page.server.ts +++ b/src/routes/vinge/pakubiiti/+page.server.ts @@ -4,7 +4,6 @@ import type { AlbumSolveState } from '$lib/types'; import { nanoid } from 'nanoid'; import { shuffleArray } from '$lib/utils'; -import { albumState } from '$lib/server/pakubiiti/AlbumState.svelte'; import { playerState } from '$lib/server/pakubiiti/PlayerState.svelte'; import { ratelimit } from '$lib/server/redis'; @@ -92,6 +91,8 @@ export const load: PageServerLoad = async (event) => { value: album.artists })); + playerState.setAlbums(user, data.albums); + return { names: shuffleArray(albumNames), images: shuffleArray(albumImages), @@ -133,9 +134,7 @@ export const actions = { state.push({ name: name, image: image, artists: artists }); } - const solved = albumState.checkSolve(state); - - playerState.score(user, solved); + const solved = playerState.score(user, state); return { solved: solved }; },