Fix: TUI initial refactor
This commit is contained in:
@@ -53,6 +53,21 @@
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jline</groupId>
|
||||
<artifactId>jline-terminal</artifactId>
|
||||
<version>3.26.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jline</groupId>
|
||||
<artifactId>jline-reader</artifactId>
|
||||
<version>3.26.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jline</groupId>
|
||||
<artifactId>jline-builtins</artifactId>
|
||||
<version>3.26.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -137,6 +152,7 @@
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>it.polimi.ingsw.gc14.ClientLauncherTUI</mainClass>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
@@ -145,6 +161,7 @@
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
<exclude>META-INF/versions/9/module-info.class</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
|
||||
@@ -1,210 +1,248 @@
|
||||
package it.polimi.ingsw.gc14;
|
||||
|
||||
import it.polimi.ingsw.gc14.Controller.ClientController;
|
||||
import it.polimi.ingsw.gc14.Model.Cards.TribeCards.Character;
|
||||
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkConfig;
|
||||
import it.polimi.ingsw.gc14.Network.InterfaceResolver;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||
import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient;
|
||||
import it.polimi.ingsw.gc14.Network.TCP.Client.TCPClient;
|
||||
import it.polimi.ingsw.gc14.View.TUI.TUI;
|
||||
|
||||
import java.net.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import org.jline.builtins.Completers.TreeCompleter;
|
||||
import org.jline.reader.*;
|
||||
import org.jline.reader.impl.completer.StringsCompleter;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.jline.builtins.Completers.TreeCompleter.node;
|
||||
|
||||
/**
|
||||
* Entry point for the TUI-based game client.
|
||||
* Handles the initial setup by asking the user for a username, the desired number of players,
|
||||
* and the preferred network protocol (RMI or TCP).
|
||||
* Once connected to the server, it continuously reads and dispatches user input to the controller.
|
||||
* Handles login, connects to the server, and drives the JLine-powered command loop.
|
||||
*/
|
||||
public class ClientLauncherTUI {
|
||||
|
||||
List<String> admissibleChar=new ArrayList<>();
|
||||
private TUI view;
|
||||
private Terminal terminal;
|
||||
private String currentUsername = "";
|
||||
|
||||
/**
|
||||
* The TUI view associated with this client.
|
||||
*/
|
||||
TUI view;
|
||||
|
||||
/**
|
||||
* Starts the TUI client.
|
||||
* Prompts the user for a username, the desired number of players, and the network protocol.
|
||||
* Attempts to connect to the server using either RMI or TCP depending on the selection.
|
||||
* If the connection is successful, enters a loop to continuously read and process user input.
|
||||
* Starts the TUI client. Creates a JLine terminal, loops through login → game → rematch.
|
||||
*
|
||||
* @throws InterruptedException if the thread is interrupted while waiting.
|
||||
*/
|
||||
public void main() throws InterruptedException {
|
||||
try {
|
||||
terminal = TerminalBuilder.builder().system(true).build();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Could not initialise terminal: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
terminal.writer().print("\033[H\033[2J");
|
||||
terminal.writer().flush();
|
||||
|
||||
view = new TUI(null);
|
||||
admissibleChar.add("0");
|
||||
admissibleChar.add("1");
|
||||
admissibleChar.add("2");
|
||||
admissibleChar.add("3");
|
||||
admissibleChar.add("4");
|
||||
admissibleChar.add("5");
|
||||
admissibleChar.add("6");
|
||||
admissibleChar.add("7");
|
||||
admissibleChar.add("8");
|
||||
admissibleChar.add("A");
|
||||
admissibleChar.add("B");
|
||||
admissibleChar.add("C");
|
||||
admissibleChar.add("a");
|
||||
admissibleChar.add("b");
|
||||
admissibleChar.add("c");
|
||||
ClientController controller = new ClientController(view);
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
LineReader gameReader = buildGameReader();
|
||||
view.setLineReader(gameReader);
|
||||
|
||||
while (true) {
|
||||
System.out.println("Insert username: ");
|
||||
String username = scanner.next();
|
||||
view.setUsername(username);
|
||||
controller.setMyUsername(username);
|
||||
System.out.println("Insert preferred number of players: ");
|
||||
int proposedNumPlayers = scanner.nextInt();
|
||||
System.out.println("Select RMI[0] o TCP[1]: ");
|
||||
int networkType = scanner.nextInt();
|
||||
System.out.println("Insert server IP: ");
|
||||
String IP = scanner.next();
|
||||
if (!doLogin(controller)) continue;
|
||||
|
||||
// RMI
|
||||
if (networkType == 0) {
|
||||
// Connect
|
||||
String myIP;
|
||||
while (controller.getClient() != null) {
|
||||
String prompt = buildPrompt(controller);
|
||||
String line;
|
||||
try {
|
||||
myIP = InterfaceResolver.resolveLocalInterface(IP);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.setProperty("java.rmi.server.hostname", myIP);
|
||||
RMIClient client = new RMIClient(controller, IP, NetworkConfig.RMI_PORT, myIP);
|
||||
ErrorType serverResponse=client.connect(username, proposedNumPlayers);
|
||||
if (serverResponse==null) {
|
||||
System.out.println("Succesfully connected to RMI server\n\n");
|
||||
} else {
|
||||
System.out.println("RMI connection refused\n\n");
|
||||
controller.view.showError(serverResponse,serverResponse.toString());
|
||||
continue;
|
||||
}
|
||||
controller.setClient(client);
|
||||
while (controller.getClient() != null) {
|
||||
getInput(scanner, controller, username);
|
||||
}
|
||||
// TCP
|
||||
} else if (networkType == 1) {
|
||||
// Connect
|
||||
TCPClient client = new TCPClient(controller, IP, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT);
|
||||
ErrorType serverResponse=client.connect(username, proposedNumPlayers);
|
||||
if (serverResponse==null) {
|
||||
System.out.println("Succesfully connected to TCP server\n\n");
|
||||
} else {
|
||||
System.out.println("TCP connection refused\n\n");
|
||||
controller.view.showError(serverResponse,serverResponse.toString());
|
||||
continue;
|
||||
}
|
||||
controller.setClient(client);
|
||||
// Play
|
||||
while (controller.getClient() != null) {
|
||||
getInput(scanner, controller, username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single action from the user and dispatches it to the controller.
|
||||
* The action is identified by a string code. Most actions also require a position index
|
||||
* (e.g. the index of the card to draw from a list), which is read as a second input.
|
||||
* Actions that do not require a position (7, 8, 9, A, B, C) skip the position prompt.
|
||||
* <p>
|
||||
* Available actions:
|
||||
* <ul>
|
||||
* <li>{@code 0} - Choose a slot by index.</li>
|
||||
* <li>{@code 1} - Draw an upper building card by index.</li>
|
||||
* <li>{@code 2} - Draw an upper tribe card by index.</li>
|
||||
* <li>{@code 3} - Draw a lower building card by index.</li>
|
||||
* <li>{@code 4} - Draw a lower tribe card by index.</li>
|
||||
* <li>{@code 5} - Pick an optional tribe card by index.</li>
|
||||
* <li>{@code 6} - Pick an optional building card by index.</li>
|
||||
* <li>{@code 7} - Skip the optional card choice.</li>
|
||||
* <li>{@code 8} - Skip the upper draw.</li>
|
||||
* <li>{@code 9} - Skip the lower draw.</li>
|
||||
* <li>{@code A} - Render the full game view.</li>
|
||||
* <li>{@code B} - Render the board view.</li>
|
||||
* <li>{@code C} - Render the player view.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param scanner the scanner used to read user input.
|
||||
* @param controller the client controller to which actions are dispatched.
|
||||
* @param username the username of the current player.
|
||||
*/
|
||||
private void getInput(Scanner scanner, ClientController controller, String username) {
|
||||
String action = scanner.nextLine().trim();
|
||||
int pos = -1;
|
||||
if(controller.getClient() == null)
|
||||
{
|
||||
if(action.equals("0"))
|
||||
{
|
||||
System.out.println("Are you sure? Y/N");
|
||||
String c= scanner.next();
|
||||
if(c.equals("Y") || c.equals("y"))
|
||||
{
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(action.isEmpty())
|
||||
return;
|
||||
if(admissibleChar.contains(action))
|
||||
{
|
||||
if(action.equals("0"))
|
||||
{
|
||||
System.out.println("Are you sure? Y/N");
|
||||
String c= scanner.next();
|
||||
if(!c.equals("Y") && !c.equals("y"))
|
||||
{
|
||||
view.render();
|
||||
return;
|
||||
}
|
||||
}else if (!action.equals("6")&&!action.equals("8") && !action.equals("A") && !action.equals("B") && !action.equals("C")&&!action.equals("a") && !action.equals("b") && !action.equals("c")) {
|
||||
try {
|
||||
System.out.println("Insert the required position:");
|
||||
pos = scanner.nextInt();
|
||||
} catch (Exception e) {
|
||||
view.showError(ErrorType.GENERIC_ERROR,"Invalid input(expected number)");
|
||||
line = gameReader.readLine(prompt);
|
||||
} catch (UserInterruptException e) {
|
||||
safeQuit(controller);
|
||||
return;
|
||||
} catch (EndOfFileException e) {
|
||||
safeQuit(controller);
|
||||
return;
|
||||
}
|
||||
if (line == null || line.isBlank()) continue;
|
||||
boolean rematch = handleCommand(line.trim(), controller);
|
||||
if (rematch) break;
|
||||
}
|
||||
switch (action) {
|
||||
case "1" -> controller.slotChoice(pos);
|
||||
case "2" -> controller.drawUpperTribeCard(pos);
|
||||
case "3" -> controller.drawUpperBuildingCard(pos);
|
||||
case "4" -> controller.drawLowerTribeCard(pos);
|
||||
case "5" -> controller.drawLowerBuildingCard(pos);
|
||||
case "6" -> controller.skipTurn();
|
||||
case "7" -> controller.totemChoice(pos);
|
||||
case "8" ->{
|
||||
if(controller.miniModel.currentState.getGameStage().equals(GameStages.ENDED)) {
|
||||
controller.disconnect();
|
||||
controller.setClient(null);
|
||||
controller.setModel(null);
|
||||
}
|
||||
}
|
||||
case "A", "a" -> view.fullRender();
|
||||
case "B", "b" -> view.renderBoard();
|
||||
case "C", "c" -> view.renderPlayer();
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
view.showError(ErrorType.GENERIC_ERROR,"Invalid input(expected number)");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doLogin(ClientController controller) {
|
||||
LineReader loginReader = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(new StringsCompleter("rmi", "tcp"))
|
||||
.build();
|
||||
LineReader ipReader = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(new StringsCompleter("localhost"))
|
||||
.build();
|
||||
|
||||
}
|
||||
String username, networkStr, ip;
|
||||
int nPlayers;
|
||||
|
||||
try {
|
||||
username = loginReader.readLine("Username: ").trim();
|
||||
nPlayers = Integer.parseInt(loginReader.readLine("Number of players [2-5]: ").trim());
|
||||
networkStr = loginReader.readLine("Network [rmi/tcp]: ").trim().toLowerCase();
|
||||
ip = ipReader.readLine("Server IP [localhost]: ").trim();
|
||||
if (ip.isEmpty()) ip = "localhost";
|
||||
} catch (UserInterruptException | EndOfFileException e) {
|
||||
System.exit(0);
|
||||
return false;
|
||||
} catch (NumberFormatException e) {
|
||||
System.out.println("Invalid number of players.");
|
||||
return false;
|
||||
}
|
||||
|
||||
currentUsername = username;
|
||||
view.setUsername(username);
|
||||
controller.setMyUsername(username);
|
||||
|
||||
if (networkStr.equals("rmi")) {
|
||||
String myIP;
|
||||
try {
|
||||
myIP = InterfaceResolver.resolveLocalInterface(ip);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Cannot resolve local interface: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
System.setProperty("java.rmi.server.hostname", myIP);
|
||||
RMIClient client = new RMIClient(controller, ip, NetworkConfig.RMI_PORT, myIP);
|
||||
ErrorType err = client.connect(username, nPlayers);
|
||||
if (err != null) {
|
||||
view.showError(err, err.toString());
|
||||
return false;
|
||||
}
|
||||
System.out.println("Connected via RMI.");
|
||||
controller.setClient(client);
|
||||
|
||||
} else if (networkStr.equals("tcp")) {
|
||||
TCPClient client = new TCPClient(controller, ip, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT);
|
||||
ErrorType err = client.connect(username, nPlayers);
|
||||
if (err != null) {
|
||||
view.showError(err, err.toString());
|
||||
return false;
|
||||
}
|
||||
System.out.println("Connected via TCP.");
|
||||
controller.setClient(client);
|
||||
|
||||
} else {
|
||||
System.out.println("Unknown network type '" + networkStr + "'. Use rmi or tcp.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private LineReader buildGameReader() {
|
||||
Completer completer = new TreeCompleter(
|
||||
node("slot"),
|
||||
node("draw",
|
||||
node("upper",
|
||||
node("tribe"),
|
||||
node("building")),
|
||||
node("lower",
|
||||
node("tribe"),
|
||||
node("building"))),
|
||||
node("totem"),
|
||||
node("skip"),
|
||||
node("render",
|
||||
node("full"),
|
||||
node("board"),
|
||||
node("players")),
|
||||
node("rematch"),
|
||||
node("quit")
|
||||
);
|
||||
return LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(completer)
|
||||
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
|
||||
.build();
|
||||
}
|
||||
|
||||
private String buildPrompt(ClientController controller) {
|
||||
return currentUsername.isEmpty() ? "> " : currentUsername + "> ";
|
||||
}
|
||||
|
||||
/** Returns {@code true} when the caller should break out of the game loop (rematch requested). */
|
||||
private boolean handleCommand(String line, ClientController controller) {
|
||||
String[] parts = line.split("\\s+");
|
||||
String cmd = parts[0].toLowerCase();
|
||||
|
||||
switch (cmd) {
|
||||
case "slot" -> {
|
||||
int pos = parsePos(parts, 1);
|
||||
if (pos >= 0) controller.slotChoice(pos);
|
||||
}
|
||||
case "draw" -> handleDraw(parts, controller);
|
||||
case "totem" -> {
|
||||
int pos = parsePos(parts, 1);
|
||||
if (pos >= 0) controller.totemChoice(pos);
|
||||
}
|
||||
case "skip" -> controller.skipTurn();
|
||||
case "render" -> handleRender(parts);
|
||||
case "rematch" -> {
|
||||
if (controller.miniModel == null
|
||||
|| !controller.miniModel.currentState.getGameStage().equals(GameStages.ENDED)) {
|
||||
System.out.println("Game not ended yet. Use 'quit' to disconnect.");
|
||||
return false;
|
||||
}
|
||||
controller.disconnect();
|
||||
controller.setClient(null);
|
||||
controller.setModel(null);
|
||||
return true;
|
||||
}
|
||||
case "quit" -> {
|
||||
safeQuit(controller);
|
||||
System.exit(0);
|
||||
}
|
||||
default -> view.showError(ErrorType.GENERIC_ERROR, "Unknown command: " + cmd);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleDraw(String[] parts, ClientController controller) {
|
||||
if (parts.length < 4) {
|
||||
view.showError(ErrorType.GENERIC_ERROR, "Usage: draw upper|lower tribe|building <pos>");
|
||||
return;
|
||||
}
|
||||
boolean upper = parts[1].equalsIgnoreCase("upper");
|
||||
boolean tribe = parts[2].equalsIgnoreCase("tribe");
|
||||
int pos = parsePos(parts, 3);
|
||||
if (pos < 0) return;
|
||||
|
||||
if (upper && tribe) controller.drawUpperTribeCard(pos);
|
||||
else if (upper) controller.drawUpperBuildingCard(pos);
|
||||
else if (tribe) controller.drawLowerTribeCard(pos);
|
||||
else controller.drawLowerBuildingCard(pos);
|
||||
}
|
||||
|
||||
private void handleRender(String[] parts) {
|
||||
String sub = parts.length > 1 ? parts[1].toLowerCase() : "board";
|
||||
switch (sub) {
|
||||
case "full" -> view.fullRender();
|
||||
case "players" -> view.renderPlayer();
|
||||
default -> view.renderBoard();
|
||||
}
|
||||
}
|
||||
|
||||
private int parsePos(String[] parts, int idx) {
|
||||
if (parts.length <= idx) {
|
||||
view.showError(ErrorType.GENERIC_ERROR, "Missing position argument.");
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(parts[idx]);
|
||||
} catch (NumberFormatException e) {
|
||||
view.showError(ErrorType.GENERIC_ERROR,
|
||||
"Invalid position '" + parts[idx] + "' — expected integer.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void safeQuit(ClientController controller) {
|
||||
if (controller.getClient() != null) controller.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||
import it.polimi.ingsw.gc14.View.IView;
|
||||
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.terminal.Terminal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -13,358 +16,347 @@ import java.util.Map;
|
||||
/**
|
||||
* Text-based User Interface (TUI) implementation of {@link IView}.
|
||||
*
|
||||
* <p>Renders the current state of a {@link Game} model directly to the
|
||||
* standard output using Unicode box-drawing characters and fixed-width
|
||||
* ASCII tables (see {@link AsciiTable}).
|
||||
* <p>Renders the current state of a {@link MiniModel} directly to the
|
||||
* terminal using Unicode box-drawing characters and fixed-width ASCII tables.
|
||||
*
|
||||
* <p>The display is split into two side-by-side panels:
|
||||
* <ul>
|
||||
* <li><b>Left panel</b> — board or player status, depending on the render method called.</li>
|
||||
* <li><b>Right panel</b> — menu options legend and the current player's hand.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The terminal is cleared before each render via a platform-aware
|
||||
* {@code cls} / {@code clear} system call.
|
||||
*
|
||||
* <p>Typical usage:
|
||||
* <pre>{@code
|
||||
* TUI tui = new TUI(game);
|
||||
* tui.setUsername("Alice");
|
||||
* tui.fullRender();
|
||||
* }</pre>
|
||||
* <p>All output goes through {@link #display(String)}, which uses
|
||||
* {@link LineReader#printAbove} when a JLine reader is set so that the
|
||||
* readline prompt is correctly redrawn after background-thread renders.
|
||||
*/
|
||||
public class TUI implements IView {
|
||||
|
||||
/**
|
||||
* The {@link MiniModel} model whose state is rendered.
|
||||
*/
|
||||
private MiniModel model;
|
||||
|
||||
/**
|
||||
* The username of the local player, used to retrieve and display
|
||||
* that player's hand in the right panel.
|
||||
*
|
||||
* @see #setUsername(String)
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Constructs a {@code TUI} bound to the given game model.
|
||||
* The username is initialised to an empty string and must be set
|
||||
* separately via {@link #setUsername(String)} before calling any
|
||||
* render method that displays the player's hand.
|
||||
* JLine reader — when non-null, all output uses {@code printAbove} so the
|
||||
* readline prompt is preserved after background-thread renders.
|
||||
*/
|
||||
private LineReader lineReader;
|
||||
|
||||
/** JLine terminal — used to query terminal width for centering. */
|
||||
private Terminal terminal;
|
||||
|
||||
/**
|
||||
* Constructs a {@code TUI} bound to the given model.
|
||||
*
|
||||
* @param model the {@link Game} model to display; must not be {@code null}
|
||||
* @param model the model to display; may be {@code null} initially.
|
||||
*/
|
||||
public TUI(MiniModel model) {
|
||||
this.model = model;
|
||||
this.model = model;
|
||||
this.username = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username of the local player.
|
||||
* This value is used by {@link #renderBoard()} and {@link #fullRender()}
|
||||
* to look up the correct player hand via
|
||||
* {@link Game#getPlayerByUsername(String)}.
|
||||
*
|
||||
* @param username the player's username; must match an existing player
|
||||
* in the current {@link Game} model
|
||||
*/
|
||||
// ── Setters ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** Sets the username of the local player. */
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the game model stored in this view.
|
||||
* Should be called whenever the game state changes so that the next
|
||||
* render reflects the latest state.
|
||||
*
|
||||
* @param model the new {@link Game} model; must not be {@code null}
|
||||
*/
|
||||
/** Updates the model stored in this view. */
|
||||
@Override
|
||||
public void setModel(MiniModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default render entry point, as required by {@link IView}.
|
||||
* Delegates to {@link #renderBoard()}.
|
||||
* Registers the active JLine {@link LineReader}.
|
||||
* Once set, all output routes through {@link LineReader#printAbove} so the
|
||||
* prompt survives background-thread renders.
|
||||
*/
|
||||
public void setLineReader(LineReader lineReader) {
|
||||
this.lineReader = lineReader;
|
||||
this.terminal = lineReader.getTerminal();
|
||||
}
|
||||
|
||||
// ── Public render entry points ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Default render: dispatches to the right sub-renderer based on game stage.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void render() {
|
||||
if(model != null)
|
||||
{
|
||||
if(model.currentState.getGameStage().equals(GameStages.TOTEM_CHOICE))
|
||||
{
|
||||
renderTotems();
|
||||
}
|
||||
else if(model.currentState.getGameStage().equals(GameStages.ENDED))
|
||||
{
|
||||
renderStanding();
|
||||
System.out.println("Press 8 to start another game or press 0 to exit");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderBoard();
|
||||
}
|
||||
System.out.println("\nYOUR ACTION:");
|
||||
if (model == null) return;
|
||||
GameStages stage = model.currentState.getGameStage();
|
||||
if (stage.equals(GameStages.TOTEM_CHOICE)) {
|
||||
display(buildTotemsContent());
|
||||
} else if (stage.equals(GameStages.ENDED)) {
|
||||
display(buildStandingContent() + "\nType 'rematch' to play again or 'quit' to exit");
|
||||
} else {
|
||||
display(buildBoardContent());
|
||||
}
|
||||
}
|
||||
|
||||
/** Prints the final standings side-by-side in pairs, then announces the winner or game-over result. */
|
||||
private void renderStanding() {
|
||||
if(model.standingPlayers!=null)
|
||||
{
|
||||
StringBuilder stringBuilder=new StringBuilder();
|
||||
for(int i = 0; i< model.standingPlayers.size()/2; i++)
|
||||
{
|
||||
stringBuilder.append(AsciiTable.sideBySide(List.of(model.standingPlayers.get((i*2)).toString().split("\n")),List.of(model.standingPlayers.get((i*2+1)).toString().split("\n")),2));
|
||||
}
|
||||
stringBuilder.append("\n");
|
||||
if(model.standingPlayers.size()%2!=0)
|
||||
{
|
||||
stringBuilder.append(model.standingPlayers.get(model.standingPlayers.size()-1).toString());
|
||||
stringBuilder.append("\n");
|
||||
}
|
||||
System.out.println(stringBuilder);
|
||||
if(model.standingPlayers.get(0).getUserName().equals(username))
|
||||
{
|
||||
System.out.println("GAME ENDED: !!!YOU WON!!!");
|
||||
}
|
||||
else {
|
||||
System.out.println("GAME ENDED: !!!YOU LOST!!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a full view of the game, combining both player status
|
||||
* and board status.
|
||||
*
|
||||
* <p>Layout:
|
||||
* <ul>
|
||||
* <li><b>Top</b> — player status table produced by
|
||||
* {@link #PlayersStamp()}.</li>
|
||||
* <li><b>Bottom-left</b> — board status produced by
|
||||
* {@link #BoardStamp()}.</li>
|
||||
* <li><b>Bottom-right</b> — menu options legend and the local
|
||||
* player's hand.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The terminal is cleared before rendering.
|
||||
*/
|
||||
public void fullRender() {
|
||||
clearTerminal();
|
||||
List<String> lines = List.of(BoardStamp().split("\n"));
|
||||
List<String> lines2 = List.of((printMenuOptions() + "\n" +
|
||||
model.players.get(username)).split("\n"));
|
||||
System.out.println(PlayersStamp() + "\n" +
|
||||
AsciiTable.sideBySide(lines, lines2, 3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the board status only.
|
||||
*
|
||||
* <p>Layout:
|
||||
* <ul>
|
||||
* <li><b>Left panel</b> — turn order, upper card row, offer track,
|
||||
* and lower card row, as produced by {@link #BoardStamp()}.</li>
|
||||
* <li><b>Right panel</b> — menu options legend followed by the local
|
||||
* player's hand.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The terminal is cleared before rendering.
|
||||
*/
|
||||
/** Renders the board panel + menu + player hand side by side. */
|
||||
public void renderBoard() {
|
||||
clearTerminal();
|
||||
List<String> lines = List.of(BoardStamp().split("\n"));
|
||||
List<String> lines2 = List.of((printMenuOptions() + "\nYOUR HAND\n" +
|
||||
model.players.get(username)).split("\n"));
|
||||
System.out.println(AsciiTable.sideBySide(lines, lines2, 3));
|
||||
display(buildBoardContent());
|
||||
}
|
||||
|
||||
/** Renders players panel + board panel + menu + player hand. */
|
||||
public void fullRender() {
|
||||
display(buildFullContent());
|
||||
}
|
||||
|
||||
/** Renders the totem-choice panel. */
|
||||
public void renderTotems() {
|
||||
display(buildTotemsContent());
|
||||
}
|
||||
|
||||
/** Renders the players panel + menu side by side. */
|
||||
public void renderPlayer() {
|
||||
display(buildPlayerContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the available totems in the terminal interface.
|
||||
* Shows an error message above (or below) the current board.
|
||||
*
|
||||
* <p>The method clears the terminal, builds a table containing the currently
|
||||
* available totems together with their selection positions, and displays the
|
||||
* instructions required to choose one.
|
||||
* <p>Board and message are combined into a single {@link #display} call so
|
||||
* that only one {@code printAbove} is issued — avoiding the double-clear
|
||||
* that would occur if render and message were displayed separately.
|
||||
*
|
||||
* @param error the error type.
|
||||
* @param message the human-readable message to append.
|
||||
*/
|
||||
public void renderTotems() {
|
||||
clearTerminal();
|
||||
public void showError(ErrorType error, String message) {
|
||||
if (model == null) {
|
||||
display(message);
|
||||
return;
|
||||
}
|
||||
GameStages stage = model.currentState.getGameStage();
|
||||
String base;
|
||||
if (stage.equals(GameStages.TOTEM_CHOICE)) {
|
||||
base = buildTotemsContent();
|
||||
} else if (stage.equals(GameStages.ENDED)) {
|
||||
base = buildStandingContent() + "\nType 'rematch' to play again or 'quit' to exit";
|
||||
} else {
|
||||
base = buildBoardContent();
|
||||
}
|
||||
|
||||
if (error.equals(ErrorType.WRONG_ACTION)
|
||||
&& model.currentState.getCurrentPlayer() != null
|
||||
&& !model.currentState.getCurrentPlayer().getUserName().equals(username)) {
|
||||
display(base + "\nIt's not your turn");
|
||||
return;
|
||||
}
|
||||
|
||||
String full = base + "\n" + message;
|
||||
if (error == ErrorType.SERVER_CRASHED) {
|
||||
full += "\nPress any key to continue";
|
||||
}
|
||||
display(full);
|
||||
}
|
||||
|
||||
// ── Content builders (return strings, do not print) ───────────────────────
|
||||
|
||||
private String buildBoardContent() {
|
||||
List<String> left = List.of(BoardStamp().split("\n"));
|
||||
List<String> right = List.of((printMenuOptions() + "\nYOUR HAND\n"
|
||||
+ model.players.get(username)).split("\n"));
|
||||
return AsciiTable.sideBySide(left, right, 3);
|
||||
}
|
||||
|
||||
private String buildFullContent() {
|
||||
List<String> left = List.of(BoardStamp().split("\n"));
|
||||
List<String> right = List.of((printMenuOptions() + "\n"
|
||||
+ model.players.get(username)).split("\n"));
|
||||
return PlayersStamp() + "\n" + AsciiTable.sideBySide(left, right, 3);
|
||||
}
|
||||
|
||||
private String buildPlayerContent() {
|
||||
List<String> left = List.of(PlayersStamp().split("\n"));
|
||||
List<String> right = List.of(printMenuOptions().split("\n"));
|
||||
return AsciiTable.sideBySide(left, right, 3);
|
||||
}
|
||||
|
||||
private String buildTotemsContent() {
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size());
|
||||
List<String> lines =new ArrayList<>();
|
||||
for(int i=0;i<model.availableTotems.size();i++)
|
||||
{
|
||||
lines.add(i+"."+model.availableTotems.get(i));
|
||||
List<String> lines = new ArrayList<>();
|
||||
for (int i = 0; i < model.availableTotems.size(); i++) {
|
||||
lines.add(i + "." + model.availableTotems.get(i));
|
||||
}
|
||||
table.addRow(lines);
|
||||
System.out.println(model.currentState+"\nTOTEMS AVAILABLE:\n"+table.build()+"\nPress 7 and then write the position");
|
||||
String current = model.currentState.getCurrentPlayer() != null
|
||||
? model.currentState.getCurrentPlayer().getUserName() + " — "
|
||||
: "";
|
||||
return current + "TOTEMS AVAILABLE:\n" + table.build() + "\nType: totem <pos>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the player status table only.
|
||||
*
|
||||
* <p>Layout:SkipTurn
|
||||
* <ul>
|
||||
* <li><b>Left panel</b> — prestige, food, character deck, and building
|
||||
* deck for all players, as produced by {@link #PlayersStamp()}.</li>
|
||||
* <li><b>Right panel</b> — menu options legend.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The terminal is cleared before rendering.
|
||||
*/
|
||||
public void renderPlayer() {
|
||||
clearTerminal();
|
||||
List<String> lines = List.of(PlayersStamp().split("\n"));
|
||||
List<String> lines2 = List.of(printMenuOptions().split("\n"));
|
||||
System.out.println(AsciiTable.sideBySide(lines, lines2, 3));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prints an error message to standard output.
|
||||
*
|
||||
* @param error the error message to display
|
||||
*/
|
||||
public void showError(ErrorType error,String message) {
|
||||
clearTerminal();
|
||||
render();
|
||||
if(error.equals(ErrorType.WRONG_ACTION))
|
||||
{
|
||||
if(!model.currentState.getCurrentPlayer().getUserName().equals(username)){
|
||||
System.out.println("It's not your turn");
|
||||
return;
|
||||
}
|
||||
private String buildStandingContent() {
|
||||
if (model.standingPlayers == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int size = model.standingPlayers.size();
|
||||
for (int i = 0; i < size / 2; i++) {
|
||||
sb.append(AsciiTable.sideBySide(
|
||||
List.of(model.standingPlayers.get(i * 2).toString().split("\n")),
|
||||
List.of(model.standingPlayers.get(i * 2 + 1).toString().split("\n")), 2));
|
||||
}
|
||||
sb.append("\n");
|
||||
if (size % 2 != 0) {
|
||||
sb.append(model.standingPlayers.get(size - 1)).append("\n");
|
||||
}
|
||||
if (model.standingPlayers.get(0).getUserName().equals(username)) {
|
||||
sb.append("GAME ENDED: !!!YOU WON!!!");
|
||||
} else {
|
||||
sb.append("GAME ENDED: !!!YOU LOST!!!");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// ── Core display primitive ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Clears the terminal and displays {@code content}, horizontally centered.
|
||||
*
|
||||
* <p>When a JLine {@link LineReader} is registered, output is routed through
|
||||
* {@link LineReader#printAbove} which pauses readline, prints the content,
|
||||
* and redraws the prompt — safe to call from any thread.
|
||||
* Otherwise, ANSI codes are written directly to stdout.
|
||||
*
|
||||
* @param content the text to display (board, error, etc.).
|
||||
*/
|
||||
private void display(String content) {
|
||||
String cleared = "\033[H\033[2J" + center(content);
|
||||
if (lineReader != null) {
|
||||
lineReader.printAbove(cleared);
|
||||
} else {
|
||||
System.out.print(cleared);
|
||||
System.out.println();
|
||||
System.out.flush();
|
||||
}
|
||||
System.out.println(message);
|
||||
if(error==ErrorType.SERVER_CRASHED)
|
||||
System.out.println("Press any key to continue");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the menu options panel as a two-column
|
||||
* {@link AsciiTable} with rounded borders.
|
||||
*
|
||||
* <p>The left column lists game action commands (slot choice, draw, pick…),
|
||||
* the right column lists render shortcuts (full, board, players).
|
||||
*
|
||||
* @return the rendered menu table as a multi-line string
|
||||
* Horizontally centers each line of {@code content} within the terminal width.
|
||||
* Empty lines are left unpadded. Falls back to the original string when the
|
||||
* terminal width is unknown.
|
||||
*/
|
||||
private String center(String content) {
|
||||
if (terminal == null) return content;
|
||||
int termWidth = terminal.getWidth();
|
||||
if (termWidth <= 0) return content;
|
||||
|
||||
String[] lines = content.split("\n", -1);
|
||||
|
||||
int maxWidth = 0;
|
||||
for (String line : lines) {
|
||||
int w = visibleLength(line);
|
||||
if (w > maxWidth) maxWidth = w;
|
||||
}
|
||||
|
||||
int pad = Math.max(0, (termWidth - maxWidth) / 2);
|
||||
if (pad == 0) return content;
|
||||
|
||||
String prefix = " ".repeat(pad);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
if (!lines[i].isEmpty()) sb.append(prefix);
|
||||
sb.append(lines[i]);
|
||||
if (i < lines.length - 1) sb.append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Returns the visible (printable) length of a string, stripping ANSI escape codes. */
|
||||
private int visibleLength(String line) {
|
||||
return line.replaceAll("\033\\[[^m]*m", "").length();
|
||||
}
|
||||
|
||||
// ── Board / players stamp helpers ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns all players rendered side-by-side in pairs.
|
||||
*
|
||||
* @return multi-line string with all player panels.
|
||||
*/
|
||||
public String PlayersStamp() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ArrayList<Player> list = new ArrayList<>(model.players.values());
|
||||
for (int i = 0; i < list.size() / 2; i++) {
|
||||
sb.append(AsciiTable.sideBySide(
|
||||
List.of(list.get(i * 2).toString().split("\n")),
|
||||
List.of(list.get(i * 2 + 1).toString().split("\n")), 2));
|
||||
}
|
||||
sb.append("\n");
|
||||
if (list.size() % 2 != 0) {
|
||||
sb.append(list.get(list.size() - 1)).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the board state as a multi-line string: turn order, upper cards,
|
||||
* offer track, lower cards.
|
||||
*
|
||||
* @return multi-line board string.
|
||||
*/
|
||||
public String BoardStamp() {
|
||||
var offerTrack = new AsciiTable(BorderStyle.UNICODE, model.slotPlayerMap.size());
|
||||
List<String> slotNames = new ArrayList<>();
|
||||
List<String> slotPlayers = new ArrayList<>();
|
||||
|
||||
int index = 0;
|
||||
for (Map.Entry<Slot, Player> entry : model.slotPlayerMap.entrySet()) {
|
||||
slotNames.add((index++) + "." + entry.getKey().toStringTUI());
|
||||
slotPlayers.add(entry.getValue() != null ? entry.getValue().getUserName() : " ");
|
||||
}
|
||||
|
||||
var TribeUpper = new AsciiTable(BorderStyle.UNICODE, 1);
|
||||
var TribeLower = new AsciiTable(BorderStyle.UNICODE, 1);
|
||||
var BuildUpper = new AsciiTable(BorderStyle.UNICODE, 1);
|
||||
BuildUpper.addHeader("Building ");
|
||||
var BuildLower = new AsciiTable(BorderStyle.UNICODE, 1);
|
||||
BuildLower.addHeader("Building ");
|
||||
|
||||
List<String> upperTribeRows = new ArrayList<>();
|
||||
List<String> lowerTribeRows = new ArrayList<>();
|
||||
upperTribeRows.add("Char/Events");
|
||||
lowerTribeRows.add("Char/Events");
|
||||
|
||||
int max = Math.max(model.upperListTribeCards.size(), model.lowerListTribeCards.size());
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (i < model.upperListTribeCards.size())
|
||||
upperTribeRows.add(i + "-" + model.upperListTribeCards.get(i).toStringBoard());
|
||||
if (i < model.lowerListTribeCards.size())
|
||||
lowerTribeRows.add(i + "-" + model.lowerListTribeCards.get(i).toStringBoard());
|
||||
}
|
||||
|
||||
int maxB = Math.max(model.upperListBuildingCards.size(), model.lowerListBuildingCards.size());
|
||||
for (int i = 0; i < maxB; i++) {
|
||||
if (i < model.upperListBuildingCards.size())
|
||||
BuildUpper.addRow(i + "-" + model.upperListBuildingCards.get(i).toString());
|
||||
if (i < model.lowerListBuildingCards.size())
|
||||
BuildLower.addRow(i + "-" + model.lowerListBuildingCards.get(i).toString());
|
||||
}
|
||||
|
||||
upperTribeRows.forEach(TribeUpper::addRow);
|
||||
lowerTribeRows.forEach(TribeLower::addRow);
|
||||
offerTrack.addRow(slotPlayers);
|
||||
offerTrack.addRow(slotNames);
|
||||
|
||||
return "CURRENT STATE\n" + model.currentState + "\n" + model.orderLogicCard + "\n"
|
||||
+ AsciiTable.sideBySide(
|
||||
Arrays.stream(TribeUpper.build().split("\n")).toList(),
|
||||
Arrays.stream(BuildUpper.build().split("\n")).toList(), 2)
|
||||
+ "\n" + offerTrack.build() + "\n"
|
||||
+ AsciiTable.sideBySide(
|
||||
List.of(TribeLower.build().split("\n")),
|
||||
Arrays.stream(BuildLower.build().split("\n")).toList(), 2);
|
||||
}
|
||||
|
||||
// ── Menu options ──────────────────────────────────────────────────────────
|
||||
|
||||
private String printMenuOptions() {
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, 2);
|
||||
table.addHeader("Menu Options", "Render Options");
|
||||
table.addRow(List.of("1-SlotChoice(pos)", "A-Full Render"));
|
||||
table.addRow(List.of("2-DrawUpperTribe(pos)", "B-Board Render"));
|
||||
table.addRow(List.of("3-DrawUpperBuilding(pos)", "C-Players Render"));
|
||||
table.addRow(List.of("4-DrawLowerTribe(pos)", ""));
|
||||
table.addRow(List.of("5-DrawLowerBuilding(pos)", ""));
|
||||
table.addRow(List.of("6-SkipTurn", ""));
|
||||
table.addRow(List.of("0-Disconnect", ""));
|
||||
table.addHeader("Commands", "Render");
|
||||
table.addRow(List.of("slot <pos>", "render full"));
|
||||
table.addRow(List.of("draw upper tribe <pos>", "render board"));
|
||||
table.addRow(List.of("draw upper building <pos>", "render players"));
|
||||
table.addRow(List.of("draw lower tribe <pos>", ""));
|
||||
table.addRow(List.of("draw lower building <pos>", ""));
|
||||
table.addRow(List.of("totem <pos>", ""));
|
||||
table.addRow(List.of("skip", ""));
|
||||
table.addRow(List.of("rematch | quit", ""));
|
||||
return table.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the terminal using a platform-aware system call.
|
||||
* Uses {@code cls} on Windows and {@code clear} on Unix-like systems.
|
||||
* Failures are silently ignored to avoid interrupting the render flow.
|
||||
*/
|
||||
private void clearTerminal() {
|
||||
try {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
ProcessBuilder pb = os.contains("win")
|
||||
? new ProcessBuilder("cmd", "/c", "cls")
|
||||
: new ProcessBuilder("clear");
|
||||
pb.inheritIO().start().waitFor();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of all the players currently in the game,
|
||||
* arranged side by side in pairs.
|
||||
* If the number of players is odd, the last player is printed on its own line.
|
||||
*
|
||||
* @return {@code String} - a string representation of all the players.
|
||||
*/
|
||||
public String PlayersStamp()
|
||||
{
|
||||
StringBuilder stringBuilder=new StringBuilder();
|
||||
ArrayList<Player> playersList=new ArrayList<>(model.players.values());
|
||||
for(int i = 0; i< model.players.size()/2; i++)
|
||||
{
|
||||
stringBuilder.append(AsciiTable.sideBySide(List.of(playersList.get((i*2)).toString().split("\n")),List.of(playersList.get((i*2+1)).toString().split("\n")),2));
|
||||
}
|
||||
stringBuilder.append("\n");
|
||||
if(playersList.size()%2!=0)
|
||||
{
|
||||
stringBuilder.append(playersList.get(playersList.size()-1).toString());
|
||||
stringBuilder.append("\n");
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the board, including the current state,
|
||||
* the offer track with slot assignments, the upper and lower tribe card lists,
|
||||
* and the upper and lower building card lists.
|
||||
*
|
||||
* @return {@code String} - a string representation of the board.
|
||||
*/
|
||||
public String BoardStamp()
|
||||
{
|
||||
var offerTrack = new AsciiTable(BorderStyle.UNICODE, model.slotPlayerMap.size());
|
||||
List<String> stringUpOffer=new ArrayList<>();
|
||||
List<String> stringDownOffer=new ArrayList<>();
|
||||
|
||||
int index=0;
|
||||
for(Map.Entry<Slot, Player> entry:model.slotPlayerMap.entrySet())
|
||||
{
|
||||
stringDownOffer.add((index++)+"."+entry.getKey().toStringTUI());
|
||||
if(entry.getValue()!=null)
|
||||
stringUpOffer.add(entry.getValue().getUserName());
|
||||
else
|
||||
stringUpOffer.add(" ");
|
||||
}
|
||||
|
||||
var TribeTableUpper = new AsciiTable(BorderStyle.UNICODE,1);
|
||||
var TribeTableLower = new AsciiTable(BorderStyle.UNICODE,1);
|
||||
|
||||
List<String> stringUpperListTribe=new ArrayList<>();
|
||||
List<String> stringLowerListTribe=new ArrayList<>();
|
||||
stringUpperListTribe.add("Char/Events");
|
||||
stringLowerListTribe.add("Char/Events");
|
||||
for(int i=0;i<Math.max(model.upperListTribeCards.size(),model.lowerListTribeCards.size());i++)
|
||||
{
|
||||
if(i<model.upperListTribeCards.size())
|
||||
{
|
||||
stringUpperListTribe.add(i+"-"+model.upperListTribeCards.get(i).toStringBoard());
|
||||
}
|
||||
if(i<model.lowerListTribeCards.size())
|
||||
{
|
||||
stringLowerListTribe.add(i+"-"+model.lowerListTribeCards.get(i).toStringBoard());
|
||||
}
|
||||
}
|
||||
var BuildTableUpper = new AsciiTable(BorderStyle.UNICODE,1);
|
||||
BuildTableUpper.addHeader("Building ");
|
||||
var BuildTableLower = new AsciiTable(BorderStyle.UNICODE,1);
|
||||
BuildTableLower.addHeader("Building ");
|
||||
|
||||
for(int i=0;i<Math.max(model.upperListBuildingCards.size(),model.lowerListBuildingCards.size());i++)
|
||||
{
|
||||
if(i<model.upperListBuildingCards.size())
|
||||
{
|
||||
BuildTableUpper.addRow(i+"-"+model.upperListBuildingCards.get(i).toString());
|
||||
}
|
||||
if(i<model.lowerListBuildingCards.size())
|
||||
{
|
||||
BuildTableLower.addRow(i+"-"+model.lowerListBuildingCards.get(i).toString());
|
||||
}
|
||||
}
|
||||
stringUpperListTribe.forEach(TribeTableUpper::addRow);
|
||||
stringLowerListTribe.forEach(TribeTableLower::addRow);
|
||||
offerTrack.addRow(stringUpOffer);
|
||||
offerTrack.addRow(stringDownOffer);
|
||||
return "CURRENT STATE\n"+model.currentState+"\n"+model.orderLogicCard.toString()+"\n"+ AsciiTable.sideBySide(Arrays.stream((TribeTableUpper.build().split("\n"))).toList(), Arrays.stream(BuildTableUpper.build().split("\n")).toList(),2)+"\n"+offerTrack.build()+"\n"+AsciiTable.sideBySide(List.of(TribeTableLower.build().split("\n")), Arrays.stream(BuildTableLower.build().split("\n")).toList(),2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ module it.polimi.ingsw.gc14 {
|
||||
requires java.smartcardio;
|
||||
requires com.google.gson;
|
||||
requires java.desktop;
|
||||
requires org.jline.terminal;
|
||||
requires org.jline.reader;
|
||||
requires org.jline.builtins;
|
||||
|
||||
opens it.polimi.ingsw.gc14 to javafx.fxml, com.google.gson;
|
||||
opens it.polimi.ingsw.gc14.Model to com.google.gson;
|
||||
|
||||
Reference in New Issue
Block a user