Game UI Optimisation #320

Merged
j.kropff merged 17 commits from feat/game-ui-optimization into main 2026-05-10 20:54:22 +02:00
8 changed files with 93 additions and 87 deletions
Showing only changes of commit 2cfc40543a - Show all commits
@@ -527,7 +527,8 @@ public class CasinoGameController {
} }
/** /**
* Refresh the list of opponents in the lobby before the game starts by fetching the latest player. * Refresh the list of opponents in the lobby before the game starts by fetching the latest
* player.
*/ */
private void refreshLobbyOpponentsBeforeGameStart() { private void refreshLobbyOpponentsBeforeGameStart() {
if (gameService != null && gameService.peekStateOrNull() != null) { if (gameService != null && gameService.peekStateOrNull() != null) {
@@ -933,12 +934,13 @@ public class CasinoGameController {
} }
/** /**
* Find the index of the current player in the provided list of players by comparing their PlayerId * Find the index of the current player in the provided list of players by comparing their
* with myPlayerId. * PlayerId with myPlayerId.
* *
* @param players The list of players to search through, which may include the current player and their opponents. * @param players The list of players to search through, which may include the current player
* @return The index of the current player in the list if found, or -1 if the current player is not present in the * and their opponents.
* list or if the list is null or empty. * @return The index of the current player in the list if found, or -1 if the current player is
* not present in the list or if the list is null or empty.
*/ */
private int findCurrentPlayerIndex(List<Player> players) { private int findCurrentPlayerIndex(List<Player> players) {
if (players == null || players.isEmpty()) { if (players == null || players.isEmpty()) {
@@ -1481,9 +1483,7 @@ public class CasinoGameController {
} }
} }
/** /** Apply styling to the myDealerIcon ImageView. */
* Apply styling to the myDealerIcon ImageView.
*/
private void styleMyDealerIcon() { private void styleMyDealerIcon() {
if (myDealerIcon == null || playerCardsBox == null) { if (myDealerIcon == null || playerCardsBox == null) {
return; return;
@@ -1508,9 +1508,11 @@ public class CasinoGameController {
} }
/** /**
* Bind the size of the myDealerIcon ImageView to the dimensions of the scene using predefined ratios. * Bind the size of the myDealerIcon ImageView to the dimensions of the scene using predefined
* ratios.
* *
* @param scene The JavaFX Scene to which the myDealerIcon belongs, used for binding the size properties. * @param scene The JavaFX Scene to which the myDealerIcon belongs, used for binding the size
* properties.
*/ */
private void bindMyDealerIconSize(javafx.scene.Scene scene) { private void bindMyDealerIconSize(javafx.scene.Scene scene) {
if (myDealerIconSizeBound || scene == null || myDealerIcon == null) { if (myDealerIconSizeBound || scene == null || myDealerIcon == null) {
@@ -1689,7 +1691,8 @@ public class CasinoGameController {
* Animate a card hover effect by scaling and lifting the card when hovered and resetting it * Animate a card hover effect by scaling and lifting the card when hovered and resetting it
* when not hovered. * when not hovered.
* *
* However, we have removed the hover animations from the cards and the pot for improved handling and a more stable Game UI. * <p>However, we have removed the hover animations from the cards and the pot for improved
* handling and a more stable Game UI.
* *
* @param view The ImageView representing the card to animate on hover. * @param view The ImageView representing the card to animate on hover.
* @param hover A boolean indicating whether the card is being hovered (true) or not (false). * @param hover A boolean indicating whether the card is being hovered (true) or not (false).
@@ -1834,8 +1837,8 @@ public class CasinoGameController {
/** /**
* Create a new HBox to represent a row of chips in the pot display, with predefined horizontal. * Create a new HBox to represent a row of chips in the pot display, with predefined horizontal.
* *
* @return A new HBox instance configured for displaying a row of chips in the pot, * @return A new HBox instance configured for displaying a row of chips in the pot, with
* with appropriate spacing and alignment. * appropriate spacing and alignment.
*/ */
private HBox createPotRow() { private HBox createPotRow() {
HBox row = new HBox(POT_CHIP_H_GAP); HBox row = new HBox(POT_CHIP_H_GAP);
@@ -1844,7 +1847,8 @@ public class CasinoGameController {
} }
/** /**
* Calculate the number of chips that can be displayed per row in the pot based on the available width. * Calculate the number of chips that can be displayed per row in the pot based on the available
* width.
* *
* @return The calculated number of chips that can fit in a single row of the pot display. * @return The calculated number of chips that can fit in a single row of the pot display.
*/ */
@@ -1994,8 +1998,8 @@ public class CasinoGameController {
} }
/** /**
* Render the opponents in the lobby based on the list of lobby player names, but only if the game * Render the opponents in the lobby based on the list of lobby player names, but only if the
* service is not yet initialized or if there is no game state available. * game service is not yet initialized or if there is no game state available.
*/ */
private void renderLobbyOpponentsIfNeeded() { private void renderLobbyOpponentsIfNeeded() {
if (gameService != null && gameService.peekStateOrNull() != null) { if (gameService != null && gameService.peekStateOrNull() != null) {
@@ -2036,8 +2040,8 @@ public class CasinoGameController {
* which will be updated with the opponent's name if available. * which will be updated with the opponent's name if available.
* @param opponents The list of opponent names currently in the lobby, which may be null or * @param opponents The list of opponent names currently in the lobby, which may be null or
* contain fewer entries than the index. * contain fewer entries than the index.
* @param index The index of the opponent in the opponents list to be displayed in the given slot, where * @param index The index of the opponent in the opponents list to be displayed in the given
* 0 corresponds to the first opponent, 1 to the second, and so on. * slot, where 0 corresponds to the first opponent, 1 to the second, and so on.
*/ */
private void setLobbyOpponent(PlayerStatusController slot, List<String> opponents, int index) { private void setLobbyOpponent(PlayerStatusController slot, List<String> opponents, int index) {
if (slot == null) { if (slot == null) {
@@ -2057,11 +2061,13 @@ public class CasinoGameController {
} }
/** /**
* Determine if the given Player object represents the current player based on the * Determine if the given Player object represents the current player based on the player's name
* player's name and ID compared to the stored myPlayerName and myPlayerId values. * and ID compared to the stored myPlayerName and myPlayerId values.
* *
* @param player The Player object to check, which may be null or contain name and ID information. * @param player The Player object to check, which may be null or contain name and ID
* @return true if the given Player is identified as the current player based on name or ID matching, false otherwise. * information.
* @return true if the given Player is identified as the current player based on name or ID
* matching, false otherwise.
*/ */
private boolean isCurrentPlayer(Player player) { private boolean isCurrentPlayer(Player player) {
if (player == null) { if (player == null) {
@@ -2077,10 +2083,13 @@ public class CasinoGameController {
} }
/** /**
* Normalize a player name or identifier by trimming whitespace and converting to lowercase, returning null if the result is empty. * Normalize a player name or identifier by trimming whitespace and converting to lowercase,
* returning null if the result is empty.
* *
* @param value The raw player name or identifier to normalize, which may be null or contain leading/trailing whitespace. * @param value The raw player name or identifier to normalize, which may be null or contain
* @return The normalized identifier in lowercase without leading/trailing whitespace, or null if the input is null or empty after trimming. * leading/trailing whitespace.
* @return The normalized identifier in lowercase without leading/trailing whitespace, or null
* if the input is null or empty after trimming.
*/ */
private String normalizeIdentifier(String value) { private String normalizeIdentifier(String value) {
if (value == null) { if (value == null) {
@@ -13,10 +13,8 @@ import org.apache.logging.log4j.Logger;
/** /**
* Controller for the movable notebook (Tips) display within the Casono Game UI. * Controller for the movable notebook (Tips) display within the Casono Game UI.
* *
* <p>Responsible for: * <p>Responsible for: - Drag-and-drop movement of the notebook, - Display of general gaming tips, -
* - Drag-and-drop movement of the notebook, * Toggle visibility of the notebook.
* - Display of general gaming tips,
* - Toggle visibility of the notebook.
*/ */
public class NotebookController { public class NotebookController {
@@ -162,9 +162,7 @@ public class SettingsController {
this.themeChangeListener = listener; this.themeChangeListener = listener;
} }
/** /** Show the settings box by setting its visibility and managed properties to true. */
* Show the settings box by setting its visibility and managed properties to true.
*/
public void show() { public void show() {
if (settingsBox == null) { if (settingsBox == null) {
return; return;
@@ -1559,8 +1559,8 @@ public class TaskbarController {
/** /**
* Finds the current player in the given game state based on the player's name or ID. * Finds the current player in the given game state based on the player's name or ID.
* *
* @param state The current GameState object representing the state of the game, * @param state The current GameState object representing the state of the game, which includes
* which includes a list of players and their details. * a list of players and their details.
* @return The Player object representing the current player if found in the game state. * @return The Player object representing the current player if found in the game state.
*/ */
private Player findCurrentPlayer(GameState state) { private Player findCurrentPlayer(GameState state) {
@@ -1574,9 +1574,10 @@ public class TaskbarController {
/** /**
* Checks if the given player matches the current player's identity based on name or ID. * Checks if the given player matches the current player's identity based on name or ID.
* *
* @param player The Player object to check against the current player's identity, * @param player The Player object to check against the current player's identity, which
* which includes the player's name and ID. * includes the player's name and ID.
* @return A boolean value indicating whether the given player matches the current player's identity. * @return A boolean value indicating whether the given player matches the current player's
* identity.
*/ */
private boolean isCurrentPlayer(Player player) { private boolean isCurrentPlayer(Player player) {
if (player == null) { if (player == null) {
@@ -1592,10 +1593,12 @@ public class TaskbarController {
} }
/** /**
* Normalizes a player identifier (name or ID) by trimming whitespace and converting to lowercase. * Normalizes a player identifier (name or ID) by trimming whitespace and converting to
* lowercase.
* *
* @param value The string value representing a player identifier, such as a name or ID. * @param value The string value representing a player identifier, such as a name or ID.
* @return A normalized version of the player identifier, where leading and trailing whitespace is removed. * @return A normalized version of the player identifier, where leading and trailing whitespace
* is removed.
*/ */
private String normalizeIdentifier(String value) { private String normalizeIdentifier(String value) {
if (value == null) { if (value == null) {
@@ -1653,9 +1656,7 @@ public class TaskbarController {
} }
} }
/** /** Opens the integrated Casono Web Browser. */
* Opens the integrated Casono Web Browser.
*/
@FXML @FXML
private void onBrowserButtonClickWiki() { private void onBrowserButtonClickWiki() {
try { try {
@@ -1667,9 +1668,7 @@ public class TaskbarController {
} }
} }
/** /** Opens the integrated Casono Web Browser. */
* Opens the integrated Casono Web Browser.
*/
@FXML @FXML
private void onBrowserButtonClickBrave() { private void onBrowserButtonClickBrave() {
try { try {
@@ -77,7 +77,8 @@ public class GetGameStateResponse extends SuccessResponse {
return builder.build(); return builder.build();
} }
private static void appendWinnerToHighscoresIfFinished(GameState state, List<String> winnerNames) { private static void appendWinnerToHighscoresIfFinished(
GameState state, List<String> winnerNames) {
if (winnerNames == null || winnerNames.isEmpty()) { if (winnerNames == null || winnerNames.isEmpty()) {
return; return;
} }
@@ -139,9 +140,7 @@ public class GetGameStateResponse extends SuccessResponse {
return -1; return -1;
} }
/** /** Computes the list of all winner names at showdown/finished phase. */
* Computes the list of all winner names at showdown/finished phase.
*/
private static List<String> computeWinnerNames(GameState state, GameController game) { private static List<String> computeWinnerNames(GameState state, GameController game) {
GamePhase phase = state.getPhase(); GamePhase phase = state.getPhase();
if (phase != GamePhase.SHOWDOWN && phase != GamePhase.FINISHED) { if (phase != GamePhase.SHOWDOWN && phase != GamePhase.FINISHED) {
@@ -160,9 +159,7 @@ public class GetGameStateResponse extends SuccessResponse {
return winnerNames; return winnerNames;
} }
/** /** Computes the pot share per winner. */
* Computes the pot share per winner.
*/
private static int computePotPerWinner(GameState state, int numWinners) { private static int computePotPerWinner(GameState state, int numWinners) {
if (numWinners <= 0 || state.getPot() == null) { if (numWinners <= 0 || state.getPot() == null) {
return 0; return 0;
@@ -204,8 +204,8 @@ public class HandEvaluator {
} }
/** /**
* Helper method to check for a four of a kind hand rank. * Helper method to check for a four of a kind hand rank. Stores the quad rank and 1 kicker for
* Stores the quad rank and 1 kicker for tie-breaking. * tie-breaking.
* *
* @param rankCount A map where the key is the card rank and the value is the count of * @param rankCount A map where the key is the card rank and the value is the count of
* occurrences. * occurrences.
@@ -265,15 +265,16 @@ public class HandEvaluator {
} }
/** /**
* Helper method to check for a three of a kind hand rank. * Helper method to check for a three of a kind hand rank. Stores the trips rank and 2 kickers
* Stores the trips rank and 2 kickers for tie-breaking. * for tie-breaking.
* *
* @param rankCount A map where the key is the card rank and the value is the count of * @param rankCount A map where the key is the card rank and the value is the count of
* occurrences. * occurrences.
* @param allRanks A sorted list of all card ranks in the hand. * @param allRanks A sorted list of all card ranks in the hand.
* @return A HandRank object representing the three of a kind hand rank, or null if not found. * @return A HandRank object representing the three of a kind hand rank, or null if not found.
*/ */
private static HandRank checkThreeOfAKind(Map<Integer, Long> rankCount, List<Integer> allRanks) { private static HandRank checkThreeOfAKind(
Map<Integer, Long> rankCount, List<Integer> allRanks) {
if (!rankCount.containsValue(THREE_OF_A_KIND_COUNT)) { if (!rankCount.containsValue(THREE_OF_A_KIND_COUNT)) {
return null; return null;
} }
@@ -288,9 +289,8 @@ public class HandEvaluator {
} }
/** /**
* Helper method to check for a two pair hand rank. * Helper method to check for a two pair hand rank. Stores the two pair ranks and 1 kicker for
* Stores the two pair ranks and 1 kicker for tie-breaking. * tie-breaking. The kicker is the highest remaining card that is not part of either pair.
* The kicker is the highest remaining card that is not part of either pair.
* *
* @param rankCount A map where the key is the card rank and the value is the count of * @param rankCount A map where the key is the card rank and the value is the count of
* occurrences. * occurrences.
@@ -322,8 +322,8 @@ public class HandEvaluator {
} }
/** /**
* Helper method to check for a one pair hand rank. * Helper method to check for a one pair hand rank. Stores the pair rank and 3 kickers for
* Stores the pair rank and 3 kickers for tie-breaking. * tie-breaking.
* *
* @param rankCount A map where the key is the card rank and the value is the count of * @param rankCount A map where the key is the card rank and the value is the count of
* occurrences. * occurrences.
@@ -364,8 +364,8 @@ public class HandEvaluator {
} }
/** /**
* Helper method to extract kickers from the list of all card ranks. * Helper method to extract kickers from the list of all card ranks. Excludes the ranks already
* Excludes the ranks already used in the main hand combination. * used in the main hand combination.
* *
* @param allRanks A sorted list of all card ranks in the hand. * @param allRanks A sorted list of all card ranks in the hand.
* @param excludeRanks A list of ranks to exclude (e.g., ranks used in pairs, trips, etc.). * @param excludeRanks A list of ranks to exclude (e.g., ranks used in pairs, trips, etc.).
@@ -61,7 +61,8 @@ public class CardsSpeakRule implements Rule {
for (Player player : players) { for (Player player : players) {
// Use folded flag from game flow to stay consistent with winner calculation in controller. // Use folded flag from game flow to stay consistent with winner calculation in
// controller.
if (player.isFolded()) { if (player.isFolded()) {
continue; continue;
} }
@@ -16,8 +16,8 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.evaluator.HandEvaluato
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.evaluator.HandRank; import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.evaluator.HandRank;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player; import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.showdown.CardsSpeakRule;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine; import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.showdown.CardsSpeakRule;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GamePhase; import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GamePhase;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState; import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
import java.util.ArrayList; import java.util.ArrayList;
@@ -657,12 +657,14 @@ public class GameControllerTest {
assertEquals("PlayerA", winners.get(0).getName()); assertEquals("PlayerA", winners.get(0).getName());
assertEquals("PlayerB", winners.get(1).getName()); assertEquals("PlayerB", winners.get(1).getName());
int chipsBeforeA = state.getPlayers().stream() int chipsBeforeA =
state.getPlayers().stream()
.filter(p -> p.getId().equals(PlayerId.of("PlayerA"))) .filter(p -> p.getId().equals(PlayerId.of("PlayerA")))
.findFirst() .findFirst()
.orElseThrow() .orElseThrow()
.getChips(); .getChips();
int chipsBeforeB = state.getPlayers().stream() int chipsBeforeB =
state.getPlayers().stream()
.filter(p -> p.getId().equals(PlayerId.of("PlayerB"))) .filter(p -> p.getId().equals(PlayerId.of("PlayerB")))
.findFirst() .findFirst()
.orElseThrow() .orElseThrow()
@@ -671,11 +673,13 @@ public class GameControllerTest {
state.getPot().add(1000); state.getPot().add(1000);
showdown.awardPot(state); showdown.awardPot(state);
Player playerA = state.getPlayers().stream() Player playerA =
state.getPlayers().stream()
.filter(p -> p.getId().equals(PlayerId.of("PlayerA"))) .filter(p -> p.getId().equals(PlayerId.of("PlayerA")))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
Player playerB = state.getPlayers().stream() Player playerB =
state.getPlayers().stream()
.filter(p -> p.getId().equals(PlayerId.of("PlayerB"))) .filter(p -> p.getId().equals(PlayerId.of("PlayerB")))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();