From 275dc51409ab3a2173aadc8e4a2c9689bb6b15f0 Mon Sep 17 00:00:00 2001 From: aleandro Date: Sun, 14 Jun 2026 16:00:35 +0200 Subject: [PATCH] Fix: full view refactor --- .../gc14/View/GUI/FireParticleSystem.java | 13 ++++- .../it/polimi/ingsw/gc14/View/GUI/GUI.java | 55 +++++++++---------- .../View/GUI/LeaderboardFXMLController.java | 26 ++------- .../gc14/View/GUI/LoginFXMLController.java | 12 ++-- .../gc14/View/GUI/MainFXMLController.java | 17 ++---- .../gc14/View/GUI/TotemFXMLController.java | 11 +--- .../java/it/polimi/ingsw/gc14/View/IView.java | 6 +- .../it/polimi/ingsw/gc14/View/TUI/TUI.java | 10 ++-- 8 files changed, 64 insertions(+), 86 deletions(-) diff --git a/src/main/java/it/polimi/ingsw/gc14/View/GUI/FireParticleSystem.java b/src/main/java/it/polimi/ingsw/gc14/View/GUI/FireParticleSystem.java index 5ce5c59..36fa456 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/GUI/FireParticleSystem.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/FireParticleSystem.java @@ -11,10 +11,18 @@ import java.util.Iterator; import java.util.List; import java.util.Random; +/** + * Ambient fire particle system for the main game scene. + * + *

Spawns embers, sparks, and dust from the four screen corners and animates + * them toward the center using an {@link AnimationTimer} capped at 30 fps. + * Add the pane returned by {@link #getPane()} to the scene root, then call + * {@link #start()} to begin the animation. + */ public class FireParticleSystem { private static final int MAX_PARTICLES = 30; - private static final long SPAWN_INTERVAL_NS = 1_00_000_000L; + private static final long SPAWN_INTERVAL_NS = 100_000_000L; private static final long FRAME_INTERVAL_NS = 1_000_000_000L / 30; // 30 fps cap private static final int STEPS_PER_FRAME = 3; // physics steps per frame → 3× speed @@ -34,8 +42,10 @@ public class FireParticleSystem { pane.setPickOnBounds(false); } + /** Returns the transparent overlay pane that holds all particle nodes. */ public Pane getPane() { return pane; } + /** Starts the animation timer. */ public void start() { timer = new AnimationTimer() { @Override @@ -48,6 +58,7 @@ public class FireParticleSystem { timer.start(); } + /** Stops the animation timer. */ public void stop() { if (timer != null) timer.stop(); } diff --git a/src/main/java/it/polimi/ingsw/gc14/View/GUI/GUI.java b/src/main/java/it/polimi/ingsw/gc14/View/GUI/GUI.java index af0906b..de43246 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/GUI/GUI.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/GUI.java @@ -34,24 +34,23 @@ import static it.polimi.ingsw.gc14.Model.GamePackage.GameStages.TOTEM_CHOICE; */ public class GUI extends Application implements IView { private Stage primaryStage; - MiniModel miniModel; + private volatile MiniModel miniModel; + private FXMLLoader loaderLogin; + private Scene loginScene; + private LoginFXMLController controllerLogin; - FXMLLoader loaderLogin; - Scene loginScene; - LoginFXMLController controllerLogin; + private FXMLLoader loaderTotem; + private Scene totemScene; + private TotemFXMLController controllerTotem; - FXMLLoader loaderTotem; - Scene totemScene; - TotemFXMLController controllerTotem; + private FXMLLoader loaderMain; + private Scene mainScene; + private MainFXMLController controllerMain; - FXMLLoader loaderMain; - Scene mainScene; - MainFXMLController controllerMain; - - FXMLLoader loaderLeaderboard; - Scene leaderboardScene; - LeaderboardFXMLController controllerLeaderboard; + private FXMLLoader loaderLeaderboard; + private Scene leaderboardScene; + private LeaderboardFXMLController controllerLeaderboard; private ClientController controller; @@ -164,7 +163,7 @@ public class GUI extends Application implements IView { */ @Override public void setModel(MiniModel miniModel) { - this.miniModel=miniModel; + this.miniModel = miniModel; } /** @@ -183,17 +182,15 @@ public class GUI extends Application implements IView { public void render() { Platform.runLater(() -> { if (miniModel == null) return; - synchronized (miniModel) { - if (controller.getMiniModel().currentState.getGameStage() == TOTEM_CHOICE) { - controllerTotem.render(); - fadeToScene(totemScene); - } else if (controller.getMiniModel().currentState.getGameStage() == ENDED) { - controllerLeaderboard.render(); - fadeToScene(leaderboardScene); - } else { - controllerMain.render(); - fadeToScene(mainScene); - } + if (controller.getMiniModel().currentState.getGameStage() == TOTEM_CHOICE) { + controllerTotem.render(); + fadeToScene(totemScene); + } else if (controller.getMiniModel().currentState.getGameStage() == ENDED) { + controllerLeaderboard.render(); + fadeToScene(leaderboardScene); + } else { + controllerMain.render(); + fadeToScene(mainScene); } }); } @@ -262,10 +259,8 @@ public class GUI extends Application implements IView { fadeToScene(loginScene); } else { if (miniModel == null) return; - synchronized (miniModel) { - controllerMain.isError = true; - controllerMain.render(); - } + controllerMain.setError(true); + controllerMain.render(); } }); } diff --git a/src/main/java/it/polimi/ingsw/gc14/View/GUI/LeaderboardFXMLController.java b/src/main/java/it/polimi/ingsw/gc14/View/GUI/LeaderboardFXMLController.java index 30ff38c..0010de4 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/GUI/LeaderboardFXMLController.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/LeaderboardFXMLController.java @@ -40,17 +40,15 @@ public class LeaderboardFXMLController { @FXML private StackPane rootPane; @FXML private VBox mainVBox; @FXML private VBox rankingList; - // titleLabel rimossa: il testo Winner/GameOver è aggiunto dinamicamente in render() + // title label removed; outcome text is added dynamically in render() private ClientController controller; private Popup popup; private VBox popupContent; - // Cache condivisa con MainFXMLController private static final Map imageCache = new HashMap<>(); private Scene loginScene; - private Stage primaryStage; private Runnable action; /** Returns a cached {@link Image} for the given classpath {@code path}, loading it on first access. */ @@ -69,8 +67,8 @@ public class LeaderboardFXMLController { */ public void setController(ClientController controller, Runnable action, Scene loginScene) { this.controller = controller; - this.loginScene=loginScene; - this.action=action; + this.loginScene = loginScene; + this.action = action; } @@ -109,7 +107,6 @@ public class LeaderboardFXMLController { outcomeLabel.setEffect(glow); rankingList.getChildren().add(outcomeLabel); - // Separatore Region sep = new Region(); sep.setPrefHeight(16); rankingList.getChildren().add(sep); @@ -153,7 +150,6 @@ public class LeaderboardFXMLController { row.setPadding(new Insets(14, 28, 14, 28)); row.setMaxWidth(Double.MAX_VALUE); - // Sfumatura scura semitrasparente come sfondo della riga LinearGradient gradient = new LinearGradient( 0, 0, 1, 0, true, CycleMethod.NO_CYCLE, new Stop(0.0, Color.rgb(0, 0, 0, 0.75)), @@ -168,7 +164,6 @@ public class LeaderboardFXMLController { addShadow(row); addHoverZoom(row); - // Posizione medaglia Label posLabel = new Label(position + "°"); posLabel.setMinWidth(55); String medalColor = switch (position) { @@ -180,13 +175,11 @@ public class LeaderboardFXMLController { posLabel.setStyle("-fx-font-size: 28px; -fx-font-weight: bold; -fx-text-fill: " + medalColor + ";"); - // Totem ImageView totem = new ImageView(loadImage( "/GUIImages/Totems/totem_" + player.getTotem().toString().toLowerCase(Locale.ROOT) + ".png")); totem.setFitHeight(55); totem.setPreserveRatio(true); - // Username Label nameLabel = new Label(player.getUserName()); nameLabel.setStyle( "-fx-font-size: 22px; -fx-font-weight: bold; -fx-text-fill: " + @@ -194,10 +187,8 @@ public class LeaderboardFXMLController { ); HBox.setHgrow(nameLabel, Priority.ALWAYS); - // Statistiche (icona + numero) HBox stats = createStatsBox(player); - // Punti prestigio in evidenza a destra HBox ppBox = new HBox(6); ppBox.setAlignment(Pos.CENTER); ImageView ppIcon = new ImageView(loadImage("/GUIImages/Icons/PrestigePoint.png")); @@ -238,7 +229,6 @@ public class LeaderboardFXMLController { icon.setPreserveRatio(true); Label label = new Label(value); label.setStyle("-fx-font-size: 16px; -fx-text-fill: #FFFFFF; -fx-font-weight: bold;"); - // Ombra sul testo per leggibilità extra DropShadow textShadow = new DropShadow(); textShadow.setColor(Color.rgb(0, 0, 0, 0.9)); textShadow.setRadius(4); @@ -274,7 +264,6 @@ public class LeaderboardFXMLController { private void openPlayerPopup(Player player) { popupContent.getChildren().clear(); - // Intestazione con totem + nome HBox header = new HBox(10); header.setAlignment(Pos.CENTER); ImageView totem = new ImageView(loadImage( @@ -286,7 +275,6 @@ public class LeaderboardFXMLController { header.getChildren().addAll(totem, nameLabel); popupContent.getChildren().add(header); - // Tutte le carte in un unico FlowPane, raggruppate per tipo con separatore icona String[][] types = { {"Artist", "artists"}, {"Gatherer", "gatherers"}, @@ -297,7 +285,6 @@ public class LeaderboardFXMLController { {"Building", "buildingCards"} }; - // Griglia unica: icona tipo | carte... per ogni riga VBox grid = new VBox(6); grid.setAlignment(Pos.CENTER_LEFT); boolean hasAnyCard = false; @@ -312,13 +299,11 @@ public class LeaderboardFXMLController { HBox row = new HBox(6); row.setAlignment(Pos.CENTER_LEFT); - // Icona tipo come intestazione riga ImageView typeIcon = new ImageView(loadImage("/GUIImages/Icons/"+typeName+".png")); typeIcon.setFitHeight(22); typeIcon.setPreserveRatio(true); row.getChildren().add(typeIcon); - // Carte piccole in riga for (PlayableCard card : cards) { ImageView img = new ImageView(loadImage("/GUIImages/Fronts/card-" + card.getIdIMG() + ".png")); img.setFitHeight(230); @@ -331,7 +316,7 @@ public class LeaderboardFXMLController { } if (!hasAnyCard) { - Label empty = new Label("Nessuna carta"); + Label empty = new Label("No cards"); empty.setStyle("-fx-font-size: 14px; -fx-text-fill: #888888;"); grid.getChildren().add(empty); } @@ -342,9 +327,8 @@ public class LeaderboardFXMLController { scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); scrollPane.setStyle("-fx-background: transparent; -fx-background-color: transparent;"); -// altezza massima = 80% della finestra Window window = rootPane.getScene().getWindow(); - scrollPane.setMaxHeight(window.getHeight() * 0.8); + scrollPane.setMaxHeight(window.getHeight() * 0.8); // cap scroll area to 80% of window height popupContent.getChildren().add(scrollPane); popup.show(window, 0, 0); popup.getScene().setFill(Color.TRANSPARENT); diff --git a/src/main/java/it/polimi/ingsw/gc14/View/GUI/LoginFXMLController.java b/src/main/java/it/polimi/ingsw/gc14/View/GUI/LoginFXMLController.java index 1801110..eb74087 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/GUI/LoginFXMLController.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/LoginFXMLController.java @@ -45,7 +45,6 @@ public class LoginFXMLController { private boolean isRMI = true; private ClientController controller; - // Definiamo lo pseudo-stato custom per il CSS corrispondente a ":active-protocol" private final PseudoClass activeProtocolPseudo = PseudoClass.getPseudoClass("active-protocol"); /** @@ -71,7 +70,6 @@ public class LoginFXMLController { protocolToggle.setOnMouseClicked(e -> switchProtocol()); protocolToggle.setStyle(protocolToggle.getStyle() + " -fx-cursor: hand;"); - // Inizializza lo stato grafico iniziale (RMI attivo) labelRMI.pseudoClassStateChanged(activeProtocolPseudo, true); labelTCP.pseudoClassStateChanged(activeProtocolPseudo, false); @@ -87,7 +85,7 @@ public class LoginFXMLController { tt.setToX(isRMI ? 0 : 110); tt.play(); - // Modifica in modo sicuro lo stato senza distruggere i parametri di layout dei nodi + // pseudoClassStateChanged preserves layout properties unlike direct style mutation labelRMI.pseudoClassStateChanged(activeProtocolPseudo, isRMI); labelTCP.pseudoClassStateChanged(activeProtocolPseudo, !isRMI); } @@ -95,10 +93,10 @@ public class LoginFXMLController { /** Validates form input and starts a background thread to connect to the server. */ @FXML private void onAccediClick() { - String nome = campoNome.getText().trim(); + String name = campoNome.getText().trim(); String ip = campoIP.getText().trim(); - if (nome.isEmpty()) { + if (name.isEmpty()) { showError("Please select a name."); return; } @@ -112,13 +110,13 @@ public class LoginFXMLController { } updateLoginButton(false); - controller.setMyUsername(nome); + controller.setMyUsername(name); new Thread(() -> { try { String localInterface = InterfaceResolver.resolveLocalInterface(ip); System.setProperty("java.rmi.server.hostname", localInterface); - connect(nome, ip, numPlayers, localInterface); + connect(name, ip, numPlayers, localInterface); } catch (Exception ex) { Platform.runLater(() -> { showError("Network error: " + ex.getMessage()); diff --git a/src/main/java/it/polimi/ingsw/gc14/View/GUI/MainFXMLController.java b/src/main/java/it/polimi/ingsw/gc14/View/GUI/MainFXMLController.java index 1a56c97..2f6ecbc 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/GUI/MainFXMLController.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/MainFXMLController.java @@ -69,7 +69,10 @@ public class MainFXMLController { private ClientController controller; /** {@code true} when the last received event was an error response. */ - public boolean isError; + private boolean isError; + + /** Sets the error flag; called by {@link GUI} before delegating to {@link #render()}. */ + public void setError(boolean error) { this.isError = error; } // ==== IMAGE CACHE ==== private static final Map imageCache = new HashMap<>(); @@ -215,7 +218,6 @@ public class MainFXMLController { card.getStyleClass().add("player-card"); card.setPadding(new Insets(4)); - // Riga 1: totem + nome HBox headerRow = new HBox(8); headerRow.setAlignment(Pos.CENTER); ImageView totem = new ImageView(loadImage("/GUIImages/Totems/totem_" @@ -233,7 +235,6 @@ public class MainFXMLController { } headerRow.getChildren().addAll(totem, usernameLabel); - // Riga 2: cibo + prestigio + building HBox statsRow = new HBox(10); statsRow.setAlignment(Pos.CENTER); @@ -269,7 +270,6 @@ public class MainFXMLController { statsRow.getChildren().addAll(foodBox, prestigeBox, buildingIcon); - // Righe 3-4: icone carte HBox iconsRow1 = new HBox(6); iconsRow1.setSpacing(20); iconsRow1.setAlignment(Pos.CENTER); @@ -287,7 +287,7 @@ public class MainFXMLController { }; List icons = new ArrayList<>(); - // aggiungo anche building come prima icona della lista per updateSidePanel + // building must be index 0; updateSidePanel iterates icons in ["building","artists",...] order icons.add(buildingIcon); for (int i = 0; i < iconTypes.length; i++) { @@ -406,7 +406,6 @@ public class MainFXMLController { icons.get(i).setEffect(empty ? new ColorAdjust() : null); } - // aggiorna current player highlight VBox card = playerCards.get(u); if(controller.getMiniModel().disconnectedPlayers.contains(u)) { @@ -432,7 +431,6 @@ public class MainFXMLController { card.setStyle(""); } - // animazione shake su errore if (isError && u.equals(controller.getMyUsername())) { TranslateTransition tt = new TranslateTransition(Duration.millis(56), card); tt.setFromX(0); @@ -611,7 +609,6 @@ public class MainFXMLController { for (int i = 0; i < controller.getMiniModel().orderLogicCard.getPlayerList().size(); i++) { OrderPlayer op = controller.getMiniModel().orderLogicCard.getPlayerList().get(i); - // Carica immagine totem come in createSlot ImageView totem = new ImageView(loadImage( "/GUIImages/Totems/totem_" + op.getPlayer().getTotem().toString().toLowerCase(Locale.ROOT) + ".png" )); @@ -626,10 +623,8 @@ public class MainFXMLController { overlay.prefHeightProperty().bind(card.heightProperty()); overlay.prefWidthProperty().bind(card.widthProperty()); - // Scala altezza totem proporzionalmente come in createSlot (0.127 della card) totem.fitHeightProperty().bind(card.heightProperty().multiply(0.323)); - // Posizione Y proporzionale, X fissa card.heightProperty().addListener((obs, ov, nv) -> { totem.setLayoutY(nv.doubleValue() * (yRatio-0.196)); }); @@ -637,7 +632,7 @@ public class MainFXMLController { totem.setLayoutX(nv.doubleValue() * 0.364); }); - // Inizializzazione immediata se già dimensionato + // initialize immediately if already laid out if (card.getHeight() > 0) totem.setLayoutY(card.getHeight() *(yRatio-0.196)); if (card.getWidth() > 0) totem.setLayoutX(card.getWidth() * 0.364); diff --git a/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java b/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java index a460663..8519e5f 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java @@ -25,8 +25,7 @@ import java.util.Locale; */ public class TotemFXMLController { - /** Background image view injected via FXML. */ - public ImageView backgroundImage; + @FXML private ImageView backgroundImage; @FXML private HBox mainHBox; @FXML private Label turnBanner; @FXML private Button confirmButton; @@ -157,7 +156,6 @@ public class TotemFXMLController { }); card.setOnMouseClicked(e -> { - // reset tutti mainHBox.getChildren().forEach(n -> { if (n instanceof VBox v) { StackPane f = (StackPane) v.getChildren().get(0); @@ -199,16 +197,13 @@ public class TotemFXMLController { /** Returns the CSS style string for the totem card frame, highlighting it when {@code selected}. */ private String frameStyle(boolean selected) { - return "-fx-border-color: " + (selected ? "#75FF79" : "rgba(200,120,20,0.28)") + ";" + - "-fx-border-width: " + (selected ? "0" : "0") + ";" + - "-fx-border-radius: 4 4 0 0; -fx-background-radius: 4 4 0 0;" + - (selected ? "-fx-effect: dropshadow(gaussian, #ffffff, 20, 0.33, 0, 0);" : ""); + return selected ? "-fx-effect: dropshadow(gaussian, #ffffff, 20, 0.33, 0, 0);" : ""; } /** Returns the CSS style string for the totem name label, brightening it when {@code selected}. */ private String nameStyle(boolean selected) { return "-fx-font-family: 'Cinzel'; -fx-font-size: 10; -fx-letter-spacing: 2;" + - "-fx-text-fill: " + (selected ? "#ffffff" : "#ffffff") + ";"; + "-fx-text-fill: " + (selected ? "#FFD700" : "#ffffff") + ";"; } /** Returns the totem name with only the first letter capitalised. */ diff --git a/src/main/java/it/polimi/ingsw/gc14/View/IView.java b/src/main/java/it/polimi/ingsw/gc14/View/IView.java index 6667024..4eeff12 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/IView.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/IView.java @@ -10,10 +10,10 @@ public interface IView { * * @param miniModel the latest mini model received from the server. */ - public void setModel(MiniModel miniModel); + void setModel(MiniModel miniModel); /** Re-renders the view based on the current mini model state. */ - public void render(); + void render(); /** * Displays an error message to the user. @@ -21,6 +21,6 @@ public interface IView { * @param error the error type. * @param message a human-readable description of the error. */ - public void showError(ErrorType error,String message); + void showError(ErrorType error, String message); } diff --git a/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java b/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java index bed2132..fd81838 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java @@ -78,9 +78,9 @@ public class TUI implements IView { public synchronized void render() { if (model == null) return; GameStages stage = model.currentState.getGameStage(); - if (stage.equals(GameStages.TOTEM_CHOICE)) { + if (stage == GameStages.TOTEM_CHOICE) { display(buildTotemsContent()); - } else if (stage.equals(GameStages.ENDED)) { + } else if (stage == GameStages.ENDED) { display(buildStandingContent() + "\nType 'rematch' to play again or 'quit' to exit"); } else { display(buildBoardContent()); @@ -106,7 +106,7 @@ public class TUI implements IView { */ public void showError(ErrorType error, String message) { String text; - if (error.equals(ErrorType.WRONG_ACTION) + if (error == ErrorType.WRONG_ACTION && model != null && model.currentState.getCurrentPlayer() != null && !model.currentState.getCurrentPlayer().getUserName().equals(username)) { @@ -169,7 +169,7 @@ public class TUI implements IView { } private String buildStandingContent() { - if (model.standingPlayers == null) return ""; + if (model.standingPlayers == null || model.standingPlayers.isEmpty()) return ""; StringBuilder sb = new StringBuilder(); int size = model.standingPlayers.size(); for (int i = 0; i < size / 2; i++) { @@ -253,7 +253,7 @@ public class TUI implements IView { /** Returns the display width of a string in terminal columns, stripping ANSI escape codes. */ private int visibleLength(String line) { - return AsciiTable.displayWidth(line.replaceAll("\033\\[[^m]*m", "")); + return AsciiTable.displayWidth(line); } // ── Board / players stamp helpers ─────────────────────────────────────────