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 5081e6b..2b1e3d3 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 @@ -1,6 +1,7 @@ package it.polimi.ingsw.gc14.View.GUI; import it.polimi.ingsw.gc14.Controller.ClientController; +import it.polimi.ingsw.gc14.Model.GamePackage.GameStages; import it.polimi.ingsw.gc14.Model.MiniModel; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.View.GUI.LoginFXMLController; @@ -32,6 +33,11 @@ public class GUI extends Application implements IView { MainFXMLController controllerMain; MiniModel miniModel; + FXMLLoader loaderLeaderboard; + Scene leaderboardScene; + LeaderboardFXMLController controllerLeaderboard; + + private ClientController controller; public GUI(Stage stage) { @@ -58,6 +64,12 @@ public class GUI extends Application implements IView { controllerMain.setController(controller); return controllerMain; }); + + loaderLeaderboard = new FXMLLoader(getClass().getResource("/GUIScene/standing.fxml")); + leaderboardScene = new Scene(loaderLeaderboard.load()); + controllerLeaderboard = loaderLeaderboard.getController(); + controllerLeaderboard.setController(controller); + mainScene = new Scene(loaderMain.load()); controllerMain = loaderMain.getController(); @@ -86,7 +98,12 @@ public class GUI extends Application implements IView { if (controller.miniModel.currentState.getGameStage() == TOTEM_CHOICE) { controllerTotem.render(); primaryStage.setScene(totemScene); - } else { + } + else if (controller.miniModel.currentState.getGameStage() == GameStages.ENDED) { + controllerLeaderboard.render(); + primaryStage.setScene(leaderboardScene); + primaryStage.setFullScreen(true); + }else { controllerMain.render(event); primaryStage.setScene(mainScene); } 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 new file mode 100644 index 0000000..0aa8e3b --- /dev/null +++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/LeaderboardFXMLController.java @@ -0,0 +1,357 @@ +package it.polimi.ingsw.gc14.View.GUI; + +import it.polimi.ingsw.gc14.Controller.ClientController; +import it.polimi.ingsw.gc14.Model.Player; +import javafx.animation.ScaleTransition; +import it.polimi.ingsw.gc14.Model.PlayableCard; +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.stage.Popup; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.effect.DropShadow; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.paint.LinearGradient; +import javafx.scene.paint.Stop; +import javafx.scene.paint.CycleMethod; +import javafx.scene.text.Font; +import javafx.stage.Window; +import javafx.util.Duration; + +import java.util.*; + +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() + + private ClientController controller; + + private Popup popup; + private VBox popupContent; + + // Cache condivisa con MainFXMLController + private static final Map imageCache = new HashMap<>(); + + private Image loadImage(String path) { + return imageCache.computeIfAbsent(path, + p -> new Image(getClass().getResourceAsStream(p))); + } + + + public void setController(ClientController controller) { + this.controller = controller; + } + + + @FXML + public void initialize() { + Font.loadFont(getClass().getResourceAsStream("/Fonts/InknutAntiqua-Regular.ttf"), 14); + mainVBox.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene != null) newScene.getRoot().applyCss(); + }); + renderBackground(); + initPopup(); + } + + + public void render() { + rankingList.getChildren().clear(); + + List sorted = new ArrayList<>(controller.miniModel.players.values()); + sorted.sort(Comparator.comparingInt(Player::getPrestigeValue).reversed()); + + boolean iWon = sorted.get(0).getUserName().equals(controller.myUsername); + + // Label grande Winner / Game Over + Label outcomeLabel = new Label(iWon ? "\uD83C\uDFC6 WINNER! \uD83C\uDFC6" : "GAME OVER"); + outcomeLabel.setStyle( + "-fx-font-size: 64px;" + + "-fx-font-weight: bold;" + + "-fx-text-fill: " + (iWon ? "#FFD700;" : "#FF4444;") + ); + DropShadow glow = new DropShadow(); + glow.setColor(iWon ? Color.rgb(255, 200, 0, 0.95) : Color.rgb(220, 0, 0, 0.95)); + glow.setRadius(35); + glow.setSpread(0.35); + outcomeLabel.setEffect(glow); + rankingList.getChildren().add(outcomeLabel); + + // Separatore + Region sep = new Region(); + sep.setPrefHeight(16); + rankingList.getChildren().add(sep); + + for (int i = 0; i < sorted.size(); i++) { + rankingList.getChildren().add(createPlayerRow(i + 1, sorted.get(i))); + } + } + + + // ==== EFFECTS ==== + private void addHoverZoom(Node node) { + ScaleTransition scaleUp = new ScaleTransition(Duration.millis(150), node); + scaleUp.setToX(1.02); + scaleUp.setToY(1.02); + ScaleTransition scaleDown = new ScaleTransition(Duration.millis(150), node); + scaleDown.setToX(1.0); + scaleDown.setToY(1.0); + node.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> { scaleUp.play(); node.setCursor(Cursor.HAND); }); + node.addEventHandler(MouseEvent.MOUSE_EXITED, e -> { scaleDown.play(); node.setCursor(Cursor.DEFAULT); }); + } + + private void addShadow(Node node) { + DropShadow shadow = new DropShadow(); + shadow.setColor(Color.rgb(0, 0, 0, 0.6)); + shadow.setRadius(12); + shadow.setOffsetX(3); + shadow.setOffsetY(3); + node.setEffect(shadow); + } + + + // ==== ROW ==== + private HBox createPlayerRow(int position, Player player) { + HBox row = new HBox(20); + row.setAlignment(Pos.CENTER_LEFT); + 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)), + new Stop(0.5, Color.rgb(0, 0, 0, 0.60)), + new Stop(1.0, Color.rgb(0, 0, 0, 0.75)) + ); + row.setBackground(new Background(new BackgroundFill( + gradient, + new CornerRadii(12), + Insets.EMPTY + ))); + addShadow(row); + addHoverZoom(row); + + // Posizione medaglia + Label posLabel = new Label(position + "°"); + posLabel.setMinWidth(55); + String medalColor = switch (position) { + case 1 -> "#FFD700"; // oro + case 2 -> "#C0C0C0"; // argento + case 3 -> "#CD7F32"; // bronzo + default -> "#EEEEEE"; + }; + posLabel.setStyle("-fx-font-size: 28px; -fx-font-weight: bold; -fx-text-fill: " + medalColor + ";"); + + + // Totem + ImageView totem = new ImageView(loadImage( + "/GUIImages/Totems/totem_" + player.totem.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: " + + (player.getUserName().equals(controller.myUsername) ? "#ff6b6b;" : "#FFFFFF;") + ); + 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")); + ppIcon.setFitHeight(45); + ppIcon.setPreserveRatio(true); + Label ppLabel = new Label(String.valueOf(player.getPrestigeValue())); + ppLabel.setStyle("-fx-font-size: 28px; -fx-font-weight: bold; -fx-text-fill: #FFD700;"); + ppBox.getChildren().addAll(ppIcon, ppLabel); + + row.getChildren().addAll(posLabel, totem, nameLabel, stats, ppBox); + row.setOnMouseClicked(e -> openPlayerPopup(player)); + return row; + } + + private HBox createStatsBox(Player player) { + HBox stats = new HBox(14); + stats.setAlignment(Pos.CENTER); + + stats.getChildren().add(createStatItem("/GUIImages/Icons/Food.png", String.valueOf(player.getFoodValue()), 26)); + stats.getChildren().add(createStatItem("/GUIImages/Icons/Artist.png", String.valueOf(player.artists.size()), 26)); + stats.getChildren().add(createStatItem("/GUIImages/Icons/Gatherer.png", String.valueOf(player.gatherers.size()), 26)); + stats.getChildren().add(createStatItem("/GUIImages/Icons/Inventor.png", String.valueOf(player.inventors.size()), 26)); + stats.getChildren().add(createStatItem("/GUIImages/Icons/Builder.png", String.valueOf(player.builders.size()), 26)); + stats.getChildren().add(createStatItem("/GUIImages/Icons/Shaman.png", String.valueOf(player.shamans.size()), 26)); + stats.getChildren().add(createStatItem("/GUIImages/Icons/Hunter.png", String.valueOf(player.hunters.size()), 26)); + stats.getChildren().add(createStatItem("/GUIImages/Icons/Building.png", String.valueOf(player.buildingCards.size()), 26)); + + return stats; + } + + private HBox createStatItem(String iconPath, String value, double iconHeight) { + HBox box = new HBox(4); + box.setAlignment(Pos.CENTER); + ImageView icon = new ImageView(loadImage(iconPath)); + icon.setFitHeight(iconHeight); + 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); + label.setEffect(textShadow); + box.getChildren().addAll(icon, label); + return box; + } + + + // ==== POPUP ==== + private void initPopup() { + popupContent = new VBox(12); + popupContent.setAlignment(Pos.CENTER); + popupContent.setPadding(new Insets(16)); + popupContent.setStyle( + "-fx-background-color: rgba(20,10,5,0.97);" + + "-fx-border-color: #8B4513;" + + "-fx-border-width: 3;" + + "-fx-border-radius: 14;" + + "-fx-background-radius: 14;" + ); + + popup = new Popup(); + popup.getContent().add(popupContent); + popup.setAutoHide(true); + popup.addEventHandler(Event.ANY, e -> { + if (popup.getScene() != null) popup.getScene().setFill(Color.TRANSPARENT); + }); + } + + 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( + "/GUIImages/Totems/totem_" + player.totem.toString().toLowerCase(Locale.ROOT) + ".png")); + totem.setFitHeight(30); + totem.setPreserveRatio(true); + Label nameLabel = new Label(player.getUserName()); + nameLabel.setStyle("-fx-font-size: 18px; -fx-font-weight: bold; -fx-text-fill: #FFD700;"); + 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"}, + {"Inventor", "inventors"}, + {"Builder", "builders"}, + {"Shaman", "shamans"}, + {"Hunter", "hunters"}, + {"Building", "buildingCards"} + }; + + // Griglia unica: icona tipo | carte... per ogni riga + VBox grid = new VBox(6); + grid.setAlignment(Pos.CENTER_LEFT); + boolean hasAnyCard = false; + + for (String[] type : types) { + String typeName = type[0]; + String field = type[1]; + ArrayList cards = getPlayerCards(player.getUserName(), field); + if (cards.isEmpty()) continue; + hasAnyCard = true; + + 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(160); + img.setPreserveRatio(true); + // Tooltip al hover: mostra la carta grande in un secondo popup + img.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> img.setFitHeight(180)); + img.addEventHandler(MouseEvent.MOUSE_EXITED, e -> img.setFitHeight(160)); + img.setCursor(Cursor.HAND); + row.getChildren().add(img); + } + + grid.getChildren().add(row); + } + + if (!hasAnyCard) { + Label empty = new Label("Nessuna carta"); + empty.setStyle("-fx-font-size: 14px; -fx-text-fill: #888888;"); + grid.getChildren().add(empty); + } + + grid.setMaxHeight(460); + grid.setMaxWidth(680); + popupContent.getChildren().add(grid); + + Window window = rootPane.getScene().getWindow(); + popup.show(window, 0, 0); + popup.getScene().setFill(Color.TRANSPARENT); + popup.setX(window.getX() + (window.getWidth() - popup.getWidth()) / 2); + popup.setY(window.getY() + (window.getHeight() - popup.getHeight()) / 2); + } + + private ArrayList getPlayerCards(String username, String type) { + Player p = controller.miniModel.players.get(username); + return switch (type) { + case "artists" -> new ArrayList<>(p.artists); + case "gatherers" -> new ArrayList<>(p.gatherers); + case "inventors" -> new ArrayList<>(p.inventors); + case "builders" -> new ArrayList<>(p.builders); + case "shamans" -> new ArrayList<>(p.shamans); + case "hunters" -> new ArrayList<>(p.hunters); + case "buildingCards" -> new ArrayList<>(p.buildingCards); + default -> new ArrayList<>(); + }; + } + + // ==== BACKGROUND ==== + private void renderBackground() { + BackgroundSize size = new BackgroundSize( + BackgroundSize.AUTO, BackgroundSize.AUTO, + false, false, true, true + ); + rootPane.setBackground(new Background(new BackgroundImage( + loadImage("/GUIImages/Background.png"), + BackgroundRepeat.NO_REPEAT, + BackgroundRepeat.NO_REPEAT, + BackgroundPosition.CENTER, + size + ))); + } + + + // ==== ACTIONS ==== + @FXML + private void onNewGame() { + //controller.requestNewGame(); + } +} diff --git a/src/main/resources/GUIImages/Icons/Building.png b/src/main/resources/GUIImages/Icons/Building.png new file mode 100644 index 0000000..b140952 Binary files /dev/null and b/src/main/resources/GUIImages/Icons/Building.png differ diff --git a/src/main/resources/GUIScene/standing.fxml b/src/main/resources/GUIScene/standing.fxml new file mode 100644 index 0000000..de0ffa8 --- /dev/null +++ b/src/main/resources/GUIScene/standing.fxml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + +