Fix: full view refactor
This commit is contained in:
@@ -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.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<String, Image> 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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<String, Image> 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<ImageView> 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);
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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 ─────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user