'float', 'playtime_minutes' => 'int', 'completion_percentage' => 'int', 'is_installed' => 'bool', 'is_favorite' => 'bool', 'release_date' => 'date', 'last_played_at' => 'datetime', 'platform_achievements' => 'array', 'platform_stats' => 'array' ]; public function source() { return new Source($this->pdo); } public function markAsPlayed(int $minutes = 60): bool { $this->playtime_minutes += $minutes; $this->last_played_at = date('Y-m-d H:i:s'); return $this->update($this->id, [ 'playtime_minutes' => $this->playtime_minutes, 'last_played_at' => $this->last_played_at ]); } public function toggleFavorite(): bool { return $this->update($this->id, [ 'is_favorite' => !$this->is_favorite ]); } public function toggleInstalled(): bool { return $this->update($this->id, [ 'is_installed' => !$this->is_installed ]); } public function updateCompletion(int $percentage): bool { return $this->update($this->id, [ 'completion_percentage' => min(100, max(0, $percentage)) ]); } public function updateRating(float $rating): bool { return $this->update($this->id, [ 'rating' => min(10.0, max(0.0, $rating)) ]); } public static function getStats(\PDO $pdo): array { $stmt = $pdo->query(" SELECT COUNT(*) as total_games, SUM(playtime_minutes) as total_playtime, AVG(rating) as avg_rating, COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_games, COUNT(CASE WHEN is_installed = 1 THEN 1 END) as installed_games, AVG(completion_percentage) as avg_completion FROM games "); return $stmt->fetch(\PDO::FETCH_ASSOC); } public static function getRecent(\PDO $pdo, int $limit = 10): array { $stmt = $pdo->prepare(" SELECT g.*, s.display_name as source_name FROM games g JOIN sources s ON g.source_id = s.id WHERE g.last_played_at IS NOT NULL ORDER BY g.last_played_at DESC LIMIT :limit "); $stmt->execute(['limit' => $limit]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } /** * Generate a game key for grouping games across platforms */ public static function generateGameKey(string $title, string $platform = null): string { // Normalize title for consistent grouping $normalized = strtolower(trim($title)); $normalized = preg_replace('/[^\w\s]/', '', $normalized); // Remove special characters $normalized = preg_replace('/\s+/', ' ', $normalized); // Normalize whitespace $normalized = trim($normalized); // Add platform to make it unique if provided if ($platform) { $normalized .= ' ' . strtolower($platform); } return $normalized; } /** * Find all platform versions of a game */ public function getPlatformVersions(): array { if (!$this->game_key) { return []; } $stmt = $this->pdo->prepare(" SELECT g.*, s.display_name as source_name FROM games g JOIN sources s ON g.source_id = s.id WHERE g.game_key = :game_key ORDER BY g.platform, g.source_id "); $stmt->execute(['game_key' => $this->game_key]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } /** * Get games grouped by title for display */ public static function getGroupedGames(\PDO $pdo, int $limit = 50): array { $stmt = $pdo->prepare(" SELECT game_key, title, COUNT(*) as platform_count, GROUP_CONCAT(DISTINCT platform ORDER BY platform) as platforms, GROUP_CONCAT(DISTINCT source_id ORDER BY source_id) as source_ids, MAX(image_url) as image_url, MAX(last_played_at) as last_played_at, SUM(playtime_minutes) as total_playtime, MAX(completion_percentage) as max_completion, GROUP_CONCAT(DISTINCT genre ORDER BY genre) as genres FROM games WHERE game_key IS NOT NULL GROUP BY game_key, title ORDER BY last_played_at DESC, total_playtime DESC LIMIT :limit "); $stmt->execute(['limit' => $limit]); $games = $stmt->fetchAll(\PDO::FETCH_ASSOC); // Enhance each game with platform details foreach ($games as &$game) { $game['platforms'] = array_unique(explode(',', $game['platforms'])); $game['source_ids'] = array_unique(explode(',', $game['source_ids'])); $game['genres'] = array_unique(array_filter(explode(',', $game['genres']))); } return $games; } /** * Update platform-specific achievements */ public function updatePlatformAchievements(array $achievements): bool { return $this->update($this->id, [ 'platform_achievements' => json_encode($achievements) ]); } /** * Update platform-specific statistics */ public function updatePlatformStats(array $stats): bool { return $this->update($this->id, [ 'platform_stats' => json_encode($stats) ]); } /** * Get platform-specific achievements */ public function getPlatformAchievements(): array { return $this->platform_achievements ?? []; } /** * Get platform-specific statistics */ public function getPlatformStats(): array { return $this->platform_stats ?? []; } /** * Get total count of games for pagination */ public static function getTotalCount(\PDO $pdo, string $search = ''): int { $sql = "SELECT COUNT(*) as count FROM games"; $params = []; if (!empty($search)) { $sql .= " WHERE title LIKE :search"; $params['search'] = "%{$search}%"; } $stmt = $pdo->prepare($sql); $stmt->execute($params); return (int) $stmt->fetch()['count']; } /** * Get grouped games with pagination and search support */ public static function getGroupedGamesWithPagination(\PDO $pdo, int $page, int $perPage, string $search = ''): array { $offset = ($page - 1) * $perPage; $sql = " SELECT game_key, title, COUNT(*) as platform_count, GROUP_CONCAT(DISTINCT platform ORDER BY platform) as platforms, GROUP_CONCAT(DISTINCT source_id ORDER BY source_id) as source_ids, MAX(image_url) as image_url, MAX(last_played_at) as last_played_at, SUM(playtime_minutes) as total_playtime, MAX(completion_percentage) as max_completion, GROUP_CONCAT(DISTINCT genre ORDER BY genre) as genres FROM games WHERE game_key IS NOT NULL "; $params = []; if (!empty($search)) { $sql .= " AND title LIKE :search"; $params['search'] = "%{$search}%"; } $sql .= " GROUP BY game_key, title ORDER BY last_played_at DESC, total_playtime DESC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT); foreach ($params as $key => $value) { $stmt->bindValue(":{$key}", $value); } $stmt->execute(); $games = $stmt->fetchAll(\PDO::FETCH_ASSOC); // Enhance each game with platform details foreach ($games as &$game) { $game['platforms'] = array_unique(explode(',', $game['platforms'])); $game['source_ids'] = array_unique(explode(',', $game['source_ids'])); $game['genres'] = array_unique(array_filter(explode(',', $game['genres']))); } return $games; } }