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
This commit is contained in:
Mihkel Martin Kasterpalu 2025-02-12 00:01:51 +02:00
parent 94b659b1f0
commit d882c670ed
5 changed files with 100 additions and 101 deletions

View file

@ -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();

View file

@ -1,90 +1,139 @@
import type { Player } from '$lib/types';
import type { AlbumSolveState, Player } from '$lib/types';
export class PlayerState {
players = $state<Player[]>([]);
private lastAccessed: Map<string, number> = 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;
});
}
}

View file

@ -41,6 +41,7 @@ export type Player = {
stage: number;
highscore: number;
playing: boolean;
albums: AlbumSolveState[];
};
export interface TimeRemaining {

View file

@ -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 });
}

View file

@ -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 };
},