diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 3e71904..94a4a1a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -1,38 +1,878 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; +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; +import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; +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.ui.gameui.gameuicomponents.PlayerStatusController; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; +import java.util.List; +import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; /** * Controller for the casino gaming area. * - *
Responsible for: - the display of the poker table and the player interface, - the processing + * Responsible for: - the display of the poker table and the player interface, - the processing * of user input, - the interface to the game engine and the network protocol. * - *
Notes: - The `onTableClick()` method currently serves only as test logic. It may no longer be + * Notes: - The `onTableClick()` method currently serves only as test logic. It may no longer be * functional and will be replaced by the final game interaction in the future. */ public class CasinoGameController { - /** Standard constructor. Used by FXML. */ + private static final Logger LOGGER = Logger.getLogger(CasinoGameController.class.getName()); + + @FXML private Label tableText; + @FXML private VBox casinoTable; + @FXML private HBox communityCardsBox; + @FXML private VBox casinoTableInnerBox; + @FXML private HBox playerCardsBox; + @FXML private HBox potBox; + @FXML private PlayerStatusController playerStatusController; + @FXML private PlayerStatusController player1Controller; + @FXML private PlayerStatusController player2Controller; + @FXML private PlayerStatusController player3Controller; + @FXML private VBox player1; + @FXML private VBox player2; + @FXML private VBox player3; + @FXML private ImageView myDealerIcon; + + private GameService gameService; + private PlayerId myPlayerId; + private TaskbarController taskbarController; + private Image dealerImage; + + private static final int TOTAL_SLOTS = 5; + private static final int PLAYER_SLOTS = 2; + private int pot = 0; + private static final String BACKSIDE = "/images/card-background-3.png"; + private static final String DEALER_IMAGE_PATH = "/images/chip-dealer-blue-3.png"; + private static final double TABLE_OFFSET_Y_FACTOR = 0.09; + private static final double PLAYER_CARDS_OFFSET_Y_FACTOR = 0.35; + private static final double MOVE_FROM_X = -40; + private static final double MOVE_FROM_Y = -10; + private static final double MOVE_TO_X = 0; + private static final double MOVE_TO_Y = 0; + private static final double FADE_FROM = 0; + private static final double FADE_TO = 1; + private static final double SCALE_FROM = 0.95; + private static final double SCALE_TO = 1; + private static final double SETTLE_FROM_Y = 0; + private static final double SETTLE_TO_Y = 10; + private static final boolean SETTLE_AUTO_REVERSE = true; + private static final int SETTLE_CYCLE_COUNT = 2; + private static final long ANIMATION_DURATION_MS = 350; + private static final long SETTLE_DURATION_MS = 150; + private static final long STAGGER_DELAY_MS = 120; + private static final double HOVER_SCALE_ON = 1.08; + private static final double HOVER_SCALE_OFF = 1.0; + private static final double HOVER_LIFT_ON = -8; + private static final double HOVER_LIFT_OFF = 0; + private static final long HOVER_DURATION_MS = 180; + private static final long UI_UPDATE_INTERVAL_SECONDS = 1; + private static final int PLAYER_INDEX_0 = 0; + private static final int PLAYER_INDEX_1 = 1; + private static final int PLAYER_INDEX_2 = 2; + private static final int DEALER_INDEX_PLAYER_1 = 0; + private static final int DEALER_INDEX_PLAYER_2 = 1; + private static final int DEALER_INDEX_PLAYER_3 = 2; + private static final int DEALER_PLAYER_1 = 0; + private static final int DEALER_PLAYER_2 = 1; + private static final int DEALER_PLAYER_3 = 2; + private static final int DEALER_MYSELF = 3; + private static final double CARD_START_OPACITY = 0.0; + 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_WIDTH_RATIO = 0.11; + private static final double PLAYER_CARD_HEIGHT_RATIO = 0.30; + private static final double PLAYER_CARD_WIDTH_RATIO = 0.15; + private static final double PLAYER_CARDS_Y_OFFSET_FACTOR = 0.10; + private static final int[] CHIP_VALUES = { + 100000, 50000, 20000, 10000, + 5000, 2000, 1000, 500, + 200, 100, 50, 20, + 10, 5, 2, 1 + }; + private static final double CHIP_HEIGHT_RATIO = 0.08; + private static final double CHIP_WIDTH_RATIO = 0.04; + private static final double CHIP_OPACITY_START = 0.0; + private static final double CHIP_SCALE_START = 0.6; + private static final double CHIP_SCALE_END = 1.0; + private static final double CHIP_RANDOM_X_FACTOR = 20.0; + private static final double CHIP_RANDOM_X_CENTER = 0.5; + private static final double CHIP_RANDOM_Y_BASE = -30.0; + private static final double CHIP_RANDOM_Y_VARIATION = 20.0; + private static final double CHIP_TRANSLATE_RESET_X = 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_SCALE_TO = 1.0; + private static final long CHIP_FADE_DURATION_MS = 200; + private static final long CHIP_SCALE_DURATION_MS = 220; + private static final long CHIP_DROP_DURATION_MS = 250; + private static final long CHIP_STAGGER_DELAY_MULTIPLIER = 35L; + + /** + * Standard constructor. Used by FXML. + */ public CasinoGameController() { // default constructor for FXML } - @FXML private Label welcomeText; - @FXML private VBox casinoTable; - - // TODO: Test logic: will be replaced by real game interactions, - // once the game engine is finished + /** + * Set the GameService for this controller. This method must be called before starting the UI to + * ensure that the controller has access to the game logic and can update the interface + * accordingly. + * + * @param gameService The GameService instance that provides access to the game state and logic. + */ + public void setGameService(GameService gameService) { + this.gameService = gameService; + } /** - * Temporary test method that performs a placeholder action when the table is clicked. - * - *
In the final implementation, this will be replaced by the game logic.
+ * Set the PlayerId of the current player.
*/
@FXML
- public void onTableClick() {
- welcomeText.setText("Einsatz akzeptiert!");
+ public void initialize() {
+ LOGGER.info("INIT UI");
+
+ if (communityCardsBox == null) {
+ LOGGER.warning("communityCardsBox is NULL");
+ return;
+ }
+
+ try {
+
+ var url = getClass().getResource(DEALER_IMAGE_PATH);
+
+ if (url != null) {
+ dealerImage = new Image(url.toExternalForm(), true);
+ myDealerIcon.setImage(dealerImage);
+ }
+
+ } catch (Exception e) {
+ LOGGER.warning("Dealer icon load failed");
+ }
+
+ myDealerIcon.setVisible(false);
+
+ javafx.stage.Screen screen = javafx.stage.Screen.getPrimary();
+ double screenHeight = screen.getBounds().getHeight();
+ casinoTableInnerBox.setTranslateY(-screenHeight * TABLE_OFFSET_Y_FACTOR);
+ playerCardsBox.setTranslateY(screenHeight * PLAYER_CARDS_OFFSET_Y_FACTOR);
+
+ uiTest();
+
+ // empty display only (optional)
+ renderCommunityCards(List.of());
+ renderPlayerCards(List.of());
+ }
+
+ /**
+ * Test method to demonstrate the UI functionality with sample data.
+ */
+ @FXML
+ public void uiTest() {
+ if (communityCardsBox == null) {
+ LOGGER.info("communityCardsBox is NULL");
+ return;
+ }
+
+ try {
+ renderCommunityCards(
+ List.of(
+ new Card("ace", "hearts"),
+ new Card("king", "spades"),
+ new Card("10", "diamonds")));
+
+ renderPlayerCards(List.of(new Card("10", "spades"), new Card("king", "spades")));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ Player mathis = new Player(PlayerId.of("Mathis"), 20000);
+ mathis.setState(PlayerState.ACTIVE);
+
+ Player jona = new Player(PlayerId.of("Jona"), 20000);
+ jona.setState(PlayerState.FOLDED);
+
+ Player lars = new Player(PlayerId.of("Lars"), 20000);
+ lars.setState(PlayerState.ACTIVE);
+
+ player1Controller.setPlayer(mathis);
+ player2Controller.setPlayer(jona);
+ player3Controller.setPlayer(lars);
+
+ LOGGER.info(
+ "Player 1: "
+ + mathis.getName()
+ + " | $"
+ + mathis.getChips()
+ + " | "
+ + mathis.getState());
+ LOGGER.info(
+ "Player 2: " + jona.getName() + " | $" + jona.getChips() + " | " + jona.getState());
+ LOGGER.info(
+ "Player 3: " + lars.getName() + " | $" + lars.getChips() + " | " + lars.getState());
+
+ if (playerStatusController != null) {
+ playerStatusController.setPlayer(mathis);
+ } else {
+ LOGGER.warning("PlayerStatusController is NULL");
+ }
+
+ javafx.stage.Screen screen = javafx.stage.Screen.getPrimary();
+ double screenHeight = screen.getBounds().getHeight();
+
+ casinoTableInnerBox.setTranslateY(-screenHeight * 0.08);
+ playerCardsBox.setTranslateY(screenHeight * 0.35);
+
+ renderPot(500);
+
+ lars.removeChips(500);
+ LOGGER.info("Lars: $" + lars.getChips());
+
+ player3Controller.refresh();
+
+ mathis.fall();
+ player1Controller.refresh();
+
+ GameState s = new GameState();
+ s.dealer = 3;
+ highlightDealer(s);
+
+ s.phase = "FLOP";
+
+ updateGameInfo(s);
+
+ s.winnerIndex = 1;
+
+ updateGameInfo(s);
+ }
+
+ /**
+ * Start the UI update loop.
+ */
+ public void start() {
+
+ if (gameService == null) {
+ throw new IllegalStateException("GameService not set!");
+ }
+
+ startLoop();
+ }
+
+ /**
+ * Start a loop that periodically updates the UI by fetching the latest game state from the
+ * GameService.
+ */
+ private void startLoop() {
+
+ javafx.animation.Timeline t =
+ new javafx.animation.Timeline(
+ new javafx.animation.KeyFrame(
+ javafx.util.Duration.seconds(UI_UPDATE_INTERVAL_SECONDS),
+ e -> updateUI()));
+
+ t.setCycleCount(javafx.animation.Animation.INDEFINITE);
+ t.play();
+ }
+
+ /**
+ * Update the UI by fetching the latest game state from the GameService and updating all
+ * relevant components.
+ */
+ private void updateUI() {
+
+ try {
+ GameState s = gameService.refresh();
+
+ if (s == null) {
+ return;
+ }
+
+ renderCommunityCards(s.communityCards);
+ renderPot(s.pot);
+
+ updatePlayers(s.players);
+ updatePlayerCards(s);
+
+ updateGameInfo(s);
+ highlightDealer(s);
+
+ updateTaskbar(s);
+
+ } catch (Exception e) {
+ LOGGER.severe("Error: UI Update failed: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Update the player's hole cards based on the active player in the game state.
+ *
+ * @param s The current game state.
+ */
+ private void updatePlayerCards(GameState s) {
+
+ int active = s.activePlayer;
+
+ if (active >= 0 && active < s.players.size()) {
+ Player p = s.players.get(active);
+ renderPlayerCards(p.getCards());
+ } else {
+ renderPlayerCards(List.of());
+ }
+ }
+
+ /**
+ * Update the game information display, such as the current phase and the winner if the hand has
+ * ended.
+ *
+ * @param s The current game state.
+ */
+ private void updateGameInfo(GameState s) {
+
+ String text = "Phase: " + s.phase;
+
+ if (s.winnerIndex >= 0 && s.winnerIndex < s.players.size()) {
+ Player winner = s.players.get(s.winnerIndex);
+ text = "Winner: " + winner.getName();
+ }
+
+ tableText.setText(text);
+ }
+
+ /**
+ * Update the taskbar with the current game state and the player's ID.
+ *
+ * @param s The current game state.
+ */
+ private void updateTaskbar(GameState s) {
+
+ taskbarController.update(s, myPlayerId);
+ }
+
+ /**
+ * Update the player status components with the latest player information from the game state.
+ *
+ * @param p The list of players in the current game state.
+ */
+ private void updatePlayers(List