package client; import server.*; import server.*; import server.automaton.ActionResult; import server.automaton.Game; import server.cards.*; import javafx.animation.*; import javafx.application.Application; import javafx.application.Platform; import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.stage.Stage; import javafx.util.Duration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.PDFRenderer; import server.cards.BuildingCard; import server.cards.Card; import server.cards.CharacterCard; import server.cards.CharacterType; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.stream.Collectors; public class DeckGridAppFX extends Application { private static final Logger logger = LogManager.getLogger(DeckGridAppFX.class); private static final int IMG_HEIGHT=200; private PDDocument documentFront; private PDDocument documentBack; private PDFRenderer pdfRendererFront; private PDFRenderer pdfRendererBack; private int totalCards = 0; // Contenitori Layout private HBox topMenu; private HBox topRow; private HBox centerRow; private HBox bottomRow; private HBox playersArea; // NUOVO: Area per i giocatori private Button btnRefresh; private Button btnTop; private Button btnBottom; private Button btnChooseOffering; private Button btnMsg; // -------------------------------- @Override public void start(Stage primaryStage) { List players = new ArrayList<>(); //players.add(new Player("Yellow", TotemColor.YELLOW)); players.add(new Player("Blue", TotemColor.BLUE)); //players.add(new Player("Purple", TotemColor.PURPLE)); players.add(new Player("Red", TotemColor.RED)); //players.add(new Player("Green", TotemColor.GREEN)); Game game = new Game(players); InputStream frontStream = getClass().getResourceAsStream("/files/Cards_total_front_PROMO.pdf"); InputStream backStream = getClass().getResourceAsStream("/files/Cards_total_back_PROMO.pdf"); InputStream csvStream = getClass().getResourceAsStream("/files/cards.csv"); try { documentFront = PDDocument.load(frontStream); pdfRendererFront = new PDFRenderer(documentFront); documentBack = PDDocument.load(backStream); pdfRendererBack = new PDFRenderer(documentBack); } catch ( IOException e) { throw new RuntimeException(e); } //File fileCsv = new File(getClass().getResource("/files/cards.csv").toURI()); game.newGame(csvStream); game.setEventListener(notification -> { Platform.runLater(() -> { drawGameState(game); switch (notification.event()) { case TOTEM_PLACEMENT-> showPopup("TOTEM PLACEMENT\n" + (notification.message()!=null?notification.message():"")); case SETUP -> showPopup("SETUP \n" + (notification.message()!=null?notification.message():"")); case ACTION_RESOLUTION -> showPopup("ACTION_RESOLUTION \n" + (notification.message()!=null?notification.message():"" )); case END_ACTION -> showPopup("END_ACTION \n" + (notification.message()!=null?notification.message():"" )); case EVENT_RESOLUTION -> showPopup("EVENT_RESOLUTION \n" + (notification.message()!=null?notification.message():"" )); case EXTRA_DRAW -> showPopup("EXTRA_DRAW \n" + (notification.message()!=null?notification.message():"" )); case GAME_OVER -> showPopup("GAME OVER"); } }); }); primaryStage.setTitle("Tavolo da Gioco - Board & Players"); btnTop = new Button("Pick Top"); btnBottom = new Button("Pick Bottom"); btnMsg = new Button("Game State"); btnRefresh= new Button("Refresh"); btnChooseOffering= new Button("Choose Offering"); //btnNext.setDisable(true); topMenu = new HBox(15, btnRefresh, btnMsg); topMenu.setAlignment(Pos.CENTER); topMenu.setPadding(new Insets(10)); Label stateLabel = new Label("Game State = " + game.getState() ); stateLabel.setFont(Font.font("System", FontWeight.BOLD, 16)); stateLabel.setTextFill(Color.DARKBLUE); //showPopup(topMenu, "STARTING GAME"); btnMsg.setOnAction(e -> { new Alert(Alert.AlertType.INFORMATION, game.toString() ).showAndWait(); }); btnRefresh.setOnAction(e -> { drawGameState(game); }); topRow = createRowContainer(); centerRow = createRowContainer(); bottomRow = createRowContainer(); // NUOVO: Inizializza l'area giocatori playersArea = new HBox(30); playersArea.setAlignment(Pos.CENTER); playersArea.setPadding(new Insets(20)); VBox tableArea = new VBox(20, new Label(" "), topRow, new Label(" "), centerRow, new Label(" "), bottomRow, new Label("--- ASSET PLAYERS ---"), playersArea ); tableArea.setAlignment(Pos.CENTER); tableArea.setPadding(new Insets(20)); ScrollPane scrollPane = new ScrollPane(tableArea); scrollPane.setFitToWidth(true); BorderPane root = new BorderPane(); root.setTop(topMenu); root.setCenter(scrollPane); Scene scene = new Scene(root, 1300, 900); primaryStage.setScene(scene); primaryStage.show(); drawGameState(game); } private HBox createRowContainer() { HBox row = new HBox(10); row.setAlignment(Pos.CENTER); return row; } private void drawGameState(Game game) { if (documentFront == null) return; topMenu.getChildren().clear(); topRow.getChildren().clear(); centerRow.getChildren().clear(); bottomRow.getChildren().clear(); playersArea.getChildren().clear(); drawTopMenu(topMenu, game); drawTopRow(topRow, game); drawTurnTileAndOffering(centerRow, game.getPlayers().size(), game); drawBottomRow(bottomRow, game ); List players =game.getPlayers(); renderPlayers(players, game); logger.info(game.getCurrentPlayer()); } private void pickTopCardAction(Player player, GameBoard board, int cardid){ Card card = board.getTopRow().stream() .filter(CharacterCard.class::isInstance) .map(CharacterCard.class::cast) .filter(c -> c.getCardId() == cardid) .findFirst() .orElse(null); if (card != null) { board.getTopRow().remove(card); CharacterCard charCard = (CharacterCard)card; player.addCharacterToTribe((CharacterCard) card); } } private void pickBottomCardAction(Player player, GameBoard board, int cardid){ Card card = board.getBottomRow().stream() .filter(CharacterCard.class::isInstance) .map(CharacterCard.class::cast) .filter(c -> c.getCardId() == cardid) .findFirst() .orElse(null); if (card != null) { board.getBottomRow().remove(card); player.addCharacterToTribe((CharacterCard) card); } } private void drawBottomRow(HBox row, Game game) { for (Card c : game.getGameBoard().getBottomRow()) { ImageView cardImage = createCardImageFromPdf(c.getCardId(), IMG_HEIGHT, true); if (cardImage != null) { // 1. Create a Label for your debugging text Label debugLabel = new Label("ID: " + c.getCardId()); // 2. Style the label so it's readable over any card background // (White text with a semi-transparent black background) debugLabel.setStyle("-fx-background-color: rgba(0, 0, 0, 0.7); " + "-fx-text-fill: white; " + "-fx-padding: 3px; " + "-fx-font-weight: bold;"); // 3. Create a StackPane and add both the image and the text StackPane cardContainer = new StackPane(); cardContainer.getChildren().addAll(cardImage, debugLabel); // 4. Align the text wherever you want (e.g., Top-Left corner of the card) StackPane.setAlignment(debugLabel, Pos.TOP_LEFT); // You can also use Pos.CENTER, Pos.BOTTOM_RIGHT, etc. cardImage.setOnMouseClicked(event -> { logger.info("Bottom Card clicked"); Player p = game.getCurrentPlayer(); //pickBottomCardAction(p, game.getGameBoard(), c.getCardId()); ActionResult result = game.resolveCardAction(p, false, c.getCardId()); if (!result.isSuccess()) { new Alert(Alert.AlertType.ERROR, result.getErrorMessage() ).showAndWait(); } drawGameState(game); }); // 5. Add the StackPane (which now holds both) to the row row.getChildren().add(cardContainer); } } } private void drawTopMenu(HBox row, Game game) { //topMenu = new HBox(15, btnChooseOffering, btnAction, btnTop, btnBottom, btnMsg); //topMenu.setAlignment(Pos.CENTER); //topMenu.setPadding(new Insets(10)); Label stateLabel = new Label("Game State = " + game.getState() ); stateLabel.setFont(Font.font("System", FontWeight.BOLD, 16)); stateLabel.setTextFill(Color.DARKBLUE); row.getChildren().add(stateLabel); row.getChildren().add(btnRefresh); row.getChildren().add(btnMsg); } private void drawTopRow(HBox row, Game game) { for (Card c : game.getGameBoard().getTopRow()) { ImageView cardImage = createCardImageFromPdf(c.getCardId(), IMG_HEIGHT, true); if (cardImage != null) { // 1. Create a Label for your debugging text Label debugLabel = new Label("ID: " + c.getCardId()); // 2. Style the label so it's readable over any card background // (White text with a semi-transparent black background) debugLabel.setStyle("-fx-background-color: rgba(0, 0, 0, 0.7); " + "-fx-text-fill: white; " + "-fx-padding: 3px; " + "-fx-font-weight: bold;"); // 3. Create a StackPane and add both the image and the text StackPane cardContainer = new StackPane(); cardContainer.getChildren().addAll(cardImage, debugLabel); // 4. Align the text wherever you want (e.g., Top-Left corner of the card) StackPane.setAlignment(debugLabel, Pos.TOP_LEFT); // You can also use Pos.CENTER, Pos.BOTTOM_RIGHT, etc. cardImage.setOnMouseClicked(event -> { logger.info("Card clicked"); Player p = game.getCurrentPlayer(); //pickTopCardAction(p, game.getGameBoard(), c.getCardId()); //game.resolveCardAction(p, true, c.getCardId()); ActionResult result = game.resolveCardAction(p, true, c.getCardId()); if (!result.isSuccess()) { new Alert(Alert.AlertType.ERROR, result.getErrorMessage() ).showAndWait(); } drawGameState(game); }); // 5. Add the StackPane (which now holds both) to the row row.getChildren().add(cardContainer); } } } private StackPane drawTurnTileCardImageWithPlayers(InputStream imgStream, int height, Player[] players) { try { Image fxImage = new Image(imgStream); ImageView imageView = new ImageView(fxImage); imageView.setFitHeight(height); imageView.setPreserveRatio(true); imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);"); StackPane stack = new StackPane(imageView); VBox playerBox = new VBox(5); playerBox.setPadding(new Insets(5)); playerBox.setAlignment(Pos.CENTER_RIGHT); // 🔥 DEBUG background (to SEE the box) playerBox.setStyle("-fx-background-color: rgba(255,0,0,0.3);"); for (Player p : players) { if (p==null) continue; Rectangle rect = new Rectangle(15,15); rect.setFill(p.getTotemColor().getFxColor()); rect.setStroke(Color.WHITE); rect.setStrokeWidth(1); playerBox.getChildren().add(rect); } playerBox.setPadding(new Insets(22)); // 🔥 IMPORTANT: bring to front playerBox.toFront(); // position it clearly StackPane.setAlignment(playerBox, Pos.CENTER); stack.getChildren().add(playerBox); return stack; } catch (Exception e) { logger.error("Errore caricamento immagine {}", imgStream, e); return null; } } private void drawTurnTileAndOffering(HBox row, int n, Game game) { // Clear row if needed (optional but recommended) row.getChildren().clear(); // --- LEFT BLOCK (Turn + initial card) --- VBox turnBox = new VBox(5); turnBox.setAlignment(Pos.CENTER); // Turn label int round = game.getRound(); // adjust if different Label turnLabel = new Label("Round = " + round); turnLabel.setFont(Font.font("System", FontWeight.BOLD, 16)); turnLabel.setTextFill(Color.DARKBLUE); Card first = game.getGameBoard().getCardDeck().getFirstCardForCover(); if (first!=null){ ImageView imgCover = createCardImageFromPdf(first.getCardId(), 120, false); turnBox.getChildren().addAll(turnLabel, imgCover); } else turnBox.getChildren().addAll(turnLabel); // Add FIRST row.getChildren().add(turnBox); // --- TURN TILE --- StackPane pane = drawTurnTileCardImageWithPlayers( getClass().getResourceAsStream("/files/Start_" + n + "P.png"), IMG_HEIGHT, game.getGameBoard().getTurnTile().getPositions() ); row.getChildren().add(pane); // --- OFFERINGS --- for (OfferingTile offering : game.getGameBoard().getOfferingTiles()) { Player occupant = offering.getOccupant(); StackPane offPane =drawOfferingCardImageWithTotem( getClass().getResourceAsStream("/files/offering" + offering.getLetter() + ".png"), IMG_HEIGHT, occupant, game, offering); if (offPane != null) { row.getChildren().add(offPane); } } } // --- NUOVO: RENDERIZZAZIONE GIOCATORI E RAGGRUPPAMENTO (JAVA 8) --- private void renderPlayers(List players, Game game) { playersArea.getChildren().clear(); for (Player player : players) { boolean active = player.equals(game.getCurrentPlayer()); VBox playerBox = new VBox(10); playerBox.setPadding(new Insets(15)); playerBox.setStyle("-fx-border-color: #555; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-color: #f9f9f9; -fx-background-radius: 10;"); if (active) { // Strong highlight (gold border + soft background) playerBox.setStyle( "-fx-border-color: gold;" + "-fx-border-width: 3;" + "-fx-border-radius: 10;" + "-fx-background-color: linear-gradient(to bottom, #fffbe6, #f9f9f9);" + "-fx-background-radius: 10;" ); // Slight scale-up playerBox.setScaleX(1.05); playerBox.setScaleY(1.05); // Glow effect DropShadow glow = new DropShadow(); glow.setColor(Color.GOLD); glow.setRadius(20); playerBox.setEffect(glow); // Smooth pulse animation (NOT blinking) Timeline pulse = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(glow.radiusProperty(), 10) ), new KeyFrame(Duration.seconds(1.2), new KeyValue(glow.radiusProperty(), 25) ) ); pulse.setAutoReverse(true); pulse.setCycleCount(Animation.INDEFINITE); pulse.play(); } HBox nameRow = new HBox(8); nameRow.setAlignment(Pos.CENTER_LEFT); Rectangle rect = new Rectangle(12, 12); TotemColor color = player.getTotemColor(); rect.setFill(color.getFxColor()); rect.setStroke(Color.BLACK); rect.setStrokeWidth(1); Label nameLbl = new Label(player.getNickname()); nameLbl.setFont(Font.font("System", FontWeight.BOLD, 16)); if (active) { Label turnLbl = new Label(" ▶ Your Turn"); turnLbl.setTextFill(Color.GOLDENROD); turnLbl.setFont(Font.font("System", FontWeight.BOLD, 14)); nameRow.getChildren().addAll(rect, nameLbl, turnLbl); } else { nameRow.getChildren().addAll(rect, nameLbl); } // Risorse Cibo e Soldi Label statsLbl = new Label("🍖 Food: " + player.getFoodTokens() + " | 💰 Points: " + player.getPrestigePoints()); statsLbl.setTextFill(Color.DARKRED); statsLbl.setFont(Font.font("System", FontWeight.BOLD, 14)); playerBox.getChildren().addAll(nameRow, statsLbl); Map> groupedCards = player.getPlayerTribe().getCharacters().stream() .collect(Collectors.groupingBy(CharacterCard::getCharacterType)); // Itera sui gruppi creati e genera la grafica groupedCards.forEach((type, cardsOfType) -> { Label typeLbl = new Label("Tipo: " + type.name() + " (" + cardsOfType.size() + ")"); typeLbl.setFont(Font.font("System", FontWeight.NORMAL, 12)); HBox cardImagesRow = new HBox(5); for (Card c : cardsOfType) { // Carte più piccole (90px) per l'area giocatore ImageView img = createCardImageFromPdf(c.getCardId(), 90, true); if (img != null) cardImagesRow.getChildren().add(img); } playerBox.getChildren().addAll(typeLbl, cardImagesRow); }); Map> groupedBuildCards = player.getPlayerTribe().getBuildingCard().stream() .collect(Collectors.groupingBy(BuildingCard::getEra)); groupedBuildCards.forEach((type, cardsOfType) -> { Label typeLbl = new Label("Build Tipo: " + type.name() + " (" + cardsOfType.size() + ")"); typeLbl.setFont(Font.font("System", FontWeight.NORMAL, 12)); HBox cardImagesRow = new HBox(5); for (Card c : cardsOfType) { // Carte più piccole (90px) per l'area giocatore ImageView img = createCardImageFromPdf(c.getCardId(), 90, true); if (img != null) cardImagesRow.getChildren().add(img); } playerBox.getChildren().addAll(typeLbl, cardImagesRow); //playerBox.getChildren().addAll(nameRow, statsLbl); }); playersArea.getChildren().add(playerBox); } } private ImageView createCardImageFromPdf(int pageIndex, int height, boolean front) { try { BufferedImage bim =null; if (front) bim = pdfRendererFront.renderImageWithDPI(pageIndex-1, 100); else bim = pdfRendererBack.renderImageWithDPI(pageIndex-1, 100); Image fxImage = SwingFXUtils.toFXImage(bim, null); ImageView imageView = new ImageView(fxImage); imageView.setFitHeight(height); imageView.setPreserveRatio(true); imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);"); return imageView; } catch (IOException e) { logger.error("Errore rendering pagina {}", pageIndex, e); return null; } } private StackPane drawOfferingCardImageWithTotem(InputStream imagePath, int height, Player occupant, Game game, OfferingTile offering) { try { Image fxImage = new Image(imagePath); ImageView imageView = new ImageView(fxImage); imageView.setFitHeight(height); imageView.setPreserveRatio(true); imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);"); imageView.setOnMouseClicked(event -> { int idx = game.getGameBoard().getOfferingTiles().indexOf(offering); game.placeTotem(game.getCurrentPlayer(), idx); logger.info(" PLAYER {} choose {} ", game.getCurrentPlayer().getNickname() , offering ); drawGameState(game); }); StackPane stack = new StackPane(imageView); if(occupant!=null){ Rectangle rect = new Rectangle(25, 25); rect.setFill(occupant.getTotemColor().getFxColor()); rect.setStroke(Color.WHITE); rect.setStrokeWidth(1); StackPane.setAlignment(rect, Pos.TOP_CENTER); rect.setTranslateY(20); StackPane.setMargin(rect, new Insets(5)); stack.getChildren().add(rect); } return stack; } catch (Exception e) { logger.error("Errore caricamento immagine {}", imagePath, e); return null; } } private ImageView createCardImage(InputStream imagePath, int height) { try { Image fxImage = new Image(imagePath); ImageView imageView = new ImageView(fxImage); imageView.setFitHeight(height); imageView.setPreserveRatio(true); imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);"); return imageView; } catch (Exception e) { logger.error("Errore caricamento immagine {}", imagePath, e); return null; } } private void showPopup(String message) { Platform.runLater(() -> { Label label = new Label("⚡ " + message); // icon improves readability label.setWrapText(true); label.setMaxWidth(400); label.setStyle( "-fx-background-color: rgba(20,20,20,0.9);" + "-fx-text-fill: white;" + "-fx-font-size: 20px;" + "-fx-font-weight: bold;" + "-fx-padding: 15 25 15 25;" + "-fx-background-radius: 12;" + "-fx-border-radius: 12;" + "-fx-border-color: #00c3ff;" ); label.setOpacity(0); // start invisible topMenu.getChildren().add(label); // Fade IN FadeTransition fadeIn = new FadeTransition(Duration.millis(250), label); fadeIn.setFromValue(0); fadeIn.setToValue(1); // Stay visible PauseTransition pause = new PauseTransition(Duration.seconds(4)); // Fade OUT FadeTransition fadeOut = new FadeTransition(Duration.millis(400), label); fadeOut.setFromValue(1.0); fadeOut.setToValue(0.0); fadeOut.setOnFinished(f -> topMenu.getChildren().remove(label)); fadeIn.play(); fadeIn.setOnFinished(e -> pause.play()); pause.setOnFinished(e -> fadeOut.play()); }); } private void showPopupNo(String message) { Platform.runLater(() -> { Label label = new Label(message); label.setStyle( "-fx-background-color: rgba(0,0,0,0.75);" + "-fx-text-fill: white;" + "-fx-font-size: 28px;" + "-fx-font-weight: bold;" + "-fx-padding: 20 40 20 40;" + "-fx-background-radius: 12;" ); label.setMouseTransparent(true); topMenu.getChildren().add(label); PauseTransition pause = new PauseTransition(Duration.seconds(5)); pause.setOnFinished(e -> { FadeTransition fade = new FadeTransition(Duration.millis(400), label); fade.setFromValue(1.0); fade.setToValue(0.0); fade.setOnFinished(f -> topMenu.getChildren().remove(label)); fade.play(); }); pause.play(); }); } @Override public void stop() throws Exception { if (documentFront != null) documentFront.close(); super.stop(); } public static void main(String[] args) { launch(args); } }