FIx: Fixed Coverage Screenshots And htmlRepot.

This commit is contained in:
GabrieleRadice
2026-06-19 22:57:43 +02:00
parent a4dc8b2540
commit 0129efe400
316 changed files with 4566 additions and 4497 deletions
@@ -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">&nbsp;package it.polimi.ingsw.gc14.View.TUI;
&nbsp;import java.util.*;
&nbsp;
&nbsp;/**
&nbsp; * General-purpose helper for building fixed-width ASCII/Unicode tables in a TUI.
&nbsp; *
&nbsp; * &lt;p&gt;A table is built by adding rows one at a time, then calling {@link #build()}
&nbsp; * to obtain the fully rendered string. Column width is computed automatically
&nbsp; * from the widest cell across all rows.
&nbsp; *
&nbsp; * &lt;p&gt;Cell widths are measured in terminal columns, not Java {@code char} units:
&nbsp; * wide characters (emoji, CJK) count as 2 columns each.
&nbsp; *
&nbsp; * &lt;p&gt;Example usage:
&nbsp; * &lt;pre&gt;{@code
&nbsp; * AsciiTable table = new AsciiTable(BorderStyle.UNICODE, 3);
&nbsp; * table.addHeader(&quot;Name&quot;, &quot;🍖&quot;, &quot;&quot;);
&nbsp; * table.addRow(&quot;Alice&quot;, &quot;4&quot;, &quot;12&quot;);
&nbsp; * table.addRow(&quot;Bob&quot;, &quot;2&quot;, &quot;8&quot;);
&nbsp; * System.out.println(table.build());
&nbsp; * }&lt;/pre&gt;
&nbsp; */
&nbsp;public class AsciiTable {
&nbsp; /** Border style used to draw corners, lines, and junctions. */
&nbsp; private final BorderStyle s;
&nbsp;
&nbsp; /** Number of columns in this table. */
&nbsp; private final int cols;
&nbsp;
&nbsp; /** All rows of the table, in insertion order. */
<b class="fc">&nbsp; private final List&lt;List&lt;String&gt;&gt; rows = new ArrayList&lt;&gt;();</b>
&nbsp;
&nbsp; /** Indices of rows after which a horizontal separator line is drawn. */
<b class="fc">&nbsp; private final List&lt;Integer&gt; separators = new ArrayList&lt;&gt;();</b>
&nbsp;
&nbsp; /**
&nbsp; * Creates a new empty table with the given border style and column count.
&nbsp; *
&nbsp; * @param s the border style to use when rendering.
&nbsp; * @param cols the number of columns.
&nbsp; */
<b class="fc">&nbsp; public AsciiTable(BorderStyle s, int cols) {</b>
<b class="fc">&nbsp; this.s = s; this.cols = cols;</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Appends a row using varargs cells.
&nbsp; *
&nbsp; * @param cells one value per column.
&nbsp; */
<b class="fc">&nbsp; public void addRow(String... cells) { rows.add(Arrays.asList(cells)); }</b>
&nbsp;
&nbsp; /**
&nbsp; * Appends a row from an existing list.
&nbsp; *
&nbsp; * @param cells one value per column.
&nbsp; */
<b class="fc">&nbsp; public void addRow(List&lt;String&gt; cells) { rows.add(cells); }</b>
&nbsp;
&nbsp; /**
&nbsp; * Inserts a header row at position 0 and marks it with a separator line below it.
&nbsp; *
&nbsp; * @param cells one header label per column.
&nbsp; */
&nbsp; public void addHeader(String... cells) {
<b class="fc">&nbsp; rows.add(0, Arrays.asList(cells));</b>
<b class="fc">&nbsp; separators.add(0);</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Marks a separator line to be drawn after the last row added so far.
&nbsp; */
<b class="fc">&nbsp; public void addSeparator() { separators.add(rows.size() - 1); }</b>
&nbsp;
&nbsp; /**
&nbsp; * Renders the table to a multi-line string.
&nbsp; * Column width is the widest cell in display columns (wide chars = 2), plus 1.
&nbsp; *
&nbsp; * @return the fully rendered table as a multi-line string.
&nbsp; */
&nbsp; public String build() {
<b class="fc">&nbsp; var sb = new StringBuilder();</b>
<b class="fc">&nbsp; int maxWidth = rows.stream()</b>
<b class="fc">&nbsp; .mapToInt(x -&gt; x.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0))</b>
<b class="fc">&nbsp; .max().orElse(0) + 1;</b>
<b class="fc">&nbsp; sb.append(hline(s.tl(), s.mt(), s.tr(), maxWidth)).append(&#39;\n&#39;);</b>
&nbsp;
<b class="fc">&nbsp; for (int i = 0; i &lt; rows.size(); i++) {</b>
<b class="fc">&nbsp; sb.append(s.v());</b>
<b class="fc">&nbsp; for (String cell : rows.get(i))</b>
<b class="fc">&nbsp; sb.append(rpad(&quot; &quot; + cell, maxWidth)).append(s.v());</b>
<b class="fc">&nbsp; sb.append(&#39;\n&#39;);</b>
<b class="pc">&nbsp; if (separators.contains(i) &amp;&amp; i &lt; rows.size() - 1)</b>
<b class="fc">&nbsp; sb.append(hline(s.sl(), s.sx(), s.sr(), maxWidth)).append(&#39;\n&#39;);</b>
&nbsp; }
<b class="fc">&nbsp; sb.append(hline(s.bl(), s.mb(), s.br(), maxWidth));</b>
<b class="fc">&nbsp; return sb.toString();</b>
&nbsp; }
&nbsp; /**
&nbsp; * Builds a single horizontal borderline across all columns.
&nbsp; *
&nbsp; * @param l left-end character.
&nbsp; * @param m middle junction character (between columns).
&nbsp; * @param r right-end character.
&nbsp; * @param maxWidth width in display columns of each cell (including padding).
&nbsp; * @return the rendered horizontal line string.
&nbsp; */
&nbsp; private String hline(String l, String m, String r, int maxWidth) {
<b class="fc">&nbsp; var sb = new StringBuilder(l);</b>
<b class="fc">&nbsp; for (int i = 0; i &lt; cols; i++) {</b>
<b class="fc">&nbsp; sb.repeat(Objects.requireNonNull(s.h()), maxWidth);</b>
<b class="fc">&nbsp; if (i &lt; cols - 1) sb.append(m);</b>
&nbsp; }
<b class="fc">&nbsp; return sb.append(r).toString();</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Pads {@code s} with trailing spaces so its display width equals {@code w}.
&nbsp; * If the string is already at or over {@code w} columns, it is returned as-is.
&nbsp; */
&nbsp; private static String rpad(String s, int w) {
<b class="fc">&nbsp; int dw = displayWidth(s);</b>
<b class="fc">&nbsp; if (dw &gt;= w) return s;</b>
<b class="fc">&nbsp; return s + &quot; &quot;.repeat(w - dw);</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Places two pre-rendered text blocks side by side, separated by a gap.
&nbsp; * Left-block lines are padded to a uniform display width so the right block
&nbsp; * always starts at the same column. Uses {@link #displayWidth} for measurement.
&nbsp; *
&nbsp; * @param left lines of the left block.
&nbsp; * @param right lines of the right block.
&nbsp; * @param gap number of space characters between the two blocks.
&nbsp; * @return the combined multi-line string.
&nbsp; */
&nbsp; public static String sideBySide(List&lt;String&gt; left, List&lt;String&gt; right, int gap) {
<b class="nc">&nbsp; int leftWidth = left.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0);</b>
<b class="nc">&nbsp; int maxHeight = Math.max(left.size(), right.size());</b>
<b class="nc">&nbsp; String padding = &quot; &quot;.repeat(gap);</b>
&nbsp;
<b class="nc">&nbsp; var sb = new StringBuilder();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; maxHeight; i++) {</b>
<b class="nc">&nbsp; String l = i &lt; left.size() ? left.get(i) : &quot; &quot;.repeat(leftWidth);</b>
<b class="nc">&nbsp; l = rpad(l, leftWidth);</b>
<b class="nc">&nbsp; String r = i &lt; right.size() ? right.get(i) : &quot;&quot;;</b>
<b class="nc">&nbsp; sb.append(l).append(padding).append(r).append(&#39;\n&#39;);</b>
&nbsp; }
<b class="nc">&nbsp; return sb.toString();</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; /**
&nbsp; * Returns the number of terminal columns required to display {@code s}.
&nbsp; * Wide characters (emoji, CJK, full-width) count as 2; all others as 1.
&nbsp; *
&nbsp; * @param s the string to measure; ANSI escape sequences are stripped before counting.
&nbsp; * @return the display width in terminal columns.
&nbsp; */
&nbsp; public static int displayWidth(String s) {
<b class="fc">&nbsp; s = s.replaceAll(&quot;\033\\[[^m]*m&quot;, &quot;&quot;);</b>
<b class="fc">&nbsp; int w = 0;</b>
<b class="fc">&nbsp; for (int i = 0; i &lt; s.length(); ) {</b>
<b class="fc">&nbsp; int cp = s.codePointAt(i);</b>
<b class="fc">&nbsp; w += isWide(cp) ? 2 : 1;</b>
<b class="fc">&nbsp; i += Character.charCount(cp);</b>
&nbsp; }
<b class="fc">&nbsp; return w;</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Returns {@code true} when {@code cp} is a wide (2-column) character.
&nbsp; * Covers CJK blocks, Hangul, full-width forms, and emoji (including
&nbsp; * the specific emoji used in this project: 🍖 U+1F357, ⭐ U+2B50).
&nbsp; */
&nbsp; private static boolean isWide(int cp) {
<b class="fc">&nbsp; if (cp &lt; 0x1100) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0x115F) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0x2E80) {</b>
<b class="nc">&nbsp; return cp == 0x2B50</b>
&nbsp; || cp == 0x2B55;
&nbsp; }
<b class="pc">&nbsp; if (cp &lt;= 0x303E) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0x3041) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xA4CF) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0xA960) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xA97F) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0xAC00) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xD7AF) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0xF900) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xFAFF) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0xFE10) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xFE1F) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0xFE30) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xFE6F) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0xFF00) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xFF60) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0xFFE0) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0xFFE6) return true;</b>
<b class="pc">&nbsp; if (cp &lt; 0x1F004) return false;</b>
<b class="pc">&nbsp; if (cp &lt;= 0x1FAFF) return true;</b>
&nbsp;
<b class="nc">&nbsp; if (cp &lt; 0x20000) return false;</b>
<b class="nc">&nbsp; return cp &lt;= 0x3FFFD;</b>
&nbsp; }
&nbsp;}
</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">&nbsp;package it.polimi.ingsw.gc14.View.TUI;
&nbsp;
&nbsp;/**
&nbsp; * Defines the available border styles used to render {@link AsciiTable}
&nbsp; * instances in the text-based user interface.
&nbsp; *
&nbsp; * &lt;p&gt;Each style stores the characters needed to draw table corners,
&nbsp; * horizontal and vertical lines, and junctions.
&nbsp; */
<b class="fc">&nbsp;@SuppressWarnings(&quot;ALL&quot;)</b>
&nbsp;public enum BorderStyle {
&nbsp;
&nbsp; /**
&nbsp; * Rounded Unicode border style.
&nbsp; */
<b class="fc">&nbsp; ROUNDED(&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;);</b>
&nbsp; /** Corner (tl/tr/bl/br), line (h/v), outer junction (ml/mr/mt/mb/x), and separator junction (sl/sr/sx) characters. */
&nbsp; private final String tl,tr,bl,br,h,v,ml,mr,mt,mb,x,sl,sr,sx;
&nbsp;
&nbsp; BorderStyle(String tl,String tr,String bl,String br,
&nbsp; String h, String v, String ml,String mr,
&nbsp; String mt,String mb,String x,
<b class="fc">&nbsp; String sl,String sr,String sx) {</b>
<b class="fc">&nbsp; this.tl = tl; this.tr = tr;</b>
<b class="fc">&nbsp; this.bl = bl; this.br = br;</b>
<b class="fc">&nbsp; this.h = h; this.v = v;</b>
<b class="fc">&nbsp; this.ml = ml; this.mr = mr;</b>
<b class="fc">&nbsp; this.mt = mt; this.mb = mb;</b>
<b class="fc">&nbsp; this.x = x;</b>
<b class="fc">&nbsp; this.sl = sl; this.sr = sr;</b>
<b class="fc">&nbsp; this.sx = sx;</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * @return the top-left corner character.
&nbsp; */
<b class="fc">&nbsp; public String tl() { return tl; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the top-right corner character.
&nbsp; */
<b class="fc">&nbsp; public String tr() { return tr; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the bottom-left corner character.
&nbsp; */
<b class="fc">&nbsp; public String bl() { return bl; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the bottom-right corner character.
&nbsp; */
<b class="fc">&nbsp; public String br() { return br; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the horizontal line character.
&nbsp; */
<b class="fc">&nbsp; public String h() { return h; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the vertical line character.
&nbsp; */
<b class="fc">&nbsp; public String v() { return v; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the top-middle junction character.
&nbsp; */
<b class="fc">&nbsp; public String mt() { return mt; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the bottom-middle junction character.
&nbsp; */
<b class="fc">&nbsp; public String mb() { return mb; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the separator-left junction character.
&nbsp; */
<b class="fc">&nbsp; public String sl() { return sl; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the separator-right junction character.
&nbsp; */
<b class="fc">&nbsp; public String sr() { return sr; }</b>
&nbsp;
&nbsp; /**
&nbsp; * @return the separator center junction character.
&nbsp; */
<b class="fc">&nbsp; public String sx() { return sx; }</b>
&nbsp;}
</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">&nbsp;package it.polimi.ingsw.gc14.View.TUI;
&nbsp;import it.polimi.ingsw.gc14.ErrorType;
&nbsp;import it.polimi.ingsw.gc14.Model.*;
&nbsp;import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
&nbsp;import it.polimi.ingsw.gc14.View.IView;
&nbsp;
&nbsp;import org.jline.reader.LineReader;
&nbsp;import org.jline.terminal.Terminal;
&nbsp;
&nbsp;import java.util.ArrayList;
&nbsp;import java.util.List;
&nbsp;import java.util.Map;
&nbsp;
&nbsp;/**
&nbsp; * Text-based User Interface (TUI) implementation of {@link IView}.
&nbsp; *
&nbsp; * &lt;p&gt;Single render view: board + menu on top, all players&#39; hands in a
&nbsp; * responsive grid below. Hands wrap to a new row when their combined width
&nbsp; * would exceed the terminal width.
&nbsp; *
&nbsp; * &lt;p&gt;All output goes through {@link #display(String)}, which uses
&nbsp; * {@link LineReader#printAbove} when a JLine reader is set so that the
&nbsp; * readline prompt is correctly redrawn after background-thread renders.
&nbsp; */
&nbsp;public class TUI implements IView {
&nbsp;
&nbsp; private MiniModel model;
&nbsp; private String username;
&nbsp;
&nbsp; /**
&nbsp; * JLine reader — when non-null, all output uses {@code printAbove} so the
&nbsp; * readline prompt is preserved after background-thread renders.
&nbsp; */
&nbsp; private LineReader lineReader;
&nbsp;
&nbsp; /** JLine terminal — used to query terminal width for centering and grid layout. */
&nbsp; private Terminal terminal;
&nbsp;
&nbsp; /**
&nbsp; * Constructs a {@code TUI} bound to the given model.
&nbsp; *
&nbsp; * @param model the model to display; may be {@code null} initially.
&nbsp; */
<b class="nc">&nbsp; public TUI(MiniModel model) {</b>
<b class="nc">&nbsp; this.model = model;</b>
<b class="nc">&nbsp; this.username = &quot;&quot;;</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; /**
&nbsp; * Sets the username of the local player.
&nbsp; *
&nbsp; * @param username the username to assign.
&nbsp; */
&nbsp; public void setUsername(String username) {
<b class="nc">&nbsp; this.username = username;</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Updates the model stored in this view.
&nbsp; *
&nbsp; * @param model the latest mini model to display.
&nbsp; */
&nbsp; @Override
&nbsp; public void setModel(MiniModel model) {
<b class="nc">&nbsp; this.model = model;</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Registers the active JLine {@link LineReader}.
&nbsp; * Once set, all output routes through {@link LineReader#printAbove} so the
&nbsp; * prompt survives background-thread renders.
&nbsp; *
&nbsp; * @param lineReader the JLine reader to use for output; also provides the terminal reference.
&nbsp; */
&nbsp; public void setLineReader(LineReader lineReader) {
<b class="nc">&nbsp; this.lineReader = lineReader;</b>
<b class="nc">&nbsp; this.terminal = lineReader.getTerminal();</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; /**
&nbsp; * Default render: dispatches to the right view based on game stage.
&nbsp; */
&nbsp; @Override
&nbsp; public synchronized void render() {
<b class="nc">&nbsp; if (model == null) return;</b>
<b class="nc">&nbsp; GameStages stage = model.currentState.getGameStage();</b>
<b class="nc">&nbsp; if (stage == GameStages.TOTEM_CHOICE) {</b>
<b class="nc">&nbsp; display(buildTotemsContent());</b>
<b class="nc">&nbsp; printLine(&quot;\033[2mCommands: totem &lt;pos&gt;\033[0m&quot;);</b>
<b class="nc">&nbsp; } else if (stage == GameStages.ENDED) {</b>
<b class="nc">&nbsp; display(buildStandingContent());</b>
<b class="nc">&nbsp; printLine(&quot;Type &#39;rematch&#39; to play again or &#39;quit&#39; to exit&quot;);</b>
&nbsp; } else {
<b class="nc">&nbsp; display(buildBoardContent());</b>
<b class="nc">&nbsp; printLine(&quot;\033[2mCommands: slot &lt;pos&gt; | draw upper/lower tribe/building &lt;pos&gt; | totem &lt;pos&gt; | skip | clear | details buildings/events/characters | quit\033[0m&quot;);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Renders the board + menu (top) and all players&#39; hands in a grid (bottom). */
&nbsp; public void renderBoard() {
<b class="nc">&nbsp; display(buildBoardContent());</b>
<b class="nc">&nbsp; printLine(&quot;\033[2mCommands: slot &lt;pos&gt; | draw upper/lower tribe/building &lt;pos&gt; | totem &lt;pos&gt; | skip | clear | details buildings/events/characters | quit\033[0m&quot;);</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Shows an error message combined with the current board in one display call,
&nbsp; * so only one {@code printAbove} is issued and the prompt is redrawn correctly.
&nbsp; *
&nbsp; * @param error the error type.
&nbsp; * @param message the human-readable message to append.
&nbsp; */
&nbsp; public void showError(ErrorType error, String message) {
&nbsp; String text;
<b class="nc">&nbsp; if (error == ErrorType.WRONG_ACTION</b>
&nbsp; &amp;&amp; model != null
<b class="nc">&nbsp; &amp;&amp; model.currentState.getCurrentPlayer() != null</b>
<b class="nc">&nbsp; &amp;&amp; !model.currentState.getCurrentPlayer().getUserName().equals(username)) {</b>
<b class="nc">&nbsp; text = &quot;It&#39;s not your turn&quot;;</b>
&nbsp; } else {
<b class="nc">&nbsp; text = message;</b>
<b class="nc">&nbsp; if (error == ErrorType.SERVER_CRASHED) {</b>
<b class="nc">&nbsp; text += &quot;\nPress any key to continue&quot;;</b>
&nbsp; }
&nbsp; }
<b class="nc">&nbsp; render();</b>
<b class="nc">&nbsp; printLine(&quot;\033[31m&quot; + text + &quot;\033[0m&quot;);</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; /**
&nbsp; * Builds the main view: board + menu side by side, followed by all players&#39;
&nbsp; * hands arranged in a responsive grid.
&nbsp; */
&nbsp; private String buildBoardContent() {
<b class="nc">&nbsp; List&lt;String&gt; left = List.of(boardStamp().split(&quot;\n&quot;));</b>
<b class="nc">&nbsp; List&lt;String&gt; right = List.of(buildAllHandsContent().split(&quot;\n&quot;));</b>
<b class="nc">&nbsp; return AsciiTable.sideBySide(left, right, 3);</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Arranges every player&#39;s hand side-by-side, wrapping to a new grid row
&nbsp; * whenever the next panel would exceed the terminal width.
&nbsp; */
&nbsp; private String buildAllHandsContent() {
<b class="nc">&nbsp; List&lt;Player&gt; players = new ArrayList&lt;&gt;(model.players.values());</b>
<b class="nc">&nbsp; players.removeIf(p -&gt; model.disconnectedPlayers.contains(p.getUserName()));</b>
<b class="nc">&nbsp; if (players.isEmpty()) return &quot;&quot;;</b>
&nbsp;
<b class="nc">&nbsp; int n = players.size();</b>
<b class="nc">&nbsp; int leftCount = Math.min(3, n);</b>
&nbsp;
<b class="nc">&nbsp; StringBuilder leftSb = new StringBuilder();</b>
<b class="nc">&nbsp; StringBuilder rightSb = new StringBuilder();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; leftCount; i++) leftSb.append(players.get(i).toString()).append(&quot;\n&quot;);</b>
<b class="nc">&nbsp; for (int i = leftCount; i &lt; n; i++) rightSb.append(players.get(i).toString()).append(&quot;\n&quot;);</b>
&nbsp;
&nbsp; String handsBlock;
<b class="nc">&nbsp; if (rightSb.isEmpty()) {</b>
<b class="nc">&nbsp; handsBlock = leftSb.toString();</b>
&nbsp; } else {
<b class="nc">&nbsp; handsBlock = AsciiTable.sideBySide(</b>
<b class="nc">&nbsp; List.of(leftSb.toString().split(&quot;\n&quot;, -1)),</b>
<b class="nc">&nbsp; List.of(rightSb.toString().split(&quot;\n&quot;, -1)), 3);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (!model.disconnectedPlayers.isEmpty()) {</b>
<b class="nc">&nbsp; var box = new AsciiTable(BorderStyle.ROUNDED, 1);</b>
<b class="nc">&nbsp; box.addHeader(&quot;Disconnected&quot;);</b>
<b class="nc">&nbsp; model.disconnectedPlayers.forEach(box::addRow);</b>
<b class="nc">&nbsp; handsBlock += &quot;\n&quot; + box.build();</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; return handsBlock;</b>
&nbsp; }
&nbsp;
&nbsp; private String buildTotemsContent() {
<b class="nc">&nbsp; var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size());</b>
<b class="nc">&nbsp; List&lt;String&gt; lines = new ArrayList&lt;&gt;();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; model.availableTotems.size(); i++) {</b>
<b class="nc">&nbsp; lines.add(i + &quot;.&quot; + model.availableTotems.get(i));</b>
&nbsp; }
<b class="nc">&nbsp; table.addRow(lines);</b>
<b class="nc">&nbsp; String current = model.currentState.getCurrentPlayer() != null</b>
<b class="nc">&nbsp; ? model.currentState.getCurrentPlayer().getUserName() + &quot;&quot;</b>
<b class="nc">&nbsp; : &quot;&quot;;</b>
<b class="nc">&nbsp; return current + &quot;TOTEMS AVAILABLE:\n&quot; + table.build();</b>
&nbsp; }
&nbsp;
&nbsp; private String buildStandingContent() {
<b class="nc">&nbsp; if (model.standingPlayers == null || model.standingPlayers.isEmpty()) return &quot;&quot;;</b>
<b class="nc">&nbsp; String banner = model.standingPlayers.get(0).getUserName().equals(username)</b>
<b class="nc">&nbsp; ? &quot;WINNER!&quot; : &quot;GAME OVER&quot;;</b>
<b class="nc">&nbsp; List&lt;String&gt; lines = new ArrayList&lt;&gt;();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; model.standingPlayers.size(); i++) {</b>
<b class="nc">&nbsp; var p = model.standingPlayers.get(i);</b>
<b class="nc">&nbsp; lines.add((i + 1) + &quot;. &quot; + p.getUserName()</b>
<b class="nc">&nbsp; + &quot; (&quot; + p.getTotem().toString() + &quot;)&quot;</b>
<b class="nc">&nbsp; + &quot; \uD83C\uDF56:&quot; + p.getFoodValue()</b>
<b class="nc">&nbsp; + &quot; \uD83C\uDFC5:&quot; + p.getPrestigeValue());</b>
&nbsp; }
<b class="nc">&nbsp; int contentWidth = lines.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0);</b>
<b class="nc">&nbsp; int pad = Math.max(0, (contentWidth - AsciiTable.displayWidth(banner)) / 2);</b>
<b class="nc">&nbsp; var table = new AsciiTable(BorderStyle.ROUNDED, 1);</b>
<b class="nc">&nbsp; table.addHeader(&quot; &quot;.repeat(pad) + banner);</b>
<b class="nc">&nbsp; lines.forEach(table::addRow);</b>
<b class="nc">&nbsp; return table.build();</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; /**
&nbsp; * Clears the terminal and displays {@code content}, horizontally centered.
&nbsp; *
&nbsp; * &lt;p&gt;When a JLine {@link LineReader} is registered, output is routed through
&nbsp; * {@link LineReader#printAbove} which pauses readline, prints the content,
&nbsp; * and redraws the prompt — safe to call from any thread.
&nbsp; * Otherwise, ANSI codes are written directly to stdout.
&nbsp; */
&nbsp; private void display(String content) {
<b class="nc">&nbsp; String cleared = &quot;\033[H\033[2J&quot; + center(content);</b>
<b class="nc">&nbsp; if (lineReader != null) {</b>
<b class="nc">&nbsp; lineReader.printAbove(cleared);</b>
&nbsp; } else {
<b class="nc">&nbsp; System.out.print(cleared);</b>
<b class="nc">&nbsp; System.out.println();</b>
<b class="nc">&nbsp; System.out.flush();</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Prints {@code text} above the prompt without clearing the screen. */
&nbsp; private void printLine(String text) {
<b class="nc">&nbsp; if (lineReader != null) {</b>
<b class="nc">&nbsp; lineReader.printAbove(text);</b>
&nbsp; } else {
<b class="nc">&nbsp; System.out.println(text);</b>
<b class="nc">&nbsp; System.out.flush();</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Horizontally centers each line of {@code content} within the terminal width.
&nbsp; * Empty lines are left unpadded. Falls back to the original string when the
&nbsp; * terminal width is unknown.
&nbsp; */
&nbsp; private String center(String content) {
<b class="nc">&nbsp; if (terminal == null) return content;</b>
<b class="nc">&nbsp; int termWidth = terminal.getWidth();</b>
<b class="nc">&nbsp; if (termWidth &lt;= 0) return content;</b>
&nbsp;
<b class="nc">&nbsp; String[] lines = content.split(&quot;\n&quot;, -1);</b>
&nbsp;
<b class="nc">&nbsp; int maxWidth = 0;</b>
<b class="nc">&nbsp; for (String line : lines) {</b>
<b class="nc">&nbsp; int w = visibleLength(line);</b>
<b class="nc">&nbsp; if (w &gt; maxWidth) maxWidth = w;</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; int pad = Math.max(0, (termWidth - maxWidth) / 2);</b>
<b class="nc">&nbsp; if (pad == 0) return content;</b>
&nbsp;
<b class="nc">&nbsp; String prefix = &quot; &quot;.repeat(pad);</b>
<b class="nc">&nbsp; StringBuilder sb = new StringBuilder();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; lines.length; i++) {</b>
<b class="nc">&nbsp; if (!lines[i].isEmpty()) sb.append(prefix);</b>
<b class="nc">&nbsp; sb.append(lines[i]);</b>
<b class="nc">&nbsp; if (i &lt; lines.length - 1) sb.append(&quot;\n&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; return sb.toString();</b>
&nbsp; }
&nbsp;
&nbsp; /** Returns the display width of a string in terminal columns, stripping ANSI escape codes. */
&nbsp; private int visibleLength(String line) {
<b class="nc">&nbsp; return AsciiTable.displayWidth(line);</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; /**
&nbsp; * Returns the board state as a multi-line string: turn order, upper cards,
&nbsp; * offer track, lower cards.
&nbsp; *
&nbsp; * @return multi-line board string.
&nbsp; */
&nbsp; public String boardStamp() {
<b class="nc">&nbsp; var offerTrack = new AsciiTable(BorderStyle.ROUNDED, model.slotPlayerMap.size());</b>
<b class="nc">&nbsp; List&lt;String&gt; slotNames = new ArrayList&lt;&gt;();</b>
<b class="nc">&nbsp; List&lt;String&gt; slotPlayers = new ArrayList&lt;&gt;();</b>
&nbsp;
<b class="nc">&nbsp; int index = 0;</b>
<b class="nc">&nbsp; for (Map.Entry&lt;Slot, Player&gt; entry : model.slotPlayerMap.entrySet()) {</b>
<b class="nc">&nbsp; slotNames.add((index++) + &quot;.&quot; + entry.getKey().toStringTUI());</b>
<b class="nc">&nbsp; slotPlayers.add(entry.getValue() != null ? entry.getValue().getUserName() : &quot; &quot;);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; var upperCards = new AsciiTable(BorderStyle.ROUNDED, 2);</b>
<b class="nc">&nbsp; var lowerCards = new AsciiTable(BorderStyle.ROUNDED, 2);</b>
<b class="nc">&nbsp; upperCards.addHeader(&quot;Char/\033[38;5;180mEvents\033[0m&quot;, &quot;Building&quot;);</b>
<b class="nc">&nbsp; lowerCards.addHeader(&quot;Char/\033[38;5;180mEvents\033[0m&quot;, &quot;Building&quot;);</b>
&nbsp;
<b class="nc">&nbsp; int maxU = Math.max(model.upperListTribeCards.size(), model.upperListBuildingCards.size());</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; maxU; i++) {</b>
<b class="nc">&nbsp; String t = i &lt; model.upperListTribeCards.size() ? i + &quot;:&quot; + model.upperListTribeCards.get(i).toStringBoard() : &quot;&quot;;</b>
<b class="nc">&nbsp; String b = i &lt; model.upperListBuildingCards.size() ? i + &quot;:&quot; + model.upperListBuildingCards.get(i).toStringBoard() : &quot;&quot;;</b>
<b class="nc">&nbsp; upperCards.addRow(t, b);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; int maxL = Math.max(model.lowerListTribeCards.size(), model.lowerListBuildingCards.size());</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; maxL; i++) {</b>
<b class="nc">&nbsp; String t = i &lt; model.lowerListTribeCards.size() ? i + &quot;:&quot; + model.lowerListTribeCards.get(i).toStringBoard() : &quot;&quot;;</b>
<b class="nc">&nbsp; String b = i &lt; model.lowerListBuildingCards.size() ? i + &quot;:&quot; + model.lowerListBuildingCards.get(i).toStringBoard() : &quot;&quot;;</b>
<b class="nc">&nbsp; lowerCards.addRow(t, b);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; offerTrack.addRow(slotPlayers);</b>
<b class="nc">&nbsp; offerTrack.addRow(slotNames);</b>
&nbsp;
<b class="nc">&nbsp; String upperSection = upperCards.build();</b>
&nbsp;
<b class="nc">&nbsp; String middleSection = model.orderLogicCard.toString() + offerTrack.build();</b>
&nbsp;
<b class="nc">&nbsp; String lowerSection = lowerCards.build();</b>
&nbsp;
<b class="nc">&nbsp; return model.currentState + &quot;\n&quot;</b>
&nbsp; + upperSection + &quot;\n&quot;
&nbsp; + middleSection + &quot;\n&quot;
&nbsp; + lowerSection;
&nbsp; }
&nbsp;
&nbsp;}
</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>