Fix: disable context-dependent buttons in taskbar
This commit is contained in:
+292
-14
@@ -7,6 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
@@ -44,6 +45,10 @@ public class TaskbarController {
|
|||||||
private static final int MIN_CREDITS = 5;
|
private static final int MIN_CREDITS = 5;
|
||||||
private static final int MAX_CREDITS = 100000;
|
private static final int MAX_CREDITS = 100000;
|
||||||
private static final int CREDIT_STEP = 5;
|
private static final int CREDIT_STEP = 5;
|
||||||
|
private static final double PREFLOP_WARN_RATIO = 0.30;
|
||||||
|
private static final double PREFLOP_BLOCK_RATIO = 0.60;
|
||||||
|
private static final double POSTFLOP_WARN_RATIO = 0.50;
|
||||||
|
private static final double POSTFLOP_BLOCK_RATIO = 0.80;
|
||||||
private static final String STYLE_YELLOW_BUTTON = "yellow-button";
|
private static final String STYLE_YELLOW_BUTTON = "yellow-button";
|
||||||
private static final String STYLE_GRAY_BUTTON = "gray-button";
|
private static final String STYLE_GRAY_BUTTON = "gray-button";
|
||||||
private static final String STYLE_RED_BUTTON = "red-button";
|
private static final String STYLE_RED_BUTTON = "red-button";
|
||||||
@@ -246,9 +251,177 @@ public class TaskbarController {
|
|||||||
boolean isGameFinished = state.phase != null && state.phase.equalsIgnoreCase("FINISHED");
|
boolean isGameFinished = state.phase != null && state.phase.equalsIgnoreCase("FINISHED");
|
||||||
|
|
||||||
updateBasicButtons(isMyTurn, isOut || isGameFinished);
|
updateBasicButtons(isMyTurn, isOut || isGameFinished);
|
||||||
|
applyActionAvailability(state, me, isMyTurn, isOut || isGameFinished);
|
||||||
setMoney(me.getChips());
|
setMoney(me.getChips());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyActionAvailability(
|
||||||
|
GameState state, Player me, boolean isMyTurn, boolean isOutOrFinished) {
|
||||||
|
if (!isMyTurn || isOutOrFinished || state == null || me == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chips = Math.max(0, me.getChips());
|
||||||
|
int toCall = getToCall(state, me);
|
||||||
|
int minBetAmount = getMinimumBetAmount(state);
|
||||||
|
|
||||||
|
boolean canBet = chips >= minBetAmount;
|
||||||
|
boolean canCall = chips > 0 && (toCall == 0 || chips >= toCall);
|
||||||
|
int raiseAmount = Math.max(0, state.currentBet);
|
||||||
|
boolean canRaise = chips > 0 && raiseAmount > 0 && chips >= toCall + raiseAmount;
|
||||||
|
|
||||||
|
if (!canBet && !canCall && !canRaise) {
|
||||||
|
setActionEnabled(betButton, false);
|
||||||
|
setActionEnabled(callButton, false);
|
||||||
|
setActionEnabled(raiseButton, false);
|
||||||
|
setActionEnabled(foldButton, true);
|
||||||
|
deactivateInputField(taskbarInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnlyBlindsInPot(state)) {
|
||||||
|
setActionEnabled(betButton, canBet);
|
||||||
|
setActionEnabled(callButton, false);
|
||||||
|
setActionEnabled(foldButton, false);
|
||||||
|
setActionEnabled(raiseButton, false);
|
||||||
|
if (canBet) {
|
||||||
|
activateInputField(taskbarInput);
|
||||||
|
} else {
|
||||||
|
deactivateInputField(taskbarInput);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActionEnabled(betButton, canBet);
|
||||||
|
setActionEnabled(callButton, canCall);
|
||||||
|
setActionEnabled(raiseButton, canRaise);
|
||||||
|
setActionEnabled(foldButton, true);
|
||||||
|
|
||||||
|
if (canBet) {
|
||||||
|
activateInputField(taskbarInput);
|
||||||
|
} else {
|
||||||
|
deactivateInputField(taskbarInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setActionEnabled(Button button, boolean enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
activateButton(button);
|
||||||
|
} else {
|
||||||
|
deactivateButton(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getToCall(GameState state, Player me) {
|
||||||
|
int current = Math.max(0, state.currentBet);
|
||||||
|
int alreadyInvested = Math.max(0, me.getBet());
|
||||||
|
return Math.max(0, current - alreadyInvested);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMinimumBetAmount(GameState state) {
|
||||||
|
int bigBlind = estimateBigBlind(state);
|
||||||
|
return Math.max(MIN_CREDITS, Math.max(state.currentBet, bigBlind));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int estimateBigBlind(GameState state) {
|
||||||
|
if (state == null) {
|
||||||
|
return MIN_CREDITS * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPreflop(state.phase) && state.currentBet > 0) {
|
||||||
|
return state.currentBet;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxCommitted = 0;
|
||||||
|
for (Player player : state.players) {
|
||||||
|
if (player != null) {
|
||||||
|
maxCommitted = Math.max(maxCommitted, player.getBet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxCommitted > 0 ? maxCommitted : MIN_CREDITS * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnlyBlindsInPot(GameState state) {
|
||||||
|
if (state == null || !isPreflop(state.phase) || state.players == null || state.players.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nonZeroBets = 0;
|
||||||
|
int maxBetCount = 0;
|
||||||
|
int lowerBlindCount = 0;
|
||||||
|
int maxBet = Math.max(0, state.currentBet);
|
||||||
|
int totalCommitted = 0;
|
||||||
|
|
||||||
|
for (Player player : state.players) {
|
||||||
|
if (player == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bet = Math.max(0, player.getBet());
|
||||||
|
totalCommitted += bet;
|
||||||
|
|
||||||
|
if (bet > 0) {
|
||||||
|
nonZeroBets++;
|
||||||
|
if (bet == maxBet) {
|
||||||
|
maxBetCount++;
|
||||||
|
} else if (bet < maxBet) {
|
||||||
|
lowerBlindCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxBet > 0
|
||||||
|
&& nonZeroBets == 2
|
||||||
|
&& maxBetCount == 1
|
||||||
|
&& lowerBlindCount == 1
|
||||||
|
&& totalCommitted == state.pot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPreflop(String phase) {
|
||||||
|
return "PREFLOP".equalsIgnoreCase(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum BetRisk {
|
||||||
|
ALLOWED,
|
||||||
|
WARNING,
|
||||||
|
BLOCKED
|
||||||
|
}
|
||||||
|
|
||||||
|
private BetRisk evaluateBetRisk(GameState state, int amount, int chips) {
|
||||||
|
if (chips <= 0 || amount <= 0) {
|
||||||
|
return BetRisk.BLOCKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ratio = (double) amount / chips;
|
||||||
|
|
||||||
|
if (isPreflop(state != null ? state.phase : null)) {
|
||||||
|
if (ratio > PREFLOP_BLOCK_RATIO) {
|
||||||
|
return BetRisk.BLOCKED;
|
||||||
|
}
|
||||||
|
if (ratio > PREFLOP_WARN_RATIO) {
|
||||||
|
return BetRisk.WARNING;
|
||||||
|
}
|
||||||
|
return BetRisk.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ratio > POSTFLOP_BLOCK_RATIO) {
|
||||||
|
return BetRisk.BLOCKED;
|
||||||
|
}
|
||||||
|
if (ratio > POSTFLOP_WARN_RATIO) {
|
||||||
|
return BetRisk.WARNING;
|
||||||
|
}
|
||||||
|
return BetRisk.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showWarningDialog(String title, String content) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(content);
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the taskbar is clicked with the mouse. Saves the relative position for later,
|
* Called when the taskbar is clicked with the mouse. Saves the relative position for later,
|
||||||
* correct repositioning.
|
* correct repositioning.
|
||||||
@@ -320,6 +493,23 @@ public class TaskbarController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameState state = ensureLatestStateForAction("call");
|
||||||
|
if (state == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player me = findCurrentPlayer(state);
|
||||||
|
if (me == null) {
|
||||||
|
LOGGER.error("Cannot CALL: player missing in state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int toCall = getToCall(state, me);
|
||||||
|
if (me.getChips() <= 0 || (toCall > 0 && me.getChips() < toCall)) {
|
||||||
|
LOGGER.warn("CALL not possible: insufficient chips (chips={}, toCall={})", me.getChips(), toCall);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
gameService.call();
|
gameService.call();
|
||||||
LOGGER.info("Player CALL");
|
LOGGER.info("Player CALL");
|
||||||
|
|
||||||
@@ -350,25 +540,32 @@ public class TaskbarController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameState state = lastState;
|
GameState state = ensureLatestStateForAction("raise");
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
try {
|
|
||||||
state = gameService.refresh();
|
|
||||||
lastState = state;
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error("Failed to refresh game state for raise: {}", e.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == null || state.currentBet <= 0) {
|
|
||||||
LOGGER.warn(
|
|
||||||
"Raise not possible: current bet is {}",
|
|
||||||
state != null ? state.currentBet : null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Player me = findCurrentPlayer(state);
|
||||||
|
if (me == null) {
|
||||||
|
LOGGER.error("Cannot RAISE: player missing in state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.currentBet <= 0) {
|
||||||
|
LOGGER.warn("Raise not possible: current bet is {}", state.currentBet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int toCall = getToCall(state, me);
|
||||||
int raiseAmount = state.currentBet;
|
int raiseAmount = state.currentBet;
|
||||||
|
if (me.getChips() <= 0 || me.getChips() < toCall + raiseAmount) {
|
||||||
|
LOGGER.warn(
|
||||||
|
"Raise not possible: insufficient chips (chips={}, required={})",
|
||||||
|
me.getChips(),
|
||||||
|
toCall + raiseAmount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
gameService.raise(raiseAmount);
|
gameService.raise(raiseAmount);
|
||||||
LOGGER.info("Player RAISE by {} (target: double table bet)", raiseAmount);
|
LOGGER.info("Player RAISE by {} (target: double table bet)", raiseAmount);
|
||||||
|
|
||||||
@@ -446,8 +643,59 @@ public class TaskbarController {
|
|||||||
|
|
||||||
int credits = Integer.parseInt(input.trim());
|
int credits = Integer.parseInt(input.trim());
|
||||||
|
|
||||||
|
GameState state = ensureLatestStateForAction("bet");
|
||||||
|
if (state == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player me = findCurrentPlayer(state);
|
||||||
|
if (me == null) {
|
||||||
|
LOGGER.error("Cannot BET: player missing in state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minBetAmount = getMinimumBetAmount(state);
|
||||||
|
|
||||||
if (credits >= MIN_CREDITS && credits <= MAX_CREDITS && credits % CREDIT_STEP == 0) {
|
if (credits >= MIN_CREDITS && credits <= MAX_CREDITS && credits % CREDIT_STEP == 0) {
|
||||||
|
|
||||||
|
if (credits < minBetAmount) {
|
||||||
|
LOGGER.error(
|
||||||
|
"Error: Bet must be at least {} (current table minimum)", minBetAmount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credits > me.getChips()) {
|
||||||
|
LOGGER.error(
|
||||||
|
"Error: Bet {} exceeds available chips {}", credits, me.getChips());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BetRisk risk = evaluateBetRisk(state, credits, me.getChips());
|
||||||
|
if (risk == BetRisk.BLOCKED) {
|
||||||
|
if (isPreflop(state.phase)) {
|
||||||
|
showWarningDialog(
|
||||||
|
"Bet blocked",
|
||||||
|
"In the preflop, bets exceeding 60% of your stack are blocked.");
|
||||||
|
} else {
|
||||||
|
showWarningDialog(
|
||||||
|
"Bet blocked",
|
||||||
|
"After the flop, bets exceeding 80% of your stack are blocked.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (risk == BetRisk.WARNING) {
|
||||||
|
if (isPreflop(state.phase)) {
|
||||||
|
showWarningDialog(
|
||||||
|
"High preflop bet",
|
||||||
|
"Warning: You're betting more than 30% of your stack preflop.");
|
||||||
|
} else {
|
||||||
|
showWarningDialog(
|
||||||
|
"High stakes",
|
||||||
|
"Warning: You are betting more than 50% of your stack in this betting round.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gameService.bet(credits);
|
gameService.bet(credits);
|
||||||
|
|
||||||
LOGGER.info("Bet placed: {}", credits);
|
LOGGER.info("Bet placed: {}", credits);
|
||||||
@@ -465,6 +713,36 @@ public class TaskbarController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Player findCurrentPlayer(GameState state) {
|
||||||
|
if (state == null || state.players == null || myPlayerId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.players.stream()
|
||||||
|
.filter(player -> player != null && myPlayerId.equals(player.getId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameState ensureLatestStateForAction(String actionName) {
|
||||||
|
GameState state = lastState;
|
||||||
|
|
||||||
|
if (state == null) {
|
||||||
|
try {
|
||||||
|
state = gameService.refresh();
|
||||||
|
lastState = state;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error(
|
||||||
|
"Failed to refresh game state for {}: {}",
|
||||||
|
actionName,
|
||||||
|
e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the integrated Casono web browser.
|
* Opens the integrated Casono web browser.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user