Merge branch 'feat/game-ui-optimization' into 'main'
Feat: Game UI optimization See merge request cs108-fs26/Gruppe-13!186
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||||
|
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.GameClient;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.GameClient;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -11,6 +13,9 @@ public class GameService {
|
|||||||
|
|
||||||
private final GameClient client;
|
private final GameClient client;
|
||||||
private GameState state;
|
private GameState state;
|
||||||
|
private final Queue<Runnable> actionQueue = new LinkedList<>();
|
||||||
|
private volatile boolean actionInProgress = false;
|
||||||
|
private static final long ACTION_TIMEOUT_MS = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a GameService with the specified GameClient.
|
* Constructs a GameService with the specified GameClient.
|
||||||
@@ -112,12 +117,12 @@ public class GameService {
|
|||||||
|
|
||||||
/** Retrieves the current phase of the game. */
|
/** Retrieves the current phase of the game. */
|
||||||
public void call() {
|
public void call() {
|
||||||
client.sendCall();
|
queueAction(() -> client.sendCall());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieves the current phase of the game. */
|
/** Retrieves the current phase of the game. */
|
||||||
public void fold() {
|
public void fold() {
|
||||||
client.sendFold();
|
queueAction(() -> client.sendFold());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,7 +131,7 @@ public class GameService {
|
|||||||
* @param amount The amount to bet. Must be a positive integer.
|
* @param amount The amount to bet. Must be a positive integer.
|
||||||
*/
|
*/
|
||||||
public void bet(int amount) {
|
public void bet(int amount) {
|
||||||
client.sendBet(amount);
|
queueAction(() -> client.sendBet(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,7 +140,63 @@ public class GameService {
|
|||||||
* @param amount The amount to raise. Must be a positive integer.
|
* @param amount The amount to raise. Must be a positive integer.
|
||||||
*/
|
*/
|
||||||
public void raise(int amount) {
|
public void raise(int amount) {
|
||||||
client.sendRaise(amount);
|
queueAction(() -> client.sendRaise(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue an action to be executed sequentially. Only one action is processed at a time to
|
||||||
|
* prevent concurrent operations from overwhelming the server or UI.
|
||||||
|
*
|
||||||
|
* @param action The action to queue and execute.
|
||||||
|
*/
|
||||||
|
private void queueAction(Runnable action) {
|
||||||
|
synchronized (actionQueue) {
|
||||||
|
actionQueue.offer(action);
|
||||||
|
if (!actionInProgress) {
|
||||||
|
processNextAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the next queued action if one is available. This method ensures that only one action
|
||||||
|
* is in progress at any given time.
|
||||||
|
*/
|
||||||
|
private void processNextAction() {
|
||||||
|
synchronized (actionQueue) {
|
||||||
|
if (actionQueue.isEmpty() || actionInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionInProgress = true;
|
||||||
|
Runnable action = actionQueue.poll();
|
||||||
|
|
||||||
|
if (action != null) {
|
||||||
|
try {
|
||||||
|
LOG.fine("Processing queued action");
|
||||||
|
action.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.WARNING, "Error executing queued action: " + e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
actionInProgress = false;
|
||||||
|
if (!actionQueue.isEmpty()) {
|
||||||
|
processNextAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actionInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an action is currently in progress, preventing the UI from accepting new actions
|
||||||
|
* while one is being processed.
|
||||||
|
*
|
||||||
|
* @return true if an action is in progress, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isActionInProgress() {
|
||||||
|
return actionInProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ensures that the game state has been initialized before accessing it. */
|
/** Ensures that the game state has been initialized before accessing it. */
|
||||||
|
|||||||
@@ -92,19 +92,39 @@ public class GameClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sendCall() {
|
public void sendCall() {
|
||||||
client.processCommand("CALL GAME_ID=" + gameId);
|
try {
|
||||||
|
client.processCommand("CALL GAME_ID=" + gameId);
|
||||||
|
LOG.fine("CALL command sent");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to send CALL: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendFold() {
|
public void sendFold() {
|
||||||
client.processCommand("FOLD GAME_ID=" + gameId);
|
try {
|
||||||
|
client.processCommand("FOLD GAME_ID=" + gameId);
|
||||||
|
LOG.fine("FOLD command sent");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to send FOLD: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendBet(int amount) {
|
public void sendBet(int amount) {
|
||||||
client.processCommand("BET GAME_ID=" + gameId + " AMOUNT=" + amount);
|
try {
|
||||||
|
client.processCommand("BET GAME_ID=" + gameId + " AMOUNT=" + amount);
|
||||||
|
LOG.fine("BET command sent: amount=" + amount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to send BET: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendRaise(int amount) {
|
public void sendRaise(int amount) {
|
||||||
client.processCommand("RAISE GAME_ID=" + gameId + " AMOUNT=" + amount);
|
try {
|
||||||
|
client.processCommand("RAISE GAME_ID=" + gameId + " AMOUNT=" + amount);
|
||||||
|
LOG.fine("RAISE command sent: amount=" + amount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to send RAISE: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GameState parseGameState(String input) {
|
private GameState parseGameState(String input) {
|
||||||
|
|||||||
+74
-14
@@ -23,6 +23,9 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import javafx.animation.Animation;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
@@ -33,6 +36,7 @@ import javafx.scene.image.ImageView;
|
|||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the casino gaming area.
|
* Controller for the casino gaming area.
|
||||||
@@ -122,6 +126,8 @@ public class CasinoGameController {
|
|||||||
private static final int DEALER_PLAYER_3 = 2;
|
private static final int DEALER_PLAYER_3 = 2;
|
||||||
private static final int DEALER_MYSELF = 3;
|
private static final int DEALER_MYSELF = 3;
|
||||||
private static final double CARD_START_OPACITY = 0.0;
|
private static final double CARD_START_OPACITY = 0.0;
|
||||||
|
private static final int TARGET_FPS = 30;
|
||||||
|
private static final double FRAME_TIME_MS = 1000.0 / TARGET_FPS;
|
||||||
private static final double CARD_START_SCALE = 0.85;
|
private static final double CARD_START_SCALE = 0.85;
|
||||||
private static final double COMMUNITY_CARD_HEIGHT_RATIO = 0.22;
|
private static final double COMMUNITY_CARD_HEIGHT_RATIO = 0.22;
|
||||||
private static final double COMMUNITY_CARD_WIDTH_RATIO = 0.11;
|
private static final double COMMUNITY_CARD_WIDTH_RATIO = 0.11;
|
||||||
@@ -149,16 +155,17 @@ public class CasinoGameController {
|
|||||||
private static final double CHIP_TRANSLATE_RESET_Y = 0.0;
|
private static final double CHIP_TRANSLATE_RESET_Y = 0.0;
|
||||||
private static final double CHIP_FADE_TO = 1.0;
|
private static final double CHIP_FADE_TO = 1.0;
|
||||||
private static final double CHIP_SCALE_TO = 1.0;
|
private static final double CHIP_SCALE_TO = 1.0;
|
||||||
private static final long CHIP_FADE_DURATION_MS = 200;
|
private static final long CHIP_FADE_DURATION_MS = 100;
|
||||||
private static final long CHIP_SCALE_DURATION_MS = 220;
|
private static final long CHIP_SCALE_DURATION_MS = 110;
|
||||||
private static final long CHIP_DROP_DURATION_MS = 250;
|
private static final long CHIP_DROP_DURATION_MS = 120;
|
||||||
private static final long CHIP_STAGGER_DELAY_MULTIPLIER = 35L;
|
private static final long CHIP_STAGGER_DELAY_MULTIPLIER = 15L;
|
||||||
private static final int MAX_POT_ROWS = 2;
|
private static final int MAX_POT_ROWS = 2;
|
||||||
private static final double POT_CHIP_H_GAP = 6.0;
|
private static final double POT_CHIP_H_GAP = 6.0;
|
||||||
private static final double POT_CHIP_V_GAP = 6.0;
|
private static final double POT_CHIP_V_GAP = 6.0;
|
||||||
private static final double POT_WIDTH_FACTOR = 0.45;
|
private static final double POT_WIDTH_FACTOR = 0.45;
|
||||||
private static final double FALLBACK_POT_WIDTH = 500.0;
|
private static final double FALLBACK_POT_WIDTH = 500.0;
|
||||||
private static final double FALLBACK_CHIP_WIDTH = 36.0;
|
private static final double FALLBACK_CHIP_WIDTH = 36.0;
|
||||||
|
private volatile boolean updating = false;
|
||||||
private static final double CHAT_WIDTH = 400;
|
private static final double CHAT_WIDTH = 400;
|
||||||
private static final double CHAT_HEIGHT = 600;
|
private static final double CHAT_HEIGHT = 600;
|
||||||
private static final String ACTIVE_CARD_BOX_STYLE_CLASS = "player-cards-active-turn";
|
private static final String ACTIVE_CARD_BOX_STYLE_CLASS = "player-cards-active-turn";
|
||||||
@@ -178,6 +185,7 @@ public class CasinoGameController {
|
|||||||
private static final int NO_BET = 0;
|
private static final int NO_BET = 0;
|
||||||
private boolean myDealerIconSizeBound;
|
private boolean myDealerIconSizeBound;
|
||||||
private java.util.List<String> lobbyPlayerNames = java.util.List.of();
|
private java.util.List<String> lobbyPlayerNames = java.util.List.of();
|
||||||
|
private static final int MAX_ANIMATED_CHIPS = 12;
|
||||||
|
|
||||||
/** Standard constructor. Used by FXML. */
|
/** Standard constructor. Used by FXML. */
|
||||||
public CasinoGameController() {
|
public CasinoGameController() {
|
||||||
@@ -498,12 +506,24 @@ public class CasinoGameController {
|
|||||||
private void startLoop() {
|
private void startLoop() {
|
||||||
|
|
||||||
timeline =
|
timeline =
|
||||||
new javafx.animation.Timeline(
|
new Timeline(
|
||||||
new javafx.animation.KeyFrame(
|
new KeyFrame(
|
||||||
javafx.util.Duration.seconds(UI_UPDATE_INTERVAL_SECONDS),
|
Duration.millis(FRAME_TIME_MS),
|
||||||
e -> updateUI()));
|
e -> {
|
||||||
|
if (updating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
timeline.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
updating = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateUI();
|
||||||
|
} finally {
|
||||||
|
updating = false;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
timeline.setCycleCount(Animation.INDEFINITE);
|
||||||
timeline.play();
|
timeline.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,6 +645,12 @@ public class CasinoGameController {
|
|||||||
updateGameInfo(s);
|
updateGameInfo(s);
|
||||||
highlightDealer(s);
|
highlightDealer(s);
|
||||||
updateTaskbar(s);
|
updateTaskbar(s);
|
||||||
|
|
||||||
|
if (s.pot != lastPot) {
|
||||||
|
lastPot = s.pot;
|
||||||
|
renderPot(s.pot);
|
||||||
|
}
|
||||||
|
|
||||||
clearActiveTurnHighlights();
|
clearActiveTurnHighlights();
|
||||||
announceLocalWinIfNeeded(s);
|
announceLocalWinIfNeeded(s);
|
||||||
finishGameUiLoop();
|
finishGameUiLoop();
|
||||||
@@ -644,6 +670,9 @@ public class CasinoGameController {
|
|||||||
highlightDealer(s);
|
highlightDealer(s);
|
||||||
updateTaskbar(s);
|
updateTaskbar(s);
|
||||||
|
|
||||||
|
// Force refresh of money display to handle UI stalls
|
||||||
|
refreshMoneyDisplay();
|
||||||
|
|
||||||
LOGGER.info("myPlayerId=" + myPlayerId);
|
LOGGER.info("myPlayerId=" + myPlayerId);
|
||||||
for (int i = 0; i < players.size(); i++) {
|
for (int i = 0; i < players.size(); i++) {
|
||||||
Player p = players.get(i);
|
Player p = players.get(i);
|
||||||
@@ -903,6 +932,30 @@ public class CasinoGameController {
|
|||||||
controller.update(s, myPlayerName);
|
controller.update(s, myPlayerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force refresh of the money display in the taskbar to ensure it always reflects the current
|
||||||
|
* player's chip count, even during rapid game state changes.
|
||||||
|
*/
|
||||||
|
private void refreshMoneyDisplay() {
|
||||||
|
TaskbarController controller = resolveTaskbarController();
|
||||||
|
if (controller == null || gameService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<Player> players = gameService.getPlayers();
|
||||||
|
if (players != null) {
|
||||||
|
for (Player p : players) {
|
||||||
|
if (isCurrentPlayer(p)) {
|
||||||
|
controller.setMoney(p.getChips());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.warning("Could not refresh money display: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the opponent player slots based on the list of players in the game state. This method
|
* Update the opponent player slots based on the list of players in the game state. This method
|
||||||
* identifies the opponents by comparing their PlayerIds with myPlayerId and updates the
|
* identifies the opponents by comparing their PlayerIds with myPlayerId and updates the
|
||||||
@@ -1829,6 +1882,10 @@ public class CasinoGameController {
|
|||||||
* @param pot The total amount in the pot that needs to be represented with chips on the UI.
|
* @param pot The total amount in the pot that needs to be represented with chips on the UI.
|
||||||
*/
|
*/
|
||||||
private void renderPot(int pot) {
|
private void renderPot(int pot) {
|
||||||
|
if (!javafx.application.Platform.isFxApplicationThread()) {
|
||||||
|
javafx.application.Platform.runLater(() -> renderPot(pot));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
potBox.getChildren().clear();
|
potBox.getChildren().clear();
|
||||||
|
|
||||||
@@ -1869,7 +1926,13 @@ public class CasinoGameController {
|
|||||||
bottomRow.getChildren().add(chip);
|
bottomRow.getChildren().add(chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
animateChipAppear(chip, index);
|
if (index < MAX_ANIMATED_CHIPS) {
|
||||||
|
animateChipAppear(chip, index);
|
||||||
|
} else {
|
||||||
|
chip.setOpacity(CHIP_FADE_TO);
|
||||||
|
chip.setScaleX(CHIP_SCALE_TO);
|
||||||
|
chip.setScaleY(CHIP_SCALE_TO);
|
||||||
|
}
|
||||||
|
|
||||||
remaining -= chipValue;
|
remaining -= chipValue;
|
||||||
index++;
|
index++;
|
||||||
@@ -1961,10 +2024,7 @@ public class CasinoGameController {
|
|||||||
*/
|
*/
|
||||||
private void bindChipSize(ImageView view, javafx.scene.Scene scene) {
|
private void bindChipSize(ImageView view, javafx.scene.Scene scene) {
|
||||||
|
|
||||||
view.fitHeightProperty()
|
view.fitHeightProperty().bind(scene.heightProperty().multiply(CHIP_HEIGHT_RATIO));
|
||||||
.bind(
|
|
||||||
scene.heightProperty().multiply(CHIP_HEIGHT_RATIO) // kleiner als Karten
|
|
||||||
);
|
|
||||||
|
|
||||||
view.fitWidthProperty().bind(scene.widthProperty().multiply(CHIP_WIDTH_RATIO));
|
view.fitWidthProperty().bind(scene.widthProperty().multiply(CHIP_WIDTH_RATIO));
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-10
@@ -112,6 +112,7 @@ public class CasinoBrowserController {
|
|||||||
TRUSTED_DOMAINS.add("mojeek.com");
|
TRUSTED_DOMAINS.add("mojeek.com");
|
||||||
TRUSTED_DOMAINS.add("searx.be");
|
TRUSTED_DOMAINS.add("searx.be");
|
||||||
TRUSTED_DOMAINS.add("startpage.com");
|
TRUSTED_DOMAINS.add("startpage.com");
|
||||||
|
TRUSTED_DOMAINS.add("vscode.dev");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Aliases for common websites. */
|
/** Aliases for common websites. */
|
||||||
@@ -138,7 +139,10 @@ public class CasinoBrowserController {
|
|||||||
Map.entry("gradle", "gradle.org"),
|
Map.entry("gradle", "gradle.org"),
|
||||||
Map.entry("spring", "spring.io"),
|
Map.entry("spring", "spring.io"),
|
||||||
Map.entry("jetbrains", "jetbrains.com"),
|
Map.entry("jetbrains", "jetbrains.com"),
|
||||||
Map.entry("uni", "unibas.ch"));
|
Map.entry("uni", "unibas.ch"),
|
||||||
|
Map.entry("vs", "vscode.dev"),
|
||||||
|
Map.entry("vscode", "vscode.dev"),
|
||||||
|
Map.entry("vs code", "vscode.dev"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves user input into a valid URL.
|
* Resolves user input into a valid URL.
|
||||||
@@ -653,23 +657,22 @@ public class CasinoBrowserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("file".equalsIgnoreCase(scheme)) {
|
if ("file".equalsIgnoreCase(scheme)) {
|
||||||
Path allowed =
|
|
||||||
Paths.get(
|
Path baseDir = Paths.get(System.getProperty("user.dir")).normalize();
|
||||||
System.getProperty("user.dir"),
|
|
||||||
"documents",
|
Path manual = baseDir.resolve("documents/docs/game-engine/manual.html").normalize();
|
||||||
"docs",
|
|
||||||
"game-engine",
|
Path outreach = baseDir.resolve("outreach/index.html").normalize();
|
||||||
"manual.html")
|
|
||||||
.normalize();
|
|
||||||
|
|
||||||
Path requested = Paths.get(uri).normalize();
|
Path requested = Paths.get(uri).normalize();
|
||||||
|
|
||||||
if (requested.equals(allowed)) {
|
if (requested.equals(manual) || requested.equals(outreach)) {
|
||||||
securityLabel.setText("LOCAL OK");
|
securityLabel.setText("LOCAL OK");
|
||||||
engine.load(uri.toString());
|
engine.load(uri.toString());
|
||||||
} else {
|
} else {
|
||||||
securityLabel.setText("BLOCKED");
|
securityLabel.setText("BLOCKED");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+119
-18
@@ -91,6 +91,7 @@ public class TaskbarController {
|
|||||||
private int lastPotSnapshot = 0;
|
private int lastPotSnapshot = 0;
|
||||||
private int lastObservedIncrease = 0;
|
private int lastObservedIncrease = 0;
|
||||||
private int lastRaiseIncrement = 0;
|
private int lastRaiseIncrement = 0;
|
||||||
|
private static final long REFRESH_DELAY_MS = 500;
|
||||||
|
|
||||||
/** Standard constructor. Used by FXML. */
|
/** Standard constructor. Used by FXML. */
|
||||||
public TaskbarController() {
|
public TaskbarController() {
|
||||||
@@ -568,7 +569,17 @@ public class TaskbarController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirstFlopPlayer(state, me)) {
|
// if (isFirstFlopPlayer(state, me)) {
|
||||||
|
// setActionEnabled(betButton, true);
|
||||||
|
// setActionEnabled(callButton, false);
|
||||||
|
// // setActionEnabled(foldButton, false);
|
||||||
|
// setActionEnabled(raiseButton, false);
|
||||||
|
// activateInputField(taskbarInput);
|
||||||
|
// inputActionAllowed = true;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (isFirstPlayerOfPhase(state, me)) {
|
||||||
setActionEnabled(betButton, true);
|
setActionEnabled(betButton, true);
|
||||||
setActionEnabled(callButton, false);
|
setActionEnabled(callButton, false);
|
||||||
// setActionEnabled(foldButton, false);
|
// setActionEnabled(foldButton, false);
|
||||||
@@ -754,6 +765,35 @@ public class TaskbarController {
|
|||||||
return isInitialPreflopBlindLayout(state);
|
return isInitialPreflopBlindLayout(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the first player who can still act when starting from a specific index.
|
||||||
|
*
|
||||||
|
* @param state The current game state.
|
||||||
|
* @param startIndex The index from which the search should begin.
|
||||||
|
* @return The first eligible player index, or -1 if none can act.
|
||||||
|
*/
|
||||||
|
private int findFirstActingPlayerIndex(GameState state, int startIndex) {
|
||||||
|
if (state == null || state.players == null || state.players.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = state.players.size();
|
||||||
|
int normalizedStart = ((startIndex % size) + size) % size;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int candidate = (normalizedStart + i) % size;
|
||||||
|
Player player = state.players.get(candidate);
|
||||||
|
|
||||||
|
if (player != null
|
||||||
|
&& player.getState() != PlayerState.FOLDED
|
||||||
|
&& player.getChips() > 0) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the current player is the first to act on the flop.
|
* Checks if the current player is the first to act on the flop.
|
||||||
*
|
*
|
||||||
@@ -783,19 +823,10 @@ public class TaskbarController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int firstIndex = (state.dealer + 1) % size;
|
int firstIndex = findFirstActingPlayerIndex(state, (state.dealer + 1) % size);
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
if (firstIndex < 0) {
|
||||||
|
return false;
|
||||||
int candidate = (firstIndex + i) % size;
|
|
||||||
|
|
||||||
Player p = state.players.get(candidate);
|
|
||||||
|
|
||||||
if (p != null && p.getState() != PlayerState.FOLDED && p.getChips() > 0) {
|
|
||||||
|
|
||||||
firstIndex = candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.activePlayer == firstIndex && myIndex == firstIndex;
|
return state.activePlayer == firstIndex && myIndex == firstIndex;
|
||||||
@@ -828,14 +859,19 @@ public class TaskbarController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int dealer = state.dealer;
|
|
||||||
|
|
||||||
int firstIndex;
|
int firstIndex;
|
||||||
|
|
||||||
if (isPreflop(state.phase)) {
|
if (isPreflop(state.phase)) {
|
||||||
firstIndex = (size == 2) ? dealer : (dealer + DEALER_OFFSET) % size;
|
firstIndex =
|
||||||
|
findFirstActingPlayerIndex(
|
||||||
|
state,
|
||||||
|
(size == 2) ? state.dealer : (state.dealer + DEALER_OFFSET) % size);
|
||||||
} else {
|
} else {
|
||||||
firstIndex = (dealer + 1) % size;
|
firstIndex = findFirstActingPlayerIndex(state, (state.dealer + 1) % size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstIndex < 0) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.activePlayer == firstIndex && myIndex == firstIndex;
|
return state.activePlayer == firstIndex && myIndex == firstIndex;
|
||||||
@@ -989,6 +1025,11 @@ public class TaskbarController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gameService.isActionInProgress()) {
|
||||||
|
LOGGER.info("Action {} blocked: another action is in progress", actionType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GameState state = ensureLatestStateForAction(actionType.name().toLowerCase());
|
GameState state = ensureLatestStateForAction(actionType.name().toLowerCase());
|
||||||
if (state == null || isHandFinished(state)) {
|
if (state == null || isHandFinished(state)) {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
@@ -1014,6 +1055,8 @@ public class TaskbarController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableAllActionButtons();
|
||||||
|
|
||||||
executeAction(state, targetBet, actionType);
|
executeAction(state, targetBet, actionType);
|
||||||
|
|
||||||
if (targetBet > 0) {
|
if (targetBet > 0) {
|
||||||
@@ -1021,7 +1064,35 @@ public class TaskbarController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
taskbarInput.clear();
|
taskbarInput.clear();
|
||||||
refreshGame();
|
|
||||||
|
javafx.application.Platform.runLater(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(REFRESH_DELAY_MS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
refreshGame();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disables all action buttons to prevent multiple concurrent action submissions. */
|
||||||
|
private void disableAllActionButtons() {
|
||||||
|
if (betButton != null) {
|
||||||
|
betButton.setDisable(true);
|
||||||
|
}
|
||||||
|
if (callButton != null) {
|
||||||
|
callButton.setDisable(true);
|
||||||
|
}
|
||||||
|
if (raiseButton != null) {
|
||||||
|
raiseButton.setDisable(true);
|
||||||
|
}
|
||||||
|
if (foldButton != null) {
|
||||||
|
foldButton.setDisable(true);
|
||||||
|
}
|
||||||
|
if (taskbarInput != null) {
|
||||||
|
taskbarInput.setDisable(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1791,6 +1862,24 @@ public class TaskbarController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the integrated Casono web browser.
|
||||||
|
*
|
||||||
|
* <p>once the content for strategies and support is available.
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void onBrowserButtonClickCasono() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
|
try {
|
||||||
|
Path path = Paths.get(System.getProperty("user.dir"), "outreach", "index.html");
|
||||||
|
|
||||||
|
CasinoBrowserController.open(path.toUri().toString());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Opens the integrated Casono Web Browser. */
|
/** Opens the integrated Casono Web Browser. */
|
||||||
@FXML
|
@FXML
|
||||||
private void onBrowserButtonClickWiki() {
|
private void onBrowserButtonClickWiki() {
|
||||||
@@ -1815,6 +1904,18 @@ public class TaskbarController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Opens the integrated Casono Web Browser. */
|
||||||
|
@FXML
|
||||||
|
private void onBrowserButtonClickVSCode() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
CasinoBrowserController.open("https://vscode.dev/");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Opens the tips notebook if it's currently closed. */
|
/** Opens the tips notebook if it's currently closed. */
|
||||||
@FXML
|
@FXML
|
||||||
private void onShowTipsButtonClick() {
|
private void onShowTipsButtonClick() {
|
||||||
|
|||||||
@@ -24,9 +24,11 @@
|
|||||||
<MenuButton text="HELP" styleClass="gray-button">
|
<MenuButton text="HELP" styleClass="gray-button">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem text="SHOW TIPS" onAction="#onShowTipsButtonClick" />
|
<MenuItem text="SHOW TIPS" onAction="#onShowTipsButtonClick" />
|
||||||
<MenuItem text="CASONO BROWSER: Manual" onAction="#onBrowserButtonClick" />
|
<MenuItem text="CB: Manual" onAction="#onBrowserButtonClick" />
|
||||||
<MenuItem text="CASONO BROWSER: Wikipedia" onAction="#onBrowserButtonClickWiki" />
|
<MenuItem text="CB: Casono" onAction="#onBrowserButtonClickCasono" />
|
||||||
<MenuItem text="CASONO BROWSER: Brave Search" onAction="#onBrowserButtonClickBrave" />
|
<MenuItem text="CB: Wikipedia" onAction="#onBrowserButtonClickWiki" />
|
||||||
|
<MenuItem text="CB: Brave Search" onAction="#onBrowserButtonClickBrave" />
|
||||||
|
<MenuItem text="CB: VS Code" onAction="#onBrowserButtonClickVSCode" />
|
||||||
</items>
|
</items>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user