Add: Added Coverage Screenshots And htlmReport.

This commit is contained in:
GabrieleRadice
2026-06-19 21:50:38 +02:00
parent 6aca188aa3
commit d78b8bbd93
316 changed files with 86329 additions and 0 deletions
@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > ClientLauncherGUI</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: ClientLauncherGUI (it.polimi.ingsw.gc14)</h1>
<table class="coverageStats">
<tr>
<th class="name">Class</th>
<th class="coverageStat
">
Method, %
</th>
<th class="coverageStat
">
Line, %
</th>
</tr>
<tr>
<td class="name">ClientLauncherGUI</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/2)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/4)
</span>
</td>
</tr>
<tr>
<td class="name">ClientLauncherGUI$GUIApp</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/2)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/5)
</span>
</td>
</tr>
<tr>
<td class="name"><strong>Total</strong></td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/4)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/9)
</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.View.GUI.GUI;
&nbsp;import javafx.application.Application;
&nbsp;import javafx.stage.Stage;
&nbsp;
&nbsp;/** Entry point for launching the game client with the JavaFX GUI. */
<b class="nc">&nbsp;public class ClientLauncherGUI {</b>
&nbsp;
&nbsp; /**
&nbsp; * Launches the GUI application.
&nbsp; *
&nbsp; * @param args command-line arguments passed to the JavaFX runtime.
&nbsp; */
&nbsp; public static void main(String[] args) {
<b class="nc">&nbsp; System.setProperty(&quot;glass.gtk.uiScale&quot;, &quot;1.0&quot;);</b>
<b class="nc">&nbsp; System.setProperty(&quot;glass.win.uiScale&quot;, &quot;1.0&quot;);</b>
<b class="nc">&nbsp; Application.launch(GUIApp.class, args);</b>
&nbsp; }
&nbsp; //TODO
<b class="nc">&nbsp; public static class GUIApp extends Application {</b>
&nbsp; @Override
&nbsp; public void start(Stage stage) throws Exception {
<b class="nc">&nbsp; GUI view = new GUI();</b>
<b class="nc">&nbsp; ClientController controller = new ClientController(view);</b>
<b class="nc">&nbsp; view.setController(controller);</b>
<b class="nc">&nbsp; view.start(stage);</b>
&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-14 21:53</div>
</div>
</body>
</html>
@@ -0,0 +1,436 @@
<!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/11)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/67)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/142)
</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.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; //TODO
&nbsp; private TUI view;
&nbsp; //TODO
&nbsp; private Terminal terminal;
&nbsp; //TODO
&nbsp; private LineReader gameReader;
&nbsp; //TODO
<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; //TODO
&nbsp; public 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; * @throws InterruptedException if the thread is interrupted while waiting.
&nbsp; */
&nbsp; private void start() throws InterruptedException {
&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(controller);</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; //TODO
&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;
&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; System.out.println(&quot;Invalid number of players.&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;
<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; System.out.println(&quot;Cannot resolve local interface: &quot; + e.getMessage());</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; RMIClient 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; terminal.writer().print(&quot;\033[H\033[2J&quot;);</b>
<b class="nc">&nbsp; terminal.writer().flush();</b>
<b class="nc">&nbsp; System.out.println(&quot;Errore: &quot; + err);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; System.out.println(&quot;Connected via RMI.&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; TCPClient 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; terminal.writer().print(&quot;\033[H\033[2J&quot;);</b>
<b class="nc">&nbsp; terminal.writer().flush();</b>
<b class="nc">&nbsp; System.out.println(&quot;Errore: &quot; + err);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; System.out.println(&quot;Connected via TCP.&quot;);</b>
<b class="nc">&nbsp; controller.setClient(client);</b>
&nbsp;
&nbsp; } else {
<b class="nc">&nbsp; System.out.println(&quot;Unknown network type &#39;&quot; + networkStr + &quot;&#39;. Use rmi or tcp.&quot;);</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
<b class="nc">&nbsp; return true;</b>
&nbsp; }
&nbsp; //TODO
&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;render&quot;),</b>
<b class="nc">&nbsp; node(&quot;help&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; //TODO
&nbsp; private String buildPrompt(ClientController controller) {
<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; if (pos &gt;= 0) 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; if (pos &gt;= 0) controller.totemChoice(pos);</b>
&nbsp; }
<b class="nc">&nbsp; case &quot;skip&quot; -&gt; controller.skipTurn();</b>
<b class="nc">&nbsp; case &quot;render&quot; -&gt; handleRender(parts);</b>
<b class="nc">&nbsp; case &quot;help&quot; -&gt; gameReader.printAbove(&quot;Commands: slot, draw, totem, skip, render, help, details, rematch, quit&quot;);</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; //TODO
&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; //TODO
&nbsp; private void handleRender(String[] parts) {
<b class="nc">&nbsp; view.renderBoard();</b>
&nbsp; }
&nbsp; //TODO
&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; //TODO
&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-14 21:53</div>
</div>
</body>
</html>
@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > ErrorType</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: ErrorType (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
">
Line, %
</th>
</tr>
<tr>
<td class="name">ErrorType</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/4)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/12)
</span>
</td>
</tr>
</table>
<br/>
<br/>
<pre>
<code class="sourceCode" id="sourceCode">&nbsp;package it.polimi.ingsw.gc14;
&nbsp;
&nbsp;/** Enumeration of error types that can be returned by server-side operations. */
<b class="nc">&nbsp;public enum ErrorType {</b>
<b class="nc">&nbsp; USER_NOT_FOUND(&quot;User not found&quot;),</b>
<b class="nc">&nbsp; USERNAME_ALREADY_USED(&quot;Username is already in use&quot;),</b>
<b class="nc">&nbsp; USER_ALREADY_CONNECTED(&quot;User is already connected&quot;),</b>
<b class="nc">&nbsp; WRONG_PLAYER_NUMBER(&quot;Wrong player number&quot;),</b>
<b class="nc">&nbsp; SERVER_CRASHED(&quot;Server crashed&quot;),</b>
<b class="nc">&nbsp; GENERIC_ERROR(&quot;Generic error&quot;),</b>
<b class="nc">&nbsp; GAME_ALREADY_STARTED(&quot;Game already started&quot;),</b>
<b class="nc">&nbsp; WRONG_ACTION(&quot;Wrong action&quot;);</b>
&nbsp; //TODO
&nbsp; private final String description;
<b class="nc">&nbsp; ErrorType(String description) {</b>
<b class="nc">&nbsp; this.description = description;</b>
&nbsp; }
&nbsp; @Override
&nbsp; public String toString() {
<b class="nc">&nbsp; return description;</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-14 21:53</div>
</div>
</body>
</html>
@@ -0,0 +1,523 @@
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > GameEventProcessor</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: GameEventProcessor (it.polimi.ingsw.gc14)</h1>
<table class="coverageStats">
<tr>
<th class="name">Class</th>
<th class="coverageStat
">
Method, %
</th>
<th class="coverageStat
">
Branch, %
</th>
<th class="coverageStat
">
Line, %
</th>
</tr>
<tr>
<td class="name">GameEventProcessor</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/19)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/48)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/107)
</span>
</td>
</tr>
<tr>
<td class="name">GameEventProcessor$1</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/1)
</span>
</td>
</tr>
<tr>
<td class="name"><strong>Total</strong></td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/20)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/48)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/108)
</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.GameController;
&nbsp;import it.polimi.ingsw.gc14.Model.Game;
&nbsp;import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
&nbsp;import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster;
&nbsp;import it.polimi.ingsw.gc14.Network.EventType;
&nbsp;import it.polimi.ingsw.gc14.Network.NetworkEvent;
&nbsp;import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
&nbsp;
&nbsp;import java.util.ArrayList;
&nbsp;import java.util.List;
&nbsp;import java.util.Map;
&nbsp;import java.util.concurrent.*;
&nbsp;import java.util.stream.Collectors;
&nbsp;
&nbsp;/**
&nbsp; * Processes game events from the action queue and routes each one
&nbsp; * based on the current game state.
&nbsp; *
&nbsp; * &lt;p&gt;The three top-level states that determine routing are:
&nbsp; * &lt;ul&gt;
&nbsp; * &lt;li&gt;&lt;b&gt;Inactive&lt;/b&gt; no game model is present, or the game has ended.
&nbsp; * Only disconnection clean-up is performed.&lt;/li&gt;
&nbsp; * &lt;li&gt;&lt;b&gt;Suspended&lt;/b&gt; a forfeit timer is running because exactly one
&nbsp; * player remains online. Only reconnection events are accepted.&lt;/li&gt;
&nbsp; * &lt;li&gt;&lt;b&gt;Active&lt;/b&gt; normal gameplay; every event is applied, saved,
&nbsp; * and broadcast.&lt;/li&gt;
&nbsp; * &lt;/ul&gt;
&nbsp; *
&nbsp; * &lt;p&gt;This class is not thread-safe by itself: it relies on the caller
&nbsp; * (the game loop in {@code ServerLauncher}) to drive it from a single
&nbsp; * thread via {@link #doFirstEvent()}.
&nbsp; */
&nbsp;public class GameEventProcessor {
&nbsp;
&nbsp; private final BlockingQueue&lt;NetworkEvent&gt; actionQueue;
&nbsp; private final GameController gameController;
&nbsp; private final LimitedMap&lt;String, Boolean&gt; playerList;
&nbsp; private final CompositeClientBroadcaster broadcaster;
&nbsp; private final SaveManager saveManager;
&nbsp;
&nbsp; /** Single-thread executor used exclusively for the forfeit timer. Daemon so it does not block JVM shutdown. */
<b class="nc">&nbsp; private final ScheduledExecutorService timerExecutor =</b>
<b class="nc">&nbsp; Executors.newSingleThreadScheduledExecutor(r -&gt; {</b>
<b class="nc">&nbsp; Thread t = new Thread(r, &quot;forfeit-timer&quot;);</b>
<b class="nc">&nbsp; t.setDaemon(true);</b>
<b class="nc">&nbsp; return t;</b>
&nbsp; });
&nbsp;
&nbsp; /**
&nbsp; * Handle to the running forfeit timer, or {@code null} when no timer is active.
&nbsp; * A non-null value signals that the game is in the {@em suspended} state.
&nbsp; */
&nbsp; private ScheduledFuture&lt;?&gt; disconnectionTimer;
&nbsp;
&nbsp; /**
&nbsp; * Constructs a {@code GameEventProcessor} with all required dependencies.
&nbsp; *
&nbsp; * @param actionQueue the queue from which incoming events are consumed.
&nbsp; * @param gameController the server-side game controller.
&nbsp; * @param playerList the shared map tracking each player&#39;s online status.
&nbsp; * @param broadcaster the broadcaster used to notify all connected clients.
&nbsp; * @param saveManager the save manager used to persist the game state.
&nbsp; */
&nbsp; public GameEventProcessor(
&nbsp; BlockingQueue&lt;NetworkEvent&gt; actionQueue,
&nbsp; GameController gameController,
&nbsp; LimitedMap&lt;String, Boolean&gt; playerList,
&nbsp; CompositeClientBroadcaster broadcaster,
<b class="nc">&nbsp; SaveManager saveManager) {</b>
<b class="nc">&nbsp; this.actionQueue = actionQueue;</b>
<b class="nc">&nbsp; this.gameController = gameController;</b>
<b class="nc">&nbsp; this.playerList = playerList;</b>
<b class="nc">&nbsp; this.broadcaster = broadcaster;</b>
<b class="nc">&nbsp; this.saveManager = saveManager;</b>
&nbsp; }
&nbsp;
&nbsp; // ── Public entry point ────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Blocks until one event is available in the queue, then routes it to the
&nbsp; * appropriate handler based on the current game state.
&nbsp; *
&nbsp; * @throws InterruptedException if the thread is interrupted while waiting
&nbsp; * for the next event.
&nbsp; */
&nbsp; public void doFirstEvent() throws InterruptedException {
<b class="nc">&nbsp; NetworkEvent event = actionQueue.take();</b>
&nbsp;
<b class="nc">&nbsp; if (!isGameActive()) {</b>
<b class="nc">&nbsp; handleInactiveGame(event);</b>
<b class="nc">&nbsp; } else if (isSuspended()) {</b>
<b class="nc">&nbsp; handleSuspendedGame(event);</b>
&nbsp; } else {
<b class="nc">&nbsp; applyAndBroadcast(event);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; // ── State guards ──────────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Returns {@code true} when there is an ongoing game that has not yet ended.
&nbsp; */
&nbsp; private boolean isGameActive() {
<b class="nc">&nbsp; Game model = gameController.getModel();</b>
<b class="nc">&nbsp; return model != null</b>
<b class="nc">&nbsp; &amp;&amp; model.getCurrentState().getGameStage() != GameStages.ENDED;</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Returns {@code true} when the forfeit timer is running, meaning only one
&nbsp; * player is currently online and the game is waiting for a reconnection.
&nbsp; */
&nbsp; private boolean isSuspended() {
<b class="nc">&nbsp; return disconnectionTimer != null;</b>
&nbsp; }
&nbsp;
&nbsp; // ── Inactive game path ────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Handles events that arrive when no active game exists (not yet started,
&nbsp; * or already ended). Only disconnection clean-up is relevant here.
&nbsp; *
&nbsp; * @param event the incoming event.
&nbsp; */
&nbsp; private void handleInactiveGame(NetworkEvent event) {
<b class="nc">&nbsp; if (event.getEventType() != EventType.DISCONNECTED_PLAYER) return;</b>
&nbsp;
<b class="nc">&nbsp; synchronized (gameController) {</b>
<b class="nc">&nbsp; playerList.remove(event.getUsername());</b>
<b class="nc">&nbsp; if (playerList.isEmpty()) {</b>
<b class="nc">&nbsp; gameController.setModel(null);</b>
<b class="nc">&nbsp; System.out.println(&quot;\n!!! Player list is now empty, ready for a new game init !!!\n&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; }</b>
&nbsp; }
&nbsp;
&nbsp; // ── Suspended game path ───────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Routes events while the game is suspended waiting for a reconnection.
&nbsp; * &lt;ul&gt;
&nbsp; * &lt;li&gt;A second disconnection while suspended means no player remains
&nbsp; * online: the game is aborted entirely.&lt;/li&gt;
&nbsp; * &lt;li&gt;Any non-reconnection event is rejected with an error.&lt;/li&gt;
&nbsp; * &lt;li&gt;A reconnection event is allowed through to {@link #applyAndBroadcast}.&lt;/li&gt;
&nbsp; * &lt;/ul&gt;
&nbsp; *
&nbsp; * @param event the incoming event.
&nbsp; */
&nbsp; private void handleSuspendedGame(NetworkEvent event) {
<b class="nc">&nbsp; switch (event.getEventType()) {</b>
<b class="nc">&nbsp; case DISCONNECTED_PLAYER -&gt; abortGame();</b>
<b class="nc">&nbsp; case RECONNECT_PLAYER -&gt; applyAndBroadcast(event);</b>
<b class="nc">&nbsp; default -&gt; rejectEvent(event);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Cancels the forfeit timer, clears the player list, and resets the model.
&nbsp; * Called when the last remaining player disconnects while the game is suspended.
&nbsp; */
&nbsp; private void abortGame() {
<b class="nc">&nbsp; disconnectionTimer.cancel(true);</b>
<b class="nc">&nbsp; disconnectionTimer = null;</b>
<b class="nc">&nbsp; playerList.clear();</b>
<b class="nc">&nbsp; gameController.setModel(null);</b>
<b class="nc">&nbsp; System.out.println(&quot;\n!!! All players disconnected — game aborted, ready for a new game init !!!\n&quot;);</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Marks the event as an error and broadcasts it back to the requesting
&nbsp; * player. Used to reject actions that are not permitted in the current state.
&nbsp; *
&nbsp; * @param event the event to reject.
&nbsp; */
&nbsp; private void rejectEvent(NetworkEvent event) {
<b class="nc">&nbsp; event.setIsError(true);</b>
<b class="nc">&nbsp; broadcaster.notifyAll(event);</b>
&nbsp; }
&nbsp;
&nbsp; // ── Active game path ──────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Applies the event to the game controller, saves the updated state,
&nbsp; * and broadcasts the result to connected clients.
&nbsp; *
&nbsp; * &lt;p&gt;The entire method body is synchronized on {@code gameController} to
&nbsp; * prevent concurrent modification of the game model by the network threads.
&nbsp; *
&nbsp; * @param event the event to apply.
&nbsp; */
&nbsp; private void applyAndBroadcast(NetworkEvent event) {
<b class="nc">&nbsp; synchronized (gameController) {</b>
<b class="nc">&nbsp; int roundBefore = gameController.getModel().getCurrentState().getRound();</b>
&nbsp;
<b class="nc">&nbsp; event.setIsError(!event.apply(gameController));</b>
<b class="nc">&nbsp; Game game = gameController.getModel();</b>
&nbsp;
<b class="nc">&nbsp; if (event.isError()) {</b>
<b class="nc">&nbsp; broadcaster.notifyAll(event);</b>
<b class="nc">&nbsp; return;</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; cancelForfeitTimerIfReconnect(event);</b>
&nbsp;
<b class="nc">&nbsp; if (!saveManager.save(game)) {</b>
<b class="nc">&nbsp; System.out.println(&quot;\n!!! Save failed !!!\n&quot;);</b>
&nbsp; }
&nbsp;
&nbsp; // During the lobby phase a disconnection only removes the player
&nbsp; // from the list; no broadcast is needed.
<b class="nc">&nbsp; if (event.getEventType() == EventType.DISCONNECTED_PLAYER</b>
<b class="nc">&nbsp; &amp;&amp; game.getCurrentState().getGameStage() == GameStages.WAITING) {</b>
<b class="nc">&nbsp; playerList.remove(event.getUsername());</b>
<b class="nc">&nbsp; return;</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; enrichEvent(event, game);</b>
<b class="nc">&nbsp; startForfeitTimerIfNeeded(event, game);</b>
<b class="nc">&nbsp; broadcastResult(event, game, roundBefore);</b>
<b class="nc">&nbsp; }</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Cancels the forfeit timer if the event is a successful reconnection.
&nbsp; *
&nbsp; * @param event the event that was just successfully applied.
&nbsp; */
&nbsp; private void cancelForfeitTimerIfReconnect(NetworkEvent event) {
<b class="nc">&nbsp; if (event.getEventType() == EventType.RECONNECT_PLAYER</b>
&nbsp; &amp;&amp; disconnectionTimer != null
<b class="nc">&nbsp; &amp;&amp; !disconnectionTimer.isDone()) {</b>
<b class="nc">&nbsp; disconnectionTimer.cancel(false);</b>
<b class="nc">&nbsp; disconnectionTimer = null;</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Populates the event with the current game state so that clients can
&nbsp; * update their mini-model after receiving it.
&nbsp; *
&nbsp; * &lt;p&gt;Each event subclass overrides {@link NetworkEvent#enrichWithGameState}
&nbsp; * to append any type-specific extra fields (available totems, etc.).
&nbsp; *
&nbsp; * @param event the event to enrich.
&nbsp; * @param game the current game model.
&nbsp; */
&nbsp; private void enrichEvent(NetworkEvent event, Game game) {
<b class="nc">&nbsp; event.enrichWithGameState(game, buildDisconnectedList(game));</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Schedules the 60-second forfeit timer if a disconnection has left
&nbsp; * exactly one player online.
&nbsp; *
&nbsp; * &lt;p&gt;If a previous timer is still pending it is cancelled first to avoid
&nbsp; * duplicate timers.
&nbsp; *
&nbsp; * @param event the event that was just applied.
&nbsp; * @param game the current game model.
&nbsp; */
&nbsp; private void startForfeitTimerIfNeeded(NetworkEvent event, Game game) {
<b class="nc">&nbsp; if (event.getEventType() != EventType.DISCONNECTED_PLAYER) return;</b>
<b class="nc">&nbsp; if (game.getCurrentState().getGameStage() == GameStages.ENDED) return;</b>
<b class="nc">&nbsp; if (onlinePlayerCount() != 1) return;</b>
&nbsp;
<b class="nc">&nbsp; if (disconnectionTimer != null &amp;&amp; !disconnectionTimer.isDone()) {</b>
<b class="nc">&nbsp; disconnectionTimer.cancel(false);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; disconnectionTimer = timerExecutor.schedule(</b>
<b class="nc">&nbsp; () -&gt; endGameForfeit(game),</b>
&nbsp; 1, TimeUnit.MINUTES
&nbsp; );
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Ends the game by forfeit when the timer expires without a reconnection.
&nbsp; * Broadcasts an {@link EndedGame} event, deletes the save, and resets state.
&nbsp; *
&nbsp; * @param game the game model captured when the timer was scheduled.
&nbsp; */
&nbsp; private void endGameForfeit(Game game) {
<b class="nc">&nbsp; synchronized (gameController) {</b>
<b class="nc">&nbsp; gameController.endGameForfeit();</b>
<b class="nc">&nbsp; EndedGame forfeitEnd = new EndedGame(</b>
<b class="nc">&nbsp; game.getSlotMap(), game.getOrderLogicCard(),</b>
<b class="nc">&nbsp; game.getCurrentState(), game.getPlayerStanding()</b>
&nbsp; );
<b class="nc">&nbsp; broadcaster.notifyAll(forfeitEnd);</b>
<b class="nc">&nbsp; System.out.println(&quot;Timer expired: no player reconnected in 60 s.&quot;);</b>
<b class="nc">&nbsp; removeOfflinePlayers();</b>
<b class="nc">&nbsp; if (!saveManager.delete()) {</b>
<b class="nc">&nbsp; System.out.println(&quot;\n!!! Couldn&#39;t delete save !!!\n&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; disconnectionTimer = null;</b>
<b class="nc">&nbsp; }</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Determines what to broadcast after a successful event application:
&nbsp; * &lt;ul&gt;
&nbsp; * &lt;li&gt;If the round advanced, an {@link ApplyNextRound} event (with updated
&nbsp; * card lists) replaces the original event.&lt;/li&gt;
&nbsp; * &lt;li&gt;If the game has ended, an {@link EndedGame} event is sent and the
&nbsp; * save file is deleted.&lt;/li&gt;
&nbsp; * &lt;li&gt;Otherwise the original event is broadcast as-is.&lt;/li&gt;
&nbsp; * &lt;/ul&gt;
&nbsp; *
&nbsp; * @param event the event that was applied.
&nbsp; * @param game the current game model (post-apply).
&nbsp; * @param roundBefore the round number before the event was applied.
&nbsp; */
&nbsp; private void broadcastResult(NetworkEvent event, Game game, int roundBefore) {
<b class="nc">&nbsp; broadcaster.notifyAll(event);</b>
<b class="nc">&nbsp; if (game.getCurrentState().getRound() != roundBefore) {</b>
<b class="nc">&nbsp; ApplyNextRound nextRound = new ApplyNextRound(</b>
<b class="nc">&nbsp; game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),</b>
<b class="nc">&nbsp; game.getPlayers(),</b>
<b class="nc">&nbsp; game.getUpperListTribeCards(), game.getLowerListTribeCards(),</b>
<b class="nc">&nbsp; game.getUpperListBuilding(), game.getLowerListBuilding()</b>
&nbsp; );
<b class="nc">&nbsp; broadcaster.notifyAll(nextRound);</b>
<b class="nc">&nbsp; } else if (game.getCurrentState().getGameStage() == GameStages.ENDED) {</b>
<b class="nc">&nbsp; EndedGame endedGame = new EndedGame(</b>
<b class="nc">&nbsp; game.getSlotMap(), game.getOrderLogicCard(),</b>
<b class="nc">&nbsp; game.getCurrentState(), game.getPlayerStanding()</b>
&nbsp; );
<b class="nc">&nbsp; broadcaster.notifyAll(endedGame);</b>
<b class="nc">&nbsp; if (!saveManager.delete()) {</b>
<b class="nc">&nbsp; System.out.println(&quot;\n!!! Couldn&#39;t delete save !!!\n&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; removeOfflinePlayers();</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; // ── Utilities ─────────────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Returns the number of players currently marked as online in the player list.
&nbsp; */
&nbsp; private long onlinePlayerCount() {
<b class="nc">&nbsp; return playerList.values().stream().filter(v -&gt; v).count();</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Builds the list of usernames of players currently marked as disconnected
&nbsp; * in the game model.
&nbsp; *
&nbsp; * @param game the current game model.
&nbsp; * @return a new {@link ArrayList} of disconnected usernames.
&nbsp; */
&nbsp; private ArrayList&lt;String&gt; buildDisconnectedList(Game game) {
<b class="nc">&nbsp; return game.getDisconnectedPlayers().entrySet().stream()</b>
<b class="nc">&nbsp; .filter(Map.Entry::getValue)</b>
<b class="nc">&nbsp; .map(e -&gt; e.getKey().getUserName())</b>
<b class="nc">&nbsp; .collect(Collectors.toCollection(ArrayList::new));</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Removes all offline entries (value {@code false}) from the player list.
&nbsp; * Online players remain until they disconnect naturally.
&nbsp; */
&nbsp; private void removeOfflinePlayers() {
<b class="nc">&nbsp; List&lt;String&gt; toRemove = playerList.entrySet().stream()</b>
<b class="nc">&nbsp; .filter(e -&gt; !e.getValue())</b>
<b class="nc">&nbsp; .map(Map.Entry::getKey)</b>
<b class="nc">&nbsp; .toList();</b>
<b class="nc">&nbsp; toRemove.forEach(playerList::remove);</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-14 21:53</div>
</div>
</body>
</html>
@@ -0,0 +1,236 @@
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > LimitedMap</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: LimitedMap (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">LimitedMap</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/10)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/28)
</span>
</td>
</tr>
</table>
<br/>
<br/>
<pre>
<code class="sourceCode" id="sourceCode">&nbsp;package it.polimi.ingsw.gc14;
&nbsp;
&nbsp;import java.util.Collection;
&nbsp;import java.util.Map;
&nbsp;import java.util.Set;
&nbsp;import java.util.concurrent.ConcurrentHashMap;
&nbsp;
&nbsp;/**
&nbsp; * A {@link ConcurrentHashMap}-backed map with a configurable size limit and an associated action.
&nbsp; * When the number of elements reaches or exceeds the limit, the specified action is automatically triggered.
&nbsp; * This implementation is thread-safe for {@code put}; callers that need compound operations must synchronize externally.
&nbsp; *
&nbsp; * @param &lt;K&gt; the type of keys maintained by this map.
&nbsp; * @param &lt;V&gt; the type of mapped values.
&nbsp; */
&nbsp;public class LimitedMap&lt;K, V&gt; implements Map&lt;K, V&gt; {
&nbsp;
<b class="nc">&nbsp; private final ConcurrentHashMap&lt;K, V&gt; map = new ConcurrentHashMap&lt;&gt;();</b>
&nbsp;
&nbsp; /**
&nbsp; * The maximum number of elements allowed in the map before the action is triggered.
&nbsp; */
&nbsp; private volatile int limit;
&nbsp;
&nbsp; /**
&nbsp; * The action to execute when the map size reaches or exceeds the limit.
&nbsp; */
&nbsp; private volatile Runnable action;
&nbsp;
&nbsp; /**
&nbsp; * Creates a new {@code LimitedMap} with the specified limit and action.
&nbsp; *
&nbsp; * @param limit the maximum number of elements before the action is triggered.
&nbsp; * @param action the action to execute when the limit is reached.
&nbsp; */
<b class="nc">&nbsp; public LimitedMap(int limit, Runnable action) {</b>
<b class="nc">&nbsp; this.limit = limit;</b>
<b class="nc">&nbsp; this.action = action;</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Associates the specified value with the specified key in this map.
&nbsp; * If the map size reaches or exceeds the limit after the insertion, the configured action is triggered.
&nbsp; *
&nbsp; * @param key the key with which the specified value is to be associated.
&nbsp; * @param value the value to be associated with the specified key.
&nbsp; * @return the previous value associated with the key, or {@code null} if there was no mapping.
&nbsp; */
&nbsp; @Override
&nbsp; public synchronized V put(K key, V value) {
<b class="nc">&nbsp; boolean added = true;</b>
<b class="nc">&nbsp; if (map.size() == limit) {</b>
<b class="nc">&nbsp; if (!map.containsKey(key))</b>
<b class="nc">&nbsp; return null;</b>
<b class="nc">&nbsp; added = false;</b>
&nbsp; }
<b class="nc">&nbsp; V result = map.put(key, value);</b>
<b class="nc">&nbsp; if (map.size() &gt;= limit &amp;&amp; added) {</b>
<b class="nc">&nbsp; action.run();</b>
&nbsp; }
<b class="nc">&nbsp; return result;</b>
&nbsp; }
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public synchronized V remove(Object key) { return map.remove(key); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public V get(Object key) { return map.get(key); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public boolean containsKey(Object key) { return map.containsKey(key); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public boolean containsValue(Object value) { return map.containsValue(value); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public int size() { return map.size(); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public boolean isEmpty() { return map.isEmpty(); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public void putAll(Map&lt;? extends K, ? extends V&gt; m) { m.forEach(this::put); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public void clear() { map.clear(); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public Set&lt;K&gt; keySet() { return map.keySet(); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public Collection&lt;V&gt; values() { return map.values(); }</b>
&nbsp;
&nbsp; @Override
<b class="nc">&nbsp; public Set&lt;Entry&lt;K, V&gt;&gt; entrySet() { return map.entrySet(); }</b>
&nbsp;
&nbsp; /**
&nbsp; * Sets a new size limit for this map.
&nbsp; *
&nbsp; * @param num the new limit.
&nbsp; */
<b class="nc">&nbsp; public void setLimit(int num) { this.limit = num; }</b>
&nbsp;
&nbsp; /**
&nbsp; * Returns the current size limit of this map.
&nbsp; *
&nbsp; * @return the current limit.
&nbsp; */
<b class="nc">&nbsp; public int getLimit() { return limit; }</b>
&nbsp;
&nbsp; /**
&nbsp; * Sets a new action to execute when the map size reaches or exceeds the limit.
&nbsp; *
&nbsp; * @param action the new action to set; must not be {@code null}.
&nbsp; */
&nbsp; public void setAction(Runnable action) {
<b class="nc">&nbsp; if (action == null) throw new IllegalArgumentException(&quot;action must not be null&quot;);</b>
<b class="nc">&nbsp; this.action = action;</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-14 21:53</div>
</div>
</body>
</html>
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > SaveManager</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: SaveManager (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
">
Line, %
</th>
</tr>
<tr>
<td class="name">SaveManager</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/4)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/23)
</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.Model.Game;
&nbsp;
&nbsp;import java.io.BufferedInputStream;
&nbsp;import java.io.BufferedOutputStream;
&nbsp;import java.io.FileInputStream;
&nbsp;import java.io.FileNotFoundException;
&nbsp;import java.io.FileOutputStream;
&nbsp;import java.io.IOException;
&nbsp;import java.io.ObjectInputStream;
&nbsp;import java.io.ObjectOutputStream;
&nbsp;import java.net.URISyntaxException;
&nbsp;import java.nio.file.Files;
&nbsp;import java.nio.file.Path;
&nbsp;import java.nio.file.Paths;
&nbsp;
&nbsp;/**
&nbsp; * Handles persistence of the game model to and from disk.
&nbsp; *
&nbsp; * &lt;p&gt;The save file is stored at {@code GameSaves/save.dat} relative to the
&nbsp; * directory containing the running JAR. The anchor class passed to the
&nbsp; * constructor is used to resolve that directory at construction time.
&nbsp; *
&nbsp; * &lt;p&gt;All three operations ({@link #save}, {@link #load}, {@link #delete})
&nbsp; * are independent and safe to call in any order; missing files are treated
&nbsp; * as a normal condition (no save present) rather than an error.
&nbsp; */
&nbsp;public class SaveManager {
&nbsp;
&nbsp; /** Relative sub-path of the save file inside the JAR directory. */
&nbsp; private static final String SAVE_RELATIVE_PATH = &quot;GameSaves/save.dat&quot;;
&nbsp;
&nbsp; /** Absolute path of the save file, resolved once at construction. */
&nbsp; private final Path filePath;
&nbsp;
&nbsp; /**
&nbsp; * Constructs a {@code SaveManager} whose save file is located relative
&nbsp; * to the JAR directory of the given anchor class.
&nbsp; *
&nbsp; * @param anchorClass the class whose code-source location is used as
&nbsp; * the base directory for the save file.
&nbsp; * @throws RuntimeException if the JAR path cannot be resolved.
&nbsp; */
<b class="nc">&nbsp; public SaveManager(Class&lt;?&gt; anchorClass) {</b>
&nbsp; try {
<b class="nc">&nbsp; Path jarDir = Paths.get(</b>
<b class="nc">&nbsp; anchorClass.getProtectionDomain().getCodeSource().getLocation().toURI()</b>
<b class="nc">&nbsp; ).getParent();</b>
<b class="nc">&nbsp; this.filePath = jarDir.resolve(SAVE_RELATIVE_PATH);</b>
&nbsp; } catch (URISyntaxException e) {
<b class="nc">&nbsp; throw new RuntimeException(&quot;Could not resolve save file path&quot;, e);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Serializes the game model to disk, creating parent directories if needed.
&nbsp; *
&nbsp; * @param game the game model to persist.
&nbsp; * @return {@code true} if the save succeeded, {@code false} on I/O error.
&nbsp; */
&nbsp; public boolean save(Game game) {
&nbsp; try {
<b class="nc">&nbsp; Files.createDirectories(filePath.getParent());</b>
<b class="nc">&nbsp; try (ObjectOutputStream oos = new ObjectOutputStream(</b>
<b class="nc">&nbsp; new BufferedOutputStream(new FileOutputStream(filePath.toFile())))) {</b>
<b class="nc">&nbsp; oos.writeObject(game);</b>
<b class="nc">&nbsp; System.out.println(&quot;Game saved to: &quot; + filePath.toAbsolutePath());</b>
<b class="nc">&nbsp; return true;</b>
&nbsp; }
&nbsp; } catch (IOException e) {
<b class="nc">&nbsp; e.printStackTrace();</b>
<b class="nc">&nbsp; return false;</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Deserializes the game model from disk.
&nbsp; *
&nbsp; * @return the saved {@link Game} instance, or {@code null} if no save file
&nbsp; * exists or an I/O error prevents reading.
&nbsp; * @throws RuntimeException if the serialized class cannot be found on
&nbsp; * the classpath (indicates a deployment mismatch).
&nbsp; */
&nbsp; public Game load() {
<b class="nc">&nbsp; try (ObjectInputStream ois = new ObjectInputStream(</b>
<b class="nc">&nbsp; new BufferedInputStream(new FileInputStream(filePath.toFile())))) {</b>
<b class="nc">&nbsp; return (Game) ois.readObject();</b>
&nbsp; } catch (FileNotFoundException e) {
<b class="nc">&nbsp; return null;</b>
&nbsp; } catch (IOException e) {
<b class="nc">&nbsp; e.printStackTrace();</b>
<b class="nc">&nbsp; return null;</b>
&nbsp; } catch (ClassNotFoundException e) {
<b class="nc">&nbsp; throw new RuntimeException(&quot;Save file references an unknown class&quot;, e);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Deletes the save file.
&nbsp; *
&nbsp; * @return {@code true} if the file was deleted, {@code false} otherwise
&nbsp; * (including when the file did not exist).
&nbsp; */
&nbsp; public boolean delete() {
&nbsp; try {
<b class="nc">&nbsp; return Files.deleteIfExists(filePath);</b>
&nbsp; } catch (IOException e) {
<b class="nc">&nbsp; return false;</b>
&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-14 21:53</div>
</div>
</body>
</html>
@@ -0,0 +1,340 @@
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > ServerLauncher</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: ServerLauncher (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">ServerLauncher</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/8)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/24)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/72)
</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.GameController;
&nbsp;import it.polimi.ingsw.gc14.Model.Game;
&nbsp;import it.polimi.ingsw.gc14.Model.MiniModel;
&nbsp;import it.polimi.ingsw.gc14.Model.Player;
&nbsp;import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster;
&nbsp;import it.polimi.ingsw.gc14.Network.NetworkEvent;
&nbsp;import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
&nbsp;import it.polimi.ingsw.gc14.Network.NetworkConfig;
&nbsp;import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
&nbsp;
&nbsp;import java.net.*;
&nbsp;import java.rmi.RemoteException;
&nbsp;import java.util.*;
&nbsp;import java.util.concurrent.*;
&nbsp;import java.util.stream.Collectors;
&nbsp;
&nbsp;/**
&nbsp; * Main server entry point.
&nbsp; *
&nbsp; * &lt;p&gt;Responsibilities of this class are intentionally limited to wiring:
&nbsp; * it creates all components, connects them together, restores a previously
&nbsp; * saved game if one exists, and starts the event-processing loop and the
&nbsp; * network servers.
&nbsp; *
&nbsp; * &lt;p&gt;All game-logic and event-routing decisions are delegated to
&nbsp; * {@link GameEventProcessor}; persistence is delegated to {@link SaveManager}.
&nbsp; *
&nbsp; * &lt;p&gt;The original {@code ServerLauncher} class is preserved and untouched.
&nbsp; * This class is a clean replacement that uses the new component structure.
&nbsp; */
&nbsp;public class ServerLauncher {
&nbsp;
&nbsp; /** Drives the main game-event loop. */
&nbsp; private final GameEventProcessor eventProcessor;
&nbsp;
&nbsp; /**
&nbsp; * Constructs a {@code ServerLauncherTest} and wires all components together.
&nbsp; *
&nbsp; * @param actionQueue the shared event queue.
&nbsp; * @param gameController the server-side game controller.
&nbsp; * @param playerList the shared player-status map.
&nbsp; * @param broadcaster the broadcaster used to reach all connected clients.
&nbsp; * @param saveManager the save manager used to persist game state.
&nbsp; */
&nbsp; public ServerLauncher(
&nbsp; BlockingQueue&lt;NetworkEvent&gt; actionQueue,
&nbsp; GameController gameController,
&nbsp; LimitedMap&lt;String, Boolean&gt; playerList,
&nbsp; CompositeClientBroadcaster broadcaster,
<b class="nc">&nbsp; SaveManager saveManager) {</b>
<b class="nc">&nbsp; this.eventProcessor = new GameEventProcessor(</b>
&nbsp; actionQueue, gameController, playerList, broadcaster, saveManager);
&nbsp; }
&nbsp;
&nbsp; // ── Entry point ───────────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Initialises all server components, wires them together, restores a saved
&nbsp; * game if available, and starts the event-processing loop and both network
&nbsp; * servers.
&nbsp; *
&nbsp; * @param args command-line arguments (unused).
&nbsp; * @throws RemoteException if the RMI server cannot be created.
&nbsp; */
&nbsp; public static void main(String[] args) throws RemoteException {
<b class="nc">&nbsp; LimitedMap&lt;String, Boolean&gt; playerList = new LimitedMap&lt;&gt;(5, () -&gt; {});</b>
<b class="nc">&nbsp; BlockingQueue&lt;NetworkEvent&gt; actionQueue = new LinkedBlockingQueue&lt;&gt;();</b>
<b class="nc">&nbsp; GameController gameController = new GameController();</b>
&nbsp;
&nbsp; String ip;
&nbsp; try {
<b class="nc">&nbsp; ip = chooseNetworkInterface(new Scanner(System.in));</b>
<b class="nc">&nbsp; System.out.println(ip);</b>
&nbsp; } catch (Exception e) {
<b class="nc">&nbsp; throw new RuntimeException(e);</b>
&nbsp; }
<b class="nc">&nbsp; System.setProperty(&quot;java.rmi.server.hostname&quot;, ip);</b>
&nbsp;
<b class="nc">&nbsp; RMIServer rmiServer = new RMIServer(gameController, NetworkConfig.RMI_PORT, actionQueue, playerList, ip);</b>
<b class="nc">&nbsp; TCPServer tcpServer = new TCPServer(gameController, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT, actionQueue, playerList);</b>
<b class="nc">&nbsp; CompositeClientBroadcaster broadcaster = new CompositeClientBroadcaster(rmiServer, tcpServer);</b>
<b class="nc">&nbsp; SaveManager saveManager = new SaveManager(ServerLauncher.class);</b>
&nbsp;
<b class="nc">&nbsp; restoreGameIfSaved(gameController, playerList, saveManager);</b>
&nbsp;
<b class="nc">&nbsp; ServerLauncher launcher = new ServerLauncher(</b>
&nbsp; actionQueue, gameController, playerList, broadcaster, saveManager);
&nbsp;
&nbsp; // When all required players have joined, broadcast the initial MiniModel.
<b class="nc">&nbsp; playerList.setAction(() -&gt; new Thread(() -&gt; {</b>
&nbsp; MiniModel miniModel;
<b class="nc">&nbsp; synchronized (gameController) {</b>
<b class="nc">&nbsp; Game game = gameController.getModel();</b>
<b class="nc">&nbsp; miniModel = new MiniModel(</b>
<b class="nc">&nbsp; game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),</b>
<b class="nc">&nbsp; game.getPlayers(), game.getAvailableTotems(),</b>
<b class="nc">&nbsp; game.getUpperListTribeCards(), game.getLowerListTribeCards(),</b>
<b class="nc">&nbsp; game.getUpperListBuilding(), game.getLowerListBuilding(),</b>
<b class="nc">&nbsp; game.getDisconnectedPlayers().entrySet().stream()</b>
<b class="nc">&nbsp; .filter(Map.Entry::getValue)</b>
<b class="nc">&nbsp; .map(e -&gt; e.getKey().getUserName())</b>
<b class="nc">&nbsp; .collect(Collectors.toCollection(ArrayList::new))</b>
&nbsp; );
<b class="nc">&nbsp; }</b>
<b class="nc">&nbsp; broadcaster.notifyAll(miniModel);</b>
<b class="nc">&nbsp; }).start());</b>
&nbsp;
<b class="nc">&nbsp; new Thread(() -&gt; {</b>
<b class="nc">&nbsp; try { launcher.run(); }</b>
&nbsp; catch (Exception e) {
<b class="nc">&nbsp; System.out.println(&quot;Unexpected exception in game loop&quot;);</b>
<b class="nc">&nbsp; e.printStackTrace();</b>
&nbsp; }
<b class="nc">&nbsp; }).start();</b>
&nbsp;
<b class="nc">&nbsp; rmiServer.start();</b>
<b class="nc">&nbsp; new Thread(() -&gt; tcpServer.start()).start();</b>
<b class="nc">&nbsp; System.out.println(&quot;Server RMI: &quot; + System.getProperty(&quot;java.rmi.server.hostname&quot;));</b>
&nbsp; }
&nbsp;
&nbsp; // ── Game loop ─────────────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Runs the event-processing loop until the thread is interrupted.
&nbsp; *
&nbsp; * &lt;p&gt;A {@link ConcurrentModificationException} is caught and logged rather
&nbsp; */
&nbsp; public void run() {
&nbsp; while (true) {
&nbsp; try {
<b class="nc">&nbsp; eventProcessor.doFirstEvent();</b>
&nbsp; } catch (InterruptedException e) {
<b class="nc">&nbsp; Thread.currentThread().interrupt();</b>
&nbsp; break;
&nbsp; }
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; // ── Crash recovery ────────────────────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Attempts to restore a previously saved game.
&nbsp; *
&nbsp; * &lt;p&gt;The save is discarded if all but at most one player was offline at the
&nbsp; * time of the crash, since there would be nobody to resume the game with.
&nbsp; * Otherwise the model is restored, the player-list limit is set, and each
&nbsp; * player who was offline at crash time is pre-populated as offline so the
&nbsp; * reconnection flow can handle them correctly.
&nbsp; *
&nbsp; * @param gameController the controller that will receive the restored model.
&nbsp; * @param playerList the player-status map to populate.
&nbsp; * @param saveManager the save manager to load from.
&nbsp; */
&nbsp; private static void restoreGameIfSaved(
&nbsp; GameController gameController,
&nbsp; LimitedMap&lt;String, Boolean&gt; playerList,
&nbsp; SaveManager saveManager) {
&nbsp;
<b class="nc">&nbsp; Game game = saveManager.load();</b>
<b class="nc">&nbsp; if (game == null) return;</b>
&nbsp;
<b class="nc">&nbsp; long disconnectedCount = game.getDisconnectedPlayers().entrySet().stream()</b>
<b class="nc">&nbsp; .filter(Map.Entry::getValue).count();</b>
&nbsp;
<b class="nc">&nbsp; if (disconnectedCount &gt;= game.getNPlayers() - 1) {</b>
<b class="nc">&nbsp; saveManager.delete();</b>
<b class="nc">&nbsp; System.out.println(&quot;Save discarded: too many players were offline at crash time.&quot;);</b>
&nbsp; return;
&nbsp; }
&nbsp;
<b class="nc">&nbsp; gameController.setModel(game);</b>
<b class="nc">&nbsp; playerList.setLimit(game.getNPlayers());</b>
&nbsp;
<b class="nc">&nbsp; for (Map.Entry&lt;Player, Boolean&gt; entry : game.getDisconnectedPlayers().entrySet()) {</b>
<b class="nc">&nbsp; if (entry.getValue()) {</b>
<b class="nc">&nbsp; playerList.put(entry.getKey().getUserName(), false);</b>
&nbsp; }
&nbsp; }
<b class="nc">&nbsp; System.out.println(&quot;Game restored from save (&quot; + game.getNPlayers() + &quot; players).&quot;);</b>
&nbsp; }
&nbsp;
&nbsp; // ── Network interface selection ───────────────────────────────────────────
&nbsp;
&nbsp; /**
&nbsp; * Lists active non-loopback IPv4 network interfaces and prompts the operator
&nbsp; * to choose one. If only one interface is available it is selected automatically.
&nbsp; *
&nbsp; * @param scanner the scanner used to read the operator&#39;s choice.
&nbsp; * @return the IPv4 address of the selected interface.
&nbsp; * @throws Exception if no valid network interface is available.
&nbsp; */
&nbsp; public static String chooseNetworkInterface(Scanner scanner) throws Exception {
<b class="nc">&nbsp; List&lt;String&gt; ips = new ArrayList&lt;&gt;();</b>
&nbsp;
<b class="nc">&nbsp; Enumeration&lt;NetworkInterface&gt; interfaces = NetworkInterface.getNetworkInterfaces();</b>
<b class="nc">&nbsp; while (interfaces.hasMoreElements()) {</b>
<b class="nc">&nbsp; NetworkInterface ni = interfaces.nextElement();</b>
<b class="nc">&nbsp; if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue;</b>
&nbsp;
<b class="nc">&nbsp; Enumeration&lt;InetAddress&gt; addresses = ni.getInetAddresses();</b>
<b class="nc">&nbsp; while (addresses.hasMoreElements()) {</b>
<b class="nc">&nbsp; InetAddress addr = addresses.nextElement();</b>
<b class="nc">&nbsp; if (addr instanceof Inet4Address) {</b>
<b class="nc">&nbsp; System.out.println(&quot;[&quot; + ips.size() + &quot;] &quot; + ni.getDisplayName()</b>
<b class="nc">&nbsp; + &quot; -&gt; &quot; + addr.getHostAddress());</b>
<b class="nc">&nbsp; ips.add(addr.getHostAddress());</b>
&nbsp; }
&nbsp; }
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (ips.isEmpty()) throw new Exception(&quot;No active network interface available&quot;);</b>
&nbsp;
<b class="nc">&nbsp; if (ips.size() == 1) {</b>
<b class="nc">&nbsp; System.out.println(&quot;One interface found, using: &quot; + ips.get(0));</b>
<b class="nc">&nbsp; return ips.get(0);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; System.out.print(&quot;Choose interface: &quot;);</b>
<b class="nc">&nbsp; int choice = Integer.parseInt(scanner.nextLine().trim());</b>
<b class="nc">&nbsp; return ips.get(choice);</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-14 21:53</div>
</div>
</body>
</html>