Fix: TUI refactor non complete
This commit is contained in:
@@ -148,10 +148,7 @@ public class ClientLauncherTUI {
|
||||
node("building"))),
|
||||
node("totem"),
|
||||
node("skip"),
|
||||
node("render",
|
||||
node("full"),
|
||||
node("board"),
|
||||
node("players")),
|
||||
node("render"),
|
||||
node("rematch"),
|
||||
node("quit")
|
||||
);
|
||||
@@ -220,12 +217,7 @@ public class ClientLauncherTUI {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
view.renderBoard();
|
||||
}
|
||||
|
||||
private int parsePos(String[] parts, int idx) {
|
||||
|
||||
@@ -274,6 +274,6 @@ public class BuildingCard extends PlayableCard implements Cloneable , BuildingEf
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "⎕:" + " ID:" + String.valueOf(getEffectId())+ " $:"+String.valueOf(getPrice())+" PV:"+String.valueOf(getPrestigeValue());
|
||||
return ":" + " ID:" + String.valueOf(getEffectId())+ " 🍗:"+String.valueOf(getPrice())+" \uD83C\uDFC5:"+String.valueOf(getPrestigeValue());
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ public class Builder extends Character {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " RV:" + String.valueOf(reductionValue) + " PV:" + String.valueOf(prestigeValue);
|
||||
return super.toString() + " \uD83D\uDD28:" + String.valueOf(reductionValue) + " \uD83C\uDFC5:" + String.valueOf(prestigeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +154,7 @@ public class Builder extends Character {
|
||||
@Override
|
||||
public String toStringBoard()
|
||||
{
|
||||
return super.toStringBoard()+" "+ " RV:" + String.valueOf(reductionValue) + " PV:" + String.valueOf(prestigeValue);
|
||||
return super.toStringBoard()+" \uD83D\uDD28:" + String.valueOf(reductionValue) + " \uD83C\uDFC5:" + String.valueOf(prestigeValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ public class Shaman extends Character {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " *:" + String.valueOf(icon);
|
||||
return super.toString() + " " + "🌟".repeat(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +102,7 @@ public class Shaman extends Character {
|
||||
*/
|
||||
@Override
|
||||
public String toStringBoard() {
|
||||
return super.toStringBoard() + " *:" + String.valueOf(icon);
|
||||
return super.toStringBoard() + " " + "🌟".repeat(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class EventCard extends TribeCard {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()+"(Event)"+type.toString();
|
||||
return super.toString() + " " + type.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +89,7 @@ public abstract class EventCard extends TribeCard {
|
||||
*/
|
||||
@Override
|
||||
public String toStringBoard() {
|
||||
return super.toString()+"(Event)"+type.toString();
|
||||
return super.toString() + " " + type.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ public class ShamanicRitual extends EventCard {
|
||||
*/
|
||||
@Override
|
||||
public String toStringBoard() {
|
||||
return super.toStringBoard()+" *>:"+prestigeToAdd+" *<:"+prestigeToRemove;
|
||||
return super.toStringBoard()+" \uD83C\uDF1F>:"+prestigeToAdd+" \uD83C\uDF1F<:"+prestigeToRemove;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ public class CurrentState implements Serializable {
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
var table= new AsciiTable(BorderStyle.UNICODE,6);
|
||||
var table= new AsciiTable(BorderStyle.ROUNDED,6);
|
||||
List<String>header= new ArrayList<>();
|
||||
header.add("Player");
|
||||
header.add("NUpper");
|
||||
|
||||
@@ -66,7 +66,7 @@ public class Order2 extends OrderLogicCard {
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
var table = new AsciiTable(BorderStyle.UNICODE, 2);
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, 2);
|
||||
List<String> stringUp=new ArrayList<>();
|
||||
for(int i=0;i<2;i++)
|
||||
{
|
||||
@@ -83,8 +83,8 @@ public class Order2 extends OrderLogicCard {
|
||||
table.addRow(stringUp);
|
||||
|
||||
List<String> stringList=new ArrayList<>();
|
||||
stringList.add("+1 Food");
|
||||
stringList.add("-1 Food / -2 PP");
|
||||
stringList.add("+1🍗");
|
||||
stringList.add("-1🍗 / -2\uD83C\uDFC5");
|
||||
table.addRow(stringList);
|
||||
return "TURN ORDER\n" + table.build() + "\n";
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class Order3 extends OrderLogicCard {
|
||||
public String toString()
|
||||
{
|
||||
|
||||
var table = new AsciiTable(BorderStyle.UNICODE, 3);
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, 3);
|
||||
|
||||
List<String> stringUp=new ArrayList<>();
|
||||
for(int i=0;i<3;i++)
|
||||
@@ -87,9 +87,9 @@ public class Order3 extends OrderLogicCard {
|
||||
|
||||
List<String> stringList=new ArrayList<>();
|
||||
|
||||
stringList.add("+2 Food");
|
||||
stringList.add("+2 🍗");
|
||||
stringList.add("--");
|
||||
stringList.add("-1 Food / -2 PP");
|
||||
stringList.add("-1 🍗 / -2 ⭐");
|
||||
table.addRow(stringList);
|
||||
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public class Order4 extends OrderLogicCard {
|
||||
public String toString()
|
||||
{
|
||||
|
||||
var table = new AsciiTable(BorderStyle.UNICODE, 4);
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, 4);
|
||||
List<String> stringUp=new ArrayList<>();
|
||||
for(int i=0;i<4;i++)
|
||||
{
|
||||
@@ -92,10 +92,10 @@ public class Order4 extends OrderLogicCard {
|
||||
table.addRow(stringUp);
|
||||
|
||||
List<String> stringList=new ArrayList<>();
|
||||
stringList.add("+2 Food");
|
||||
stringList.add("+1 Food");
|
||||
stringList.add("+2 🍗");
|
||||
stringList.add("+1 🍗");
|
||||
stringList.add("--");
|
||||
stringList.add("-1 Food / -2 PP");
|
||||
stringList.add("-1 🍗 / -2 ⭐");
|
||||
table.addRow(stringList);
|
||||
return "TURN ORDER\n" + table.build() + "\n";
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class Order5 extends OrderLogicCard {
|
||||
public String toString()
|
||||
{
|
||||
|
||||
var table = new AsciiTable(BorderStyle.UNICODE, 5);
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, 5);
|
||||
List<String> stringUp=new ArrayList<>();
|
||||
for(int i=0;i<5;i++)
|
||||
{
|
||||
@@ -93,11 +93,11 @@ public class Order5 extends OrderLogicCard {
|
||||
table.addRow(stringUp);
|
||||
|
||||
List<String> stringList=new ArrayList<>();
|
||||
stringList.add("+3 Food");
|
||||
stringList.add("+1 Food");
|
||||
stringList.add("+3 🍗");
|
||||
stringList.add("+1 🍗");
|
||||
stringList.add("--");
|
||||
stringList.add("--");
|
||||
stringList.add("-1 Food / -2 PP");
|
||||
stringList.add("-1 🍗 / -2 ⭐");
|
||||
table.addRow(stringList);
|
||||
return "TURN ORDER\n" + table.build() + "\n";
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class PlayableCard implements Serializable {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "⎕:";
|
||||
return ":";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +86,6 @@ public abstract class PlayableCard implements Serializable {
|
||||
*/
|
||||
public String toStringBoard()
|
||||
{
|
||||
return "⎕:";
|
||||
return ":";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,15 +271,15 @@ public class Player implements Serializable {
|
||||
public String toString() {
|
||||
int Last = -1;
|
||||
|
||||
var table = new AsciiTable(UNICODE, 1);
|
||||
var table = new AsciiTable(ROUNDED, 1);
|
||||
|
||||
table.addHeader(this.getUserName());
|
||||
table.addRow("RESOURCES:");
|
||||
if(this.totem != null) {
|
||||
table.addRow("Totem: " + this.totem.toString());
|
||||
}
|
||||
table.addRow("Food: " + this.getFoodValue());
|
||||
table.addRow("Prestige: " + this.getPrestigeValue());
|
||||
table.addRow("🍗 " + this.getFoodValue());
|
||||
table.addRow("2\uD83C\uDFC5 " + this.getPrestigeValue());
|
||||
table.addSeparator();
|
||||
|
||||
if(!this.hunters.isEmpty()){
|
||||
|
||||
@@ -182,7 +182,7 @@ public class Slot implements Serializable {
|
||||
s.append("↓");
|
||||
}
|
||||
if(getFood()!=0)
|
||||
s.append("+"+getFood()+" Food");
|
||||
s.append("+"+getFood()+" 🍗");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,13 @@ import java.util.*;
|
||||
* to obtain the fully rendered string. Column width is computed automatically
|
||||
* from the widest cell across all rows.
|
||||
*
|
||||
* <p>Cell widths are measured in terminal columns, not Java {@code char} units:
|
||||
* wide characters (emoji, CJK) count as 2 columns each.
|
||||
*
|
||||
* <p>Example usage:
|
||||
* <pre>{@code
|
||||
* AsciiTable table = new AsciiTable(BorderStyle.UNICODE, 3);
|
||||
* table.addHeader("Name", "Food", "PP");
|
||||
* table.addHeader("Name", "🍗", "⭐");
|
||||
* table.addRow("Alice", "4", "12");
|
||||
* table.addRow("Bob", "2", "8");
|
||||
* System.out.println(table.build());
|
||||
@@ -19,92 +22,33 @@ import java.util.*;
|
||||
*/
|
||||
public class AsciiTable {
|
||||
|
||||
/**
|
||||
* The border style used to draw the table (e.g. Unicode box-drawing,
|
||||
* plain ASCII, rounded corners).
|
||||
*/
|
||||
private final BorderStyle s;
|
||||
|
||||
/**
|
||||
* The number of columns in the table.
|
||||
* Every row added via {@link #addRow} must contain exactly this many cells.
|
||||
*/
|
||||
private final int cols;
|
||||
|
||||
/**
|
||||
* 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<List<String>> rows = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Row indices after which a horizontal separator line is drawn.
|
||||
* Populated by {@link #addHeader} and {@link #addSeparator}.
|
||||
*/
|
||||
private final List<Integer> separators = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)); }
|
||||
|
||||
/**
|
||||
* 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<String> cells) { rows.add(cells); }
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the current last row so that a horizontal separator line is
|
||||
* drawn below it when the table is built.
|
||||
*
|
||||
* <p>Call this method immediately after the row that should be followed
|
||||
* by the separator.
|
||||
*/
|
||||
public void addSeparator() { separators.add(rows.size() - 1); }
|
||||
|
||||
/**
|
||||
* Renders the table to a multi-line string.
|
||||
*
|
||||
* <p>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
|
||||
* Column width is the widest cell in display columns (wide chars = 2), plus 1.
|
||||
*/
|
||||
public String build() {
|
||||
var sb = new StringBuilder();
|
||||
int maxWidth = rows.stream()
|
||||
.mapToInt(x -> x.stream().mapToInt(y -> y.length()).max().getAsInt())
|
||||
.mapToInt(x -> x.stream().mapToInt(y -> displayWidth(y)).max().getAsInt())
|
||||
.max().getAsInt() + 1;
|
||||
sb.append(hline(s.tl(), s.mt(), s.tr(), maxWidth)).append('\n');
|
||||
|
||||
@@ -120,16 +64,6 @@ public class AsciiTable {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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++) {
|
||||
@@ -140,41 +74,22 @@ public class AsciiTable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Pads {@code s} with trailing spaces so its display width equals {@code w}.
|
||||
* If the string is already at or over {@code w} columns, it is returned as-is.
|
||||
*/
|
||||
private static String rpad(String s, int w) {
|
||||
if (s.length() >= w) return s.substring(0, w);
|
||||
return s + " ".repeat(w - s.length());
|
||||
int dw = displayWidth(s);
|
||||
if (dw >= w) return s;
|
||||
return s + " ".repeat(w - dw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places two pre-rendered text blocks side by side, separated by a gap.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* List<String> left = Arrays.asList(table1.build().split("\n"));
|
||||
* List<String> right = Arrays.asList(table2.build().split("\n"));
|
||||
* System.out.print(AsciiTable.sideBySide(left, right, 2));
|
||||
* }</pre>
|
||||
*
|
||||
* @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
|
||||
* Left-block lines are padded to a uniform display width so the right block
|
||||
* always starts at the same column. Uses {@link #displayWidth} for measurement.
|
||||
*/
|
||||
public static String sideBySide(List<String> left, List<String> right, int gap) {
|
||||
int leftWidth = left.stream().mapToInt(String::length).max().orElse(0);
|
||||
int leftWidth = left.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0);
|
||||
int maxHeight = Math.max(left.size(), right.size());
|
||||
String padding = " ".repeat(gap);
|
||||
|
||||
@@ -187,4 +102,57 @@ public class AsciiTable {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// ── Wide-character width ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns the number of terminal columns required to display {@code s}.
|
||||
* Wide characters (emoji, CJK, full-width) count as 2; all others as 1.
|
||||
*/
|
||||
public static int displayWidth(String s) {
|
||||
int w = 0;
|
||||
for (int i = 0; i < s.length(); ) {
|
||||
int cp = s.codePointAt(i);
|
||||
w += isWide(cp) ? 2 : 1;
|
||||
i += Character.charCount(cp);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} when {@code cp} is a wide (2-column) character.
|
||||
* Covers CJK blocks, Hangul, full-width forms, and emoji (including
|
||||
* the specific emoji used in this project: 🍗 U+1F357, ⭐ U+2B50).
|
||||
*/
|
||||
private static boolean isWide(int cp) {
|
||||
if (cp < 0x1100) return false;
|
||||
if (cp <= 0x115F) return true; // Hangul Jamo
|
||||
if (cp < 0x2E80) {
|
||||
// Specific wide symbols below 0x2E80
|
||||
return cp == 0x2B50 // ⭐ STAR
|
||||
|| cp == 0x2B55; // ⭕ HEAVY LARGE CIRCLE
|
||||
}
|
||||
if (cp <= 0x303E) return true; // CJK Radicals, Kangxi, CJK Symbols
|
||||
if (cp < 0x3041) return false;
|
||||
if (cp <= 0xA4CF) return true; // Hiragana, Katakana, Bopomofo, CJK Unified
|
||||
if (cp < 0xA960) return false;
|
||||
if (cp <= 0xA97F) return true; // Hangul Jamo Extended-A
|
||||
if (cp < 0xAC00) return false;
|
||||
if (cp <= 0xD7AF) return true; // Hangul Syllables
|
||||
if (cp < 0xF900) return false;
|
||||
if (cp <= 0xFAFF) return true; // CJK Compatibility Ideographs
|
||||
if (cp < 0xFE10) return false;
|
||||
if (cp <= 0xFE1F) return true; // Vertical Forms
|
||||
if (cp < 0xFE30) return false;
|
||||
if (cp <= 0xFE6F) return true; // CJK Compatibility Forms
|
||||
if (cp < 0xFF00) return false;
|
||||
if (cp <= 0xFF60) return true; // Fullwidth Latin, punctuation
|
||||
if (cp < 0xFFE0) return false;
|
||||
if (cp <= 0xFFE6) return true; // Fullwidth Signs
|
||||
if (cp < 0x1F004) return false;
|
||||
if (cp <= 0x1FAFF) return true; // Emoji (mahjong, playing cards, flags,
|
||||
// misc symbols, transport, 🍗 U+1F357, ...)
|
||||
if (cp < 0x20000) return false;
|
||||
return cp <= 0x3FFFD; // CJK Extension B through G
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,9 @@ import java.util.Map;
|
||||
/**
|
||||
* Text-based User Interface (TUI) implementation of {@link IView}.
|
||||
*
|
||||
* <p>Renders the current state of a {@link MiniModel} directly to the
|
||||
* terminal using Unicode box-drawing characters and fixed-width ASCII tables.
|
||||
* <p>Single render view: board + menu on top, all players' hands in a
|
||||
* responsive grid below. Hands wrap to a new row when their combined width
|
||||
* would exceed the terminal width.
|
||||
*
|
||||
* <p>All output goes through {@link #display(String)}, which uses
|
||||
* {@link LineReader#printAbove} when a JLine reader is set so that the
|
||||
@@ -34,7 +35,7 @@ public class TUI implements IView {
|
||||
*/
|
||||
private LineReader lineReader;
|
||||
|
||||
/** JLine terminal — used to query terminal width for centering. */
|
||||
/** JLine terminal — used to query terminal width for centering and grid layout. */
|
||||
private Terminal terminal;
|
||||
|
||||
/**
|
||||
@@ -73,7 +74,7 @@ public class TUI implements IView {
|
||||
// ── Public render entry points ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Default render: dispatches to the right sub-renderer based on game stage.
|
||||
* Default render: dispatches to the right view based on game stage.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void render() {
|
||||
@@ -88,32 +89,19 @@ public class TUI implements IView {
|
||||
}
|
||||
}
|
||||
|
||||
/** Renders the board panel + menu + player hand side by side. */
|
||||
/** Renders the board + menu (top) and all players' hands in a grid (bottom). */
|
||||
public void renderBoard() {
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error message above (or below) the current board.
|
||||
*
|
||||
* <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.
|
||||
* Shows an error message combined with the current board in one display call,
|
||||
* so only one {@code printAbove} is issued and the prompt is redrawn correctly.
|
||||
*
|
||||
* @param error the error type.
|
||||
* @param message the human-readable message to append.
|
||||
@@ -149,24 +137,54 @@ public class TUI implements IView {
|
||||
|
||||
// ── Content builders (return strings, do not print) ───────────────────────
|
||||
|
||||
/**
|
||||
* Builds the main view: board + menu side by side, followed by all players'
|
||||
* hands arranged in a responsive grid.
|
||||
*/
|
||||
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);
|
||||
return AsciiTable.sideBySide(left, right, 3) + "\n" + buildAllHandsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges every player's hand side-by-side, wrapping to a new grid row
|
||||
* whenever the next panel would exceed the terminal width.
|
||||
*/
|
||||
private String buildAllHandsContent() {
|
||||
List<Player> players = new ArrayList<>(model.players.values());
|
||||
if (players.isEmpty()) return "";
|
||||
|
||||
int termWidth = (terminal != null && terminal.getWidth() > 0) ? terminal.getWidth() : 80;
|
||||
int gap = 3;
|
||||
|
||||
List<List<String>> panels = new ArrayList<>();
|
||||
List<Integer> panelWidths = new ArrayList<>();
|
||||
for (Player p : players) {
|
||||
List<String> lines = Arrays.asList(p.toString().split("\n"));
|
||||
panels.add(lines);
|
||||
panelWidths.add(lines.stream().mapToInt(l -> visibleLength(l)).max().orElse(0));
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < panels.size()) {
|
||||
int rowWidth = panelWidths.get(i);
|
||||
int j = i + 1;
|
||||
while (j < panels.size()
|
||||
&& rowWidth + gap + panelWidths.get(j) <= termWidth) {
|
||||
rowWidth += gap + panelWidths.get(j);
|
||||
j++;
|
||||
}
|
||||
List<String> merged = panels.get(i);
|
||||
for (int k = i + 1; k < j; k++) {
|
||||
merged = Arrays.asList(
|
||||
AsciiTable.sideBySide(merged, panels.get(k), gap).split("\n"));
|
||||
}
|
||||
result.append(String.join("\n", merged)).append("\n");
|
||||
i = j;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String buildTotemsContent() {
|
||||
@@ -212,8 +230,6 @@ public class TUI implements IView {
|
||||
* {@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);
|
||||
@@ -257,9 +273,9 @@ public class TUI implements IView {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Returns the visible (printable) length of a string, stripping ANSI escape codes. */
|
||||
/** Returns the display width of a string in terminal columns, stripping ANSI escape codes. */
|
||||
private int visibleLength(String line) {
|
||||
return line.replaceAll("\033\\[[^m]*m", "").length();
|
||||
return AsciiTable.displayWidth(line.replaceAll("\033\\[[^m]*m", ""));
|
||||
}
|
||||
|
||||
// ── Board / players stamp helpers ─────────────────────────────────────────
|
||||
@@ -291,7 +307,7 @@ public class TUI implements IView {
|
||||
* @return multi-line board string.
|
||||
*/
|
||||
public String BoardStamp() {
|
||||
var offerTrack = new AsciiTable(BorderStyle.UNICODE, model.slotPlayerMap.size());
|
||||
var offerTrack = new AsciiTable(BorderStyle.ROUNDED, model.slotPlayerMap.size());
|
||||
List<String> slotNames = new ArrayList<>();
|
||||
List<String> slotPlayers = new ArrayList<>();
|
||||
|
||||
@@ -301,11 +317,11 @@ public class TUI implements IView {
|
||||
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);
|
||||
var TribeUpper = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||
var TribeLower = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||
var BuildUpper = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||
BuildUpper.addHeader("Building ");
|
||||
var BuildLower = new AsciiTable(BorderStyle.UNICODE, 1);
|
||||
var BuildLower = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||
BuildLower.addHeader("Building ");
|
||||
|
||||
List<String> upperTribeRows = new ArrayList<>();
|
||||
@@ -316,17 +332,17 @@ public class TUI implements IView {
|
||||
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());
|
||||
upperTribeRows.add(i + model.upperListTribeCards.get(i).toStringBoard());
|
||||
if (i < model.lowerListTribeCards.size())
|
||||
lowerTribeRows.add(i + "-" + model.lowerListTribeCards.get(i).toStringBoard());
|
||||
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());
|
||||
BuildUpper.addRow(i + model.upperListBuildingCards.get(i).toString());
|
||||
if (i < model.lowerListBuildingCards.size())
|
||||
BuildLower.addRow(i + "-" + model.lowerListBuildingCards.get(i).toString());
|
||||
BuildLower.addRow(i + model.lowerListBuildingCards.get(i).toString());
|
||||
}
|
||||
|
||||
upperTribeRows.forEach(TribeUpper::addRow);
|
||||
@@ -347,16 +363,17 @@ public class TUI implements IView {
|
||||
// ── Menu options ──────────────────────────────────────────────────────────
|
||||
|
||||
private String printMenuOptions() {
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, 2);
|
||||
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", ""));
|
||||
var table = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||
table.addHeader("Commands");
|
||||
table.addRow("slot <pos>");
|
||||
table.addRow("draw upper tribe <pos>");
|
||||
table.addRow("draw upper building <pos>");
|
||||
table.addRow("draw lower tribe <pos>");
|
||||
table.addRow("draw lower building <pos>");
|
||||
table.addRow("totem <pos>");
|
||||
table.addRow("skip");
|
||||
table.addRow("render");
|
||||
table.addRow("rematch | quit");
|
||||
return table.build();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user