Refactor BSC Score to Astro, TypeScript, and modular architecture

This commit is contained in:
Cursor Agent
2025-06-24 11:44:19 +00:00
parent bcf793b9e3
commit 6f626c9977
30 changed files with 1836 additions and 497 deletions

155
src/services/gameService.ts Normal file
View File

@@ -0,0 +1,155 @@
import type { Game, GameType, StandardGame, EndlosGame, NewGameData } from '../types/game';
const LOCAL_STORAGE_KEY = 'bscscore_games';
export class GameService {
/**
* Load games from localStorage
*/
static loadGames(): Game[] {
try {
const savedGames = localStorage.getItem(LOCAL_STORAGE_KEY);
return savedGames ? JSON.parse(savedGames) : [];
} catch (error) {
console.error('Error loading games from localStorage:', error);
return [];
}
}
/**
* Save games to localStorage
*/
static saveGames(games: Game[]): void {
try {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(games));
} catch (error) {
console.error('Error saving games to localStorage:', error);
}
}
/**
* Create a new game
*/
static createGame(gameData: NewGameData): Game {
const baseGame = {
id: Date.now(),
gameType: gameData.gameType as GameType,
raceTo: parseInt(gameData.raceTo, 10),
status: 'active' as const,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
log: [],
undoStack: [],
};
if (gameData.gameType === '14/1 endlos') {
const players = [
{ name: gameData.player1, score: 0, consecutiveFouls: 0 },
{ name: gameData.player2, score: 0, consecutiveFouls: 0 }
];
if (gameData.player3) {
players.push({ name: gameData.player3, score: 0, consecutiveFouls: 0 });
}
return {
...baseGame,
players,
currentPlayer: null,
ballsOnTable: 15,
} as EndlosGame;
} else {
const standardGame: StandardGame = {
...baseGame,
player1: gameData.player1,
player2: gameData.player2,
score1: 0,
score2: 0,
};
if (gameData.player3) {
standardGame.player3 = gameData.player3;
standardGame.score3 = 0;
}
return standardGame;
}
}
/**
* Update a game's score (for standard games)
*/
static updateGameScore(game: StandardGame, player: number, change: number): StandardGame {
const updated = { ...game };
if (player === 1) updated.score1 = Math.max(0, updated.score1 + change);
if (player === 2) updated.score2 = Math.max(0, updated.score2 + change);
if (player === 3 && updated.score3 !== undefined) {
updated.score3 = Math.max(0, updated.score3 + change);
}
updated.updatedAt = new Date().toISOString();
return updated;
}
/**
* Check if a game is completed based on raceTo
*/
static isGameCompleted(game: Game): boolean {
if (game.status === 'completed') return true;
if ('players' in game) {
// EndlosGame
return game.players.some(player => player.score >= game.raceTo);
} else {
// StandardGame
const scores = [game.score1, game.score2, game.score3].filter(score => score !== undefined);
return scores.some(score => score >= game.raceTo);
}
}
/**
* Get the winner of a completed game
*/
static getGameWinner(game: Game): string | null {
if (!this.isGameCompleted(game)) return null;
if ('players' in game) {
// EndlosGame
const winner = game.players.find(player => player.score >= game.raceTo);
return winner?.name || null;
} else {
// StandardGame
if (game.score1 >= game.raceTo) return game.player1;
if (game.score2 >= game.raceTo) return game.player2;
if (game.player3 && game.score3 && game.score3 >= game.raceTo) return game.player3;
}
return null;
}
/**
* Extract player name history from games
*/
static getPlayerNameHistory(games: Game[]): string[] {
const nameLastUsed: Record<string, number> = {};
games.forEach(game => {
const timestamp = new Date(game.updatedAt).getTime();
if ('players' in game) {
// EndlosGame
game.players.forEach(player => {
nameLastUsed[player.name] = Math.max(nameLastUsed[player.name] || 0, timestamp);
});
} else {
// StandardGame
if (game.player1) nameLastUsed[game.player1] = Math.max(nameLastUsed[game.player1] || 0, timestamp);
if (game.player2) nameLastUsed[game.player2] = Math.max(nameLastUsed[game.player2] || 0, timestamp);
if (game.player3) nameLastUsed[game.player3] = Math.max(nameLastUsed[game.player3] || 0, timestamp);
}
});
return [...new Set(Object.keys(nameLastUsed))].sort((a, b) => nameLastUsed[b] - nameLastUsed[a]);
}
}