Feat: New Game UI Features:
- Call, Raise, Fold, Bet and Winner action are displayed in the Lobby Chat. - More security features in the Game UI - The Game UI is now even more Texas Hold'em friendly
This commit is contained in:
+100
-22
@@ -1,6 +1,8 @@
|
||||
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.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.GameService;
|
||||
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 int DEALER_OFFSET = 3;
|
||||
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 ClientService chatClientService;
|
||||
private int chatLobbyId = -1;
|
||||
private boolean chatInitialized;
|
||||
private boolean gameFinished;
|
||||
private boolean localWinAnnouncementSent;
|
||||
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. */
|
||||
public CasinoGameController() {
|
||||
@@ -201,11 +207,18 @@ public class CasinoGameController {
|
||||
if (controller != null && myPlayerId != null) {
|
||||
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) {
|
||||
this.chatController = chatController;
|
||||
initializeChatIfPossible();
|
||||
configureTaskbarLobbyActionAnnouncements();
|
||||
}
|
||||
|
||||
/** Set the PlayerId of the current player. */
|
||||
@@ -217,6 +230,7 @@ public class CasinoGameController {
|
||||
if (taskbarController != null && gameService != null && myPlayerId != null) {
|
||||
taskbarController.setGameService(gameService, myPlayerId);
|
||||
}
|
||||
configureTaskbarLobbyActionAnnouncements();
|
||||
|
||||
if (communityCardsBox == null) {
|
||||
LOGGER.warning("communityCardsBox is NULL");
|
||||
@@ -270,6 +284,7 @@ public class CasinoGameController {
|
||||
this.chatUsername = username;
|
||||
this.chatClientService = clientService;
|
||||
this.chatLobbyId = lobbyId;
|
||||
configureTaskbarLobbyActionAnnouncements();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -512,6 +527,7 @@ public class CasinoGameController {
|
||||
highlightDealer(s);
|
||||
updateTaskbar(s);
|
||||
clearActiveTurnHighlights();
|
||||
announceLocalWinIfNeeded(s);
|
||||
finishGameUiLoop();
|
||||
return;
|
||||
}
|
||||
@@ -683,11 +699,11 @@ public class CasinoGameController {
|
||||
* Returns a context-specific info message for the first player to act preflop.
|
||||
*
|
||||
* @param s The current game state.
|
||||
* @return An English hint with the big blind amount and its double, or null if the special
|
||||
* case does not apply.
|
||||
* @return An English hint with the big blind amount and its double, or null if the special case
|
||||
* does not apply.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -695,22 +711,9 @@ public class CasinoGameController {
|
||||
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 =
|
||||
(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;
|
||||
}
|
||||
|
||||
@@ -724,7 +727,8 @@ public class CasinoGameController {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -746,11 +750,11 @@ public class CasinoGameController {
|
||||
}
|
||||
|
||||
int bet = Math.max(0, player.getBet());
|
||||
if (bet == 100) {
|
||||
if (bet == SMALL_BLIND) {
|
||||
sbCount++;
|
||||
} else if (bet == 200) {
|
||||
} else if (bet == BIG_BLIND) {
|
||||
bbCount++;
|
||||
} else if (bet == 0) {
|
||||
} else if (bet == NO_BET) {
|
||||
zeroCount++;
|
||||
}
|
||||
}
|
||||
@@ -1089,6 +1093,39 @@ public class CasinoGameController {
|
||||
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
|
||||
* winner name.
|
||||
@@ -1736,6 +1773,47 @@ public class CasinoGameController {
|
||||
if (controller != null && gameService != null && myPlayerId != null) {
|
||||
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.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.UnaryOperator;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
@@ -18,6 +20,8 @@ import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
@@ -71,6 +75,11 @@ public class TaskbarController {
|
||||
private static final double ALERT_LOGO_HEIGHT = 40.0;
|
||||
private boolean inputActionAllowed;
|
||||
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. */
|
||||
public TaskbarController() {
|
||||
@@ -223,6 +232,11 @@ public class TaskbarController {
|
||||
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
|
||||
* components have been loaded.
|
||||
@@ -230,15 +244,102 @@ public class TaskbarController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
updateBasicButtons(false, false);
|
||||
|
||||
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
|
||||
.textProperty()
|
||||
.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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -258,6 +359,7 @@ public class TaskbarController {
|
||||
}
|
||||
|
||||
this.lastState = state;
|
||||
|
||||
if (state.currentBet > 0) {
|
||||
lastReferenceBet = state.currentBet;
|
||||
}
|
||||
@@ -279,8 +381,14 @@ public class TaskbarController {
|
||||
boolean isOut = me.getState() == PlayerState.FOLDED;
|
||||
boolean isGameFinished = isHandFinished(state);
|
||||
|
||||
boolean phaseChanged = lastPhase == null || !lastPhase.equalsIgnoreCase(state.phase);
|
||||
|
||||
lastPhase = state.phase;
|
||||
|
||||
updateBasicButtons(isMyTurn, isOut || isGameFinished);
|
||||
applyActionAvailability(state, me, isMyTurn, isOut || isGameFinished);
|
||||
|
||||
applyActionAvailability(state, me, isMyTurn, isOut || isGameFinished, phaseChanged);
|
||||
|
||||
setMoney(me.getChips());
|
||||
refreshBetInputUi();
|
||||
}
|
||||
@@ -297,7 +405,14 @@ public class TaskbarController {
|
||||
* if the game is finished, which disables actions.
|
||||
*/
|
||||
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;
|
||||
if (!isMyTurn || isOutOrFinished || state == null || me == null) {
|
||||
setBetButtonVisible(false);
|
||||
@@ -318,7 +433,17 @@ public class TaskbarController {
|
||||
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(callButton, false);
|
||||
setActionEnabled(foldButton, false);
|
||||
@@ -363,7 +488,16 @@ public class TaskbarController {
|
||||
if (value > Integer.MAX_VALUE / 2) {
|
||||
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.
|
||||
*/
|
||||
private int effectiveCallTarget(GameState state) {
|
||||
if (state != null && state.currentBet > 0) {
|
||||
return state.currentBet;
|
||||
int currentBet = (state != null) ? Math.max(0, state.currentBet) : 0;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -492,6 +678,10 @@ public class TaskbarController {
|
||||
* the pre-flop phase.
|
||||
*/
|
||||
private boolean isInitialPreflopBlindLayout(GameState state) {
|
||||
if (state == null || state.players == null || state.players.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int sbCount = 0;
|
||||
int bbCount = 0;
|
||||
int zeroCount = 0;
|
||||
@@ -652,7 +842,7 @@ public class TaskbarController {
|
||||
return;
|
||||
}
|
||||
|
||||
executeAction(state, targetBet);
|
||||
executeAction(state, targetBet, actionType);
|
||||
|
||||
if (targetBet > 0) {
|
||||
lastReferenceBet = targetBet;
|
||||
@@ -682,7 +872,8 @@ public class TaskbarController {
|
||||
}
|
||||
|
||||
if (actionType == ActionType.RAISE_BUTTON) {
|
||||
return safeDouble(callTarget);
|
||||
Integer input = parseInputTarget(taskbarInput.getText());
|
||||
return input != null ? input : callTarget + BIG_BLIND;
|
||||
}
|
||||
|
||||
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
|
||||
* 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);
|
||||
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) {
|
||||
|
||||
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();
|
||||
LOGGER.info("CALL {}", targetBet);
|
||||
|
||||
LOGGER.info("[{}] CALL +{} (total to {})", phase, amount, callTarget);
|
||||
announceLobbyAction("CALL " + amount);
|
||||
|
||||
} else {
|
||||
int raiseBy = Math.max(0, targetBet - state.currentBet);
|
||||
|
||||
int raiseBy = Math.max(0, targetBet - callTarget);
|
||||
|
||||
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()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(text.trim());
|
||||
int value = Integer.parseInt(text.trim());
|
||||
|
||||
if (value % SMALL_BLIND != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value;
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
@@ -803,21 +1060,44 @@ public class TaskbarController {
|
||||
* triggers a warning, or if it is blocked due to being invalid or too risky.
|
||||
*/
|
||||
private ValidationResult validateTarget(GameState state, Player me, int targetBet) {
|
||||
|
||||
if (state == null || me == null) {
|
||||
return ValidationResult.blocked("Invalid game state");
|
||||
}
|
||||
|
||||
int callTarget = effectiveCallTarget(state);
|
||||
int raiseTarget = safeDouble(callTarget);
|
||||
if (targetBet != callTarget && targetBet != raiseTarget) {
|
||||
return ValidationResult.blocked("Only calls or exactly two raises allowed");
|
||||
final int callTarget = effectiveCallTarget(state);
|
||||
|
||||
final int alreadyInvested = Math.max(0, me.getBet());
|
||||
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());
|
||||
int required = Math.max(0, targetBet - alreadyInvested);
|
||||
int chips = Math.max(0, me.getChips());
|
||||
if (isFirstPlayerOfPhase(state, me)) {
|
||||
if (required > FIRST_PLAYER_MAX_BET) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -826,16 +1106,19 @@ public class TaskbarController {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
return ValidationResult.warning(warningTextForPhase(state));
|
||||
}
|
||||
|
||||
return ValidationResult.ok();
|
||||
}
|
||||
|
||||
@@ -980,8 +1263,6 @@ public class TaskbarController {
|
||||
* Called while dragging the taskbar with the mouse. Updates the position and slightly scales
|
||||
* 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
|
||||
*/
|
||||
@FXML
|
||||
@@ -1099,7 +1380,7 @@ public class TaskbarController {
|
||||
/** Called when the Call button is clicked. */
|
||||
@FXML
|
||||
private void onInputPlayerCall() {
|
||||
submitPresetInputAndProcess("call");
|
||||
submitAction(ActionType.CALL_BUTTON);
|
||||
}
|
||||
|
||||
/** Called when the Fold button is clicked. */
|
||||
@@ -1120,6 +1401,7 @@ public class TaskbarController {
|
||||
|
||||
gameService.fold();
|
||||
LOGGER.info("Player FOLD");
|
||||
announceLobbyAction("FOLD");
|
||||
|
||||
refreshGame();
|
||||
}
|
||||
@@ -1233,6 +1515,9 @@ public class TaskbarController {
|
||||
GameState refreshed = gameService.refresh();
|
||||
if (refreshed != null) {
|
||||
lastState = refreshed;
|
||||
if (refreshed.currentBet > 0 && refreshed.currentBet >= lastReferenceBet) {
|
||||
lastReferenceBet = refreshed.currentBet;
|
||||
}
|
||||
return refreshed;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user