Merge branch 'feat/137-Save-money-in-highscore-list' into 'main'

Feat: Money is now saved in the Higshcore list.

Closes #137

See merge request cs108-fs26/Gruppe-13!167
This commit was merged in pull request #323.
This commit is contained in:
Jona Walpert
2026-05-13 11:06:31 +00:00
4 changed files with 74 additions and 15 deletions
@@ -60,12 +60,9 @@ public class GetGameStateResponse extends SuccessResponse {
builder.param("WINNER", winnerIndex);
List<String> winnerNames = computeWinnerNames(state, game);
int potPerWinner = computePotPerWinner(state, winnerNames.size());
for (String name : winnerNames) {
builder.param("WINNER_NAME", name);
}
builder.param("POT_PER_WINNER", potPerWinner);
appendWinnerToHighscoresIfFinished(state, winnerNames);
appendHighscoreEntries(builder);
@@ -92,13 +89,22 @@ public class GetGameStateResponse extends SuccessResponse {
return;
}
// Keep ordering stable, avoid duplicate writes for the same winner in one hand.
// Keep ordering stable and persist tied winners as a single shared highscore
// entry. Compute the per-winner share here (pot is split equally among
// winners)
int potPerWinner = computePotPerWinner(state, winnerNames.size());
Set<String> uniqueWinners = new LinkedHashSet<>(winnerNames);
List<String> formattedWinners = new ArrayList<>();
for (String name : uniqueWinners) {
if (name != null && !name.isBlank()) {
HighscoreService.getInstance().appendWinner(name);
formattedWinners.add(name + " (" + Math.max(0, potPerWinner) + ")");
}
}
if (!formattedWinners.isEmpty()) {
HighscoreService.getInstance()
.appendFormattedEntry(String.join(", ", formattedWinners));
}
}
private static Player findPlayerByIndex(GameState state, int winnerIndex) {
@@ -18,6 +18,7 @@ public final class HighscoreService {
private static final DateTimeFormatter DISPLAY_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
private static final int MAX_RETURNED_ENTRIES = 100;
private static final int MIN_MONEY_COLUMN_COUNT = 3;
private static final String LINE_SEPARATOR = "\t";
private final Path storagePath;
@@ -32,6 +33,11 @@ public final class HighscoreService {
}
public synchronized void appendWinner(String winnerName) {
appendWinner(winnerName, 0);
}
/** Append a winner with an optional money value (money may be 0). */
public synchronized void appendWinner(String winnerName, int money) {
if (winnerName == null) {
return;
}
@@ -41,6 +47,44 @@ public final class HighscoreService {
return;
}
String line;
if (money > 0) {
line =
Instant.now()
+ LINE_SEPARATOR
+ sanitized
+ LINE_SEPARATOR
+ money
+ System.lineSeparator();
} else {
// Keep legacy two-column format for backward compatibility
line = Instant.now() + LINE_SEPARATOR + sanitized + System.lineSeparator();
}
try {
Files.createDirectories(storagePath.getParent());
Files.writeString(
storagePath,
line,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
} catch (IOException ignored) {
// Highscore persistence must never break the game state response path.
}
}
/** Append a preformatted highscore entry such as "Lars (1000), Jona (1000)". */
public synchronized void appendFormattedEntry(String entryText) {
if (entryText == null) {
return;
}
String sanitized = sanitizeName(entryText);
if (sanitized.isEmpty()) {
return;
}
String line = Instant.now() + LINE_SEPARATOR + sanitized + System.lineSeparator();
try {
@@ -74,15 +118,25 @@ public final class HighscoreService {
if (raw == null || raw.isBlank()) {
continue;
}
String[] parts = raw.split(LINE_SEPARATOR, 2);
// Support both legacy format (TIMESTAMP \t NAME) and new format (TIMESTAMP \t
// NAME \t MONEY)
String[] parts = raw.split(LINE_SEPARATOR);
if (parts.length < 2) {
continue;
}
try {
Instant ts = Instant.parse(parts[0].trim());
String display = DISPLAY_FORMATTER.format(ts) + " | " + parts[1].trim();
String name = parts[1].trim();
String display = DISPLAY_FORMATTER.format(ts) + " | " + name;
if (parts.length >= MIN_MONEY_COLUMN_COUNT) {
try {
int money = Integer.parseInt(parts[2].trim());
display = display + " | $" + money;
} catch (NumberFormatException nfe) {
// ignore malformed money
}
}
formatted.add(display);
} catch (Exception ignored) {
// Skip malformed lines and keep all valid entries.
@@ -4,7 +4,7 @@
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<AnchorPane xmlns="http://javafx.com"
<AnchorPane fx:id="rootPane" xmlns="http://javafx.com"
xmlns:fx="http://javafx.com"
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.CasinomainuiController"
styleClass="anchor-pane-bg"
@@ -14,10 +14,10 @@ class TaskbarControllerInputValidationTest {
void updateLogicRunsWithoutCrash() {
GameState state = new GameState();
state.players = List.of(
new Player(PlayerId.of("me"), 20000),
new Player(PlayerId.of("other"), 20000)
);
state.players =
List.of(
new Player(PlayerId.of("me"), 20000),
new Player(PlayerId.of("other"), 20000));
state.phase = "PREFLOP";
state.currentBet = 200;
@@ -31,7 +31,6 @@ class TaskbarControllerInputValidationTest {
}
static class DummyController {
void update(GameState state, PlayerId id) {
}
void update(GameState state, PlayerId id) {}
}
}