FIx: Fixed Coverage Screenshots And htmlRepot.
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html id="htmlId">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title>Coverage Report > AsciiTable</title>
|
||||
<style type="text/css">
|
||||
@import "../../css/coverage.css";
|
||||
@import "../../css/idea.min.css";
|
||||
</style>
|
||||
<script type="text/javascript" src="../../js/highlight.min.js"></script>
|
||||
<script type="text/javascript" src="../../js/highlightjs-line-numbers.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="breadCrumbs">
|
||||
Current scope: <a href="../../index.html">all classes</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="../index.html">it.polimi.ingsw.gc14.View.TUI</a>
|
||||
</div>
|
||||
|
||||
<h1>Coverage Summary for Class: AsciiTable (it.polimi.ingsw.gc14.View.TUI)</h1>
|
||||
|
||||
<table class="coverageStats">
|
||||
<tr>
|
||||
<th class="name">Class</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Class, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Method, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Branch, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Line, %
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="name">AsciiTable</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
100%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(1/1)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
90.9%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(10/11)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
52.6%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(40/76)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
82.2%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(60/73)
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
<pre>
|
||||
<code class="sourceCode" id="sourceCode"> package it.polimi.ingsw.gc14.View.TUI;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* General-purpose helper for building fixed-width ASCII/Unicode tables in a TUI.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <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", "🍖", "⭐");
|
||||
* table.addRow("Alice", "4", "12");
|
||||
* table.addRow("Bob", "2", "8");
|
||||
* System.out.println(table.build());
|
||||
* }</pre>
|
||||
*/
|
||||
public class AsciiTable {
|
||||
/** Border style used to draw corners, lines, and junctions. */
|
||||
private final BorderStyle s;
|
||||
|
||||
/** Number of columns in this table. */
|
||||
private final int cols;
|
||||
|
||||
/** All rows of the table, in insertion order. */
|
||||
<b class="fc"> private final List<List<String>> rows = new ArrayList<>();</b>
|
||||
|
||||
/** Indices of rows after which a horizontal separator line is drawn. */
|
||||
<b class="fc"> private final List<Integer> separators = new ArrayList<>();</b>
|
||||
|
||||
/**
|
||||
* Creates a new empty table with the given border style and column count.
|
||||
*
|
||||
* @param s the border style to use when rendering.
|
||||
* @param cols the number of columns.
|
||||
*/
|
||||
<b class="fc"> public AsciiTable(BorderStyle s, int cols) {</b>
|
||||
<b class="fc"> this.s = s; this.cols = cols;</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a row using varargs cells.
|
||||
*
|
||||
* @param cells one value per column.
|
||||
*/
|
||||
<b class="fc"> public void addRow(String... cells) { rows.add(Arrays.asList(cells)); }</b>
|
||||
|
||||
/**
|
||||
* Appends a row from an existing list.
|
||||
*
|
||||
* @param cells one value per column.
|
||||
*/
|
||||
<b class="fc"> public void addRow(List<String> cells) { rows.add(cells); }</b>
|
||||
|
||||
/**
|
||||
* Inserts a header row at position 0 and marks it with a separator line below it.
|
||||
*
|
||||
* @param cells one header label per column.
|
||||
*/
|
||||
public void addHeader(String... cells) {
|
||||
<b class="fc"> rows.add(0, Arrays.asList(cells));</b>
|
||||
<b class="fc"> separators.add(0);</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a separator line to be drawn after the last row added so far.
|
||||
*/
|
||||
<b class="fc"> public void addSeparator() { separators.add(rows.size() - 1); }</b>
|
||||
|
||||
/**
|
||||
* Renders the table to a multi-line string.
|
||||
* Column width is the widest cell in display columns (wide chars = 2), plus 1.
|
||||
*
|
||||
* @return the fully rendered table as a multi-line string.
|
||||
*/
|
||||
public String build() {
|
||||
<b class="fc"> var sb = new StringBuilder();</b>
|
||||
<b class="fc"> int maxWidth = rows.stream()</b>
|
||||
<b class="fc"> .mapToInt(x -> x.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0))</b>
|
||||
<b class="fc"> .max().orElse(0) + 1;</b>
|
||||
<b class="fc"> sb.append(hline(s.tl(), s.mt(), s.tr(), maxWidth)).append('\n');</b>
|
||||
|
||||
<b class="fc"> for (int i = 0; i < rows.size(); i++) {</b>
|
||||
<b class="fc"> sb.append(s.v());</b>
|
||||
<b class="fc"> for (String cell : rows.get(i))</b>
|
||||
<b class="fc"> sb.append(rpad(" " + cell, maxWidth)).append(s.v());</b>
|
||||
<b class="fc"> sb.append('\n');</b>
|
||||
<b class="pc"> if (separators.contains(i) && i < rows.size() - 1)</b>
|
||||
<b class="fc"> sb.append(hline(s.sl(), s.sx(), s.sr(), maxWidth)).append('\n');</b>
|
||||
}
|
||||
<b class="fc"> sb.append(hline(s.bl(), s.mb(), s.br(), maxWidth));</b>
|
||||
<b class="fc"> return sb.toString();</b>
|
||||
}
|
||||
/**
|
||||
* Builds a single horizontal borderline across all columns.
|
||||
*
|
||||
* @param l left-end character.
|
||||
* @param m middle junction character (between columns).
|
||||
* @param r right-end character.
|
||||
* @param maxWidth width in display columns of each cell (including padding).
|
||||
* @return the rendered horizontal line string.
|
||||
*/
|
||||
private String hline(String l, String m, String r, int maxWidth) {
|
||||
<b class="fc"> var sb = new StringBuilder(l);</b>
|
||||
<b class="fc"> for (int i = 0; i < cols; i++) {</b>
|
||||
<b class="fc"> sb.repeat(Objects.requireNonNull(s.h()), maxWidth);</b>
|
||||
<b class="fc"> if (i < cols - 1) sb.append(m);</b>
|
||||
}
|
||||
<b class="fc"> return sb.append(r).toString();</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
<b class="fc"> int dw = displayWidth(s);</b>
|
||||
<b class="fc"> if (dw >= w) return s;</b>
|
||||
<b class="fc"> return s + " ".repeat(w - dw);</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* always starts at the same column. Uses {@link #displayWidth} for measurement.
|
||||
*
|
||||
* @param left lines of the left block.
|
||||
* @param right lines of the right block.
|
||||
* @param gap number of space characters between the two blocks.
|
||||
* @return the combined multi-line string.
|
||||
*/
|
||||
public static String sideBySide(List<String> left, List<String> right, int gap) {
|
||||
<b class="nc"> int leftWidth = left.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0);</b>
|
||||
<b class="nc"> int maxHeight = Math.max(left.size(), right.size());</b>
|
||||
<b class="nc"> String padding = " ".repeat(gap);</b>
|
||||
|
||||
<b class="nc"> var sb = new StringBuilder();</b>
|
||||
<b class="nc"> for (int i = 0; i < maxHeight; i++) {</b>
|
||||
<b class="nc"> String l = i < left.size() ? left.get(i) : " ".repeat(leftWidth);</b>
|
||||
<b class="nc"> l = rpad(l, leftWidth);</b>
|
||||
<b class="nc"> String r = i < right.size() ? right.get(i) : "";</b>
|
||||
<b class="nc"> sb.append(l).append(padding).append(r).append('\n');</b>
|
||||
}
|
||||
<b class="nc"> return sb.toString();</b>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of terminal columns required to display {@code s}.
|
||||
* Wide characters (emoji, CJK, full-width) count as 2; all others as 1.
|
||||
*
|
||||
* @param s the string to measure; ANSI escape sequences are stripped before counting.
|
||||
* @return the display width in terminal columns.
|
||||
*/
|
||||
public static int displayWidth(String s) {
|
||||
<b class="fc"> s = s.replaceAll("\033\\[[^m]*m", "");</b>
|
||||
<b class="fc"> int w = 0;</b>
|
||||
<b class="fc"> for (int i = 0; i < s.length(); ) {</b>
|
||||
<b class="fc"> int cp = s.codePointAt(i);</b>
|
||||
<b class="fc"> w += isWide(cp) ? 2 : 1;</b>
|
||||
<b class="fc"> i += Character.charCount(cp);</b>
|
||||
}
|
||||
<b class="fc"> return w;</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
<b class="fc"> if (cp < 0x1100) return false;</b>
|
||||
<b class="pc"> if (cp <= 0x115F) return true;</b>
|
||||
<b class="pc"> if (cp < 0x2E80) {</b>
|
||||
<b class="nc"> return cp == 0x2B50</b>
|
||||
|| cp == 0x2B55;
|
||||
}
|
||||
<b class="pc"> if (cp <= 0x303E) return true;</b>
|
||||
<b class="pc"> if (cp < 0x3041) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xA4CF) return true;</b>
|
||||
<b class="pc"> if (cp < 0xA960) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xA97F) return true;</b>
|
||||
<b class="pc"> if (cp < 0xAC00) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xD7AF) return true;</b>
|
||||
<b class="pc"> if (cp < 0xF900) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xFAFF) return true;</b>
|
||||
<b class="pc"> if (cp < 0xFE10) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xFE1F) return true;</b>
|
||||
<b class="pc"> if (cp < 0xFE30) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xFE6F) return true;</b>
|
||||
<b class="pc"> if (cp < 0xFF00) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xFF60) return true;</b>
|
||||
<b class="pc"> if (cp < 0xFFE0) return false;</b>
|
||||
<b class="pc"> if (cp <= 0xFFE6) return true;</b>
|
||||
<b class="pc"> if (cp < 0x1F004) return false;</b>
|
||||
<b class="pc"> if (cp <= 0x1FAFF) return true;</b>
|
||||
|
||||
<b class="nc"> if (cp < 0x20000) return false;</b>
|
||||
<b class="nc"> return cp <= 0x3FFFD;</b>
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-19 22:53</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,192 @@
|
||||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html id="htmlId">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title>Coverage Report > BorderStyle</title>
|
||||
<style type="text/css">
|
||||
@import "../../css/coverage.css";
|
||||
@import "../../css/idea.min.css";
|
||||
</style>
|
||||
<script type="text/javascript" src="../../js/highlight.min.js"></script>
|
||||
<script type="text/javascript" src="../../js/highlightjs-line-numbers.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="breadCrumbs">
|
||||
Current scope: <a href="../../index.html">all classes</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="../index.html">it.polimi.ingsw.gc14.View.TUI</a>
|
||||
</div>
|
||||
|
||||
<h1>Coverage Summary for Class: BorderStyle (it.polimi.ingsw.gc14.View.TUI)</h1>
|
||||
|
||||
<table class="coverageStats">
|
||||
<tr>
|
||||
<th class="name">Class</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Class, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Method, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Line, %
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="name">BorderStyle</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
100%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(1/1)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
100%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(14/14)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
100%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(22/22)
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
<pre>
|
||||
<code class="sourceCode" id="sourceCode"> package it.polimi.ingsw.gc14.View.TUI;
|
||||
|
||||
/**
|
||||
* Defines the available border styles used to render {@link AsciiTable}
|
||||
* instances in the text-based user interface.
|
||||
*
|
||||
* <p>Each style stores the characters needed to draw table corners,
|
||||
* horizontal and vertical lines, and junctions.
|
||||
*/
|
||||
<b class="fc"> @SuppressWarnings("ALL")</b>
|
||||
public enum BorderStyle {
|
||||
|
||||
/**
|
||||
* Rounded Unicode border style.
|
||||
*/
|
||||
<b class="fc"> ROUNDED("╭","╮","╰","╯","─","│","├","┤","┬","┴","┼","├","┤","┼");</b>
|
||||
/** Corner (tl/tr/bl/br), line (h/v), outer junction (ml/mr/mt/mb/x), and separator junction (sl/sr/sx) characters. */
|
||||
private final String tl,tr,bl,br,h,v,ml,mr,mt,mb,x,sl,sr,sx;
|
||||
|
||||
BorderStyle(String tl,String tr,String bl,String br,
|
||||
String h, String v, String ml,String mr,
|
||||
String mt,String mb,String x,
|
||||
<b class="fc"> String sl,String sr,String sx) {</b>
|
||||
<b class="fc"> this.tl = tl; this.tr = tr;</b>
|
||||
<b class="fc"> this.bl = bl; this.br = br;</b>
|
||||
<b class="fc"> this.h = h; this.v = v;</b>
|
||||
<b class="fc"> this.ml = ml; this.mr = mr;</b>
|
||||
<b class="fc"> this.mt = mt; this.mb = mb;</b>
|
||||
<b class="fc"> this.x = x;</b>
|
||||
<b class="fc"> this.sl = sl; this.sr = sr;</b>
|
||||
<b class="fc"> this.sx = sx;</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the top-left corner character.
|
||||
*/
|
||||
<b class="fc"> public String tl() { return tl; }</b>
|
||||
|
||||
/**
|
||||
* @return the top-right corner character.
|
||||
*/
|
||||
<b class="fc"> public String tr() { return tr; }</b>
|
||||
|
||||
/**
|
||||
* @return the bottom-left corner character.
|
||||
*/
|
||||
<b class="fc"> public String bl() { return bl; }</b>
|
||||
|
||||
/**
|
||||
* @return the bottom-right corner character.
|
||||
*/
|
||||
<b class="fc"> public String br() { return br; }</b>
|
||||
|
||||
/**
|
||||
* @return the horizontal line character.
|
||||
*/
|
||||
<b class="fc"> public String h() { return h; }</b>
|
||||
|
||||
/**
|
||||
* @return the vertical line character.
|
||||
*/
|
||||
<b class="fc"> public String v() { return v; }</b>
|
||||
|
||||
/**
|
||||
* @return the top-middle junction character.
|
||||
*/
|
||||
<b class="fc"> public String mt() { return mt; }</b>
|
||||
|
||||
/**
|
||||
* @return the bottom-middle junction character.
|
||||
*/
|
||||
<b class="fc"> public String mb() { return mb; }</b>
|
||||
|
||||
/**
|
||||
* @return the separator-left junction character.
|
||||
*/
|
||||
<b class="fc"> public String sl() { return sl; }</b>
|
||||
|
||||
/**
|
||||
* @return the separator-right junction character.
|
||||
*/
|
||||
<b class="fc"> public String sr() { return sr; }</b>
|
||||
|
||||
/**
|
||||
* @return the separator center junction character.
|
||||
*/
|
||||
<b class="fc"> public String sx() { return sx; }</b>
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-19 22:53</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,445 @@
|
||||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html id="htmlId">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title>Coverage Report > TUI</title>
|
||||
<style type="text/css">
|
||||
@import "../../css/coverage.css";
|
||||
@import "../../css/idea.min.css";
|
||||
</style>
|
||||
<script type="text/javascript" src="../../js/highlight.min.js"></script>
|
||||
<script type="text/javascript" src="../../js/highlightjs-line-numbers.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="breadCrumbs">
|
||||
Current scope: <a href="../../index.html">all classes</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="../index.html">it.polimi.ingsw.gc14.View.TUI</a>
|
||||
</div>
|
||||
|
||||
<h1>Coverage Summary for Class: TUI (it.polimi.ingsw.gc14.View.TUI)</h1>
|
||||
|
||||
<table class="coverageStats">
|
||||
<tr>
|
||||
<th class="name">Class</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Class, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Method, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Branch, %
|
||||
</th>
|
||||
<th class="coverageStat
|
||||
">
|
||||
Line, %
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="name">TUI</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
0%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(0/1)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
0%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(0/16)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
0%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(0/74)
|
||||
</span>
|
||||
</td>
|
||||
<td class="coverageStat">
|
||||
<span class="percent">
|
||||
0%
|
||||
</span>
|
||||
<span class="absValue">
|
||||
(0/131)
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
<pre>
|
||||
<code class="sourceCode" id="sourceCode"> package it.polimi.ingsw.gc14.View.TUI;
|
||||
import it.polimi.ingsw.gc14.ErrorType;
|
||||
import it.polimi.ingsw.gc14.Model.*;
|
||||
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
||||
import it.polimi.ingsw.gc14.View.IView;
|
||||
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.terminal.Terminal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Text-based User Interface (TUI) implementation of {@link IView}.
|
||||
*
|
||||
* <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
|
||||
* readline prompt is correctly redrawn after background-thread renders.
|
||||
*/
|
||||
public class TUI implements IView {
|
||||
|
||||
private MiniModel model;
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* JLine reader — when non-null, all output uses {@code printAbove} so the
|
||||
* readline prompt is preserved after background-thread renders.
|
||||
*/
|
||||
private LineReader lineReader;
|
||||
|
||||
/** JLine terminal — used to query terminal width for centering and grid layout. */
|
||||
private Terminal terminal;
|
||||
|
||||
/**
|
||||
* Constructs a {@code TUI} bound to the given model.
|
||||
*
|
||||
* @param model the model to display; may be {@code null} initially.
|
||||
*/
|
||||
<b class="nc"> public TUI(MiniModel model) {</b>
|
||||
<b class="nc"> this.model = model;</b>
|
||||
<b class="nc"> this.username = "";</b>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the username of the local player.
|
||||
*
|
||||
* @param username the username to assign.
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
<b class="nc"> this.username = username;</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the model stored in this view.
|
||||
*
|
||||
* @param model the latest mini model to display.
|
||||
*/
|
||||
@Override
|
||||
public void setModel(MiniModel model) {
|
||||
<b class="nc"> this.model = model;</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the active JLine {@link LineReader}.
|
||||
* Once set, all output routes through {@link LineReader#printAbove} so the
|
||||
* prompt survives background-thread renders.
|
||||
*
|
||||
* @param lineReader the JLine reader to use for output; also provides the terminal reference.
|
||||
*/
|
||||
public void setLineReader(LineReader lineReader) {
|
||||
<b class="nc"> this.lineReader = lineReader;</b>
|
||||
<b class="nc"> this.terminal = lineReader.getTerminal();</b>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default render: dispatches to the right view based on game stage.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void render() {
|
||||
<b class="nc"> if (model == null) return;</b>
|
||||
<b class="nc"> GameStages stage = model.currentState.getGameStage();</b>
|
||||
<b class="nc"> if (stage == GameStages.TOTEM_CHOICE) {</b>
|
||||
<b class="nc"> display(buildTotemsContent());</b>
|
||||
<b class="nc"> printLine("\033[2mCommands: totem <pos>\033[0m");</b>
|
||||
<b class="nc"> } else if (stage == GameStages.ENDED) {</b>
|
||||
<b class="nc"> display(buildStandingContent());</b>
|
||||
<b class="nc"> printLine("Type 'rematch' to play again or 'quit' to exit");</b>
|
||||
} else {
|
||||
<b class="nc"> display(buildBoardContent());</b>
|
||||
<b class="nc"> printLine("\033[2mCommands: slot <pos> | draw upper/lower tribe/building <pos> | totem <pos> | skip | clear | details buildings/events/characters | quit\033[0m");</b>
|
||||
}
|
||||
}
|
||||
|
||||
/** Renders the board + menu (top) and all players' hands in a grid (bottom). */
|
||||
public void renderBoard() {
|
||||
<b class="nc"> display(buildBoardContent());</b>
|
||||
<b class="nc"> printLine("\033[2mCommands: slot <pos> | draw upper/lower tribe/building <pos> | totem <pos> | skip | clear | details buildings/events/characters | quit\033[0m");</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void showError(ErrorType error, String message) {
|
||||
String text;
|
||||
<b class="nc"> if (error == ErrorType.WRONG_ACTION</b>
|
||||
&& model != null
|
||||
<b class="nc"> && model.currentState.getCurrentPlayer() != null</b>
|
||||
<b class="nc"> && !model.currentState.getCurrentPlayer().getUserName().equals(username)) {</b>
|
||||
<b class="nc"> text = "It's not your turn";</b>
|
||||
} else {
|
||||
<b class="nc"> text = message;</b>
|
||||
<b class="nc"> if (error == ErrorType.SERVER_CRASHED) {</b>
|
||||
<b class="nc"> text += "\nPress any key to continue";</b>
|
||||
}
|
||||
}
|
||||
<b class="nc"> render();</b>
|
||||
<b class="nc"> printLine("\033[31m" + text + "\033[0m");</b>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the main view: board + menu side by side, followed by all players'
|
||||
* hands arranged in a responsive grid.
|
||||
*/
|
||||
private String buildBoardContent() {
|
||||
<b class="nc"> List<String> left = List.of(boardStamp().split("\n"));</b>
|
||||
<b class="nc"> List<String> right = List.of(buildAllHandsContent().split("\n"));</b>
|
||||
<b class="nc"> return AsciiTable.sideBySide(left, right, 3);</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
<b class="nc"> List<Player> players = new ArrayList<>(model.players.values());</b>
|
||||
<b class="nc"> players.removeIf(p -> model.disconnectedPlayers.contains(p.getUserName()));</b>
|
||||
<b class="nc"> if (players.isEmpty()) return "";</b>
|
||||
|
||||
<b class="nc"> int n = players.size();</b>
|
||||
<b class="nc"> int leftCount = Math.min(3, n);</b>
|
||||
|
||||
<b class="nc"> StringBuilder leftSb = new StringBuilder();</b>
|
||||
<b class="nc"> StringBuilder rightSb = new StringBuilder();</b>
|
||||
<b class="nc"> for (int i = 0; i < leftCount; i++) leftSb.append(players.get(i).toString()).append("\n");</b>
|
||||
<b class="nc"> for (int i = leftCount; i < n; i++) rightSb.append(players.get(i).toString()).append("\n");</b>
|
||||
|
||||
String handsBlock;
|
||||
<b class="nc"> if (rightSb.isEmpty()) {</b>
|
||||
<b class="nc"> handsBlock = leftSb.toString();</b>
|
||||
} else {
|
||||
<b class="nc"> handsBlock = AsciiTable.sideBySide(</b>
|
||||
<b class="nc"> List.of(leftSb.toString().split("\n", -1)),</b>
|
||||
<b class="nc"> List.of(rightSb.toString().split("\n", -1)), 3);</b>
|
||||
}
|
||||
|
||||
<b class="nc"> if (!model.disconnectedPlayers.isEmpty()) {</b>
|
||||
<b class="nc"> var box = new AsciiTable(BorderStyle.ROUNDED, 1);</b>
|
||||
<b class="nc"> box.addHeader("Disconnected");</b>
|
||||
<b class="nc"> model.disconnectedPlayers.forEach(box::addRow);</b>
|
||||
<b class="nc"> handsBlock += "\n" + box.build();</b>
|
||||
}
|
||||
|
||||
<b class="nc"> return handsBlock;</b>
|
||||
}
|
||||
|
||||
private String buildTotemsContent() {
|
||||
<b class="nc"> var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size());</b>
|
||||
<b class="nc"> List<String> lines = new ArrayList<>();</b>
|
||||
<b class="nc"> for (int i = 0; i < model.availableTotems.size(); i++) {</b>
|
||||
<b class="nc"> lines.add(i + "." + model.availableTotems.get(i));</b>
|
||||
}
|
||||
<b class="nc"> table.addRow(lines);</b>
|
||||
<b class="nc"> String current = model.currentState.getCurrentPlayer() != null</b>
|
||||
<b class="nc"> ? model.currentState.getCurrentPlayer().getUserName() + " — "</b>
|
||||
<b class="nc"> : "";</b>
|
||||
<b class="nc"> return current + "TOTEMS AVAILABLE:\n" + table.build();</b>
|
||||
}
|
||||
|
||||
private String buildStandingContent() {
|
||||
<b class="nc"> if (model.standingPlayers == null || model.standingPlayers.isEmpty()) return "";</b>
|
||||
<b class="nc"> String banner = model.standingPlayers.get(0).getUserName().equals(username)</b>
|
||||
<b class="nc"> ? "WINNER!" : "GAME OVER";</b>
|
||||
<b class="nc"> List<String> lines = new ArrayList<>();</b>
|
||||
<b class="nc"> for (int i = 0; i < model.standingPlayers.size(); i++) {</b>
|
||||
<b class="nc"> var p = model.standingPlayers.get(i);</b>
|
||||
<b class="nc"> lines.add((i + 1) + ". " + p.getUserName()</b>
|
||||
<b class="nc"> + " (" + p.getTotem().toString() + ")"</b>
|
||||
<b class="nc"> + " \uD83C\uDF56:" + p.getFoodValue()</b>
|
||||
<b class="nc"> + " \uD83C\uDFC5:" + p.getPrestigeValue());</b>
|
||||
}
|
||||
<b class="nc"> int contentWidth = lines.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0);</b>
|
||||
<b class="nc"> int pad = Math.max(0, (contentWidth - AsciiTable.displayWidth(banner)) / 2);</b>
|
||||
<b class="nc"> var table = new AsciiTable(BorderStyle.ROUNDED, 1);</b>
|
||||
<b class="nc"> table.addHeader(" ".repeat(pad) + banner);</b>
|
||||
<b class="nc"> lines.forEach(table::addRow);</b>
|
||||
<b class="nc"> return table.build();</b>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the terminal and displays {@code content}, horizontally centered.
|
||||
*
|
||||
* <p>When a JLine {@link LineReader} is registered, output is routed through
|
||||
* {@link LineReader#printAbove} which pauses readline, prints the content,
|
||||
* and redraws the prompt — safe to call from any thread.
|
||||
* Otherwise, ANSI codes are written directly to stdout.
|
||||
*/
|
||||
private void display(String content) {
|
||||
<b class="nc"> String cleared = "\033[H\033[2J" + center(content);</b>
|
||||
<b class="nc"> if (lineReader != null) {</b>
|
||||
<b class="nc"> lineReader.printAbove(cleared);</b>
|
||||
} else {
|
||||
<b class="nc"> System.out.print(cleared);</b>
|
||||
<b class="nc"> System.out.println();</b>
|
||||
<b class="nc"> System.out.flush();</b>
|
||||
}
|
||||
}
|
||||
|
||||
/** Prints {@code text} above the prompt without clearing the screen. */
|
||||
private void printLine(String text) {
|
||||
<b class="nc"> if (lineReader != null) {</b>
|
||||
<b class="nc"> lineReader.printAbove(text);</b>
|
||||
} else {
|
||||
<b class="nc"> System.out.println(text);</b>
|
||||
<b class="nc"> System.out.flush();</b>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontally centers each line of {@code content} within the terminal width.
|
||||
* Empty lines are left unpadded. Falls back to the original string when the
|
||||
* terminal width is unknown.
|
||||
*/
|
||||
private String center(String content) {
|
||||
<b class="nc"> if (terminal == null) return content;</b>
|
||||
<b class="nc"> int termWidth = terminal.getWidth();</b>
|
||||
<b class="nc"> if (termWidth <= 0) return content;</b>
|
||||
|
||||
<b class="nc"> String[] lines = content.split("\n", -1);</b>
|
||||
|
||||
<b class="nc"> int maxWidth = 0;</b>
|
||||
<b class="nc"> for (String line : lines) {</b>
|
||||
<b class="nc"> int w = visibleLength(line);</b>
|
||||
<b class="nc"> if (w > maxWidth) maxWidth = w;</b>
|
||||
}
|
||||
|
||||
<b class="nc"> int pad = Math.max(0, (termWidth - maxWidth) / 2);</b>
|
||||
<b class="nc"> if (pad == 0) return content;</b>
|
||||
|
||||
<b class="nc"> String prefix = " ".repeat(pad);</b>
|
||||
<b class="nc"> StringBuilder sb = new StringBuilder();</b>
|
||||
<b class="nc"> for (int i = 0; i < lines.length; i++) {</b>
|
||||
<b class="nc"> if (!lines[i].isEmpty()) sb.append(prefix);</b>
|
||||
<b class="nc"> sb.append(lines[i]);</b>
|
||||
<b class="nc"> if (i < lines.length - 1) sb.append("\n");</b>
|
||||
}
|
||||
<b class="nc"> return sb.toString();</b>
|
||||
}
|
||||
|
||||
/** Returns the display width of a string in terminal columns, stripping ANSI escape codes. */
|
||||
private int visibleLength(String line) {
|
||||
<b class="nc"> return AsciiTable.displayWidth(line);</b>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the board state as a multi-line string: turn order, upper cards,
|
||||
* offer track, lower cards.
|
||||
*
|
||||
* @return multi-line board string.
|
||||
*/
|
||||
public String boardStamp() {
|
||||
<b class="nc"> var offerTrack = new AsciiTable(BorderStyle.ROUNDED, model.slotPlayerMap.size());</b>
|
||||
<b class="nc"> List<String> slotNames = new ArrayList<>();</b>
|
||||
<b class="nc"> List<String> slotPlayers = new ArrayList<>();</b>
|
||||
|
||||
<b class="nc"> int index = 0;</b>
|
||||
<b class="nc"> for (Map.Entry<Slot, Player> entry : model.slotPlayerMap.entrySet()) {</b>
|
||||
<b class="nc"> slotNames.add((index++) + "." + entry.getKey().toStringTUI());</b>
|
||||
<b class="nc"> slotPlayers.add(entry.getValue() != null ? entry.getValue().getUserName() : " ");</b>
|
||||
}
|
||||
|
||||
<b class="nc"> var upperCards = new AsciiTable(BorderStyle.ROUNDED, 2);</b>
|
||||
<b class="nc"> var lowerCards = new AsciiTable(BorderStyle.ROUNDED, 2);</b>
|
||||
<b class="nc"> upperCards.addHeader("Char/\033[38;5;180mEvents\033[0m", "Building");</b>
|
||||
<b class="nc"> lowerCards.addHeader("Char/\033[38;5;180mEvents\033[0m", "Building");</b>
|
||||
|
||||
<b class="nc"> int maxU = Math.max(model.upperListTribeCards.size(), model.upperListBuildingCards.size());</b>
|
||||
<b class="nc"> for (int i = 0; i < maxU; i++) {</b>
|
||||
<b class="nc"> String t = i < model.upperListTribeCards.size() ? i + ":" + model.upperListTribeCards.get(i).toStringBoard() : "";</b>
|
||||
<b class="nc"> String b = i < model.upperListBuildingCards.size() ? i + ":" + model.upperListBuildingCards.get(i).toStringBoard() : "";</b>
|
||||
<b class="nc"> upperCards.addRow(t, b);</b>
|
||||
}
|
||||
|
||||
<b class="nc"> int maxL = Math.max(model.lowerListTribeCards.size(), model.lowerListBuildingCards.size());</b>
|
||||
<b class="nc"> for (int i = 0; i < maxL; i++) {</b>
|
||||
<b class="nc"> String t = i < model.lowerListTribeCards.size() ? i + ":" + model.lowerListTribeCards.get(i).toStringBoard() : "";</b>
|
||||
<b class="nc"> String b = i < model.lowerListBuildingCards.size() ? i + ":" + model.lowerListBuildingCards.get(i).toStringBoard() : "";</b>
|
||||
<b class="nc"> lowerCards.addRow(t, b);</b>
|
||||
}
|
||||
|
||||
<b class="nc"> offerTrack.addRow(slotPlayers);</b>
|
||||
<b class="nc"> offerTrack.addRow(slotNames);</b>
|
||||
|
||||
<b class="nc"> String upperSection = upperCards.build();</b>
|
||||
|
||||
<b class="nc"> String middleSection = model.orderLogicCard.toString() + offerTrack.build();</b>
|
||||
|
||||
<b class="nc"> String lowerSection = lowerCards.build();</b>
|
||||
|
||||
<b class="nc"> return model.currentState + "\n"</b>
|
||||
+ upperSection + "\n"
|
||||
+ middleSection + "\n"
|
||||
+ lowerSection;
|
||||
}
|
||||
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-19 22:53</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user