Fix: potential windows solution for TUI

This commit is contained in:
2026-06-10 19:08:39 +02:00
parent 0e28f8c6d8
commit 73153762c3
17 changed files with 90 additions and 116 deletions
+5
View File
@@ -68,6 +68,11 @@
<artifactId>jline-builtins</artifactId>
<version>3.26.3</version>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal-jansi</artifactId>
<version>3.26.3</version>
</dependency>
</dependencies>
<build>
@@ -15,6 +15,8 @@ import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import static org.jline.builtins.Completers.TreeCompleter.node;
@@ -35,7 +37,12 @@ public class ClientLauncherTUI {
*/
public void main() throws InterruptedException {
try {
terminal = TerminalBuilder.builder().system(true).build();
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
terminal = TerminalBuilder.builder()
.system(true)
.encoding(StandardCharsets.UTF_8)
.build();
} catch (IOException e) {
System.err.println("Could not initialise terminal: " + e.getMessage());
return;
@@ -149,6 +156,7 @@ public class ClientLauncherTUI {
node("totem"),
node("skip"),
node("render"),
node("help"),
node("rematch"),
node("quit")
);
@@ -180,6 +188,7 @@ public class ClientLauncherTUI {
}
case "skip" -> controller.skipTurn();
case "render" -> handleRender(parts);
case "help" -> view.renderHelp();
case "rematch" -> {
if (controller.miniModel == null
|| !controller.miniModel.currentState.getGameStage().equals(GameStages.ENDED)) {
@@ -274,6 +274,6 @@ public class BuildingCard extends PlayableCard implements Cloneable , BuildingEf
*/
@Override
public String toString() {
return ":" + " ID:" + String.valueOf(getEffectId())+ " 🍗:"+String.valueOf(getPrice())+" \uD83C\uDFC5:"+String.valueOf(getPrestigeValue());
return ":" + " ID:" + String.valueOf(getEffectId())+ " \uD83C\uDF56:"+String.valueOf(getPrice())+" \uD83C\uDFC5:"+String.valueOf(getPrestigeValue());
}
}
@@ -93,7 +93,7 @@ public class Inventor extends Character {
*/
@Override
public String toString() {
return super.toString() + " I_ID:" + String.valueOf(icon);
return super.toString() + "ID:" + String.valueOf(icon);
}
/**
@@ -86,7 +86,7 @@ public class Shaman extends Character {
*/
@Override
public String toString() {
return super.toString() + " " + "🌟".repeat(icon);
return super.toString() + "🌟:" + icon;
}
/**
@@ -102,7 +102,7 @@ public class Shaman extends Character {
*/
@Override
public String toStringBoard() {
return super.toStringBoard() + " " + "🌟".repeat(icon);
return super.toStringBoard() + "🌟:" + icon;
}
/**
@@ -89,7 +89,7 @@ public abstract class EventCard extends TribeCard {
*/
@Override
public String toStringBoard() {
return super.toString() + " " + type.toString();
return super.toStringBoard() + " " + type.toString();
}
/**
@@ -96,7 +96,7 @@ public class Hunt extends EventCard {
*/
@Override
public String toStringBoard() {
return super.toStringBoard()+" 1F+"+prestigeMultiplier+"PP"+" X N Hunter";
return super.toStringBoard()+" 1\uD83C\uDF56+"+prestigeMultiplier+"\uD83C\uDFC5"+" X N Hunter";
}
}
@@ -201,11 +201,9 @@ public class CurrentState implements Serializable {
}
@Override
public String toString(){
var table= new AsciiTable(BorderStyle.ROUNDED,6);
var table= new AsciiTable(BorderStyle.ROUNDED,4);
List<String>header= new ArrayList<>();
header.add("Player");
header.add("NUpper");
header.add("NLower");
header.add("Round");
header.add("Era");
header.add("GameStage");
@@ -213,8 +211,6 @@ public class CurrentState implements Serializable {
table.addSeparator();
List<String>values= new ArrayList<>();
values.add(player.getUserName());
values.add(Integer.toString(NUpper));
values.add(Integer.toString(NLower));
values.add(Integer.toString(round));
values.add(Integer.toString(Era));
values.add(GameStage.toString());
@@ -72,9 +72,9 @@ public class Order2 extends OrderLogicCard {
{
try {
if(playerList.get(i).played)
stringUp.add(" ");
stringUp.add("");
else
stringUp.add(i+". "+playerList.get(i).player.getUserName());
stringUp.add(playerList.get(i).player.getUserName());
}
catch (IndexOutOfBoundsException e) {
stringUp.add("");
@@ -83,9 +83,9 @@ public class Order2 extends OrderLogicCard {
table.addRow(stringUp);
List<String> stringList=new ArrayList<>();
stringList.add("+1🍗");
stringList.add("-1🍗 / -2\uD83C\uDFC5");
stringList.add("+1\uD83C\uDF56");
stringList.add("-1\uD83C\uDF56/-2\uD83C\uDFC5");
table.addRow(stringList);
return "TURN ORDER\n" + table.build() + "\n";
return table.build() + "\n";
}
}
@@ -75,9 +75,9 @@ public class Order3 extends OrderLogicCard {
{
try {
if(playerList.get(i).played)
stringUp.add(" ");
stringUp.add("");
else
stringUp.add(i+". "+playerList.get(i).player.getUserName());
stringUp.add(playerList.get(i).player.getUserName());
}
catch (IndexOutOfBoundsException e) {
stringUp.add("");
@@ -87,12 +87,12 @@ public class Order3 extends OrderLogicCard {
List<String> stringList=new ArrayList<>();
stringList.add("+2 🍗");
stringList.add("+2\uD83C\uDF56");
stringList.add("--");
stringList.add("-1 🍗 / -2 ⭐");
stringList.add("-1\uD83C\uDF56/-2\uD83C\uDFC5");
table.addRow(stringList);
return "TURN ORDER\n" + table.build() + "\n";
return table.build() + "\n";
}
}
@@ -81,9 +81,9 @@ public class Order4 extends OrderLogicCard {
{
try {
if(playerList.get(i).played)
stringUp.add(" ");
stringUp.add("");
else
stringUp.add(i+". "+playerList.get(i).player.getUserName());
stringUp.add(playerList.get(i).player.getUserName());
}
catch (IndexOutOfBoundsException e) {
stringUp.add("");
@@ -92,11 +92,11 @@ public class Order4 extends OrderLogicCard {
table.addRow(stringUp);
List<String> stringList=new ArrayList<>();
stringList.add("+2 🍗");
stringList.add("+1 🍗");
stringList.add("+2\uD83C\uDF56");
stringList.add("+1\uD83C\uDF56");
stringList.add("--");
stringList.add("-1 🍗 / -2 ⭐");
stringList.add("-1\uD83C\uDF56/-2\uD83C\uDFC5");
table.addRow(stringList);
return "TURN ORDER\n" + table.build() + "\n";
return table.build() + "\n";
}
}
@@ -82,9 +82,9 @@ public class Order5 extends OrderLogicCard {
{
try {
if(playerList.get(i).played)
stringUp.add(" ");
stringUp.add("");
else
stringUp.add(i+". "+playerList.get(i).player.getUserName());
stringUp.add(playerList.get(i).player.getUserName());
}
catch (IndexOutOfBoundsException e) {
stringUp.add("");
@@ -93,12 +93,12 @@ public class Order5 extends OrderLogicCard {
table.addRow(stringUp);
List<String> stringList=new ArrayList<>();
stringList.add("+3 🍗");
stringList.add("+1 🍗");
stringList.add("+3\uD83C\uDF56");
stringList.add("+1\uD83C\uDF56");
stringList.add("--");
stringList.add("--");
stringList.add("-1 🍗 / -2 ⭐");
stringList.add("-1\uD83C\uDF56/-2\uD83C\uDFC5");
table.addRow(stringList);
return "TURN ORDER\n" + table.build() + "\n";
return table.build() + "\n";
}
}
@@ -74,7 +74,7 @@ public abstract class PlayableCard implements Serializable {
*/
@Override
public String toString() {
return ":";
return "";
}
/**
@@ -273,13 +273,8 @@ public class Player implements Serializable {
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("🍗 " + this.getFoodValue());
table.addRow("2\uD83C\uDFC5 " + this.getPrestigeValue());
String totemStr = (this.totem != null) ? " (" + this.totem.toString() + ")" : "";
table.addHeader(this.getUserName() + totemStr + " | \uD83C\uDF56:" + this.getFoodValue() + " | \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()+" 🍗");
s.append("+"+getFood()+" \uD83C\uDF56");
return s.toString();
}
@@ -14,7 +14,7 @@ import java.util.*;
* <p>Example usage:
* <pre>{@code
* AsciiTable table = new AsciiTable(BorderStyle.UNICODE, 3);
* table.addHeader("Name", "🍗", "");
* table.addHeader("Name", "🍖", "");
* table.addRow("Alice", "4", "12");
* table.addRow("Bob", "2", "8");
* System.out.println(table.build());
@@ -122,7 +122,7 @@ public class AsciiTable {
/**
* 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).
* the specific emoji used in this project: 🍖 U+1F357, U+2B50).
*/
private static boolean isWide(int cp) {
if (cp < 0x1100) return false;
@@ -151,7 +151,7 @@ public class AsciiTable {
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, ...)
// misc symbols, transport, 🍖 U+1F357, ...)
if (cp < 0x20000) return false;
return cp <= 0x3FFFD; // CJK Extension B through G
}
@@ -9,7 +9,6 @@ import org.jline.reader.LineReader;
import org.jline.terminal.Terminal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -143,8 +142,8 @@ public class TUI implements IView {
*/
private String buildBoardContent() {
List<String> left = List.of(BoardStamp().split("\n"));
List<String> right = List.of(printMenuOptions().split("\n"));
return AsciiTable.sideBySide(left, right, 3) + "\n" + buildAllHandsContent();
List<String> right = List.of(buildAllHandsContent().split("\n"));
return AsciiTable.sideBySide(left, right, 3);
}
/**
@@ -154,35 +153,9 @@ public class TUI implements IView {
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;
for (Player p : players) {
result.append(p.toString()).append("\n");
}
return result.toString();
}
@@ -317,62 +290,58 @@ public class TUI implements IView {
slotPlayers.add(entry.getValue() != null ? entry.getValue().getUserName() : " ");
}
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.ROUNDED, 1);
BuildLower.addHeader("Building ");
var UpperCards = new AsciiTable(BorderStyle.ROUNDED, 2);
var LowerCards = new AsciiTable(BorderStyle.ROUNDED, 2);
UpperCards.addHeader("Char/Events", "Building");
LowerCards.addHeader("Char/Events", "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 maxU = Math.max(model.upperListTribeCards.size(), model.upperListBuildingCards.size());
for (int i = 0; i < maxU; i++) {
String t = i < model.upperListTribeCards.size() ? i + model.upperListTribeCards.get(i).toStringBoard() : "";
String b = i < model.upperListBuildingCards.size() ? i + model.upperListBuildingCards.get(i).toString() : "";
UpperCards.addRow(t, b);
}
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());
int maxL = Math.max(model.lowerListTribeCards.size(), model.lowerListBuildingCards.size());
for (int i = 0; i < maxL; i++) {
String t = i < model.lowerListTribeCards.size() ? i + model.lowerListTribeCards.get(i).toStringBoard() : "";
String b = i < model.lowerListBuildingCards.size() ? i + model.lowerListBuildingCards.get(i).toString() : "";
LowerCards.addRow(t, b);
}
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);
String upperSection = UpperCards.build();
String middleSection = AsciiTable.sideBySide(
List.of(model.orderLogicCard.toString().split("\n")),
List.of(offerTrack.build().split("\n")), 3);
String lowerSection = LowerCards.build();
return model.currentState + "\n"
+ upperSection + "\n"
+ middleSection + "\n"
+ lowerSection;
}
// Menu options
// Help
private String printMenuOptions() {
/** Displays the command help panel. */
public void renderHelp() {
display(buildHelpContent());
}
private String buildHelpContent() {
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("draw upper|lower tribe|building <pos>");
table.addRow("totem <pos>");
table.addRow("skip");
table.addRow("render");
table.addRow("help");
table.addRow("rematch | quit");
return table.build();
}