diff --git a/src/main/java/it/polimi/ingsw/gc14/View/TUI/AsciiTable.java b/src/main/java/it/polimi/ingsw/gc14/View/TUI/AsciiTable.java index 26ccb31..813708c 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/TUI/AsciiTable.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/TUI/AsciiTable.java @@ -1,43 +1,112 @@ package it.polimi.ingsw.gc14.View.TUI; import java.util.*; -//TODO javadoc -// Helper generale per costruire tabelle ASCII +/** + * General-purpose helper for building fixed-width ASCII/Unicode tables in a TUI. + * + *

A table is built by adding rows one at a time, then calling {@link #build()} + * to obtain the fully rendered string. Column width is computed automatically + * from the widest cell across all rows. + * + *

Example usage: + *

{@code
+ * AsciiTable table = new AsciiTable(BorderStyle.UNICODE, 3);
+ * table.addHeader("Name", "Food", "PP");
+ * table.addRow("Alice", "4", "12");
+ * table.addRow("Bob",   "2", "8");
+ * System.out.println(table.build());
+ * }
+ */ public class AsciiTable { - //TODO javadoc + + /** + * The border style used to draw the table (e.g. Unicode box-drawing, + * plain ASCII, rounded corners). + */ private final BorderStyle s; - //TODO javadoc + /** + * The number of columns in the table. + * Every row added via {@link #addRow} must contain exactly this many cells. + */ private final int cols; - //TODO javadoc + /** + * The rows of the table, each represented as a list of cell strings. + * Rows are stored in display order; {@link #addHeader} inserts at index 0. + */ private final List> rows = new ArrayList<>(); - //TODO javadoc + /** + * Row indices after which a horizontal separator line is drawn. + * Populated by {@link #addHeader} and {@link #addSeparator}. + */ private final List separators = new ArrayList<>(); - //TODO javadoc + /** + * Constructs an empty {@code AsciiTable} with the given border style + * and column count. + * + * @param s the {@link BorderStyle} to use for box-drawing characters + * @param cols the number of columns; every row must supply exactly this + * many cells + */ public AsciiTable(BorderStyle s, int cols) { this.s = s; this.cols = cols; } - //TODO javadoc + /** + * Appends a row at the bottom of the table. + * + * @param cells one string per column, in left-to-right order + */ public void addRow(String... cells) { rows.add(Arrays.asList(cells)); } - //TODO javadoc + /** + * Appends a row at the bottom of the table. + * + * @param cells a list of strings, one per column, in left-to-right order + */ public void addRow(List cells) { rows.add(cells); } - //TODO javadoc - public void addHeader(String... cells) { rows.add(0, Arrays.asList(cells)); separators.add(0); } + /** + * Inserts a header row at the top of the table and marks it with a + * horizontal separator so that a dividing line is drawn below it. + * + *

Calling this method after rows have already been added will push + * all existing rows down by one position. + * + * @param cells one string per column, in left-to-right order + */ + public void addHeader(String... cells) { + rows.add(0, Arrays.asList(cells)); + separators.add(0); + } - //TODO javadoc - public void addSeparator() { separators.add(rows.size()-1); } + /** + * Marks the current last row so that a horizontal separator line is + * drawn below it when the table is built. + * + *

Call this method immediately after the row that should be followed + * by the separator. + */ + public void addSeparator() { separators.add(rows.size() - 1); } - //TODO javadoc + /** + * Renders the table to a multi-line string. + * + *

Column width is determined dynamically as the length of the longest + * cell across all rows, plus one padding space. All cells are left-aligned + * and padded or truncated to the same width. + * + * @return the fully rendered table as a single string with embedded newlines + */ public String build() { var sb = new StringBuilder(); - int maxWidth = rows.stream().mapToInt(x->x.stream().mapToInt(y->y.length()).max().getAsInt()).max().getAsInt()+1; - sb.append(hline(s.tl(), s.mt(), s.tr(),maxWidth)).append('\n'); + int maxWidth = rows.stream() + .mapToInt(x -> x.stream().mapToInt(y -> y.length()).max().getAsInt()) + .max().getAsInt() + 1; + sb.append(hline(s.tl(), s.mt(), s.tr(), maxWidth)).append('\n'); for (int i = 0; i < rows.size(); i++) { sb.append(s.v()); @@ -45,14 +114,23 @@ public class AsciiTable { sb.append(rpad(" " + cell, maxWidth)).append(s.v()); sb.append('\n'); if (separators.contains(i) && i < rows.size() - 1) - sb.append(hline(s.sl(), s.sx(), s.sr(),maxWidth)).append('\n'); + sb.append(hline(s.sl(), s.sx(), s.sr(), maxWidth)).append('\n'); } sb.append(hline(s.bl(), s.mb(), s.br(), maxWidth)); return sb.toString(); } - //TODO javadoc - private String hline(String l, String m, String r,int maxWidth) { + /** + * Builds a single horizontal border line spanning all columns. + * + * @param l the left-end character (e.g. {@code ╔}, {@code ╠}, {@code ╚}) + * @param m the column-junction character (e.g. {@code ╦}, {@code ╬}, {@code ╩}) + * @param r the right-end character (e.g. {@code ╗}, {@code ╣}, {@code ╝}) + * @param maxWidth the width in characters of each column segment, + * filled with the horizontal line character of the current style + * @return the rendered horizontal line as a string + */ + private String hline(String l, String m, String r, int maxWidth) { var sb = new StringBuilder(l); for (int i = 0; i < cols; i++) { sb.append(s.h().repeat(maxWidth)); @@ -61,13 +139,40 @@ public class AsciiTable { return sb.append(r).toString(); } - //TODO javadoc + /** + * Right-pads a string with spaces to the given width, or truncates it + * if it exceeds that width. + * + * @param s the input string + * @param w the desired output width in characters + * @return a string of exactly {@code w} characters + */ private static String rpad(String s, int w) { if (s.length() >= w) return s.substring(0, w); return s + " ".repeat(w - s.length()); } - //TODO javadoc + /** + * Places two pre-rendered text blocks side by side, separated by a gap. + * + *

Each block is a list of lines as returned by {@link #build()}. + * Lines in the left block are padded to a uniform width so that the + * right block always starts at the same horizontal position. If one + * block is taller than the other, the shorter one is padded with blank + * lines. + * + *

Example: + *

{@code
+     * List left  = Arrays.asList(table1.build().split("\n"));
+     * List right = Arrays.asList(table2.build().split("\n"));
+     * System.out.print(AsciiTable.sideBySide(left, right, 2));
+     * }
+ * + * @param left lines of the left block + * @param right lines of the right block + * @param gap number of blank spaces between the two blocks + * @return the merged string with embedded newlines + */ public static String sideBySide(List left, List right, int gap) { int leftWidth = left.stream().mapToInt(String::length).max().orElse(0); int maxHeight = Math.max(left.size(), right.size()); @@ -75,17 +180,11 @@ public class AsciiTable { var sb = new StringBuilder(); for (int i = 0; i < maxHeight; i++) { - String l = i < left.size() - ? left.get(i) - : " ".repeat(leftWidth); - - // padda la riga sinistra se è più corta delle altre + String l = i < left.size() ? left.get(i) : " ".repeat(leftWidth); l = rpad(l, leftWidth); - String r = i < right.size() ? right.get(i) : ""; sb.append(l).append(padding).append(r).append('\n'); } return sb.toString(); } - } \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java b/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java index e97f1ad..ecd489e 100644 --- a/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java +++ b/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java @@ -4,113 +4,214 @@ import it.polimi.ingsw.gc14.View.IView; import java.util.List; +/** + * Text-based User Interface (TUI) implementation of {@link IView}. + * + *

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}). + * + *

The display is split into two side-by-side panels: + *

    + *
  • Left panel — board or player status, depending on the render method called.
  • + *
  • Right panel — menu options legend and the current player's hand.
  • + *
+ * + *

The terminal is cleared before each render via a platform-aware + * {@code cls} / {@code clear} system call. + * + *

Typical usage: + *

{@code
+ * TUI tui = new TUI(game);
+ * tui.setUsername("Alice");
+ * tui.fullRender();
+ * }
+ */ public class TUI implements IView { - // ── dati di stato ─────────────────────────────────────────── - private BorderStyle style = BorderStyle.UNICODE; + /** + * The {@link Game} model whose state is rendered. + * Updated via {@link #update(Game)} whenever the game state changes. + */ private Game 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. + * + * @param model the {@link Game} model to display; must not be {@code null} + */ public TUI(Game model) { this.model = model; - this.username=""; + 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 + */ 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} + */ @Override public void update(Game model) { this.model = model; } - // ── punto di ingresso ─────────────────────────────────────── - public void render() - { + /** + * Default render entry point, as required by {@link IView}. + * Delegates to {@link #renderBoard()}. + */ + @Override + public void render() { renderBoard(); } - public void fullRender() - { - try{ - String os = System.getProperty("os.name").toLowerCase(); - ProcessBuilder pb; - if (os.contains("win")) { - pb = new ProcessBuilder("cmd", "/c", "cls"); - } else { - pb = new ProcessBuilder("clear"); - } - pb.inheritIO().start().waitFor(); - } - catch(Exception e){ - } - Listlines=List.of(model.BoardStamp().split("\n")); - List lines2=List.of((PrintMenuOptions()+"\n"+model.getPlayerByUsername(username)).split("\n")); - System.out.println(model.PlayersStamp()+"\n"+ AsciiTable.sideBySide(lines,lines2,3)); + /** + * Renders a full view of the game, combining both player status + * and board status. + * + *

Layout: + *

    + *
  • Top — player status table produced by + * {@link Game#PlayersStamp()}.
  • + *
  • Bottom-left — board status produced by + * {@link Game#BoardStamp()}.
  • + *
  • Bottom-right — menu options legend and the local + * player's hand.
  • + *
+ * + *

The terminal is cleared before rendering. + */ + public void fullRender() { + clearTerminal(); + List lines = List.of(model.BoardStamp().split("\n")); + List lines2 = List.of((printMenuOptions() + "\n" + + model.getPlayerByUsername(username)).split("\n")); + System.out.println(model.PlayersStamp() + "\n" + + AsciiTable.sideBySide(lines, lines2, 3)); } - public void renderBoard() - { - try{ - String os = System.getProperty("os.name").toLowerCase(); - ProcessBuilder pb; - if (os.contains("win")) { - pb = new ProcessBuilder("cmd", "/c", "cls"); - } else { - pb = new ProcessBuilder("clear"); - } - pb.inheritIO().start().waitFor(); - } - catch(Exception e){ - } - Listlines=List.of(model.BoardStamp().split("\n")); - List lines2=List.of((PrintMenuOptions()+"\nYOUR HAND\n"+model.getPlayerByUsername(username)).split("\n")); - System.out.println(AsciiTable.sideBySide(lines,lines2,3)); - } - public void renderPlayer() - { - try{ - String os = System.getProperty("os.name").toLowerCase(); - ProcessBuilder pb; - if (os.contains("win")) { - pb = new ProcessBuilder("cmd", "/c", "cls"); - } else { - pb = new ProcessBuilder("clear"); - } - pb.inheritIO().start().waitFor(); - } - catch(Exception e){ - } - Listlines=List.of(model.PlayersStamp().split("\n")); - List lines2=List.of(PrintMenuOptions().split("\n")); - System.out.println(AsciiTable.sideBySide(lines,lines2,3)); + /** + * Renders the board status only. + * + *

Layout: + *

    + *
  • Left panel — turn order, upper card row, offer track, + * and lower card row, as produced by {@link Game#BoardStamp()}.
  • + *
  • Right panel — menu options legend followed by the local + * player's hand.
  • + *
+ * + *

The terminal is cleared before rendering. + */ + public void renderBoard() { + clearTerminal(); + List lines = List.of(model.BoardStamp().split("\n")); + List lines2 = List.of((printMenuOptions() + "\nYOUR HAND\n" + + model.getPlayerByUsername(username)).split("\n")); + System.out.println(AsciiTable.sideBySide(lines, lines2, 3)); } - public void showMessage(String message) - { + /** + * Renders the player status table only. + * + *

Layout: + *

    + *
  • Left panel — prestige, food, character deck, and building + * deck for all players, as produced by {@link Game#PlayersStamp()}.
  • + *
  • Right panel — menu options legend.
  • + *
+ * + *

The terminal is cleared before rendering. + */ + public void renderPlayer() { + clearTerminal(); + List lines = List.of(model.PlayersStamp().split("\n")); + List lines2 = List.of(printMenuOptions().split("\n")); + System.out.println(AsciiTable.sideBySide(lines, lines2, 3)); + } + + /** + * Prints a plain message to standard output. + * + * @param message the message to display + */ + public void showMessage(String message) { System.out.println(message); } - - public void showError(String message) - { + /** + * Prints an error message to standard output. + * + * @param message the error message to display + */ + public void showError(String message) { + clearTerminal(); + render(); System.out.println(message); } - private String PrintMenuOptions() - { - var table=new AsciiTable(BorderStyle.ROUNDED,2); - table.addHeader( "Menu Options","Render Options"); - table.addRow( List.of("0-SlotChoice(pos)","A-Full Render")); - table.addRow(List.of("1-DrawUpperTribe(pos)","B-Board Render")); - table.addRow(List.of("2-DrawUpperBuilding(pos)","C-Players Render")); - table.addRow(List.of("3-DrawLowerTribe(pos)","")); - table.addRow(List.of("4-DrawLowerBuilding(pos)","")); - table.addRow(List.of("5-PickOptionalTribe(pos)","")); - table.addRow(List.of("6-PickOptionalBuilding(pos)","")); - table.addRow(List.of("7-NoOptional","")); - table.addRow(List.of("8-NoUpperCard","")); - table.addRow(List.of("9-NoLowerCard","")); + /** + * Builds and returns the menu options panel as a two-column + * {@link AsciiTable} with rounded borders. + * + *

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 + */ + private String printMenuOptions() { + var table = new AsciiTable(BorderStyle.ROUNDED, 2); + table.addHeader("Menu Options", "Render Options"); + table.addRow(List.of("0-SlotChoice(pos)", "A-Full Render")); + table.addRow(List.of("1-DrawUpperTribe(pos)", "B-Board Render")); + table.addRow(List.of("2-DrawUpperBuilding(pos)", "C-Players Render")); + table.addRow(List.of("3-DrawLowerTribe(pos)", "")); + table.addRow(List.of("4-DrawLowerBuilding(pos)", "")); + table.addRow(List.of("5-PickOptionalTribe(pos)", "")); + table.addRow(List.of("6-PickOptionalBuilding(pos)", "")); + table.addRow(List.of("7-NoOptional", "")); + table.addRow(List.of("8-NoUpperCard", "")); + table.addRow(List.of("9-NoLowerCard", "")); 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) {} + } +} \ No newline at end of file