Spaces:
Sleeping
Sleeping
| /** | |
| * Player Store | |
| * | |
| * Manages player state for multiplayer mode, including nickname, connection status, | |
| * and opponent information. | |
| */ | |
| import { defineStore } from "pinia"; | |
| import { StorageKey, localStorageManager } from "@/utils/storage"; | |
| import { getRandomNickname } from "@/data/defaultNicknames"; | |
| /** | |
| * Player state interface | |
| */ | |
| export interface PlayerState { | |
| nickname: string; // Local player nickname | |
| playerId: string | null; // Socket ID | |
| playerColor: "black" | "white" | null; // Player's stone color in current game | |
| roomId: string | null; // Current room ID | |
| roomName: string | null; // Human-readable room name | |
| isAdmin: boolean; // Whether player is room admin | |
| opponentNickname: string | null; // Opponent's nickname | |
| connectionStatus: "disconnected" | "connected" | "in-room"; // Connection state | |
| } | |
| /** | |
| * Nickname validation result | |
| */ | |
| interface ValidationResult { | |
| valid: boolean; | |
| error?: string; | |
| } | |
| /** | |
| * Validate a nickname against the rules: | |
| * - Length: 3-20 characters | |
| * - Characters: alphanumeric + spaces only | |
| * - No leading/trailing whitespace | |
| */ | |
| function validateNickname(nickname: string): ValidationResult { | |
| if (!nickname || typeof nickname !== "string") { | |
| return { valid: false, error: "Nickname is required" }; | |
| } | |
| if (nickname.length < 3) { | |
| return { valid: false, error: "Nickname must be at least 3 characters" }; | |
| } | |
| if (nickname.length > 20) { | |
| return { valid: false, error: "Nickname must be 20 characters or less" }; | |
| } | |
| if (!/^[a-zA-Z0-9 ]+$/.test(nickname)) { | |
| return { valid: false, error: "Only letters, numbers, and spaces allowed" }; | |
| } | |
| if (nickname.trim() !== nickname) { | |
| return { valid: false, error: "No leading or trailing spaces allowed" }; | |
| } | |
| return { valid: true }; | |
| } | |
| /** | |
| * Load nickname from localStorage or generate a random one | |
| */ | |
| function loadNickname(): string { | |
| const stored = localStorageManager.getString(StorageKey.PLAYER_NICKNAME); | |
| if (stored) { | |
| // Validate stored nickname | |
| const validation = validateNickname(stored); | |
| if (validation.valid) { | |
| return stored; | |
| } | |
| } | |
| // Generate random nickname if none exists or invalid | |
| return getRandomNickname(); | |
| } | |
| /** | |
| * Save nickname to localStorage | |
| */ | |
| function saveNickname(nickname: string): void { | |
| localStorageManager.setString(StorageKey.PLAYER_NICKNAME, nickname); | |
| } | |
| /** | |
| * Player store | |
| */ | |
| export const usePlayerStore = defineStore("player", { | |
| state: (): PlayerState => ({ | |
| nickname: loadNickname(), | |
| playerId: null, | |
| playerColor: null, | |
| roomId: null, | |
| roomName: null, | |
| isAdmin: false, | |
| opponentNickname: null, | |
| connectionStatus: "disconnected" | |
| }), | |
| getters: { | |
| /** | |
| * Is player currently in a room? | |
| */ | |
| isInRoom: (state): boolean => state.connectionStatus === "in-room", | |
| /** | |
| * Does player have an opponent in the room? | |
| */ | |
| hasOpponent: (state): boolean => state.opponentNickname !== null, | |
| /** | |
| * Display name with fallback to "Guest" | |
| */ | |
| displayName: (state): string => state.nickname || "Guest", | |
| /** | |
| * Is player connected to socket server? | |
| */ | |
| isConnected: (state): boolean => state.connectionStatus !== "disconnected" | |
| }, | |
| actions: { | |
| /** | |
| * Set the player's nickname | |
| * @param nickname - New nickname to set | |
| * @returns true if successful, false if validation failed | |
| */ | |
| setNickname(nickname: string): boolean { | |
| const validation = validateNickname(nickname); | |
| if (!validation.valid) { | |
| console.warn(`[PlayerStore] Invalid nickname: ${validation.error}`); | |
| return false; | |
| } | |
| this.nickname = nickname; | |
| saveNickname(nickname); | |
| return true; | |
| }, | |
| /** | |
| * Set the player's socket ID | |
| * @param id - Socket ID from server | |
| */ | |
| setPlayerId(id: string): void { | |
| this.playerId = id; | |
| this.connectionStatus = "connected"; | |
| }, | |
| /** | |
| * Join a room with the given ID and assigned color | |
| * @param roomId - Room ID | |
| * @param color - Assigned player color | |
| * @param roomName - Human-readable room name | |
| * @param isAdmin - Whether player is room admin | |
| */ | |
| joinRoom(roomId: string, color: "black" | "white", roomName?: string, isAdmin?: boolean): void { | |
| this.roomId = roomId; | |
| this.roomName = roomName || null; | |
| this.isAdmin = isAdmin || false; | |
| this.playerColor = color; | |
| this.connectionStatus = "in-room"; | |
| }, | |
| /** | |
| * Set the room name | |
| * @param name - New room name | |
| */ | |
| setRoomName(name: string): void { | |
| this.roomName = name; | |
| }, | |
| /** | |
| * Set the opponent's nickname | |
| * @param nickname - Opponent's nickname | |
| */ | |
| setOpponentNickname(nickname: string): void { | |
| this.opponentNickname = nickname; | |
| }, | |
| /** | |
| * Leave the current room | |
| */ | |
| leaveRoom(): void { | |
| this.roomId = null; | |
| this.roomName = null; | |
| this.isAdmin = false; | |
| this.playerColor = null; | |
| this.opponentNickname = null; | |
| this.connectionStatus = "connected"; | |
| }, | |
| /** | |
| * Disconnect from the server | |
| */ | |
| disconnect(): void { | |
| this.playerId = null; | |
| this.roomId = null; | |
| this.roomName = null; | |
| this.isAdmin = false; | |
| this.playerColor = null; | |
| this.opponentNickname = null; | |
| this.connectionStatus = "disconnected"; | |
| }, | |
| /** | |
| * Reset player state to initial state | |
| * Note: Nickname is preserved in localStorage | |
| */ | |
| reset(): void { | |
| this.playerId = null; | |
| this.playerColor = null; | |
| this.roomId = null; | |
| this.roomName = null; | |
| this.isAdmin = false; | |
| this.opponentNickname = null; | |
| this.connectionStatus = "disconnected"; | |
| } | |
| } | |
| }); | |
| /** | |
| * Export validation function for use in other components | |
| */ | |
| export { validateNickname }; | |