FIx: Fixed Coverage Screenshots And htmlRepot.

This commit is contained in:
GabrieleRadice
2026-06-19 22:57:43 +02:00
parent a4dc8b2540
commit 0129efe400
316 changed files with 4566 additions and 4497 deletions
@@ -0,0 +1,930 @@
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > MainFXMLController</title>
<style type="text/css">
@import "../../css/coverage.css";
@import "../../css/idea.min.css";
</style>
<script type="text/javascript" src="../../js/highlight.min.js"></script>
<script type="text/javascript" src="../../js/highlightjs-line-numbers.min.js"></script>
</head>
<body>
<div class="content">
<div class="breadCrumbs">
Current scope: <a href="../../index.html">all classes</a>
<span class="separator">|</span>
<a href="../index.html">it.polimi.ingsw.gc14.View.GUI</a>
</div>
<h1>Coverage Summary for Class: MainFXMLController (it.polimi.ingsw.gc14.View.GUI)</h1>
<table class="coverageStats">
<tr>
<th class="name">Class</th>
<th class="coverageStat
">
Class, %
</th>
<th class="coverageStat
">
Method, %
</th>
<th class="coverageStat
">
Branch, %
</th>
<th class="coverageStat
">
Line, %
</th>
</tr>
<tr>
<td class="name">MainFXMLController</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/1)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/53)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/137)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/448)
</span>
</td>
</tr>
</table>
<br/>
<br/>
<pre>
<code class="sourceCode" id="sourceCode">&nbsp;package it.polimi.ingsw.gc14.View.GUI;
&nbsp;
&nbsp;import it.polimi.ingsw.gc14.Controller.ClientController;
&nbsp;import it.polimi.ingsw.gc14.Model.*;
&nbsp;import it.polimi.ingsw.gc14.Model.Cards.BuildingCard;
&nbsp;import it.polimi.ingsw.gc14.Model.Cards.TribeCard;
&nbsp;import it.polimi.ingsw.gc14.Model.Orders.OrderPlayer;
&nbsp;import it.polimi.ingsw.gc14.Network.EventType;
&nbsp;import javafx.application.Platform;
&nbsp;import javafx.animation.PauseTransition;
&nbsp;import javafx.animation.ScaleTransition;
&nbsp;import javafx.animation.TranslateTransition;
&nbsp;import javafx.event.Event;
&nbsp;import javafx.fxml.FXML;
&nbsp;import javafx.geometry.Insets;
&nbsp;import javafx.geometry.Pos;
&nbsp;import javafx.scene.Cursor;
&nbsp;import javafx.scene.Node;
&nbsp;import javafx.scene.control.Button;
&nbsp;import javafx.scene.control.Label;
&nbsp;import javafx.scene.control.ScrollPane;
&nbsp;import javafx.scene.effect.ColorAdjust;
&nbsp;import javafx.scene.effect.DropShadow;
&nbsp;import javafx.scene.image.*;
&nbsp;import javafx.scene.input.MouseEvent;
&nbsp;import javafx.scene.layout.*;
&nbsp;import javafx.scene.paint.Color;
&nbsp;import javafx.scene.shape.*;
&nbsp;import javafx.scene.text.Font;
&nbsp;import javafx.stage.Popup;
&nbsp;import javafx.stage.Window;
&nbsp;import javafx.util.Duration;
&nbsp;
&nbsp;import java.util.*;
&nbsp;
&nbsp;/**
&nbsp; * FXML controller for the main game scene.
&nbsp; *
&nbsp; * &lt;p&gt;Renders the board (upper/lower tribe and building card rows), player stats,
&nbsp; * the player&#39;s hand, and action buttons (skip, details).
&nbsp; */
<b class="nc">&nbsp;public class MainFXMLController {</b>
&nbsp; /** Scrollable container holding all player side-panel cards. */
&nbsp; @FXML private ScrollPane playerSide;
&nbsp; /** Root HBox containing all main scene elements. */
&nbsp; @FXML private HBox mainHBox;
&nbsp; /** HBox holding the board slot and order cards. */
&nbsp; @FXML private HBox board;
&nbsp; /** HBox holding the upper tribe card row (and upper building stack). */
&nbsp; @FXML private HBox upperList;
&nbsp; /** HBox holding the lower tribe card row (and lower building stack). */
&nbsp; @FXML private HBox lowerList;
&nbsp; /** HBox displaying the local player&#39;s card piles. */
&nbsp; @FXML private HBox myHand;
&nbsp; /** Button that sends a skip-turn command. */
&nbsp; @FXML private Button skipBtn;
&nbsp; /** Button that opens the game details popup. */
&nbsp; @FXML private Button detailsBtn;
&nbsp; /** Button that disconnects and closes the client. */
&nbsp; @FXML private Button quitBtn;
&nbsp; /** Label showing the current round number and game stage. */
&nbsp; @FXML private Label infoText;
&nbsp; /** Maps each player&#39;s username to their current food-value label in the side panel. */
<b class="nc">&nbsp; private final Map&lt;String, Label&gt; foodLabels = new HashMap&lt;&gt;();</b>
&nbsp; /** Maps each player&#39;s username to their current prestige-value label in the side panel. */
<b class="nc">&nbsp; private final Map&lt;String, Label&gt; prestigeLabels = new HashMap&lt;&gt;();</b>
&nbsp; /** Maps each player&#39;s username to their player-card VBox in the side panel. */
<b class="nc">&nbsp; private final Map&lt;String, VBox&gt; playerCards = new HashMap&lt;&gt;();</b>
&nbsp; /** Maps each player&#39;s username to their ordered list of card-type icon ImageViews. */
<b class="nc">&nbsp; private final Map&lt;String, List&lt;ImageView&gt;&gt; iconViews = new HashMap&lt;&gt;();</b>
&nbsp; /** Shared popup used for card previews and building selection. */
&nbsp; private Popup popup;
&nbsp; /** HBox inside {@link #popup} that holds the displayed card images. */
&nbsp; private HBox popupCards;
&nbsp; /** Client controller for fetching game state and sending player actions. */
&nbsp; private ClientController controller;
&nbsp;
&nbsp; /** {@code true} when the last received event was an error response. */
&nbsp; private boolean isError;
&nbsp;
&nbsp; /**
&nbsp; * Sets the error flag; called by {@link GUI} before delegating to {@link #render()}.
&nbsp; *
&nbsp; * @param error {@code true} if the last received event was an error response.
&nbsp; */
<b class="nc">&nbsp; public void setError(boolean error) { this.isError = error; }</b>
&nbsp;
&nbsp; // ==== IMAGE CACHE ====
<b class="nc">&nbsp; private static final Map&lt;String, Image&gt; imageCache = new HashMap&lt;&gt;();</b>
&nbsp;
&nbsp; /** Returns a cached {@link Image} for the given classpath {@code path}, loading it on first access. */
&nbsp; private Image loadImage(String path) {
<b class="nc">&nbsp; return imageCache.computeIfAbsent(path,</b>
<b class="nc">&nbsp; p -&gt; new Image(Objects.requireNonNull(getClass().getResourceAsStream(p))));</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Injects the client controller and wires up the skip-turn button action.
&nbsp; *
&nbsp; * @param controller the client controller to use.
&nbsp; */
&nbsp; public void setController(ClientController controller) {
<b class="nc">&nbsp; this.controller = controller;</b>
<b class="nc">&nbsp; skipBtn.setOnAction(e -&gt; controller.skipTurn());</b>
&nbsp;
<b class="nc">&nbsp; PauseTransition quitTimer = new PauseTransition(javafx.util.Duration.seconds(3));</b>
<b class="nc">&nbsp; quitTimer.setOnFinished(e -&gt; {</b>
<b class="nc">&nbsp; quitBtn.setText(&quot;Quit&quot;);</b>
<b class="nc">&nbsp; quitBtn.setStyle(&quot;&quot;);</b>
&nbsp; });
<b class="nc">&nbsp; final boolean[] quitPending = {false};</b>
<b class="nc">&nbsp; quitBtn.setOnAction(e -&gt; {</b>
<b class="nc">&nbsp; if (quitPending[0]) {</b>
<b class="nc">&nbsp; controller.disconnect();</b>
<b class="nc">&nbsp; Platform.exit();</b>
<b class="nc">&nbsp; System.exit(0);</b>
&nbsp; } else {
<b class="nc">&nbsp; quitPending[0] = true;</b>
<b class="nc">&nbsp; quitBtn.setText(&quot;Sure?&quot;);</b>
<b class="nc">&nbsp; quitBtn.setStyle(&quot;-fx-background-color: #c0392b; -fx-text-fill: white;&quot;);</b>
<b class="nc">&nbsp; quitTimer.playFromStart();</b>
<b class="nc">&nbsp; quitTimer.setOnFinished(ev -&gt; {</b>
<b class="nc">&nbsp; quitPending[0] = false;</b>
<b class="nc">&nbsp; quitBtn.setText(&quot;Quit&quot;);</b>
<b class="nc">&nbsp; quitBtn.setStyle(&quot;&quot;);</b>
&nbsp; });
&nbsp; }
&nbsp; });
&nbsp; }
&nbsp;
&nbsp; /** Initializes the scene: loads fonts, sets up the popup, and registers input listeners. */
&nbsp; @FXML
&nbsp; public void initialize() {
<b class="nc">&nbsp; Font.loadFont(getClass().getResourceAsStream(&quot;/Fonts/InknutAntiqua-Regular.ttf&quot;), 14);</b>
&nbsp;
<b class="nc">&nbsp; addHoverZoom(skipBtn);</b>
<b class="nc">&nbsp; addHoverZoom(detailsBtn);</b>
<b class="nc">&nbsp; addHoverZoom(quitBtn);</b>
<b class="nc">&nbsp; popup = new Popup();</b>
<b class="nc">&nbsp; mainHBox.sceneProperty().addListener((obs, oldScene, newScene) -&gt; {</b>
<b class="nc">&nbsp; if (newScene != null) {</b>
<b class="nc">&nbsp; newScene.addEventFilter(MouseEvent.MOUSE_PRESSED, e -&gt; {</b>
<b class="nc">&nbsp; if (popup.isShowing()) popup.hide();</b>
&nbsp; });
<b class="nc">&nbsp; newScene.getRoot().applyCss();</b>
<b class="nc">&nbsp; newScene.windowProperty().addListener((o2, ow, nw) -&gt; {</b>
<b class="nc">&nbsp; if (nw != null) {</b>
<b class="nc">&nbsp; nw.focusedProperty().addListener((o3, wf, nf) -&gt; {</b>
<b class="nc">&nbsp; if (!nf) popup.hide();</b>
&nbsp; });
&nbsp; }
&nbsp; });
&nbsp; }
&nbsp; else {
<b class="nc">&nbsp; popup.hide();</b>
&nbsp; }
&nbsp; });
<b class="nc">&nbsp; initPopup();</b>
<b class="nc">&nbsp; detailsBtn.setOnAction(e -&gt; openDetailsPopup());</b>
<b class="nc">&nbsp; renderBackground();</b>
<b class="nc">&nbsp; isError=false;</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== RENDER ====
&nbsp; /** Incrementally re-renders only the parts of the scene affected by the last network event. */
&nbsp; public void render() {
<b class="nc">&nbsp; if(foodLabels.isEmpty() || controller.getMiniModel().lastEvent == null</b>
<b class="nc">&nbsp; || controller.getMiniModel().lastEvent.getEventType().equals(EventType.TOTEM_CHOICE)</b>
<b class="nc">&nbsp; || controller.getMiniModel().lastEvent.getEventType().equals(EventType.RECONNECT_PLAYER)</b>
<b class="nc">&nbsp; || controller.getMiniModel().lastEvent.getEventType().equals(EventType.DISCONNECTED_PLAYER))</b>
<b class="nc">&nbsp; buildSidePanel();</b>
<b class="nc">&nbsp; if(isError)</b>
&nbsp; {
<b class="nc">&nbsp; updateSidePanel();</b>
<b class="nc">&nbsp; isError=false;</b>
&nbsp; return;
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent==null ||controller.getMiniModel().lastEvent.getEventType().equals(EventType.TOTEM_CHOICE) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.NEXT_ROUND) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.RECONNECT_PLAYER) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.DISCONNECTED_PLAYER)) {</b>
<b class="nc">&nbsp; renderMyHand();</b>
<b class="nc">&nbsp; renderUpper();</b>
<b class="nc">&nbsp; renderBoard();</b>
<b class="nc">&nbsp; renderLower();</b>
<b class="nc">&nbsp; updateSidePanel();</b>
&nbsp; return;
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_UPPER_TRIBE) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_UPPER_BUILD))</b>
&nbsp; {
<b class="nc">&nbsp; renderUpper();</b>
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_LOWER_TRIBE) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_LOWER_BUILD))</b>
&nbsp; {
<b class="nc">&nbsp; renderLower();</b>
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent.getUsername().equals(controller.getMyUsername()))</b>
&nbsp; {
<b class="nc">&nbsp; renderMyHand();</b>
&nbsp; }
<b class="nc">&nbsp; renderBoard();</b>
<b class="nc">&nbsp; updateSidePanel();</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== EFFECTS ====
&nbsp; /** Binds a rounded-rectangle clip to {@code img} so its corners are cropped. */
&nbsp; private void addClip(ImageView img) {
<b class="nc">&nbsp; Rectangle clip = new Rectangle();</b>
<b class="nc">&nbsp; clip.setArcWidth(20);</b>
<b class="nc">&nbsp; clip.setArcHeight(20);</b>
<b class="nc">&nbsp; img.setClip(clip);</b>
<b class="nc">&nbsp; clip.heightProperty().bind(img.layoutBoundsProperty().map(b -&gt; b.getHeight()));</b>
<b class="nc">&nbsp; clip.widthProperty().bind(img.layoutBoundsProperty().map(b -&gt; b.getWidth()));</b>
&nbsp; }
&nbsp;
&nbsp; /** Scales {@code node} to 1.1× on hover and restores on exit; sets a hand cursor. */
&nbsp; private void addHoverZoom(Node node) {
<b class="nc">&nbsp; ScaleTransition scaleUp = new ScaleTransition(Duration.millis(150), node);</b>
<b class="nc">&nbsp; scaleUp.setToX(1.1);</b>
<b class="nc">&nbsp; scaleUp.setToY(1.1);</b>
<b class="nc">&nbsp; ScaleTransition scaleDown = new ScaleTransition(Duration.millis(150), node);</b>
<b class="nc">&nbsp; scaleDown.setToX(1.0);</b>
<b class="nc">&nbsp; scaleDown.setToY(1.0);</b>
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_ENTERED, e -&gt; { scaleUp.play(); node.setCursor(Cursor.HAND); });</b>
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_EXITED, e -&gt; { scaleDown.play(); node.setCursor(Cursor.DEFAULT); });</b>
&nbsp; }
&nbsp;
&nbsp; /** Applies a drop-shadow to {@code node} that intensifies on hover. */
&nbsp; private void addShadow(Node node) {
<b class="nc">&nbsp; DropShadow shadow = new DropShadow();</b>
<b class="nc">&nbsp; shadow.setColor(Color.rgb(0, 0, 0, 0.3));</b>
<b class="nc">&nbsp; shadow.setRadius(6);</b>
<b class="nc">&nbsp; shadow.setOffsetX(2);</b>
<b class="nc">&nbsp; shadow.setOffsetY(2);</b>
<b class="nc">&nbsp; node.setEffect(shadow);</b>
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_ENTERED, e -&gt; {</b>
<b class="nc">&nbsp; shadow.setRadius(16);</b>
<b class="nc">&nbsp; shadow.setOffsetX(4);</b>
<b class="nc">&nbsp; shadow.setOffsetY(4);</b>
<b class="nc">&nbsp; shadow.setColor(Color.rgb(0, 0, 0, 0.5));</b>
&nbsp; });
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_EXITED, e -&gt; {</b>
<b class="nc">&nbsp; shadow.setRadius(6);</b>
<b class="nc">&nbsp; shadow.setOffsetX(2);</b>
<b class="nc">&nbsp; shadow.setOffsetY(2);</b>
<b class="nc">&nbsp; shadow.setColor(Color.rgb(0, 0, 0, 0.3));</b>
&nbsp; });
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== ELEMENTS ====
&nbsp;
&nbsp; /** Builds the side-panel card widget for {@code player} with totem, stats, and card-type icons. */
&nbsp; private VBox buildPlayerCard(Player player) {
<b class="nc">&nbsp; VBox card = new VBox(3);</b>
<b class="nc">&nbsp; VBox.setMargin(card, new Insets(15, 15, 0, 15));</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card&quot;);</b>
<b class="nc">&nbsp; card.setPadding(new Insets(4));</b>
&nbsp;
<b class="nc">&nbsp; HBox headerRow = new HBox(8);</b>
<b class="nc">&nbsp; headerRow.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; ImageView totem = new ImageView(loadImage(&quot;/GUIImages/Totems/totem_&quot;</b>
<b class="nc">&nbsp; + player.getTotem().toString().toLowerCase(Locale.ROOT) + &quot;.png&quot;));</b>
<b class="nc">&nbsp; totem.setFitHeight(20);</b>
<b class="nc">&nbsp; totem.setPreserveRatio(true);</b>
<b class="nc">&nbsp; Label usernameLabel = new Label(player.getUserName());</b>
<b class="nc">&nbsp; usernameLabel.getStyleClass().add(&quot;label-medium&quot;);</b>
<b class="nc">&nbsp; usernameLabel.setStyle(&quot;-fx-text-fill: #711423;&quot;);</b>
<b class="nc">&nbsp; if (player.getUserName().equals(controller.getMyUsername())) {</b>
<b class="nc">&nbsp; usernameLabel.setText(usernameLabel.getText() + &quot; (you)&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; if (controller.getMiniModel().disconnectedPlayers.contains(player.getUserName())) {</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card-crashed&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; headerRow.getChildren().addAll(totem, usernameLabel);</b>
&nbsp;
<b class="nc">&nbsp; HBox statsRow = new HBox(10);</b>
<b class="nc">&nbsp; statsRow.setAlignment(Pos.CENTER);</b>
&nbsp;
<b class="nc">&nbsp; HBox foodBox = new HBox(4);</b>
<b class="nc">&nbsp; foodBox.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; ImageView foodIcon = new ImageView(loadImage(&quot;/GUIImages/Icons/Food.png&quot;));</b>
<b class="nc">&nbsp; foodIcon.setFitHeight(20);</b>
<b class="nc">&nbsp; foodIcon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; Label foodLabel = new Label(String.valueOf(player.getFoodValue()));</b>
<b class="nc">&nbsp; foodLabel.getStyleClass().add(&quot;label-small&quot;);</b>
<b class="nc">&nbsp; foodLabels.put(player.getUserName(), foodLabel);</b>
<b class="nc">&nbsp; foodBox.getChildren().addAll(foodIcon, foodLabel);</b>
&nbsp;
<b class="nc">&nbsp; HBox prestigeBox = new HBox(1);</b>
<b class="nc">&nbsp; prestigeBox.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; ImageView ppIcon = new ImageView(loadImage(&quot;/GUIImages/Icons/PrestigePoint.png&quot;));</b>
<b class="nc">&nbsp; ppIcon.setFitHeight(35);</b>
<b class="nc">&nbsp; ppIcon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; Label prestigeLabel = new Label(String.valueOf(player.getPrestigeValue()));</b>
<b class="nc">&nbsp; prestigeLabel.getStyleClass().add(&quot;label-small&quot;);</b>
<b class="nc">&nbsp; prestigeLabels.put(player.getUserName(), prestigeLabel);</b>
<b class="nc">&nbsp; prestigeBox.getChildren().addAll(ppIcon, prestigeLabel);</b>
&nbsp;
<b class="nc">&nbsp; ImageView buildingIcon = new ImageView(loadImage(&quot;/GUIImages/Icons/Building.png&quot;));</b>
<b class="nc">&nbsp; buildingIcon.setFitWidth(28);</b>
<b class="nc">&nbsp; buildingIcon.setFitHeight(28);</b>
<b class="nc">&nbsp; buildingIcon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; buildingIcon.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; ArrayList&lt;PlayableCard&gt; cards = getPlayerCards(player.getUserName(), &quot;building&quot;);</b>
<b class="nc">&nbsp; if (!cards.isEmpty()) openPopup(cards);</b>
&nbsp; });
<b class="nc">&nbsp; addHoverZoom(buildingIcon);</b>
&nbsp;
<b class="nc">&nbsp; statsRow.getChildren().addAll(foodBox, prestigeBox, buildingIcon);</b>
&nbsp;
<b class="nc">&nbsp; HBox iconsRow1 = new HBox(6);</b>
<b class="nc">&nbsp; iconsRow1.setSpacing(20);</b>
<b class="nc">&nbsp; iconsRow1.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; HBox iconsRow2 = new HBox(6);</b>
<b class="nc">&nbsp; iconsRow2.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; iconsRow2.setSpacing(20);</b>
&nbsp;
<b class="nc">&nbsp; String[][] iconTypes = {</b>
&nbsp; {&quot;Artist&quot;, &quot;artists&quot;},
&nbsp; {&quot;Gatherer&quot;, &quot;gatherers&quot;},
&nbsp; {&quot;Inventor&quot;, &quot;inventors&quot;},
&nbsp; {&quot;Builder&quot;, &quot;builders&quot;},
&nbsp; {&quot;Shaman&quot;, &quot;shamans&quot;},
&nbsp; {&quot;Hunter&quot;, &quot;hunters&quot;}
&nbsp; };
&nbsp;
<b class="nc">&nbsp; List&lt;ImageView&gt; icons = new ArrayList&lt;&gt;();</b>
&nbsp; // building must be index 0; updateSidePanel iterates icons in [&quot;building&quot;,&quot;artists&quot;,...] order
<b class="nc">&nbsp; icons.add(buildingIcon);</b>
&nbsp;
<b class="nc">&nbsp; for (int i = 0; i &lt; iconTypes.length; i++) {</b>
<b class="nc">&nbsp; String iconName = iconTypes[i][0];</b>
<b class="nc">&nbsp; String fieldName = iconTypes[i][1];</b>
&nbsp;
<b class="nc">&nbsp; ImageView icon = new ImageView(loadImage(&quot;/GUIImages/Icons/&quot; + iconName + &quot;.png&quot;));</b>
<b class="nc">&nbsp; icon.setFitWidth(28);</b>
<b class="nc">&nbsp; icon.setFitHeight(28);</b>
<b class="nc">&nbsp; icon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; icon.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; ArrayList&lt;PlayableCard&gt; cards = getPlayerCards(player.getUserName(), fieldName);</b>
<b class="nc">&nbsp; if (!cards.isEmpty()) openPopup(cards);</b>
&nbsp; });
<b class="nc">&nbsp; addHoverZoom(icon);</b>
<b class="nc">&nbsp; icons.add(icon);</b>
&nbsp;
<b class="nc">&nbsp; if (i &lt; 3) iconsRow1.getChildren().add(icon);</b>
<b class="nc">&nbsp; else iconsRow2.getChildren().add(icon);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; iconViews.put(player.getUserName(), icons);</b>
&nbsp;
<b class="nc">&nbsp; card.getChildren().addAll(headerRow, statsRow, iconsRow1, iconsRow2);</b>
<b class="nc">&nbsp; return card;</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a {@link StackPane} containing the order card image for a game of {@code num} players. */
&nbsp; private StackPane createOrder(String num) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Orders/order-&quot; + num + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.fitHeightProperty().bind(board.sceneProperty().get().heightProperty().subtract(56).divide(4).multiply(0.94));</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; return new StackPane(img);</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a {@link StackPane} for a board slot, optionally overlaying the occupying player&#39;s totem. */
&nbsp; private StackPane createSlot(Slot slot, boolean withZoom, boolean withShadow) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Fronts/card-&quot; + slot.getSlotId() + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; img.fitHeightProperty().bind(board.sceneProperty().get().heightProperty().subtract(56).divide(4).multiply(0.94));</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
&nbsp;
<b class="nc">&nbsp; Player player = controller.getMiniModel().slotPlayerMap.get(slot);</b>
<b class="nc">&nbsp; if (player != null &amp;&amp; controller.getMiniModel().getPositionByUsername(player.getUserName()) != -1) {</b>
<b class="nc">&nbsp; ImageView totem = new ImageView(loadImage(&quot;/GUIImages/Totems/totem_&quot; + player.getTotem().toString().toLowerCase(Locale.ROOT) + &quot;.png&quot;));</b>
<b class="nc">&nbsp; totem.fitHeightProperty().bind(img.fitHeightProperty().multiply(0.332));</b>
<b class="nc">&nbsp; totem.setPreserveRatio(true);</b>
<b class="nc">&nbsp; StackPane.setAlignment(totem, Pos.TOP_LEFT);</b>
<b class="nc">&nbsp; img.fitHeightProperty().addListener((obs, ov, nv) -&gt; {</b>
<b class="nc">&nbsp; StackPane.setMargin(totem, new Insets(0, 0, 0, 0.224 * nv.doubleValue()));</b>
&nbsp; });
<b class="nc">&nbsp; if (img.getFitHeight() &gt; 0) {</b>
<b class="nc">&nbsp; StackPane.setMargin(totem, new Insets(0, 0, 0, 0.224 * img.getFitHeight()));</b>
&nbsp; }
<b class="nc">&nbsp; wrapper.getChildren().add(totem);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (withShadow) addShadow(wrapper);</b>
<b class="nc">&nbsp; if (withZoom) addHoverZoom(wrapper);</b>
<b class="nc">&nbsp; return wrapper;</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a {@link StackPane} for a playable card, scaling its height to a fraction of {@code parent}&#39;s scene. */
&nbsp; private StackPane createCard(PlayableCard card, boolean withZoom, boolean withShadow, Region parent) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Fronts/card-&quot; + card.getIdIMG() + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; if(controller.getMiniModel().players.size()==5)</b>
&nbsp; {
<b class="nc">&nbsp; img.fitHeightProperty().bind(parent.getScene().heightProperty().subtract(56).divide(4).multiply(0.85));</b>
&nbsp; }
&nbsp; else
<b class="nc">&nbsp; img.fitHeightProperty().bind(parent.getScene().heightProperty().subtract(56).divide(4).multiply(0.90));</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
<b class="nc">&nbsp; if (withShadow) addShadow(wrapper);</b>
<b class="nc">&nbsp; if (withZoom) addHoverZoom(wrapper);</b>
<b class="nc">&nbsp; return wrapper;</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp;
&nbsp; // ==== GROUPS ====
&nbsp; /** Builds the full side panel from scratch, creating a player card for each connected player. */
&nbsp; private void buildSidePanel() {
<b class="nc">&nbsp; infoText.setText(&quot;Round: &quot;+ controller.getMiniModel().currentState.getRound() + &quot;&quot; + controller.getMiniModel().currentState.getGameStage().toString());</b>
<b class="nc">&nbsp; VBox playerList = new VBox();</b>
<b class="nc">&nbsp; playerList.setFillWidth(true);</b>
<b class="nc">&nbsp; for (Player p : controller.getMiniModel().players.values()) {</b>
<b class="nc">&nbsp; VBox card = buildPlayerCard(p);</b>
<b class="nc">&nbsp; playerCards.put(p.getUserName(), card);</b>
<b class="nc">&nbsp; playerList.getChildren().add(card);</b>
&nbsp; }
<b class="nc">&nbsp; playerSide.setContent(playerList);</b>
&nbsp; }
&nbsp;
&nbsp; /** Updates food/prestige labels, current-player highlight, card-icon opacity, and plays error shake if needed. */
&nbsp; private void updateSidePanel() {
<b class="nc">&nbsp; infoText.setText(&quot;Round: &quot;+ controller.getMiniModel().currentState.getRound() + &quot;&quot; + controller.getMiniModel().currentState.getGameStage().toString());</b>
<b class="nc">&nbsp; if (controller.getMiniModel().currentState.getCurrentPlayer() == null) return;</b>
<b class="nc">&nbsp; String current = controller.getMiniModel().currentState.getCurrentPlayer().getUserName();</b>
<b class="nc">&nbsp; String[] fields = {&quot;building&quot;, &quot;artists&quot;, &quot;gatherers&quot;, &quot;inventors&quot;, &quot;builders&quot;, &quot;shamans&quot;, &quot;hunters&quot;};</b>
&nbsp;
<b class="nc">&nbsp; for (Player p : controller.getMiniModel().players.values()) {</b>
<b class="nc">&nbsp; String u = p.getUserName();</b>
&nbsp;
<b class="nc">&nbsp; foodLabels.get(u).setText(String.valueOf(p.getFoodValue()));</b>
<b class="nc">&nbsp; prestigeLabels.get(u).setText(String.valueOf(p.getPrestigeValue()));</b>
&nbsp;
<b class="nc">&nbsp; List&lt;ImageView&gt; icons = iconViews.get(u);</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; icons.size(); i++) {</b>
<b class="nc">&nbsp; boolean empty = getPlayerCards(u, fields[i]).isEmpty();</b>
<b class="nc">&nbsp; icons.get(i).setOpacity(empty ? 0.3 : 1.0);</b>
<b class="nc">&nbsp; icons.get(i).setEffect(empty ? new ColorAdjust() : null);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; VBox card = playerCards.get(u);</b>
<b class="nc">&nbsp; if(controller.getMiniModel().disconnectedPlayers.contains(u))</b>
&nbsp; {
<b class="nc">&nbsp; card.getStyleClass().clear();</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card-crashed&quot;);</b>
&nbsp; }
&nbsp; else {
<b class="nc">&nbsp; card.getStyleClass().clear();</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card&quot;);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (u.equals(current)) {</b>
<b class="nc">&nbsp; card.setStyle(&quot;-fx-effect: dropshadow(gaussian, #fff8dc, 15, 0.10, 0, 0)&quot;);</b>
<b class="nc">&nbsp; ScaleTransition st = new ScaleTransition(Duration.millis(150), card);</b>
<b class="nc">&nbsp; st.setToX(1.1);</b>
<b class="nc">&nbsp; st.setToY(1.1);</b>
<b class="nc">&nbsp; st.play();</b>
&nbsp; } else {
<b class="nc">&nbsp; ScaleTransition st = new ScaleTransition(Duration.millis(150), card);</b>
<b class="nc">&nbsp; st.setToX(1.0);</b>
<b class="nc">&nbsp; st.setToY(1.0);</b>
<b class="nc">&nbsp; st.play();</b>
<b class="nc">&nbsp; card.setStyle(&quot;&quot;);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (isError &amp;&amp; u.equals(controller.getMyUsername())) {</b>
<b class="nc">&nbsp; TranslateTransition tt = new TranslateTransition(Duration.millis(56), card);</b>
<b class="nc">&nbsp; tt.setFromX(0);</b>
<b class="nc">&nbsp; tt.setByX(10);</b>
<b class="nc">&nbsp; tt.setCycleCount(6);</b>
<b class="nc">&nbsp; tt.setAutoReverse(true);</b>
<b class="nc">&nbsp; tt.play();</b>
&nbsp; }
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Renders the top card of the upper building stack with a count badge; clicking opens the selection popup. */
&nbsp; private void drawUpperBuilding() {
<b class="nc">&nbsp; ArrayList&lt;BuildingCard&gt; cardList = new ArrayList&lt;&gt;(controller.getMiniModel().upperListBuildingCards);</b>
<b class="nc">&nbsp; if (!cardList.isEmpty()) {</b>
<b class="nc">&nbsp; StackPane img = createCard(cardList.getLast(), true, true, upperList);</b>
<b class="nc">&nbsp; Label label = new Label(String.valueOf(cardList.size()));</b>
<b class="nc">&nbsp; label.setTranslateY(-56);</b>
<b class="nc">&nbsp; label.setTranslateX(10);</b>
<b class="nc">&nbsp; label.getStyleClass().add(&quot;label-large&quot;);</b>
<b class="nc">&nbsp; StackPane.setAlignment(label, Pos.TOP_RIGHT);</b>
<b class="nc">&nbsp; img.getChildren().add(label);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; openPopupUpperBuilding(cardList));</b>
<b class="nc">&nbsp; upperList.getChildren().add(img);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Renders the top card of the lower building stack with a count badge; clicking opens the selection popup. */
&nbsp; private void drawLowerBuilding() {
<b class="nc">&nbsp; ArrayList&lt;BuildingCard&gt; cardList = new ArrayList&lt;&gt;(controller.getMiniModel().lowerListBuildingCards);</b>
<b class="nc">&nbsp; if (!cardList.isEmpty()) {</b>
<b class="nc">&nbsp; StackPane img = createCard(cardList.getLast(), true, true, lowerList);</b>
<b class="nc">&nbsp; Label label = new Label(String.valueOf(cardList.size()));</b>
<b class="nc">&nbsp; label.setTranslateY(-56);</b>
<b class="nc">&nbsp; label.setTranslateX(10);</b>
<b class="nc">&nbsp; label.getStyleClass().add(&quot;label-large&quot;);</b>
<b class="nc">&nbsp; StackPane.setAlignment(label, Pos.TOP_RIGHT);</b>
<b class="nc">&nbsp; img.getChildren().add(label);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; openPopupLowerBuilding(cardList));</b>
<b class="nc">&nbsp; lowerList.getChildren().add(img);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Renders a single card-type pile in the hand area, or an invisible placeholder if the list is empty. */
&nbsp; private void drawMyHandList(ArrayList&lt;? extends PlayableCard&gt; cardList) {
<b class="nc">&nbsp; if (!cardList.isEmpty()) {</b>
<b class="nc">&nbsp; StackPane img = createCard(cardList.getLast(), true, true, myHand);</b>
<b class="nc">&nbsp; Label label = new Label(String.valueOf(cardList.size()));</b>
<b class="nc">&nbsp; label.setTranslateY(-56);</b>
<b class="nc">&nbsp; label.setTranslateX(10);</b>
<b class="nc">&nbsp; label.getStyleClass().add(&quot;label-large&quot;);</b>
<b class="nc">&nbsp; StackPane.setAlignment(label, Pos.TOP_RIGHT);</b>
<b class="nc">&nbsp; img.getChildren().add(label);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; openPopup(new ArrayList&lt;&gt;(cardList)));</b>
<b class="nc">&nbsp; myHand.getChildren().add(img);</b>
&nbsp; } else {
<b class="nc">&nbsp; Region placeholder = new Region();</b>
<b class="nc">&nbsp; placeholder.prefHeightProperty().bind(myHand.getScene().heightProperty().subtract(56).divide(4).multiply(0.90));</b>
<b class="nc">&nbsp; placeholder.prefWidthProperty().bind(placeholder.prefHeightProperty().multiply(0.675));</b>
<b class="nc">&nbsp; placeholder.setStyle(&quot;-fx-background-color: transparent;&quot;);</b>
<b class="nc">&nbsp; myHand.getChildren().add(placeholder);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Returns a copy of the named card collection for {@code username}. */
&nbsp; private ArrayList&lt;PlayableCard&gt; getPlayerCards(String username, String type) {
<b class="nc">&nbsp; Player p = controller.getMiniModel().players.get(username);</b>
<b class="nc">&nbsp; return switch (type) {</b>
<b class="nc">&nbsp; case &quot;artists&quot; -&gt; new ArrayList&lt;&gt;(p.getArtists());</b>
<b class="nc">&nbsp; case &quot;gatherers&quot; -&gt; new ArrayList&lt;&gt;(p.getGatherers());</b>
<b class="nc">&nbsp; case &quot;inventors&quot; -&gt; new ArrayList&lt;&gt;(p.getInventors());</b>
<b class="nc">&nbsp; case &quot;builders&quot; -&gt; new ArrayList&lt;&gt;(p.getBuilders());</b>
<b class="nc">&nbsp; case &quot;shamans&quot; -&gt; new ArrayList&lt;&gt;(p.getShamans());</b>
<b class="nc">&nbsp; case &quot;hunters&quot; -&gt; new ArrayList&lt;&gt;(p.getHunters());</b>
<b class="nc">&nbsp; case &quot;building&quot; -&gt; new ArrayList&lt;&gt;(p.getBuildingCards());</b>
<b class="nc">&nbsp; default -&gt; new ArrayList&lt;&gt;();</b>
&nbsp; };
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== ABSOLUTES ====
&nbsp; /** Clears and rebuilds the upper tribe row with click handlers for drawing, then appends the upper building stack. */
&nbsp; private void renderUpper() {
<b class="nc">&nbsp; upperList.setSpacing(14);</b>
<b class="nc">&nbsp; upperList.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; upperList.getChildren().clear();</b>
&nbsp;
<b class="nc">&nbsp; int i = 0;</b>
<b class="nc">&nbsp; for (TribeCard card : controller.getMiniModel().upperListTribeCards) {</b>
<b class="nc">&nbsp; final int index = i;</b>
&nbsp; StackPane img;
<b class="nc">&nbsp; if(!card.isEventCard()) {</b>
<b class="nc">&nbsp; img = createCard(card, true, true, upperList);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; controller.drawUpperTribeCard(index));</b>
&nbsp; } else {
<b class="nc">&nbsp; img = createCard(card, false, false, upperList);</b>
&nbsp; }
<b class="nc">&nbsp; upperList.getChildren().add(img);</b>
<b class="nc">&nbsp; i++;</b>
&nbsp; }
<b class="nc">&nbsp; drawUpperBuilding();</b>
&nbsp; }
&nbsp;
&nbsp; /** Clears and rebuilds the lower tribe row with click handlers for drawing, then appends the lower building stack. */
&nbsp; private void renderLower() {
<b class="nc">&nbsp; lowerList.setSpacing(14);</b>
<b class="nc">&nbsp; lowerList.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; lowerList.getChildren().clear();</b>
&nbsp;
<b class="nc">&nbsp; int i = 0;</b>
<b class="nc">&nbsp; for (TribeCard card : controller.getMiniModel().lowerListTribeCards) {</b>
<b class="nc">&nbsp; final int index = i;</b>
&nbsp; StackPane img;
<b class="nc">&nbsp; if(!card.isEventCard()) {</b>
<b class="nc">&nbsp; img = createCard(card, true, true, lowerList);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; controller.drawLowerTribeCard(index));</b>
&nbsp; } else {
<b class="nc">&nbsp; img = createCard(card, false, false, lowerList);</b>
&nbsp; }
<b class="nc">&nbsp; lowerList.getChildren().add(img);</b>
<b class="nc">&nbsp; i++;</b>
&nbsp; }
<b class="nc">&nbsp; drawLowerBuilding();</b>
&nbsp; }
&nbsp;
&nbsp; /** Sets the full-cover background image on the main HBox container. */
&nbsp; private void renderBackground() {
<b class="nc">&nbsp; BackgroundSize size = new BackgroundSize(</b>
&nbsp; BackgroundSize.AUTO, BackgroundSize.AUTO,
&nbsp; false, false, true, true
&nbsp; );
<b class="nc">&nbsp; mainHBox.setBackground(new Background(new BackgroundImage(</b>
<b class="nc">&nbsp; loadImage(&quot;/GUIImages/Background.png&quot;),</b>
&nbsp; BackgroundRepeat.NO_REPEAT,
&nbsp; BackgroundRepeat.NO_REPEAT,
&nbsp; BackgroundPosition.CENTER,
&nbsp; size
&nbsp; )));
&nbsp; }
&nbsp;
&nbsp; /** Clears the board area and orchestrates deck, order card, and slot-map rendering. */
&nbsp; private void renderBoard() {
<b class="nc">&nbsp; board.setSpacing(14);</b>
<b class="nc">&nbsp; board.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; board.getChildren().clear();</b>
<b class="nc">&nbsp; renderDeck();</b>
<b class="nc">&nbsp; renderOrder();</b>
<b class="nc">&nbsp; renderSlotMap();</b>
&nbsp; }
&nbsp;
&nbsp; /** Adds the era-specific deck back image to the board. */
&nbsp; private void renderDeck() {
<b class="nc">&nbsp; String path = switch (controller.getMiniModel().currentState.getEra()) {</b>
<b class="nc">&nbsp; case 2 -&gt; &quot;/GUIImages/Backs/back-030.png&quot;;</b>
<b class="nc">&nbsp; case 3 -&gt; &quot;/GUIImages/Backs/back-058.png&quot;;</b>
<b class="nc">&nbsp; default -&gt; &quot;/GUIImages/Backs/back-001.png&quot;;</b>
<b class="nc">&nbsp; };</b>
<b class="nc">&nbsp; ImageView back = new ImageView(loadImage(path));</b>
<b class="nc">&nbsp; back.fitHeightProperty().bind(board.sceneProperty().get().heightProperty().subtract(56).divide(4).multiply(0.94));</b>
<b class="nc">&nbsp; back.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(back);</b>
<b class="nc">&nbsp; board.getChildren().add(back);</b>
&nbsp; }
&nbsp;
&nbsp; /** Renders the order card and overlays each player&#39;s totem at their proportional position. */
&nbsp; private void renderOrder() {
<b class="nc">&nbsp; int numPlayers = controller.getMiniModel().players.size();</b>
<b class="nc">&nbsp; StackPane card = createOrder(Integer.toString(numPlayers));</b>
&nbsp;
<b class="nc">&nbsp; Map&lt;Integer, double[]&gt; slotPositions = Map.of(</b>
<b class="nc">&nbsp; 2, new double[]{0.229, 0.413},</b>
<b class="nc">&nbsp; 3, new double[]{0.183, 0.367, 0.548},</b>
<b class="nc">&nbsp; 4, new double[]{0.142, 0.316, 0.503, 0.690},</b>
<b class="nc">&nbsp; 5, new double[]{0.066, 0.251, 0.433, 0.617, 0.802}</b>
&nbsp; );
<b class="nc">&nbsp; double[] positions = slotPositions.get(numPlayers);</b>
<b class="nc">&nbsp; if (positions == null) return;</b>
&nbsp;
<b class="nc">&nbsp; Pane overlay = new Pane();</b>
<b class="nc">&nbsp; overlay.setPickOnBounds(false);</b>
<b class="nc">&nbsp; overlay.setMouseTransparent(true);</b>
&nbsp;
<b class="nc">&nbsp; for (int i = 0; i &lt; controller.getMiniModel().orderLogicCard.getPlayerList().size(); i++) {</b>
<b class="nc">&nbsp; OrderPlayer op = controller.getMiniModel().orderLogicCard.getPlayerList().get(i);</b>
&nbsp;
<b class="nc">&nbsp; ImageView totem = new ImageView(loadImage(</b>
<b class="nc">&nbsp; &quot;/GUIImages/Totems/totem_&quot; + op.getPlayer().getTotem().toString().toLowerCase(Locale.ROOT) + &quot;.png&quot;</b>
&nbsp; ));
<b class="nc">&nbsp; totem.setPreserveRatio(true);</b>
&nbsp;
<b class="nc">&nbsp; if (op.isPlayed()) {</b>
<b class="nc">&nbsp; totem.setVisible(false);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; final double yRatio = positions[i];</b>
&nbsp;
<b class="nc">&nbsp; overlay.prefHeightProperty().bind(card.heightProperty());</b>
<b class="nc">&nbsp; overlay.prefWidthProperty().bind(card.widthProperty());</b>
&nbsp;
<b class="nc">&nbsp; totem.fitHeightProperty().bind(card.heightProperty().multiply(0.323));</b>
&nbsp;
<b class="nc">&nbsp; card.heightProperty().addListener((obs, ov, nv) -&gt; {</b>
<b class="nc">&nbsp; totem.setLayoutY(nv.doubleValue() * (yRatio-0.196));</b>
&nbsp; });
<b class="nc">&nbsp; card.widthProperty().addListener((obs, ov, nv) -&gt; {</b>
<b class="nc">&nbsp; totem.setLayoutX(nv.doubleValue() * 0.364);</b>
&nbsp; });
&nbsp;
<b class="nc">&nbsp; if (card.getHeight() &gt; 0) totem.setLayoutY(card.getHeight() *(yRatio-0.196));</b>
<b class="nc">&nbsp; if (card.getWidth() &gt; 0) totem.setLayoutX(card.getWidth() * 0.364);</b>
&nbsp;
<b class="nc">&nbsp; overlay.getChildren().add(totem);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; card.getChildren().add(overlay);</b>
<b class="nc">&nbsp; board.getChildren().add(card);</b>
&nbsp; }
&nbsp;
&nbsp; /** Adds a clickable slot widget for each entry in the slot-player map. */
&nbsp; private void renderSlotMap() {
<b class="nc">&nbsp; int i = 0;</b>
<b class="nc">&nbsp; for (Map.Entry&lt;Slot, Player&gt; entry : controller.getMiniModel().slotPlayerMap.entrySet()) {</b>
<b class="nc">&nbsp; Slot slot = entry.getKey();</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createSlot(slot, true, true);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; controller.slotChoice(index));</b>
<b class="nc">&nbsp; board.getChildren().add(img);</b>
<b class="nc">&nbsp; i++;</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Clears and re-renders all seven card-type piles in the local player&#39;s hand area. */
&nbsp; private void renderMyHand() {
<b class="nc">&nbsp; myHand.getChildren().clear();</b>
<b class="nc">&nbsp; myHand.setSpacing(14);</b>
<b class="nc">&nbsp; myHand.setAlignment(Pos.CENTER);</b>
&nbsp;
<b class="nc">&nbsp; Player me = controller.getMiniModel().players.get(controller.getMyUsername());</b>
<b class="nc">&nbsp; drawMyHandList(me.getArtists());</b>
<b class="nc">&nbsp; drawMyHandList(me.getGatherers());</b>
<b class="nc">&nbsp; drawMyHandList(me.getInventors());</b>
<b class="nc">&nbsp; drawMyHandList(me.getBuilders());</b>
<b class="nc">&nbsp; drawMyHandList(me.getShamans());</b>
<b class="nc">&nbsp; drawMyHandList(me.getHunters());</b>
<b class="nc">&nbsp; drawMyHandList(me.getBuildingCards());</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== POPUP ====
&nbsp; /** Creates and configures the shared card-preview popup container. */
&nbsp; private void initPopup() {
<b class="nc">&nbsp; popupCards = new HBox(10);</b>
<b class="nc">&nbsp; popupCards.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; popupCards.setPadding(new Insets(10));</b>
<b class="nc">&nbsp; popupCards.setStyle(</b>
&nbsp; &quot;-fx-background-color: #b42224;&quot; +
&nbsp; &quot;-fx-border-color: black;&quot; +
&nbsp; &quot;-fx-border-width: 2;&quot; +
&nbsp; &quot;-fx-border-radius: 10;&quot; +
&nbsp; &quot;-fx-background-radius: 10;&quot;
&nbsp; );
&nbsp;
<b class="nc">&nbsp; popup.getContent().add(popupCards);</b>
<b class="nc">&nbsp; popup.setAutoHide(false);</b>
<b class="nc">&nbsp; popup.addEventHandler(Event.ANY, e -&gt; {</b>
<b class="nc">&nbsp; if (popup.getScene() != null) {</b>
<b class="nc">&nbsp; popup.getScene().setFill(Color.TRANSPARENT);</b>
&nbsp; }
&nbsp; });
&nbsp; }
&nbsp;
&nbsp; /** Centers the popup over the current window and makes it visible. */
&nbsp; private void showPopup() {
<b class="nc">&nbsp; Window window = myHand.getScene().getWindow();</b>
<b class="nc">&nbsp; popup.show(window, 0, 0);</b>
<b class="nc">&nbsp; popup.getScene().setFill(Color.TRANSPARENT);</b>
<b class="nc">&nbsp; popup.setX(window.getX() + (window.getWidth() - popup.getWidth()) / 2);</b>
<b class="nc">&nbsp; popup.setY(window.getY() + (window.getHeight() - popup.getHeight()) / 2);</b>
&nbsp; }
&nbsp;
&nbsp; /** Populates the popup with non-clickable card images and shows it. */
&nbsp; private void openPopup(ArrayList&lt;? extends PlayableCard&gt; cardList) {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; for (PlayableCard card : cardList) {</b>
<b class="nc">&nbsp; popupCards.getChildren().add(createCardPopup(card, false, false));</b>
&nbsp; }
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Populates the popup with clickable upper-building cards; clicking one draws it via the controller. */
&nbsp; private void openPopupUpperBuilding(ArrayList&lt;BuildingCard&gt; cardList) {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; cardList.size(); i++) {</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createCardPopup(cardList.get(i), true, false);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; popup.hide();</b>
<b class="nc">&nbsp; controller.drawUpperBuildingCard(index);</b>
&nbsp; });
<b class="nc">&nbsp; popupCards.getChildren().add(img);</b>
&nbsp; }
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Populates the popup with clickable lower-building cards; clicking one draws it via the controller. */
&nbsp; private void openPopupLowerBuilding(ArrayList&lt;BuildingCard&gt; cardList) {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; cardList.size(); i++) {</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createCardPopup(cardList.get(i), true, false);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; popup.hide();</b>
<b class="nc">&nbsp; controller.drawLowerBuildingCard(index);</b>
&nbsp; });
<b class="nc">&nbsp; popupCards.getChildren().add(img);</b>
&nbsp; }
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Shows the deck details/rules card in the popup. */
&nbsp; private void openDetailsPopup() {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Backs/back-118.png&quot;));</b>
<b class="nc">&nbsp; img.setFitHeight(290);</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
<b class="nc">&nbsp; addShadow(wrapper);</b>
<b class="nc">&nbsp; popupCards.getChildren().add(wrapper);</b>
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a fixed-height {@link StackPane} card widget suitable for use inside the popup. */
&nbsp; private StackPane createCardPopup(PlayableCard card, boolean withZoom, boolean withShadow) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Fronts/card-&quot; + card.getIdIMG() + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.setFitHeight(200);</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
<b class="nc">&nbsp; if (withShadow) addShadow(wrapper);</b>
<b class="nc">&nbsp; if (withZoom) addHoverZoom(wrapper);</b>
<b class="nc">&nbsp; return wrapper;</b>
&nbsp; }
&nbsp;}
</code>
</pre>
</div>
<script type="text/javascript">
(function() {
var msie = false, msie9 = false;
/*@cc_on
msie = true;
@if (@_jscript_version >= 9)
msie9 = true;
@end
@*/
if (!msie || msie && msie9) {
hljs.highlightAll()
hljs.initLineNumbersOnLoad();
}
})();
</script>
<div class="footer">
<div style="float:right;">generated on 2026-06-19 22:53</div>
</div>
</body>
</html>