Feat: New Game UI Features and bug fixed #308
+100
-22
@@ -1,6 +1,8 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
||||||
|
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Card;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.Card;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
||||||
@@ -142,14 +144,18 @@ public class CasinoGameController {
|
|||||||
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";
|
||||||
private static final int DEALER_OFFSET = 3;
|
private static final int DEALER_OFFSET = 3;
|
||||||
private static final String FIRST_PREFLOP_INFO_TEMPLATE =
|
private static final String FIRST_PREFLOP_INFO_TEMPLATE =
|
||||||
"Set the big blind amount: %d$ or %d$";
|
"Bet the big blind amount: %d$ or +%d$";
|
||||||
|
|
||||||
private String chatUsername;
|
private String chatUsername;
|
||||||
private ClientService chatClientService;
|
private ClientService chatClientService;
|
||||||
private int chatLobbyId = -1;
|
private int chatLobbyId = -1;
|
||||||
private boolean chatInitialized;
|
private boolean chatInitialized;
|
||||||
private boolean gameFinished;
|
private boolean gameFinished;
|
||||||
|
private boolean localWinAnnouncementSent;
|
||||||
private String lastWinnerName;
|
private String lastWinnerName;
|
||||||
|
private static final int SMALL_BLIND = 100;
|
||||||
|
private static final int BIG_BLIND = 200;
|
||||||
|
private static final int NO_BET = 0;
|
||||||
|
|
||||||
/** Standard constructor. Used by FXML. */
|
/** Standard constructor. Used by FXML. */
|
||||||
public CasinoGameController() {
|
public CasinoGameController() {
|
||||||
@@ -201,11 +207,18 @@ public class CasinoGameController {
|
|||||||
if (controller != null && myPlayerId != null) {
|
if (controller != null && myPlayerId != null) {
|
||||||
controller.setGameService(gameService, myPlayerId);
|
controller.setGameService(gameService, myPlayerId);
|
||||||
}
|
}
|
||||||
|
configureTaskbarLobbyActionAnnouncements();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the PlayerId of the current player.
|
||||||
|
*
|
||||||
|
* @param chatController The ChatController instance used for managing the chat functionality.
|
||||||
|
*/
|
||||||
public void startChat(ChatController chatController) {
|
public void startChat(ChatController chatController) {
|
||||||
this.chatController = chatController;
|
this.chatController = chatController;
|
||||||
initializeChatIfPossible();
|
initializeChatIfPossible();
|
||||||
|
configureTaskbarLobbyActionAnnouncements();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the PlayerId of the current player. */
|
/** Set the PlayerId of the current player. */
|
||||||
@@ -217,6 +230,7 @@ public class CasinoGameController {
|
|||||||
if (taskbarController != null && gameService != null && myPlayerId != null) {
|
if (taskbarController != null && gameService != null && myPlayerId != null) {
|
||||||
taskbarController.setGameService(gameService, myPlayerId);
|
taskbarController.setGameService(gameService, myPlayerId);
|
||||||
}
|
}
|
||||||
|
configureTaskbarLobbyActionAnnouncements();
|
||||||
|
|
||||||
if (communityCardsBox == null) {
|
if (communityCardsBox == null) {
|
||||||
LOGGER.warning("communityCardsBox is NULL");
|
LOGGER.warning("communityCardsBox is NULL");
|
||||||
@@ -270,6 +284,7 @@ public class CasinoGameController {
|
|||||||
this.chatUsername = username;
|
this.chatUsername = username;
|
||||||
this.chatClientService = clientService;
|
this.chatClientService = clientService;
|
||||||
this.chatLobbyId = lobbyId;
|
this.chatLobbyId = lobbyId;
|
||||||
|
configureTaskbarLobbyActionAnnouncements();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -512,6 +527,7 @@ public class CasinoGameController {
|
|||||||
highlightDealer(s);
|
highlightDealer(s);
|
||||||
updateTaskbar(s);
|
updateTaskbar(s);
|
||||||
clearActiveTurnHighlights();
|
clearActiveTurnHighlights();
|
||||||
|
announceLocalWinIfNeeded(s);
|
||||||
finishGameUiLoop();
|
finishGameUiLoop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -683,11 +699,11 @@ public class CasinoGameController {
|
|||||||
* Returns a context-specific info message for the first player to act preflop.
|
* Returns a context-specific info message for the first player to act preflop.
|
||||||
*
|
*
|
||||||
* @param s The current game state.
|
* @param s The current game state.
|
||||||
* @return An English hint with the big blind amount and its double, or null if the special
|
* @return An English hint with the big blind amount and its double, or null if the special case
|
||||||
* case does not apply.
|
* does not apply.
|
||||||
*/
|
*/
|
||||||
private String resolveFirstPreflopInfo(GameState s) {
|
private String resolveFirstPreflopInfo(GameState s) {
|
||||||
if (s == null || myPlayerId == null || s.players == null || s.players.size() < 2) {
|
if (s == null || s.players == null || s.players.size() < 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,22 +711,9 @@ public class CasinoGameController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int myIndex = -1;
|
|
||||||
for (int i = 0; i < s.players.size(); i++) {
|
|
||||||
Player player = s.players.get(i);
|
|
||||||
if (player != null && myPlayerId.equals(player.getId())) {
|
|
||||||
myIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (myIndex < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int firstIndex =
|
int firstIndex =
|
||||||
(s.players.size() == 2) ? s.dealer : (s.dealer + DEALER_OFFSET) % s.players.size();
|
(s.players.size() == 2) ? s.dealer : (s.dealer + DEALER_OFFSET) % s.players.size();
|
||||||
if (s.activePlayer != firstIndex || myIndex != firstIndex) {
|
if (s.activePlayer != firstIndex) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,7 +727,8 @@ public class CasinoGameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long raiseTarget = (long) callTarget * 2L;
|
long raiseTarget = (long) callTarget * 2L;
|
||||||
int safeRaiseTarget = raiseTarget > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) raiseTarget;
|
int safeRaiseTarget =
|
||||||
|
raiseTarget > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) raiseTarget;
|
||||||
return String.format(FIRST_PREFLOP_INFO_TEMPLATE, callTarget, safeRaiseTarget);
|
return String.format(FIRST_PREFLOP_INFO_TEMPLATE, callTarget, safeRaiseTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,11 +750,11 @@ public class CasinoGameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int bet = Math.max(0, player.getBet());
|
int bet = Math.max(0, player.getBet());
|
||||||
if (bet == 100) {
|
if (bet == SMALL_BLIND) {
|
||||||
sbCount++;
|
sbCount++;
|
||||||
} else if (bet == 200) {
|
} else if (bet == BIG_BLIND) {
|
||||||
bbCount++;
|
bbCount++;
|
||||||
} else if (bet == 0) {
|
} else if (bet == NO_BET) {
|
||||||
zeroCount++;
|
zeroCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1089,6 +1093,39 @@ public class CasinoGameController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a short lobby chat message when the local player wins, but only once per hand.
|
||||||
|
*
|
||||||
|
* @param s The current game state.
|
||||||
|
*/
|
||||||
|
private void announceLocalWinIfNeeded(GameState s) {
|
||||||
|
if (localWinAnnouncementSent || s == null || !isTerminalPhase(s.phase)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player winner = resolveWinner(s);
|
||||||
|
boolean isLocalWinner =
|
||||||
|
winner != null
|
||||||
|
&& winner.getId() != null
|
||||||
|
&& myPlayerId != null
|
||||||
|
&& myPlayerId.equals(winner.getId());
|
||||||
|
|
||||||
|
if (!isLocalWinner && lastWinnerName != null && !lastWinnerName.isBlank()) {
|
||||||
|
String localName =
|
||||||
|
(chatUsername != null && !chatUsername.isBlank())
|
||||||
|
? chatUsername
|
||||||
|
: (winner != null ? winner.getName() : null);
|
||||||
|
isLocalWinner = localName != null && localName.equalsIgnoreCase(lastWinnerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLocalWinner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localWinAnnouncementSent = true;
|
||||||
|
sendLobbyActionMessage("I won");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the game is in a finished state based on the game state and the presence of a
|
* Determine if the game is in a finished state based on the game state and the presence of a
|
||||||
* winner name.
|
* winner name.
|
||||||
@@ -1736,6 +1773,47 @@ public class CasinoGameController {
|
|||||||
if (controller != null && gameService != null && myPlayerId != null) {
|
if (controller != null && gameService != null && myPlayerId != null) {
|
||||||
controller.setGameService(gameService, myPlayerId);
|
controller.setGameService(gameService, myPlayerId);
|
||||||
}
|
}
|
||||||
|
configureTaskbarLobbyActionAnnouncements();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the TaskbarController to announce lobby actions by sending chat messages when
|
||||||
|
* certain actions occur in the game, such as winning a hand.
|
||||||
|
*/
|
||||||
|
private void configureTaskbarLobbyActionAnnouncements() {
|
||||||
|
TaskbarController controller = resolveTaskbarController();
|
||||||
|
if (controller == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.setLobbyActionAnnouncer(this::sendLobbyActionMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a lobby action message to the chat controller with the specified action label, which can
|
||||||
|
* be used to announce game events in the lobby chat.
|
||||||
|
*
|
||||||
|
* @param actionLabel The label describing the action to announce in the lobby chat, such as "I
|
||||||
|
* won" when the local player wins a hand.
|
||||||
|
*/
|
||||||
|
private void sendLobbyActionMessage(String actionLabel) {
|
||||||
|
if (chatController == null
|
||||||
|
|| chatLobbyId < 0
|
||||||
|
|| actionLabel == null
|
||||||
|
|| actionLabel.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String sender =
|
||||||
|
(chatUsername != null && !chatUsername.isBlank())
|
||||||
|
? chatUsername
|
||||||
|
: chatController.getCurrentUsername();
|
||||||
|
if (sender == null || sender.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message message =
|
||||||
|
new Message(ChatType.LOBBY, chatLobbyId, sender, null, actionLabel.trim());
|
||||||
|
chatController.onSendToNetwork(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+314
-29
@@ -11,6 +11,8 @@ import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
@@ -18,6 +20,8 @@ import javafx.scene.control.ButtonBar;
|
|||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.TextFormatter;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
@@ -71,6 +75,11 @@ public class TaskbarController {
|
|||||||
private static final double ALERT_LOGO_HEIGHT = 40.0;
|
private static final double ALERT_LOGO_HEIGHT = 40.0;
|
||||||
private boolean inputActionAllowed;
|
private boolean inputActionAllowed;
|
||||||
private int lastReferenceBet = BIG_BLIND;
|
private int lastReferenceBet = BIG_BLIND;
|
||||||
|
private Consumer<String> lobbyActionAnnouncer;
|
||||||
|
private String lastPhase;
|
||||||
|
private static final int FIRST_PLAYER_MAX_BET = 3000;
|
||||||
|
private static final int MAX_INPUT_LENGTH = 6;
|
||||||
|
private static final double MAX_CHIP_PERCENT = 0.30;
|
||||||
|
|
||||||
/** Standard constructor. Used by FXML. */
|
/** Standard constructor. Used by FXML. */
|
||||||
public TaskbarController() {
|
public TaskbarController() {
|
||||||
@@ -223,6 +232,11 @@ public class TaskbarController {
|
|||||||
this.myPlayerId = myPlayerId;
|
this.myPlayerId = myPlayerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets an optional callback that publishes simple action labels to lobby chat. */
|
||||||
|
public void setLobbyActionAnnouncer(Consumer<String> lobbyActionAnnouncer) {
|
||||||
|
this.lobbyActionAnnouncer = lobbyActionAnnouncer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the taskbar controller. This method is called automatically after the FXML
|
* Initializes the taskbar controller. This method is called automatically after the FXML
|
||||||
* components have been loaded.
|
* components have been loaded.
|
||||||
@@ -230,15 +244,102 @@ public class TaskbarController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
updateBasicButtons(false, false);
|
updateBasicButtons(false, false);
|
||||||
|
|
||||||
if (taskbarInput != null) {
|
if (taskbarInput != null) {
|
||||||
|
|
||||||
|
UnaryOperator<TextFormatter.Change> filter =
|
||||||
|
change -> {
|
||||||
|
String newText = change.getControlNewText();
|
||||||
|
|
||||||
|
if (newText.length() > MAX_INPUT_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newText.matches("\\d*")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return change;
|
||||||
|
};
|
||||||
|
|
||||||
|
taskbarInput.setTextFormatter(new TextFormatter<>(filter));
|
||||||
|
|
||||||
taskbarInput
|
taskbarInput
|
||||||
.textProperty()
|
.textProperty()
|
||||||
.addListener((obs, oldValue, newValue) -> refreshBetInputUi());
|
.addListener((obs, oldValue, newValue) -> refreshBetInputUi());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Tooltip callTooltip = new Tooltip();
|
||||||
|
callButton.setTooltip(callTooltip);
|
||||||
|
callButton.setOnMouseEntered(e -> callTooltip.setText(previewCall()));
|
||||||
|
|
||||||
|
Tooltip raiseTooltip = new Tooltip();
|
||||||
|
raiseButton.setTooltip(raiseTooltip);
|
||||||
|
raiseButton.setOnMouseEntered(e -> raiseTooltip.setText(previewRaise()));
|
||||||
|
|
||||||
setBetButtonVisible(false);
|
setBetButtonVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a preview string for the Call action, showing the amount that would be called based
|
||||||
|
* on the current game state and the player's bet status.
|
||||||
|
*
|
||||||
|
* @return A string representing the Call action preview, including the amount that would be
|
||||||
|
* called, or an empty string if the game state is not available.
|
||||||
|
*/
|
||||||
|
private String previewCall() {
|
||||||
|
if (lastState == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int callTarget = effectiveCallTarget(lastState);
|
||||||
|
Player me = findCurrentPlayer(lastState);
|
||||||
|
|
||||||
|
int alreadyInvested = (me != null) ? Math.max(0, me.getBet()) : 0;
|
||||||
|
int amount = Math.max(0, callTarget - alreadyInvested);
|
||||||
|
|
||||||
|
return "CALL " + amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a preview string for the Raise action, showing the amount that would be raised.
|
||||||
|
*
|
||||||
|
* @return A string representing the Raise action preview, including the amount that would be
|
||||||
|
* raised.
|
||||||
|
*/
|
||||||
|
private String previewRaise() {
|
||||||
|
if (lastState == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// int callTarget = effectiveCallTarget(lastState);
|
||||||
|
// Player me = findCurrentPlayer(lastState);
|
||||||
|
|
||||||
|
// int alreadyInvested = (me != null) ? Math.max(0, me.getBet()) : 0;
|
||||||
|
|
||||||
|
// Integer targetBet = resolveTargetBet(ActionType.RAISE_BUTTON, lastState);
|
||||||
|
// if (targetBet == null) {
|
||||||
|
// return "";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// int totalContribution = Math.max(0, targetBet);
|
||||||
|
|
||||||
|
GameState state = ensureLatestStateForAction(ActionType.CALL_BUTTON.name().toLowerCase());
|
||||||
|
|
||||||
|
// int raiseBy = Math.max(0, targetBet);
|
||||||
|
int raiseBy = effectiveCallTarget(state);
|
||||||
|
|
||||||
|
int callTarget = effectiveCallTarget(lastState);
|
||||||
|
Player me = findCurrentPlayer(lastState);
|
||||||
|
|
||||||
|
int alreadyInvested = (me != null) ? Math.max(0, me.getBet()) : 0;
|
||||||
|
int amount = Math.max(0, callTarget - alreadyInvested);
|
||||||
|
|
||||||
|
int raiseamout = amount + raiseBy;
|
||||||
|
|
||||||
|
return "RAISE +" + raiseBy + " TO " + raiseamout;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the taskbar based on the current game state and the player's status.
|
* Updates the taskbar based on the current game state and the player's status.
|
||||||
*
|
*
|
||||||
@@ -258,6 +359,7 @@ public class TaskbarController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.lastState = state;
|
this.lastState = state;
|
||||||
|
|
||||||
if (state.currentBet > 0) {
|
if (state.currentBet > 0) {
|
||||||
lastReferenceBet = state.currentBet;
|
lastReferenceBet = state.currentBet;
|
||||||
}
|
}
|
||||||
@@ -279,8 +381,14 @@ public class TaskbarController {
|
|||||||
boolean isOut = me.getState() == PlayerState.FOLDED;
|
boolean isOut = me.getState() == PlayerState.FOLDED;
|
||||||
boolean isGameFinished = isHandFinished(state);
|
boolean isGameFinished = isHandFinished(state);
|
||||||
|
|
||||||
|
boolean phaseChanged = lastPhase == null || !lastPhase.equalsIgnoreCase(state.phase);
|
||||||
|
|
||||||
|
lastPhase = state.phase;
|
||||||
|
|
||||||
updateBasicButtons(isMyTurn, isOut || isGameFinished);
|
updateBasicButtons(isMyTurn, isOut || isGameFinished);
|
||||||
applyActionAvailability(state, me, isMyTurn, isOut || isGameFinished);
|
|
||||||
|
applyActionAvailability(state, me, isMyTurn, isOut || isGameFinished, phaseChanged);
|
||||||
|
|
||||||
setMoney(me.getChips());
|
setMoney(me.getChips());
|
||||||
refreshBetInputUi();
|
refreshBetInputUi();
|
||||||
}
|
}
|
||||||
@@ -297,7 +405,14 @@ public class TaskbarController {
|
|||||||
* if the game is finished, which disables actions.
|
* if the game is finished, which disables actions.
|
||||||
*/
|
*/
|
||||||
private void applyActionAvailability(
|
private void applyActionAvailability(
|
||||||
GameState state, Player me, boolean isMyTurn, boolean isOutOrFinished) {
|
GameState state,
|
||||||
|
Player me,
|
||||||
|
boolean isMyTurn,
|
||||||
|
boolean isOutOrFinished,
|
||||||
|
boolean phaseChanged) {
|
||||||
|
|
||||||
|
// boolean firstPlayerNewRound =
|
||||||
|
// phaseChanged && isFirstPreflopPlayerInputOnly(state, me);
|
||||||
inputActionAllowed = false;
|
inputActionAllowed = false;
|
||||||
if (!isMyTurn || isOutOrFinished || state == null || me == null) {
|
if (!isMyTurn || isOutOrFinished || state == null || me == null) {
|
||||||
setBetButtonVisible(false);
|
setBetButtonVisible(false);
|
||||||
@@ -318,7 +433,17 @@ public class TaskbarController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirstPreflopPlayerInputOnly(state, me)) {
|
// if (isFirstPreflopPlayerInputOnly(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);
|
||||||
@@ -363,7 +488,16 @@ public class TaskbarController {
|
|||||||
if (value > Integer.MAX_VALUE / 2) {
|
if (value > Integer.MAX_VALUE / 2) {
|
||||||
return Integer.MAX_VALUE;
|
return Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
return value * 2;
|
|
||||||
|
int callTarget = effectiveCallTarget(lastState);
|
||||||
|
Player me = findCurrentPlayer(lastState);
|
||||||
|
|
||||||
|
int alreadyInvested = (me != null) ? Math.max(0, me.getBet()) : 0;
|
||||||
|
int amount = Math.max(0, callTarget - alreadyInvested);
|
||||||
|
|
||||||
|
int raisevalue = amount + value;
|
||||||
|
|
||||||
|
return raisevalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -441,10 +575,22 @@ public class TaskbarController {
|
|||||||
* @return An integer representing the effective call target for the current game state.
|
* @return An integer representing the effective call target for the current game state.
|
||||||
*/
|
*/
|
||||||
private int effectiveCallTarget(GameState state) {
|
private int effectiveCallTarget(GameState state) {
|
||||||
if (state != null && state.currentBet > 0) {
|
int currentBet = (state != null) ? Math.max(0, state.currentBet) : 0;
|
||||||
return state.currentBet;
|
int rememberedBet = Math.max(0, lastReferenceBet);
|
||||||
|
|
||||||
|
if (state != null && isPreflop(state.phase) && isInitialPreflopBlindLayout(state)) {
|
||||||
|
return currentBet;
|
||||||
}
|
}
|
||||||
return Math.max(0, lastReferenceBet);
|
|
||||||
|
if (currentBet <= 0) {
|
||||||
|
return rememberedBet;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rememberedBet <= 0) {
|
||||||
|
return currentBet;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(currentBet, rememberedBet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -483,6 +629,46 @@ public class TaskbarController {
|
|||||||
return isInitialPreflopBlindLayout(state);
|
return isInitialPreflopBlindLayout(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current player is the first to act in the current phase of the game, based on
|
||||||
|
* the dealer position and the active player index.
|
||||||
|
*
|
||||||
|
* @param state The current GameState object representing the state of the game, which includes
|
||||||
|
* information about
|
||||||
|
* @param me The Player object representing the current player, used to determine their
|
||||||
|
* position.
|
||||||
|
* @return A boolean value indicating whether the current player is the first to act in the
|
||||||
|
* current phase of the game.
|
||||||
|
*/
|
||||||
|
private boolean isFirstPlayerOfPhase(GameState state, Player me) {
|
||||||
|
|
||||||
|
if (state == null || me == null || state.players == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = state.players.size();
|
||||||
|
if (size < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int myIndex = state.players.indexOf(me);
|
||||||
|
if (myIndex < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dealer = state.dealer;
|
||||||
|
|
||||||
|
int firstIndex;
|
||||||
|
|
||||||
|
if (isPreflop(state.phase)) {
|
||||||
|
firstIndex = (size == 2) ? dealer : (dealer + DEALER_OFFSET) % size;
|
||||||
|
} else {
|
||||||
|
firstIndex = (dealer + 1) % size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.activePlayer == firstIndex && myIndex == firstIndex;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the initial blind layout is still in place during the pre-flop phase.
|
* Checks if the initial blind layout is still in place during the pre-flop phase.
|
||||||
*
|
*
|
||||||
@@ -492,6 +678,10 @@ public class TaskbarController {
|
|||||||
* the pre-flop phase.
|
* the pre-flop phase.
|
||||||
*/
|
*/
|
||||||
private boolean isInitialPreflopBlindLayout(GameState state) {
|
private boolean isInitialPreflopBlindLayout(GameState state) {
|
||||||
|
if (state == null || state.players == null || state.players.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int sbCount = 0;
|
int sbCount = 0;
|
||||||
int bbCount = 0;
|
int bbCount = 0;
|
||||||
int zeroCount = 0;
|
int zeroCount = 0;
|
||||||
@@ -652,7 +842,7 @@ public class TaskbarController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
executeAction(state, targetBet);
|
executeAction(state, targetBet, actionType);
|
||||||
|
|
||||||
if (targetBet > 0) {
|
if (targetBet > 0) {
|
||||||
lastReferenceBet = targetBet;
|
lastReferenceBet = targetBet;
|
||||||
@@ -682,7 +872,8 @@ public class TaskbarController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (actionType == ActionType.RAISE_BUTTON) {
|
if (actionType == ActionType.RAISE_BUTTON) {
|
||||||
return safeDouble(callTarget);
|
Integer input = parseInputTarget(taskbarInput.getText());
|
||||||
|
return input != null ? input : callTarget + BIG_BLIND;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseInputTarget(taskbarInput.getText());
|
return parseInputTarget(taskbarInput.getText());
|
||||||
@@ -728,19 +919,78 @@ public class TaskbarController {
|
|||||||
* @param targetBet The integer value representing the target bet amount for the action being
|
* @param targetBet The integer value representing the target bet amount for the action being
|
||||||
* executed, which is used to determine
|
* executed, which is used to determine
|
||||||
*/
|
*/
|
||||||
private void executeAction(GameState state, int targetBet) {
|
private void executeAction(GameState state, int targetBet, ActionType actionType) {
|
||||||
int callTarget = effectiveCallTarget(state);
|
int callTarget = effectiveCallTarget(state);
|
||||||
|
String phase = (state != null ? state.phase : "UNKNOWN");
|
||||||
|
|
||||||
|
int alreadyInvested =
|
||||||
|
(findCurrentPlayer(state) != null)
|
||||||
|
? Math.max(0, findCurrentPlayer(state).getBet())
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (actionType == ActionType.CALL_BUTTON) {
|
||||||
|
|
||||||
|
int amount = Math.max(0, callTarget - alreadyInvested);
|
||||||
|
|
||||||
|
gameService.call();
|
||||||
|
|
||||||
|
LOGGER.info("[{}] CALL +{} (total to {})", phase, amount, callTarget);
|
||||||
|
announceLobbyAction("CALL " + amount);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionType == ActionType.RAISE_BUTTON) {
|
||||||
|
|
||||||
|
int raiseBy = Math.max(0, targetBet - callTarget);
|
||||||
|
|
||||||
|
gameService.raise(raiseBy);
|
||||||
|
|
||||||
|
LOGGER.info("[{}] RAISE +{} (target={})", phase, raiseBy, targetBet);
|
||||||
|
announceLobbyAction("RAISE +" + raiseBy + " TO " + targetBet);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.currentBet <= 0) {
|
if (state.currentBet <= 0) {
|
||||||
|
|
||||||
gameService.bet(targetBet);
|
gameService.bet(targetBet);
|
||||||
LOGGER.info("BET {}", targetBet);
|
|
||||||
} else if (targetBet == callTarget) {
|
LOGGER.info("[{}] BET {}", phase, targetBet);
|
||||||
|
announceLobbyAction("BET " + targetBet);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetBet == callTarget) {
|
||||||
|
|
||||||
|
int amount = Math.max(0, callTarget - alreadyInvested);
|
||||||
|
|
||||||
gameService.call();
|
gameService.call();
|
||||||
LOGGER.info("CALL {}", targetBet);
|
|
||||||
|
LOGGER.info("[{}] CALL +{} (total to {})", phase, amount, callTarget);
|
||||||
|
announceLobbyAction("CALL " + amount);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
int raiseBy = Math.max(0, targetBet - state.currentBet);
|
|
||||||
|
int raiseBy = Math.max(0, targetBet - callTarget);
|
||||||
|
|
||||||
gameService.raise(raiseBy);
|
gameService.raise(raiseBy);
|
||||||
LOGGER.info("RAISE {} (target={})", raiseBy, targetBet);
|
|
||||||
|
LOGGER.info("[{}] RAISE +{} (target={})", phase, raiseBy, targetBet);
|
||||||
|
announceLobbyAction("RAISE +" + raiseBy + " TO " + targetBet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Publishes a simple action label to the lobby chat, if a publisher is configured. */
|
||||||
|
private void announceLobbyAction(String actionLabel) {
|
||||||
|
if (lobbyActionAnnouncer == null || actionLabel == null || actionLabel.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
lobbyActionAnnouncer.accept(actionLabel.trim());
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
LOGGER.warn("Could not publish lobby action '{}': {}", actionLabel, ex.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,8 +1007,15 @@ public class TaskbarController {
|
|||||||
if (text == null || text.trim().isEmpty()) {
|
if (text == null || text.trim().isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return Integer.parseInt(text.trim());
|
int value = Integer.parseInt(text.trim());
|
||||||
|
|
||||||
|
if (value % SMALL_BLIND != 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -803,21 +1060,44 @@ public class TaskbarController {
|
|||||||
* triggers a warning, or if it is blocked due to being invalid or too risky.
|
* triggers a warning, or if it is blocked due to being invalid or too risky.
|
||||||
*/
|
*/
|
||||||
private ValidationResult validateTarget(GameState state, Player me, int targetBet) {
|
private ValidationResult validateTarget(GameState state, Player me, int targetBet) {
|
||||||
|
|
||||||
if (state == null || me == null) {
|
if (state == null || me == null) {
|
||||||
return ValidationResult.blocked("Invalid game state");
|
return ValidationResult.blocked("Invalid game state");
|
||||||
}
|
}
|
||||||
|
|
||||||
int callTarget = effectiveCallTarget(state);
|
final int callTarget = effectiveCallTarget(state);
|
||||||
int raiseTarget = safeDouble(callTarget);
|
|
||||||
if (targetBet != callTarget && targetBet != raiseTarget) {
|
final int alreadyInvested = Math.max(0, me.getBet());
|
||||||
return ValidationResult.blocked("Only calls or exactly two raises allowed");
|
final int chips = Math.max(0, me.getChips());
|
||||||
|
|
||||||
|
final int required = Math.max(0, targetBet - alreadyInvested);
|
||||||
|
|
||||||
|
final int minRequiredToCall = Math.max(0, callTarget - alreadyInvested);
|
||||||
|
|
||||||
|
if (required < minRequiredToCall) {
|
||||||
|
return ValidationResult.blocked("Bet must match at least the call amount");
|
||||||
}
|
}
|
||||||
|
|
||||||
int alreadyInvested = Math.max(0, me.getBet());
|
if (isFirstPlayerOfPhase(state, me)) {
|
||||||
int required = Math.max(0, targetBet - alreadyInvested);
|
if (required > FIRST_PLAYER_MAX_BET) {
|
||||||
int chips = Math.max(0, me.getChips());
|
return ValidationResult.blocked(
|
||||||
|
"First player cannot bet more than " + FIRST_PLAYER_MAX_BET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (targetBet == callTarget && required == 0) {
|
if (isPreflop(state.phase)) {
|
||||||
|
int maxAllowed = (int) Math.floor(me.getChips() * MAX_CHIP_PERCENT);
|
||||||
|
|
||||||
|
if (required > maxAllowed) {
|
||||||
|
return ValidationResult.blocked(
|
||||||
|
"Preflop: You can only bet up to 30% of your stack (" + maxAllowed + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (required == minRequiredToCall) {
|
||||||
|
if (required > chips) {
|
||||||
|
return ValidationResult.blocked("Not enough chips to call");
|
||||||
|
}
|
||||||
return ValidationResult.ok();
|
return ValidationResult.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,16 +1106,19 @@ public class TaskbarController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (required > chips) {
|
if (required > chips) {
|
||||||
return ValidationResult.blocked("Not enough chips for the next bet");
|
return ValidationResult.blocked("Not enough chips for this bet");
|
||||||
}
|
}
|
||||||
|
|
||||||
BetRisk risk = evaluateBetRisk(state, required, chips);
|
BetRisk risk = evaluateBetRisk(state, required, chips);
|
||||||
|
|
||||||
if (risk == BetRisk.BLOCKED) {
|
if (risk == BetRisk.BLOCKED) {
|
||||||
return ValidationResult.blocked("Assignment for this phase is blocked");
|
return ValidationResult.blocked("Bet too risky for this phase");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (risk == BetRisk.WARNING) {
|
if (risk == BetRisk.WARNING) {
|
||||||
return ValidationResult.warning(warningTextForPhase(state));
|
return ValidationResult.warning(warningTextForPhase(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ValidationResult.ok();
|
return ValidationResult.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,8 +1263,6 @@ public class TaskbarController {
|
|||||||
* Called while dragging the taskbar with the mouse. Updates the position and slightly scales
|
* Called while dragging the taskbar with the mouse. Updates the position and slightly scales
|
||||||
* the taskbar for visual feedback.
|
* the taskbar for visual feedback.
|
||||||
*
|
*
|
||||||
* <p>TODO: It still needs to be fixed that the taskbar cannot disappear out of the window.
|
|
||||||
*
|
|
||||||
* @param event Das Mausereignis
|
* @param event Das Mausereignis
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
@@ -1099,7 +1380,7 @@ public class TaskbarController {
|
|||||||
/** Called when the Call button is clicked. */
|
/** Called when the Call button is clicked. */
|
||||||
@FXML
|
@FXML
|
||||||
private void onInputPlayerCall() {
|
private void onInputPlayerCall() {
|
||||||
submitPresetInputAndProcess("call");
|
submitAction(ActionType.CALL_BUTTON);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when the Fold button is clicked. */
|
/** Called when the Fold button is clicked. */
|
||||||
@@ -1120,6 +1401,7 @@ public class TaskbarController {
|
|||||||
|
|
||||||
gameService.fold();
|
gameService.fold();
|
||||||
LOGGER.info("Player FOLD");
|
LOGGER.info("Player FOLD");
|
||||||
|
announceLobbyAction("FOLD");
|
||||||
|
|
||||||
refreshGame();
|
refreshGame();
|
||||||
}
|
}
|
||||||
@@ -1233,6 +1515,9 @@ public class TaskbarController {
|
|||||||
GameState refreshed = gameService.refresh();
|
GameState refreshed = gameService.refresh();
|
||||||
if (refreshed != null) {
|
if (refreshed != null) {
|
||||||
lastState = refreshed;
|
lastState = refreshed;
|
||||||
|
if (refreshed.currentBet > 0 && refreshed.currentBet >= lastReferenceBet) {
|
||||||
|
lastReferenceBet = refreshed.currentBet;
|
||||||
|
}
|
||||||
return refreshed;
|
return refreshed;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user