Files
Progetto-ingegneria-del-sof…/deliverables/Coverage/htmReport/ns-1/sources/source-2.html
T
2026-06-19 22:57:43 +02:00

485 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > ClientLauncherTUI</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</a>
</div>
<h1>Coverage Summary for Class: ClientLauncherTUI (it.polimi.ingsw.gc14)</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">ClientLauncherTUI</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/12)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/62)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/139)
</span>
</td>
</tr>
</table>
<br/>
<br/>
<pre>
<code class="sourceCode" id="sourceCode">&nbsp;package it.polimi.ingsw.gc14;
&nbsp;
&nbsp;import it.polimi.ingsw.gc14.Controller.ClientController;
&nbsp;import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
&nbsp;import it.polimi.ingsw.gc14.Network.IClient;
&nbsp;import it.polimi.ingsw.gc14.Network.NetworkConfig;
&nbsp;import it.polimi.ingsw.gc14.Network.InterfaceResolver;
&nbsp;import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient;
&nbsp;import it.polimi.ingsw.gc14.Network.TCP.Client.TCPClient;
&nbsp;import it.polimi.ingsw.gc14.View.TUI.TUI;
&nbsp;
&nbsp;import org.jline.builtins.Completers.TreeCompleter;
&nbsp;import org.jline.reader.*;
&nbsp;import org.jline.reader.impl.completer.StringsCompleter;
&nbsp;import org.jline.terminal.Terminal;
&nbsp;import org.jline.terminal.TerminalBuilder;
&nbsp;
&nbsp;import java.io.IOException;
&nbsp;import java.io.PrintStream;
&nbsp;import java.nio.charset.StandardCharsets;
&nbsp;
&nbsp;import static org.jline.builtins.Completers.TreeCompleter.node;
&nbsp;
&nbsp;/**
&nbsp; * Entry point for the TUI-based game client.
&nbsp; * Handles login, connects to the server, and drives the JLine-powered command loop.
&nbsp; */
<b class="nc">&nbsp;public class ClientLauncherTUI {</b>
&nbsp; /** TUI view shared with the client controller. */
&nbsp; private TUI view;
&nbsp; /** JLine terminal used for all input/output. */
&nbsp; private Terminal terminal;
&nbsp; /** Line reader used during active gameplay, with tab-completion wired. */
&nbsp; private LineReader gameReader;
&nbsp; /** Username of the currently logged-in player; empty string before login. */
<b class="nc">&nbsp; private String currentUsername = &quot;&quot;;</b>
&nbsp;
&nbsp; private static final String BUILDING_DETAILS =
&nbsp; &quot;ID 0 — Each time you complete a set of 6 different Character cards, take 5 \uD83C\uDF56. (Sets completed before acquiring don&#39;t count.)\n&quot; +
&nbsp; &quot;ID 1 — During the Sustenance Event, pay 1 less \uD83C\uDF56 for each Artist/Inventor/Gatherer in your tribe.\n&quot; +
&nbsp; &quot;ID 2 — During the Shamanic Ritual Event, you do not lose Prestige Points if you have fewer ⭐ than all other players.\n&quot; +
&nbsp; &quot;ID 3 — When you move your Totem to a Food-bonus space, take 1 extra \uD83C\uDF56. If placed last, pay 1 \uD83C\uDF56 normally (no effect).\n&quot; +
&nbsp; &quot;ID 4 — Each time you obtain a pair of identical Inventors (same icon), take 3 \uD83C\uDF56. (Pairs owned before don&#39;t count.)\n&quot; +
&nbsp; &quot;ID 5 — During the Shamanic Ritual Event, your tribe has 3 additional ⭐ icons.\n&quot; +
&nbsp; &quot;ID 6 — During the Shamanic Ritual Event, if you have more ⭐ than any other player, gain double Prestige. Ties still count.\n&quot; +
&nbsp; &quot;ID 7 — During the Hunt Event, take 1 \uD83C\uDF56 and gain 1 extra Prestige Point for each Hunter in your tribe.\n&quot; +
&nbsp; &quot;ID 8 — At end of game, gain double the Prestige Points shown on Builder cards in your tribe.\n&quot; +
&nbsp; &quot;ID 9 — During the Cave Paintings Event, take 1 \uD83C\uDF56 for each Artist in your tribe.\n&quot; +
&nbsp; &quot;ID 10 — At end of game, gain 6 Prestige Points for each complete set of 6 different Character cards.\n&quot; +
&nbsp; &quot;ID 11 — At end of game, gain the indicated Prestige Points for each Character card of the indicated type.\n&quot; +
&nbsp; &quot;ID 12 — After all Totems return to Turn Order (before End of Round), take 1 Character or 1 Building card from the top row (paying its cost).\n&quot; +
&nbsp; &quot;ID 13 — At end of game, gain 25 Prestige Points.&quot;;
&nbsp;
&nbsp; private static final String EVENT_DETAILS =
&nbsp; &quot;SUSTENANCE — Pay 1 \uD83C\uDF56 per Character (Buildings don&#39;t count). If you can&#39;t feed all Characters, lose the\n&quot; +
&nbsp; &quot; Prestige Points shown on the card for each one you couldn&#39;t feed. Each Gatherer gives a 3\uD83C\uDF56 discount.\n&quot; +
&nbsp; &quot; You cannot choose to lose PP to avoid paying Food. Sustenance resolves last if multiple Events occur.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;HUNT — Take 1 \uD83C\uDF56 and gain the Prestige Points shown on the card for each Hunter in your tribe.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;SHAMANIC RITUAL — Player with the most \uD83C\uDF1F icons gains the Prestige Points on the card.\n&quot; +
&nbsp; &quot; Player with the fewest \uD83C\uDF1F icons loses those Prestige Points. In case of a tie, all tied players gain/lose.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;CAVE PAINTINGS — If you have fewer Artists than the top-line threshold, lose the indicated Prestige Points.\n&quot; +
&nbsp; &quot; If you meet the bottom-line threshold, gain the indicated Prestige Points for each Artist in your tribe.&quot;;
&nbsp;
&nbsp; private static final String CHARACTER_DETAILS =
&nbsp; &quot;INVENTORS — At end of game, gain PP equal to (number of Inventors) × (number of different Invention icons).\n&quot; +
&nbsp; &quot; There are 10 different Invention icons.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;GATHERERS — During Sustenance, each Gatherer gives a 3\uD83C\uDF56 discount on what you owe.\n&quot; +
&nbsp; &quot; Note: you do not take any Food from Gatherers under any circumstances.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;SHAMANS — Each Shaman shows 13 \uD83C\uDF1F icons. During Shamanic Ritual, majority = gain PP; minority = lose PP.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;BUILDERS — During the game, each Builder reduces the \uD83C\uDF56 cost of every Building card by the amount\n&quot; +
&nbsp; &quot; shown in the top-right corner. At end of game, each Builder gives the PP shown in the bottom-left corner.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;ARTISTS — During Cave Paintings, gain or lose PP based on Artist count (see &#39;details events&#39;).\n&quot; +
&nbsp; &quot; At end of game, gain 10 PP for every 2 Artists in your tribe.\n&quot; +
&nbsp; &quot;\n&quot; +
&nbsp; &quot;HUNTERS — Adding a Hunter without the \uD83C\uDF56 icon: nothing. Adding a Hunter WITH the \uD83C\uDF56 icon:\n&quot; +
&nbsp; &quot; immediately take 1 \uD83C\uDF56 for each Hunter in your tribe (with or without the icon).\n&quot; +
&nbsp; &quot; During Hunt Event, take \uD83C\uDF56 and gain PP based on your Hunter count (see &#39;details events&#39;).&quot;;
&nbsp; /**
&nbsp; * Launches the TUI client.
&nbsp; *
&nbsp; * @param args command-line arguments (unused).
&nbsp; * @throws InterruptedException if the thread is interrupted during startup.
&nbsp; */
&nbsp; static void main(String[] args) throws InterruptedException {
<b class="nc">&nbsp; new ClientLauncherTUI().start();</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Starts the TUI client. Creates a JLine terminal, loops through login → game → rematch.
&nbsp; *
&nbsp; */
&nbsp; private void start() {
&nbsp; try {
<b class="nc">&nbsp; System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));</b>
<b class="nc">&nbsp; System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));</b>
<b class="nc">&nbsp; terminal = TerminalBuilder.builder()</b>
<b class="nc">&nbsp; .system(true)</b>
<b class="nc">&nbsp; .encoding(StandardCharsets.UTF_8)</b>
<b class="nc">&nbsp; .build();</b>
&nbsp; } catch (IOException e) {
<b class="nc">&nbsp; System.err.println(&quot;Could not initialise terminal: &quot; + e.getMessage());</b>
&nbsp; return;
&nbsp; }
<b class="nc">&nbsp; terminal.writer().print(&quot;\033[H\033[2J&quot;);</b>
<b class="nc">&nbsp; terminal.writer().flush();</b>
&nbsp;
<b class="nc">&nbsp; view = new TUI(null);</b>
<b class="nc">&nbsp; ClientController controller = new ClientController(view);</b>
<b class="nc">&nbsp; gameReader = buildGameReader();</b>
<b class="nc">&nbsp; view.setLineReader(gameReader);</b>
&nbsp;
&nbsp; while (true) {
<b class="nc">&nbsp; if (!doLogin(controller)) continue;</b>
&nbsp;
<b class="nc">&nbsp; while (controller.getClient() != null) {</b>
<b class="nc">&nbsp; String prompt = buildPrompt();</b>
&nbsp; String line;
&nbsp; try {
<b class="nc">&nbsp; line = gameReader.readLine(prompt);</b>
&nbsp; } catch (UserInterruptException e) {
<b class="nc">&nbsp; safeQuit(controller);</b>
&nbsp; return;
&nbsp; } catch (EndOfFileException e) {
<b class="nc">&nbsp; safeQuit(controller);</b>
&nbsp; return;
&nbsp; }
<b class="nc">&nbsp; if (line == null || line.isBlank()) continue;</b>
<b class="nc">&nbsp; boolean rematch = handleCommand(line.trim(), controller);</b>
<b class="nc">&nbsp; if (rematch) break;</b>
&nbsp; }
&nbsp; }
&nbsp; }
&nbsp; /**
&nbsp; * Prompts for username, player count, network type, and server IP, then connects.
&nbsp; *
&nbsp; * @param controller the client controller to connect.
&nbsp; * @return {@code true} on successful connection; {@code false} if input is invalid or connection fails.
&nbsp; */
&nbsp; private boolean doLogin(ClientController controller) {
<b class="nc">&nbsp; LineReader loginReader = LineReaderBuilder.builder()</b>
<b class="nc">&nbsp; .terminal(terminal)</b>
<b class="nc">&nbsp; .completer(new StringsCompleter(&quot;rmi&quot;, &quot;tcp&quot;))</b>
<b class="nc">&nbsp; .build();</b>
<b class="nc">&nbsp; LineReader ipReader = LineReaderBuilder.builder()</b>
<b class="nc">&nbsp; .terminal(terminal)</b>
<b class="nc">&nbsp; .completer(new StringsCompleter(&quot;localhost&quot;))</b>
<b class="nc">&nbsp; .build();</b>
&nbsp;
<b class="nc">&nbsp; view.setLineReader(loginReader);</b>
&nbsp; try {
<b class="nc">&nbsp; return doLoginInner(controller, loginReader, ipReader);</b>
&nbsp; } finally {
<b class="nc">&nbsp; view.setLineReader(gameReader);</b>
&nbsp; }
&nbsp; }
&nbsp; /**
&nbsp; * Reads login fields from the terminal and connects to the server.
&nbsp; *
&nbsp; * @param controller the client controller to connect.
&nbsp; * @param loginReader line reader used for username, player count, and network type.
&nbsp; * @param ipReader line reader with localhost auto-complete for the server IP.
&nbsp; * @return {@code true} on successful connection; {@code false} if input is invalid or connection fails.
&nbsp; */
&nbsp; private boolean doLoginInner(ClientController controller, LineReader loginReader, LineReader ipReader) {
&nbsp; String username, networkStr, ip;
&nbsp; int nPlayers;
&nbsp;
&nbsp; try {
<b class="nc">&nbsp; username = loginReader.readLine(&quot;Username: &quot;).trim();</b>
<b class="nc">&nbsp; nPlayers = Integer.parseInt(loginReader.readLine(&quot;Number of players [2-5]: &quot;).trim());</b>
<b class="nc">&nbsp; networkStr = loginReader.readLine(&quot;Network [rmi/tcp]: &quot;).trim().toLowerCase();</b>
<b class="nc">&nbsp; ip = ipReader.readLine(&quot;Server IP [localhost]: &quot;).trim();</b>
<b class="nc">&nbsp; if (ip.isEmpty()) ip = &quot;localhost&quot;;</b>
&nbsp; } catch (UserInterruptException | EndOfFileException e) {
<b class="nc">&nbsp; System.exit(0);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; } catch (NumberFormatException e) {
<b class="nc">&nbsp; loginReader.printAbove(&quot;\033[H\033[2J&quot; + &quot;\033[31mERROR: Invalid number of players.\033[0m&quot;);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; currentUsername = username;</b>
<b class="nc">&nbsp; view.setUsername(username);</b>
<b class="nc">&nbsp; controller.setMyUsername(username);</b>
&nbsp;
&nbsp; IClient client;
<b class="nc">&nbsp; if (networkStr.equals(&quot;rmi&quot;)) {</b>
&nbsp; String myIP;
&nbsp; try {
<b class="nc">&nbsp; myIP = InterfaceResolver.resolveLocalInterface(ip);</b>
&nbsp; } catch (Exception e) {
<b class="nc">&nbsp; loginReader.printAbove(&quot;\033[H\033[2J&quot; + &quot;\033[31mERROR: Cannot resolve local interface: &quot; + e.getMessage() + &quot;\033[0m&quot;);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; System.setProperty(&quot;java.rmi.server.hostname&quot;, myIP);</b>
<b class="nc">&nbsp; client = new RMIClient(controller, ip, NetworkConfig.RMI_PORT, myIP);</b>
<b class="nc">&nbsp; ErrorType err = client.connect(username, nPlayers);</b>
<b class="nc">&nbsp; if (err != null) {</b>
<b class="nc">&nbsp; loginReader.printAbove(&quot;\033[H\033[2J&quot; + &quot;\033[31mERROR: &quot; + err + &quot;\033[0m&quot;);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; System.out.println(&quot;Connected via RMI, waiting for other players...&quot;);</b>
<b class="nc">&nbsp; controller.setClient(client);</b>
&nbsp;
<b class="nc">&nbsp; } else if (networkStr.equals(&quot;tcp&quot;)) {</b>
<b class="nc">&nbsp; client = new TCPClient(controller, ip, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT);</b>
<b class="nc">&nbsp; ErrorType err = client.connect(username, nPlayers);</b>
<b class="nc">&nbsp; if (err != null) {</b>
<b class="nc">&nbsp; loginReader.printAbove(&quot;\033[H\033[2J&quot; + &quot;\033[31mERROR: &quot; + err + &quot;\033[0m&quot;);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; System.out.println(&quot;Connected via TCP, waiting for other players...&quot;);</b>
<b class="nc">&nbsp; controller.setClient(client);</b>
&nbsp;
&nbsp; } else {
<b class="nc">&nbsp; loginReader.printAbove(&quot;\033[H\033[2J&quot; + &quot;\033[31mERROR: Unknown network type &#39;&quot; + networkStr + &quot;&#39;. Use rmi or tcp.\033[0m&quot;);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; return true;</b>
&nbsp; }
&nbsp; /**
&nbsp; * Builds the JLine {@link LineReader} used during gameplay, with tab-completion
&nbsp; * for all valid commands.
&nbsp; *
&nbsp; * @return the configured {@link LineReader}.
&nbsp; */
&nbsp; private LineReader buildGameReader() {
<b class="nc">&nbsp; Completer completer = new TreeCompleter(</b>
<b class="nc">&nbsp; node(&quot;slot&quot;),</b>
<b class="nc">&nbsp; node(&quot;draw&quot;,</b>
<b class="nc">&nbsp; node(&quot;upper&quot;,</b>
<b class="nc">&nbsp; node(&quot;tribe&quot;),</b>
<b class="nc">&nbsp; node(&quot;building&quot;)),</b>
<b class="nc">&nbsp; node(&quot;lower&quot;,</b>
<b class="nc">&nbsp; node(&quot;tribe&quot;),</b>
<b class="nc">&nbsp; node(&quot;building&quot;))),</b>
<b class="nc">&nbsp; node(&quot;totem&quot;),</b>
<b class="nc">&nbsp; node(&quot;skip&quot;),</b>
<b class="nc">&nbsp; node(&quot;clear&quot;),</b>
<b class="nc">&nbsp; node(&quot;details&quot;, node(&quot;buildings&quot;), node(&quot;events&quot;), node(&quot;characters&quot;)),</b>
<b class="nc">&nbsp; node(&quot;rematch&quot;),</b>
<b class="nc">&nbsp; node(&quot;quit&quot;)</b>
&nbsp; );
<b class="nc">&nbsp; return LineReaderBuilder.builder()</b>
<b class="nc">&nbsp; .terminal(terminal)</b>
<b class="nc">&nbsp; .completer(completer)</b>
<b class="nc">&nbsp; .option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)</b>
<b class="nc">&nbsp; .build();</b>
&nbsp; }
&nbsp; /**
&nbsp; * Returns the command-line prompt string showing the current username.
&nbsp; *
&nbsp; * @return the prompt string.
&nbsp; */
&nbsp; private String buildPrompt() {
<b class="nc">&nbsp; return currentUsername.isEmpty() ? &quot;&gt; &quot; : currentUsername + &quot;&gt; &quot;;</b>
&nbsp; }
&nbsp;
&nbsp; /** Returns {@code true} when the caller should break out of the game loop (rematch requested). */
&nbsp; private boolean handleCommand(String line, ClientController controller) {
<b class="nc">&nbsp; String[] parts = line.split(&quot;\\s+&quot;);</b>
<b class="nc">&nbsp; String cmd = parts[0].toLowerCase();</b>
&nbsp;
<b class="nc">&nbsp; switch (cmd) {</b>
&nbsp; case &quot;slot&quot; -&gt; {
<b class="nc">&nbsp; int pos = parsePos(parts, 1);</b>
<b class="nc">&nbsp; controller.slotChoice(pos);</b>
&nbsp; }
<b class="nc">&nbsp; case &quot;draw&quot; -&gt; handleDraw(parts, controller);</b>
&nbsp; case &quot;totem&quot; -&gt; {
<b class="nc">&nbsp; int pos = parsePos(parts, 1);</b>
<b class="nc">&nbsp; controller.totemChoice(pos);</b>
&nbsp; }
<b class="nc">&nbsp; case &quot;skip&quot; -&gt; controller.skipTurn();</b>
<b class="nc">&nbsp; case &quot;clear&quot; -&gt; handleRender();</b>
&nbsp; case &quot;details&quot; -&gt; {
<b class="nc">&nbsp; if (parts.length &gt; 1 &amp;&amp; parts[1].equalsIgnoreCase(&quot;buildings&quot;))</b>
<b class="nc">&nbsp; gameReader.printAbove(BUILDING_DETAILS);</b>
<b class="nc">&nbsp; else if (parts.length &gt; 1 &amp;&amp; parts[1].equalsIgnoreCase(&quot;events&quot;))</b>
<b class="nc">&nbsp; gameReader.printAbove(EVENT_DETAILS);</b>
<b class="nc">&nbsp; else if (parts.length &gt; 1 &amp;&amp; parts[1].equalsIgnoreCase(&quot;characters&quot;))</b>
<b class="nc">&nbsp; gameReader.printAbove(CHARACTER_DETAILS);</b>
&nbsp; else
<b class="nc">&nbsp; gameReader.printAbove(&quot;Usage: details buildings | details events | details characters&quot;);</b>
&nbsp; }
&nbsp; case &quot;rematch&quot; -&gt; {
<b class="nc">&nbsp; if (controller.getMiniModel() == null</b>
<b class="nc">&nbsp; || !controller.getMiniModel().currentState.getGameStage().equals(GameStages.ENDED)) {</b>
<b class="nc">&nbsp; System.out.println(&quot;Game not ended yet. Use &#39;quit&#39; to disconnect.&quot;);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; controller.disconnect();</b>
<b class="nc">&nbsp; controller.setClient(null);</b>
<b class="nc">&nbsp; controller.setModel(null);</b>
<b class="nc">&nbsp; return true;</b>
&nbsp; }
&nbsp; case &quot;quit&quot; -&gt; {
<b class="nc">&nbsp; safeQuit(controller);</b>
<b class="nc">&nbsp; System.exit(0);</b>
&nbsp; }
<b class="nc">&nbsp; default -&gt; view.showError(ErrorType.GENERIC_ERROR, &quot;Unknown command: &quot; + cmd);</b>
&nbsp; }
<b class="nc">&nbsp; return false;</b>
&nbsp; }
&nbsp; /**
&nbsp; * Handles the {@code draw} command, dispatching to the appropriate controller draw method.
&nbsp; *
&nbsp; * @param parts the tokenized command: {@code [draw, upper|lower, tribe|building, pos]}.
&nbsp; * @param controller the client controller to invoke.
&nbsp; */
&nbsp; private void handleDraw(String[] parts, ClientController controller) {
<b class="nc">&nbsp; if (parts.length &lt; 4) {</b>
<b class="nc">&nbsp; view.showError(ErrorType.GENERIC_ERROR, &quot;Usage: draw upper|lower tribe|building &lt;pos&gt;&quot;);</b>
&nbsp; return;
&nbsp; }
<b class="nc">&nbsp; boolean upper = parts[1].equalsIgnoreCase(&quot;upper&quot;);</b>
<b class="nc">&nbsp; boolean tribe = parts[2].equalsIgnoreCase(&quot;tribe&quot;);</b>
<b class="nc">&nbsp; int pos = parsePos(parts, 3);</b>
<b class="nc">&nbsp; if (pos &lt; 0) return;</b>
&nbsp;
<b class="nc">&nbsp; if (upper &amp;&amp; tribe) controller.drawUpperTribeCard(pos);</b>
<b class="nc">&nbsp; else if (upper) controller.drawUpperBuildingCard(pos);</b>
<b class="nc">&nbsp; else if (tribe) controller.drawLowerTribeCard(pos);</b>
<b class="nc">&nbsp; else controller.drawLowerBuildingCard(pos);</b>
&nbsp; }
&nbsp; /**
&nbsp; * Handles the {@code clear} command by triggering a full board re-render.
&nbsp; *
&nbsp; */
&nbsp; private void handleRender() {
<b class="nc">&nbsp; view.renderBoard();</b>
&nbsp; }
&nbsp; /**
&nbsp; * Parses an integer position argument at {@code idx} in {@code parts}.
&nbsp; *
&nbsp; * @param parts the tokenized command array.
&nbsp; * @param idx index of the position argument.
&nbsp; * @return the parsed integer, or {@code -1} if missing or malformed.
&nbsp; */
&nbsp; private int parsePos(String[] parts, int idx) {
<b class="nc">&nbsp; if (parts.length &lt;= idx) {</b>
<b class="nc">&nbsp; view.showError(ErrorType.GENERIC_ERROR, &quot;Missing position argument.&quot;);</b>
<b class="nc">&nbsp; return -1;</b>
&nbsp; }
&nbsp; try {
<b class="nc">&nbsp; return Integer.parseInt(parts[idx]);</b>
&nbsp; } catch (NumberFormatException e) {
<b class="nc">&nbsp; view.showError(ErrorType.GENERIC_ERROR,</b>
&nbsp; &quot;Invalid position &#39;&quot; + parts[idx] + &quot;&#39; — expected integer.&quot;);
<b class="nc">&nbsp; return -1;</b>
&nbsp; }
&nbsp; }
&nbsp; /**
&nbsp; * Disconnects the client if one is currently connected; safe to call when already disconnected.
&nbsp; *
&nbsp; * @param controller the client controller to disconnect.
&nbsp; */
&nbsp; private void safeQuit(ClientController controller) {
<b class="nc">&nbsp; if (controller.getClient() != null) controller.disconnect();</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>