Skip to content

Commit

Permalink
Merge pull request #48 from PaulKreft/multiplayer-stats
Browse files Browse the repository at this point in the history
Multiplayer stats
  • Loading branch information
PaulKreft authored Feb 25, 2024
2 parents 0cdbbee + 19b614a commit 57bc23a
Show file tree
Hide file tree
Showing 28 changed files with 469 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,10 @@ public String handleException(RequestQueueNotFoundException exception) {
public String handleException(PlayerNotPartOfLobbyException exception) {
return exception.getMessage();
}

@ExceptionHandler({LobbyCapacityExceededException.class})
@ResponseStatus(HttpStatus.CONFLICT)
public String handleException(LobbyCapacityExceededException exception) {
return exception.getMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.neuefische.paulkreft.backend.exception;

public class LobbyCapacityExceededException extends RuntimeException {
public LobbyCapacityExceededException(String errorMessage) {
super(errorMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ public record MultiplayerGame(
List<String> playerIds,
int difficulty,
Integer streakToWin,
String winnerId,
List<String> winnerIds,
List<String> loserIds,
Integer wonInMilliseconds,
int totalPlayers,
Instant createdAt
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ public record MultiplayerGameCreate(
List<String> playerIds,
int difficulty,
Integer streakToWin,
String winnerId,
List<String> winnerIds,
List<String> loserIds,
Integer wonInMilliseconds
Integer wonInMilliseconds,
int totalPlayers
) {
public MultiplayerGame withIdAndCreatedAt(String id, Instant createdAt) {
return new MultiplayerGame(id, this.playerIds, this.difficulty, this.streakToWin, this.winnerId, this.loserIds, this.wonInMilliseconds, createdAt);
return new MultiplayerGame(id, this.playerIds, this.difficulty, this.streakToWin, this.winnerIds, this.loserIds, this.wonInMilliseconds, this.totalPlayers , createdAt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MultiplayerGameRepo extends MongoRepository<MultiplayerGame, String> {
List<MultiplayerGame> findAllByTotalPlayersAndPlayerIdsIsContainingOrderByCreatedAtAsc(int totalPlayers, List<String> playerIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public record Lobby(
List<Player> losers,
Integer streakToWin,
Integer timeToBeat,
Instant lastGameStarted
Instant lastGameStarted,
Integer capacity
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import de.neuefische.paulkreft.backend.exception.LobbyCapacityExceededException;
import de.neuefische.paulkreft.backend.exception.LobbyGoneException;
import de.neuefische.paulkreft.backend.exception.PlayerNotPartOfLobbyException;
import de.neuefische.paulkreft.backend.lobby.model.Lobby;
Expand Down Expand Up @@ -43,6 +44,10 @@ public Lobby joinLobby(String id, @RequestBody Player player) {
return lobby;
}

if (lobby.capacity() != null && lobby.players().size() == lobby.capacity()) {
throw new LobbyCapacityExceededException("This lobby is full");
}

lobby.players().add(player);
return lobbyRepo.save(lobby);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.neuefische.paulkreft.backend.user.model;
package de.neuefische.paulkreft.backend.statistic.model;

public record Statistics(
public record ClassicStatistics(
ScoreMap longestWinningStreak,
ScoreMap longestLosingStreak,
ScoreMap gamesPlayed,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package de.neuefische.paulkreft.backend.statistic.model;

public record DuelStatistics(
String player,
String opponent,
ScoreMap gamesPlayed,
ScoreMap gamesWon
) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.neuefische.paulkreft.backend.user.model;
package de.neuefische.paulkreft.backend.statistic.model;

public record ScoreMap(
Double easy,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package de.neuefische.paulkreft.backend.statistic.service;

import de.neuefische.paulkreft.backend.game.classic.model.Game;
import de.neuefische.paulkreft.backend.game.classic.repository.GameRepo;
import de.neuefische.paulkreft.backend.game.multiplayer.model.MultiplayerGame;
import de.neuefische.paulkreft.backend.game.multiplayer.repository.MultiplayerGameRepo;
import de.neuefische.paulkreft.backend.statistic.model.ClassicStatistics;
import de.neuefische.paulkreft.backend.statistic.model.DuelStatistics;
import de.neuefische.paulkreft.backend.statistic.model.ScoreMap;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;


@Service
@RequiredArgsConstructor
public class StatisticService {
private final GameRepo gameRepo;
private final MultiplayerGameRepo multiplayerGameRepo;

private static final int EASY = 1;
private static final int MEDIUM = 2;
private static final int HARD = 4;

private static final int DUEL_TOTAL_PLAYERS = 2;

public ClassicStatistics getUserClassicStatistics(String id) {
List<Game> games = gameRepo.findAllByUserIdOrderByCreatedAtAsc(id);

List<Game> easyGames = games.stream().filter(game -> game.difficulty() == EASY).toList();
List<Game> mediumGames = games.stream().filter(game -> game.difficulty() == MEDIUM).toList();
List<Game> hardGames = games.stream().filter(game -> game.difficulty() == HARD).toList();

ScoreMap longestWinningStreaks = getLongestWinningStreaks(easyGames, mediumGames, hardGames);
ScoreMap longestLosingStreaks = getLongestLosingStreaks(easyGames, mediumGames, hardGames);
ScoreMap totalGames = getTotalGames(easyGames, mediumGames, hardGames);
ScoreMap totalGamesWon = getGamesWon(easyGames, mediumGames, hardGames);
ScoreMap fastestSolve = getFastestSolves(easyGames, mediumGames, hardGames);
ScoreMap averageDuration = getAverageDurations(easyGames, mediumGames, hardGames);

return new ClassicStatistics(longestWinningStreaks, longestLosingStreaks, totalGames, totalGamesWon, fastestSolve, averageDuration);
}


public DuelStatistics getUserDuelStatistics(String id, String opponentId) {
List<MultiplayerGame> games = multiplayerGameRepo.findAllByTotalPlayersAndPlayerIdsIsContainingOrderByCreatedAtAsc(DUEL_TOTAL_PLAYERS, List.of(id, opponentId));

List<MultiplayerGame> easyGames = games.stream().filter(game -> game.difficulty() == EASY).toList();
List<MultiplayerGame> mediumGames = games.stream().filter(game -> game.difficulty() == MEDIUM).toList();
List<MultiplayerGame> hardGames = games.stream().filter(game -> game.difficulty() == HARD).toList();

double easyGamesWon = easyGames.stream().filter(game -> game.winnerIds().contains(id)).toList().size();
double mediumGamesWon = mediumGames.stream().filter(game -> game.winnerIds().contains(id)).toList().size();
double hardGamesWon = hardGames.stream().filter(game -> game.winnerIds().contains(id)).toList().size();

return new DuelStatistics(id, opponentId, new ScoreMap((double) easyGames.size(), (double) mediumGames.size(), (double) hardGames.size()), new ScoreMap(easyGamesWon, mediumGamesWon, hardGamesWon));
}


private ScoreMap getAverageDurations(List<Game> easyGames, List<Game> mediumGames, List<Game> hardGames) {
List<Double> easyDurations = easyGames.stream().map(game -> Double.valueOf(game.duration())).filter(d -> d != 0).toList();
List<Double> mediumDurations = mediumGames.stream().map(game -> Double.valueOf(game.duration())).filter(d -> d != 0).toList();
List<Double> hardDurations = hardGames.stream().map(game -> Double.valueOf(game.duration())).filter(d -> d != 0).toList();

Double averageDurationEasy = !easyDurations.isEmpty() ? easyDurations.stream().mapToDouble(v -> v).sum() / easyDurations.size() : null;
Double averageDurationMedium = !mediumDurations.isEmpty() ? mediumDurations.stream().mapToDouble(v -> v).sum() : null;
Double averageDurationHard = !hardDurations.isEmpty() ? hardDurations.stream().mapToDouble(v -> v).sum() : null;

return new ScoreMap(averageDurationEasy, averageDurationMedium, averageDurationHard);
}

private ScoreMap getFastestSolves(List<Game> easyGames, List<Game> mediumGames, List<Game> hardGames) {
List<Double> easyDurations = easyGames.stream().map(game -> Double.valueOf(game.duration())).filter(d -> d != 0).toList();
List<Double> mediumDurations = mediumGames.stream().map(game -> Double.valueOf(game.duration())).filter(d -> d != 0).toList();
List<Double> hardDurations = hardGames.stream().map(game -> Double.valueOf(game.duration())).filter(d -> d != 0).toList();

Double fastestSolveEasy = !easyDurations.isEmpty() ? Collections.min(easyDurations) : null;
Double fastestSolveMedium = !mediumDurations.isEmpty() ? Collections.min(mediumDurations) : null;
Double fastestSolveHard = !hardDurations.isEmpty() ? Collections.min(hardDurations) : null;

return new ScoreMap(fastestSolveEasy, fastestSolveMedium, fastestSolveHard);
}

private ScoreMap getTotalGames(List<Game> easyGames, List<Game> mediumGames, List<Game> hardGames) {
return new ScoreMap((double) easyGames.size(), (double) mediumGames.size(), (double) hardGames.size());
}

private ScoreMap getGamesWon(List<Game> easyGames, List<Game> mediumGames, List<Game> hardGames) {
return new ScoreMap((double) easyGames.stream().filter(Game::isSuccess).toList().size(), (double) mediumGames.stream().filter(Game::isSuccess).toList().size(), (double) hardGames.stream().filter(Game::isSuccess).toList().size());
}

private ScoreMap getLongestWinningStreaks(List<Game> easyGames, List<Game> mediumGames, List<Game> hardGames) {
Map<String, Double> easyStreaks = getStreaks(easyGames);
Map<String, Double> mediumStreaks = getStreaks(mediumGames);
Map<String, Double> hardStreaks = getStreaks(hardGames);


return new ScoreMap(easyStreaks.get("win"), mediumStreaks.get("win"), hardStreaks.get("win"));
}

private ScoreMap getLongestLosingStreaks(List<Game> easyGames, List<Game> mediumGames, List<Game> hardGames) {
Map<String, Double> easyStreaks = getStreaks(easyGames);
Map<String, Double> mediumStreaks = getStreaks(mediumGames);
Map<String, Double> hardStreaks = getStreaks(hardGames);


return new ScoreMap(easyStreaks.get("lose"), mediumStreaks.get("lose"), hardStreaks.get("lose"));
}

private Map<String, Double> getStreaks(List<Game> games) {
Map<String, Double> streaks = new HashMap<>();

List<Double> winningStreaks = new ArrayList<>();
List<Double> losingStreaks = new ArrayList<>();

double winningStreak = 0;
double losingStreak = 0;

for (Game game : games) {
if (game.isSuccess()) {
winningStreak++;
if (losingStreak != 0) {
losingStreaks.add(losingStreak);
losingStreak = 0;
}
continue;
}

losingStreak++;
if (winningStreak != 0) {
winningStreaks.add(winningStreak);
winningStreak = 0;
}
}

if (winningStreak != 0) {
winningStreaks.add(winningStreak);
}

if (losingStreak != 0) {
losingStreaks.add(losingStreak);
}


streaks.put("win", !winningStreaks.isEmpty() ? Collections.max(winningStreaks) : 0.0);
streaks.put("lose", !losingStreaks.isEmpty() ? Collections.max(losingStreaks) : 0.0);

return streaks;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.neuefische.paulkreft.backend.user.controller;

import de.neuefische.paulkreft.backend.user.model.Statistics;
import de.neuefische.paulkreft.backend.statistic.model.ClassicStatistics;
import de.neuefische.paulkreft.backend.statistic.model.DuelStatistics;
import de.neuefische.paulkreft.backend.user.model.User;
import de.neuefische.paulkreft.backend.user.model.UserGet;
import de.neuefische.paulkreft.backend.user.service.UserService;
Expand All @@ -27,7 +28,12 @@ public UserGet updateUser(@RequestBody User user) {
}

@GetMapping("{id}/statistics")
public Statistics getUserStatistics(@PathVariable String id) {
return userService.getStatistics(id);
public ClassicStatistics getUserClassicStatistics(@PathVariable String id) {
return userService.getClassicStatistics(id);
}

@GetMapping("{id}/statistics/duel")
public DuelStatistics getUserDuelStatistics(@PathVariable String id, @RequestParam String opponentId) {
return userService.getDuelStatistics(id, opponentId);
}
}
Loading

0 comments on commit 57bc23a

Please sign in to comment.