Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/main/java/model/session/PlayerLeaderboardEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package model.session;

import java.math.BigDecimal;

/**
* Immutable leaderboard snapshot for one saved player profile.
*
* @param username display username
* @param netWorth current total net worth
* @param totalReturnPercent total return as a decimal ratio (for example 0.25 = 25%)
*/
public record PlayerLeaderboardEntry(
String username,
BigDecimal netWorth,
BigDecimal totalReturnPercent
) {
}
9 changes: 9 additions & 0 deletions src/main/java/model/session/PlayerLeaderboardMetric.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package model.session;

/**
* Supported leaderboard ranking metrics.
*/
public enum PlayerLeaderboardMetric {
NET_WORTH,
TOTAL_RETURN_PERCENT
}
73 changes: 73 additions & 0 deletions src/main/java/model/session/PlayerLeaderboardRanking.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package model.session;

import java.math.BigDecimal;
import java.util.Comparator;

/**
* Comparator helpers for leaderboard ordering and rank calculation.
*/
public final class PlayerLeaderboardRanking {

private PlayerLeaderboardRanking() {
}

/**
* Comparator used to calculate best-first leaderboard ranks for the supplied metric.
*
* @param metric active ranking metric
* @return comparator ordering strongest players first
*/
public static Comparator<PlayerLeaderboardEntry> bestFirstComparator(PlayerLeaderboardMetric metric) {
return Comparator
.comparing(
(PlayerLeaderboardEntry entry) -> metricValue(entry, metric),
Comparator.reverseOrder())
.thenComparing(
entry -> metricValue(entry, secondaryMetric(metric)),
Comparator.reverseOrder())
.thenComparing(PlayerLeaderboardEntry::username, String.CASE_INSENSITIVE_ORDER)
.thenComparing(PlayerLeaderboardEntry::username);
}

/**
* Comparator used for display ordering.
*
* @param metric active display metric
* @param ascending whether the primary metric should be shown low-to-high
* @return comparator matching the requested display order
*/
public static Comparator<PlayerLeaderboardEntry> displayComparator(
PlayerLeaderboardMetric metric,
boolean ascending) {
if (!ascending) {
return bestFirstComparator(metric);
}
return Comparator
.comparing((PlayerLeaderboardEntry entry) -> metricValue(entry, metric), BigDecimal::compareTo)
.thenComparing(
entry -> metricValue(entry, secondaryMetric(metric)),
Comparator.reverseOrder())
.thenComparing(PlayerLeaderboardEntry::username, String.CASE_INSENSITIVE_ORDER)
.thenComparing(PlayerLeaderboardEntry::username);
}

/**
* Returns the numeric metric value used for sorting.
*
* @param entry leaderboard entry
* @param metric chosen metric
* @return numeric value for that metric
*/
public static BigDecimal metricValue(PlayerLeaderboardEntry entry, PlayerLeaderboardMetric metric) {
return switch (metric) {
case NET_WORTH -> entry.netWorth();
case TOTAL_RETURN_PERCENT -> entry.totalReturnPercent();
};
}

private static PlayerLeaderboardMetric secondaryMetric(PlayerLeaderboardMetric metric) {
return metric == PlayerLeaderboardMetric.NET_WORTH
? PlayerLeaderboardMetric.TOTAL_RETURN_PERCENT
: PlayerLeaderboardMetric.NET_WORTH;
}
}
39 changes: 39 additions & 0 deletions src/main/java/model/session/SessionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
Expand Down Expand Up @@ -195,6 +197,43 @@ public List<String> listRegisteredUsers() {
return userAccountRepository.listUsernames();
}

/**
* Lists leaderboard entries for all saved profiles, using live in-memory state for the active
* session when available.
*
* @return default-ranked leaderboard entries
*/
public List<PlayerLeaderboardEntry> listLeaderboardEntries() {
List<PlayerLeaderboardEntry> entries = new ArrayList<>();
for (String username : userAccountRepository.listUsernames()) {
String normalizedUsername = ProfileDirectories.normalizeUsername(username);
if (activeSession != null && activeSession.normalizedUsername().equals(normalizedUsername)) {
entries.add(toLeaderboardEntry(activeSession.player()));
continue;
}

GameStateSnapshot snapshot = gameStateRepository.load(normalizedUsername)
.orElseThrow(() -> new IllegalStateException("Saved game state not found for " + username + "."));
Exchange exchange = gameStateMapper.restoreExchange(snapshot.exchange(), loadMarketData());
Player player = gameStateMapper.restorePlayer(snapshot.player(), exchange);
entries.add(toLeaderboardEntry(player));
}
return entries.stream()
.sorted(PlayerLeaderboardRanking.bestFirstComparator(PlayerLeaderboardMetric.NET_WORTH))
.toList();
}

private static PlayerLeaderboardEntry toLeaderboardEntry(Player player) {
BigDecimal netWorth = player.getNetWorth();
BigDecimal startingMoney = player.getStartingMoney();
BigDecimal totalReturnPercent = BigDecimal.ZERO;
if (startingMoney.compareTo(BigDecimal.ZERO) != 0) {
totalReturnPercent = netWorth.subtract(startingMoney)
.divide(startingMoney, 8, RoundingMode.HALF_UP);
}
return new PlayerLeaderboardEntry(player.getName(), netWorth, totalReturnPercent);
}

/**
* Persists the active game, then saves a snapshot of the current run for later comparison.
*
Expand Down
48 changes: 46 additions & 2 deletions src/main/java/view/AuthPane.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import model.session.PlayerLeaderboardEntry;

/**
* Collects login and registration input for the session-based GUI shell.
Expand All @@ -36,6 +37,8 @@ public class AuthPane extends BorderPane {
private final PasswordField registerPinField = new PasswordField();
private final TextField registerStartingMoneyField = new TextField();
private final Label statusLabel = new Label();
private final Button returnButton = new Button("Back to current session");
private final PlayerLeaderboardPanel leaderboardPanel;

/**
* Builds the authentication view with login and registration forms.
Expand All @@ -49,6 +52,7 @@ public class AuthPane extends BorderPane {
*/
public AuthPane(
List<String> users,
List<PlayerLeaderboardEntry> leaderboardEntries,
boolean allowReturnToSession,
LoginAction loginAction,
RegisterAction registerAction,
Expand Down Expand Up @@ -133,15 +137,18 @@ public AuthPane(
tabPane.setStyle("-fx-background-color: #1e1e1e;");
HBox.setHgrow(tabPane, Priority.ALWAYS);

HBox center = new HBox(24, usersBox, tabPane);
leaderboardPanel = new PlayerLeaderboardPanel(leaderboardEntries);
leaderboardPanel.setPrefWidth(440);
HBox.setHgrow(leaderboardPanel, Priority.ALWAYS);

HBox center = new HBox(24, usersBox, tabPane, leaderboardPanel);
center.setAlignment(Pos.TOP_LEFT);
setCenter(center);

Button helpButton = new Button("Help");
styleButton(helpButton);
helpButton.setOnAction(_ -> helpAction.run());

Button returnButton = new Button("Back to current session");
styleButton(returnButton);
returnButton.setVisible(allowReturnToSession);
returnButton.setManaged(allowReturnToSession);
Expand Down Expand Up @@ -191,6 +198,43 @@ public int getRegisteredUserCount() {
return registeredUsersView.getItems().size();
}

/**
* Returns the embedded leaderboard panel for test assertions.
*
* @return auth-screen leaderboard panel
*/
public PlayerLeaderboardPanel getLeaderboardPanel() {
return leaderboardPanel;
}

/**
* Returns the visible leaderboard usernames in display order.
*
* @return displayed leaderboard usernames
*/
public List<String> getLeaderboardDisplayedUsernames() {
return leaderboardPanel.getDisplayedUsernames();
}

/**
* Returns the formatted net worth shown for a specific leaderboard user.
*
* @param username target username
* @return formatted net worth text or {@code null} if missing
*/
public String getLeaderboardNetWorthForUser(String username) {
return leaderboardPanel.getDisplayedNetWorthForUser(username);
}

/**
* Simulates pressing the return button when it is visible.
*/
public void triggerReturnToSession() {
if (returnButton.isManaged()) {
returnButton.fire();
}
}

/**
* Fills the login form for tests or higher-level helpers.
*
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/view/GuiAppShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public void shutdown() {
private void showAuthView(boolean allowReturnToSession) {
authPane = new AuthPane(
listRegisteredUsers(),
listLeaderboardEntries(),
allowReturnToSession,
this::handleLogin,
this::handleRegistration,
Expand Down Expand Up @@ -230,6 +231,10 @@ private List<String> listRegisteredUsers() {
return sessionService.listRegisteredUsers();
}

private List<model.session.PlayerLeaderboardEntry> listLeaderboardEntries() {
return sessionService.listLeaderboardEntries();
}

private void disposeWorkspace() {
if (workspaceView != null) {
workspaceView.dispose();
Expand Down
Loading
Loading