Fix: TUI refactor non complete
This commit is contained in:
@@ -148,10 +148,7 @@ public class ClientLauncherTUI {
|
|||||||
node("building"))),
|
node("building"))),
|
||||||
node("totem"),
|
node("totem"),
|
||||||
node("skip"),
|
node("skip"),
|
||||||
node("render",
|
node("render"),
|
||||||
node("full"),
|
|
||||||
node("board"),
|
|
||||||
node("players")),
|
|
||||||
node("rematch"),
|
node("rematch"),
|
||||||
node("quit")
|
node("quit")
|
||||||
);
|
);
|
||||||
@@ -220,12 +217,7 @@ public class ClientLauncherTUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleRender(String[] parts) {
|
private void handleRender(String[] parts) {
|
||||||
String sub = parts.length > 1 ? parts[1].toLowerCase() : "board";
|
view.renderBoard();
|
||||||
switch (sub) {
|
|
||||||
case "full" -> view.fullRender();
|
|
||||||
case "players" -> view.renderPlayer();
|
|
||||||
default -> view.renderBoard();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int parsePos(String[] parts, int idx) {
|
private int parsePos(String[] parts, int idx) {
|
||||||
|
|||||||
@@ -274,6 +274,6 @@ public class BuildingCard extends PlayableCard implements Cloneable , BuildingEf
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
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
|
@Override
|
||||||
public String toString() {
|
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
|
@Override
|
||||||
public String toStringBoard()
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + " *:" + String.valueOf(icon);
|
return super.toString() + " " + "🌟".repeat(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,7 +102,7 @@ public class Shaman extends Character {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toStringBoard() {
|
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
|
@Override
|
||||||
public String toString() {
|
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
|
@Override
|
||||||
public String toStringBoard() {
|
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
|
@Override
|
||||||
public String toStringBoard() {
|
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
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
var table= new AsciiTable(BorderStyle.UNICODE,6);
|
var table= new AsciiTable(BorderStyle.ROUNDED,6);
|
||||||
List<String>header= new ArrayList<>();
|
List<String>header= new ArrayList<>();
|
||||||
header.add("Player");
|
header.add("Player");
|
||||||
header.add("NUpper");
|
header.add("NUpper");
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class Order2 extends OrderLogicCard {
|
|||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
var table = new AsciiTable(BorderStyle.UNICODE, 2);
|
var table = new AsciiTable(BorderStyle.ROUNDED, 2);
|
||||||
List<String> stringUp=new ArrayList<>();
|
List<String> stringUp=new ArrayList<>();
|
||||||
for(int i=0;i<2;i++)
|
for(int i=0;i<2;i++)
|
||||||
{
|
{
|
||||||
@@ -83,8 +83,8 @@ public class Order2 extends OrderLogicCard {
|
|||||||
table.addRow(stringUp);
|
table.addRow(stringUp);
|
||||||
|
|
||||||
List<String> stringList=new ArrayList<>();
|
List<String> stringList=new ArrayList<>();
|
||||||
stringList.add("+1 Food");
|
stringList.add("+1🍗");
|
||||||
stringList.add("-1 Food / -2 PP");
|
stringList.add("-1🍗 / -2\uD83C\uDFC5");
|
||||||
table.addRow(stringList);
|
table.addRow(stringList);
|
||||||
return "TURN ORDER\n" + table.build() + "\n";
|
return "TURN ORDER\n" + table.build() + "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class Order3 extends OrderLogicCard {
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
||||||
var table = new AsciiTable(BorderStyle.UNICODE, 3);
|
var table = new AsciiTable(BorderStyle.ROUNDED, 3);
|
||||||
|
|
||||||
List<String> stringUp=new ArrayList<>();
|
List<String> stringUp=new ArrayList<>();
|
||||||
for(int i=0;i<3;i++)
|
for(int i=0;i<3;i++)
|
||||||
@@ -87,9 +87,9 @@ public class Order3 extends OrderLogicCard {
|
|||||||
|
|
||||||
List<String> stringList=new ArrayList<>();
|
List<String> stringList=new ArrayList<>();
|
||||||
|
|
||||||
stringList.add("+2 Food");
|
stringList.add("+2 🍗");
|
||||||
stringList.add("--");
|
stringList.add("--");
|
||||||
stringList.add("-1 Food / -2 PP");
|
stringList.add("-1 🍗 / -2 ⭐");
|
||||||
table.addRow(stringList);
|
table.addRow(stringList);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class Order4 extends OrderLogicCard {
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
||||||
var table = new AsciiTable(BorderStyle.UNICODE, 4);
|
var table = new AsciiTable(BorderStyle.ROUNDED, 4);
|
||||||
List<String> stringUp=new ArrayList<>();
|
List<String> stringUp=new ArrayList<>();
|
||||||
for(int i=0;i<4;i++)
|
for(int i=0;i<4;i++)
|
||||||
{
|
{
|
||||||
@@ -92,10 +92,10 @@ public class Order4 extends OrderLogicCard {
|
|||||||
table.addRow(stringUp);
|
table.addRow(stringUp);
|
||||||
|
|
||||||
List<String> stringList=new ArrayList<>();
|
List<String> stringList=new ArrayList<>();
|
||||||
stringList.add("+2 Food");
|
stringList.add("+2 🍗");
|
||||||
stringList.add("+1 Food");
|
stringList.add("+1 🍗");
|
||||||
stringList.add("--");
|
stringList.add("--");
|
||||||
stringList.add("-1 Food / -2 PP");
|
stringList.add("-1 🍗 / -2 ⭐");
|
||||||
table.addRow(stringList);
|
table.addRow(stringList);
|
||||||
return "TURN ORDER\n" + table.build() + "\n";
|
return "TURN ORDER\n" + table.build() + "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class Order5 extends OrderLogicCard {
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
||||||
var table = new AsciiTable(BorderStyle.UNICODE, 5);
|
var table = new AsciiTable(BorderStyle.ROUNDED, 5);
|
||||||
List<String> stringUp=new ArrayList<>();
|
List<String> stringUp=new ArrayList<>();
|
||||||
for(int i=0;i<5;i++)
|
for(int i=0;i<5;i++)
|
||||||
{
|
{
|
||||||
@@ -93,11 +93,11 @@ public class Order5 extends OrderLogicCard {
|
|||||||
table.addRow(stringUp);
|
table.addRow(stringUp);
|
||||||
|
|
||||||
List<String> stringList=new ArrayList<>();
|
List<String> stringList=new ArrayList<>();
|
||||||
stringList.add("+3 Food");
|
stringList.add("+3 🍗");
|
||||||
stringList.add("+1 Food");
|
stringList.add("+1 🍗");
|
||||||
stringList.add("--");
|
stringList.add("--");
|
||||||
stringList.add("--");
|
stringList.add("--");
|
||||||
stringList.add("-1 Food / -2 PP");
|
stringList.add("-1 🍗 / -2 ⭐");
|
||||||
table.addRow(stringList);
|
table.addRow(stringList);
|
||||||
return "TURN ORDER\n" + table.build() + "\n";
|
return "TURN ORDER\n" + table.build() + "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public abstract class PlayableCard implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "⎕:";
|
return ":";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,6 +86,6 @@ public abstract class PlayableCard implements Serializable {
|
|||||||
*/
|
*/
|
||||||
public String toStringBoard()
|
public String toStringBoard()
|
||||||
{
|
{
|
||||||
return "⎕:";
|
return ":";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,15 +271,15 @@ public class Player implements Serializable {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
int Last = -1;
|
int Last = -1;
|
||||||
|
|
||||||
var table = new AsciiTable(UNICODE, 1);
|
var table = new AsciiTable(ROUNDED, 1);
|
||||||
|
|
||||||
table.addHeader(this.getUserName());
|
table.addHeader(this.getUserName());
|
||||||
table.addRow("RESOURCES:");
|
table.addRow("RESOURCES:");
|
||||||
if(this.totem != null) {
|
if(this.totem != null) {
|
||||||
table.addRow("Totem: " + this.totem.toString());
|
table.addRow("Totem: " + this.totem.toString());
|
||||||
}
|
}
|
||||||
table.addRow("Food: " + this.getFoodValue());
|
table.addRow("🍗 " + this.getFoodValue());
|
||||||
table.addRow("Prestige: " + this.getPrestigeValue());
|
table.addRow("2\uD83C\uDFC5 " + this.getPrestigeValue());
|
||||||
table.addSeparator();
|
table.addSeparator();
|
||||||
|
|
||||||
if(!this.hunters.isEmpty()){
|
if(!this.hunters.isEmpty()){
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ public class Slot implements Serializable {
|
|||||||
s.append("↓");
|
s.append("↓");
|
||||||
}
|
}
|
||||||
if(getFood()!=0)
|
if(getFood()!=0)
|
||||||
s.append("+"+getFood()+" Food");
|
s.append("+"+getFood()+" 🍗");
|
||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ import java.util.*;
|
|||||||
* to obtain the fully rendered string. Column width is computed automatically
|
* to obtain the fully rendered string. Column width is computed automatically
|
||||||
* from the widest cell across all rows.
|
* 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:
|
* <p>Example usage:
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* AsciiTable table = new AsciiTable(BorderStyle.UNICODE, 3);
|
* AsciiTable table = new AsciiTable(BorderStyle.UNICODE, 3);
|
||||||
* table.addHeader("Name", "Food", "PP");
|
* table.addHeader("Name", "🍗", "⭐");
|
||||||
* table.addRow("Alice", "4", "12");
|
* table.addRow("Alice", "4", "12");
|
||||||
* table.addRow("Bob", "2", "8");
|
* table.addRow("Bob", "2", "8");
|
||||||
* System.out.println(table.build());
|
* System.out.println(table.build());
|
||||||
@@ -19,92 +22,33 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class AsciiTable {
|
public class AsciiTable {
|
||||||
|
|
||||||
/**
|
|
||||||
* The border style used to draw the table (e.g. Unicode box-drawing,
|
|
||||||
* plain ASCII, rounded corners).
|
|
||||||
*/
|
|
||||||
private final BorderStyle s;
|
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;
|
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<>();
|
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<>();
|
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) {
|
public AsciiTable(BorderStyle s, int cols) {
|
||||||
this.s = s; this.cols = 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)); }
|
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); }
|
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) {
|
public void addHeader(String... cells) {
|
||||||
rows.add(0, Arrays.asList(cells));
|
rows.add(0, Arrays.asList(cells));
|
||||||
separators.add(0);
|
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); }
|
public void addSeparator() { separators.add(rows.size() - 1); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the table to a multi-line string.
|
* Renders the table to a multi-line string.
|
||||||
*
|
* Column width is the widest cell in display columns (wide chars = 2), plus 1.
|
||||||
* <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
|
|
||||||
*/
|
*/
|
||||||
public String build() {
|
public String build() {
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
int maxWidth = rows.stream()
|
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;
|
.max().getAsInt() + 1;
|
||||||
sb.append(hline(s.tl(), s.mt(), s.tr(), maxWidth)).append('\n');
|
sb.append(hline(s.tl(), s.mt(), s.tr(), maxWidth)).append('\n');
|
||||||
|
|
||||||
@@ -120,16 +64,6 @@ public class AsciiTable {
|
|||||||
return sb.toString();
|
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) {
|
private String hline(String l, String m, String r, int maxWidth) {
|
||||||
var sb = new StringBuilder(l);
|
var sb = new StringBuilder(l);
|
||||||
for (int i = 0; i < cols; i++) {
|
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
|
* Pads {@code s} with trailing spaces so its display width equals {@code w}.
|
||||||
* if it exceeds that width.
|
* If the string is already at or over {@code w} columns, it is returned as-is.
|
||||||
*
|
|
||||||
* @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) {
|
private static String rpad(String s, int w) {
|
||||||
if (s.length() >= w) return s.substring(0, w);
|
int dw = displayWidth(s);
|
||||||
return s + " ".repeat(w - s.length());
|
if (dw >= w) return s;
|
||||||
|
return s + " ".repeat(w - dw);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Places two pre-rendered text blocks side by side, separated by a gap.
|
* Places two pre-rendered text blocks side by side, separated by a gap.
|
||||||
*
|
* Left-block lines are padded to a uniform display width so the right block
|
||||||
* <p>Each block is a list of lines as returned by {@link #build()}.
|
* always starts at the same column. Uses {@link #displayWidth} for measurement.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
public static String sideBySide(List<String> left, List<String> right, int gap) {
|
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());
|
int maxHeight = Math.max(left.size(), right.size());
|
||||||
String padding = " ".repeat(gap);
|
String padding = " ".repeat(gap);
|
||||||
|
|
||||||
@@ -187,4 +102,57 @@ public class AsciiTable {
|
|||||||
}
|
}
|
||||||
return sb.toString();
|
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}.
|
* Text-based User Interface (TUI) implementation of {@link IView}.
|
||||||
*
|
*
|
||||||
* <p>Renders the current state of a {@link MiniModel} directly to the
|
* <p>Single render view: board + menu on top, all players' hands in a
|
||||||
* terminal using Unicode box-drawing characters and fixed-width ASCII tables.
|
* 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
|
* <p>All output goes through {@link #display(String)}, which uses
|
||||||
* {@link LineReader#printAbove} when a JLine reader is set so that the
|
* {@link LineReader#printAbove} when a JLine reader is set so that the
|
||||||
@@ -34,7 +35,7 @@ public class TUI implements IView {
|
|||||||
*/
|
*/
|
||||||
private LineReader lineReader;
|
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;
|
private Terminal terminal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +74,7 @@ public class TUI implements IView {
|
|||||||
// ── Public render entry points ────────────────────────────────────────────
|
// ── 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
|
@Override
|
||||||
public synchronized void render() {
|
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() {
|
public void renderBoard() {
|
||||||
display(buildBoardContent());
|
display(buildBoardContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Renders players panel + board panel + menu + player hand. */
|
|
||||||
public void fullRender() {
|
|
||||||
display(buildFullContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Renders the totem-choice panel. */
|
/** Renders the totem-choice panel. */
|
||||||
public void renderTotems() {
|
public void renderTotems() {
|
||||||
display(buildTotemsContent());
|
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.
|
* 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.
|
||||||
* <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 error the error type.
|
||||||
* @param message the human-readable message to append.
|
* @param message the human-readable message to append.
|
||||||
@@ -149,24 +137,54 @@ public class TUI implements IView {
|
|||||||
|
|
||||||
// ── Content builders (return strings, do not print) ───────────────────────
|
// ── 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() {
|
private String buildBoardContent() {
|
||||||
List<String> left = List.of(BoardStamp().split("\n"));
|
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"));
|
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() {
|
private String buildTotemsContent() {
|
||||||
@@ -212,8 +230,6 @@ public class TUI implements IView {
|
|||||||
* {@link LineReader#printAbove} which pauses readline, prints the content,
|
* {@link LineReader#printAbove} which pauses readline, prints the content,
|
||||||
* and redraws the prompt — safe to call from any thread.
|
* and redraws the prompt — safe to call from any thread.
|
||||||
* Otherwise, ANSI codes are written directly to stdout.
|
* Otherwise, ANSI codes are written directly to stdout.
|
||||||
*
|
|
||||||
* @param content the text to display (board, error, etc.).
|
|
||||||
*/
|
*/
|
||||||
private void display(String content) {
|
private void display(String content) {
|
||||||
String cleared = "\033[H\033[2J" + center(content);
|
String cleared = "\033[H\033[2J" + center(content);
|
||||||
@@ -257,9 +273,9 @@ public class TUI implements IView {
|
|||||||
return sb.toString();
|
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) {
|
private int visibleLength(String line) {
|
||||||
return line.replaceAll("\033\\[[^m]*m", "").length();
|
return AsciiTable.displayWidth(line.replaceAll("\033\\[[^m]*m", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Board / players stamp helpers ─────────────────────────────────────────
|
// ── Board / players stamp helpers ─────────────────────────────────────────
|
||||||
@@ -291,7 +307,7 @@ public class TUI implements IView {
|
|||||||
* @return multi-line board string.
|
* @return multi-line board string.
|
||||||
*/
|
*/
|
||||||
public String BoardStamp() {
|
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> slotNames = new ArrayList<>();
|
||||||
List<String> slotPlayers = new ArrayList<>();
|
List<String> slotPlayers = new ArrayList<>();
|
||||||
|
|
||||||
@@ -301,11 +317,11 @@ public class TUI implements IView {
|
|||||||
slotPlayers.add(entry.getValue() != null ? entry.getValue().getUserName() : " ");
|
slotPlayers.add(entry.getValue() != null ? entry.getValue().getUserName() : " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
var TribeUpper = new AsciiTable(BorderStyle.UNICODE, 1);
|
var TribeUpper = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||||
var TribeLower = new AsciiTable(BorderStyle.UNICODE, 1);
|
var TribeLower = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||||
var BuildUpper = new AsciiTable(BorderStyle.UNICODE, 1);
|
var BuildUpper = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||||
BuildUpper.addHeader("Building ");
|
BuildUpper.addHeader("Building ");
|
||||||
var BuildLower = new AsciiTable(BorderStyle.UNICODE, 1);
|
var BuildLower = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||||
BuildLower.addHeader("Building ");
|
BuildLower.addHeader("Building ");
|
||||||
|
|
||||||
List<String> upperTribeRows = new ArrayList<>();
|
List<String> upperTribeRows = new ArrayList<>();
|
||||||
@@ -316,17 +332,17 @@ public class TUI implements IView {
|
|||||||
int max = Math.max(model.upperListTribeCards.size(), model.lowerListTribeCards.size());
|
int max = Math.max(model.upperListTribeCards.size(), model.lowerListTribeCards.size());
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
if (i < model.upperListTribeCards.size())
|
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())
|
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());
|
int maxB = Math.max(model.upperListBuildingCards.size(), model.lowerListBuildingCards.size());
|
||||||
for (int i = 0; i < maxB; i++) {
|
for (int i = 0; i < maxB; i++) {
|
||||||
if (i < model.upperListBuildingCards.size())
|
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())
|
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);
|
upperTribeRows.forEach(TribeUpper::addRow);
|
||||||
@@ -347,16 +363,17 @@ public class TUI implements IView {
|
|||||||
// ── Menu options ──────────────────────────────────────────────────────────
|
// ── Menu options ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private String printMenuOptions() {
|
private String printMenuOptions() {
|
||||||
var table = new AsciiTable(BorderStyle.ROUNDED, 2);
|
var table = new AsciiTable(BorderStyle.ROUNDED, 1);
|
||||||
table.addHeader("Commands", "Render");
|
table.addHeader("Commands");
|
||||||
table.addRow(List.of("slot <pos>", "render full"));
|
table.addRow("slot <pos>");
|
||||||
table.addRow(List.of("draw upper tribe <pos>", "render board"));
|
table.addRow("draw upper tribe <pos>");
|
||||||
table.addRow(List.of("draw upper building <pos>", "render players"));
|
table.addRow("draw upper building <pos>");
|
||||||
table.addRow(List.of("draw lower tribe <pos>", ""));
|
table.addRow("draw lower tribe <pos>");
|
||||||
table.addRow(List.of("draw lower building <pos>", ""));
|
table.addRow("draw lower building <pos>");
|
||||||
table.addRow(List.of("totem <pos>", ""));
|
table.addRow("totem <pos>");
|
||||||
table.addRow(List.of("skip", ""));
|
table.addRow("skip");
|
||||||
table.addRow(List.of("rematch | quit", ""));
|
table.addRow("render");
|
||||||
|
table.addRow("rematch | quit");
|
||||||
return table.build();
|
return table.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user