Add: Added Coverage Screenshots And htlmReport.
This commit is contained in:
@@ -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"> package it.polimi.ingsw.gc14;
|
||||
|
||||
import it.polimi.ingsw.gc14.Controller.ClientController;
|
||||
import it.polimi.ingsw.gc14.View.GUI.GUI;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/** Entry point for launching the game client with the JavaFX GUI. */
|
||||
<b class="nc"> public class ClientLauncherGUI {</b>
|
||||
|
||||
/**
|
||||
* Launches the GUI application.
|
||||
*
|
||||
* @param args command-line arguments passed to the JavaFX runtime.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
<b class="nc"> System.setProperty("glass.gtk.uiScale", "1.0");</b>
|
||||
<b class="nc"> System.setProperty("glass.win.uiScale", "1.0");</b>
|
||||
<b class="nc"> Application.launch(GUIApp.class, args);</b>
|
||||
}
|
||||
//TODO
|
||||
<b class="nc"> public static class GUIApp extends Application {</b>
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
<b class="nc"> GUI view = new GUI();</b>
|
||||
<b class="nc"> ClientController controller = new ClientController(view);</b>
|
||||
<b class="nc"> view.setController(controller);</b>
|
||||
<b class="nc"> view.start(stage);</b>
|
||||
}
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-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"> package it.polimi.ingsw.gc14;
|
||||
|
||||
import it.polimi.ingsw.gc14.Controller.ClientController;
|
||||
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkConfig;
|
||||
import it.polimi.ingsw.gc14.Network.InterfaceResolver;
|
||||
import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient;
|
||||
import it.polimi.ingsw.gc14.Network.TCP.Client.TCPClient;
|
||||
import it.polimi.ingsw.gc14.View.TUI.TUI;
|
||||
|
||||
import org.jline.builtins.Completers.TreeCompleter;
|
||||
import org.jline.reader.*;
|
||||
import org.jline.reader.impl.completer.StringsCompleter;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.jline.builtins.Completers.TreeCompleter.node;
|
||||
|
||||
/**
|
||||
* Entry point for the TUI-based game client.
|
||||
* Handles login, connects to the server, and drives the JLine-powered command loop.
|
||||
*/
|
||||
<b class="nc"> public class ClientLauncherTUI {</b>
|
||||
//TODO
|
||||
private TUI view;
|
||||
//TODO
|
||||
private Terminal terminal;
|
||||
//TODO
|
||||
private LineReader gameReader;
|
||||
//TODO
|
||||
<b class="nc"> private String currentUsername = "";</b>
|
||||
|
||||
private static final String BUILDING_DETAILS =
|
||||
"ID 0 — Each time you complete a set of 6 different Character cards, take 5 \uD83C\uDF56. (Sets completed before acquiring don't count.)\n" +
|
||||
"ID 1 — During the Sustenance Event, pay 1 less \uD83C\uDF56 for each Artist/Inventor/Gatherer in your tribe.\n" +
|
||||
"ID 2 — During the Shamanic Ritual Event, you do not lose Prestige Points if you have fewer ⭐ than all other players.\n" +
|
||||
"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" +
|
||||
"ID 4 — Each time you obtain a pair of identical Inventors (same icon), take 3 \uD83C\uDF56. (Pairs owned before don't count.)\n" +
|
||||
"ID 5 — During the Shamanic Ritual Event, your tribe has 3 additional ⭐ icons.\n" +
|
||||
"ID 6 — During the Shamanic Ritual Event, if you have more ⭐ than any other player, gain double Prestige. Ties still count.\n" +
|
||||
"ID 7 — During the Hunt Event, take 1 \uD83C\uDF56 and gain 1 extra Prestige Point for each Hunter in your tribe.\n" +
|
||||
"ID 8 — At end of game, gain double the Prestige Points shown on Builder cards in your tribe.\n" +
|
||||
"ID 9 — During the Cave Paintings Event, take 1 \uD83C\uDF56 for each Artist in your tribe.\n" +
|
||||
"ID 10 — At end of game, gain 6 Prestige Points for each complete set of 6 different Character cards.\n" +
|
||||
"ID 11 — At end of game, gain the indicated Prestige Points for each Character card of the indicated type.\n" +
|
||||
"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" +
|
||||
"ID 13 — At end of game, gain 25 Prestige Points.";
|
||||
|
||||
private static final String EVENT_DETAILS =
|
||||
"SUSTENANCE — Pay 1 \uD83C\uDF56 per Character (Buildings don't count). If you can't feed all Characters, lose the\n" +
|
||||
" Prestige Points shown on the card for each one you couldn't feed. Each Gatherer gives a 3\uD83C\uDF56 discount.\n" +
|
||||
" You cannot choose to lose PP to avoid paying Food. Sustenance resolves last if multiple Events occur.\n" +
|
||||
"\n" +
|
||||
"HUNT — Take 1 \uD83C\uDF56 and gain the Prestige Points shown on the card for each Hunter in your tribe.\n" +
|
||||
"\n" +
|
||||
"SHAMANIC RITUAL — Player with the most \uD83C\uDF1F icons gains the Prestige Points on the card.\n" +
|
||||
" Player with the fewest \uD83C\uDF1F icons loses those Prestige Points. In case of a tie, all tied players gain/lose.\n" +
|
||||
"\n" +
|
||||
"CAVE PAINTINGS — If you have fewer Artists than the top-line threshold, lose the indicated Prestige Points.\n" +
|
||||
" If you meet the bottom-line threshold, gain the indicated Prestige Points for each Artist in your tribe.";
|
||||
|
||||
private static final String CHARACTER_DETAILS =
|
||||
"INVENTORS — At end of game, gain PP equal to (number of Inventors) × (number of different Invention icons).\n" +
|
||||
" There are 10 different Invention icons.\n" +
|
||||
"\n" +
|
||||
"GATHERERS — During Sustenance, each Gatherer gives a 3\uD83C\uDF56 discount on what you owe.\n" +
|
||||
" Note: you do not take any Food from Gatherers under any circumstances.\n" +
|
||||
"\n" +
|
||||
"SHAMANS — Each Shaman shows 1–3 \uD83C\uDF1F icons. During Shamanic Ritual, majority = gain PP; minority = lose PP.\n" +
|
||||
"\n" +
|
||||
"BUILDERS — During the game, each Builder reduces the \uD83C\uDF56 cost of every Building card by the amount\n" +
|
||||
" shown in the top-right corner. At end of game, each Builder gives the PP shown in the bottom-left corner.\n" +
|
||||
"\n" +
|
||||
"ARTISTS — During Cave Paintings, gain or lose PP based on Artist count (see 'details events').\n" +
|
||||
" At end of game, gain 10 PP for every 2 Artists in your tribe.\n" +
|
||||
"\n" +
|
||||
"HUNTERS — Adding a Hunter without the \uD83C\uDF56 icon: nothing. Adding a Hunter WITH the \uD83C\uDF56 icon:\n" +
|
||||
" immediately take 1 \uD83C\uDF56 for each Hunter in your tribe (with or without the icon).\n" +
|
||||
" During Hunt Event, take \uD83C\uDF56 and gain PP based on your Hunter count (see 'details events').";
|
||||
//TODO
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
<b class="nc"> new ClientLauncherTUI().start();</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the TUI client. Creates a JLine terminal, loops through login → game → rematch.
|
||||
*
|
||||
* @throws InterruptedException if the thread is interrupted while waiting.
|
||||
*/
|
||||
private void start() throws InterruptedException {
|
||||
try {
|
||||
<b class="nc"> System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));</b>
|
||||
<b class="nc"> System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));</b>
|
||||
<b class="nc"> terminal = TerminalBuilder.builder()</b>
|
||||
<b class="nc"> .system(true)</b>
|
||||
<b class="nc"> .encoding(StandardCharsets.UTF_8)</b>
|
||||
<b class="nc"> .build();</b>
|
||||
} catch (IOException e) {
|
||||
<b class="nc"> System.err.println("Could not initialise terminal: " + e.getMessage());</b>
|
||||
return;
|
||||
}
|
||||
<b class="nc"> terminal.writer().print("\033[H\033[2J");</b>
|
||||
<b class="nc"> terminal.writer().flush();</b>
|
||||
|
||||
<b class="nc"> view = new TUI(null);</b>
|
||||
<b class="nc"> ClientController controller = new ClientController(view);</b>
|
||||
<b class="nc"> gameReader = buildGameReader();</b>
|
||||
<b class="nc"> view.setLineReader(gameReader);</b>
|
||||
|
||||
while (true) {
|
||||
<b class="nc"> if (!doLogin(controller)) continue;</b>
|
||||
|
||||
<b class="nc"> while (controller.getClient() != null) {</b>
|
||||
<b class="nc"> String prompt = buildPrompt(controller);</b>
|
||||
String line;
|
||||
try {
|
||||
<b class="nc"> line = gameReader.readLine(prompt);</b>
|
||||
} catch (UserInterruptException e) {
|
||||
<b class="nc"> safeQuit(controller);</b>
|
||||
return;
|
||||
} catch (EndOfFileException e) {
|
||||
<b class="nc"> safeQuit(controller);</b>
|
||||
return;
|
||||
}
|
||||
<b class="nc"> if (line == null || line.isBlank()) continue;</b>
|
||||
<b class="nc"> boolean rematch = handleCommand(line.trim(), controller);</b>
|
||||
<b class="nc"> if (rematch) break;</b>
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO
|
||||
private boolean doLogin(ClientController controller) {
|
||||
<b class="nc"> LineReader loginReader = LineReaderBuilder.builder()</b>
|
||||
<b class="nc"> .terminal(terminal)</b>
|
||||
<b class="nc"> .completer(new StringsCompleter("rmi", "tcp"))</b>
|
||||
<b class="nc"> .build();</b>
|
||||
<b class="nc"> LineReader ipReader = LineReaderBuilder.builder()</b>
|
||||
<b class="nc"> .terminal(terminal)</b>
|
||||
<b class="nc"> .completer(new StringsCompleter("localhost"))</b>
|
||||
<b class="nc"> .build();</b>
|
||||
|
||||
String username, networkStr, ip;
|
||||
int nPlayers;
|
||||
|
||||
try {
|
||||
<b class="nc"> username = loginReader.readLine("Username: ").trim();</b>
|
||||
<b class="nc"> nPlayers = Integer.parseInt(loginReader.readLine("Number of players [2-5]: ").trim());</b>
|
||||
<b class="nc"> networkStr = loginReader.readLine("Network [rmi/tcp]: ").trim().toLowerCase();</b>
|
||||
<b class="nc"> ip = ipReader.readLine("Server IP [localhost]: ").trim();</b>
|
||||
<b class="nc"> if (ip.isEmpty()) ip = "localhost";</b>
|
||||
} catch (UserInterruptException | EndOfFileException e) {
|
||||
<b class="nc"> System.exit(0);</b>
|
||||
<b class="nc"> return false;</b>
|
||||
} catch (NumberFormatException e) {
|
||||
<b class="nc"> System.out.println("Invalid number of players.");</b>
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
|
||||
<b class="nc"> currentUsername = username;</b>
|
||||
<b class="nc"> view.setUsername(username);</b>
|
||||
<b class="nc"> controller.setMyUsername(username);</b>
|
||||
|
||||
<b class="nc"> if (networkStr.equals("rmi")) {</b>
|
||||
String myIP;
|
||||
try {
|
||||
<b class="nc"> myIP = InterfaceResolver.resolveLocalInterface(ip);</b>
|
||||
} catch (Exception e) {
|
||||
<b class="nc"> System.out.println("Cannot resolve local interface: " + e.getMessage());</b>
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
<b class="nc"> System.setProperty("java.rmi.server.hostname", myIP);</b>
|
||||
<b class="nc"> RMIClient client = new RMIClient(controller, ip, NetworkConfig.RMI_PORT, myIP);</b>
|
||||
<b class="nc"> ErrorType err = client.connect(username, nPlayers);</b>
|
||||
<b class="nc"> if (err != null) {</b>
|
||||
<b class="nc"> terminal.writer().print("\033[H\033[2J");</b>
|
||||
<b class="nc"> terminal.writer().flush();</b>
|
||||
<b class="nc"> System.out.println("Errore: " + err);</b>
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
<b class="nc"> System.out.println("Connected via RMI.");</b>
|
||||
<b class="nc"> controller.setClient(client);</b>
|
||||
|
||||
<b class="nc"> } else if (networkStr.equals("tcp")) {</b>
|
||||
<b class="nc"> TCPClient client = new TCPClient(controller, ip, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT);</b>
|
||||
<b class="nc"> ErrorType err = client.connect(username, nPlayers);</b>
|
||||
<b class="nc"> if (err != null) {</b>
|
||||
<b class="nc"> terminal.writer().print("\033[H\033[2J");</b>
|
||||
<b class="nc"> terminal.writer().flush();</b>
|
||||
<b class="nc"> System.out.println("Errore: " + err);</b>
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
<b class="nc"> System.out.println("Connected via TCP.");</b>
|
||||
<b class="nc"> controller.setClient(client);</b>
|
||||
|
||||
} else {
|
||||
<b class="nc"> System.out.println("Unknown network type '" + networkStr + "'. Use rmi or tcp.");</b>
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
<b class="nc"> return true;</b>
|
||||
}
|
||||
//TODO
|
||||
private LineReader buildGameReader() {
|
||||
<b class="nc"> Completer completer = new TreeCompleter(</b>
|
||||
<b class="nc"> node("slot"),</b>
|
||||
<b class="nc"> node("draw",</b>
|
||||
<b class="nc"> node("upper",</b>
|
||||
<b class="nc"> node("tribe"),</b>
|
||||
<b class="nc"> node("building")),</b>
|
||||
<b class="nc"> node("lower",</b>
|
||||
<b class="nc"> node("tribe"),</b>
|
||||
<b class="nc"> node("building"))),</b>
|
||||
<b class="nc"> node("totem"),</b>
|
||||
<b class="nc"> node("skip"),</b>
|
||||
<b class="nc"> node("render"),</b>
|
||||
<b class="nc"> node("help"),</b>
|
||||
<b class="nc"> node("details", node("buildings"), node("events"), node("characters")),</b>
|
||||
<b class="nc"> node("rematch"),</b>
|
||||
<b class="nc"> node("quit")</b>
|
||||
);
|
||||
<b class="nc"> return LineReaderBuilder.builder()</b>
|
||||
<b class="nc"> .terminal(terminal)</b>
|
||||
<b class="nc"> .completer(completer)</b>
|
||||
<b class="nc"> .option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)</b>
|
||||
<b class="nc"> .build();</b>
|
||||
}
|
||||
//TODO
|
||||
private String buildPrompt(ClientController controller) {
|
||||
<b class="nc"> return currentUsername.isEmpty() ? "> " : currentUsername + "> ";</b>
|
||||
}
|
||||
|
||||
/** Returns {@code true} when the caller should break out of the game loop (rematch requested). */
|
||||
private boolean handleCommand(String line, ClientController controller) {
|
||||
<b class="nc"> String[] parts = line.split("\\s+");</b>
|
||||
<b class="nc"> String cmd = parts[0].toLowerCase();</b>
|
||||
|
||||
<b class="nc"> switch (cmd) {</b>
|
||||
case "slot" -> {
|
||||
<b class="nc"> int pos = parsePos(parts, 1);</b>
|
||||
<b class="nc"> if (pos >= 0) controller.slotChoice(pos);</b>
|
||||
}
|
||||
<b class="nc"> case "draw" -> handleDraw(parts, controller);</b>
|
||||
case "totem" -> {
|
||||
<b class="nc"> int pos = parsePos(parts, 1);</b>
|
||||
<b class="nc"> if (pos >= 0) controller.totemChoice(pos);</b>
|
||||
}
|
||||
<b class="nc"> case "skip" -> controller.skipTurn();</b>
|
||||
<b class="nc"> case "render" -> handleRender(parts);</b>
|
||||
<b class="nc"> case "help" -> gameReader.printAbove("Commands: slot, draw, totem, skip, render, help, details, rematch, quit");</b>
|
||||
case "details" -> {
|
||||
<b class="nc"> if (parts.length > 1 && parts[1].equalsIgnoreCase("buildings"))</b>
|
||||
<b class="nc"> gameReader.printAbove(BUILDING_DETAILS);</b>
|
||||
<b class="nc"> else if (parts.length > 1 && parts[1].equalsIgnoreCase("events"))</b>
|
||||
<b class="nc"> gameReader.printAbove(EVENT_DETAILS);</b>
|
||||
<b class="nc"> else if (parts.length > 1 && parts[1].equalsIgnoreCase("characters"))</b>
|
||||
<b class="nc"> gameReader.printAbove(CHARACTER_DETAILS);</b>
|
||||
else
|
||||
<b class="nc"> gameReader.printAbove("Usage: details buildings | details events | details characters");</b>
|
||||
}
|
||||
case "rematch" -> {
|
||||
<b class="nc"> if (controller.getMiniModel() == null</b>
|
||||
<b class="nc"> || !controller.getMiniModel().currentState.getGameStage().equals(GameStages.ENDED)) {</b>
|
||||
<b class="nc"> System.out.println("Game not ended yet. Use 'quit' to disconnect.");</b>
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
<b class="nc"> controller.disconnect();</b>
|
||||
<b class="nc"> controller.setClient(null);</b>
|
||||
<b class="nc"> controller.setModel(null);</b>
|
||||
<b class="nc"> return true;</b>
|
||||
}
|
||||
case "quit" -> {
|
||||
<b class="nc"> safeQuit(controller);</b>
|
||||
<b class="nc"> System.exit(0);</b>
|
||||
}
|
||||
<b class="nc"> default -> view.showError(ErrorType.GENERIC_ERROR, "Unknown command: " + cmd);</b>
|
||||
}
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
//TODO
|
||||
private void handleDraw(String[] parts, ClientController controller) {
|
||||
<b class="nc"> if (parts.length < 4) {</b>
|
||||
<b class="nc"> view.showError(ErrorType.GENERIC_ERROR, "Usage: draw upper|lower tribe|building <pos>");</b>
|
||||
return;
|
||||
}
|
||||
<b class="nc"> boolean upper = parts[1].equalsIgnoreCase("upper");</b>
|
||||
<b class="nc"> boolean tribe = parts[2].equalsIgnoreCase("tribe");</b>
|
||||
<b class="nc"> int pos = parsePos(parts, 3);</b>
|
||||
<b class="nc"> if (pos < 0) return;</b>
|
||||
|
||||
<b class="nc"> if (upper && tribe) controller.drawUpperTribeCard(pos);</b>
|
||||
<b class="nc"> else if (upper) controller.drawUpperBuildingCard(pos);</b>
|
||||
<b class="nc"> else if (tribe) controller.drawLowerTribeCard(pos);</b>
|
||||
<b class="nc"> else controller.drawLowerBuildingCard(pos);</b>
|
||||
}
|
||||
//TODO
|
||||
private void handleRender(String[] parts) {
|
||||
<b class="nc"> view.renderBoard();</b>
|
||||
}
|
||||
//TODO
|
||||
private int parsePos(String[] parts, int idx) {
|
||||
<b class="nc"> if (parts.length <= idx) {</b>
|
||||
<b class="nc"> view.showError(ErrorType.GENERIC_ERROR, "Missing position argument.");</b>
|
||||
<b class="nc"> return -1;</b>
|
||||
}
|
||||
try {
|
||||
<b class="nc"> return Integer.parseInt(parts[idx]);</b>
|
||||
} catch (NumberFormatException e) {
|
||||
<b class="nc"> view.showError(ErrorType.GENERIC_ERROR,</b>
|
||||
"Invalid position '" + parts[idx] + "' — expected integer.");
|
||||
<b class="nc"> return -1;</b>
|
||||
}
|
||||
}
|
||||
//TODO
|
||||
private void safeQuit(ClientController controller) {
|
||||
<b class="nc"> if (controller.getClient() != null) controller.disconnect();</b>
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-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"> package it.polimi.ingsw.gc14;
|
||||
|
||||
/** Enumeration of error types that can be returned by server-side operations. */
|
||||
<b class="nc"> public enum ErrorType {</b>
|
||||
<b class="nc"> USER_NOT_FOUND("User not found"),</b>
|
||||
<b class="nc"> USERNAME_ALREADY_USED("Username is already in use"),</b>
|
||||
<b class="nc"> USER_ALREADY_CONNECTED("User is already connected"),</b>
|
||||
<b class="nc"> WRONG_PLAYER_NUMBER("Wrong player number"),</b>
|
||||
<b class="nc"> SERVER_CRASHED("Server crashed"),</b>
|
||||
<b class="nc"> GENERIC_ERROR("Generic error"),</b>
|
||||
<b class="nc"> GAME_ALREADY_STARTED("Game already started"),</b>
|
||||
<b class="nc"> WRONG_ACTION("Wrong action");</b>
|
||||
//TODO
|
||||
private final String description;
|
||||
<b class="nc"> ErrorType(String description) {</b>
|
||||
<b class="nc"> this.description = description;</b>
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
<b class="nc"> return description;</b>
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-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"> package it.polimi.ingsw.gc14;
|
||||
|
||||
import it.polimi.ingsw.gc14.Controller.GameController;
|
||||
import it.polimi.ingsw.gc14.Model.Game;
|
||||
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
||||
import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster;
|
||||
import it.polimi.ingsw.gc14.Network.EventType;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Processes game events from the action queue and routes each one
|
||||
* based on the current game state.
|
||||
*
|
||||
* <p>The three top-level states that determine routing are:
|
||||
* <ul>
|
||||
* <li><b>Inactive</b> – no game model is present, or the game has ended.
|
||||
* Only disconnection clean-up is performed.</li>
|
||||
* <li><b>Suspended</b> – a forfeit timer is running because exactly one
|
||||
* player remains online. Only reconnection events are accepted.</li>
|
||||
* <li><b>Active</b> – normal gameplay; every event is applied, saved,
|
||||
* and broadcast.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>This class is not thread-safe by itself: it relies on the caller
|
||||
* (the game loop in {@code ServerLauncher}) to drive it from a single
|
||||
* thread via {@link #doFirstEvent()}.
|
||||
*/
|
||||
public class GameEventProcessor {
|
||||
|
||||
private final BlockingQueue<NetworkEvent> actionQueue;
|
||||
private final GameController gameController;
|
||||
private final LimitedMap<String, Boolean> playerList;
|
||||
private final CompositeClientBroadcaster broadcaster;
|
||||
private final SaveManager saveManager;
|
||||
|
||||
/** Single-thread executor used exclusively for the forfeit timer. Daemon so it does not block JVM shutdown. */
|
||||
<b class="nc"> private final ScheduledExecutorService timerExecutor =</b>
|
||||
<b class="nc"> Executors.newSingleThreadScheduledExecutor(r -> {</b>
|
||||
<b class="nc"> Thread t = new Thread(r, "forfeit-timer");</b>
|
||||
<b class="nc"> t.setDaemon(true);</b>
|
||||
<b class="nc"> return t;</b>
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle to the running forfeit timer, or {@code null} when no timer is active.
|
||||
* A non-null value signals that the game is in the {@em suspended} state.
|
||||
*/
|
||||
private ScheduledFuture<?> disconnectionTimer;
|
||||
|
||||
/**
|
||||
* Constructs a {@code GameEventProcessor} with all required dependencies.
|
||||
*
|
||||
* @param actionQueue the queue from which incoming events are consumed.
|
||||
* @param gameController the server-side game controller.
|
||||
* @param playerList the shared map tracking each player's online status.
|
||||
* @param broadcaster the broadcaster used to notify all connected clients.
|
||||
* @param saveManager the save manager used to persist the game state.
|
||||
*/
|
||||
public GameEventProcessor(
|
||||
BlockingQueue<NetworkEvent> actionQueue,
|
||||
GameController gameController,
|
||||
LimitedMap<String, Boolean> playerList,
|
||||
CompositeClientBroadcaster broadcaster,
|
||||
<b class="nc"> SaveManager saveManager) {</b>
|
||||
<b class="nc"> this.actionQueue = actionQueue;</b>
|
||||
<b class="nc"> this.gameController = gameController;</b>
|
||||
<b class="nc"> this.playerList = playerList;</b>
|
||||
<b class="nc"> this.broadcaster = broadcaster;</b>
|
||||
<b class="nc"> this.saveManager = saveManager;</b>
|
||||
}
|
||||
|
||||
// ── Public entry point ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Blocks until one event is available in the queue, then routes it to the
|
||||
* appropriate handler based on the current game state.
|
||||
*
|
||||
* @throws InterruptedException if the thread is interrupted while waiting
|
||||
* for the next event.
|
||||
*/
|
||||
public void doFirstEvent() throws InterruptedException {
|
||||
<b class="nc"> NetworkEvent event = actionQueue.take();</b>
|
||||
|
||||
<b class="nc"> if (!isGameActive()) {</b>
|
||||
<b class="nc"> handleInactiveGame(event);</b>
|
||||
<b class="nc"> } else if (isSuspended()) {</b>
|
||||
<b class="nc"> handleSuspendedGame(event);</b>
|
||||
} else {
|
||||
<b class="nc"> applyAndBroadcast(event);</b>
|
||||
}
|
||||
}
|
||||
|
||||
// ── State guards ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns {@code true} when there is an ongoing game that has not yet ended.
|
||||
*/
|
||||
private boolean isGameActive() {
|
||||
<b class="nc"> Game model = gameController.getModel();</b>
|
||||
<b class="nc"> return model != null</b>
|
||||
<b class="nc"> && model.getCurrentState().getGameStage() != GameStages.ENDED;</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} when the forfeit timer is running, meaning only one
|
||||
* player is currently online and the game is waiting for a reconnection.
|
||||
*/
|
||||
private boolean isSuspended() {
|
||||
<b class="nc"> return disconnectionTimer != null;</b>
|
||||
}
|
||||
|
||||
// ── Inactive game path ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Handles events that arrive when no active game exists (not yet started,
|
||||
* or already ended). Only disconnection clean-up is relevant here.
|
||||
*
|
||||
* @param event the incoming event.
|
||||
*/
|
||||
private void handleInactiveGame(NetworkEvent event) {
|
||||
<b class="nc"> if (event.getEventType() != EventType.DISCONNECTED_PLAYER) return;</b>
|
||||
|
||||
<b class="nc"> synchronized (gameController) {</b>
|
||||
<b class="nc"> playerList.remove(event.getUsername());</b>
|
||||
<b class="nc"> if (playerList.isEmpty()) {</b>
|
||||
<b class="nc"> gameController.setModel(null);</b>
|
||||
<b class="nc"> System.out.println("\n!!! Player list is now empty, ready for a new game init !!!\n");</b>
|
||||
}
|
||||
<b class="nc"> }</b>
|
||||
}
|
||||
|
||||
// ── Suspended game path ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Routes events while the game is suspended waiting for a reconnection.
|
||||
* <ul>
|
||||
* <li>A second disconnection while suspended means no player remains
|
||||
* online: the game is aborted entirely.</li>
|
||||
* <li>Any non-reconnection event is rejected with an error.</li>
|
||||
* <li>A reconnection event is allowed through to {@link #applyAndBroadcast}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param event the incoming event.
|
||||
*/
|
||||
private void handleSuspendedGame(NetworkEvent event) {
|
||||
<b class="nc"> switch (event.getEventType()) {</b>
|
||||
<b class="nc"> case DISCONNECTED_PLAYER -> abortGame();</b>
|
||||
<b class="nc"> case RECONNECT_PLAYER -> applyAndBroadcast(event);</b>
|
||||
<b class="nc"> default -> rejectEvent(event);</b>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the forfeit timer, clears the player list, and resets the model.
|
||||
* Called when the last remaining player disconnects while the game is suspended.
|
||||
*/
|
||||
private void abortGame() {
|
||||
<b class="nc"> disconnectionTimer.cancel(true);</b>
|
||||
<b class="nc"> disconnectionTimer = null;</b>
|
||||
<b class="nc"> playerList.clear();</b>
|
||||
<b class="nc"> gameController.setModel(null);</b>
|
||||
<b class="nc"> System.out.println("\n!!! All players disconnected — game aborted, ready for a new game init !!!\n");</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the event as an error and broadcasts it back to the requesting
|
||||
* player. Used to reject actions that are not permitted in the current state.
|
||||
*
|
||||
* @param event the event to reject.
|
||||
*/
|
||||
private void rejectEvent(NetworkEvent event) {
|
||||
<b class="nc"> event.setIsError(true);</b>
|
||||
<b class="nc"> broadcaster.notifyAll(event);</b>
|
||||
}
|
||||
|
||||
// ── Active game path ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Applies the event to the game controller, saves the updated state,
|
||||
* and broadcasts the result to connected clients.
|
||||
*
|
||||
* <p>The entire method body is synchronized on {@code gameController} to
|
||||
* prevent concurrent modification of the game model by the network threads.
|
||||
*
|
||||
* @param event the event to apply.
|
||||
*/
|
||||
private void applyAndBroadcast(NetworkEvent event) {
|
||||
<b class="nc"> synchronized (gameController) {</b>
|
||||
<b class="nc"> int roundBefore = gameController.getModel().getCurrentState().getRound();</b>
|
||||
|
||||
<b class="nc"> event.setIsError(!event.apply(gameController));</b>
|
||||
<b class="nc"> Game game = gameController.getModel();</b>
|
||||
|
||||
<b class="nc"> if (event.isError()) {</b>
|
||||
<b class="nc"> broadcaster.notifyAll(event);</b>
|
||||
<b class="nc"> return;</b>
|
||||
}
|
||||
|
||||
<b class="nc"> cancelForfeitTimerIfReconnect(event);</b>
|
||||
|
||||
<b class="nc"> if (!saveManager.save(game)) {</b>
|
||||
<b class="nc"> System.out.println("\n!!! Save failed !!!\n");</b>
|
||||
}
|
||||
|
||||
// During the lobby phase a disconnection only removes the player
|
||||
// from the list; no broadcast is needed.
|
||||
<b class="nc"> if (event.getEventType() == EventType.DISCONNECTED_PLAYER</b>
|
||||
<b class="nc"> && game.getCurrentState().getGameStage() == GameStages.WAITING) {</b>
|
||||
<b class="nc"> playerList.remove(event.getUsername());</b>
|
||||
<b class="nc"> return;</b>
|
||||
}
|
||||
|
||||
<b class="nc"> enrichEvent(event, game);</b>
|
||||
<b class="nc"> startForfeitTimerIfNeeded(event, game);</b>
|
||||
<b class="nc"> broadcastResult(event, game, roundBefore);</b>
|
||||
<b class="nc"> }</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the forfeit timer if the event is a successful reconnection.
|
||||
*
|
||||
* @param event the event that was just successfully applied.
|
||||
*/
|
||||
private void cancelForfeitTimerIfReconnect(NetworkEvent event) {
|
||||
<b class="nc"> if (event.getEventType() == EventType.RECONNECT_PLAYER</b>
|
||||
&& disconnectionTimer != null
|
||||
<b class="nc"> && !disconnectionTimer.isDone()) {</b>
|
||||
<b class="nc"> disconnectionTimer.cancel(false);</b>
|
||||
<b class="nc"> disconnectionTimer = null;</b>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the event with the current game state so that clients can
|
||||
* update their mini-model after receiving it.
|
||||
*
|
||||
* <p>Each event subclass overrides {@link NetworkEvent#enrichWithGameState}
|
||||
* to append any type-specific extra fields (available totems, etc.).
|
||||
*
|
||||
* @param event the event to enrich.
|
||||
* @param game the current game model.
|
||||
*/
|
||||
private void enrichEvent(NetworkEvent event, Game game) {
|
||||
<b class="nc"> event.enrichWithGameState(game, buildDisconnectedList(game));</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the 60-second forfeit timer if a disconnection has left
|
||||
* exactly one player online.
|
||||
*
|
||||
* <p>If a previous timer is still pending it is cancelled first to avoid
|
||||
* duplicate timers.
|
||||
*
|
||||
* @param event the event that was just applied.
|
||||
* @param game the current game model.
|
||||
*/
|
||||
private void startForfeitTimerIfNeeded(NetworkEvent event, Game game) {
|
||||
<b class="nc"> if (event.getEventType() != EventType.DISCONNECTED_PLAYER) return;</b>
|
||||
<b class="nc"> if (game.getCurrentState().getGameStage() == GameStages.ENDED) return;</b>
|
||||
<b class="nc"> if (onlinePlayerCount() != 1) return;</b>
|
||||
|
||||
<b class="nc"> if (disconnectionTimer != null && !disconnectionTimer.isDone()) {</b>
|
||||
<b class="nc"> disconnectionTimer.cancel(false);</b>
|
||||
}
|
||||
|
||||
<b class="nc"> disconnectionTimer = timerExecutor.schedule(</b>
|
||||
<b class="nc"> () -> endGameForfeit(game),</b>
|
||||
1, TimeUnit.MINUTES
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the game by forfeit when the timer expires without a reconnection.
|
||||
* Broadcasts an {@link EndedGame} event, deletes the save, and resets state.
|
||||
*
|
||||
* @param game the game model captured when the timer was scheduled.
|
||||
*/
|
||||
private void endGameForfeit(Game game) {
|
||||
<b class="nc"> synchronized (gameController) {</b>
|
||||
<b class="nc"> gameController.endGameForfeit();</b>
|
||||
<b class="nc"> EndedGame forfeitEnd = new EndedGame(</b>
|
||||
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(),</b>
|
||||
<b class="nc"> game.getCurrentState(), game.getPlayerStanding()</b>
|
||||
);
|
||||
<b class="nc"> broadcaster.notifyAll(forfeitEnd);</b>
|
||||
<b class="nc"> System.out.println("Timer expired: no player reconnected in 60 s.");</b>
|
||||
<b class="nc"> removeOfflinePlayers();</b>
|
||||
<b class="nc"> if (!saveManager.delete()) {</b>
|
||||
<b class="nc"> System.out.println("\n!!! Couldn't delete save !!!\n");</b>
|
||||
}
|
||||
<b class="nc"> disconnectionTimer = null;</b>
|
||||
<b class="nc"> }</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what to broadcast after a successful event application:
|
||||
* <ul>
|
||||
* <li>If the round advanced, an {@link ApplyNextRound} event (with updated
|
||||
* card lists) replaces the original event.</li>
|
||||
* <li>If the game has ended, an {@link EndedGame} event is sent and the
|
||||
* save file is deleted.</li>
|
||||
* <li>Otherwise the original event is broadcast as-is.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param event the event that was applied.
|
||||
* @param game the current game model (post-apply).
|
||||
* @param roundBefore the round number before the event was applied.
|
||||
*/
|
||||
private void broadcastResult(NetworkEvent event, Game game, int roundBefore) {
|
||||
<b class="nc"> broadcaster.notifyAll(event);</b>
|
||||
<b class="nc"> if (game.getCurrentState().getRound() != roundBefore) {</b>
|
||||
<b class="nc"> ApplyNextRound nextRound = new ApplyNextRound(</b>
|
||||
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),</b>
|
||||
<b class="nc"> game.getPlayers(),</b>
|
||||
<b class="nc"> game.getUpperListTribeCards(), game.getLowerListTribeCards(),</b>
|
||||
<b class="nc"> game.getUpperListBuilding(), game.getLowerListBuilding()</b>
|
||||
);
|
||||
<b class="nc"> broadcaster.notifyAll(nextRound);</b>
|
||||
<b class="nc"> } else if (game.getCurrentState().getGameStage() == GameStages.ENDED) {</b>
|
||||
<b class="nc"> EndedGame endedGame = new EndedGame(</b>
|
||||
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(),</b>
|
||||
<b class="nc"> game.getCurrentState(), game.getPlayerStanding()</b>
|
||||
);
|
||||
<b class="nc"> broadcaster.notifyAll(endedGame);</b>
|
||||
<b class="nc"> if (!saveManager.delete()) {</b>
|
||||
<b class="nc"> System.out.println("\n!!! Couldn't delete save !!!\n");</b>
|
||||
}
|
||||
<b class="nc"> removeOfflinePlayers();</b>
|
||||
}
|
||||
}
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns the number of players currently marked as online in the player list.
|
||||
*/
|
||||
private long onlinePlayerCount() {
|
||||
<b class="nc"> return playerList.values().stream().filter(v -> v).count();</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the list of usernames of players currently marked as disconnected
|
||||
* in the game model.
|
||||
*
|
||||
* @param game the current game model.
|
||||
* @return a new {@link ArrayList} of disconnected usernames.
|
||||
*/
|
||||
private ArrayList<String> buildDisconnectedList(Game game) {
|
||||
<b class="nc"> return game.getDisconnectedPlayers().entrySet().stream()</b>
|
||||
<b class="nc"> .filter(Map.Entry::getValue)</b>
|
||||
<b class="nc"> .map(e -> e.getKey().getUserName())</b>
|
||||
<b class="nc"> .collect(Collectors.toCollection(ArrayList::new));</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all offline entries (value {@code false}) from the player list.
|
||||
* Online players remain until they disconnect naturally.
|
||||
*/
|
||||
private void removeOfflinePlayers() {
|
||||
<b class="nc"> List<String> toRemove = playerList.entrySet().stream()</b>
|
||||
<b class="nc"> .filter(e -> !e.getValue())</b>
|
||||
<b class="nc"> .map(Map.Entry::getKey)</b>
|
||||
<b class="nc"> .toList();</b>
|
||||
<b class="nc"> toRemove.forEach(playerList::remove);</b>
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-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"> package it.polimi.ingsw.gc14;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A {@link ConcurrentHashMap}-backed map with a configurable size limit and an associated action.
|
||||
* When the number of elements reaches or exceeds the limit, the specified action is automatically triggered.
|
||||
* This implementation is thread-safe for {@code put}; callers that need compound operations must synchronize externally.
|
||||
*
|
||||
* @param <K> the type of keys maintained by this map.
|
||||
* @param <V> the type of mapped values.
|
||||
*/
|
||||
public class LimitedMap<K, V> implements Map<K, V> {
|
||||
|
||||
<b class="nc"> private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();</b>
|
||||
|
||||
/**
|
||||
* The maximum number of elements allowed in the map before the action is triggered.
|
||||
*/
|
||||
private volatile int limit;
|
||||
|
||||
/**
|
||||
* The action to execute when the map size reaches or exceeds the limit.
|
||||
*/
|
||||
private volatile Runnable action;
|
||||
|
||||
/**
|
||||
* Creates a new {@code LimitedMap} with the specified limit and action.
|
||||
*
|
||||
* @param limit the maximum number of elements before the action is triggered.
|
||||
* @param action the action to execute when the limit is reached.
|
||||
*/
|
||||
<b class="nc"> public LimitedMap(int limit, Runnable action) {</b>
|
||||
<b class="nc"> this.limit = limit;</b>
|
||||
<b class="nc"> this.action = action;</b>
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key in this map.
|
||||
* If the map size reaches or exceeds the limit after the insertion, the configured action is triggered.
|
||||
*
|
||||
* @param key the key with which the specified value is to be associated.
|
||||
* @param value the value to be associated with the specified key.
|
||||
* @return the previous value associated with the key, or {@code null} if there was no mapping.
|
||||
*/
|
||||
@Override
|
||||
public synchronized V put(K key, V value) {
|
||||
<b class="nc"> boolean added = true;</b>
|
||||
<b class="nc"> if (map.size() == limit) {</b>
|
||||
<b class="nc"> if (!map.containsKey(key))</b>
|
||||
<b class="nc"> return null;</b>
|
||||
<b class="nc"> added = false;</b>
|
||||
}
|
||||
<b class="nc"> V result = map.put(key, value);</b>
|
||||
<b class="nc"> if (map.size() >= limit && added) {</b>
|
||||
<b class="nc"> action.run();</b>
|
||||
}
|
||||
<b class="nc"> return result;</b>
|
||||
}
|
||||
|
||||
@Override
|
||||
<b class="nc"> public synchronized V remove(Object key) { return map.remove(key); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public V get(Object key) { return map.get(key); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public boolean containsKey(Object key) { return map.containsKey(key); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public boolean containsValue(Object value) { return map.containsValue(value); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public int size() { return map.size(); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public boolean isEmpty() { return map.isEmpty(); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public void putAll(Map<? extends K, ? extends V> m) { m.forEach(this::put); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public void clear() { map.clear(); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public Set<K> keySet() { return map.keySet(); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public Collection<V> values() { return map.values(); }</b>
|
||||
|
||||
@Override
|
||||
<b class="nc"> public Set<Entry<K, V>> entrySet() { return map.entrySet(); }</b>
|
||||
|
||||
/**
|
||||
* Sets a new size limit for this map.
|
||||
*
|
||||
* @param num the new limit.
|
||||
*/
|
||||
<b class="nc"> public void setLimit(int num) { this.limit = num; }</b>
|
||||
|
||||
/**
|
||||
* Returns the current size limit of this map.
|
||||
*
|
||||
* @return the current limit.
|
||||
*/
|
||||
<b class="nc"> public int getLimit() { return limit; }</b>
|
||||
|
||||
/**
|
||||
* Sets a new action to execute when the map size reaches or exceeds the limit.
|
||||
*
|
||||
* @param action the new action to set; must not be {@code null}.
|
||||
*/
|
||||
public void setAction(Runnable action) {
|
||||
<b class="nc"> if (action == null) throw new IllegalArgumentException("action must not be null");</b>
|
||||
<b class="nc"> this.action = action;</b>
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-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"> package it.polimi.ingsw.gc14;
|
||||
|
||||
import it.polimi.ingsw.gc14.Model.Game;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Handles persistence of the game model to and from disk.
|
||||
*
|
||||
* <p>The save file is stored at {@code GameSaves/save.dat} relative to the
|
||||
* directory containing the running JAR. The anchor class passed to the
|
||||
* constructor is used to resolve that directory at construction time.
|
||||
*
|
||||
* <p>All three operations ({@link #save}, {@link #load}, {@link #delete})
|
||||
* are independent and safe to call in any order; missing files are treated
|
||||
* as a normal condition (no save present) rather than an error.
|
||||
*/
|
||||
public class SaveManager {
|
||||
|
||||
/** Relative sub-path of the save file inside the JAR directory. */
|
||||
private static final String SAVE_RELATIVE_PATH = "GameSaves/save.dat";
|
||||
|
||||
/** Absolute path of the save file, resolved once at construction. */
|
||||
private final Path filePath;
|
||||
|
||||
/**
|
||||
* Constructs a {@code SaveManager} whose save file is located relative
|
||||
* to the JAR directory of the given anchor class.
|
||||
*
|
||||
* @param anchorClass the class whose code-source location is used as
|
||||
* the base directory for the save file.
|
||||
* @throws RuntimeException if the JAR path cannot be resolved.
|
||||
*/
|
||||
<b class="nc"> public SaveManager(Class<?> anchorClass) {</b>
|
||||
try {
|
||||
<b class="nc"> Path jarDir = Paths.get(</b>
|
||||
<b class="nc"> anchorClass.getProtectionDomain().getCodeSource().getLocation().toURI()</b>
|
||||
<b class="nc"> ).getParent();</b>
|
||||
<b class="nc"> this.filePath = jarDir.resolve(SAVE_RELATIVE_PATH);</b>
|
||||
} catch (URISyntaxException e) {
|
||||
<b class="nc"> throw new RuntimeException("Could not resolve save file path", e);</b>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the game model to disk, creating parent directories if needed.
|
||||
*
|
||||
* @param game the game model to persist.
|
||||
* @return {@code true} if the save succeeded, {@code false} on I/O error.
|
||||
*/
|
||||
public boolean save(Game game) {
|
||||
try {
|
||||
<b class="nc"> Files.createDirectories(filePath.getParent());</b>
|
||||
<b class="nc"> try (ObjectOutputStream oos = new ObjectOutputStream(</b>
|
||||
<b class="nc"> new BufferedOutputStream(new FileOutputStream(filePath.toFile())))) {</b>
|
||||
<b class="nc"> oos.writeObject(game);</b>
|
||||
<b class="nc"> System.out.println("Game saved to: " + filePath.toAbsolutePath());</b>
|
||||
<b class="nc"> return true;</b>
|
||||
}
|
||||
} catch (IOException e) {
|
||||
<b class="nc"> e.printStackTrace();</b>
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the game model from disk.
|
||||
*
|
||||
* @return the saved {@link Game} instance, or {@code null} if no save file
|
||||
* exists or an I/O error prevents reading.
|
||||
* @throws RuntimeException if the serialized class cannot be found on
|
||||
* the classpath (indicates a deployment mismatch).
|
||||
*/
|
||||
public Game load() {
|
||||
<b class="nc"> try (ObjectInputStream ois = new ObjectInputStream(</b>
|
||||
<b class="nc"> new BufferedInputStream(new FileInputStream(filePath.toFile())))) {</b>
|
||||
<b class="nc"> return (Game) ois.readObject();</b>
|
||||
} catch (FileNotFoundException e) {
|
||||
<b class="nc"> return null;</b>
|
||||
} catch (IOException e) {
|
||||
<b class="nc"> e.printStackTrace();</b>
|
||||
<b class="nc"> return null;</b>
|
||||
} catch (ClassNotFoundException e) {
|
||||
<b class="nc"> throw new RuntimeException("Save file references an unknown class", e);</b>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the save file.
|
||||
*
|
||||
* @return {@code true} if the file was deleted, {@code false} otherwise
|
||||
* (including when the file did not exist).
|
||||
*/
|
||||
public boolean delete() {
|
||||
try {
|
||||
<b class="nc"> return Files.deleteIfExists(filePath);</b>
|
||||
} catch (IOException e) {
|
||||
<b class="nc"> return false;</b>
|
||||
}
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-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"> package it.polimi.ingsw.gc14;
|
||||
|
||||
import it.polimi.ingsw.gc14.Controller.GameController;
|
||||
import it.polimi.ingsw.gc14.Model.Game;
|
||||
import it.polimi.ingsw.gc14.Model.MiniModel;
|
||||
import it.polimi.ingsw.gc14.Model.Player;
|
||||
import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||
import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
|
||||
import it.polimi.ingsw.gc14.Network.NetworkConfig;
|
||||
import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
|
||||
|
||||
import java.net.*;
|
||||
import java.rmi.RemoteException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Main server entry point.
|
||||
*
|
||||
* <p>Responsibilities of this class are intentionally limited to wiring:
|
||||
* it creates all components, connects them together, restores a previously
|
||||
* saved game if one exists, and starts the event-processing loop and the
|
||||
* network servers.
|
||||
*
|
||||
* <p>All game-logic and event-routing decisions are delegated to
|
||||
* {@link GameEventProcessor}; persistence is delegated to {@link SaveManager}.
|
||||
*
|
||||
* <p>The original {@code ServerLauncher} class is preserved and untouched.
|
||||
* This class is a clean replacement that uses the new component structure.
|
||||
*/
|
||||
public class ServerLauncher {
|
||||
|
||||
/** Drives the main game-event loop. */
|
||||
private final GameEventProcessor eventProcessor;
|
||||
|
||||
/**
|
||||
* Constructs a {@code ServerLauncherTest} and wires all components together.
|
||||
*
|
||||
* @param actionQueue the shared event queue.
|
||||
* @param gameController the server-side game controller.
|
||||
* @param playerList the shared player-status map.
|
||||
* @param broadcaster the broadcaster used to reach all connected clients.
|
||||
* @param saveManager the save manager used to persist game state.
|
||||
*/
|
||||
public ServerLauncher(
|
||||
BlockingQueue<NetworkEvent> actionQueue,
|
||||
GameController gameController,
|
||||
LimitedMap<String, Boolean> playerList,
|
||||
CompositeClientBroadcaster broadcaster,
|
||||
<b class="nc"> SaveManager saveManager) {</b>
|
||||
<b class="nc"> this.eventProcessor = new GameEventProcessor(</b>
|
||||
actionQueue, gameController, playerList, broadcaster, saveManager);
|
||||
}
|
||||
|
||||
// ── Entry point ───────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Initialises all server components, wires them together, restores a saved
|
||||
* game if available, and starts the event-processing loop and both network
|
||||
* servers.
|
||||
*
|
||||
* @param args command-line arguments (unused).
|
||||
* @throws RemoteException if the RMI server cannot be created.
|
||||
*/
|
||||
public static void main(String[] args) throws RemoteException {
|
||||
<b class="nc"> LimitedMap<String, Boolean> playerList = new LimitedMap<>(5, () -> {});</b>
|
||||
<b class="nc"> BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>();</b>
|
||||
<b class="nc"> GameController gameController = new GameController();</b>
|
||||
|
||||
String ip;
|
||||
try {
|
||||
<b class="nc"> ip = chooseNetworkInterface(new Scanner(System.in));</b>
|
||||
<b class="nc"> System.out.println(ip);</b>
|
||||
} catch (Exception e) {
|
||||
<b class="nc"> throw new RuntimeException(e);</b>
|
||||
}
|
||||
<b class="nc"> System.setProperty("java.rmi.server.hostname", ip);</b>
|
||||
|
||||
<b class="nc"> RMIServer rmiServer = new RMIServer(gameController, NetworkConfig.RMI_PORT, actionQueue, playerList, ip);</b>
|
||||
<b class="nc"> TCPServer tcpServer = new TCPServer(gameController, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT, actionQueue, playerList);</b>
|
||||
<b class="nc"> CompositeClientBroadcaster broadcaster = new CompositeClientBroadcaster(rmiServer, tcpServer);</b>
|
||||
<b class="nc"> SaveManager saveManager = new SaveManager(ServerLauncher.class);</b>
|
||||
|
||||
<b class="nc"> restoreGameIfSaved(gameController, playerList, saveManager);</b>
|
||||
|
||||
<b class="nc"> ServerLauncher launcher = new ServerLauncher(</b>
|
||||
actionQueue, gameController, playerList, broadcaster, saveManager);
|
||||
|
||||
// When all required players have joined, broadcast the initial MiniModel.
|
||||
<b class="nc"> playerList.setAction(() -> new Thread(() -> {</b>
|
||||
MiniModel miniModel;
|
||||
<b class="nc"> synchronized (gameController) {</b>
|
||||
<b class="nc"> Game game = gameController.getModel();</b>
|
||||
<b class="nc"> miniModel = new MiniModel(</b>
|
||||
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),</b>
|
||||
<b class="nc"> game.getPlayers(), game.getAvailableTotems(),</b>
|
||||
<b class="nc"> game.getUpperListTribeCards(), game.getLowerListTribeCards(),</b>
|
||||
<b class="nc"> game.getUpperListBuilding(), game.getLowerListBuilding(),</b>
|
||||
<b class="nc"> game.getDisconnectedPlayers().entrySet().stream()</b>
|
||||
<b class="nc"> .filter(Map.Entry::getValue)</b>
|
||||
<b class="nc"> .map(e -> e.getKey().getUserName())</b>
|
||||
<b class="nc"> .collect(Collectors.toCollection(ArrayList::new))</b>
|
||||
);
|
||||
<b class="nc"> }</b>
|
||||
<b class="nc"> broadcaster.notifyAll(miniModel);</b>
|
||||
<b class="nc"> }).start());</b>
|
||||
|
||||
<b class="nc"> new Thread(() -> {</b>
|
||||
<b class="nc"> try { launcher.run(); }</b>
|
||||
catch (Exception e) {
|
||||
<b class="nc"> System.out.println("Unexpected exception in game loop");</b>
|
||||
<b class="nc"> e.printStackTrace();</b>
|
||||
}
|
||||
<b class="nc"> }).start();</b>
|
||||
|
||||
<b class="nc"> rmiServer.start();</b>
|
||||
<b class="nc"> new Thread(() -> tcpServer.start()).start();</b>
|
||||
<b class="nc"> System.out.println("Server RMI: " + System.getProperty("java.rmi.server.hostname"));</b>
|
||||
}
|
||||
|
||||
// ── Game loop ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Runs the event-processing loop until the thread is interrupted.
|
||||
*
|
||||
* <p>A {@link ConcurrentModificationException} is caught and logged rather
|
||||
*/
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
<b class="nc"> eventProcessor.doFirstEvent();</b>
|
||||
} catch (InterruptedException e) {
|
||||
<b class="nc"> Thread.currentThread().interrupt();</b>
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Crash recovery ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Attempts to restore a previously saved game.
|
||||
*
|
||||
* <p>The save is discarded if all but at most one player was offline at the
|
||||
* time of the crash, since there would be nobody to resume the game with.
|
||||
* Otherwise the model is restored, the player-list limit is set, and each
|
||||
* player who was offline at crash time is pre-populated as offline so the
|
||||
* reconnection flow can handle them correctly.
|
||||
*
|
||||
* @param gameController the controller that will receive the restored model.
|
||||
* @param playerList the player-status map to populate.
|
||||
* @param saveManager the save manager to load from.
|
||||
*/
|
||||
private static void restoreGameIfSaved(
|
||||
GameController gameController,
|
||||
LimitedMap<String, Boolean> playerList,
|
||||
SaveManager saveManager) {
|
||||
|
||||
<b class="nc"> Game game = saveManager.load();</b>
|
||||
<b class="nc"> if (game == null) return;</b>
|
||||
|
||||
<b class="nc"> long disconnectedCount = game.getDisconnectedPlayers().entrySet().stream()</b>
|
||||
<b class="nc"> .filter(Map.Entry::getValue).count();</b>
|
||||
|
||||
<b class="nc"> if (disconnectedCount >= game.getNPlayers() - 1) {</b>
|
||||
<b class="nc"> saveManager.delete();</b>
|
||||
<b class="nc"> System.out.println("Save discarded: too many players were offline at crash time.");</b>
|
||||
return;
|
||||
}
|
||||
|
||||
<b class="nc"> gameController.setModel(game);</b>
|
||||
<b class="nc"> playerList.setLimit(game.getNPlayers());</b>
|
||||
|
||||
<b class="nc"> for (Map.Entry<Player, Boolean> entry : game.getDisconnectedPlayers().entrySet()) {</b>
|
||||
<b class="nc"> if (entry.getValue()) {</b>
|
||||
<b class="nc"> playerList.put(entry.getKey().getUserName(), false);</b>
|
||||
}
|
||||
}
|
||||
<b class="nc"> System.out.println("Game restored from save (" + game.getNPlayers() + " players).");</b>
|
||||
}
|
||||
|
||||
// ── Network interface selection ───────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Lists active non-loopback IPv4 network interfaces and prompts the operator
|
||||
* to choose one. If only one interface is available it is selected automatically.
|
||||
*
|
||||
* @param scanner the scanner used to read the operator's choice.
|
||||
* @return the IPv4 address of the selected interface.
|
||||
* @throws Exception if no valid network interface is available.
|
||||
*/
|
||||
public static String chooseNetworkInterface(Scanner scanner) throws Exception {
|
||||
<b class="nc"> List<String> ips = new ArrayList<>();</b>
|
||||
|
||||
<b class="nc"> Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();</b>
|
||||
<b class="nc"> while (interfaces.hasMoreElements()) {</b>
|
||||
<b class="nc"> NetworkInterface ni = interfaces.nextElement();</b>
|
||||
<b class="nc"> if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue;</b>
|
||||
|
||||
<b class="nc"> Enumeration<InetAddress> addresses = ni.getInetAddresses();</b>
|
||||
<b class="nc"> while (addresses.hasMoreElements()) {</b>
|
||||
<b class="nc"> InetAddress addr = addresses.nextElement();</b>
|
||||
<b class="nc"> if (addr instanceof Inet4Address) {</b>
|
||||
<b class="nc"> System.out.println("[" + ips.size() + "] " + ni.getDisplayName()</b>
|
||||
<b class="nc"> + " -> " + addr.getHostAddress());</b>
|
||||
<b class="nc"> ips.add(addr.getHostAddress());</b>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<b class="nc"> if (ips.isEmpty()) throw new Exception("No active network interface available");</b>
|
||||
|
||||
<b class="nc"> if (ips.size() == 1) {</b>
|
||||
<b class="nc"> System.out.println("One interface found, using: " + ips.get(0));</b>
|
||||
<b class="nc"> return ips.get(0);</b>
|
||||
}
|
||||
|
||||
<b class="nc"> System.out.print("Choose interface: ");</b>
|
||||
<b class="nc"> int choice = Integer.parseInt(scanner.nextLine().trim());</b>
|
||||
<b class="nc"> return ips.get(choice);</b>
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var msie = false, msie9 = false;
|
||||
/*@cc_on
|
||||
msie = true;
|
||||
@if (@_jscript_version >= 9)
|
||||
msie9 = true;
|
||||
@end
|
||||
@*/
|
||||
|
||||
if (!msie || msie && msie9) {
|
||||
hljs.highlightAll()
|
||||
hljs.initLineNumbersOnLoad();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<div style="float:right;">generated on 2026-06-14 21:53</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user