diff --git a/src/main/java/com/mayadem/battlearena/api/repository/ArenaLeaderboardRepository.java b/src/main/java/com/mayadem/battlearena/api/repository/ArenaLeaderboardRepository.java index cae82ab..efc6888 100644 --- a/src/main/java/com/mayadem/battlearena/api/repository/ArenaLeaderboardRepository.java +++ b/src/main/java/com/mayadem/battlearena/api/repository/ArenaLeaderboardRepository.java @@ -1,48 +1,55 @@ package com.mayadem.battlearena.api.repository; -import com.mayadem.battlearena.api.entity.ArenaLeaderboard; +import java.util.List; +import java.util.Optional; + import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.Optional; +import com.mayadem.battlearena.api.entity.ArenaLeaderboard; public interface ArenaLeaderboardRepository extends JpaRepository { - interface LeaderboardStatsProjection { - Long getTotalActiveWarriors(); + interface LeaderboardStatsProjection { + Long getTotalActiveWarriors(); + + Double getAverageWinRate(); + + Double getAverageScore(); - Double getAverageRankPoints(); + Double getAverageRankPoints(); - Integer getHighestRankPoints(); + Integer getHighestRankPoints(); - String getTopWarriorUsername(); - } + String getTopWarriorUsername(); + } // En iyi 100 oyuncu (JPQL'de LIMIT yok → Pageable kullanılmalı) @Query("SELECT a FROM ArenaLeaderboard a ORDER BY a.rankPoints DESC") List findTopPlayers(Pageable pageable); - // Belirli oyuncunun pozisyonu - @Query("SELECT a FROM ArenaLeaderboard a WHERE a.id = :warriorId") - Optional findWarriorPosition(@Param("warriorId") Long warriorId); - - // Global istatistikler - @Query(value = """ - SELECT COUNT(*) AS total_active_warriors, - AVG(rank_points) AS average_rank_points, - MAX(rank_points) AS highest_rank_points, - (SELECT username FROM arena_leaderboard ORDER BY rank_points DESC LIMIT 1) AS top_warrior_username - FROM arena_leaderboard - """, nativeQuery = true) - LeaderboardStatsProjection findGlobalStatsProjection(); - - // Rank aralığındaki oyuncular - @Query("SELECT a FROM ArenaLeaderboard a " + - "WHERE a.rankPosition BETWEEN :startRank AND :endRank " + - "ORDER BY a.rankPosition ASC") - List findWarriorsAroundRank(@Param("startRank") int startRank, - @Param("endRank") int endRank); + // Belirli oyuncunun pozisyonu + @Query("SELECT a FROM ArenaLeaderboard a WHERE a.id = :warriorId") + Optional findWarriorPosition(@Param("warriorId") Long warriorId); + + // Global istatistikler + @Query(value = """ + SELECT COUNT(*) AS total_active_warriors, + AVG(rank_points) AS average_rank_points, + AVG(win_rate) AS average_win_rate, + AVG(best_score) AS average_score, + MAX(rank_points) AS highest_rank_points, + (SELECT username FROM arena_leaderboard ORDER BY rank_points DESC LIMIT 1) AS top_warrior_username + FROM arena_leaderboard + """, nativeQuery = true) + LeaderboardStatsProjection findGlobalStatsProjection(); + + // Rank aralığındaki oyuncular + @Query("SELECT a FROM ArenaLeaderboard a " + + "WHERE a.rankPosition BETWEEN :startRank AND :endRank " + + "ORDER BY a.rankPosition ASC") + List findWarriorsAroundRank(@Param("startRank") int startRank, + @Param("endRank") int endRank); } diff --git a/src/main/java/com/mayadem/battlearena/api/service/WarriorStatisticsService.java b/src/main/java/com/mayadem/battlearena/api/service/WarriorStatisticsService.java new file mode 100644 index 0000000..55999a2 --- /dev/null +++ b/src/main/java/com/mayadem/battlearena/api/service/WarriorStatisticsService.java @@ -0,0 +1,173 @@ +package com.mayadem.battlearena.api.service; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.mayadem.battlearena.api.dto.BattleTypeStatsDto; +import com.mayadem.battlearena.api.dto.GlobalComparisonDto; +import com.mayadem.battlearena.api.dto.OverallStatsDto; +import com.mayadem.battlearena.api.dto.RecentPerformanceDto; +import com.mayadem.battlearena.api.dto.WarriorDetailedStatsDto; +import com.mayadem.battlearena.api.dto.enums.PerformanceTrend; +import com.mayadem.battlearena.api.dto.enums.StreakType; +import com.mayadem.battlearena.api.entity.ArenaLeaderboard; +import com.mayadem.battlearena.api.entity.Warrior; +import com.mayadem.battlearena.api.repository.ArenaLeaderboardRepository; +import com.mayadem.battlearena.api.repository.BattleParticipantRepository; +import com.mayadem.battlearena.api.repository.projection.DailyStatsProjection; +import com.mayadem.battlearena.api.repository.projection.OverallStatsProjection; +import com.mayadem.battlearena.api.repository.projection.RecentPerformanceProjection; +import com.mayadem.battlearena.api.repository.projection.StreakProjection; + +@Service +public class WarriorStatisticsService { + + private final BattleParticipantRepository battleParticipantRepository; + private final ArenaLeaderboardRepository arenaLeaderboardRepository; + + public WarriorStatisticsService(BattleParticipantRepository battleParticipantRepository, + ArenaLeaderboardRepository arenaLeaderboardRepository) { + this.battleParticipantRepository = battleParticipantRepository; + this.arenaLeaderboardRepository = arenaLeaderboardRepository; + } + + @Transactional(readOnly = true) + public WarriorDetailedStatsDto getDetailedStatsForWarrior(Warrior warrior) { + + OverallStatsDto overallStats = buildOverallStats(warrior); + + RecentPerformanceDto recentPerformance = buildRecentPerformance(warrior, overallStats.winRate()); + + List statsByType = battleParticipantRepository.findStatsByBattleType(warrior); + + Integer currentRank = arenaLeaderboardRepository.findWarriorPosition(warrior.getId()) + .map(ArenaLeaderboard::getRankPosition) + .orElse(null); + + GlobalComparisonDto globalComparison = buildGlobalComparison(); + + return new WarriorDetailedStatsDto( + warrior.getUsername(), + warrior.getDisplayName(), + warrior.getRankPoints(), + currentRank, + overallStats, + statsByType, + recentPerformance, + globalComparison); + } + + @Transactional(readOnly = true) + public OverallStatsDto getOverallStats(Warrior warrior) { + return buildOverallStats(warrior); + } + + @Transactional(readOnly = true) + public RecentPerformanceDto getRecentPerformance(Warrior warrior) { + OverallStatsDto overallStats = buildOverallStats(warrior); + return buildRecentPerformance(warrior, overallStats.winRate()); + } + + private GlobalComparisonDto buildGlobalComparison() { + ArenaLeaderboardRepository.LeaderboardStatsProjection projection = arenaLeaderboardRepository + .findGlobalStatsProjection(); + + return new GlobalComparisonDto( + projection.getAverageWinRate() != null ? projection.getAverageWinRate() : 0.0, + projection.getAverageScore() != null ? projection.getAverageScore() : 0.0, + projection.getTotalActiveWarriors() != null ? projection.getTotalActiveWarriors().intValue() : 0); + } + + private OverallStatsDto buildOverallStats(Warrior warrior) { + OverallStatsProjection stats = battleParticipantRepository.findOverallStatsByWarrior(warrior) + .orElse(new OverallStatsProjection(0, 0, 0, 0, 0.0, 0, 0.0, 0L)); + + List streakResults = battleParticipantRepository.findStreakInfoByWarrior(warrior.getId()); + + StreakProjection streakProjection; + if (streakResults != null && !streakResults.isEmpty()) { + Object[] streakResult = streakResults.get(0); + if (streakResult != null && streakResult.length > 0 && streakResult[0] != null) { + int currentStreak = ((Number) streakResult[0]).intValue(); + String streakTypeStr = (String) streakResult[1]; + int longestWinStreak = ((Number) streakResult[2]).intValue(); + + StreakType streakType = StreakType.NONE; + if ("WIN".equalsIgnoreCase(streakTypeStr)) { + streakType = StreakType.WIN; + } else if ("LOSS".equalsIgnoreCase(streakTypeStr)) { + streakType = StreakType.LOSS; + } + + streakProjection = new StreakProjection(currentStreak, streakType, longestWinStreak); + } else { + streakProjection = new StreakProjection(0, StreakType.NONE, 0); + } + } else { + streakProjection = new StreakProjection(0, StreakType.NONE, 0); + } + + return new OverallStatsDto( + (int) stats.totalBattles(), + (int) stats.victories(), + (int) stats.defeats(), + (int) stats.draws(), + stats.winRate() != null ? stats.winRate() : 0.0, + stats.bestScore() != null ? stats.bestScore() : 0, + stats.averageScore() != null ? stats.averageScore() : 0.0, + stats.totalRankPointsGained() != null ? stats.totalRankPointsGained().intValue() : 0, + streakProjection.currentStreak(), + streakProjection.streakType(), + streakProjection.longestWinStreak()); + } + + private RecentPerformanceDto buildRecentPerformance(Warrior warrior, double overallWinRate) { + + Instant since = Instant.now().minus(30, ChronoUnit.DAYS); + + RecentPerformanceProjection recentStats = battleParticipantRepository.findRecentStats(warrior, since) + .orElse(new RecentPerformanceProjection(0, 0, 0L)); + + List dailyCounts = battleParticipantRepository.findDailyBattleCounts(warrior.getId(), + since); + + Map dailyStatsMap = dailyCounts.stream() + .collect(Collectors.toMap( + + projection -> projection.getBattleDate().toString(), + + DailyStatsProjection::getBattleCount)); + + long battles = recentStats.battles(); + long victories = recentStats.victories(); + long rankPointsChange = recentStats.rankPointsChange() != null ? recentStats.rankPointsChange() : 0L; + + double winRateLast30Days = (battles > 0) ? ((double) victories / battles) * 100.0 : 0.0; + PerformanceTrend trend; + double difference = winRateLast30Days - overallWinRate; + + if (battles < 10) { + trend = PerformanceTrend.STEADY; + } else if (difference > 5) { + trend = PerformanceTrend.IMPROVING; + } else if (difference < -5) { + trend = PerformanceTrend.DECLINING; + } else { + trend = PerformanceTrend.STEADY; + } + + return new RecentPerformanceDto( + (int) battles, + (int) victories, + Math.round(winRateLast30Days * 100.0) / 100.0, + (int) rankPointsChange, + trend, + dailyStatsMap); + } +} \ No newline at end of file