diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
index cac72e65..691e2087 100644
--- a/.github/badges/jacoco.svg
+++ b/.github/badges/jacoco.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index da655e64..5b92e583 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -63,12 +63,12 @@ jobs:
echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
echo "branch coverage = ${{ steps.jacoco.outputs.branches }}"
- - name: Commit the badge (if it changed)
- run: |
- if [[ `git status --porcelain **/jacoco.svg` ]]; then
- git config --global user.name 'mattteochen'
- git config --global user.email 'kaiximatteo.chen@mail.polimi.it'
- git add -A **/jacoco.svg
- git commit -m "[Dev] Autogenerated JaCoCo coverage badge"
- git push --force https://mattteochen:${{ secrets.BOT_ACTIONS_KEY }}@github.com/mattteochen/IS23-AM10.git
- fi
+ # - name: Commit the badge (if it changed)
+ # run: |
+ # if [[ `git status --porcelain **/jacoco.svg` ]]; then
+ # git config --global user.name 'mattteochen'
+ # git config --global user.email 'kaiximatteo.chen@mail.polimi.it'
+ # git add -A **/jacoco.svg
+ # git commit -m "[Dev] Autogenerated JaCoCo coverage badge"
+ # git push --force https://mattteochen:${{ secrets.BOT_ACTIONS_KEY }}@github.com/mattteochen/IS23-AM10.git
+ # fi
diff --git a/.gitignore b/.gitignore
index 015e08ae..10be2e2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,8 @@
.mtj.tmp/
# Package Files #
-*.jar
+target/*.jar
+!deliverables/is23am10-0.0.1-jar-with-dependencies.jar
*.war
*.nar
*.ear
@@ -38,4 +39,4 @@ hs_err_pid*
.devcontainer/
Dockerfile
-.DS_Store
\ No newline at end of file
+.DS_Store
diff --git a/README.md b/README.md
index 8a3cf3d4..e64f7839 100644
--- a/README.md
+++ b/README.md
@@ -63,26 +63,7 @@ Run the app with the following command line arguments:
---
## Development
-Project works fine as a stand-alone Maven project. Can be opened in any IDE, but a Docker container with ready-to-code extensions is also provided. If you want to use it follow these instructions:
-
-- Open the project with VSCode Developer Docker [Container](https://code.visualstudio.com/docs/devcontainers/containers), hence build the container (for MacOS users `cmd+shift+p` and type "Build Container").
-- There are two options to run tests:
- - Use `Java Test Runner` extension: Enable Junit testing with `Java Test Runner` extension that you will find in the tool bar, it will ask you to choose the version to download, select `JUnit Jupiter`.
- - Use Maven test runner embedded in the available plugin.
-
-### Code format
-Red Hat auto code formatter is available. Please run `cmd+shift+p` + `Format document with` and select `Red Hat` option before committing.
-Universal code format is required to maintain an unique format style.
-
-You can also enable live format compliance check with the `Checkstyle` [extension](https://marketplace.visualstudio.com/items?itemName=shengchen.vscode-checkstyle).
-Configuration:
-- `cmd+shift+p` + `Set the Checkstyle Version` -> select `built-in`
-- `cmd+shift+p` + `Set the Checkstyle Configuration File` -> select `Google's Style`
-`Checkstyle` should live check you code format.
-
-### Live bug checks
-Sonar lint extensions is available (auto enabled) to detect [issues](https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarlint-vscode) during the development.
-Please follow all the best practices.
+Project works fine as a stand-alone Maven project.
### Generate Jacoco test coverage report
To generate test coverage report launch JaCoCo with the following command from `/is23am10`:
diff --git a/deliverables/coverage/coverage_report.png b/deliverables/coverage/coverage_report.png
new file mode 100644
index 00000000..d07b89bb
Binary files /dev/null and b/deliverables/coverage/coverage_report.png differ
diff --git a/deliverables/coverage/jacoco/index.html b/deliverables/coverage/jacoco/index.html
new file mode 100644
index 00000000..ad5f0b30
--- /dev/null
+++ b/deliverables/coverage/jacoco/index.html
@@ -0,0 +1 @@
+is23am10
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/ANSICodes.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/ANSICodes.html
new file mode 100644
index 00000000..963e81ca
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/ANSICodes.html
@@ -0,0 +1 @@
+ANSICodes
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/ANSICodes.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/ANSICodes.java.html
new file mode 100644
index 00000000..31414e98
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/ANSICodes.java.html
@@ -0,0 +1,78 @@
+ANSICodes.java
package it.polimi.is23am10.client.userinterface.helpers;
+
+public final class ANSICodes {
+
+ // Reset
+ public static final String RESET = "\033[0m"; // Text Reset
+
+ // Regular Colors
+ public static final String BLACK = "\033[0;30m"; // BLACK
+ public static final String RED = "\033[0;31m"; // RED
+ public static final String GREEN = "\033[0;32m"; // GREEN
+ public static final String YELLOW = "\033[0;33m"; // YELLOW
+ public static final String BLUE = "\033[0;34m"; // BLUE
+ public static final String PURPLE = "\033[0;35m"; // PURPLE
+ public static final String CYAN = "\033[0;36m"; // CYAN
+ public static final String WHITE = "\033[0;37m"; // WHITE
+
+ // Bold
+ public static final String BLACK_BOLD = "\033[1;30m"; // BLACK
+ public static final String RED_BOLD = "\033[1;31m"; // RED
+ public static final String GREEN_BOLD = "\033[1;32m"; // GREEN
+ public static final String YELLOW_BOLD = "\033[1;33m"; // YELLOW
+ public static final String BLUE_BOLD = "\033[1;34m"; // BLUE
+ public static final String PURPLE_BOLD = "\033[1;35m"; // PURPLE
+ public static final String CYAN_BOLD = "\033[1;36m"; // CYAN
+ public static final String WHITE_BOLD = "\033[1;37m"; // WHITE
+
+ // Underline
+ public static final String BLACK_UNDERLINED = "\033[4;30m"; // BLACK
+ public static final String RED_UNDERLINED = "\033[4;31m"; // RED
+ public static final String GREEN_UNDERLINED = "\033[4;32m"; // GREEN
+ public static final String YELLOW_UNDERLINED = "\033[4;33m"; // YELLOW
+ public static final String BLUE_UNDERLINED = "\033[4;34m"; // BLUE
+ public static final String PURPLE_UNDERLINED = "\033[4;35m"; // PURPLE
+ public static final String CYAN_UNDERLINED = "\033[4;36m"; // CYAN
+ public static final String WHITE_UNDERLINED = "\033[4;37m"; // WHITE
+
+ // Background
+ public static final String BLACK_BACKGROUND = "\033[40m"; // BLACK
+ public static final String RED_BACKGROUND = "\033[41m"; // RED
+ public static final String GREEN_BACKGROUND = "\033[42m"; // GREEN
+ public static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOW
+ public static final String BLUE_BACKGROUND = "\033[44m"; // BLUE
+ public static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLE
+ public static final String CYAN_BACKGROUND = "\033[46m"; // CYAN
+ public static final String WHITE_BACKGROUND = "\033[47m"; // WHITE
+
+ // High Intensity
+ public static final String BLACK_BRIGHT = "\033[0;90m"; // BLACK
+ public static final String RED_BRIGHT = "\033[0;91m"; // RED
+ public static final String GREEN_BRIGHT = "\033[0;92m"; // GREEN
+ public static final String YELLOW_BRIGHT = "\033[0;93m"; // YELLOW
+ public static final String BLUE_BRIGHT = "\033[0;94m"; // BLUE
+ public static final String PURPLE_BRIGHT = "\033[0;95m"; // PURPLE
+ public static final String CYAN_BRIGHT = "\033[0;96m"; // CYAN
+ public static final String WHITE_BRIGHT = "\033[0;97m"; // WHITE
+
+ // Bold High Intensity
+ public static final String BLACK_BOLD_BRIGHT = "\033[1;90m"; // BLACK
+ public static final String RED_BOLD_BRIGHT = "\033[1;91m"; // RED
+ public static final String GREEN_BOLD_BRIGHT = "\033[1;92m"; // GREEN
+ public static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";// YELLOW
+ public static final String BLUE_BOLD_BRIGHT = "\033[1;94m"; // BLUE
+ public static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";// PURPLE
+ public static final String CYAN_BOLD_BRIGHT = "\033[1;96m"; // CYAN
+ public static final String WHITE_BOLD_BRIGHT = "\033[1;97m"; // WHITE
+
+ // High Intensity backgrounds
+ public static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACK
+ public static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// RED
+ public static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREEN
+ public static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOW
+ public static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUE
+ public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLE
+ public static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYAN
+ public static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITE
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CLIStrings.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CLIStrings.html
new file mode 100644
index 00000000..e825a256
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CLIStrings.html
@@ -0,0 +1 @@
+CLIStrings
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CLIStrings.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CLIStrings.java.html
new file mode 100644
index 00000000..960cb054
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CLIStrings.java.html
@@ -0,0 +1,110 @@
+CLIStrings.java
package it.polimi.is23am10.client.userinterface.helpers;
+
+import java.util.Map;
+
+/**
+ * An helper class containing all the strings to display
+ * to the user through UserInterface.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class CLIStrings {
+ public final static String welcomeString =
+ """
+ ███ ███ ██ ██ ███████ ██ ██ ███████ ██ ███████ ██ ███████
+ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ██ ████ ██ ████ ███████ ███████ █████ ██ █████ ██ █████
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ██ ██ ██ ███████ ██ ██ ███████ ███████ ██ ██ ███████
+
+ Politecnico di Milano - Software Engineering Project - Group IS23AM10
+
+ """;
+ public final static String joinOrCreateString = "Great! Please choose how to proceed. You can create a new game or join an existing one.";
+ public final static String joinExisting = " - To join an existing game run the command `j [idx]` specifying by the index of the game.";
+ public final static String createGame = " - To create a new game run the command `c [2-4]` specifying the number of players.";
+ public final static String noGamesString = "No available game to join at the moment. Please create one.";
+ public final static String insertPlayerNameString = "Insert your player name here:";
+ public final static String listGamesString = "Here a list of the available games. Join one by typing the relative index.";
+ public final static String disconnectedPlayers = " (%d disconnected)";
+ public final static String availableGameString = "[%d] - %d/%d joined%s - GameId: %s";
+ public final static String currentStateString = "Current state of the game:";
+ public final static String gameOverString = "Game is over. Here the leaderboard:";
+ public final static String playerScoreString = "%s - Score: %d";
+ public final static String winnerString = "WINNER: %s";
+ public final static String lastRoundString = "Attention! Someone already completed their Bookshelf: this is last round";
+ public final static String nowPlaying = "Now playing: %s";
+ public final static String moveTilesInviteString = "Now make your move specifying the `XY` of the tile you want to pick from board (max 3) followed by the column index of the bookshelf you want to put the tile in.";
+ public final static String moveTilesExampleString = "E.g. `move 12 22 25 A` moves the tiles with coordinates (1,2),(2,5) and (5,7) to the bookshelf first three available spots in your bookshelf column 'A' in that order.";
+ public final static String messageStringReceiver = "(%s -> You): %s";
+ public final static String messageStringSender = "(You -> %s): %s";
+ public final static String broadcastMessageString = "(%s -> All): %s";
+ public final static String errorMessage = "(Server -> You): %s";
+ public final static String broadcastErrorString = "(Server -> All): %s";
+ public final static String bottomPaddingBoard = "\t⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛\n\n";
+ public final static String topPaddingBoard = "\t⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛" + ANSICodes.RED_BOLD + "\sY\t" + ANSICodes.RESET;
+ public final static String paddingBookshelf = "\t⬛⬛⬛⬛⬛⬛⬛\t";
+ public final static String indexBoard = ANSICodes.RED_BOLD + "\t\sX" + ANSICodes.RESET + " 0 1 2 3 4 5 6 7 8\t";
+ public final static String verticalBoardIndex = "⬛\s%d\t";
+ public final static String indexBookshelf = "\t\s\sA B C D E\s\t";
+ public final static String boardStatus = "\n\tGame board status:\t\n";
+ public final static String playerIdx = "\tPlayer #%d\t";
+ public final static String blackLargeSquare = "";
+ public final static String tabBlackSquare = "\t⬛";
+ public final static String blackSquareTab = "⬛\t";
+ public final static String tab = "\t";
+ public final static String newLine = "\n";
+ public final static String doubleNewLine = "\n\n";
+ public final static String tableHeader1 = "\t%-" + OutputWrapper.XXS_PADDING + "s | %-" + OutputWrapper.XS_PADDING + "s | %-";
+ public final static String tableHeader2 = "s | %-" + OutputWrapper.M_PADDING + "s | %-" + OutputWrapper.XL_PADDING
+ + "s | %-" + OutputWrapper.XXL_PADDING + "s | %-" + 14 + "s | %-" + OutputWrapper.M_PADDING + "s | %-"
+ + OutputWrapper.S_PADDING + "s ";
+ public final static String N = "No.";
+ public final static String status = "Status";
+ public final static String player = "Player";
+ public final static String role = "Role";
+ public final static String scoreBlockPoints = "Score Block Points";
+ public final static String bookshelfPoints = "Bookshelf Points";
+ public final static String extraPoints = "Extra Points";
+ public final static String totalScore = "Total Score";
+ public final static String privatePoints = "Private Points";
+ public final static String tableLines1 = "\t%-" + OutputWrapper.XXS_PADDING + "s | %-" + OutputWrapper.XS_PADDING + "s | %-";
+ public final static String tableLines2 = "s | %-" + OutputWrapper.M_PADDING + "s | %-" + OutputWrapper.XL_PADDING
+ + "s | %-" + OutputWrapper.XXL_PADDING + "s | %-" + OutputWrapper.L_PADDING + "s | %-" + OutputWrapper.M_PADDING
+ + "s | %-" + OutputWrapper.S_PADDING + "s";
+ public final static String firstPlayer = "First Player";
+ public final static String yourTurn = "Your turn";
+ public final static String tableBody1 = "\t#%-" + OutputWrapper.XXS_PADDING + "d| %-" + OutputWrapper.XS_PADDING + "s | %-";
+ public final static String tableBody2 = "s | %-" + OutputWrapper.M_PADDING + "s | %-" + OutputWrapper.XL_PADDING
+ + "d | %-" + OutputWrapper.XXL_PADDING + "d | %-" + OutputWrapper.L_PADDING + "s | %-" + OutputWrapper.M_PADDING
+ + "d | %-" + OutputWrapper.S_PADDING + "s";
+ public final static String line = "-";
+ public final static String bookshelfError = "Wrong bookshelf coordinates!";
+ public final static String inputError = "Can't read your commands. Please re-join.";
+ public final static String sharedCardsHeader = "\t%-" + OutputWrapper.XL_PADDING + "s | %-";
+ public final static String sharedCardHeaderDescription = "s ";
+ public final static String sharedCardsBody = "\t#%-" + OutputWrapper.XL_PADDING + "d| %-";
+ public final static String sharedCardsBodyDescription = "s ";
+ public final static String idx = "Shared Card Idx.";
+ public final static String description = "Card Description";
+ public final static String privateCardIdx = "\tPrivate Card #%d\t";
+ public final static Map<Integer, String> sharedPatternsDesc = Map.ofEntries(
+ Map.entry(1, "Two groups of four tiles of the same type forming a 2x2 square shape. The tile type of the two squares has to be the same."),
+ Map.entry(2, "At least two full columns (filled with six tiles), having tiles of all different types."),
+ Map.entry(3, "Four separated groups made of four adjacent tiles of the same type. The tile's type of different groups can be different."),
+ Map.entry(4, "Six separated groups made of two adjacent tiles of the same type. The tile type of different groups can be different."),
+ Map.entry(5, "At least three full columns (filled with six tiles), having maximum three different tile types per column."),
+ Map.entry(6, "At least two full rows (filled with five tiles), having tiles of all different types."),
+ Map.entry(7, "At least four full rows (filled with five tiles), having maximum three different tile types per row."),
+ Map.entry(8, "The four tiles at the corners of the bookshelf are of the same type."),
+ Map.entry(9, "At least eight tiles of the same type. There are no restrictions concerning their positions."),
+ Map.entry(10, "Five tiles of the same type, forming an X shape."),
+ Map.entry(11, "Five tiles of the same type forming a diagonal."),
+ Map.entry(12, "Five columns with ascending or descending height. Starting from the first or the last column, the next column has to have one tile more. The tile types are not considered.")
+ );
+ public final static String waitingForPlayers = "Waiting for game to be full before starting, %d/%d joined. GameID: %s";
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CommandsBuilder.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CommandsBuilder.html
new file mode 100644
index 00000000..ab24df2b
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CommandsBuilder.html
@@ -0,0 +1 @@
+CommandsBuilder
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CommandsBuilder.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CommandsBuilder.java.html
new file mode 100644
index 00000000..230e6bc0
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/CommandsBuilder.java.html
@@ -0,0 +1,83 @@
+CommandsBuilder.java
package it.polimi.is23am10.client.userinterface.helpers;
+
+/**
+ * Game mode string constants.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class CommandsBuilder {
+
+ /**
+ * The command string for creating a game.
+ */
+ public final static String CREATE_GAME = "c";
+
+ /**
+ * The command string for joining a game.
+ */
+ public final static String JOIN_GAME = "j";
+
+ /**
+ * The command string for moving a tile.
+ */
+ public final static String MOVE_TILE = "move";
+
+ /**
+ * The command string for sending a message
+ */
+ public final static String SEND_MESSAGE = "chat";
+
+ /**
+ * Builds a create game command with the specified player name.
+ *
+ * @param pn The player name.
+ * @return The create game command string.
+ */
+ public final static String buildCreateGameCmd(String pn) {
+ return CREATE_GAME + " " + pn;
+ }
+
+ /**
+ * Builds a join game command with the specified game ID.
+ *
+ * @param id The game ID.
+ * @return The join game command string.
+ */
+ public final static String buildJoinGameCmd(String id) {
+ return JOIN_GAME + " " + id;
+ }
+
+ /**
+ * Builds a move tile command with the specified move.
+ *
+ * @param move The move to be performed.
+ * @return The move tile command string.
+ */
+ public final static String moveTileCmd(String move) {
+ return MOVE_TILE + " " + move;
+ }
+
+ /**
+ * Builds a chat message and sends it.
+ * The syntax for the chat messages is the following:
+ * Broadcast: "textContent"
+ * To player: "> playerName textContent"
+ *
+ * @param msg The msg to be sent.
+ * @return The send message command string.
+ */
+ public final static String sendChatMessageCmd(String msg) {
+ if(msg.startsWith(">")){
+ String receiverName = msg.stripLeading().split(" ")[1];
+ int indexToTrim = msg.stripLeading().indexOf(" ", msg.stripLeading().indexOf(" ") + 1);
+ String msgTextTrimmed = msg.stripLeading().trim().substring(indexToTrim);
+ return SEND_MESSAGE + " " + receiverName + " \"" + msgTextTrimmed + "\" ";
+ }
+ return SEND_MESSAGE + " \"" + msg + "\" ";
+ }
+
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper$OutputLevel.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper$OutputLevel.html
new file mode 100644
index 00000000..338f48ef
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper$OutputLevel.html
@@ -0,0 +1 @@
+OutputWrapper.OutputLevel
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper.html
new file mode 100644
index 00000000..970c05f6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper.html
@@ -0,0 +1 @@
+OutputWrapper
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper.java.html
new file mode 100644
index 00000000..5587b0e4
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/OutputWrapper.java.html
@@ -0,0 +1,466 @@
+OutputWrapper.java
package it.polimi.is23am10.client.userinterface.helpers;
+
+import java.io.Serializable;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+import it.polimi.is23am10.server.model.items.board.Board;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.tile.Tile;
+import it.polimi.is23am10.server.model.items.tile.Tile.TileType;
+import it.polimi.is23am10.server.network.virtualview.VirtualPlayer;
+import it.polimi.is23am10.server.network.virtualview.VirtualView;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+
+/**
+ * An helper class with all the methods needed to properly print
+ * CLI messages for client. It wraps System.out enriching it with colors.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class OutputWrapper implements Serializable {
+
+ /**
+ * Enum containing the output types.
+ */
+ public enum OutputLevel {
+ DEBUG,
+ INFO,
+ CHAT,
+ WARNING,
+ ERROR,
+ CRITICAL
+ }
+
+ private final Integer CLEAN_SCREEN_REPS = 100;
+
+ // Variable padding sizes for string formatting.
+ public static final Integer XXS_PADDING = 4;
+ public static final Integer XS_PADDING = 6;
+ public static final Integer S_PADDING = 11;
+ public static final Integer M_PADDING = 12;
+ public static final Integer L_PADDING = 14;
+ public static final Integer XL_PADDING = 16;
+ public static final Integer XXL_PADDING = 18;
+ public static final Integer HUNDRED_PADDING = 100;
+ public static final Integer MIN_PADDING_FOR_NAMES = 15;
+
+ /**
+ * A flag relative to the instance of {@link OutputWrapper}
+ * set with constructor, used for showing or hiding debug lines.
+ */
+ private boolean showDebug = false;
+
+ /**
+ * A map that associates output levels to their string
+ * template. A template uses ANSI codes to display colors.
+ */
+ private final Map<OutputLevel, String> debugTemplates = Map.of(
+ OutputLevel.DEBUG, ANSICodes.BLUE + "🔎 %s" + ANSICodes.RESET,
+ OutputLevel.INFO, "%s",
+ OutputLevel.CHAT, ANSICodes.GREEN + "💬 %s" + ANSICodes.RESET,
+ OutputLevel.WARNING, ANSICodes.YELLOW + "🔶 %s" + ANSICodes.RESET,
+ OutputLevel.ERROR, ANSICodes.RED + "🔴 %s" + ANSICodes.RESET,
+ OutputLevel.CRITICAL, ANSICodes.PURPLE_BACKGROUND + "⚫️ %s" + ANSICodes.RESET);
+
+ /**
+ * A map that associates TileType to their Java source code encoding.
+ *
+ */
+ private static final Map<TileType, String> emojiMap = Map.of(
+ TileType.BOOK, "📔", // NOTEBOOK WITH DECORATIVE COVER
+ TileType.CAT, "🐈", // CAT
+ TileType.FRAME, "🟧", // ORANGE SQUARE ,
+ TileType.GAME, "🎮", // VIDEO GAME,
+ TileType.PLANT, "🍀", // FOUR LEAF CLOVER
+ TileType.TROPHY, "🏆", // TROPHY
+ TileType.EMPTY, "⬜" // WHITE LARGE SQUARE
+ );
+
+ /**
+ * A map that associates a boolean referring to the online/offline player status
+ * to green/red emoticon. *
+ */
+ private static final Map<Boolean, String> onlineOffline = Map.of(
+ true, "🟢", // ONLINE
+ false, "🔴" // OFFLINE
+ );
+
+ /**
+ * Public constructor for OutputWrapper.
+ *
+ * @param showDebug instance-specific debug flag.
+ */
+ public OutputWrapper(boolean showDebug) {
+ this.showDebug = showDebug;
+ }
+
+ /**
+ * Prints a debug line on console.
+ *
+ * @param string Debug string to display.
+ * @param cleanFirst Flag to set if message should be preceded by a console
+ * clean.
+ */
+ public void debug(String string, boolean cleanFirst) {
+ if (showDebug) {
+ printString(OutputLevel.DEBUG, string, cleanFirst);
+ }
+ }
+
+ /**
+ * Prints a info line on console.
+ *
+ * @param string Info string to display.
+ * @param cleanFirst Flag to set if message should be preceded by a console
+ * clean.
+ */
+ public void info(String string, boolean cleanFirst) {
+ printString(OutputLevel.INFO, string, cleanFirst);
+ }
+
+ /**
+ * Private method to print repeated sequence of char.
+ */
+ private static String repeatString(String str, int count) {
+ return String.join("", Collections.nCopies(count, str));
+ }
+
+ /**
+ * Print the current game status on console.
+ *
+ * @param vw The virtualView.
+ * @param cleanFirst Flag to set if message should be preceded by a console
+ * clean.
+ */
+ public void show(VirtualView vw, boolean cleanFirst) {
+
+ // Print game board.
+ Board gameBoard = vw.getGameBoard();
+
+ // Header.
+ info(CLIStrings.boardStatus, false);
+
+ // Index.
+ info(CLIStrings.indexBoard, false);
+
+ // Top padding.
+ info(CLIStrings.topPaddingBoard, false);
+
+ // Body.
+ for (int i = 0; i < Board.BOARD_GRID_ROWS; i++) {
+ StringBuilder row = new StringBuilder();
+ row.append(CLIStrings.tabBlackSquare);
+ for (int j = 0; j < Board.BOARD_GRID_COLS; j++) {
+ Tile tile = gameBoard.getBoardGrid()[i][j];
+ row.append(emojiMap.get(tile.getType()));
+ }
+ row.append(String.format(CLIStrings.verticalBoardIndex, (i)));
+ info(row.toString(), false);
+ }
+
+ // Bottom padding.
+ info(CLIStrings.bottomPaddingBoard, false);
+
+ List<VirtualPlayer> players = vw.getPlayers();
+
+ int maxLength = Math.max(
+ players.stream()
+ .mapToInt(p -> p.getPlayerName().length())
+ .max().getAsInt(),
+ MIN_PADDING_FOR_NAMES);
+
+ // Print Player Status.
+ StringBuilder playersStatus = new StringBuilder();
+ // Header.
+ playersStatus
+ .append(String.format(CLIStrings.tableHeader1 + maxLength + CLIStrings.tableHeader2,
+ CLIStrings.N, CLIStrings.status, CLIStrings.player, CLIStrings.role,
+ CLIStrings.bookshelfPoints, CLIStrings.scoreBlockPoints, CLIStrings.privatePoints, CLIStrings.extraPoints,
+ CLIStrings.totalScore))
+ .append(CLIStrings.newLine)
+ .append(
+ String.format(CLIStrings.tableLines1 + maxLength + CLIStrings.tableLines2,
+ repeatString(CLIStrings.line, XXS_PADDING), repeatString(CLIStrings.line, XS_PADDING),
+ repeatString(CLIStrings.line, maxLength), repeatString(CLIStrings.line, M_PADDING),
+ repeatString(CLIStrings.line, XL_PADDING), repeatString(CLIStrings.line, XXL_PADDING),
+ repeatString(CLIStrings.line, L_PADDING), repeatString(CLIStrings.line, M_PADDING),
+ repeatString(CLIStrings.line, S_PADDING)))
+ .append(CLIStrings.newLine);
+
+ // Body.
+ int pos = 1;
+ for (VirtualPlayer vp : players) {
+
+ String status = onlineOffline.get(vp.getIsConnected());
+ String player = vp.getPlayerName();
+ String role = "";
+ String totalScoreString = vp.getScore().getStringTotalScore();
+ int extraPoints = vp.getScore().getExtraPoint();
+ int bookshelfPoints = vp.getScore().getBookshelfPoints();
+ int scoreBlocksPoint = vp.getScore().getScoreBlockPoints();
+ String privatePointsString = (vp.getScore().getPrivatePoints() == -1) ? "?" : vp.getScore().getPrivatePoints().toString();
+
+ if (vw.getFirstPlayer().equals(vp)) {
+ role = CLIStrings.firstPlayer;
+ }
+ if (vw.getActivePlayer().equals(vp)) {
+ role = CLIStrings.yourTurn;
+ }
+
+ playersStatus
+ .append(String.format(CLIStrings.tableBody1 + maxLength + CLIStrings.tableBody2,
+ pos++, status, player, role, bookshelfPoints, scoreBlocksPoint, privatePointsString, extraPoints, totalScoreString))
+ .append(CLIStrings.newLine);
+ }
+ info(playersStatus.toString(), false);
+
+ // Print Bookshelfs.
+ // Name.
+ StringBuilder name = new StringBuilder();
+ name.append(CLIStrings.newLine); // New Line for aesthetic purpose.
+ for (int i = 1; i < players.size() + 1; i++) {
+ name.append(String.format(CLIStrings.playerIdx, pos));
+ }
+ info(name.toString(), false);
+
+ // Index.
+ StringBuilder playerIndex = new StringBuilder();
+ playerIndex.append(CLIStrings.newLine); // New Line for aesthetic purpose.
+ for (VirtualPlayer vp : players) {
+ playerIndex.append(CLIStrings.indexBookshelf);
+ }
+ info(playerIndex.toString(), false);
+
+ // Top padding.
+ StringBuilder topPadding = new StringBuilder();
+ for (VirtualPlayer vp : players) {
+ topPadding.append(CLIStrings.paddingBookshelf);
+ }
+ info(topPadding.toString(), false);
+
+ // Body.
+ for (int i = 0; i < Bookshelf.BOOKSHELF_ROWS; i++) {
+ StringBuilder row = new StringBuilder();
+ for (VirtualPlayer vp : players) {
+ Bookshelf b = vp.getBookshelf();
+ row.append(CLIStrings.tabBlackSquare);
+ for (int j = 0; j < Bookshelf.BOOKSHELF_COLS; j++) {
+ try {
+ row.append(emojiMap.get(b.getBookshelfGridAt(i, j).getType()));
+ } catch (BookshelfGridColIndexOutOfBoundsException
+ | BookshelfGridRowIndexOutOfBoundsException
+ | NullIndexValueException e) {
+ error(CLIStrings.bookshelfError, true);
+ }
+ }
+ row.append(CLIStrings.blackSquareTab);
+ }
+ info(row.toString(), false);
+ }
+
+ // Bottom padding.
+ StringBuilder bottomPadding = new StringBuilder();
+ for (VirtualPlayer vp : players) {
+ bottomPadding.append(CLIStrings.paddingBookshelf);
+ }
+ bottomPadding.append(CLIStrings.doubleNewLine); // New Line for aesthetic purpose.
+ info(bottomPadding.toString(), false);
+
+ // Print Shared Cards descriptions.
+ StringBuilder sharedCards = new StringBuilder();
+
+ maxLength = Math.max(vw.getSharedCards().stream()
+ .mapToInt(p -> CLIStrings.sharedPatternsDesc.get(p).length())
+ .max().getAsInt(),HUNDRED_PADDING);
+
+ // Header.
+ sharedCards
+ .append(String.format(CLIStrings.sharedCardsHeader + maxLength + CLIStrings.sharedCardHeaderDescription ,
+ CLIStrings.idx, CLIStrings.description))
+ .append(CLIStrings.newLine)
+ .append(
+ String
+ .format(String.format(CLIStrings.sharedCardsHeader + maxLength + CLIStrings.sharedCardHeaderDescription,
+ repeatString(CLIStrings.line, XL_PADDING), repeatString(CLIStrings.line, maxLength))))
+ .append(CLIStrings.newLine);
+
+ // Body.
+ for (Integer sc : vw.getSharedCards()) {
+ sharedCards
+ .append(String.format(CLIStrings.sharedCardsBody + maxLength + CLIStrings.sharedCardsBodyDescription, sc,
+ CLIStrings.sharedPatternsDesc.get(sc)))
+ .append(CLIStrings.newLine);
+ }
+ info(sharedCards.toString(), false);
+
+ // Print Private Cards.
+ StringBuilder privateCards = new StringBuilder();
+ privateCards.append(CLIStrings.newLine); // New Line for esthetic purpose.
+ pos = 1;
+ // Header
+ for (VirtualPlayer vp : players) {
+ privateCards.append(String.format(CLIStrings.privateCardIdx, pos++));
+ }
+ info(privateCards.toString(), false);
+
+ // Index.
+ StringBuilder bsIndex = new StringBuilder();
+ bsIndex.append(CLIStrings.newLine); // New Line for esthetic purpose.
+ for (VirtualPlayer vp : players) {
+ bsIndex.append(CLIStrings.indexBookshelf);
+ }
+ info(bsIndex.toString(), false);
+
+ // Top padding.
+ topPadding = new StringBuilder();
+ for (VirtualPlayer vp : players) {
+ topPadding.append(CLIStrings.paddingBookshelf);
+ }
+ info(topPadding.toString(), false);
+
+ // Body.
+ for (int i = 0; i < Bookshelf.BOOKSHELF_ROWS; i++) {
+ StringBuilder row = new StringBuilder();
+ for (VirtualPlayer vp : players) {
+ Bookshelf b = PrivatePatternsHelper.getBookshelf(vp.getPrivateCardIndex());
+ row.append(CLIStrings.tabBlackSquare);
+ for (int j = 0; j < Bookshelf.BOOKSHELF_COLS; j++) {
+ try {
+ row.append(emojiMap.get(b.getBookshelfGridAt(i, j).getType()));
+ } catch (BookshelfGridColIndexOutOfBoundsException
+ | BookshelfGridRowIndexOutOfBoundsException
+ | NullIndexValueException e) {
+ error(CLIStrings.bookshelfError, true);
+ }
+ }
+ row.append(CLIStrings.blackSquareTab);
+ }
+ info(row.toString(), false);
+ }
+
+ // Bottom padding.
+ bottomPadding = new StringBuilder();
+ for (VirtualPlayer vp : players) {
+ bottomPadding.append(CLIStrings.paddingBookshelf);
+ }
+ bottomPadding.append(CLIStrings.doubleNewLine); // New Line for esthetic purpose.
+ info(bottomPadding.toString(), false);
+ }
+
+ /**
+ * Prints a chat message on console.
+ *
+ * @param string Message string to display.
+ * @param cleanFirst Flag to set if message should be preceded by a console
+ * clean.
+ */
+ public void chat(String string, boolean cleanFirst) {
+ printString(OutputLevel.CHAT, string, cleanFirst);
+ }
+
+ /**
+ * Prints a warning line on console.
+ *
+ * @param string Warning string to display.
+ * @param cleanFirst Flag to set if message should be preceded by a console
+ * clean.
+ */
+ public void warning(String string, boolean cleanFirst) {
+ printString(OutputLevel.WARNING, string, cleanFirst);
+ }
+
+ /**
+ * Prints a error line on console.
+ *
+ * @param string Error string to display.
+ * @param cleanFirst Flag to set if message should be preceded by a console
+ * clean.
+ */
+ public void error(String string, boolean cleanFirst) {
+ printString(OutputLevel.ERROR, string, cleanFirst);
+ }
+
+ /**
+ * Prints a critical error line on console.
+ *
+ * @param string Critical error string to display.
+ * @param cleanFirst Flag to set if message should be preceded by a console
+ * clean.
+ */
+ public void critical(String string, boolean cleanFirst) {
+ printString(OutputLevel.CRITICAL, string, cleanFirst);
+ }
+
+ /**
+ * Helper method to call to clean the console.
+ *
+ */
+ public void clean() {
+ for (int i = 0; i < CLEAN_SCREEN_REPS; i++) {
+ System.out.println();
+ }
+ }
+
+ /**
+ * Helper method to get current timestamp to show when in debug mode.
+ *
+ * @return Formatted timestamp as string.
+ */
+ private String getTimestamp() {
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
+ ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
+ return dtf.format(now);
+ }
+
+ /**
+ * Helper method used by tests to retrieve the string to be printed
+ * before actually printing it.
+ *
+ * @param level {@link OutputLevel} of the message.
+ * @param string The string of the message to display.
+ * @return The formatted string ready to be printed.
+ */
+ public String getString(OutputLevel level, String string) {
+ String template = debugTemplates.get(level);
+ if (showDebug) {
+ template = String.format("[%s] %s", getTimestamp(), template);
+ }
+ return String.format(template, string);
+ }
+
+ /**
+ * Public method to print a string. Used from CLI.
+ *
+ * @param level {@link OutputLevel} of the message.
+ * @param string The string of the message to display.
+ * @param cleanFirst Flag that resets the console before print if true.
+ */
+ public void printString(OutputLevel level, String string, boolean cleanFirst) {
+ if (cleanFirst) {
+ clean();
+ }
+ System.out.println(getString(level, string));
+ }
+
+ /**
+ * Setter for the debug flag.
+ *
+ * @param toSet Debug flag.
+ */
+ public void setDebug(boolean toSet) {
+ showDebug = toSet;
+ }
+
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/PrivatePatternsHelper.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/PrivatePatternsHelper.html
new file mode 100644
index 00000000..8f3280f9
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/PrivatePatternsHelper.html
@@ -0,0 +1 @@
+PrivatePatternsHelper
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/PrivatePatternsHelper.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/PrivatePatternsHelper.java.html
new file mode 100644
index 00000000..630c50ca
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/PrivatePatternsHelper.java.html
@@ -0,0 +1,47 @@
+PrivatePatternsHelper.java
package it.polimi.is23am10.client.userinterface.helpers;
+
+import java.util.Map;
+
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.WrongCharBookshelfStringException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.WrongLengthBookshelfStringException;
+
+/**
+ * Map containing the private bookshelf patterns associated with an index.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class PrivatePatternsHelper {
+ private final static Map<Integer, String> privatePatternBS = Map.ofEntries(
+ Map.entry(1, "PXFXXXXXXCXXXBXXGXXXXXXXXXXTXX"),
+ Map.entry(2, "XXXXXXPXXXCXGXXXXXXBXXXTXXXXXF"),
+ Map.entry(3, "XXXXXFXXGXXXPXXXCXXTXXXXXBXXXX"),
+ Map.entry(4, "XXXXGXXXXXTXFXXXXXPXXBCXXXXXXX"),
+ Map.entry(5, "XXXXXXTXXXXXXXXXFBXXXXXXPGXXCX"),
+ Map.entry(6, "XXTXCXXXXXXXXBXXXXXXXGXFXPXXXX"),
+ Map.entry(7, "CXXXXXXXFXXPXXXTXXXXXXXXGXXBXX"),
+ Map.entry(8, "XXXXFXCXXXXXTXXPXXXXXXXBXXXXGX"),
+ Map.entry(9, "XXGXXXXXXXXXCXXXXXXBXTXXPFXXXX"),
+ Map.entry(10, "XXXXTXGXXXBXXXXXXXCXXFXXXXXXPX"),
+ Map.entry(11, "XXPXXXBXXXGXXXXXXFXXXXXXCXXXTX"),
+ Map.entry(12, "XXBXXXPXXXXXFXXXXXTXXXXXGCXXXX"));
+
+ /**
+ * Public function used to retrieve a bookshelf from its
+ * private card.
+ *
+ * @param idx index of bookshelf.
+ * @return relative bookshelf.
+ */
+ public static Bookshelf getBookshelf(Integer idx) {
+ try {
+ return new Bookshelf(privatePatternBS.get(idx));
+ } catch (NullPointerException | WrongLengthBookshelfStringException | WrongCharBookshelfStringException e) {
+ return new Bookshelf();
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/index.html
new file mode 100644
index 00000000..bf806d38
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.client.userinterface.helpers
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/index.source.html
new file mode 100644
index 00000000..f5baab7d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface.helpers/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.client.userinterface.helpers
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/GraphicUserInterface.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/GraphicUserInterface.java.html
new file mode 100644
index 00000000..5fbc8113
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/GraphicUserInterface.java.html
@@ -0,0 +1,322 @@
+GraphicUserInterface.java
package it.polimi.is23am10.client.userinterface;
+
+import it.polimi.is23am10.client.Client;
+import it.polimi.is23am10.client.userinterface.guifactory.GuiFactory;
+import it.polimi.is23am10.client.userinterface.guifactory.GuiFactory.SCENE;
+import it.polimi.is23am10.server.model.game.Game.GameStatus;
+
+import java.io.Serializable;
+import java.util.List;
+
+import it.polimi.is23am10.server.network.messages.ChatMessage;
+import it.polimi.is23am10.server.network.messages.ErrorMessage;
+import it.polimi.is23am10.server.network.messages.ErrorMessage.ErrorSeverity;
+import it.polimi.is23am10.server.network.virtualview.VirtualView;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.scene.Scene;
+import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+
+/**
+ * A VirtualView pair class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+class VirtualViewPair {
+ /**
+ * The new {@link VirtualView}.
+ */
+ VirtualView newVw;
+
+ /**
+ * The old {@link VirtualView}.
+ */
+ VirtualView oldVw;
+
+ /**
+ * Notify the receiver that this pair is intended to be treated as null.
+ */
+ boolean isNull;
+
+ /**
+ * Constructor.
+ *
+ * @param o The old {@link VirtualView} instance.
+ * @param n The new {@link VirtualView} instance.
+ * @param in The isNull flag.
+ *
+ */
+ public VirtualViewPair(VirtualView o, VirtualView n, boolean in) {
+ newVw = n;
+ oldVw = o;
+ isNull = in;
+ }
+}
+
+class VirtualViewSceneHandler {
+
+ /**
+ * Custom lock object.
+ */
+ private static final Object stopGameSnapshotHandlerLock = new Object();
+
+ /**
+ * A list {@link VirtualViewPair} to be shown.
+ */
+ private static final BlockingQueue<VirtualViewPair> gameSnapshots = new LinkedBlockingQueue<VirtualViewPair>();
+
+ /**
+ * Boolean flag to stop the game scene handler thread.
+ */
+ private static boolean stopGameSnapshotHandler = false;
+
+ /**
+ * Setter for {@link VirtualViewSceneHandler#stopGameSnapshotHandler}
+ *
+ * @param flag The flag to be set.
+ */
+ protected static void setStopGameSnapshotHandler(boolean flag) {
+ synchronized(stopGameSnapshotHandlerLock) {
+ stopGameSnapshotHandler = flag;
+ }
+ }
+
+ /**
+ * Getter for {@link VirtualViewSceneHandler#stopGameSnapshotHandler}
+ *
+ * @return The flag status.
+ */
+ protected static boolean getStopGameSnapshotHandler() {
+ synchronized(stopGameSnapshotHandlerLock) {
+ return stopGameSnapshotHandler;
+ }
+ }
+
+ /**
+ * Add a new {@link VirtualViewPair} object to the {@link VirtualViewSceneHandler#gameSnapshots}.
+ *
+ * @param vwp The {@link VirtualViewPair}.
+ */
+ protected static void addVirtualViewPair(VirtualViewPair vwp) {
+ gameSnapshots.add(vwp);
+ }
+
+ protected static void notifyVirtualViewPairQueue() {
+ gameSnapshots.notifyAll();
+ }
+
+ /**
+ * Run the game scene handler thread.
+ *
+ */
+ protected static void runGameSnapShotHandler() {
+ new Thread(() -> {
+ while(!getStopGameSnapshotHandler()) {
+ VirtualViewPair vwp = null;
+ try {
+ vwp = gameSnapshots.take();
+ if (vwp != null) {
+ if (vwp.isNull) {
+ break;
+ }
+ if (vwp.newVw.getStatus() == GameStatus.ENDED) {
+ GuiFactory.stages.put(SCENE.END_GAME, GuiFactory.getEndGameScene(vwp.newVw));
+ GuiFactory.executeOnJavaFX(
+ () -> GuiFactory.mainStage.setScene(GuiFactory.stages.get(SCENE.END_GAME)));
+ } else {
+ if (vwp.newVw.getStatus() == GameStatus.WAITING_FOR_PLAYERS) {
+ GuiFactory.executeOnJavaFX(
+ () -> GuiFactory.mainStage.setScene(GuiFactory.stages.get(SCENE.WAIT_GAME)));
+ } else {
+ if (GuiFactory.stages.get(SCENE.GAME_SNAPSHOT) == null) {
+ GuiFactory.stages.put(SCENE.GAME_SNAPSHOT, GuiFactory.getGameSnapshotScene(vwp.newVw));
+ GuiFactory.executeOnJavaFX(
+ () -> GuiFactory.mainStage.setScene(GuiFactory.stages.get(SCENE.GAME_SNAPSHOT)));
+ } else {
+ StackPane root = (StackPane) GuiFactory.mainStage.getScene().getRoot();
+ final VirtualView oldVw = vwp.oldVw;
+ final VirtualView newVw = vwp.newVw;
+ GuiFactory.executeOnJavaFX(() -> GuiFactory.GameSnapshotFactory.updateGameWidget(root, oldVw, newVw));
+ }
+ }
+ }
+ }
+ } catch(InterruptedException e) {
+
+ } catch(NullPointerException e) {
+
+ }
+ }
+ }).start();
+ }
+}
+
+/**
+ * A client interface using a graphic UI as I/O.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class GraphicUserInterface extends Application implements UserInterface, Serializable {
+
+ /**
+ * Queue containing all the inputs the user sent trough readline. Content to be
+ * consumed by client
+ * controller on premise.
+ */
+ private static final BlockingQueue<String> userInputQueue = new LinkedBlockingQueue<>();
+
+ /**
+ * Constructor.
+ */
+ public GraphicUserInterface() {
+ VirtualViewSceneHandler.runGameSnapShotHandler();
+ }
+
+ /**
+ * Add a new message to the input list queue.
+ *
+ * @param msg The message to be added.
+ */
+ public static void addMsgQueue(String msg) {
+ userInputQueue.add(msg);
+ }
+
+ /**
+ * Retrirve the head of the message queue.
+ *
+ * @return The oldest message.
+ */
+ public static String takeMsgQueue() {
+ try {
+ return userInputQueue.isEmpty() ? null : userInputQueue.take();
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void start(Stage stage) {
+ GuiFactory.mainStage = stage;
+ stage.setResizable(false);
+ Map<SCENE, Scene> m = Map.of(
+ SCENE.SPLASH_SCREEN, GuiFactory.getSplashScreenScene(),
+ SCENE.ENTER_GAME_SELECTION, GuiFactory.getEnterGameSelectionScene(),
+ SCENE.CREATE_GAME, GuiFactory.getCreateNewGameSelectionScene(),
+ SCENE.WAIT_GAME, GuiFactory.getWaitGameScene()
+ // some scenes require the {@link VirtualView}, they are built later
+ );
+ GuiFactory.stages.putAll(m);
+
+ stage.setTitle("MyShelfie - IS23AM10");
+ stage.setScene(GuiFactory.stages.get(SCENE.SPLASH_SCREEN));
+ stage.setOnCloseRequest(e -> {
+ Client.setForceCloseApplication(true);
+ VirtualViewSceneHandler.addVirtualViewPair(new VirtualViewPair(null, null, true));
+ VirtualViewSceneHandler.setStopGameSnapshotHandler(true);
+ });
+ stage.show();
+ }
+
+ /** {@inheritDoc} */
+ public String getUserInput() {
+ return takeMsgQueue();
+ }
+
+ /** {@inheritDoc} */
+ public void displaySplashScreen() {
+ // this will perform javaFX init process and show the first scene
+ new Thread(() -> {
+ launch();
+ }).start();
+ }
+
+ /** {@inheritDoc} */
+ public void displayAvailableGames(List<VirtualView> availableGames) {
+ GuiFactory.stages.put(
+ SCENE.JOIN_GAME, GuiFactory.getCreateJoinScene(Client.getActiveGameServers()));
+ }
+
+ //For RMI: This was the original implementation (with GUI update from remote calls), but it does have some functional issues.
+ //We are aware that the turnaround that we have chosen (to save the data and trigger the scene from another thread) does not reflect the RMI paradigm at 100%
+ //
+ // /** {@inheritDoc} */
+ // public void displayVirtualView(VirtualView old, VirtualView vw) {
+ // if (vw.getStatus() == GameStatus.ENDED) {
+ // GuiFactory.stages.put(SCENE.END_GAME, GuiFactory.getEndGameScene(vw));
+ // GuiFactory.executeOnJavaFX(
+ // () -> GuiFactory.mainStage.setScene(GuiFactory.stages.get(SCENE.END_GAME)));
+ // } else {
+ // if (vw.getStatus() == GameStatus.WAITING_FOR_PLAYERS) {
+ // GuiFactory.executeOnJavaFX(
+ // () -> GuiFactory.mainStage.setScene(GuiFactory.stages.get(SCENE.WAIT_GAME)));
+ // } else {
+ // if (GuiFactory.stages.get(SCENE.GAME_SNAPSHOT) == null) {
+ // GuiFactory.stages.put(SCENE.GAME_SNAPSHOT, GuiFactory.getGameSnapshotScene(vw));
+ // GuiFactory.executeOnJavaFX(
+ // () -> GuiFactory.mainStage.setScene(GuiFactory.stages.get(SCENE.GAME_SNAPSHOT)));
+ // } else {
+ // StackPane root = (StackPane) GuiFactory.mainStage.getScene().getRoot();
+ // GuiFactory.executeOnJavaFX(() -> GuiFactory.GameSnapshotFactory.updateGameWidget(root, old, vw));
+ // }
+ // }
+ // }
+ // }
+
+ /** {@inheritDoc} */
+ public void displayVirtualView(VirtualView old, VirtualView vw) {
+ VirtualViewSceneHandler.addVirtualViewPair(new VirtualViewPair(old, vw, false));
+ }
+
+ /** {@inheritDoc} */
+ public void displayChatMessage(ChatMessage message) {
+ StackPane root = (StackPane) GuiFactory.mainStage.getScene().getRoot();
+ GuiFactory.executeOnJavaFX(
+ () -> GuiFactory.GameSnapshotFactory.updateChatHistory(root, message));
+ }
+
+ /** {@inheritDoc}} */
+ public void displayError(ErrorMessage errorMessage) {
+ StackPane root = (StackPane) GuiFactory.mainStage.getScene().getRoot();
+ if(errorMessage.getErrorSeverity() != ErrorSeverity.INFO){
+ GuiFactory.executeOnJavaFX(
+ () -> GuiFactory.getErrorMessage(root, errorMessage)
+ );
+ if (errorMessage.getMessage().contains("You have been disconnected")) {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ Client.setForceCloseApplication(true);
+ }
+ } else {
+ GuiFactory.executeOnJavaFX(
+ () -> GuiFactory.GameSnapshotFactory.updateChatHistory(root, errorMessage)
+ );
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void terminateUserInterface() {
+ Platform.exit();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void displayGameJoinGuide() {
+ GuiFactory.executeOnJavaFX(
+ () -> GuiFactory.mainStage.setScene(GuiFactory.stages.get(SCENE.ENTER_GAME_SELECTION)));
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/VirtualViewPair.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/VirtualViewPair.html
new file mode 100644
index 00000000..bc7610fc
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/VirtualViewPair.html
@@ -0,0 +1 @@
+VirtualViewPair
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/VirtualViewSceneHandler.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/VirtualViewSceneHandler.html
new file mode 100644
index 00000000..616e928a
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/VirtualViewSceneHandler.html
@@ -0,0 +1 @@
+VirtualViewSceneHandler
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/index.html
new file mode 100644
index 00000000..d2b26c83
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.client.userinterface
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/index.source.html
new file mode 100644
index 00000000..8e3d5ca6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client.userinterface/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.client.userinterface
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$1.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$1.html
new file mode 100644
index 00000000..012bb5de
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$1.html
@@ -0,0 +1 @@
+Client.new TypeToken() {...}
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$AlarmTask.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$AlarmTask.html
new file mode 100644
index 00000000..4e311f10
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$AlarmTask.html
@@ -0,0 +1 @@
+Client.AlarmTask
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$ClientGameStatus.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$ClientGameStatus.html
new file mode 100644
index 00000000..c12a80aa
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$ClientGameStatus.html
@@ -0,0 +1 @@
+Client.ClientGameStatus
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$MessageDeserializer.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$MessageDeserializer.html
new file mode 100644
index 00000000..4e2f56a0
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client$MessageDeserializer.html
@@ -0,0 +1 @@
+Client.MessageDeserializer
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client.java.html
new file mode 100644
index 00000000..1df90679
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/Client.java.html
@@ -0,0 +1,834 @@
+Client.java
package it.polimi.is23am10.client;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.reflect.TypeToken;
+
+import it.polimi.is23am10.client.exceptions.ForceCloseApplicationException;
+import it.polimi.is23am10.client.interfaces.AlarmConsumer;
+import it.polimi.is23am10.client.userinterface.UserInterface;
+import it.polimi.is23am10.server.model.game.Game.GameStatus;
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerIdException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerNameException;
+import it.polimi.is23am10.server.network.messages.AbstractMessage;
+import it.polimi.is23am10.server.network.messages.AvailableGamesMessage;
+import it.polimi.is23am10.server.network.messages.ChatMessage;
+import it.polimi.is23am10.server.network.messages.ErrorMessage;
+import it.polimi.is23am10.server.network.messages.ErrorMessage.ErrorSeverity;
+import it.polimi.is23am10.server.network.messages.GameMessage;
+import it.polimi.is23am10.server.network.messages.SnoozeACKMessage;
+import it.polimi.is23am10.server.network.playerconnector.AbstractPlayerConnector;
+import it.polimi.is23am10.server.network.playerconnector.interfaces.IPlayerConnector;
+import it.polimi.is23am10.server.network.virtualview.VirtualView;
+import it.polimi.is23am10.utils.CommandSyntaxValidator;
+import it.polimi.is23am10.utils.Coordinates;
+import it.polimi.is23am10.utils.ErrorTypeString;
+import it.polimi.is23am10.utils.MoveCommandHelper;
+import it.polimi.is23am10.utils.MovesValidator;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import it.polimi.is23am10.utils.exceptions.WrongBookShelfPicksException;
+import it.polimi.is23am10.utils.exceptions.WrongGameBoardPicksException;
+import it.polimi.is23am10.utils.exceptions.WrongMovesNumberException;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Type;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+
+/** Custom lock object. */
+class LockObject implements Serializable {}
+
+/**
+ * An abstract class representing the app running in client mode. Holds the three elements needed
+ * for proper functioning:
+ *
+ * <ul>
+ * <li>Networking: Player Connector
+ * <li>Game state: VirtualView
+ * <li>UI/UX: UserInterface
+ * </ul>
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public abstract class Client extends UnicastRemoteObject implements IClient {
+ /** Custom lock object. */
+ protected LockObject gameRefLock = new LockObject();
+
+ /** Custom lock object. */
+ protected LockObject hasDuplicateLock = new LockObject();
+
+ /** Custom lock object. */
+ protected LockObject virtualViewLock = new LockObject();
+
+ /** Custom lock object. */
+ protected static Object availableServersLock = new Object();
+
+ /** Custom lock object. */
+ protected static Object playerConnectorLock = new Object();
+
+ /**
+ * Protected constructor for client using Socket as communication method.
+ *
+ * @param pc Player connector.
+ * @param ui User interface.
+ * @throws UnknownHostException On localhost retrieval failure.
+ */
+ protected Client(IPlayerConnector pc, UserInterface ui)
+ throws UnknownHostException, RemoteException {
+ playerConnector = pc;
+ userInterface = ui;
+ serverAddress = InetAddress.getLocalHost();
+ gson =
+ new GsonBuilder()
+ .registerTypeAdapter(AbstractMessage.class, new MessageDeserializer())
+ .create();
+ requestedDisconnection = false;
+ alarm = new Timer();
+ clientStatus = ClientGameStatus.INIT;
+ forceCloseApplication = false;
+ }
+
+ /**
+ * Check if the current client has joined a game or not.
+ *
+ * @return The requested flag.
+ */
+ protected abstract boolean hasJoined();
+
+ /** Client alarm interval in milliseconds. */
+ protected final int ALARM_INTERVAL_MS = 5000;
+
+ /** Client alarm initial delay in milliseconds. */
+ protected final int ALARM_INITIAL_DELAY_MS = 0;
+
+ /** Clean disconnection request. */
+ protected boolean requestedDisconnection;
+
+ /** Force application close request, for example when gui is closed. */
+ protected static boolean forceCloseApplication;
+
+ /** Duplicate name error flag. */
+ private boolean hasDuplicateName;
+
+ /** A {@link Gson} instance to serialize and deserialize commands. */
+ protected transient Gson gson;
+
+ /** The server host IP address. */
+ protected transient InetAddress serverAddress;
+
+ /** The three possible states in which the client can be. */
+ public enum ClientGameStatus {
+ /** Starting state. Player hasn't selected name yet. */
+ INIT,
+ /** Player selected name but not yet the game. */
+ GAME_SELECTION,
+ /** Player is in the game. */
+ PLAYING
+ }
+
+ /** Current status of the client. */
+ protected ClientGameStatus clientStatus;
+
+ /**
+ * Player connector. Allows the client to communicate with the server and receive updates (game
+ * snapshots, chat messages)
+ */
+ protected static IPlayerConnector playerConnector;
+
+ /**
+ * Maximum player name length.
+ */
+ private final Integer MAX_PLAYER_NAME_LENGTH = 15;
+
+ /**
+ * Retrieve the player connector intance.
+ *
+ * @return The {@link IPlayerConnector}.
+ */
+ public static IPlayerConnector getPlayerConnector() {
+ synchronized (playerConnectorLock) {
+ return playerConnector;
+ }
+ }
+
+ /**
+ * Instance of the game currently played on client. Initially null, filled when joining games,
+ * updated constantly at each turn with the updated instance arriving in playerConnector's queue
+ * Completely replaced when starting new games.
+ */
+ protected VirtualView virtualView;
+
+ /**
+ * Interface used for communicating with the user. Can be either graphical or textual. Only output
+ * methods are exposed by interface.
+ */
+ public UserInterface userInterface;
+
+ /** List of available games set when a message from getAvailableGames is received. */
+ protected static List<VirtualView> availableGames;
+
+ /** Game id reference. */
+ protected UUID gameIdRef;
+
+ /*
+ * Application timer.
+ */
+ protected transient Timer alarm;
+
+ /**
+ * Detected if the use has requested a clean disconnection.
+ *
+ * @return The disconnection flag.
+ */
+ protected boolean hasRequestedDisconnection() {
+ return requestedDisconnection;
+ }
+
+ /**
+ * Set a force close application request.
+ * This method is defined as static as JavaFX thread must have access.
+ *
+ * @param flag The request flag.
+ *
+ */
+ public static void setForceCloseApplication(boolean flag) {
+ forceCloseApplication = flag;
+ }
+
+ /**
+ * Show the received message to the client.
+ *
+ * @param msg The message. Its dynamic type is inferred by {@link Gson}.
+ * @throws RemoteException
+ */
+ public void showServerMessage(AbstractMessage msg) throws RemoteException {
+ if (gson == null) {
+ gson =
+ new GsonBuilder()
+ .registerTypeAdapter(AbstractMessage.class, new MessageDeserializer())
+ .create();
+ }
+ switch (msg.getMessageType()) {
+ case CHAT_MESSAGE:
+ userInterface.displayChatMessage((ChatMessage) msg);
+ break;
+ case GAME_SNAPSHOT:
+ VirtualView old = virtualView;
+ VirtualView v = gson.fromJson(msg.getMessage(), VirtualView.class);
+ setVirtualView(v);
+ // do not overwrite game id
+ if (getGameIdRef() == null) {
+ setGameIdRef(v.getGameId());
+ }
+ userInterface.displayVirtualView(old, v);
+ break;
+ case ERROR_MESSAGE:
+ userInterface.displayError((ErrorMessage) msg);
+ if (msg.getMessage().equals(ErrorTypeString.ERROR_ADDING_PLAYERS)) {
+ setHasDuplicateName(true);
+ }
+ break;
+ case AVAILABLE_GAMES:
+ Type listOfMyClassObject = new TypeToken<ArrayList<VirtualView>>() {}.getType();
+ List<VirtualView> availableGamesList = gson.fromJson(msg.getMessage(), listOfMyClassObject);
+ setActiveGameServers(availableGamesList);
+ userInterface.displayAvailableGames(availableGamesList);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /** Abstract method that run the client into the game. */
+ public abstract void run();
+
+ /**
+ * Available games param setter.
+ *
+ * @param ag list of available games
+ */
+ protected void setActiveGameServers(List<VirtualView> ag) {
+ synchronized (availableServersLock) {
+ availableGames = ag;
+ }
+ }
+
+ /**
+ * Available games param getter.
+ *
+ * @return The list of available games
+ */
+ public static List<VirtualView> getActiveGameServers() {
+ synchronized (availableServersLock) {
+ return availableGames;
+ }
+ }
+
+ /**
+ * GameIdRef setter.
+ *
+ * @param id game id ref
+ */
+ protected void setGameIdRef(UUID id) {
+ synchronized (gameRefLock) {
+ this.gameIdRef = id;
+ }
+ }
+
+ /** GameIdRef getter. */
+ protected UUID getGameIdRef() {
+ synchronized (gameRefLock) {
+ return gameIdRef;
+ }
+ }
+
+ /**
+ * Duplicate name flag setter.
+ *
+ * @param b flag
+ */
+ protected void setHasDuplicateName(boolean b) {
+ synchronized (hasDuplicateLock) {
+ this.hasDuplicateName = b;
+ }
+ }
+
+ /**
+ * Duplicate name flag getter.
+ *
+ * @return flag
+ */
+ protected boolean getHasDuplicateName() {
+ synchronized (hasDuplicateLock) {
+ return hasDuplicateName;
+ }
+ }
+
+ /**
+ * User interface setter.
+ *
+ * @param ui user interface.
+ */
+ protected void setUserInterface(UserInterface ui) {
+ this.userInterface = ui;
+ }
+
+ /**
+ * User interface getter.
+ *
+ * @return user interface.
+ */
+ protected UserInterface getUserInterface() {
+ return userInterface;
+ }
+
+ /**
+ * Virtual view getter.
+ *
+ * @return virtual view
+ */
+ protected VirtualView getVirtualView() {
+ synchronized (virtualViewLock) {
+ return virtualView;
+ }
+ }
+
+ /** Virtual view setter. */
+ protected void setVirtualView(VirtualView vv) {
+ synchronized (virtualViewLock) {
+ this.virtualView = vv;
+ }
+ }
+
+ /**
+ * Client status getter.
+ *
+ * @return status.
+ */
+ public ClientGameStatus getClientStatus() {
+ return clientStatus;
+ }
+
+ /**
+ * Client status setter.
+ *
+ * @param clientStatus new status of client.
+ */
+ public void setClientStatus(ClientGameStatus clientStatus) {
+ this.clientStatus = clientStatus;
+ }
+
+ /**
+ * Abstract method that send command to get all available games.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ abstract void getAvailableGames() throws IOException, InterruptedException;
+
+ /**
+ * Abstract method that send command to start a new game.
+ *
+ * @param playerName selected player name
+ * @param maxPlayerNum max number of players selected
+ * @throws IOException
+ */
+ abstract void startGame(String playerName, int maxPlayerNum) throws IOException;
+
+ /**
+ * Abstract method that send command to add a new player.
+ *
+ * @param playerName selected player name
+ * @param gameId selected game id
+ * @throws IOException
+ */
+ abstract void addPlayer(String playerName, UUID gameId) throws IOException;
+
+ /**
+ * Abstract method that send command to move tiles.
+ *
+ * @param moves map of moves
+ * @throws IOException
+ */
+ abstract void moveTiles(Map<Coordinates, Coordinates> moves) throws IOException;
+
+ /**
+ * Abstract function that send chat message
+ *
+ * @param msg chat message
+ * @throws IOException
+ */
+ abstract void sendChatMessage(ChatMessage msg) throws IOException;
+
+ /**
+ * Abstract function that snoozes virtual alarm.
+ *
+ * @param apc abstract player connector
+ * @param msg chat message
+ * @throws IOException
+ */
+ abstract void snoozeAlarm() throws IOException;
+
+ /**
+ * Set the game id to the {@link IPlayerConnector}. The value source is {@link
+ * Client#getGameIdRef()}.
+ *
+ * @param reset A flag stating if this files has to be reset.
+ */
+ protected void setConnectorGameId(boolean reset) {
+ synchronized (playerConnectorLock) {
+ ((AbstractPlayerConnector) playerConnector).setGameId(reset ? null : getGameIdRef());
+ }
+ }
+
+ /**
+ * Set the {@link Player} to the {@link IPlayerConnector}.
+ *
+ * @param name The player name.
+ * @throws NullPlayerIdException
+ * @throws NullPlayerIdException
+ */
+ protected void setConnectorPlayer(String name)
+ throws NullPlayerNameException, NullPlayerIdException {
+ synchronized (playerConnectorLock) {
+ Player p = new Player();
+ if (name != null) {
+ p.setPlayerName(name);
+ p.setPlayerID(UUID.nameUUIDFromBytes(name.getBytes()));
+ }
+ ((AbstractPlayerConnector) playerConnector).setPlayer(p);
+ }
+ }
+
+ /**
+ * Handling function for move tiles command.
+ *
+ * @throws IOException
+ */
+ protected void handleCommands() throws IOException {
+
+ Player connectorPlayer;
+ synchronized (playerConnectorLock) {
+ AbstractPlayerConnector pc = (AbstractPlayerConnector) playerConnector;
+ connectorPlayer = pc.getPlayer();
+ }
+
+ if (clientStatus == ClientGameStatus.GAME_SELECTION) {
+ clientStatus = ClientGameStatus.PLAYING;
+ }
+
+ String fullCommand = userInterface.getUserInput();
+
+ if (fullCommand != null) {
+ String command = fullCommand.stripLeading().split(" ")[0];
+
+ switch (command) {
+ case "chat":
+ if (fullCommand.stripLeading().split(" ").length > 1) {
+ if (fullCommand.stripLeading().split("\"").length > 1) {
+ // This selects only the part between double quotes which is gonna be the
+ // message sent.
+ String msg = fullCommand.split("\"")[1];
+ // If the second string begins with double quotes,
+ // there's no receiver and the message is broadcast
+ if (fullCommand.stripLeading().split(" ")[1].startsWith("\"")) {
+ sendChatMessage(new ChatMessage(connectorPlayer, msg));
+ } else {
+ String receiverName = fullCommand.split(" ")[1];
+ sendChatMessage(new ChatMessage(connectorPlayer, msg, receiverName));
+ }
+ }
+ }
+ break;
+ case "logout":
+ setForceCloseApplication(true);
+ break;
+ case "move":
+ if (getVirtualView() == null) {
+ userInterface.displayError(
+ new ErrorMessage("Wait the game to be loaded", ErrorSeverity.ERROR));
+ break;
+ }
+ if (connectorPlayer
+ .getPlayerName()
+ .equals(getVirtualView().getActivePlayer().getPlayerName())
+ && getVirtualView().getStatus() != GameStatus.WAITING_FOR_PLAYERS) {
+
+ Map<Coordinates, Coordinates> moves = new HashMap<Coordinates, Coordinates>();
+ List<Coordinates> boardCoords = new ArrayList<Coordinates>();
+ List<Coordinates> bsCoords = new ArrayList<Coordinates>();
+
+ // Reads a string containing coordinates of a tile and the column index
+ String[] moveArgs = fullCommand.stripLeading().split(" ");
+ for (int maxArgs = 0; maxArgs < 4 && moveArgs.length - (maxArgs + 1) > 0; maxArgs++) {
+
+ /*
+ * If we receive a board coordinate input add it to the list, otherwise if it is
+ * a
+ * column index we are at the end of the move command and we convert that idx
+ * to the right bookshelf coordinates. Then we add the mapping between board
+ * coordinates
+ * and bookshelf coordinates.
+ */
+ if (CommandSyntaxValidator.validateCoord(moveArgs[maxArgs + 1])) {
+ String coordBoard = moveArgs[maxArgs + 1];
+ Integer colBoardCoord = coordBoard.charAt(0) - '0';
+ Integer rowBoardCoord = coordBoard.charAt(1) - '0';
+ boardCoords.add(new Coordinates(rowBoardCoord, colBoardCoord));
+ } else if (CommandSyntaxValidator.validateColIdx(moveArgs[maxArgs + 1])) {
+ String idx = moveArgs[maxArgs + 1];
+ try {
+ // Transform idx to list of coords
+ // NB: boardCoords.size() is the number of moves done
+ bsCoords =
+ MoveCommandHelper.fromColIdxToCoord(
+ idx,
+ getVirtualView().getActivePlayer().getBookshelf(),
+ boardCoords.size());
+ // I put the coords into the map
+ for (int i = 0; i < boardCoords.size(); i++) {
+ moves.put(boardCoords.get(i), bsCoords.get(i));
+ }
+ break;
+ } catch (BookshelfGridColIndexOutOfBoundsException
+ | BookshelfGridRowIndexOutOfBoundsException
+ | NullIndexValueException
+ | WrongBookShelfPicksException e) {
+ userInterface.displayError(new ErrorMessage(e.getMessage(), ErrorSeverity.ERROR));
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ // Checks if no valid moves were added
+ if (moves.isEmpty()) {
+ userInterface.displayError(
+ new ErrorMessage("No valid moves found.", ErrorSeverity.ERROR));
+ } else {
+ try {
+ MovesValidator.validateGameMoves(
+ moves,
+ virtualView.getActivePlayer().getBookshelf(),
+ virtualView.getGameBoard());
+ moveTiles(moves);
+ } catch (BoardGridRowIndexOutOfBoundsException
+ | BoardGridColIndexOutOfBoundsException
+ | BookshelfGridColIndexOutOfBoundsException
+ | BookshelfGridRowIndexOutOfBoundsException
+ | WrongMovesNumberException
+ | WrongGameBoardPicksException
+ | NullIndexValueException
+ | WrongBookShelfPicksException e) {
+ userInterface.displayError(
+ new ErrorMessage("Invalid move:" + e.getMessage(), ErrorSeverity.ERROR));
+ break;
+ }
+ }
+ break;
+ } else {
+ userInterface.displayError(new ErrorMessage("Not your turn.", ErrorSeverity.WARNING));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Handling function for name selection.
+ *
+ * @return The operation result.
+ * @throws IOException
+ */
+ protected boolean handlePlayerNameSelection() throws IOException {
+ // Select only the string before the space if the client writes more words
+ String selectedPlayerName = userInterface.getUserInput();
+ if (selectedPlayerName != null) {
+ selectedPlayerName = selectedPlayerName.stripLeading().split(" ")[0];
+ if (selectedPlayerName.length() > MAX_PLAYER_NAME_LENGTH){
+ selectedPlayerName = selectedPlayerName.substring(0, MAX_PLAYER_NAME_LENGTH);
+ }
+ try {
+ setConnectorPlayer(selectedPlayerName);
+ } catch (NullPlayerNameException | NullPlayerIdException e) {
+ userInterface.displayError(new ErrorMessage("Invalid player name", ErrorSeverity.ERROR));
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Handling function for game selection.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ protected void handleGameSelection() throws IOException, InterruptedException {
+
+ Player connectorPlayer;
+ UUID connectorGameId;
+ synchronized (playerConnectorLock) {
+ AbstractPlayerConnector pc = (AbstractPlayerConnector) playerConnector;
+ connectorPlayer = pc.getPlayer();
+ connectorGameId = pc.getGameId();
+ }
+
+ // Executed if I still haven't selected a game
+ if (connectorGameId == null) {
+
+ // We use the check over client status to perform one-time actions
+ // like displaying stuff and sending
+ if (clientStatus == ClientGameStatus.INIT) {
+ userInterface.displayGameJoinGuide();
+ clientStatus = ClientGameStatus.GAME_SELECTION;
+ getAvailableGames();
+ }
+
+ String fullCommand = userInterface.getUserInput();
+ if (fullCommand != null) {
+ String command = fullCommand.stripLeading().split(" ")[0];
+ Integer maxPlayers = null;
+ switch (command) {
+ case "j":
+ if (fullCommand.split(" ").length > 1) {
+ String idx = fullCommand.split(" ")[1];
+ if (CommandSyntaxValidator.validateGameIdx(idx, availableGames.size())) {
+ UUID selectedGameId = availableGames.get(Integer.parseInt(idx)).getGameId();
+ addPlayer(connectorPlayer.getPlayerName(), selectedGameId);
+ /*
+ * Since the gameId ref is set when the message handler receives a GAME_SNAPSHOT
+ * message
+ * and since that GAME_SNAPSHOT message is received only when the player is
+ * added correctly to the game (so there's not duplicate name exception),
+ * here we know that even if the duplicateName flag is set after a few seconds,
+ * the player will not be added by mistake because it exits the while loop
+ * because of the gameId flag.
+ */
+ while (getGameIdRef() == null && !getHasDuplicateName()) {}
+ if (getHasDuplicateName()) {
+ userInterface.displayError(
+ new ErrorMessage("Failed to add player, retry", ErrorSeverity.CRITICAL));
+ setHasDuplicateName(false);
+ break;
+ }
+ setConnectorGameId(false);
+ } else {
+ userInterface.displayError(
+ new ErrorMessage("Failed to select game", ErrorSeverity.CRITICAL));
+ }
+ } else {
+ userInterface.displayError(
+ new ErrorMessage(
+ "Insert the index of the game you want to join, if none present please create a new game",
+ ErrorSeverity.ERROR));
+ }
+ break;
+ case "c":
+ if (fullCommand.split(" ").length > 1) {
+ String numMaxPlayers = fullCommand.split(" ")[1];
+ if (CommandSyntaxValidator.validateMaxPlayer(numMaxPlayers)) {
+ maxPlayers = Integer.parseInt(numMaxPlayers);
+ startGame(connectorPlayer.getPlayerName(), maxPlayers);
+ while (getGameIdRef() == null) {}
+ setConnectorGameId(false);
+ } else {
+ userInterface.displayError(
+ new ErrorMessage("Failed to create game", ErrorSeverity.CRITICAL));
+ }
+ } else {
+ userInterface.displayError(
+ new ErrorMessage("Insert value of max players", ErrorSeverity.ERROR));
+ }
+ break;
+ case "q":
+ try {
+ setConnectorPlayer(null);
+ } catch (NullPlayerIdException | NullPlayerNameException e) {
+ }
+ clientStatus = ClientGameStatus.INIT;
+ setConnectorGameId(true);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * The client game core machine state.
+ *
+ * @throws IOException
+ * @throws NullPlayerIdException
+ * @throws InterruptedException
+ */
+ protected void clientRunnerCore()
+ throws IOException, InterruptedException, NullPlayerIdException, ForceCloseApplicationException {
+
+ //this flag is raised by external threads, as JavaFX
+ if (forceCloseApplication) {
+ throw new ForceCloseApplicationException();
+ }
+
+ Player connectorPlayer;
+ UUID connectorGameId;
+ synchronized (playerConnectorLock) {
+ AbstractPlayerConnector pc = (AbstractPlayerConnector) playerConnector;
+ connectorPlayer = pc.getPlayer();
+ connectorGameId = pc.getGameId();
+ }
+
+ // First I'm gonna ask the player name
+ if (connectorPlayer == null || connectorPlayer.getPlayerName() == null) {
+ userInterface.displaySplashScreen();
+ while (!handlePlayerNameSelection()) {}
+ }
+
+ // Execute if the client is not connected to a game.
+ if (connectorGameId == null) {
+ handleGameSelection();
+ } else {
+ // Executed if the client is connected to a game.
+ handleCommands();
+ }
+ }
+
+ /**
+ * Method to terminate the client and all client's threads.
+ */
+ public void terminateClient() {
+ try {
+ UnicastRemoteObject.unexportObject(this, true);
+ } catch (NoSuchObjectException e) {
+ userInterface.displayError(new ErrorMessage("Unable to close connection safely. Please close client manually", ErrorSeverity.CRITICAL));
+ }
+ requestedDisconnection = true;
+ alarm.cancel();
+ userInterface.terminateUserInterface();
+ }
+
+ /**
+ * Custom deserializer class definition for {@link Gson} usage. This works on polymorphic {@link
+ * AbstractMessage} objects.
+ */
+ class MessageDeserializer implements JsonDeserializer<AbstractMessage> {
+ @Override
+ public AbstractMessage deserialize(
+ JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject jsonObject = json.getAsJsonObject();
+
+ String className = "";
+ try {
+ className = jsonObject.get("className").getAsString();
+ } catch (Exception e) {
+ throw new JsonParseException(e);
+ }
+
+ switch (className) {
+ case "it.polimi.is23am10.server.network.messages.GameMessage":
+ return context.deserialize(jsonObject, GameMessage.class);
+ case "it.polimi.is23am10.server.network.messages.ChatMessage":
+ return context.deserialize(jsonObject, ChatMessage.class);
+ case "it.polimi.is23am10.server.network.messages.AvailableGamesMessage":
+ return context.deserialize(jsonObject, AvailableGamesMessage.class);
+ case "it.polimi.is23am10.server.network.messages.ErrorMessage":
+ return context.deserialize(jsonObject, ErrorMessage.class);
+ case "it.polimi.is23am10.server.network.messages.SnoozeACKMessage":
+ return context.deserialize(jsonObject, SnoozeACKMessage.class);
+ default:
+ throw new JsonParseException("Unknown class name: " + className);
+ }
+ }
+ }
+
+ /** The timer schedule execution class. */
+ protected class AlarmTask extends TimerTask {
+ /** The consumer to be executed. */
+ AlarmConsumer task;
+
+ /**
+ * Constructor.
+ *
+ * @param task The consumer to be assigned.
+ */
+ public AlarmTask(AlarmConsumer task) {
+ this.task = task;
+ }
+
+ /** {@inheritDoc} */
+ public void run() {
+ task.start();
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/LockObject.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/LockObject.html
new file mode 100644
index 00000000..55ef9935
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/LockObject.html
@@ -0,0 +1 @@
+LockObject
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/index.html
new file mode 100644
index 00000000..60ffb249
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.client
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.client/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.client/index.source.html
new file mode 100644
index 00000000..63dda138
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.client/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.client
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand$Opcode.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand$Opcode.html
new file mode 100644
index 00000000..61461845
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand$Opcode.html
@@ -0,0 +1 @@
+AbstractCommand.Opcode
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand.html
new file mode 100644
index 00000000..8bbf4cd6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand.html
@@ -0,0 +1 @@
+AbstractCommand
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand.java.html
new file mode 100644
index 00000000..7e9e19d4
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AbstractCommand.java.html
@@ -0,0 +1,76 @@
+AbstractCommand.java
package it.polimi.is23am10.server.command;
+
+import java.io.Serializable;
+
+/**
+ * The abstract command class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public abstract class AbstractCommand implements Serializable {
+
+ /**
+ * Opcodes to communicate the action taken by a player.
+ *
+ */
+ public enum Opcode {
+ /**
+ * Start a new game command.
+ */
+ START,
+ /**
+ * Add a new player to existing game command.
+ */
+ ADD_PLAYER,
+ /**
+ * Move tiles from board to user's bookshelf command.
+ */
+ MOVE_TILES,
+ /**
+ * Retrieve existing games to join command.
+ */
+ GET_GAMES,
+ /**
+ * Send chat message command
+ */
+ SEND_CHAT_MESSAGE,
+ /**
+ * Game timer snooze command.
+ */
+ GAME_TIMER,
+ /**
+ * Null command.
+ */
+ NULL
+ }
+
+ /**
+ * The opcode instance for the current command instance.
+ *
+ */
+ protected Opcode opcode;
+
+ /**
+ * The opcode instance for the current command instance.
+ *
+ * @param opcode command op code.
+ *
+ */
+ protected AbstractCommand(Opcode opcode) {
+ this.opcode = opcode != null ? opcode : Opcode.NULL;
+ }
+
+ /**
+ * Opcode getter.
+ *
+ * @return The current command Opcode.
+ *
+ */
+ public Opcode getOpcode() {
+ return opcode;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AddPlayerCommand.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AddPlayerCommand.html
new file mode 100644
index 00000000..f48030e6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AddPlayerCommand.html
@@ -0,0 +1 @@
+AddPlayerCommand
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AddPlayerCommand.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AddPlayerCommand.java.html
new file mode 100644
index 00000000..3d717824
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/AddPlayerCommand.java.html
@@ -0,0 +1,94 @@
+AddPlayerCommand.java
package it.polimi.is23am10.server.command;
+
+import java.util.UUID;
+
+/**
+ * The add new player to game command class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class AddPlayerCommand extends AbstractCommand {
+
+ /**
+ * The player name to add.
+ *
+ */
+ private String playerName;
+
+ /**
+ * The game id reference.
+ *
+ */
+ private UUID gameId;
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Constructor.
+ *
+ * @param playerName The player name to add.
+ * @param gameId The game instance id.
+ *
+ */
+ public AddPlayerCommand(String playerName, UUID gameId) {
+ super(Opcode.ADD_PLAYER);
+ this.playerName = playerName;
+ this.gameId = gameId;
+ }
+
+ /**
+ * Starting player name getter.
+ *
+ * @return The player name.
+ *
+ */
+ public String getPlayerName() {
+ return playerName;
+ }
+
+ /**
+ * Game id getter.
+ *
+ * @return The game id reference.
+ *
+ */
+ public UUID getGameId() {
+ return gameId;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AddPlayerCommand)) {
+ return false;
+ }
+
+ AddPlayerCommand casted = (AddPlayerCommand) obj;
+
+ return (opcode == casted.getOpcode()
+ && playerName.equals(casted.getPlayerName())
+ && gameId.equals(casted.getGameId()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ //we can have same player name for multiple games!
+ return playerName.hashCode() * gameId.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/GetAvailableGamesCommand.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/GetAvailableGamesCommand.html
new file mode 100644
index 00000000..6b9c0e52
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/GetAvailableGamesCommand.html
@@ -0,0 +1 @@
+GetAvailableGamesCommand
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/GetAvailableGamesCommand.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/GetAvailableGamesCommand.java.html
new file mode 100644
index 00000000..2c0afa7d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/GetAvailableGamesCommand.java.html
@@ -0,0 +1,37 @@
+GetAvailableGamesCommand.java
package it.polimi.is23am10.server.command;
+
+/**
+ * The command used by player to get the list of the games they can join.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class GetAvailableGamesCommand extends AbstractCommand {
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Constructor.
+ *
+ */
+ public GetAvailableGamesCommand() {
+ super(Opcode.GET_GAMES);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof GetAvailableGamesCommand);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/MoveTilesCommand.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/MoveTilesCommand.html
new file mode 100644
index 00000000..98245353
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/MoveTilesCommand.html
@@ -0,0 +1 @@
+MoveTilesCommand
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/MoveTilesCommand.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/MoveTilesCommand.java.html
new file mode 100644
index 00000000..b2dbe6d0
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/MoveTilesCommand.java.html
@@ -0,0 +1,110 @@
+MoveTilesCommand.java
package it.polimi.is23am10.server.command;
+
+import it.polimi.is23am10.utils.Coordinates;
+import it.polimi.is23am10.utils.MoveTileCommandTypeAdaptor;
+
+import java.util.Map;
+import java.util.UUID;
+import com.google.gson.annotations.JsonAdapter;
+import it.polimi.is23am10.server.model.items.board.Board;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+
+/**
+ * The move tiles command class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class MoveTilesCommand extends AbstractCommand {
+
+ /**
+ * The player who calls the operation.
+ */
+ private String movingPlayer;
+
+ /**
+ * Map that associates the position of a picked tile
+ * on the {@link Board} to the destination position to move
+ * that same tile inside user's {@link Bookshelf}.
+ */
+ @JsonAdapter(MoveTileCommandTypeAdaptor.class)
+ private Map<Coordinates, Coordinates> moves;
+
+ /**
+ * Game id to specify in which match this command
+ * is executed. Must match with game currently playing.
+ */
+ private UUID gameId;
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Public constructor.
+ *
+ * @param movingPlayer The player requesting the move action.
+ * @param gameId The game id reference.
+ * @param moves The map of moves. See javadoc above.
+ */
+ public MoveTilesCommand(String movingPlayer, UUID gameId, Map<Coordinates, Coordinates> moves) {
+ super(Opcode.MOVE_TILES);
+ this.movingPlayer = movingPlayer;
+ this.gameId = gameId;
+ this.moves = moves;
+ }
+
+ /**
+ * Moving player getter.
+ */
+ public String getMovingPlayer() {
+ return movingPlayer;
+ }
+
+ /**
+ * GameId getter.
+ */
+ public UUID getGameId() {
+ return gameId;
+ }
+
+ /**
+ * Moves map getter.
+ */
+ public Map<Coordinates, Coordinates> getMoves() {
+ return moves;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MoveTilesCommand)) {
+ return false;
+ }
+
+ MoveTilesCommand casted = (MoveTilesCommand) obj;
+
+ return (opcode == casted.getOpcode()
+ && movingPlayer.equals(casted.getMovingPlayer())
+ && gameId.equals(casted.getGameId())
+ && moves.equals(casted.getMoves()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return movingPlayer.hashCode() * gameId.hashCode() * moves.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SendChatMessageCommand.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SendChatMessageCommand.html
new file mode 100644
index 00000000..d7484f79
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SendChatMessageCommand.html
@@ -0,0 +1 @@
+SendChatMessageCommand
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SendChatMessageCommand.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SendChatMessageCommand.java.html
new file mode 100644
index 00000000..b46329d6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SendChatMessageCommand.java.html
@@ -0,0 +1,56 @@
+SendChatMessageCommand.java
package it.polimi.is23am10.server.command;
+
+import it.polimi.is23am10.server.network.messages.ChatMessage;
+
+/**
+ * The command used by player to send a chat message.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class SendChatMessageCommand extends AbstractCommand {
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Chat message to be sent
+ */
+ private ChatMessage msg;
+
+ /**
+ * Constructor.
+ *
+ */
+ public SendChatMessageCommand(ChatMessage m) {
+ super(Opcode.SEND_CHAT_MESSAGE);
+ this.msg = m;
+ }
+
+ /**
+ * Chat message getter.
+ *
+ * @return chat message.
+ */
+ public ChatMessage getChatMessage() {
+ return msg;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof SendChatMessageCommand);
+ }
+}
+
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SnoozeGameTimerCommand.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SnoozeGameTimerCommand.html
new file mode 100644
index 00000000..e4904913
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SnoozeGameTimerCommand.html
@@ -0,0 +1 @@
+SnoozeGameTimerCommand
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SnoozeGameTimerCommand.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SnoozeGameTimerCommand.java.html
new file mode 100644
index 00000000..42bea386
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/SnoozeGameTimerCommand.java.html
@@ -0,0 +1,72 @@
+SnoozeGameTimerCommand.java
package it.polimi.is23am10.server.command;
+
+/**
+ * The snooze game timer command class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class SnoozeGameTimerCommand extends AbstractCommand {
+
+ /**
+ * The player name who has snoozed the game timer.
+ *
+ */
+ private String playerName;
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Constructor.
+ *
+ * @param playerName The player name.
+ *
+ */
+ public SnoozeGameTimerCommand(String playerName) {
+ super(Opcode.GAME_TIMER);
+ this.playerName = playerName;
+ }
+
+ /**
+ * Player name getter.
+ *
+ * @return The player name.
+ *
+ */
+ public String getPlayerName() {
+ return playerName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SnoozeGameTimerCommand)) {
+ return false;
+ }
+
+ SnoozeGameTimerCommand casted = (SnoozeGameTimerCommand) obj;
+
+ return (opcode == casted.getOpcode()
+ && playerName.equals(casted.getPlayerName()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return playerName.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/StartGameCommand.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/StartGameCommand.html
new file mode 100644
index 00000000..e700d43f
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/StartGameCommand.html
@@ -0,0 +1 @@
+StartGameCommand
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/StartGameCommand.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/StartGameCommand.java.html
new file mode 100644
index 00000000..c7bd7e39
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/StartGameCommand.java.html
@@ -0,0 +1,91 @@
+StartGameCommand.java
package it.polimi.is23am10.server.command;
+
+/**
+ * The start game command class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class StartGameCommand extends AbstractCommand {
+
+ /**
+ * The player name who has started a game.
+ *
+ */
+ private String startingPlayerName;
+
+ /**
+ * The chosen max player value.
+ *
+ */
+ private Integer maxPlayers;
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Constructor.
+ *
+ * @param startingPlayerName The chosen player name who has started a game request.
+ * @param maxPlayers The chosen max player value for the game request.
+ *
+ */
+ public StartGameCommand(String startingPlayerName, Integer maxPlayers) {
+ super(Opcode.START);
+ this.startingPlayerName = startingPlayerName;
+ this.maxPlayers = maxPlayers;
+ }
+
+ /**
+ * Starting player name getter.
+ *
+ * @return The player name.
+ *
+ */
+ public String getStartingPlayerName() {
+ return startingPlayerName;
+ }
+
+ /**
+ * Max player getter.
+ *
+ * @return The max player value allowed for the requested game.
+ *
+ */
+ public Integer getMaxPlayers() {
+ return maxPlayers;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof StartGameCommand)) {
+ return false;
+ }
+
+ StartGameCommand casted = (StartGameCommand) obj;
+
+ return (opcode == casted.getOpcode()
+ && startingPlayerName.equals(casted.getStartingPlayerName())
+ && maxPlayers == (casted.getMaxPlayers()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return startingPlayerName.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/index.html
new file mode 100644
index 00000000..a88f8a52
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.command
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/index.source.html
new file mode 100644
index 00000000..8b8ea035
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.command/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.command
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ClientConnectionChecker.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ClientConnectionChecker.html
new file mode 100644
index 00000000..acd1ee97
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ClientConnectionChecker.html
@@ -0,0 +1 @@
+ClientConnectionChecker
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ClientConnectionChecker.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ClientConnectionChecker.java.html
new file mode 100644
index 00000000..d52b151f
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ClientConnectionChecker.java.html
@@ -0,0 +1,234 @@
+ClientConnectionChecker.java
package it.polimi.is23am10.server.controller;
+
+import java.rmi.RemoteException;
+import java.util.Set;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import it.polimi.is23am10.server.controller.exceptions.NullGameHandlerInstance;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NegativeMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullScoreBlockListException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerBookshelfException;
+import it.polimi.is23am10.server.network.gamehandler.CurrentPlayerHandler;
+import it.polimi.is23am10.server.network.gamehandler.GameHandler;
+import it.polimi.is23am10.server.network.gamehandler.exceptions.GameSnapshotUpdateException;
+import it.polimi.is23am10.server.network.messages.ErrorMessage.ErrorSeverity;
+import it.polimi.is23am10.server.network.messages.ErrorMessage;
+import it.polimi.is23am10.server.network.playerconnector.AbstractPlayerConnector;
+import it.polimi.is23am10.utils.ErrorTypeString;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+
+/**
+ * The server client connection checker class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:nonemptyatclausedescriptioncheck" })
+public final class ClientConnectionChecker implements Runnable {
+
+ /**
+ * The logger, an instance of {@link Logger}.
+ *
+ */
+ protected Logger logger = LogManager.getLogger(ClientConnectionChecker.class);
+
+ /**
+ * The connected players.
+ *
+ */
+ protected Set<AbstractPlayerConnector> pcs;
+
+ /**
+ * The acive game handlers.
+ *
+ */
+ protected Set<GameHandler> ghs;
+
+ /**
+ * The max connection snoozer miss time: 15 seconds.
+ * This is 3 times the client snooze frequency.
+ *
+ */
+ protected final long MAX_SNOOZE_TIMEOUT_MS = 1000 * 15;
+
+ /**
+ * The max game turn inactivity time.
+ *
+ */
+ protected long MAX_TURN_INACTIVITY_MS;
+
+ /**
+ * Constructor.
+ *
+ * @param maxTurnInactivityTimeMs the max configured game turn inactivity time in ms.
+ *
+ */
+ public ClientConnectionChecker(long maxTurnInactivityTimeMs) {
+ MAX_TURN_INACTIVITY_MS = maxTurnInactivityTimeMs;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public void run() {
+ while(true) {
+ try {
+ checkAllPlayers();
+ checkActivePlayersInactivity();
+ Thread.sleep(5000);
+ } catch (GameSnapshotUpdateException e) {
+ // Logging as fatal here as it's failing to send a game snapshot. Other
+ // message delivery failures will be considered errors.
+ logger.fatal("{} {} {}",
+ ServerDebugPrefixString.START_COMMAND_PREFIX,
+ ErrorTypeString.ERROR_UPDATING_GAME, e);
+ // Not adding the error here since it will not be possible to be sent
+ // to player if it already failed delivering a game.
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Advance the game state if an active player has been disconnected.
+ *
+ * @param pc the disconnected player connector reference.
+ * @throws NullScoreBlockListException If the list of scoreblocks is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws NegativeMatchedBlockCountException If the number of matched blocks to set is negative.
+ * @throws NullMatchedBlockCountException If the number of matched blocks to set is null.
+ * @throws NullPointerException Generic NPE.
+ * @throws InterruptedException Thread interruption exception.
+ * @throws NullGameHandlerInstance If it is not possible to retrieve a game handler referece by id.
+ * @throws GameSnapshotUpdateException If not able to push game update.
+ *
+ */
+ protected void advanceGame(AbstractPlayerConnector pc)
+ throws BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException,
+ NullIndexValueException, NullPlayerBookshelfException, NullScoreBlockListException, NullPointerException,
+ NullMatchedBlockCountException, NegativeMatchedBlockCountException, InterruptedException, NullGameHandlerInstance, GameSnapshotUpdateException {
+ if (pc == null) {
+ logger.error("Can not advance the game state after the active player disconnection, null player connector received");
+ return;
+ }
+ //call next turn if the disconnected player is the active player
+ GameHandler handlerRef = ServerControllerState.getGameHandlerByUUID(pc.getGameId());
+ if (handlerRef.getGame().getActivePlayer().equals(pc.getPlayer())) {
+ handlerRef.getGame().nextTurn();
+ handlerRef.pushGameState();
+ handlerRef.updateCurrentPlayerHandler();
+ }
+ }
+
+ /**
+ * Perform connection check on all connected players.
+ * @throws GameSnapshotUpdateException
+ *
+ */
+ protected void checkAllPlayers() throws GameSnapshotUpdateException {
+ try {
+ pcs = ServerControllerState.getPlayersPool();
+ synchronized(pcs) {
+ for (AbstractPlayerConnector pc : pcs) {
+ if (pc.getPlayer().getIsConnected() && expired(pc.getLastSnoozeMs(), System.currentTimeMillis(), MAX_SNOOZE_TIMEOUT_MS)) {
+ logger.warn("Detected connection loss for {}, disconnecting the player", pc.getPlayer().getPlayerName());
+ pc.getPlayer().setIsConnected(false);
+
+ //call next turn if the disconnected player is the active player
+ advanceGame(pc);
+ }
+ }
+ }
+ } catch(
+ NullGameHandlerInstance | BookshelfGridColIndexOutOfBoundsException | BookshelfGridRowIndexOutOfBoundsException
+ | NullIndexValueException | NullPlayerBookshelfException | NullScoreBlockListException | NullPointerException
+ | NullMatchedBlockCountException | NegativeMatchedBlockCountException e) {
+ logger.error("{} {}", ErrorTypeString.ERROR_GAME_STATE, e);
+ } catch(InterruptedException e) {
+ logger.error("{} {}", ErrorTypeString.ERROR_MESSAGE_DELIVERY, e);
+ }
+ }
+
+ /**
+ * Perform activity check on all the active players across all games.
+ * @throws GameSnapshotUpdateException
+ *
+ */
+ protected void checkActivePlayersInactivity() throws GameSnapshotUpdateException {
+ try {
+ ghs = ServerControllerState.getGamePools();
+ synchronized(ghs) {
+ for (GameHandler gh : ghs) {
+ CurrentPlayerHandler h = gh.getCurrentPlayerHandler();
+ if (h.getPlayer().getIsConnected()
+ && expired(
+ h.getStartPlayingTimeMs(),
+ System.currentTimeMillis(), MAX_TURN_INACTIVITY_MS)
+ ) {
+ AbstractPlayerConnector connectorRef = gh.getPlayerConnectorFromPlayer(h.getPlayer());
+ //already notified, perform disconnectoion
+ if (h.getNotified()) {
+ logger.warn("Detected turn inactivity for {}, disconnecting the player", h.getPlayer().getPlayerName());
+ h.getPlayer().setIsConnected(false);
+ if (connectorRef != null) {
+ connectorRef.notify(new ErrorMessage("You have been disconnected due to inactivity", h.getPlayer(), ErrorSeverity.ERROR));
+ } else {
+ logger.error(
+ "{}: Failed to push warning message, can not find player connector from player", ErrorTypeString.ERROR_GAME_STATE);
+ }
+
+ //call next turn if the disconnected player is the active player
+ advanceGame(connectorRef);
+ //notify disconnection warning
+ } else {
+ h.setNotified(true);
+ h.setStartPlayingTimeMs(System.currentTimeMillis());
+ if (connectorRef != null) {
+ connectorRef.notify(
+ new ErrorMessage("You will be disconnected for inactivity in " + String.valueOf(MAX_TURN_INACTIVITY_MS/1000) + " seconds",
+ h.getPlayer(), ErrorSeverity.INFO));
+ } else {
+ logger.error("{}: Failed to push warning message, can not find player connector from player", ErrorTypeString.ERROR_GAME_STATE);
+ }
+ }
+ }
+ }
+ }
+ } catch(
+ NullGameHandlerInstance | BookshelfGridColIndexOutOfBoundsException | BookshelfGridRowIndexOutOfBoundsException
+ | NullIndexValueException | NullPlayerBookshelfException | NullScoreBlockListException | NullPointerException
+ | NullMatchedBlockCountException | NegativeMatchedBlockCountException e) {
+ logger.error("{} {}", ErrorTypeString.ERROR_GAME_STATE, e);
+ } catch(InterruptedException | RemoteException e) {
+ logger.error("{} {}", ErrorTypeString.ERROR_MESSAGE_DELIVERY, e);
+ }
+ }
+
+ /**
+ * Check if a player activity is expired or not.
+ *
+ * @param lhs The left hand side of the operation.
+ * @param rhs The right hand side of the operation.
+ * @param limit The operation limit value.
+ * @return The requested check result.
+ *
+ */
+ protected boolean expired(long lhs, long rhs, long limit) {
+ return rhs - lhs > limit;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerAction.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerAction.html
new file mode 100644
index 00000000..061d839b
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerAction.html
@@ -0,0 +1 @@
+ServerControllerAction
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerAction.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerAction.java.html
new file mode 100644
index 00000000..0b1752d6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerAction.java.html
@@ -0,0 +1,67 @@
+ServerControllerAction.java
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerRmiBindings.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerRmiBindings.html
new file mode 100644
index 00000000..4359121e
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerRmiBindings.html
@@ -0,0 +1 @@
+ServerControllerRmiBindings
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerRmiBindings.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerRmiBindings.java.html
new file mode 100644
index 00000000..0c27f9c2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerRmiBindings.java.html
@@ -0,0 +1,115 @@
+ServerControllerRmiBindings.java
package it.polimi.is23am10.server.controller;
+
+import java.rmi.RemoteException;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
+
+import it.polimi.is23am10.server.controller.exceptions.NullRmiServerException;
+import it.polimi.is23am10.server.controller.exceptions.NullRmiStubException;
+import it.polimi.is23am10.server.controller.interfaces.IServerControllerAction;
+
+public final class ServerControllerRmiBindings {
+
+ /**
+ * RMI don't care port flag.
+ *
+ */
+ private static final int DONT_CARE = 0;
+
+ /**
+ * RMI global registry service.
+ *
+ */
+ private static Registry globalRmiRegistry;
+
+ /**
+ * RMI {@link ServerControllerAction} server instance.
+ *
+ */
+ @SuppressWarnings("unused")
+ private static IServerControllerAction serverControllerActionRmiServer;
+
+ /**
+ * RMI {@link ServerControllerAction} stub instance.
+ *
+ */
+ private static IServerControllerAction serverControllerActionRmiStub;
+
+ /**
+ * Private constructor.
+ *
+ */
+ private ServerControllerRmiBindings() {
+ }
+
+ /**
+ * Retrieve the Server rmi registry.
+ *
+ * @return The registry.
+ *
+ */
+ public static Registry getRmiRegistry() {
+ return globalRmiRegistry;
+ }
+
+ /**
+ * Set the rmi registry.
+ *
+ * @param r The registry to be set.
+ *
+ */
+ public static void setRmiRegistry(Registry r) {
+ globalRmiRegistry = r;
+ }
+
+ /**
+ * Retrieve the rmi stub for {@link ServerControllerAction}.
+ *
+ * @return The stub.
+ *
+ */
+ public static IServerControllerAction getServerControllerActionRmiStub() {
+ return serverControllerActionRmiStub;
+ }
+
+ /**
+ * Set the rmi server for {@link ServerControllerAction}.
+ *
+ * @param s The rmi server.
+ *
+ */
+ public static void setServerControllerActionServer(IServerControllerAction s) throws NullRmiServerException {
+ if (s == null) {
+ throw new NullRmiServerException();
+ }
+ serverControllerActionRmiServer = s;
+ }
+
+ /**
+ * Set the rmi stub for {@link ServerControllerAction}.
+ *
+ * @param s The rmi stub.
+ *
+ */
+ public static void setServerControllerActionStub(IServerControllerAction s) throws NullRmiServerException {
+ if (s == null) {
+ throw new NullRmiStubException();
+ }
+ serverControllerActionRmiStub = s;
+ }
+
+ /**
+ * Rebind a {@link ServerControllerAction}.
+ *
+ * @param sca The {@link ServerControllerAction} instance.
+ *
+ */
+ public static void rebindServerControllerAction(IServerControllerAction sca) throws RemoteException {
+ ServerControllerRmiBindings.setServerControllerActionServer(sca);
+ ServerControllerRmiBindings
+ .setServerControllerActionStub((IServerControllerAction) UnicastRemoteObject.exportObject(sca, DONT_CARE));
+ ServerControllerRmiBindings.getRmiRegistry().rebind(IServerControllerAction.class.getName(),
+ ServerControllerRmiBindings.getServerControllerActionRmiStub());
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket$CommandDeserializer.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket$CommandDeserializer.html
new file mode 100644
index 00000000..5a040eda
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket$CommandDeserializer.html
@@ -0,0 +1 @@
+ServerControllerSocket.CommandDeserializer
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket$CoordinatesDeserializer.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket$CoordinatesDeserializer.html
new file mode 100644
index 00000000..82a363c5
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket$CoordinatesDeserializer.html
@@ -0,0 +1 @@
+ServerControllerSocket.CoordinatesDeserializer
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket.html
new file mode 100644
index 00000000..38928bd4
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket.html
@@ -0,0 +1 @@
+ServerControllerSocket
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket.java.html
new file mode 100644
index 00000000..4523def8
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerSocket.java.html
@@ -0,0 +1,242 @@
+ServerControllerSocket.java
package it.polimi.is23am10.server.controller;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSyntaxException;
+
+import it.polimi.is23am10.server.command.AbstractCommand;
+import it.polimi.is23am10.server.command.AddPlayerCommand;
+import it.polimi.is23am10.server.command.GetAvailableGamesCommand;
+import it.polimi.is23am10.server.command.MoveTilesCommand;
+import it.polimi.is23am10.server.command.SendChatMessageCommand;
+import it.polimi.is23am10.server.command.SnoozeGameTimerCommand;
+import it.polimi.is23am10.server.command.StartGameCommand;
+import it.polimi.is23am10.server.command.AbstractCommand.Opcode;
+import it.polimi.is23am10.server.controller.exceptions.NullGameHandlerInstance;
+import it.polimi.is23am10.server.network.messages.AbstractMessage;
+import it.polimi.is23am10.server.network.messages.ErrorMessage;
+import it.polimi.is23am10.server.network.messages.ErrorMessage.ErrorSeverity;
+import it.polimi.is23am10.server.network.playerconnector.PlayerConnectorSocket;
+import it.polimi.is23am10.utils.Coordinates;
+import it.polimi.is23am10.utils.ErrorTypeString;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.rmi.RemoteException;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * The server controller class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:nonemptyatclausedescriptioncheck" })
+public final class ServerControllerSocket implements Runnable {
+
+ /**
+ * The logger, an instance of {@link Logger}.
+ *
+ */
+ protected Logger logger = LogManager.getLogger(ServerControllerSocket.class);
+
+ /**
+ * The single client connection instance of type {@link PlayerConnectorSocket}.
+ * This is the entry and exit point for out responsive application.
+ *
+ */
+ protected PlayerConnectorSocket playerConnector;
+
+ /**
+ * The {@link Gson} serializer and deserializer for game's
+ * {@link AbstractCommand}.
+ * We need a custom {@link Gson} instance as we have to deserialize polymorphic
+ * objects {@link AbstractCommand}.
+ *
+ */
+ protected Gson gson = new GsonBuilder()
+ .registerTypeAdapter(AbstractCommand.class, new CommandDeserializer())
+ .registerTypeAdapter(Coordinates.class, new CoordinatesDeserializer())
+ .create();
+
+ /**
+ * The action taker instance. This works on a given command {@link Opcode}.
+ *
+ */
+ protected ServerControllerAction serverControllerAction;
+
+ /**
+ * Constructor.
+ *
+ * @param playerConnector The already build player connector instance
+ * with the low level socket instance.
+ * @param serverControllerAction The server action taker instance.
+ */
+ public ServerControllerSocket(PlayerConnectorSocket playerConnector,
+ ServerControllerAction serverControllerAction) {
+ this.playerConnector = playerConnector;
+ this.serverControllerAction = serverControllerAction;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public void run() {
+
+ while (playerConnector != null && !playerConnector.getConnector().isClosed()) {
+ try {
+ AbstractCommand command = buildCommand();
+ serverControllerAction.execute(playerConnector, command);
+ update();
+ } catch (IOException e) {
+ logger.error("Failed to retrieve socket payload", e);
+ break;
+ } catch (JsonIOException | JsonSyntaxException e) {
+ logger.error("Failed to parse socket payload", e);
+ } catch (InterruptedException e) {
+ logger.error("Failed get response message from the queue", e);
+ // note, we are not raising the interrupted flag as we don't want to stop this
+ // thread.
+ }
+ }
+
+ playerConnector.getPlayer().setIsConnected(false);
+ logger.info("Player {} disconnected", playerConnector.getPlayer().getPlayerName());
+ try {
+ ServerControllerState.getGameHandlerByUUID(
+ playerConnector.getGameId()).getPlayerConnectors()
+ .stream()
+ .filter(pc -> !pc.getPlayer().getPlayerName().equals(playerConnector.getPlayer().getPlayerName()))
+ .forEach(pc -> {
+ try {
+ pc.notify(
+ new ErrorMessage(String.format(ErrorTypeString.WARNING_PLAYER_DISCONNECT,
+ playerConnector.getPlayer().getPlayerName()), ErrorSeverity.INFO));
+ } catch (InterruptedException | RemoteException e) {
+ logger.error("{} {}", ErrorTypeString.ERROR_MESSAGE_DELIVERY, e);
+ }
+ });
+ } catch (NullGameHandlerInstance e) {
+ logger.error(" {} {}", ErrorTypeString.ERROR_ADDING_HANDLER, e);
+ }
+ }
+ /**
+ * Build the response message and sent it to the client when any game update is
+ * available.
+ *
+ * @throws IOException On output stream failure.
+ * @throws InterruptedException On queue message retrieval failure.
+ *
+ */
+ protected void update() throws InterruptedException, IOException {
+ AbstractMessage msg = playerConnector.getMessageFromQueue();
+ if (msg != null) {
+ PrintWriter printer;
+ synchronized (playerConnector.getConnector()) {
+ printer = new PrintWriter(playerConnector.getConnector().getOutputStream(), true,
+ StandardCharsets.UTF_8);
+ printer.println(gson.toJson(msg));
+ }
+ logger.info("{} sent to client {}", msg.getMessageType(), msg.getMessage() == null ? "": msg.getMessage());
+ }
+ }
+
+ /**
+ * Build the player command deserializing the byte stream.
+ * The gson deserialization returns a base class type but if the byte stream
+ * contained a derived type, this can be casted at runtime on the need.
+ *
+ * @return An instance of the command object.
+ * @throws IOException On output stream failure.
+ * @throws JsonIOException On serialization failure due to I/O.
+ * @throws JsonSyntaxException On serialization failure due to malformed JSON.
+ *
+ */
+ protected AbstractCommand buildCommand()
+ throws IOException, JsonIOException, JsonSyntaxException {
+ BufferedReader reader;
+ String payload = null;
+ synchronized (playerConnector.getConnector()) {
+ reader = new BufferedReader(
+ new InputStreamReader(playerConnector.getConnector().getInputStream()));
+ if (reader.ready()) {
+ payload = reader.readLine();
+ }
+ }
+ if (payload != null) {
+ logger.info("Socket buffer reader received {}", payload);
+ }
+ return gson.fromJson(payload, AbstractCommand.class);
+ }
+
+ /**
+ * Custom deserializer class definition for {@link Gson} usage.
+ * This works on polymorphic {@link AbstractCommand} objects.
+ *
+ */
+ class CommandDeserializer implements JsonDeserializer<AbstractCommand> {
+ @Override
+ public AbstractCommand deserialize(
+ JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject jsonObject = json.getAsJsonObject();
+
+ String className = "";
+ try {
+ className = jsonObject.get("className").getAsString();
+ } catch (Exception e) {
+ throw new JsonParseException(e);
+ }
+
+ switch (className) {
+ case "it.polimi.is23am10.server.command.StartGameCommand":
+ return context.deserialize(jsonObject, StartGameCommand.class);
+ case "it.polimi.is23am10.server.command.AddPlayerCommand":
+ return context.deserialize(jsonObject, AddPlayerCommand.class);
+ case "it.polimi.is23am10.server.command.MoveTilesCommand":
+ return context.deserialize(jsonObject, MoveTilesCommand.class);
+ case "it.polimi.is23am10.server.command.GetAvailableGamesCommand":
+ return context.deserialize(jsonObject, GetAvailableGamesCommand.class);
+ case "it.polimi.is23am10.server.command.SendChatMessageCommand":
+ return context.deserialize(jsonObject, SendChatMessageCommand.class);
+ case "it.polimi.is23am10.server.command.SnoozeGameTimerCommand":
+ return context.deserialize(jsonObject, SnoozeGameTimerCommand.class);
+ default:
+ throw new JsonParseException("Unknown class name: " + className);
+ }
+ }
+ }
+
+ /**
+ * Custom deserializer class definition for {@link Gson} usage.
+ * This works on {@link Coordinates} objects.
+ *
+ */
+ class CoordinatesDeserializer implements JsonDeserializer<Coordinates> {
+ @Override
+ public Coordinates deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject obj = json.getAsJsonObject();
+ int row = obj.get("row").getAsInt();
+ int col = obj.get("col").getAsInt();
+ return new Coordinates(row, col);
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerState.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerState.html
new file mode 100644
index 00000000..d069e627
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerState.html
@@ -0,0 +1 @@
+ServerControllerState
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerState.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerState.java.html
new file mode 100644
index 00000000..6a2b247e
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/ServerControllerState.java.html
@@ -0,0 +1,248 @@
+ServerControllerState.java
package it.polimi.is23am10.server.controller;
+
+import it.polimi.is23am10.server.controller.exceptions.NullGameHandlerInstance;
+import it.polimi.is23am10.server.model.factory.exceptions.DuplicatePlayerNameException;
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.network.gamehandler.GameHandler;
+import it.polimi.is23am10.server.network.gamehandler.exceptions.NullPlayerConnector;
+import it.polimi.is23am10.server.network.playerconnector.AbstractPlayerConnector;
+import it.polimi.is23am10.server.network.playerconnector.PlayerConnectorSocket;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * The server controller state class definition.
+ * This should be a singleton-like instance owning
+ * the server current state.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:abbreviationaswordinnamecheck" })
+public final class ServerControllerState {
+
+ /**
+ * The logger, an instance of {@link Logger}.
+ *
+ */
+ private static final Logger logger = LogManager.getLogger(ServerControllerState.class);
+
+ /**
+ * Active {@link GameHandler} instances.
+ *
+ */
+ private static Set<GameHandler> gamePool = Collections.synchronizedSet(new HashSet<>());
+
+ /**
+ * Active players connected with their {@link AbstractPlayerConnector}
+ * instances.
+ *
+ */
+ private static Set<AbstractPlayerConnector> playersPool =
+ Collections.synchronizedSet(new HashSet<>());
+
+ /**
+ * References to rmi connectors proxies. The link is made through the unique playerIds.
+ *
+ */
+ private static Map<UUID, AbstractPlayerConnector> rmiConnectorsProxies =
+ Collections.synchronizedMap(new HashMap<>());
+
+ /**
+ * Private constructor.
+ *
+ */
+ private ServerControllerState() {
+ }
+
+ /**
+ * Add a new game handler to the pool.
+ *
+ * @param handler The game handler instance to add.
+ * @throws NullGameHandlerInstance If the game handler is null. On null game handler.
+ *
+ */
+ public static final void addGameHandler(
+ GameHandler handler) throws NullGameHandlerInstance {
+ if (handler == null) {
+ throw new NullGameHandlerInstance();
+ }
+ //synch performed by the collection
+ gamePool.add(handler);
+ logger.info(
+ "Added new game handler with id {}", handler.getGame().getGameId());
+ }
+
+ /**
+ * Remove a game handler from the pool.
+ * This performs all the clients disconnections.
+ *
+ * @param id The game id to remove.
+ *
+ */
+ public static final void removeGameHandlerById(UUID id) {
+ if (id == null) {
+ return;
+ }
+
+ Optional<GameHandler> target;
+
+ synchronized (gamePool) {
+ target = gamePool.stream()
+ .filter(game -> game.getGame().getGameId().equals(id))
+ .findFirst();
+ }
+ if (target.isPresent()) {
+ GameHandler targetHandler = target.get();
+ synchronized (targetHandler) {
+ targetHandler.getPlayerConnectors()
+ .stream()
+ // point of optimization, can be parallelized
+ .forEach(connector ->
+ removePlayerByGame(connector.getGameId(), connector.getPlayer()));
+ }
+ gamePool.remove(targetHandler);
+ logger.info("Removed game handler with id {}", id);
+ }
+ }
+
+ /**
+ * Add player link to the pool.
+ *
+ * @param playerConnector The connector object to be linked with a player.
+ * @throws NullPlayerConnector On null Player connector.
+ * @throws DuplicatePlayerNameException On duplicate player name.
+ *
+ */
+ public static final void addPlayerConnector(
+ AbstractPlayerConnector playerConnector) throws NullPlayerConnector, DuplicatePlayerNameException {
+ if (playerConnector == null) {
+ throw new NullPlayerConnector();
+ }
+ Optional<AbstractPlayerConnector> found;
+ synchronized(playersPool) {
+ found = playersPool.stream()
+ .filter(p -> p.getPlayer().getPlayerName().equals(playerConnector.getPlayer().getPlayerName()))
+ .findFirst();
+ }
+ if (found.isPresent()) {
+ throw new DuplicatePlayerNameException("Player name already in use");
+ }
+ //synch is performed by the collection
+ playersPool.add(playerConnector);
+ logger.info("Added new player connector");
+ }
+
+ /**
+ * Remove a player connector from the pool.
+ * This closes the socket connection.
+ *
+ * @param gameId The game id reference.
+ * @param player The player to remove.
+ *
+ */
+ public static final void removePlayerByGame(UUID gameId, Player player) {
+ if (gameId == null || player == null) {
+ return;
+ }
+
+ Optional<AbstractPlayerConnector> target;
+
+ synchronized (playersPool) {
+ target = playersPool.stream()
+ .filter(connector ->
+ connector.getGameId().equals(gameId) && connector.getPlayer().equals(player))
+ .findFirst();
+ }
+ if (target.isPresent()) {
+ AbstractPlayerConnector targetConnector = target.get();
+ if (targetConnector.getClass() == PlayerConnectorSocket.class) {
+ try {
+ PlayerConnectorSocket ps = (PlayerConnectorSocket) targetConnector;
+ synchronized (ps.getConnector()) {
+ ps.getConnector().close();
+ }
+ } catch (IOException e) {
+ logger.error("Failed to close socket connection", e);
+ }
+ }
+ playersPool.remove(targetConnector);
+ logger.info("Removed player {} connector from game {}", player.getPlayerName(), gameId);
+ }
+ }
+
+ /**
+ * Finds a game handler in the game pool by its game id.
+ *
+ * @param gameId the UUID to search for
+ * @return the GameHandler, if found
+ * @throws NullGameHandlerInstance If the game handler is null. If game handler with that id is not found.
+ */
+ public static GameHandler getGameHandlerByUUID(UUID gameId) throws NullGameHandlerInstance {
+ Optional<GameHandler> target;
+
+ synchronized (gamePool) {
+ target = gamePool.stream()
+ .filter(gh -> gh.getGame().getGameId().equals(gameId))
+ .findFirst();
+ }
+ if (target.isPresent()) {
+ return target.get();
+ } else {
+ throw new NullGameHandlerInstance();
+ }
+ }
+
+ /**
+ * Player pool getter.
+ *
+ * @return The actively connected players.
+ *
+ */
+ public static synchronized Set<AbstractPlayerConnector> getPlayersPool() {
+ return playersPool;
+ }
+
+ /**
+ * Game pool getter.
+ *
+ * @return The actively started games instances.
+ *
+ */
+ public static synchronized Set<GameHandler> getGamePools() {
+ return gamePool;
+ }
+
+ /**
+ * Add a new rmi connector reference.
+ *
+ * @param id The player id.
+ * @param pc The proxy connector.
+ *
+ */
+ public static void addRmiProxyConnector(UUID id, AbstractPlayerConnector pc) {
+ rmiConnectorsProxies.put(id, pc);
+ }
+
+ /**
+ * Retrieve the rmi connector proxy associated to a player id.
+ *
+ * @return The rmi connector proxy.
+ *
+ */
+ public static AbstractPlayerConnector getRmiProxyConnector(UUID id) {
+ return rmiConnectorsProxies.get(id);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/index.html
new file mode 100644
index 00000000..633a46dd
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.controller
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/index.source.html
new file mode 100644
index 00000000..23750cbb
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.controller/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.controller
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/GameFactory.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/GameFactory.html
new file mode 100644
index 00000000..1a01b7fe
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/GameFactory.html
@@ -0,0 +1 @@
+GameFactory
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/GameFactory.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/GameFactory.java.html
new file mode 100644
index 00000000..eec9cc4d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/GameFactory.java.html
@@ -0,0 +1,95 @@
+GameFactory.java
package it.polimi.is23am10.server.model.factory;
+
+import java.util.Arrays;
+
+import it.polimi.is23am10.server.model.factory.exceptions.DuplicatePlayerNameException;
+import it.polimi.is23am10.server.model.factory.exceptions.NullPlayerNamesException;
+import it.polimi.is23am10.server.model.game.Game;
+import it.polimi.is23am10.server.model.game.Game.GameStatus;
+import it.polimi.is23am10.server.model.game.exceptions.FullGameException;
+import it.polimi.is23am10.server.model.game.exceptions.InvalidMaxPlayerException;
+import it.polimi.is23am10.server.model.game.exceptions.NullAssignedPatternException;
+import it.polimi.is23am10.server.model.game.exceptions.NullMaxPlayerException;
+import it.polimi.is23am10.server.model.game.exceptions.PlayerNotFoundException;
+import it.polimi.is23am10.server.model.items.board.exceptions.InvalidNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.board.exceptions.NullNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.card.SharedCard;
+import it.polimi.is23am10.server.model.items.card.exceptions.AlreadyInitiatedPatternException;
+import it.polimi.is23am10.server.model.items.scoreblock.exceptions.NotValidScoreBlockValueException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerBookshelfException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerIdException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerNameException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerPrivateCardException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreBlocksException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreException;
+
+/**
+ * The GameFactory class definition.
+ * This creates a new {@link Game} object.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:nonemptyatclausedescriptioncheck" })
+public final class GameFactory {
+ /**
+ * Private constructor.
+ *
+ */
+ private GameFactory() {
+
+ }
+
+ /**
+ * Create a new {@link Game} instance.
+ *
+ * @param startingPlayerName The starting player name, who has requested the
+ * game.
+ * @param maxPlayerNum The desired maximum player number, specified by the
+ * first player.
+ * @throws NullMaxPlayerException If no value for maximum number of players in the game is provided.
+ * @throws InvalidMaxPlayerException If value for maximum number of players in the game is not valid.
+ * @throws NullPlayerNameException If player name is null.
+ * @throws NullPlayerIdException If player id is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullPlayerScoreException If player's score object is null.
+ * @throws NullPlayerPrivateCardException If player's private card object is null.
+ * @throws NullPlayerScoreBlocksException If player's scoreblocks list is null.
+ * @throws DuplicatePlayerNameException If player with that name already exists.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one.
+ * @throws NullPlayerNamesException If, while adding multiple players, the list of player names is null.
+ * @throws InvalidNumOfPlayersException If, while adding multiple players, there is an invalid number of them.
+ * @throws NullNumOfPlayersException If the number of players provided when filling the board is null.
+ * @throws NullAssignedPatternException If the pattern assigned to a card is null.
+ * @throws NotValidScoreBlockValueException If the value assigned to a scoreblock is not valid.
+ * @throws PlayerNotFoundException If the player with the name provided is not found.
+ *
+ */
+ public static Game getNewGame(String startingPlayerName, Integer maxPlayerNum)
+ throws NullMaxPlayerException, InvalidMaxPlayerException, NullPlayerNameException,
+ NullPlayerIdException, NullPlayerBookshelfException, NullPlayerScoreException,
+ NullPlayerPrivateCardException, NullPlayerScoreBlocksException,
+ DuplicatePlayerNameException, AlreadyInitiatedPatternException,
+ NullPlayerNamesException, InvalidNumOfPlayersException, NullNumOfPlayersException,
+ NullAssignedPatternException,FullGameException, NotValidScoreBlockValueException, PlayerNotFoundException {
+
+ Game game = new Game();
+ game.setMaxPlayers(maxPlayerNum);
+ SharedCard firstCard = new SharedCard(game.getAssignedSharedPatterns(), game.getMaxPlayer());
+ game.addAssignedSharedPattern(firstCard.getPattern());
+ SharedCard secondCard = new SharedCard(game.getAssignedSharedPatterns(), game.getMaxPlayer());
+ game.addAssignedSharedPattern(secondCard.getPattern());
+
+ game.addPlayer(startingPlayerName);
+ // For testing purposes, setting active player to the "creator" player. Will be randomized once all players join.
+ game.setActivePlayer(game.getPlayerByName(startingPlayerName));
+ game.setGameBoard();
+ game.setSharedCards(Arrays.asList(firstCard, secondCard));
+ game.setStatus(GameStatus.WAITING_FOR_PLAYERS);
+
+ return game;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PlayerFactory.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PlayerFactory.html
new file mode 100644
index 00000000..5d01c685
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PlayerFactory.html
@@ -0,0 +1 @@
+PlayerFactory
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PlayerFactory.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PlayerFactory.java.html
new file mode 100644
index 00000000..29f37dea
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PlayerFactory.java.html
@@ -0,0 +1,107 @@
+PlayerFactory.java
package it.polimi.is23am10.server.model.factory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import it.polimi.is23am10.server.model.factory.exceptions.DuplicatePlayerNameException;
+import it.polimi.is23am10.server.model.game.Game;
+import it.polimi.is23am10.server.model.game.exceptions.NullAssignedPatternException;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.card.PrivateCard;
+import it.polimi.is23am10.server.model.items.card.exceptions.AlreadyInitiatedPatternException;
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerBookshelfException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerIdException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerNameException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerPrivateCardException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreBlocksException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreException;
+import it.polimi.is23am10.server.model.score.Score;
+
+/**
+ * The PlayerFactory class definition.
+ * This creates a new {@link Player} object.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:nonemptyatclausedescriptioncheck" })
+public class PlayerFactory {
+
+ /**
+ * Private constructor.
+ *
+ */
+ private PlayerFactory() {
+ }
+
+ /**
+ * Check if a player name has already been used across the game instance.
+ *
+ * @param playerName The chosen player name.
+ * @param playerNames Current game instance already available players names.
+ *
+ */
+ public static boolean isPlayerNameDuplicate(String playerName, List<String> playerNames) {
+ for (String name : playerNames) {
+ if (name.equals(playerName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Build a new {@link Player} object.
+ * This method has the ownership to ensure unique player names inside
+ * a game instance.
+ *
+ * @param playerName The chosen player name.
+ * @param game The game reference instance where the new player will be added.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one.
+ * @throws DuplicatePlayerNameException If player with that name already exists.
+ * @throws NullPlayerIdException If player id is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullPlayerNameException If player name is null.
+ * @throws NullPlayerPrivateCardException If player's private card object is null.
+ * @throws NullPlayerScoreBlocksException If player's scoreblocks list is null.
+ * @throws NullPlayerScoreException If player's score object is null.
+ * @throws NullAssignedPatternException If the pattern assigned to a card is null.
+ *
+ */
+ public static Player getNewPlayer(String playerName, Game game)
+ throws NullPlayerNameException, NullPlayerIdException, NullPlayerBookshelfException,
+ NullPlayerScoreException, NullPlayerPrivateCardException, NullPlayerScoreBlocksException,
+ DuplicatePlayerNameException, AlreadyInitiatedPatternException, NullAssignedPatternException {
+
+ // Consumer must handle this {@link DuplicatePlayerNameException}.
+
+ if (playerName == null) {
+ throw new NullPlayerNameException(
+ "[Class PlayerFactory, method getNewPlayer]: attribute playerName must not be null");
+ }
+
+ if (isPlayerNameDuplicate(playerName, game.getPlayerNames())) {
+ throw new DuplicatePlayerNameException(
+ "[Class PlayerFactory, method getNewPlayer]: The name " + playerName + " already exists");
+ }
+
+ Player instance = new Player();
+ PrivateCard privateCard = new PrivateCard(game.getAssignedPrivatePatterns());
+ game.addAssignedPrivatePattern(privateCard.getPattern());
+
+ instance.setPlayerID(UUID.nameUUIDFromBytes(playerName.getBytes()));
+ instance.setPlayerName(playerName);
+ instance.setScore(new Score());
+ instance.setBookshelf(new Bookshelf());
+ instance.setPrivateCard(privateCard);
+ instance.setScoreBlocks(new ArrayList<>());
+ instance.setIsConnected(true);
+
+ return instance;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PrivatePatternFactory.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PrivatePatternFactory.html
new file mode 100644
index 00000000..24570fd2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PrivatePatternFactory.html
@@ -0,0 +1 @@
+PrivatePatternFactory
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PrivatePatternFactory.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PrivatePatternFactory.java.html
new file mode 100644
index 00000000..8a376a2c
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/PrivatePatternFactory.java.html
@@ -0,0 +1,472 @@
+PrivatePatternFactory.java
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/SharedPatternFactory.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/SharedPatternFactory.html
new file mode 100644
index 00000000..a0e1ff88
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/SharedPatternFactory.html
@@ -0,0 +1 @@
+SharedPatternFactory
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/SharedPatternFactory.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/SharedPatternFactory.java.html
new file mode 100644
index 00000000..0d63a289
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/SharedPatternFactory.java.html
@@ -0,0 +1,603 @@
+SharedPatternFactory.java
package it.polimi.is23am10.server.model.factory;
+
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.tile.Tile;
+import it.polimi.is23am10.server.model.items.tile.Tile.TileType;
+import it.polimi.is23am10.server.model.pattern.SharedPattern;
+
+/**
+ * Shared pattern factory object.
+ *
+ * <p>
+ * NOTE: if not specified, each iteration of the player's bookshelf inside the
+ * functions is gonna be first over rows,then columns
+ * </p>
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+
+public final class SharedPatternFactory {
+
+ /**
+ * Private constructor.
+ *
+ */
+ private SharedPatternFactory() {
+
+ }
+
+ /**
+ * Number of occurrences need to comply checkTwoAdjacent rule.
+ */
+ private static final int TWO_ADJACENT_OCC = 6;
+
+ /**
+ * Number of occurrences need to comply checkFourAdjacent rule.
+ */
+ private static final int FOUR_ADJACENT_OCC = 4;
+
+ /**
+ * Number of occurrences need to comply checkSquares rule.
+ */
+ private static final int SQUARES_OCC = 2;
+
+ /**
+ * Number of occurrences need to comply checkMaxThreeTypesCol rule.
+ */
+ private static final int MAX_THREE_TYPES_COL_OCC = 3;
+
+ /**
+ * Number of occurrences need to comply checkEightDiff rule.
+ */
+ private static final int EIGHT_DIFF_OCC = 8;
+
+ /**
+ * Number of occurrences need to comply checkMaxThreeTypesRow rule.
+ */
+ private static final int MAX_THREE_TYPES_ROW_OCC = 4;
+
+ /**
+ * Number of occurrences need to comply checkTwoColsAllDiff rule.
+ */
+ private static final int COL_ALL_DIFF_OCC = 2;
+
+ /**
+ * Number of occurrences need to comply checkTwoRowsAllDiff rule.
+ */
+ private static final int ROW_ALL_DIFF_OCC = 2;
+
+ /**
+ * The random generator instance.
+ *
+ */
+ private static final Random random = new Random();
+
+ /**
+ * #1 in rulebook, #4 in images
+ * Rule that checks if there are at least six couples of the same tile type in
+ * adjacent positions (row or column).
+ *
+ */
+ public static final Predicate<Bookshelf> checkTwoAdjacent = bs -> {
+ int count = 0;
+ Tile[][] grid = bs.getBookshelfGrid();
+ Set<String> coordsAdjAlreadyCounted = new HashSet<>();
+
+ for (int i = 0; i < grid.length; i++) {
+ for (int j = 0; j < grid[i].length; j++) {
+ if (i < grid.length - 1 && grid[i][j].equals(grid[i + 1][j]) && !grid[i][j].isEmpty()
+ && (!coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i + 1) + String.valueOf(j)))) {
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j));
+ coordsAdjAlreadyCounted.add(String.valueOf(i + 1) + String.valueOf(j));
+ count++;
+ if (count >= TWO_ADJACENT_OCC) {
+ return true;
+ }
+ }
+
+ if (j < grid[i].length - 1 && grid[i][j].equals(grid[i][j + 1]) && !grid[i][j + 1].isEmpty()
+ && (!coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j + 1)))) {
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j));
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j + 1));
+ count++;
+ if (count >= TWO_ADJACENT_OCC) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ };
+
+ /*
+ * #2 in rulebook, #3 in images
+ * Rule that checks if the elements on all the corners match.
+ *
+ */
+ public static final Predicate<Bookshelf> checkCornersMatch = bs -> {
+ Tile[][] grid = bs.getBookshelfGrid();
+ if (grid[0][0].isEmpty()) {
+ return false;
+ }
+ return (grid[0][0].equals(grid[0][grid[0].length - 1]) &&
+ grid[0][0].equals(grid[grid.length - 1][0]) &&
+ grid[0][0].equals(grid[grid.length - 1][grid[0].length - 1]));
+ };
+
+ /**
+ * #3 in rulebook, #8 in images
+ * Rule that checks if there are at least 4 separate groups of 4 elements of
+ * the same type in adjacent positions(row or column).
+ *
+ */
+ public static final Predicate<Bookshelf> checkFourAdjacent = bs -> {
+ int count = 0;
+ Tile[][] grid = bs.getBookshelfGrid();
+ Set<String> coordsAdjAlreadyCounted = new HashSet<>();
+
+ for (int i = 0; i < grid.length; i++) {
+ for (int j = 0; j < grid[i].length; j++) {
+ if (i < grid.length - 3 && grid[i][j].equals(grid[i + 1][j]) && grid[i][j].equals(grid[i + 2][j])
+ && grid[i][j].equals(grid[i + 3][j]) && !grid[i][j].isEmpty()
+ && (!coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i + 1) + String.valueOf(j))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i + 2) + String.valueOf(j))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i + 3) + String.valueOf(j)))) {
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j));
+ coordsAdjAlreadyCounted.add(String.valueOf(i + 1) + String.valueOf(j));
+ coordsAdjAlreadyCounted.add(String.valueOf(i + 2) + String.valueOf(j));
+ coordsAdjAlreadyCounted.add(String.valueOf(i + 3) + String.valueOf(j));
+ count++;
+ if (count >= FOUR_ADJACENT_OCC) {
+ return true;
+ }
+ }
+
+ if (j < grid[i].length - 3 && grid[i][j].equals(grid[i][j + 1]) && grid[i][j].equals(grid[i][j + 2])
+ && grid[i][j].equals(grid[i][j + 3]) && !grid[i][j].isEmpty()
+ && (!coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j + 1))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j + 2))
+ && !coordsAdjAlreadyCounted.contains(String.valueOf(i) + String.valueOf(j + 3)))) {
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j));
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j + 1));
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j + 2));
+ coordsAdjAlreadyCounted.add(String.valueOf(i) + String.valueOf(j + 3));
+ count++;
+ if (count >= FOUR_ADJACENT_OCC) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ };
+
+ /**
+ * Support function that checks if there's a 2x2 square of tiles with the same
+ * type.
+ *
+ * @param startRow Starting row from where I want to check if it's a valid
+ * square.
+ * @param startCol Starting col from where I want to check if it's a valid
+ * square.
+ * @param grid bookshelf grid.
+ * @return True if the square is present.
+ */
+ private static final boolean isValidSquare(int startRow, int startCol, Tile[][] grid) {
+ if (startRow >= grid.length || startCol >= grid[0].length || startRow < 0 || startCol < 0) {
+ return false;
+ }
+ TileType currType = grid[startRow][startCol].getType();
+ return (grid[startRow][startCol].equals(grid[startRow + 1][startCol])
+ && grid[startRow][startCol].equals(grid[startRow][startCol + 1])
+ && grid[startRow][startCol].equals(grid[startRow + 1][startCol + 1]) && currType != TileType.EMPTY);
+ }
+
+ /*
+ * #4 in rulebook, #1 in images
+ * Rule that checks if there are at least two 2x2 squares of tiles of the same
+ * type (the type has to be the same for both the squares).
+ *
+ */
+ public static final Predicate<Bookshelf> checkSquares = bs -> {
+ Tile[][] grid = bs.getBookshelfGrid();
+ EnumMap<TileType, Integer> checkCount = new EnumMap<>(TileType.class);
+
+ for (int i = 0; i < grid.length - 1; i++) {
+ for (int j = 0; j < grid[i].length - 1; j++) {
+ TileType currType = grid[i][j].getType();
+ if (isValidSquare(i, j, grid)) {
+ if (!checkCount.containsKey(currType)) {
+ checkCount.put(currType, 1);
+ } else {
+ int oldCount = checkCount.get(currType);
+ checkCount.put(currType, oldCount + 1);
+ if (checkCount.get(currType) >= SQUARES_OCC) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ };
+
+ /**
+ * #5 both in rulebook and images
+ * Rule that checks if there are at least three columns containing maximum 3
+ * different types of tiles.
+ *
+ */
+ public static final Predicate<Bookshelf> checkMaxThreeTypesInColumn = bs -> {
+ int countedColumns = 0;
+ Tile[][] grid = bs.getBookshelfGrid();
+
+ // here we want to check every column so we are iterating first over columns
+ for (int i = 0; i < grid[0].length; i++) {
+ Set<TileType> seenTypes = new HashSet<>();
+ boolean isFull = true;
+ for (int j = 0; j < grid.length; j++) {
+ if (grid[j][i].isEmpty()){
+ isFull = false;
+ }
+ if (!grid[j][i].isEmpty() && !seenTypes.contains(grid[j][i].getType())) {
+ seenTypes.add(grid[j][i].getType());
+ }
+ }
+ if (seenTypes.size() <= 3 && isFull) {
+ countedColumns++;
+ if (countedColumns >= MAX_THREE_TYPES_COL_OCC) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /*
+ * #6 in rulebook, #9 in images
+ * Rule that checks if there are at least eight different tiles of the same
+ * type.
+ *
+ */
+ public static final Predicate<Bookshelf> checkEightOfSameType = bs -> {
+ Tile[][] grid = bs.getBookshelfGrid();
+ EnumMap<TileType, Integer> checkCount = new EnumMap<>(TileType.class);
+ for (int i = 0; i < grid.length; i++) {
+ for (int j = 0; j < grid[i].length; j++) {
+ if (!grid[i][j].isEmpty()) {
+ if (!checkCount.containsKey(grid[i][j].getType())) {
+ checkCount.put(grid[i][j].getType(), 1);
+ } else {
+ int oldCount = checkCount.get(grid[i][j].getType());
+ checkCount.put(grid[i][j].getType(), oldCount + 1);
+ }
+ if (checkCount.get(grid[i][j].getType()) >= EIGHT_DIFF_OCC) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ };
+
+ /**
+ * Supporting method checking ascendent diagonal in Bookshelf.
+ *
+ * @param startingRowOffset tells us if the diagonal is shifted vertically.
+ * @param grid bookshelf grid
+ * @return True if diagonal is present.
+ */
+ private static final boolean checkAscDiagonal(int startingRowOffset, Tile[][] grid) {
+ if (startingRowOffset >= grid.length || startingRowOffset < 0) {
+ return false;
+ }
+ for (int i = 0; i < grid[0].length - 1; i++) {
+ if (!grid[i + startingRowOffset][i].equals(grid[i + 1 + startingRowOffset][i + 1])
+ || grid[i + startingRowOffset][i].isEmpty()) {
+ break;
+ }
+ if (i == grid[0].length - 2) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Supporting method checking descendent diagonal in Bookshelf.
+ *
+ * @param startingRowOffset tells us if the diagonal is shifted vertically.
+ * @param grid bookshelf grid.
+ * @return True if diagonal is present.
+ */
+ private static final boolean checkDescDiagonal(int startingRowOffset, Tile[][] grid) {
+ if (startingRowOffset >= grid.length) {
+ return false;
+ }
+ for (int i = 0; i < grid[0].length - 1; i++) {
+ if (!grid[grid.length - 1 - i - startingRowOffset][i]
+ .equals(grid[grid.length - 2 - i - startingRowOffset][i + 1])
+ || grid[grid.length - 1 - i - startingRowOffset][i].isEmpty()) {
+ break;
+ }
+ if (i == grid[0].length - 2) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * #7 in rulebook, #11 in images
+ * Rule that checks if the diagonals are filled with tiles of the same type.
+ *
+ */
+ public static final Predicate<Bookshelf> checkDiagonalsSameType = bs -> {
+ Tile[][] grid = bs.getBookshelfGrid();
+
+ // I'm cycling with i=0,1 meaning that I'm checking both the diagonals if the
+ // starting row is the first one(i=0) or the second one (i=1)
+ for (int i = 0; i <= 1; i++) {
+ if (checkAscDiagonal(i, grid) || checkDescDiagonal(i, grid)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ /**
+ * #8 in rulebook, #7 in images
+ * Rule that checks if there are maximum three different types in at least 4
+ * rows.
+ *
+ */
+ public static final Predicate<Bookshelf> checkMaxThreeTypesInRow = bs -> {
+ int countRows = 0;
+ Tile[][] grid = bs.getBookshelfGrid();
+ for (int i = 0; i < grid.length; i++) {
+ Set<TileType> seenTypes = new HashSet<>();
+ boolean isFull = true;
+ for (int j = 0; j < grid[0].length; j++) {
+ if (grid[i][j].isEmpty()) {
+ isFull = false;
+ }
+ if (!grid[i][j].isEmpty() && !seenTypes.contains(grid[i][j].getType())) {
+ seenTypes.add(grid[i][j].getType());
+ }
+ }
+
+ if (seenTypes.size() <= 3 && isFull) {
+ countRows++;
+ if (countRows >= MAX_THREE_TYPES_ROW_OCC) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /**
+ * #9 in rulebook, #2 in images
+ * Rule that checks if there are at least two columns with all the elements of
+ * different type.
+ *
+ */
+ public static final Predicate<Bookshelf> checkTwoColumnAllDiff = bs -> {
+ int countColumns = 0;
+ Tile[][] grid = bs.getBookshelfGrid();
+ /*
+ * here I am iterating over columns first since I want to check if in a column
+ * there are all different types
+ *
+ */
+ for (int i = 0; i < grid[0].length; i++) {
+ Set<TileType> seenTypes = new HashSet<>();
+ for (int j = 0; j < grid.length; j++) {
+ if (grid[j][i].isEmpty() || seenTypes.contains(grid[j][i].getType())) {
+ break;
+ }
+ seenTypes.add(grid[j][i].getType());
+ if (j == grid.length - 1) {
+ countColumns++;
+ }
+ }
+ if (countColumns >= COL_ALL_DIFF_OCC) {
+ return true;
+ }
+ }
+ return false;
+
+ };
+
+ /**
+ * #10 in rulebook, #6 in images
+ * Rule that checks if there are at least two rows full of different types of
+ * tiles.
+ *
+ */
+ public static final Predicate<Bookshelf> checkTwoRowsAllDiff = bs -> {
+ int countRows = 0;
+ Tile[][] grid = bs.getBookshelfGrid();
+ for (int i = 0; i < grid.length; i++) {
+ Set<TileType> seenTypes = new HashSet<>();
+ for (int j = 0; j < grid[0].length; j++) {
+ if (grid[i][j].isEmpty() || seenTypes.contains(grid[i][j].getType())) {
+ break;
+ }
+ seenTypes.add(grid[i][j].getType());
+ if (j == grid[0].length - 1) {
+ countRows++;
+ }
+ }
+ if (countRows >= ROW_ALL_DIFF_OCC) {
+ return true;
+ }
+ }
+ return false;
+
+ };
+
+ /**
+ * Support function that checks, starting from a tile in the grid, if it's part
+ * of an X shape of tiles with the same type.
+ *
+ * @param row Starting row index from where I start to check.
+ * @param col Starting col index from where I start to check.
+ * @param grid Bookshelf grid.
+ * @return True if X shape is present.
+ *
+ */
+ private static final boolean checkXShape(int row, int col, Tile[][] grid) {
+ if (row >= grid.length || col >= grid[0].length || row < 0 || col < 0) {
+ return false;
+ }
+ return (grid[row][col].equals(grid[row + 2][col]) &&
+ grid[row][col].equals(grid[row + 1][col + 1]) &&
+ grid[row][col].equals(grid[row + 2][col + 2]) &&
+ grid[row][col].equals(grid[row][col + 2]) && !grid[row][col].isEmpty());
+ }
+
+ /**
+ * #11 in rulebook, #10 in images
+ * Rule that checks if there are 5 tiles of the same type on a 'X' shape.
+ *
+ */
+ public static final Predicate<Bookshelf> checkTilesXShape = bs -> {
+ Tile[][] grid = bs.getBookshelfGrid();
+
+ for (int i = 0; i < grid.length - 2; i++) {
+ for (int j = 0; j < grid[0].length - 2; j++) {
+ if (checkXShape(i, j, grid)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /**
+ * #12 both in rulebook and images
+ * Rule that checks if the columns in the bookshelf are ordered (asc o desc) and
+ * the rest of the bookshelf is filled with {@link TileType#EMPTY}.
+ *
+ */
+ public static final Predicate<Bookshelf> checkOrderedBookshelfColumns = bs -> {
+ Tile[][] grid = bs.getBookshelfGrid();
+
+ Predicate<Tile[][]> checkDescOrder = g -> {
+ for (int i = 0; i < g.length; i++) {
+ for (int j = 0; j < g[0].length; j++) {
+ if (j >= i && (!g[i][j].isEmpty())) {
+ return false;
+ }
+ if (j < i && (g[i][j].isEmpty())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ Predicate<Tile[][]> checkDescOrderShifted = g -> {
+ for (int i = 0; i < g.length; i++) {
+ for (int j = 0; j < g[0].length; j++) {
+ if (j > i && (g[i][j].getType() != TileType.EMPTY)) {
+ return false;
+ }
+ if (j <= i && (g[i][j].getType() == TileType.EMPTY)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ Predicate<Tile[][]> checkAscOrder = g -> {
+ for (int i = 0; i < g.length; i++) {
+ for (int j = 0; j < g[0].length; j++) {
+ if (j < g[0].length - i && (g[i][j].getType() != TileType.EMPTY)) {
+ return false;
+ }
+ if (j >= g[0].length - i && (g[i][j].getType() == TileType.EMPTY)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ Predicate<Tile[][]> checkAscOrderShifted = g -> {
+ for (int i = 0; i < g.length; i++) {
+ for (int j = 0; j < g[0].length; j++) {
+ if (j < g[0].length - 1 - i && (g[i][j].getType() != TileType.EMPTY)) {
+ return false;
+ }
+ if (j >= g[0].length - 1 - i && (g[i][j].getType() == TileType.EMPTY)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ return (checkAscOrder.test(grid) || checkDescOrder.test(grid) || checkAscOrderShifted.test(grid)
+ || checkDescOrderShifted.test(grid));
+
+ };
+
+ /**
+ * The list of {@link SharedPattern} containing all the 12 different
+ * patterns.
+ * rules with their lambda functions
+ *
+ */
+ private static final List<SharedPattern<Predicate<Bookshelf>>> patternsArray = List.of(
+ (new SharedPattern<Predicate<Bookshelf>>(checkTwoAdjacent, 4)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkCornersMatch, 8)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkFourAdjacent, 3)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkSquares, 1)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkMaxThreeTypesInColumn,5)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkEightOfSameType,9)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkDiagonalsSameType,11)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkMaxThreeTypesInRow, 7)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkTwoColumnAllDiff,2)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkTwoRowsAllDiff, 6)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkTilesXShape, 10)),
+ (new SharedPattern<Predicate<Bookshelf>>(checkOrderedBookshelfColumns, 12)));
+
+ /**
+ * Method used to get random SharedPattern between the 12 possible.
+ *
+ * @param usedPatterns a List of {@link SharedPattern} storing the already used
+ * patterns.
+ * @return a random pattern between the 12 possible.
+ */
+ public static SharedPattern<Predicate<Bookshelf>> getNotUsedPattern(
+ List<SharedPattern<Predicate<Bookshelf>>> usedPatterns) {
+ if (usedPatterns.isEmpty()) {
+ return patternsArray.get(random.nextInt(patternsArray.size()));
+ } else {
+ List<SharedPattern<Predicate<Bookshelf>>> unusedPatterns = patternsArray.stream()
+ .filter(pattern -> !usedPatterns.contains(pattern))
+ .collect(Collectors.toList());
+ return unusedPatterns.get(random.nextInt(unusedPatterns.size()));
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/index.html
new file mode 100644
index 00000000..f1ec8212
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.factory
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/index.source.html
new file mode 100644
index 00000000..d9dcdb55
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.factory/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.factory
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game$GameStatus.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game$GameStatus.html
new file mode 100644
index 00000000..e93f689e
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game$GameStatus.html
@@ -0,0 +1 @@
+Game.GameStatus
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game.html
new file mode 100644
index 00000000..a60f690d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game.html
@@ -0,0 +1 @@
+Game
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game.java.html
new file mode 100644
index 00000000..2f877604
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/Game.java.html
@@ -0,0 +1,754 @@
+Game.java
package it.polimi.is23am10.server.model.game;
+
+import it.polimi.is23am10.server.controller.exceptions.NullGameHandlerInstance;
+import it.polimi.is23am10.server.model.factory.PlayerFactory;
+import it.polimi.is23am10.server.model.factory.exceptions.DuplicatePlayerNameException;
+import it.polimi.is23am10.server.model.factory.exceptions.NullPlayerNamesException;
+import it.polimi.is23am10.server.model.game.exceptions.FullGameException;
+import it.polimi.is23am10.server.model.game.exceptions.InvalidMaxPlayerException;
+import it.polimi.is23am10.server.model.game.exceptions.InvalidPlayersNumberException;
+import it.polimi.is23am10.server.model.game.exceptions.NullAssignedPatternException;
+import it.polimi.is23am10.server.model.game.exceptions.NullMaxPlayerException;
+import it.polimi.is23am10.server.model.game.exceptions.NullPlayerException;
+import it.polimi.is23am10.server.model.game.exceptions.PlayerNotFoundException;
+import it.polimi.is23am10.server.model.items.board.Board;
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.board.exceptions.InvalidNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.board.exceptions.NullNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.NullTileException;
+import it.polimi.is23am10.server.model.items.card.SharedCard;
+import it.polimi.is23am10.server.model.items.card.exceptions.AlreadyInitiatedPatternException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NegativeMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullScoreBlockListException;
+import it.polimi.is23am10.server.model.items.tile.Tile;
+import it.polimi.is23am10.server.model.pattern.PrivatePattern;
+import it.polimi.is23am10.server.model.pattern.SharedPattern;
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerBookshelfException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerIdException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerNameException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerPrivateCardException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreBlocksException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreException;
+import it.polimi.is23am10.utils.Coordinates;
+import it.polimi.is23am10.utils.MovesValidator;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import it.polimi.is23am10.utils.exceptions.WrongBookShelfPicksException;
+import it.polimi.is23am10.utils.exceptions.WrongGameBoardPicksException;
+import it.polimi.is23am10.utils.exceptions.WrongMovesNumberException;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * The Game class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class Game implements Serializable {
+
+ /**
+ * The minimum number of players in a game instance.
+ *
+ */
+ private static final Integer MIN_PLAYER_NUM = 2;
+
+ /**
+ * The maximum number of players in a game instance.
+ *
+ */
+ private static final Integer MAX_PLAYER_NUM = 4;
+
+ /**
+ * A randomly generated {@link UUID} id.
+ *
+ */
+ private UUID gameId;
+
+ /**
+ * The max number of players.
+ * Setting a default value to avoid undefined behaviors.
+ *
+ */
+ private Integer maxPlayers = MIN_PLAYER_NUM;
+
+ /**
+ * List of {@link Player} type.
+ * This instance must never be null.
+ *
+ */
+ private List<Player> players = new ArrayList<>();
+
+ /**
+ * A randomly chosen first player of the game.
+ *
+ */
+ private Player firstPlayer;
+
+ /**
+ * Player currently playing.
+ */
+ private Player activePlayer;
+
+ /**
+ * Winner player.
+ */
+ private Player winnerPlayer;
+
+ /**
+ * The instance {@link Board} type.
+ *
+ */
+ private Board gameBoard;
+
+ /**
+ * List of {@link SharedCard} type containing two randomly selected
+ * shared cards for this game.
+ */
+ private List<SharedCard> sharedCards;
+
+ /**
+ * All the possible status the game can be in.
+ *
+ */
+ public enum GameStatus {
+ WAITING_FOR_PLAYERS,
+ STARTED,
+ LAST_ROUND,
+ ENDED
+ }
+
+ /**
+ * The current status of the game.
+ *
+ */
+ private GameStatus status;
+
+ /**
+ * A cache to store already used shared patterns.
+ *
+ */
+ private transient List<SharedPattern<Predicate<Bookshelf>>> assignedSharedPatterns;
+
+ /**
+ * A cache to store already used private patterns.
+ *
+ */
+ private transient List<PrivatePattern<Function<Bookshelf, Integer>>> assignedPrivatePatterns;
+
+ /**
+ * Random object used to pick starting player and player positions.
+ */
+ private final transient Random random = new Random();
+
+ /**
+ * Constructor that assigns the only value that is
+ * generated, immutable and not set by factory.
+ */
+ public Game() {
+ gameId = UUID.randomUUID();
+ assignedSharedPatterns = new ArrayList<>();
+ assignedPrivatePatterns = new ArrayList<>();
+ }
+
+ /**
+ * Retrieve the already used {@link SharedPattern}s.
+ *
+ * @return The already assigned {@link SharedPattern}s.
+ *
+ */
+ public synchronized List<SharedPattern<Predicate<Bookshelf>>> getAssignedSharedPatterns() {
+ return assignedSharedPatterns;
+ }
+
+ /**
+ * Retrieve the already used {@link PrivatePattern}s.
+ *
+ * @return The already assigned {@link PrivatePattern}s.
+ *
+ */
+ public synchronized List<PrivatePattern<Function<Bookshelf, Integer>>> getAssignedPrivatePatterns() {
+ return assignedPrivatePatterns;
+ }
+
+ /**
+ * Add a new consumed {@link SharedPattern}.
+ *
+ * @param pattern The {@link SharedPattern} to be added.
+ * @throws NullAssignedPatternException If the pattern assigned to a card is null.
+ *
+ */
+ public synchronized void addAssignedSharedPattern(SharedPattern<Predicate<Bookshelf>> pattern)
+ throws NullAssignedPatternException {
+ if (pattern == null) {
+ throw new NullAssignedPatternException("shared");
+ }
+ assignedSharedPatterns.add(pattern);
+ }
+
+ /**
+ * Add a new consumed {@link PrivatePattern}.
+ *
+ * @param pattern The {@link PrivatePattern} to be added.
+ * @throws NullAssignedPatternException If the pattern assigned to a card is null.
+ *
+ */
+ public synchronized void addAssignedPrivatePattern(PrivatePattern<Function<Bookshelf, Integer>> pattern)
+ throws NullAssignedPatternException {
+ if (pattern == null) {
+ throw new NullAssignedPatternException("private");
+ }
+ assignedPrivatePatterns.add(pattern);
+ }
+
+ /**
+ * Check if a maxPlayer value is correct.
+ *
+ * @param maxPlayers The value to be controlled.
+ * @throws NullMaxPlayerException If no value for maximum number of players in the game is provided..
+ *
+ */
+ private boolean validMaxPlayers(Integer maxPlayers) throws NullMaxPlayerException {
+ if (maxPlayers == null) {
+ throw new NullMaxPlayerException();
+ }
+ return maxPlayers >= MIN_PLAYER_NUM && maxPlayers <= MAX_PLAYER_NUM;
+ }
+
+ /**
+ * The maxPlayers setter.
+ *
+ * @param maxPlayers The value to be assigned.
+ * @throws NullMaxPlayerException If no value for maximum number of players in the game is provided.
+ * @throws InvalidMaxPlayerException If value for maximum number of players in the game is not valid.
+ *
+ */
+ public synchronized void setMaxPlayers(Integer maxPlayers)
+ throws NullMaxPlayerException, InvalidMaxPlayerException {
+ if (!validMaxPlayers(maxPlayers)) {
+ throw new InvalidMaxPlayerException();
+ }
+ this.maxPlayers = maxPlayers;
+ }
+
+ /**
+ * The firstPlayer setter.
+ *
+ * @param playerToSet The first player's name.
+ *
+ */
+ public synchronized void setFirstPlayer(Player playerToSet) {
+ players.stream()
+ .filter(player -> player.equals(playerToSet))
+ .findFirst()
+ .ifPresent(player -> firstPlayer = player);
+ }
+
+ /**
+ * Add a new player to the game. Position is randomly determined,
+ * as position in players list is the order in the game.
+ *
+ * @param playerName The player's name.
+ * @throws NullPlayerNamesException If, while adding multiple players, the list of player names is null.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one.
+ * @throws DuplicatePlayerNameException If player with that name already exists.
+ * @throws NullPlayerScoreBlocksException If player's scoreblocks list is null.
+ * @throws NullPlayerPrivateCardException If player's private card object is null.
+ * @throws NullPlayerScoreException If player's score object is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullPlayerIdException If player id is null.
+ * @throws NullPlayerNameException If player name is null.
+ * @throws NullAssignedPatternException If the pattern assigned to a card is null.
+ *
+ */
+ private synchronized void addPlayer(Player player) {
+ final Integer position = players.isEmpty() ? 0 : random.nextInt(players.size());
+ players.add(position, player);
+ if (players.size() == maxPlayers) {
+ setStatus(GameStatus.STARTED);
+ assignPlayers();
+ }
+ }
+
+ /**
+ * Creates and adds a new player to the game. Position is randomly determined,
+ * as position in players list is the order in the game.
+ *
+ * @param playerName The player's name.
+ * @return The instance of created player.
+ * @throws NullPlayerNamesException If, while adding multiple players, the list of player names is null.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one.
+ * @throws DuplicatePlayerNameException If player with that name already exists.
+ * @throws NullPlayerScoreBlocksException If player's scoreblocks list is null.
+ * @throws NullPlayerPrivateCardException If player's private card object is null.
+ * @throws NullPlayerScoreException If player's score object is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullPlayerIdException If player id is null.
+ * @throws NullPlayerNameException If player name is null.
+ * @throws NullAssignedPatternException If the pattern assigned to a card is null.
+ * @throws FullGameException If game is full, on player trying to join.
+ */
+ public Player addPlayer(String playerName)
+ throws NullPlayerNamesException, NullPlayerNameException, NullPlayerIdException,
+ NullPlayerBookshelfException, NullPlayerScoreException, NullPlayerPrivateCardException,
+ NullPlayerScoreBlocksException, DuplicatePlayerNameException, AlreadyInitiatedPatternException,
+ FullGameException, NullAssignedPatternException {
+ if (getPlayers().size() == getMaxPlayer()) {
+ throw new FullGameException(
+ playerName + "could not be added, because the game reached its maximum number of players");
+ }
+ Player playerToAdd = PlayerFactory.getNewPlayer(playerName, this);
+ addPlayer(playerToAdd);
+ return playerToAdd;
+ }
+
+ /**
+ * Function that adds multiple players to game.
+ *
+ * @param players List of players to add.
+ * @throws NullPlayerException If player object is null.
+ * @throws InvalidPlayersNumberException If number of players to add is invalid.
+ * @throws DuplicatePlayerNameException If player with that name already exists.
+ */
+ public synchronized void addPlayers(List<Player> players)
+ throws NullPlayerException, InvalidPlayersNumberException, DuplicatePlayerNameException {
+
+ if (players == null) {
+ throw new NullPlayerException();
+ }
+ if ((players.size() + this.players.size()) != maxPlayers) {
+ throw new InvalidPlayersNumberException();
+ }
+ for (Player newPlayer : players) {
+ if (PlayerFactory.isPlayerNameDuplicate(newPlayer.getPlayerName(), getPlayerNames())) {
+ throw new DuplicatePlayerNameException(
+ "[Class Game, method addPlayers]: The name " + newPlayer.getPlayerName() + " already exists");
+ }
+ }
+ this.players.addAll(players);
+ }
+
+ /**
+ * GameBoard setter.
+ *
+ * @throws InvalidNumOfPlayersException If, while adding multiple players, there is an invalid number of them.
+ * @throws NullNumOfPlayersException If the number of players provided when filling the board is null.
+ *
+ */
+ public synchronized void setGameBoard() throws InvalidNumOfPlayersException, NullNumOfPlayersException {
+ this.gameBoard = new Board(maxPlayers);
+ }
+
+ /**
+ * The sharedCards setter.
+ *
+ */
+ public synchronized void setSharedCards(List<SharedCard> cards) {
+ this.sharedCards = new ArrayList<>();
+ sharedCards.add(cards.get(0));
+ sharedCards.add(cards.get(1));
+ }
+
+ /**
+ * The status setter.
+ *
+ * @param status The status to set.
+ *
+ */
+ public synchronized void setStatus(GameStatus status) {
+ this.status = status;
+ }
+
+ /**
+ * The gameId getter.
+ *
+ * @return The game id.
+ *
+ */
+ public synchronized UUID getGameId() {
+ return gameId;
+ }
+
+ /**
+ * MaxPlayer getter.
+ *
+ * @return The maximum number of players for the current game instance.
+ *
+ */
+ public synchronized Integer getMaxPlayer() {
+ return maxPlayers;
+ }
+
+ /**
+ * The players getter.
+ *
+ * @return A list containing all the current players.
+ *
+ */
+ public synchronized List<Player> getPlayers() {
+ return players;
+ }
+
+ /**
+ * The firstPlayer getter.
+ *
+ * @return The game first player.
+ * This player has started the game.
+ *
+ */
+ public synchronized Player getFirstPlayer() {
+ return firstPlayer;
+ }
+
+ /**
+ * The gameBoard getter.
+ *
+ * @return The game board grid.
+ *
+ */
+ public synchronized Board getGameBoard() {
+ return gameBoard;
+ }
+
+ /**
+ * The sharedCards getter.
+ *
+ * @return The assigned shared cards to the current game instance.
+ *
+ */
+ public synchronized List<SharedCard> getSharedCard() {
+ return sharedCards;
+ }
+
+ /**
+ * The ended status getter.
+ *
+ * @return The current status of the game.
+ *
+ */
+ public synchronized GameStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * Retrieve the current players' names.
+ *
+ * @return A {@link List} containing all the current players' names.
+ *
+ */
+ public synchronized List<String> getPlayerNames() {
+ return players.stream()
+ .map(Player::getPlayerName)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Method used to retrieve a player from the list
+ * given its name.
+ *
+ * @param playerName The player name we are looking for.
+ * @return Player matching provided name.
+ * @throws PlayerNotFoundException If the player with the name provided is not found.
+ */
+ public synchronized Player getPlayerByName(String playerName) throws NullPlayerNameException, PlayerNotFoundException {
+ if (playerName == null) {
+ throw new NullPlayerNameException("[Class Game, method getPlayerByName]");
+ }
+ Optional<Player> player = players.stream()
+ .filter(p -> p.getPlayerName().equals(playerName))
+ .findFirst();
+ if (player.isPresent()) {
+ return player.get();
+ } else {
+ throw new PlayerNotFoundException();
+ }
+ }
+
+ /**
+ * Method to set the active player (playing this turn).
+ *
+ * @param player Player to set as active
+ */
+ public synchronized void setActivePlayer(Player player) {
+ this.activePlayer = player;
+ }
+
+ /**
+ * WinnerPlayer setter. To be called by {@link Game#endGame()} only.
+ *
+ * @param player The winning player to set.
+ */
+ public synchronized void setWinnerPlayer(Player player) {
+ this.winnerPlayer = player;
+ }
+
+ /**
+ * ActivePlayer getter.
+ *
+ * @return The active player.
+ */
+ public synchronized Player getActivePlayer() {
+ return activePlayer;
+ }
+
+ /**
+ * WinnerPlayer getter.
+ *
+ * @return The winning player.
+ */
+ public synchronized Player getWinnerPlayer() {
+ return this.winnerPlayer;
+ }
+
+ /**
+ * Method to assign the scoreBlocks. For both shared cards
+ * their pattern is checked against activePlayer's BS.
+ * If pattern is satisfied AND player didn't get a scoreBlock
+ * from that card, the first available SB is given to the player.
+ *
+ */
+ private synchronized void assignScoreBlocks() {
+ sharedCards.forEach(c -> {
+ if (c.getPattern().getRule().test(activePlayer.getBookshelf()) && !c.getCardWinners().contains(activePlayer)) {
+ c.addCardWinner(activePlayer);
+ activePlayer.addScoreBlock(c.getScoreBlocks().remove(0));
+ }
+ });
+ }
+
+ /**
+ * Method that computes active player's Score, updates the view,
+ * checks if game is over and if not picks next player.
+ *
+ * @throws NullScoreBlockListException If the list of scoreblocks is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws NegativeMatchedBlockCountException If the number of matched blocks to set is negative.
+ * @throws NullMatchedBlockCountException If the number of matched blocks to set is null.
+ * @throws NullPointerException Generic NPE.
+ */
+ public synchronized void nextTurn()
+ throws BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException,
+ NullIndexValueException, NullPlayerBookshelfException, NullScoreBlockListException, NullPointerException,
+ NullMatchedBlockCountException, NegativeMatchedBlockCountException {
+ activePlayer.setIsActivePlayer(false);
+ assignScoreBlocks();
+ activePlayer.updateScore();
+ checkEndGame();
+
+ /*
+ * If there's only one player left, checkEndGame() will eventually
+ * end the game here setting the ended flag to true, otherwise
+ * if the game has two or more players still connected we're entering
+ * this part of code to decide next player playing
+ */
+ if (getStatus() != GameStatus.ENDED) {
+ try {
+ gameBoard.refillIfNeeded();
+ } catch (IndexOutOfBoundsException e) {
+ endGame();
+ return;
+ }
+ int nextPlayerIdx = (getPlayers().indexOf(activePlayer) + 1) % getPlayers().size();
+
+ while(!players.get(nextPlayerIdx).getIsConnected()){
+ nextPlayerIdx = (nextPlayerIdx+ 1) % getPlayers().size();
+ }
+ setActivePlayer(players.get(nextPlayerIdx));
+ players.get(nextPlayerIdx).setIsActivePlayer(true);
+ }
+ }
+
+ /**
+ * Function that allows the player to take a tile from the board.
+ *
+ * @param coord The coordinates of the tile.
+ * @return The tile of the board the player wants to take.
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds.
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ */
+ public synchronized Tile takeTileAction(Coordinates coord)
+ throws BoardGridRowIndexOutOfBoundsException, BoardGridColIndexOutOfBoundsException,
+ NullIndexValueException {
+ return gameBoard.takeTileAt(coord.getRow(), coord.getCol());
+ }
+
+ /**
+ * Function that puts a tile inside the active player's bookshelf.
+ *
+ * @param t Tile taken from the board.
+ * @param coord Coordinates of the bookshelf.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws NullTileException If the tile is null.
+ */
+ public synchronized void putTileAction(Tile t, Coordinates coord)
+ throws BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException,
+ NullIndexValueException, NullTileException {
+ activePlayer.getBookshelf().setBookshelfGridIndex(coord.getRow(), coord.getCol(), t);
+ }
+
+ /**
+ * Quick helper function to determine if the player is the last in turn.
+ *
+ * @param playerToCheck A reference player instance on which to operate the
+ * check.
+ * @return Is playerToCheck the last one in turn
+ */
+ private synchronized boolean isLastPlayer(Player playerToCheck) {
+ final Integer idxDiff = players.indexOf(playerToCheck) - players.indexOf(firstPlayer);
+ return (idxDiff == -1 || idxDiff == (maxPlayers - 1));
+ }
+
+ /**
+ * Function that checks if there's a player who completed
+ * their bookshelf and sets flags accordingly.
+ *
+ */
+ public synchronized void checkEndGame() {
+ if (activePlayer.getBookshelf().isBookshelfFull() && getStatus() != GameStatus.LAST_ROUND ) {
+ activePlayer.getScore().setExtraPoint();
+ // When one player completes their bookshelf, last turn starts
+ setStatus(GameStatus.LAST_ROUND);
+ }
+ // Regardless of bookshelf, if last player and lastRound, end game
+ if ((getStatus() == GameStatus.LAST_ROUND && isLastPlayer(activePlayer))
+ || players.stream().filter(p -> p.getIsConnected()).count() <= 1) {
+ endGame();
+ }
+ }
+
+ /**
+ * Helper method to be passed to {@link Game#endGame()}
+ * in order to determine the winner, according to game rules:
+ * In case of score parity, last player in turn wins.
+ *
+ * @param p1 First player
+ * @param p2 Second player
+ * @return Player who should win between two
+ */
+ private synchronized Player decideWinner(Player p1, Player p2) {
+ final Integer p1Score = p1.getScore().getTotalScore();
+ final Integer p2Score = p2.getScore().getTotalScore();
+
+ if (p1Score.equals(p2Score)) {
+ // Positions relative to firstPlayer can be negative -> Modular arithmetics
+ Integer startingPos1 = players.indexOf(p1) - players.indexOf(firstPlayer);
+ startingPos1 = startingPos1 > 0 ? startingPos1 : startingPos1 + maxPlayers;
+ Integer startingPos2 = players.indexOf(p2) - players.indexOf(firstPlayer);
+ startingPos2 = startingPos2 > 0 ? startingPos2 : startingPos2 + maxPlayers;
+ return (startingPos1 > startingPos2 ? p1 : p2);
+ } else {
+ return (p1Score > p2Score ? p1 : p2);
+ }
+ }
+
+ /**
+ * Method that is called when all players joined
+ * the game and the first one should be picked.
+ * Can be used in tests to force starting a game before
+ * the players threshold is met.
+ */
+ public synchronized void assignPlayers() {
+ Player choosenFirstPlayer = players.get(random.nextInt(players.size()));
+ activePlayer = choosenFirstPlayer;
+ activePlayer.setIsActivePlayer(true);
+ firstPlayer = choosenFirstPlayer;
+ }
+
+ /**
+ * Helper method that sets the game as ended
+ * and declares the winner.
+ */
+ private synchronized void endGame() {
+ setStatus(GameStatus.ENDED);
+ players.stream()
+ .reduce(this::decideWinner)
+ .ifPresent(this::setWinnerPlayer);
+ }
+
+ /**
+ * Simple helper function to get the number of disconnected
+ * players to discount when looking for available games.
+ * @return disconnected player num.
+ */
+ public synchronized Integer getDisconnectedPlayersNum() {
+ return (int) players
+ .stream()
+ .filter(p -> !p.getIsConnected())
+ .count();
+ }
+
+ /**
+ * Method that plays the active player's turn.
+ * It's important to understand the structure of the Hashmap, which allows to
+ * find a correspondence between the coordinates of the taken tile of the board
+ * and the coordinates of the player's bookshelf where he/she/they wants to put
+ * the taken tile in.
+ *
+ * <p>
+ * Note that I'm assuming all the params given to the method are valid since the
+ * input validation will be implemented client side in the selection of those
+ * coordinates.
+ * </p>
+ *
+ * @param selectedCoordinates Map containing the coordinates of selected tiles.
+ * from board as key and the corresponding
+ * coordinates of the active player bookshelf as
+ * value.
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds.
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws NullTileException If the tile is null.
+ * @throws NullPointerException Generic NPE.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullScoreBlockListException If the list of scoreblocks is null.
+ * @throws NegativeMatchedBlockCountException If the number of matched blocks to set is negative.
+ * @throws NullMatchedBlockCountException If the number of matched blocks to set is null.
+ * @throws WrongBookShelfPicksException If the game moves are invalid because of bookshelf placement.
+ * @throws WrongGameBoardPicksException If the game moves are invalid because of board picking.
+ * @throws WrongMovesNumberException If the game moves are in an illegal number.
+ * @throws NullGameHandlerInstance If the game handler is null.
+ */
+ public synchronized void activePlayerMove(Map<Coordinates, Coordinates> selectedCoordinates)
+ throws BoardGridColIndexOutOfBoundsException, BoardGridRowIndexOutOfBoundsException,
+ NullIndexValueException, BookshelfGridColIndexOutOfBoundsException,
+ BookshelfGridRowIndexOutOfBoundsException, NullTileException, NullPlayerBookshelfException,
+ NullScoreBlockListException, NullPointerException, NullMatchedBlockCountException,
+ NegativeMatchedBlockCountException,
+ WrongMovesNumberException, WrongGameBoardPicksException, WrongBookShelfPicksException, NullGameHandlerInstance {
+
+ MovesValidator.validateGameMoves(selectedCoordinates, activePlayer.getBookshelf(), gameBoard);
+
+ for (Map.Entry<Coordinates, Coordinates> entry : selectedCoordinates.entrySet()) {
+ Coordinates boardCoord = entry.getKey();
+ Coordinates bsCoord = entry.getValue();
+ Tile takenTile = takeTileAction(boardCoord);
+ putTileAction(takenTile, bsCoord);
+ }
+ nextTurn();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/index.html
new file mode 100644
index 00000000..6930f4a6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.game
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/index.source.html
new file mode 100644
index 00000000..9d1b0037
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.game/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.game
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/Board.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/Board.html
new file mode 100644
index 00000000..39b81375
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/Board.html
@@ -0,0 +1 @@
+Board
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/Board.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/Board.java.html
new file mode 100644
index 00000000..cdfa3891
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/Board.java.html
@@ -0,0 +1,372 @@
+Board.java
package it.polimi.is23am10.server.model.items.board;
+
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.board.exceptions.InvalidNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.board.exceptions.NullNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.tile.Tile;
+import it.polimi.is23am10.server.model.items.tile.Tile.TileType;
+import it.polimi.is23am10.utils.IndexValidator;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Game's board class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class Board implements Serializable {
+
+ /**
+ * The allowed player values.
+ *
+ */
+ public static final List<Integer> allowedNumOfPlayers = Arrays.asList(2, 3, 4);
+
+ /**
+ * The game board max row value.
+ *
+ */
+ public static final Integer BOARD_GRID_ROWS = 9;
+
+ /**
+ * The game board max col value.
+ *
+ */
+ public static final Integer BOARD_GRID_COLS = 9;
+
+ /**
+ * The number of tiles for each {@link TileType}.
+ *
+ */
+ public static final Integer TILE_TYPE_NUM = 22;
+
+ /**
+ * The number of players of the current grid.
+ *
+ */
+ private Integer numOfPlayers;
+
+ /**
+ * A fixed 2d array referencing the physical grid instance.
+ *
+ */
+ private Tile[][] boardGrid;
+
+ /**
+ * A fixed 2d array referencing a weight for each boardGrid cell.
+ * Each value represents the minimum number of players needed to get that square
+ * filled.
+ *
+ * <ul>
+ * <li>Values >4 are meant for squares out of the board
+ * <li>Values <3 instead will always be filled
+ * </ul>
+ */
+ private static final Integer[][] blackMap = {
+ {
+ 9, 9, 9, 3, 4, 9, 9, 9, 9
+ },
+ {
+ 9, 9, 9, 2, 2, 4, 9, 9, 9
+ },
+ {
+ 9, 9, 3, 2, 2, 2, 3, 9, 9
+ },
+ {
+ 9, 4, 2, 2, 2, 2, 2, 2, 3
+ },
+ {
+ 4, 2, 2, 2, 2, 2, 2, 2, 4
+ },
+ {
+ 3, 2, 2, 2, 2, 2, 2, 4, 9
+ },
+ {
+ 9, 9, 3, 2, 2, 2, 3, 9, 9
+ },
+ {
+ 9, 9, 9, 4, 2, 2, 9, 9, 9
+ },
+ {
+ 9, 9, 9, 9, 4, 3, 9, 9, 9
+ }
+ };
+
+ /**
+ * A list containing the available tiles.
+ *
+ */
+ private List<Tile> tileSack;
+
+ /**
+ * Constructor.
+ *
+ * @param numOfPlayers The current game instance number of players.
+ * @throws InvalidNumOfPlayersException If, while adding multiple players, there is an invalid number of them.
+ * @throws NullNumOfPlayersException If the number of players provided when filling the board is null.
+ */
+ public Board(Integer numOfPlayers)
+ throws InvalidNumOfPlayersException, NullNumOfPlayersException {
+ if (!validNumOfPlayers(numOfPlayers)) {
+ throw new InvalidNumOfPlayersException(numOfPlayers);
+ }
+ /**
+ * Save a reference about the current number of players.
+ *
+ */
+ this.numOfPlayers = numOfPlayers;
+ this.boardGrid = new Tile[BOARD_GRID_ROWS][BOARD_GRID_COLS];
+
+ createInitialTileSack();
+
+ fillBoardGrid();
+ }
+
+ /**
+ * Copy constructor for Board.
+ *
+ * @param toCopy board to copy
+ */
+ public Board(Board toCopy) {
+ numOfPlayers = toCopy.numOfPlayers;
+ boardGrid = Arrays.stream(toCopy.boardGrid)
+ .map(Tile[]::clone)
+ .toArray(Tile[][]::new);
+ tileSack = toCopy.tileSack.stream().map(Tile::new).collect(Collectors.toList());
+ }
+
+ /**
+ * Validate the number of players.
+ *
+ * @throws NullNumOfPlayersException If the number of players provided when filling the board is null..
+ */
+ private boolean validNumOfPlayers(Integer numOfPlayers) throws NullNumOfPlayersException {
+ if (numOfPlayers == null) {
+ throw new NullNumOfPlayersException();
+ }
+ return allowedNumOfPlayers.contains(numOfPlayers);
+ }
+
+ /**
+ * Extract a tile from the sack.
+ * This mutate the sack list.
+ *
+ * @return The extracted tile.
+ *
+ */
+ public Tile getTileFromSack() throws IndexOutOfBoundsException {
+ Tile tile = tileSack.get(tileSack.size() - 1);
+ tileSack.remove(tileSack.size() - 1);
+ return tile;
+ }
+
+ /**
+ * Create the board tile sack.
+ * The complete sack has 22 tiles for each {@link TileType}.
+ *
+ */
+ private void createInitialTileSack() {
+ tileSack = Stream.of(TileType.values())
+ .filter(t -> !t.equals(TileType.EMPTY))
+ .map(t -> Stream.generate(() -> new Tile(t)).limit(TILE_TYPE_NUM))
+ .flatMap(stream -> stream)
+ .collect(Collectors.toList());
+
+ Collections.shuffle(tileSack);
+ }
+
+ /**
+ * Fill the board grid based on the current player number.
+ * Note that this method works both when first filling the
+ * board AND when re-filling it partially mid-game.
+ *
+ */
+ private void fillBoardGrid() throws IndexOutOfBoundsException {
+ for (int i = 0; i < Board.BOARD_GRID_ROWS; i++) {
+ for (int j = 0; j < Board.BOARD_GRID_COLS; j++) {
+ if (blackMap[i][j] <= numOfPlayers) {
+ if (boardGrid[i][j] == null || (boardGrid[i][j].getType().equals(TileType.EMPTY))) {
+ boardGrid[i][j] = getTileFromSack();
+ }
+ } else {
+ boardGrid[i][j] = new Tile(TileType.EMPTY);
+ }
+ }
+ }
+ }
+
+ /**
+ * boardGrid getter.
+ *
+ * @return The board grid.
+ *
+ */
+ public Tile[][] getBoardGrid() {
+ return boardGrid;
+ }
+
+ /**
+ * Retrieve the number of tiles remained inside the sack.
+ *
+ * @return The remained tiles inside the sack.
+ *
+ */
+ public Integer getTileSackSize() {
+ return tileSack.size();
+ }
+
+ /**
+ * View the tile in a specific board position.
+ *
+ * @param row The row index.
+ * @param col The column index.
+ * @return The requested tile.
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds.
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ *
+ */
+ public Tile getTileAt(Integer row, Integer col)
+ throws BoardGridRowIndexOutOfBoundsException, BoardGridColIndexOutOfBoundsException, NullIndexValueException {
+ if (!IndexValidator.validColIndex(col, Board.BOARD_GRID_COLS)) {
+ throw new BoardGridColIndexOutOfBoundsException(col);
+ }
+ if (!IndexValidator.validRowIndex(row, Board.BOARD_GRID_ROWS)) {
+ throw new BoardGridRowIndexOutOfBoundsException(row);
+ }
+ return boardGrid[row][col];
+ }
+
+ /**
+ * View the tile in a specific black map position.
+ *
+ * @param row The row index.
+ * @param col The column index.
+ * @return The black map value.
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds.
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ *
+ */
+ public Integer getBlackMapAt(Integer row, Integer col)
+ throws BoardGridRowIndexOutOfBoundsException, BoardGridColIndexOutOfBoundsException, NullIndexValueException {
+ if (!IndexValidator.validColIndex(col, Board.BOARD_GRID_COLS)) {
+ throw new BoardGridColIndexOutOfBoundsException(col);
+ }
+ if (!IndexValidator.validRowIndex(row, Board.BOARD_GRID_ROWS)) {
+ throw new BoardGridRowIndexOutOfBoundsException(row);
+ }
+ return blackMap[row][col];
+ }
+
+ /**
+ * Support method that removes a tile
+ *
+ * @param row row index of removed tile
+ * @param col col index of removed tile
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds.
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ */
+ public void removeTileAt(Integer row, Integer col)
+ throws BoardGridColIndexOutOfBoundsException, BoardGridRowIndexOutOfBoundsException, NullIndexValueException {
+ if (!IndexValidator.validColIndex(col, Board.BOARD_GRID_COLS)) {
+ throw new BoardGridColIndexOutOfBoundsException(col);
+ }
+ if (!IndexValidator.validRowIndex(row, Board.BOARD_GRID_ROWS)) {
+ throw new BoardGridRowIndexOutOfBoundsException(row);
+ }
+ boardGrid[row][col] = new Tile(TileType.EMPTY);
+ }
+
+ /**
+ * Retrieve the tile in a specific board position.
+ * This method removes the returned tile from the sack.
+ *
+ * @param row The row index.
+ * @param col The column index.
+ * @return The requested tile.
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds.
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ *
+ */
+ public Tile takeTileAt(Integer row, Integer col)
+ throws BoardGridRowIndexOutOfBoundsException, BoardGridColIndexOutOfBoundsException, NullIndexValueException {
+ if (!IndexValidator.validColIndex(col, Board.BOARD_GRID_COLS)) {
+ throw new BoardGridColIndexOutOfBoundsException(col);
+ }
+ if (!IndexValidator.validRowIndex(row, Board.BOARD_GRID_ROWS)) {
+ throw new BoardGridRowIndexOutOfBoundsException(row);
+ }
+ Tile tile = boardGrid[row][col];
+ boardGrid[row][col] = new Tile(TileType.EMPTY);
+ return tile;
+ }
+
+ /**
+ * Helper method used to determine if the board should be refilled or not.
+ *
+ * @return Should board be refilled.
+ */
+ public boolean isRefillNeeded() {
+ for (int i = 0; i < Board.BOARD_GRID_ROWS; i++) {
+ for (int j = 0; j < Board.BOARD_GRID_COLS; j++) {
+ if (boardGrid[i][j].getType() != TileType.EMPTY
+ && ((i > 0 && boardGrid[i - 1][j].getType() != TileType.EMPTY) ||
+ (i < Board.BOARD_GRID_ROWS - 1 && boardGrid[i + 1][j].getType() != TileType.EMPTY) ||
+ (j > 0 && boardGrid[i][j - 1].getType() != TileType.EMPTY) ||
+ (j < Board.BOARD_GRID_COLS - 1 && boardGrid[i][j + 1].getType() != TileType.EMPTY))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Method called at the end of the turn that
+ * checks if the Board needs to be refilled
+ * and proceeds if so.
+ */
+ public void refillIfNeeded() throws IndexOutOfBoundsException {
+ if (isRefillNeeded()) {
+ fillBoardGrid();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Board)) {
+ return false;
+ }
+ Board brd = (Board) obj;
+ if (numOfPlayers != brd.numOfPlayers) {
+ return false;
+ }
+ return (Arrays.deepEquals(boardGrid, brd.boardGrid) && tileSack.equals(brd.tileSack));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return numOfPlayers.hashCode() * boardGrid.hashCode() * tileSack.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/index.html
new file mode 100644
index 00000000..d9119fe6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.board
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/index.source.html
new file mode 100644
index 00000000..c874a1d2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.board/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.board
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/Bookshelf.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/Bookshelf.html
new file mode 100644
index 00000000..f273b1d9
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/Bookshelf.html
@@ -0,0 +1 @@
+Bookshelf
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/Bookshelf.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/Bookshelf.java.html
new file mode 100644
index 00000000..9c3749a9
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/Bookshelf.java.html
@@ -0,0 +1,267 @@
+Bookshelf.java
package it.polimi.is23am10.server.model.items.bookshelf;
+
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.NullTileException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.WrongCharBookshelfStringException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.WrongLengthBookshelfStringException;
+import it.polimi.is23am10.server.model.items.tile.Tile;
+import it.polimi.is23am10.server.model.items.tile.Tile.TileType;
+import it.polimi.is23am10.utils.IndexValidator;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Players' bookshelf class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class Bookshelf implements Serializable {
+ /**
+ * The bookshelf max rows value.
+ */
+ public static final int BOOKSHELF_ROWS = 6;
+
+ /**
+ * The bookshelf max columns value.
+ */
+ public static final int BOOKSHELF_COLS = 5;
+
+ /**
+ * The support map to reference each {@link TileType} with a char.
+ * Used for the constructor of Bookshelf made for tests.
+ *
+ */
+ transient Map<String, TileType> tileMap = Map.of(
+ "C", TileType.CAT,
+ "B", TileType.BOOK,
+ "G", TileType.GAME,
+ "F", TileType.FRAME,
+ "T", TileType.TROPHY,
+ "P", TileType.PLANT,
+ "X", TileType.EMPTY);
+
+ /**
+ * Max bookshelf grid size.
+ */
+ private static final int BOOKSHELF_SIZE = BOOKSHELF_COLS * BOOKSHELF_ROWS;
+
+ /**
+ * A fixed 2d array referencing the physical bookshelf instance.
+ *
+ */
+ private Tile[][] bookshelfGrid;
+
+ /**
+ * Constructor for the Bookshelf instance.
+ */
+ public Bookshelf() {
+ bookshelfGrid = new Tile[BOOKSHELF_ROWS][BOOKSHELF_COLS];
+ for (int i = 0; i < BOOKSHELF_ROWS; i++) {
+ for (int j = 0; j < BOOKSHELF_COLS; j++) {
+ bookshelfGrid[i][j] = new Tile(TileType.EMPTY);
+ }
+ }
+ }
+
+ /**
+ * Method used to get a string representation for a bookshelf.
+ *
+ * @return string representation of this bookshelf.
+ */
+ public String getBookshelfString(){
+ StringBuilder toReturn = new StringBuilder();
+ for (int i = 0; i < BOOKSHELF_ROWS; i++) {
+ for (int j = 0; j < BOOKSHELF_COLS; j++) {
+ // Apparently needed because "Local variable i defined in an enclosing scope must be final or effectively final Java(536871575)"
+ final Integer ii = i;
+ final Integer jj = j;
+ toReturn.append(tileMap.keySet().stream().filter(s -> tileMap.get(s) == (bookshelfGrid[ii][jj]).getType()).findFirst().get());
+ }
+ }
+ return toReturn.toString();
+ }
+
+ /**
+ * Copy constructor of Bookshelf.
+ *
+ * @param toCopy original instance
+ */
+ public Bookshelf(Bookshelf toCopy) {
+ bookshelfGrid = Arrays.stream(toCopy.bookshelfGrid)
+ .map(Tile[]::clone)
+ .toArray(Tile[][]::new);
+ }
+
+ /**
+ * This constructor takes a 30 char long string containing the content
+ * of a bookshelf, with each tile associated to a letter, as shown below
+ * and builds and returns the matching bookshelf object.
+ *
+ * @param bookshelfString A string that allows us to fill the bookshelfGrid with
+ * the
+ * correspondence between each char and the position in
+ * the
+ * grid, there's a map to help us matching the char with
+ * the {@link TileType}
+ * @throws WrongLengthBookshelfStringException If when building a bookshelf based on string, it is of an invalid length.
+ * @throws WrongCharBookshelfStringException If when building a bookshelf based on string, it contains an invalid character.
+ * @throws NullPointerException Generic NPE.
+ */
+ public Bookshelf(String bookshelfString)
+ throws WrongLengthBookshelfStringException, WrongCharBookshelfStringException,
+ NullPointerException {
+
+ if (bookshelfString.length() != BOOKSHELF_SIZE) {
+ throw new WrongLengthBookshelfStringException(
+ "[Class Bookshelf, method constructor]: Bookshelf string has incorrect length exception");
+ }
+ String[] tileChars = bookshelfString.split("");
+ for (String c : tileChars) {
+ if (!tileMap.containsKey(c)) {
+ throw new WrongCharBookshelfStringException(
+ "[Class Bookshelf, method constructor]: Bookshelf string contains invalid char exception");
+ }
+ }
+ bookshelfGrid = new Tile[BOOKSHELF_ROWS][BOOKSHELF_COLS];
+
+ /*
+ * Here we are filling the bookshelf inserting a Tile of the corresponding
+ * TileType, using the mapping we implemented before.
+ * To access the right char in each cycle we are using an index which maps the
+ * bi-dimensional array indexes into one single index.
+ */
+ for (int i = 0; i < BOOKSHELF_ROWS; i++) {
+ for (int j = 0; j < BOOKSHELF_COLS; j++) {
+ bookshelfGrid[i][j] = new Tile(tileMap.get(tileChars[BOOKSHELF_COLS * i + j]));
+ }
+ }
+ }
+
+ /**
+ * Set a {@link Tile} inside the bookshelf grid.
+ *
+ * @param row The bookshelf grid row's value.
+ * @param col The bookshelf grid col's value.
+ * @param tile The tile to be set.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws NullTileException If tile is null.
+ *
+ */
+ public void setBookshelfGridIndex(Integer row, Integer col, Tile tile)
+ throws BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException,
+ NullIndexValueException, NullTileException {
+ if (!IndexValidator.validRowIndex(row, Bookshelf.BOOKSHELF_ROWS)) {
+ throw new BookshelfGridRowIndexOutOfBoundsException(row);
+ }
+ if (!IndexValidator.validColIndex(col, Bookshelf.BOOKSHELF_COLS)) {
+ throw new BookshelfGridColIndexOutOfBoundsException(col);
+ }
+ if (tile == null) {
+ throw new NullTileException("[Class Bookshelf, method SetBookshelfGridIndex]");
+ }
+ bookshelfGrid[row][col] = tile;
+ }
+
+ /**
+ * bookshelfGrid getter.
+ *
+ * @return The bookshelf's 6x5 playground grid.
+ *
+ */
+ public Tile[][] getBookshelfGrid() {
+ return bookshelfGrid;
+ }
+
+ /**
+ * bookshelfGrid index getter.
+ *
+ * @param row The bookshelf grid's row value.
+ * @param col The bookshelf grid's col value.
+ * @return The tile at the given indexes.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ *
+ */
+ public Tile getBookshelfGridAt(Integer row, Integer col)
+ throws BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException,
+ NullIndexValueException {
+ if (!IndexValidator.validRowIndex(row, Bookshelf.BOOKSHELF_ROWS)) {
+ throw new BookshelfGridRowIndexOutOfBoundsException(row);
+ }
+ if (!IndexValidator.validColIndex(col, Bookshelf.BOOKSHELF_COLS)) {
+ throw new BookshelfGridColIndexOutOfBoundsException(col);
+ }
+ return bookshelfGrid[row][col];
+ }
+
+ /**
+ * This function checks if player's bookshelf is full of tiles.
+ *
+ * @return True if bookshelf grid is full.
+ */
+ public boolean isBookshelfFull() {
+ for (int i = 0; i < BOOKSHELF_ROWS; i++) {
+ for (int j = 0; j < BOOKSHELF_COLS; j++) {
+ if (bookshelfGrid[i][j].isEmpty()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Retrieve the number of {@link TileType#EMPTY} inside the bookshelf for a
+ * given column.
+ *
+ * @param column The column to be checked
+ * @return The number of {@link TileType#EMPTY}
+ * @throws BookshelfGridColIndexOutOfBoundsException If bookshelf column index is out of bounds.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If bookshelf row index is out of bounds.
+ * @throws NullIndexValueException If index value is null.
+ */
+ public int getFreeRowsInCol(int column)
+ throws BookshelfGridColIndexOutOfBoundsException,
+ BookshelfGridRowIndexOutOfBoundsException, NullIndexValueException {
+ int res = 0;
+ for (int i = 0; i < BOOKSHELF_ROWS; i++) {
+ if (getBookshelfGridAt(i, column).isEmpty()) {
+ res++;
+ }
+ }
+ return res;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Bookshelf)) {
+ return false;
+ }
+ Bookshelf bs = (Bookshelf) obj;
+ return (Arrays.deepEquals(bookshelfGrid, bs.bookshelfGrid));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return bookshelfGrid.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/index.html
new file mode 100644
index 00000000..3cc52887
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.bookshelf
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/index.source.html
new file mode 100644
index 00000000..e1e8b55c
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.bookshelf/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.bookshelf
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/AbstractCard.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/AbstractCard.html
new file mode 100644
index 00000000..daf53b2c
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/AbstractCard.html
@@ -0,0 +1 @@
+AbstractCard
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/AbstractCard.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/AbstractCard.java.html
new file mode 100644
index 00000000..18ca1f1d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/AbstractCard.java.html
@@ -0,0 +1,51 @@
+AbstractCard.java
package it.polimi.is23am10.server.model.items.card;
+
+import java.io.Serializable;
+
+import it.polimi.is23am10.server.model.items.card.exceptions.AlreadyInitiatedPatternException;
+import it.polimi.is23am10.server.model.pattern.AbstractPattern;
+
+/**
+ * Abstract card object.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ *
+ */
+public abstract class AbstractCard<R, T extends AbstractPattern<R>> implements Serializable {
+
+ /**
+ * The pattern instance.
+ * Its type must extends {@link AbstractPattern}
+ */
+ private transient T pattern;
+
+ /**
+ * Pattern setter.
+ * Pattern can not be final as it must be randomly chosen and it has to be
+ * unique across all instantiated cards.
+ * Only the derived class can set the pattern.
+ *
+ * @param pattern The pattern to assign.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one. If the pattern is already initiated.
+ */
+ protected void setPattern(T pattern) throws AlreadyInitiatedPatternException {
+ if (this.pattern != null) {
+ throw new AlreadyInitiatedPatternException(
+ "[Class " + this.getClass() + ", method setPattern]: The pattern has already been instantiated");
+ }
+ this.pattern = pattern;
+ }
+
+ /**
+ * Pattern getter.
+ *
+ * @return The instance pattern.
+ */
+ public T getPattern() {
+ return pattern;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/PrivateCard.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/PrivateCard.html
new file mode 100644
index 00000000..ead123b0
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/PrivateCard.html
@@ -0,0 +1 @@
+PrivateCard
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/PrivateCard.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/PrivateCard.java.html
new file mode 100644
index 00000000..fd4e9faa
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/PrivateCard.java.html
@@ -0,0 +1,85 @@
+PrivateCard.java
package it.polimi.is23am10.server.model.items.card;
+
+import java.util.List;
+import java.util.function.Function;
+
+import it.polimi.is23am10.server.model.factory.PrivatePatternFactory;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.card.exceptions.AlreadyInitiatedPatternException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NegativeMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullMatchedBlockCountException;
+import it.polimi.is23am10.server.model.pattern.PrivatePattern;
+
+/**
+ * Private card class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class PrivateCard
+ extends
+ AbstractCard<Function<Bookshelf, Integer>, PrivatePattern<Function<Bookshelf, Integer>>> {
+
+ /**
+ * A counter for the number of matched blocks in the {@link PrivatePattern}.
+ *
+ */
+ private Integer matchedBlocksCount;
+
+ /**
+ * Constructor.
+ *
+ * @param usedPrivatePatterns is a list of PrivatePattern used to store the
+ * already
+ * used one.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one.
+ */
+ public PrivateCard(List<PrivatePattern<Function<Bookshelf, Integer>>> usedPrivatePatterns)
+ throws AlreadyInitiatedPatternException {
+ matchedBlocksCount = 0;
+ setPattern(PrivatePatternFactory.getNotUsedPattern(usedPrivatePatterns));
+ }
+
+ /**
+ * Empty constructor. To be used when obscuring private card
+ * of other players when sending game updates
+ */
+ public PrivateCard() {
+ }
+
+
+ /**
+ * matchedBlocksCount setter.
+ *
+ * @param matchedBlocksCount The value about how many matches the played has
+ * achieved inside his/her private card.
+ * @throws NullMatchedBlockCountException If the number of matched blocks to set is null.
+ * @throws NegativeMatchedBlockCountException If the number of matched blocks to set is negative.
+ *
+ */
+ public void setMatchedBlocksCount(Integer matchedBlocksCount)
+ throws NullMatchedBlockCountException, NegativeMatchedBlockCountException {
+ if (matchedBlocksCount == null) {
+ throw new NullMatchedBlockCountException(
+ "[Class " + this.getClass() + ", method setMatchedBlocksCount]: Null matchedBlocksCount");
+ }
+ if (matchedBlocksCount < 0) {
+ throw new NegativeMatchedBlockCountException(
+ "[Class " + this.getClass() + ", method setMatchedBlocksCount]: Negative matchedBlocksCount");
+ }
+ this.matchedBlocksCount = matchedBlocksCount;
+ }
+
+ /**
+ * matchedBlocksCount getter.
+ *
+ * @return The value of matched blocks inside the user private card.
+ *
+ */
+ public Integer getMatchedBlocksCount() {
+ return matchedBlocksCount;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/SharedCard.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/SharedCard.html
new file mode 100644
index 00000000..888303f6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/SharedCard.html
@@ -0,0 +1 @@
+SharedCard
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/SharedCard.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/SharedCard.java.html
new file mode 100644
index 00000000..44ee7071
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/SharedCard.java.html
@@ -0,0 +1,116 @@
+SharedCard.java
package it.polimi.is23am10.server.model.items.card;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import it.polimi.is23am10.server.model.factory.SharedPatternFactory;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.card.exceptions.AlreadyInitiatedPatternException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullScoreBlockListException;
+import it.polimi.is23am10.server.model.items.scoreblock.ScoreBlock;
+import it.polimi.is23am10.server.model.items.scoreblock.exceptions.NotValidScoreBlockValueException;
+import it.polimi.is23am10.server.model.pattern.SharedPattern;
+import it.polimi.is23am10.server.model.player.Player;
+
+/**
+ * Shared card object.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class SharedCard extends AbstractCard<Predicate<Bookshelf>, SharedPattern<Predicate<Bookshelf>>> {
+
+ /**
+ * A list of {@link ScoreBlock} instances.
+ * They are used to track the score points that the current shared card instance
+ * can assign to the players.
+ *
+ */
+ private List<ScoreBlock> scoreBlocks;
+
+ /**
+ * List of players who satisfied the pattern and won the SB.
+ * Used to prevent one player from taking two SBs.
+ */
+ private List<Player> cardWinners;
+
+ /**
+ * A map to get the correct list of scoreblocks
+ * by number of players playing. Order is reversed as first
+ * to pick gets the highest valued SB.
+ */
+ private Map<Integer,List<Integer>> scoreBlocksMap = Map.of(
+ 2, List.of(8,4),
+ 3, List.of(8,6,4),
+ 4, List.of(8,6,4,2)
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param usedSharedPatterns is a list of SharedPattern used to store the already
+ * used.
+ * @param numPlayers the number of players in the game.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one.
+ */
+ public SharedCard(List<SharedPattern<Predicate<Bookshelf>>> usedSharedPatterns, Integer numPlayers)
+ throws AlreadyInitiatedPatternException, NotValidScoreBlockValueException {
+ cardWinners = new ArrayList<>();
+ scoreBlocks = new ArrayList<>();
+ for (Integer scoreBlockValue : scoreBlocksMap.get(numPlayers)) {
+ scoreBlocks.add(new ScoreBlock(scoreBlockValue));
+ }
+ setPattern(SharedPatternFactory.getNotUsedPattern(usedSharedPatterns));
+ }
+
+ /**
+ * ScoreBlocks setter.
+ *
+ * @param scoreBlockList The score block list.
+ * @throws NullScoreBlockListException If the list of scoreblocks is null.
+ *
+ */
+ public void setScoreBlocks(List<ScoreBlock> scoreBlockList) throws NullScoreBlockListException {
+ if (scoreBlockList == null) {
+ throw new NullScoreBlockListException(
+ "[Class " + this.getClass() + ", method setScoreBlock]: Null score block list");
+ }
+ scoreBlocks = scoreBlockList;
+ }
+
+ /**
+ * scoreBlocks getter.
+ *
+ * @return The current score block points assigned to this card instance.
+ *
+ */
+ public List<ScoreBlock> getScoreBlocks() {
+ return scoreBlocks;
+ }
+
+ /**
+ * cardWinners getter.
+ *
+ * @return The list of player who got a SB from this card.
+ *
+ */
+ public List<Player> getCardWinners() {
+ return cardWinners;
+ }
+
+ /**
+ * Method used to add a player to the winner array.
+ * When a player is added it's not allowed to take
+ * another scoreblock from this card.
+ *
+ * @param player the player to add to array
+ */
+ public void addCardWinner(Player player) {
+ cardWinners.add(player);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/index.html
new file mode 100644
index 00000000..4ec2707a
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.card
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/index.source.html
new file mode 100644
index 00000000..c0ff5c0d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.card/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.card
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/ScoreBlock.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/ScoreBlock.html
new file mode 100644
index 00000000..3b5b01ea
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/ScoreBlock.html
@@ -0,0 +1 @@
+ScoreBlock
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/ScoreBlock.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/ScoreBlock.java.html
new file mode 100644
index 00000000..610a80a1
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/ScoreBlock.java.html
@@ -0,0 +1,73 @@
+ScoreBlock.java
package it.polimi.is23am10.server.model.items.scoreblock;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+
+import it.polimi.is23am10.server.model.items.scoreblock.exceptions.NotValidScoreBlockValueException;
+
+/**
+ * ScoreBlock class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class ScoreBlock implements Serializable {
+
+ protected static final List<Integer> allowedScoreValues = Arrays.asList(2, 4, 6, 8);
+
+ /**
+ * The value assigned to the current instance of the score block.
+ *
+ */
+ private Integer score;
+
+ /**
+ * Constructor.
+ *
+ * @throws NotValidScoreBlockValueException If the value assigned to a scoreblock is not valid.
+ *
+ */
+ public ScoreBlock(Integer score) throws NotValidScoreBlockValueException {
+ if (!allowedScoreValues.contains(score)) {
+ throw new NotValidScoreBlockValueException(
+ "[Class " + this.getClass() + ", constructor]: Assigned score value is not compliant with the rules");
+ }
+ this.score = score;
+ }
+
+ /**
+ * score getter.
+ *
+ * @return The score value associated with this instance.
+ *
+ */
+ public Integer getScore() {
+ return score;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ScoreBlock)) {
+ return false;
+ }
+ ScoreBlock sb = (ScoreBlock) obj;
+ return (score == sb.score);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return score.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/index.html
new file mode 100644
index 00000000..6493edf2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.scoreblock
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/index.source.html
new file mode 100644
index 00000000..118e5d6f
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.scoreblock/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.scoreblock
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile$TileType.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile$TileType.html
new file mode 100644
index 00000000..fedd9e98
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile$TileType.html
@@ -0,0 +1 @@
+Tile.TileType
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile.html
new file mode 100644
index 00000000..d4413099
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile.html
@@ -0,0 +1 @@
+Tile
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile.java.html
new file mode 100644
index 00000000..bf304623
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/Tile.java.html
@@ -0,0 +1,112 @@
+Tile.java
package it.polimi.is23am10.server.model.items.tile;
+
+import java.io.Serializable;
+
+/**
+ * The tile class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class Tile implements Serializable {
+
+ /**
+ * An enumeration about the available tile types.
+ *
+ */
+ public enum TileType {
+ CAT,
+ BOOK,
+ GAME,
+ FRAME,
+ TROPHY,
+ PLANT,
+ EMPTY
+ }
+
+ /**
+ * The instance Tile type.
+ *
+ */
+ private TileType type;
+
+
+ /**
+ * Constructor.
+ * If the desired type has null value, an empty tile will be set.
+ *
+ */
+ public Tile(TileType type) {
+ this.type = type == null ? TileType.EMPTY : type;
+ }
+
+ /**
+ * Copy constructor for Tile.
+ *
+ * @param toCopy tile to copy
+ */
+ public Tile(Tile toCopy) {
+ type = toCopy.getType();
+ }
+
+
+ /**
+ * type getter.
+ *
+ * @return The type of the current Tile instance.
+ *
+ */
+ public TileType getType() {
+ return type;
+ }
+
+ /**
+ * The method that allows us to manually change the type of a specific Tile.
+ *
+ * @throws NullPointerException Generic NPE.
+ *
+ * @param tt The tile type we want to set for the tile.
+ */
+ public void setTile(TileType tt) throws NullPointerException {
+ if (tt == null) {
+ throw new NullPointerException("[Class Tile, method setTile]: Null pointer exception");
+ }
+ type = tt;
+ }
+
+
+ /**
+ * {@inheritDoc}}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Tile)) {
+ return false;
+ }
+ Tile tile = (Tile) obj;
+ return tile.getType() == type;
+ }
+
+ /**
+ * {@inheritDoc}}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return type.toString().hashCode();
+ }
+
+ /**
+ * Method that checks if the Tile's {@link TileType} is EMPTY or not.
+ *
+ * @return True if the tile has {@link TileType#EMPTY}.
+ *
+ */
+ public boolean isEmpty() {
+ return (this.getType() == TileType.EMPTY);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/index.html
new file mode 100644
index 00000000..476e9deb
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.tile
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/index.source.html
new file mode 100644
index 00000000..3445eaaf
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.items.tile/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.items.tile
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/AbstractPattern.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/AbstractPattern.html
new file mode 100644
index 00000000..8189d8f8
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/AbstractPattern.html
@@ -0,0 +1 @@
+AbstractPattern
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/AbstractPattern.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/AbstractPattern.java.html
new file mode 100644
index 00000000..c32c4884
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/AbstractPattern.java.html
@@ -0,0 +1,55 @@
+AbstractPattern.java
package it.polimi.is23am10.server.model.pattern;
+
+/**
+ * Abstract pattern object.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public abstract class AbstractPattern<T> {
+
+ /**
+ * The assigned rule to this pattern.
+ *
+ */
+ protected T rule;
+
+ /**
+ * A 1-12 number to identify the card sprite in view
+ */
+ private Integer cardIndex;
+
+ /**
+ * Constructor.
+ *
+ * @param rule The rule assigned to the current pattern..
+ * @param cardIndex The card index associated
+ *
+ */
+ protected AbstractPattern(T rule, Integer cardIndex) {
+ this.rule = rule;
+ this.cardIndex = cardIndex;
+ }
+
+ /**
+ * Rule getter.
+ *
+ * @return The rule function.
+ */
+ public T getRule() {
+ return rule;
+ }
+
+ /**
+ * CardIndex getter
+ *
+ * @return The index
+ */
+ public Integer getIndex() {
+ return cardIndex;
+ }
+
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/PrivatePattern.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/PrivatePattern.html
new file mode 100644
index 00000000..f8715dc8
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/PrivatePattern.html
@@ -0,0 +1 @@
+PrivatePattern
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/PrivatePattern.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/PrivatePattern.java.html
new file mode 100644
index 00000000..d6e03a23
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/PrivatePattern.java.html
@@ -0,0 +1,45 @@
+PrivatePattern.java
package it.polimi.is23am10.server.model.pattern;
+
+/**
+ * Private pattern object.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class PrivatePattern<T> extends AbstractPattern<T> {
+
+ /**
+ * The constructor of the class PrivatePattern.
+ *
+ * @param rule a function that takes a Bookshelf object and returns an Integer.
+ * @param cardIndex The card index associated
+ */
+ public PrivatePattern(T rule, Integer cardIndex) {
+ super(rule, cardIndex);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PrivatePattern)) {
+ return false;
+ }
+ return this.rule == ((PrivatePattern<T>) obj).getRule();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return rule.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/SharedPattern.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/SharedPattern.html
new file mode 100644
index 00000000..229f1949
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/SharedPattern.html
@@ -0,0 +1 @@
+SharedPattern
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/SharedPattern.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/SharedPattern.java.html
new file mode 100644
index 00000000..dff2bc64
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/SharedPattern.java.html
@@ -0,0 +1,43 @@
+SharedPattern.java
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/index.html
new file mode 100644
index 00000000..a4011688
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.pattern
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/index.source.html
new file mode 100644
index 00000000..c07dcbb7
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.pattern/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.pattern
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/Player.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/Player.html
new file mode 100644
index 00000000..480424c3
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/Player.html
@@ -0,0 +1 @@
+Player
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/Player.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/Player.java.html
new file mode 100644
index 00000000..dde2803d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/Player.java.html
@@ -0,0 +1,335 @@
+Player.java
package it.polimi.is23am10.server.model.player;
+
+import it.polimi.is23am10.server.model.factory.PlayerFactory;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.card.PrivateCard;
+import it.polimi.is23am10.server.model.items.card.exceptions.NegativeMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullScoreBlockListException;
+import it.polimi.is23am10.server.model.items.scoreblock.ScoreBlock;
+import it.polimi.is23am10.server.model.pattern.PrivatePattern;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerBookshelfException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerIdException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerNameException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerPrivateCardException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreBlocksException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreException;
+import it.polimi.is23am10.server.model.score.Score;
+import it.polimi.is23am10.server.model.game.Game;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * The Player class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class Player implements Serializable {
+
+ /**
+ * Copy constructor for Player class.
+ *
+ * @param player The player object to be copied.
+ */
+ public Player(Player player) {
+ this.playerId = player.playerId;
+ this.playerName = player.playerName;
+ }
+
+ /**
+ * Constructor for Player class.
+ *
+ */
+ public Player() {
+ }
+
+ /**
+ * A randomly generated {@link UUID} id.
+ *
+ */
+ private UUID playerId;
+
+ /**
+ * The player name.
+ * Chosen by the player itself.
+ *
+ */
+ private String playerName;
+
+ /**
+ * Status of the player.
+ * Tells us if he's connected or not.
+ *
+ */
+ private boolean isConnected;
+
+ /**
+ * Status of the player.
+ * Tells us if he's connected or not.
+ *
+ */
+ private boolean isActivePlayer;
+
+ /**
+ * The score storage for the player.
+ * It groups all the possible score values that a player can earn during a game.
+ *
+ */
+ private Score score;
+
+ /**
+ * The player's bookshelf playground.
+ *
+ */
+ private Bookshelf bookshelf;
+
+ /**
+ * The player's {@link PrivateCard} with a specific {@link PrivatePattern}.
+ *
+ */
+ private PrivateCard privateCard;
+
+ /**
+ * A list of all {@link ScoreBlock} earned by the player.
+ *
+ */
+ private List<ScoreBlock> scoreBlocks;
+
+ /**
+ * playerId setter.
+ *
+ * @param playerId The player id.
+ *
+ */
+ public synchronized void setPlayerID(UUID playerId) throws NullPlayerIdException {
+ if (playerId == null) {
+ throw new NullPlayerIdException("[Class Player, Method setPlayerID]: Null player id");
+ }
+ this.playerId = playerId;
+ }
+
+ /**
+ * playerName setter.
+ * The {@link PlayerFactory} has the ownership to guarantee the uniqueness of
+ * this
+ * name in a game instance.
+ *
+ * @param playerName The player name.
+ *
+ */
+ public synchronized void setPlayerName(String playerName) throws NullPlayerNameException {
+ if (playerName == null) {
+ throw new NullPlayerNameException("[Class Player, method setPlayerName]: Null player name");
+ }
+ this.playerName = playerName;
+ }
+
+ /**
+ * score setter.
+ *
+ * @param score The score.
+ *
+ */
+ public synchronized void setScore(Score score) throws NullPlayerScoreException {
+ if (score == null) {
+ throw new NullPlayerScoreException("[Class Player, method setScore]: Null score");
+ }
+ this.score = score;
+ }
+
+ /**
+ * bookshelf setter.
+ *
+ * @param bookshelf The bookshelf.
+ *
+ */
+ public synchronized void setBookshelf(Bookshelf bookshelf) throws NullPlayerBookshelfException {
+ if (bookshelf == null) {
+ throw new NullPlayerBookshelfException("[Class Player, method setBookshelf]: Null bookshelf");
+ }
+ this.bookshelf = bookshelf;
+ }
+
+ /**
+ * privateCard setter.
+ *
+ * @param privateCard The private card.
+ *
+ */
+ public synchronized void setPrivateCard(PrivateCard privateCard) throws NullPlayerPrivateCardException {
+ if (privateCard == null) {
+ throw new NullPlayerPrivateCardException("[Class Player, method setPrivateCard]: Null private card");
+ }
+ this.privateCard = privateCard;
+ }
+
+ /**
+ * scoreBlocks setter.
+ *
+ * @param scoreBlocks The score blocks list.
+ *
+ */
+ public synchronized void setScoreBlocks(List<ScoreBlock> scoreBlocks) throws NullPlayerScoreBlocksException {
+ if (scoreBlocks == null) {
+ throw new NullPlayerScoreBlocksException("[Class Player, method setScoreBlocks]: Null score blocks");
+ }
+ this.scoreBlocks = scoreBlocks;
+ }
+
+ /**
+ * Method to add a scoreblock to the player.
+ *
+ * @param scoreBlock The scoreblock to add.
+ */
+ public synchronized void addScoreBlock(ScoreBlock scoreBlock) {
+ scoreBlocks.add(scoreBlock);
+ }
+
+ /**
+ * playerId getter.
+ *
+ * @return The player's id.
+ *
+ */
+ public synchronized UUID getPlayerID() {
+ return playerId;
+ }
+
+ /**
+ * playerName getter.
+ *
+ * @return The player's name.
+ *
+ */
+ public synchronized String getPlayerName() {
+ return playerName;
+ }
+
+ /**
+ * score getter.
+ *
+ * @return The player's score.
+ *
+ */
+ public synchronized Score getScore() {
+ return score;
+ }
+
+ /**
+ * bookshelf getter.
+ *
+ * @return The player's bookshelf.
+ *
+ */
+ public synchronized Bookshelf getBookshelf() {
+ return bookshelf;
+ }
+
+ /**
+ * privateCard getter.
+ *
+ * @return The player's private card.
+ *
+ */
+ public synchronized PrivateCard getPrivateCard() {
+ return privateCard;
+ }
+
+ /**
+ * scoreBlocks getter.
+ *
+ * @return The player's score blocks list.
+ *
+ */
+ public synchronized List<ScoreBlock> getScoreBlocks() {
+ return scoreBlocks;
+ }
+
+ /**
+ * Function to be called by {@link Game} at the end of Player's turn.
+ * Updates its scores passing their score-giving objects to specific methods.
+ *
+ * @throws NullPointerException Generic NPE.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullScoreBlockListException If the list of scoreblocks is null.
+ * @throws NegativeMatchedBlockCountException If the number of matched blocks to set is negative.
+ * @throws NullMatchedBlockCountException If the number of matched blocks to set is null.
+ */
+ public void updateScore() throws NullPointerException, BookshelfGridColIndexOutOfBoundsException,
+ BookshelfGridRowIndexOutOfBoundsException, NullIndexValueException, NullPlayerBookshelfException,
+ NullScoreBlockListException, NullMatchedBlockCountException, NegativeMatchedBlockCountException {
+ score.setBookshelfPoints(bookshelf);
+ score.setPrivatePoints(bookshelf, privateCard);
+ score.setScoreBlockPoints(scoreBlocks);
+ }
+
+ /**
+ * Setter of connected status.
+ *
+ * @param status status of player connection.
+ */
+ public synchronized void setIsConnected(boolean status) {
+ this.isConnected = status;
+ }
+
+ /**
+ * Getter of connected status.
+ *
+ * @return status of player connection.
+ */
+ public synchronized boolean getIsConnected() {
+ return isConnected;
+ }
+
+ /**
+ * Setter of active player status.
+ *
+ * @param status status of player.
+ */
+ public void setIsActivePlayer(boolean status) {
+ this.isActivePlayer = status;
+ }
+
+ /**
+ * Getter of active player status.
+ *
+ * @return status of player .
+ */
+ public boolean getIsActivePlayer() {
+ return isActivePlayer;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Player)) {
+ return false;
+ }
+ Player player = (Player) obj;
+ return (playerId.equals(player.getPlayerID())
+ && playerName.equals(player.getPlayerName()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return playerName.hashCode() * playerId.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/index.html
new file mode 100644
index 00000000..ab456fa0
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.player
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/index.source.html
new file mode 100644
index 00000000..cdbb84a5
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.player/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.player
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/Score.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/Score.html
new file mode 100644
index 00000000..866ee495
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/Score.html
@@ -0,0 +1 @@
+Score
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/Score.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/Score.java.html
new file mode 100644
index 00000000..a0674191
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/Score.java.html
@@ -0,0 +1,347 @@
+Score.java
package it.polimi.is23am10.server.model.score;
+
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.card.PrivateCard;
+import it.polimi.is23am10.server.model.items.card.exceptions.NegativeMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullMatchedBlockCountException;
+import it.polimi.is23am10.server.model.items.card.exceptions.NullScoreBlockListException;
+import it.polimi.is23am10.server.model.items.scoreblock.ScoreBlock;
+import it.polimi.is23am10.server.model.items.tile.Tile;
+import it.polimi.is23am10.server.model.items.tile.Tile.TileType;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerBookshelfException;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * The Score class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+
+public final class Score implements Serializable {
+ /**
+ * Integer referencing the extra point given to the first player
+ * to complete their bookshelf.
+ *
+ */
+ private Integer extraPoint;
+
+ /**
+ * Integer referencing the points the player receives from
+ * the groups of same type tiles in their bookshelf.
+ *
+ */
+ private Integer bookshelfPoints;
+
+ /**
+ * Integer referencing the points the player receives from
+ * completing shared goals.
+ *
+ */
+ private Integer scoreBlockPoints;
+
+ /**
+ * Integer referencing the points the player receives from
+ * completing their private (secret) goal.
+ *
+ */
+ private Integer privatePoints;
+
+ /**
+ * Map that allows the conversion from number of matches
+ * in private cards to points received.
+ */
+ public static final Map<Integer, Integer> privateCardPointsMap = Map.of(
+ 0, 0,
+ 1, 1,
+ 2, 2,
+ 3, 4,
+ 4, 6,
+ 5, 9,
+ 6, 12
+ );
+
+ /**
+ * Map that allows the conversion from number of groups
+ * in player's bookshelf to points received.
+ */
+ private static final Map<Integer, Integer> bookshelfPointsMap = Map.of(
+ 3, 2,
+ 4, 3,
+ 5, 5,
+ 6, 8
+ );
+
+ /**
+ * Integer representing the minimum group size
+ * for counting bookshelf points.
+ */
+ private static final Integer MIN_GROUP_SIZE = 3;
+
+ /**
+ * Integer representing the maximum "useful" group
+ * size for counting bookshelf points. Bigger groups
+ * count as groups of MAX_GROUP_SIZE
+ */
+ private static final Integer MAX_GROUP_SIZE = 6;
+
+ /**
+ * Constructor.
+ * Set all the default values.
+ *
+ */
+ public Score() {
+ extraPoint = 0;
+ bookshelfPoints = 0;
+ scoreBlockPoints = 0;
+ privatePoints = 0;
+ }
+
+ /**
+ * Copy constructor to generate another copy of same Score.
+ *
+ * @param toCopy score to copy
+ */
+ public Score(Score toCopy) {
+ bookshelfPoints = toCopy.getBookshelfPoints();
+ extraPoint = toCopy.getExtraPoint();
+ scoreBlockPoints = toCopy.getScoreBlockPoints();
+ privatePoints = toCopy.getPrivatePoints();
+ }
+
+ /**
+ * extraPoint setter. Value can be only set to 1.
+ *
+ */
+ public void setExtraPoint() {
+ this.extraPoint = 1;
+ }
+
+ /**
+ * bookshelfPoints setter.
+ *
+ * @param bs The bookshelf object to check for groups in.
+ * @throws NullIndexValueException If the index provided is null.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullPointerException Generic NPE.
+ *
+ */
+ public void setBookshelfPoints(Bookshelf bs)
+ throws NullPointerException, BookshelfGridColIndexOutOfBoundsException,
+ BookshelfGridRowIndexOutOfBoundsException, NullIndexValueException, NullPlayerBookshelfException {
+ if (bs == null) {
+ throw new NullPlayerBookshelfException("[Score:setBookshelfPoints]");
+ }
+
+ List<Integer> groupsSizes = new ArrayList<>();
+ Integer[][] visitedMap = new Integer[Bookshelf.BOOKSHELF_ROWS][Bookshelf.BOOKSHELF_COLS];
+
+ // Traverse each tile in the grid
+ for (int i = 0; i < Bookshelf.BOOKSHELF_ROWS; i++) {
+ for (int j = 0; j < Bookshelf.BOOKSHELF_COLS; j++) {
+ // Check if the tile has already been visited
+ if (visitedMap[i][j] == null) {
+ Tile tile = bs.getBookshelfGridAt(i, j);
+ if (!tile.isEmpty()) {
+ int groupSize = countAdjacentTilesRecursive(bs, i, j, visitedMap, tile.getType());
+
+ // I only consider valid groups.
+ if (groupSize >= MIN_GROUP_SIZE && groupSize <= MAX_GROUP_SIZE) {
+ groupsSizes.add(groupSize);
+ }
+ if (groupSize > MAX_GROUP_SIZE) {
+ groupsSizes.add(MAX_GROUP_SIZE);
+ }
+ }
+ }
+ }
+ }
+
+ bookshelfPoints = groupsSizes.stream().mapToInt(bookshelfPointsMap::get).sum();
+ }
+
+ /**
+ * scoreBlockPoints setter.
+ *
+ * @param scoreBlocks The scoreblock list to get points from.
+ * @throws NullScoreBlockListException If the list of scoreblocks is null.
+ *
+ */
+ public void setScoreBlockPoints(List<ScoreBlock> scoreBlocks) throws NullScoreBlockListException {
+ if (scoreBlocks == null) {
+ throw new NullScoreBlockListException("[Score:setScoreBlockPoints]");
+ }
+ scoreBlockPoints = scoreBlocks.stream().mapToInt(sb -> sb.getScore()).sum();
+ }
+
+ /**
+ * privatePoints setter.
+ *
+ * @param pc The private card to get points from.
+ * @throws NegativeMatchedBlockCountException If the number of matched blocks to set is negative.
+ * @throws NullMatchedBlockCountException If the number of matched blocks to set is null.
+ * @throws NullPointerException Generic NPE.
+ *
+ */
+ public void setPrivatePoints(Bookshelf bs, PrivateCard pc) throws NullPointerException, NullMatchedBlockCountException, NegativeMatchedBlockCountException {
+ if (pc == null) {
+ throw new NullPointerException("[Score:setPrivatePoints]");
+ }
+ pc.setMatchedBlocksCount(pc.getPattern().getRule().apply(bs));
+ privatePoints = privateCardPointsMap.get(pc.getMatchedBlocksCount());
+ }
+
+ public void obfuscatePrivatePoints() {
+ privatePoints = -1;
+ }
+
+ /**
+ * extraPoints getter.
+ *
+ * @return The extra points value.
+ *
+ */
+ public Integer getExtraPoint() {
+ return extraPoint;
+ }
+
+ /**
+ * bookshelfPoints getter.
+ *
+ * @return The bookshelf points value.
+ *
+ */
+ public Integer getBookshelfPoints() {
+ return bookshelfPoints;
+ }
+
+ /**
+ * scoreBlockPoints getter.
+ *
+ * @return The score block points value.
+ *
+ */
+ public Integer getScoreBlockPoints() {
+ return scoreBlockPoints;
+ }
+
+ /**
+ * privatePoints getter.
+ *
+ * @return The private points value.
+ *
+ */
+ public Integer getPrivatePoints() {
+ return privatePoints;
+ }
+
+ /**
+ * Helper function to help with BFS in bookshelf to find groups of same tile type.
+ * NOTE: this function should be called from setBookshelfPoints()
+ *
+ * @param bs Bookshelf to check for groups in
+ * @param row Row index
+ * @param col Column index
+ * @param visitedMap map to prevent repeatedly checking same spots
+ * @param type type of tile to check for
+ * @return integer representing the count of items in that group
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds.
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds.
+ * @throws NullIndexValueException If the index provided is null.
+ */
+ private int countAdjacentTilesRecursive(Bookshelf bs, int row, int col, Integer[][] visitedMap, TileType type)
+ throws BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException, NullIndexValueException {
+ // Check if the current tile is within the grid bounds and has the same type as the original tile, is not empty not visited
+ if (
+ row < 0 || row >= Bookshelf.BOOKSHELF_ROWS
+ || col < 0 || col >= Bookshelf.BOOKSHELF_COLS
+ || bs.getBookshelfGridAt(row, col).getType() != type
+ || bs.getBookshelfGridAt(row, col).getType() == TileType.EMPTY
+ || visitedMap[row][col] != null
+ ) {
+ return 0;
+ }
+
+ // Mark the current tile as visited by setting it to 1
+ visitedMap[row][col] = 1;
+
+ // Recursively count the adjacent tiles with the same type
+ int count = 1;
+ count += countAdjacentTilesRecursive(bs, row + 1, col, visitedMap, type);
+ count += countAdjacentTilesRecursive(bs, row - 1, col, visitedMap, type);
+ count += countAdjacentTilesRecursive(bs, row, col + 1, visitedMap, type);
+ count += countAdjacentTilesRecursive(bs, row, col - 1, visitedMap, type);
+
+ return count;
+ }
+
+ /**
+ * Getter method that returns the total score
+ * computing it from all the available scores.
+ *
+ * @return total score.
+ */
+ public Integer getTotalScore() {
+ return extraPoint + scoreBlockPoints + privatePoints + bookshelfPoints;
+ }
+
+ /**
+ * Method used to retrieve the total score from a
+ * possibly obfuscated score. It ignores the invalidated privatescore.
+ * Used for sorting purposes in CLI.
+ *
+ * @return visible score.
+ */
+ public Integer getVisibleScore() {
+ return (privatePoints != -1) ? getTotalScore() : extraPoint + scoreBlockPoints + bookshelfPoints;
+ }
+
+ /**
+ * Method used to retrieve a string representing the
+ * total score. If score is obfuscated (private card points)
+ * it returns a partial score and a graphic indication of possibly missing points.
+ *
+ * @return String representing total score.
+ */
+ public String getStringTotalScore() {
+ return (privatePoints != -1) ? getTotalScore().toString() : String.format("%d (+?)", getVisibleScore());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Score)) {
+ return false;
+ }
+ Score sc = (Score) obj;
+ return (
+ extraPoint == sc.getExtraPoint()
+ && scoreBlockPoints == sc.getScoreBlockPoints()
+ && privatePoints == sc.getPrivatePoints()
+ && bookshelfPoints == sc.getBookshelfPoints());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ return extraPoint.hashCode() * scoreBlockPoints.hashCode() * privatePoints.hashCode() * bookshelfPoints.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/index.html
new file mode 100644
index 00000000..0bacf15b
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.score
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/index.source.html
new file mode 100644
index 00000000..bf2814f4
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.model.score/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.model.score
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/CurrentPlayerHandler.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/CurrentPlayerHandler.html
new file mode 100644
index 00000000..533be2fa
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/CurrentPlayerHandler.html
@@ -0,0 +1 @@
+CurrentPlayerHandler
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/CurrentPlayerHandler.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/CurrentPlayerHandler.java.html
new file mode 100644
index 00000000..975ec940
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/CurrentPlayerHandler.java.html
@@ -0,0 +1,92 @@
+CurrentPlayerHandler.java
+package it.polimi.is23am10.server.network.gamehandler;
+
+import it.polimi.is23am10.server.model.player.Player;
+
+/**
+ * The current player handler class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class CurrentPlayerHandler {
+
+ /**
+ * The active player instance.
+ *
+ */
+ private Player player;
+
+ /**
+ * The active player turn starting time in ms.
+ *
+ */
+ private long startPlayingTimeMs;
+
+ /**
+ * The notified flag if a first inactivity has been alreasy notified.
+ *
+ */
+ private boolean notified;
+
+ /**
+ * The player setter.
+ *
+ * @param p the player to be assigned.
+ *
+ */
+ public synchronized void setPlayer(Player p) {
+ player = p;
+ }
+
+ /**
+ * The start playing time setter.
+ *
+ * @param time the player turn starting time in ms.
+ *
+ */
+ public synchronized void setStartPlayingTimeMs(long time) {
+ startPlayingTimeMs = time;
+ }
+
+ /**
+ * The notified setter.
+ *
+ * @param f the notified flag to be set.
+ *
+ */
+ public synchronized void setNotified(boolean f) {
+ notified = f;
+ }
+
+ /**
+ * The player getter.
+ *
+ * @return the assigned player.
+ *
+ */
+ public synchronized Player getPlayer() {
+ return player;
+ }
+
+ /** The start playing time getter.
+ *
+ * @return the player turn starting time in ms.
+ *
+ */
+ public synchronized long getStartPlayingTimeMs() {
+ return startPlayingTimeMs;
+ }
+
+ /**
+ * The notified getter.
+ *
+ */
+ public synchronized boolean getNotified() {
+ return notified;
+ }
+}
+
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/GameHandler.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/GameHandler.html
new file mode 100644
index 00000000..511c0d5c
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/GameHandler.html
@@ -0,0 +1 @@
+GameHandler
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/GameHandler.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/GameHandler.java.html
new file mode 100644
index 00000000..44992063
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/GameHandler.java.html
@@ -0,0 +1,278 @@
+GameHandler.java
package it.polimi.is23am10.server.network.gamehandler;
+
+import it.polimi.is23am10.server.model.factory.GameFactory;
+import it.polimi.is23am10.server.model.factory.exceptions.DuplicatePlayerNameException;
+import it.polimi.is23am10.server.model.factory.exceptions.NullPlayerNamesException;
+import it.polimi.is23am10.server.model.game.Game;
+import it.polimi.is23am10.server.model.game.Game.GameStatus;
+import it.polimi.is23am10.server.model.game.exceptions.FullGameException;
+import it.polimi.is23am10.server.model.game.exceptions.InvalidMaxPlayerException;
+import it.polimi.is23am10.server.model.game.exceptions.NullAssignedPatternException;
+import it.polimi.is23am10.server.model.game.exceptions.NullMaxPlayerException;
+import it.polimi.is23am10.server.model.game.exceptions.PlayerNotFoundException;
+import it.polimi.is23am10.server.model.items.board.exceptions.InvalidNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.board.exceptions.NullNumOfPlayersException;
+import it.polimi.is23am10.server.model.items.card.exceptions.AlreadyInitiatedPatternException;
+import it.polimi.is23am10.server.model.items.scoreblock.exceptions.NotValidScoreBlockValueException;
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerBookshelfException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerIdException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerNameException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerPrivateCardException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreBlocksException;
+import it.polimi.is23am10.server.model.player.exceptions.NullPlayerScoreException;
+import it.polimi.is23am10.server.network.gamehandler.exceptions.GameSnapshotUpdateException;
+import it.polimi.is23am10.server.network.gamehandler.exceptions.NullPlayerConnector;
+import it.polimi.is23am10.server.network.messages.GameMessage;
+import it.polimi.is23am10.server.network.playerconnector.AbstractPlayerConnector;
+import it.polimi.is23am10.server.network.playerconnector.PlayerConnectorSocket;
+import it.polimi.is23am10.server.network.virtualview.VirtualView;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * The match class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class GameHandler {
+
+ /**
+ * The logger, an instance of {@link Logger}.
+ *
+ */
+ final Logger logger = LogManager.getLogger(GameHandler.class);
+
+ /**
+ * The underlying game state.
+ *
+ */
+ private Game game;
+
+ /**
+ * The connected players connectors.
+ *
+ */
+ private Set<AbstractPlayerConnector> playerConnectors =
+ Collections.synchronizedSet(new HashSet<>());
+
+ /**
+ * The current player handler.
+ *
+ */
+ private CurrentPlayerHandler currentPlayerHandler;
+
+ /**
+ * Constructor.
+ *
+ * @param firstPlayerName The match starting player name.
+ * @param maxPlayersNum The chosen max players for this match.
+ * @throws NullNumOfPlayersException If the number of players provided when filling the board is null.
+ * @throws InvalidNumOfPlayersException If, while adding multiple players, there is an invalid number of them.
+ * @throws NullPlayerNamesException If, while adding multiple players, the list of player names is null.
+ * @throws AlreadyInitiatedPatternException If assigning a pattern to a card that already has one.
+ * @throws DuplicatePlayerNameException If player with that name already exists.
+ * @throws NullPlayerScoreBlocksException If player's scoreblocks list is null.
+ * @throws NullPlayerPrivateCardException If player's private card object is null.
+ * @throws NullPlayerScoreException If player's score object is null.
+ * @throws NullPlayerBookshelfException If bookshelf is null.
+ * @throws NullPlayerIdException If player id is null.
+ * @throws NullPlayerNameException If player name is null.
+ * @throws InvalidMaxPlayerException If value for maximum number of players in the game is not valid.
+ * @throws NullMaxPlayerException If no value for maximum number of players in the game is provided.
+ * @throws NullAssignedPatternException If the pattern assigned to a card is null.
+ * @throws FullGameException If game is full, on player trying to join.
+ * @throws NotValidScoreBlockValueException If the value assigned to a scoreblock is not valid.
+ * @throws PlayerNotFoundException If the player with the name provided is not found.
+ *
+ */
+ public GameHandler(String firstPlayerName, Integer maxPlayersNum)
+ throws NullMaxPlayerException, InvalidMaxPlayerException, NullPlayerNameException,
+ NullPlayerIdException, NullPlayerBookshelfException, NullPlayerScoreException,
+ NullPlayerPrivateCardException, NullPlayerScoreBlocksException, DuplicatePlayerNameException,
+ AlreadyInitiatedPatternException, NullPlayerNamesException, InvalidNumOfPlayersException,
+ NullNumOfPlayersException, NullAssignedPatternException, FullGameException, NotValidScoreBlockValueException, PlayerNotFoundException {
+ this.game = GameFactory.getNewGame(firstPlayerName, maxPlayersNum);
+ this.currentPlayerHandler = new CurrentPlayerHandler();
+ updateCurrentPlayerHandler();
+ }
+
+ /**
+ * Update the current player handler based on the game model updates.
+ *
+ */
+ public synchronized void updateCurrentPlayerHandler() {
+ currentPlayerHandler.setPlayer(game.getActivePlayer());
+ currentPlayerHandler.setStartPlayingTimeMs(System.currentTimeMillis());
+ currentPlayerHandler.setNotified(false);
+ }
+
+ /**
+ * Current player handler getter.
+ *
+ * @return The current player handler instance.
+ *
+ */
+ public synchronized CurrentPlayerHandler getCurrentPlayerHandler() {
+ return currentPlayerHandler;
+ }
+
+ /**
+ * Retrieve the {@link AbstractPlayerConnector} from a {@link Player} instance.
+ *
+ * @param player the player assinged to a connector to find.
+ *
+ */
+ public AbstractPlayerConnector getPlayerConnectorFromPlayer(Player player) {
+ if (player == null) {
+ return null;
+ }
+ Optional<AbstractPlayerConnector> res;
+ synchronized (playerConnectors) {
+ res = playerConnectors.stream()
+ .filter(p -> p.getPlayer().equals(player))
+ .findFirst();
+ }
+
+ if (res.isPresent()) {
+ return res.get();
+ }
+ return null;
+ }
+
+
+ /**
+ * Getter for {@link Game} instance.
+ *
+ * @return The current game instance containing the game state.
+ *
+ */
+ public synchronized Game getGame() {
+ return game;
+ }
+
+ /**
+ * Getter for {@link AbstractPlayerConnector} list instance.
+ *
+ * @return The current game instance containing the game state.
+ *
+ */
+ public synchronized Set<AbstractPlayerConnector> getPlayerConnectors() {
+ return playerConnectors;
+ }
+
+ /**
+ * Add a new player connector from socket server.
+ * Will accept a built instance of {@link AbstractPlayerConnector}
+ *
+ * @param playerConnector The connector to be added to the current game.
+ * @throws NullPlayerConnector On null Player connector.
+ *
+ */
+ public void addPlayerConnector(AbstractPlayerConnector playerConnector)
+ throws NullPlayerConnector {
+ if (playerConnector == null) {
+ throw new NullPlayerConnector();
+ }
+ playerConnectors.add(playerConnector);
+ }
+
+ /**
+ * Push a new game state to the message queue for each connected player.
+ *
+ * @throws GameSnapshotUpdateException On notification failure.
+ *
+ */
+ public void pushGameState() throws GameSnapshotUpdateException {
+ // iterating over the Collections.synchronizedList requires synch.
+ synchronized (playerConnectors) {
+ playerConnectors.parallelStream().forEach((pc) -> {
+ if (!pc.getPlayer().getIsConnected()) {
+ return;
+ }
+ VirtualView gameCopy = new VirtualView(game);
+ if (game.getStatus() != GameStatus.ENDED) {
+ gameCopy.getPlayers()
+ .stream()
+ .filter(p -> !p.getPlayerName().equals(pc.getPlayer().getPlayerName()))
+ .forEach(p -> p.obfuscatePrivateCard());
+ }
+ // synch is performed by the blocking queue.
+ try {
+ pc.notify(new GameMessage(gameCopy));
+ } catch (InterruptedException | RemoteException e) {
+ logger.error("Failed to notify game state {}", e.getMessage());
+ }
+ });
+ }
+ }
+
+ /**
+ * Method that remove player by the game handler
+ *
+ * @param gameId game id.
+ * @param player player to be removed.
+ */
+ public void removePlayerByGame(UUID gameId, Player player){
+ if (gameId == null || player == null) {
+ return;
+ }
+
+ Optional<AbstractPlayerConnector> target;
+
+ synchronized (playerConnectors) {
+ target = getPlayerConnectors().stream()
+ .filter(connector ->
+ connector.getGameId().equals(gameId) && connector.getPlayer().equals(player))
+ .findFirst();
+ }
+ if (target.isPresent()) {
+ AbstractPlayerConnector targetConnector = target.get();
+ if (targetConnector.getClass() == PlayerConnectorSocket.class) {
+ try {
+ PlayerConnectorSocket ps = (PlayerConnectorSocket) targetConnector;
+ synchronized (ps.getConnector()) {
+ ps.getConnector().close();
+ }
+ } catch (IOException e) {
+ logger.error("Failed to close socket connection", e);
+ }
+ }
+ playerConnectors.remove(targetConnector);
+ logger.info("Removed player {} connector from game {}", player.getPlayerName(), gameId);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof GameHandler)) {
+ return false;
+ }
+ GameHandler casted = (GameHandler) obj;
+ return game.getGameId().equals(casted.getGame().getGameId());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public int hashCode() {
+ return game.getGameId().hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/index.html
new file mode 100644
index 00000000..7909df54
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.gamehandler
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/index.source.html
new file mode 100644
index 00000000..8768c9d8
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.gamehandler/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.gamehandler
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage$MessageType.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage$MessageType.html
new file mode 100644
index 00000000..a8a5df05
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage$MessageType.html
@@ -0,0 +1 @@
+AbstractMessage.MessageType
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage.html
new file mode 100644
index 00000000..425c8673
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage.html
@@ -0,0 +1 @@
+AbstractMessage
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage.java.html
new file mode 100644
index 00000000..b4701d08
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AbstractMessage.java.html
@@ -0,0 +1,70 @@
+AbstractMessage.java
package it.polimi.is23am10.server.network.messages;
+
+import java.io.Serializable;
+
+/**
+ * Abstract class representing a generic message exchanged.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class AbstractMessage implements Serializable {
+
+ /**
+ * Message in string. Plaintext or JSON.
+ */
+ protected String message;
+
+ /**
+ * Enum for type of message sent.
+ */
+ public enum MessageType {
+ /**
+ * Message containing a VirtualView, representing a game snapshot.
+ */
+ GAME_SNAPSHOT,
+ /**
+ * Message containing a chat textual exchange between players.
+ */
+ CHAT_MESSAGE,
+ /**
+ * Message containing an error to display to the player.
+ */
+ ERROR_MESSAGE,
+ /**
+ * Message containing the available games joinable by player.
+ */
+ AVAILABLE_GAMES,
+ /**
+ * ACK message sent from server to client to confirm that the timer has
+ * been snoozed. If not received by client implies client termination.
+ */
+ SNOOZE_ACK
+ }
+
+ /**
+ * Type of message sent.
+ */
+ protected MessageType msgType;
+
+ /**
+ * Getter for message.
+ *
+ * @return the string message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Getter for message type.
+ *
+ * @return message type
+ */
+ public MessageType getMessageType() {
+ return msgType;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AvailableGamesMessage.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AvailableGamesMessage.html
new file mode 100644
index 00000000..499d0a64
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AvailableGamesMessage.html
@@ -0,0 +1 @@
+AvailableGamesMessage
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AvailableGamesMessage.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AvailableGamesMessage.java.html
new file mode 100644
index 00000000..30a330e6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/AvailableGamesMessage.java.html
@@ -0,0 +1,76 @@
+AvailableGamesMessage.java
package it.polimi.is23am10.server.network.messages;
+
+import java.util.List;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.network.virtualview.VirtualView;
+import it.polimi.is23am10.utils.ThreadLocalTypeAdapterFactory;
+
+/**
+ * A message containing a list of available games to be sent to the client.
+ * Note that games are already converted to network-friendly {@link VirtualView}
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class AvailableGamesMessage extends AbstractMessage {
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Gson object for serialization and deserialization
+ */
+ protected final transient Gson gson = new GsonBuilder()
+.registerTypeAdapterFactory(new ThreadLocalTypeAdapterFactory())
+.create();
+
+
+ /**
+ * Player receiving the list.
+ */
+ private Player receiver;
+
+ /**
+ * Public constructor for AvailableGamesMessage in broadcast.
+ *
+ * @param availableGames games to send.
+ * @param receivers the optional receiver player.
+ */
+ public AvailableGamesMessage(List<VirtualView> availableGames, Player ...receivers) {
+ msgType = MessageType.AVAILABLE_GAMES;
+ this.message = gson.toJson(availableGames);
+ if (receivers != null && receivers.length > 0) {
+ this.receiver = receivers[0];
+ }
+ }
+
+ /**
+ * Boolean to check if message is direct or broadcast.
+ * Overridden to false as this message can only be private.
+ *
+ * @return is the message broadcast?
+ */
+ public boolean isBroadcast() {
+ return false;
+ }
+
+ /**
+ * Getter for the receiving player.
+ *
+ * @return the player instance
+ */
+ public Player getReceiver() {
+ return receiver;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ChatMessage.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ChatMessage.html
new file mode 100644
index 00000000..2fa4a165
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ChatMessage.html
@@ -0,0 +1 @@
+ChatMessage
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ChatMessage.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ChatMessage.java.html
new file mode 100644
index 00000000..1c644b3d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ChatMessage.java.html
@@ -0,0 +1,126 @@
+ChatMessage.java
package it.polimi.is23am10.server.network.messages;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.utils.ThreadLocalTypeAdapterFactory;
+
+/**
+ * A message sent via chat.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class ChatMessage extends AbstractMessage {
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Gson object for serialization and deserialization
+ */
+ protected final transient Gson gson = new GsonBuilder()
+ .registerTypeAdapterFactory(new ThreadLocalTypeAdapterFactory())
+ .create();
+
+ /**
+ * Not mandatory. Not null if direct message.
+ */
+ private Player receiver;
+
+ /**
+ * Not mandatory, used for alt constructor.
+ */
+ private String receiverName;
+
+
+ /**
+ * Player sending the message.
+ */
+ protected Player sender;
+
+ /**
+ * Public constructor for building a direct message.
+ *
+ * @param sender the player sending the message
+ * @param chatMessage the actual message
+ * @param receiver the player receiving the message
+ */
+ public ChatMessage(Player sender, String chatMessage, Player receiver) {
+ msgType = MessageType.CHAT_MESSAGE;
+ this.sender = sender;
+ message = chatMessage;
+ this.receiver = receiver;
+ }
+
+ /**
+ * Public constructor for building a direct message.
+ *
+ * @param sender the player sending the message
+ * @param chatMessage the actual message
+ * @param receiver the player name receiving the message
+ */
+ public ChatMessage(Player sender, String chatMessage, String receiver) {
+ msgType = MessageType.CHAT_MESSAGE;
+ this.sender = sender;
+ message = chatMessage;
+ this.receiverName = receiver;
+ }
+
+ /**
+ * Public constructor for building a broadcast message.
+ *
+ * @param sender the player sending the message
+ * @param chatMessage the actual message
+ */
+ public ChatMessage(Player sender, String chatMessage){
+ msgType = MessageType.CHAT_MESSAGE;
+ this.sender = sender;
+ message = chatMessage;
+ }
+
+ /**
+ * Boolean to check if message is direct or broadcast.
+ *
+ * @return is the message broadcast?
+ */
+ public boolean isBroadcast() {
+ return receiverName == null;
+ }
+
+ /**
+ * Getter for the receiving player.
+ *
+ * @return the player instance
+ */
+ public Player getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Getter for the receiving player.
+ *
+ * @return the player instance
+ */
+ public String getReceiverName() {
+ return receiverName;
+ }
+
+ /**
+ * Getter for the sending player.
+ *
+ * @return the sending player
+ */
+ public Player getSender() {
+ return sender;
+ }
+
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage$ErrorSeverity.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage$ErrorSeverity.html
new file mode 100644
index 00000000..b699bf8d
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage$ErrorSeverity.html
@@ -0,0 +1 @@
+ErrorMessage.ErrorSeverity
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage.html
new file mode 100644
index 00000000..59b50e79
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage.html
@@ -0,0 +1 @@
+ErrorMessage
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage.java.html
new file mode 100644
index 00000000..3e0fa766
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/ErrorMessage.java.html
@@ -0,0 +1,98 @@
+ErrorMessage.java
package it.polimi.is23am10.server.network.messages;
+
+
+import it.polimi.is23am10.server.model.player.Player;
+
+
+/**
+ * A message containing an error message to be sent to the client.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class ErrorMessage extends AbstractMessage {
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Not mandatory. Not null if direct message.
+ */
+ private Player receiver;
+
+ /**
+ * Three degrees of severity.
+ * Rule of thumb:
+ * <ul>
+ * <li>Warning = something changed the flow of the game. User must be aware.
+ * <li>Error = something unexpected happened but game can continue.
+ * <li>Critical = something so bad happened that the game must end.
+ * </ul>
+ */
+ public enum ErrorSeverity {
+ INFO,
+ WARNING,
+ ERROR,
+ CRITICAL
+ }
+
+ private ErrorSeverity errorSeverity;
+
+ /**
+ * Public constructor for ErrorMessage.
+ *
+ * @param errorMessage message to send.
+ * @param receiver player who receives the message.
+ */
+ public ErrorMessage(String errorMessage, Player receiver, ErrorSeverity errorSeverity) {
+ msgType = MessageType.ERROR_MESSAGE;
+ this.message = errorMessage;
+ this.receiver = receiver;
+ this.errorSeverity = errorSeverity;
+ }
+
+ /**
+ * Public constructor for ErrorMessage in broadcast.
+ *
+ * @param errorMessage message to send.
+ */
+ public ErrorMessage(String errorMessage, ErrorSeverity errorSeverity) {
+ msgType = MessageType.ERROR_MESSAGE;
+ this.message = errorMessage;
+ this.errorSeverity = errorSeverity;
+ }
+
+ /**
+ * Boolean to check if message is direct or broadcast.
+ *
+ * @return is the message broadcast?
+ */
+ public boolean isBroadcast() {
+ return receiver == null;
+ }
+
+ /**
+ * Getter for the receiving player.
+ *
+ * @return the player instance
+ */
+ public Player getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Getter for the severity.
+ *
+ * @return the severity of the error.
+ */
+ public ErrorSeverity getErrorSeverity() {
+ return errorSeverity;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/GameMessage.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/GameMessage.html
new file mode 100644
index 00000000..f363c879
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/GameMessage.html
@@ -0,0 +1 @@
+GameMessage
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/GameMessage.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/GameMessage.java.html
new file mode 100644
index 00000000..acfa7e88
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/GameMessage.java.html
@@ -0,0 +1,54 @@
+GameMessage.java
package it.polimi.is23am10.server.network.messages;
+
+import it.polimi.is23am10.server.network.virtualview.VirtualView;
+import it.polimi.is23am10.utils.ThreadLocalTypeAdapterFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * A message containing a serialized JSON of a virtual view instance.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class GameMessage extends AbstractMessage {
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Gson object for serialization and deserialization
+ */
+ protected final transient Gson gson = new GsonBuilder()
+ .registerTypeAdapterFactory(new ThreadLocalTypeAdapterFactory())
+ .create();
+
+ /**
+ * Public constructor for virtualView message.
+ * Game object is serialized into JSON and set as message.
+ *
+ * @param virtualView virtual view to serialize
+ */
+ public GameMessage(VirtualView virtualView) {
+ msgType = MessageType.GAME_SNAPSHOT;
+ message = gson.toJson(virtualView);
+ }
+
+ /**
+ * Getter for virtualView instance. It deserializes it from
+ * JSON message.
+ *
+ * @return deserialized virtual view object
+ */
+ public VirtualView getGame() {
+ return gson.fromJson(message, VirtualView.class);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/SnoozeACKMessage.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/SnoozeACKMessage.html
new file mode 100644
index 00000000..24396587
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/SnoozeACKMessage.html
@@ -0,0 +1 @@
+SnoozeACKMessage
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/SnoozeACKMessage.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/SnoozeACKMessage.java.html
new file mode 100644
index 00000000..6bc82e55
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/SnoozeACKMessage.java.html
@@ -0,0 +1,29 @@
+SnoozeACKMessage.java
package it.polimi.is23am10.server.network.messages;
+
+/**
+ * Class representing a ACK message sent from server to client
+ * in response to a snooze command. Used to keep alice client.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class SnoozeACKMessage extends AbstractMessage {
+
+ /**
+ * An utility to be used during deserialization processes.
+ *
+ */
+ @SuppressWarnings("unused")
+ private final String className = this.getClass().getName();
+
+ /**
+ * Public constructor.
+ *
+ */
+ public SnoozeACKMessage(){
+ msgType = MessageType.SNOOZE_ACK;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/index.html
new file mode 100644
index 00000000..76be8ac4
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.messages
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/index.source.html
new file mode 100644
index 00000000..37465cb6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.messages/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.messages
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/AbstractPlayerConnector.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/AbstractPlayerConnector.html
new file mode 100644
index 00000000..70452a04
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/AbstractPlayerConnector.html
@@ -0,0 +1 @@
+AbstractPlayerConnector
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/AbstractPlayerConnector.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/AbstractPlayerConnector.java.html
new file mode 100644
index 00000000..0f734552
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/AbstractPlayerConnector.java.html
@@ -0,0 +1,188 @@
+AbstractPlayerConnector.java
package it.polimi.is23am10.server.network.playerconnector;
+
+import it.polimi.is23am10.server.model.game.Game;
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.network.messages.AbstractMessage;
+import it.polimi.is23am10.server.network.playerconnector.exceptions.NullBlockingQueueException;
+import it.polimi.is23am10.server.network.playerconnector.interfaces.IPlayerConnector;
+
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * The player connector class definition.
+ * This class is responsible to handle clients' sockets connections.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:nonemptyatclausedescriptioncheck" })
+public abstract class AbstractPlayerConnector implements Serializable, IPlayerConnector {
+
+ /**
+ * The player inside a game session.
+ *
+ */
+ protected Player player;
+
+ /**
+ * The unique {@link Game} id reference.
+ *
+ */
+ protected UUID gameId;
+
+ /**
+ * The connector message queue.
+ *
+ */
+ protected BlockingQueue<AbstractMessage> msgQueue;
+
+ /**
+ * The client last alarm snooze time in ms truggered by the alarm.
+ *
+ */
+ protected long lastSnoozeMs;
+
+ /**
+ * Constructor.
+ *
+ *
+ * @param msgQueue The message queue instance.
+ * @throws NullBlockingQueueException If providing a null queue when building player connector.
+ *
+ */
+ protected AbstractPlayerConnector(LinkedBlockingQueue<AbstractMessage> msgQueue)
+ throws NullBlockingQueueException {
+ if (msgQueue == null) {
+ throw new NullBlockingQueueException();
+ }
+ this.msgQueue = msgQueue;
+ this.lastSnoozeMs = System.currentTimeMillis();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
+ */
+ @Override
+ public synchronized UUID getGameId() {
+ return gameId;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
+ */
+ @Override
+ public synchronized Player getPlayer() {
+ return player;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
+ */
+ @Override
+ public AbstractMessage getMessageFromQueue() throws InterruptedException {
+ if (msgQueue.isEmpty()) {
+ return null;
+ }
+ //synch is performed by the collection
+ return (msgQueue.take());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
+ */
+ @Override
+ public synchronized int getMsgQueueSize() {
+ return msgQueue.size();
+ }
+
+ /**
+ * Get the client last snooze time in ms.
+ *
+ * @return The ms of the last alarm snooze.
+ *
+ */
+ public long getLastSnoozeMs() {
+ return lastSnoozeMs;
+ }
+
+ /**
+ * Notify the player an update.
+ * Different delivery strategies are applied based on the {@link AbstractMessage} dynamic type.
+ * This blocks undefinably until the queue is available (in case socket is used).
+ * {@link Game} model instances should leverage this
+ * queue to send responses to the client (i.e. game updates).
+ *
+ * @param message The message to be added.
+ * @throws InterruptedException On queue message insertion failure.
+ * @throws RemoteException On remote call failure.
+ *
+ */
+ public abstract void notify(AbstractMessage message) throws InterruptedException, RemoteException;
+
+ /**
+ * Setter for the associated game id.
+ *
+ * @param gameId The game id to associate to the current player connector.
+ *
+ */
+ public synchronized void setGameId(UUID gameId) {
+ this.gameId = gameId;
+ }
+
+ /**
+ * Setter for the player reference.
+ *
+ * @param player The player to associate to the current player connector.
+ *
+ */
+ @Override
+ public synchronized void setPlayer(Player player) {
+ this.player = player;
+ }
+
+ /**
+ * Set the client last snooze time in ms.
+ *
+ * @param lastSnoozeMs The snooze ms.
+ *
+ */
+ public void setLastSnoozeMs(long lastSnoozeMs) {
+ this.lastSnoozeMs = lastSnoozeMs;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AbstractPlayerConnector)) {
+ return false;
+ }
+ AbstractPlayerConnector casted = (AbstractPlayerConnector) obj;
+ return casted.getPlayer().equals(player)
+ && casted.getGameId().equals(gameId);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public int hashCode() {
+ return player.hashCode() * gameId.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorRmi.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorRmi.html
new file mode 100644
index 00000000..ce883738
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorRmi.html
@@ -0,0 +1 @@
+PlayerConnectorRmi
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorRmi.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorRmi.java.html
new file mode 100644
index 00000000..75d90fb1
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorRmi.java.html
@@ -0,0 +1,70 @@
+PlayerConnectorRmi.java
package it.polimi.is23am10.server.network.playerconnector;
+
+import java.rmi.RemoteException;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import it.polimi.is23am10.client.Client;
+import it.polimi.is23am10.client.IClient;
+import it.polimi.is23am10.server.network.messages.AbstractMessage;
+import it.polimi.is23am10.server.network.playerconnector.exceptions.NullBlockingQueueException;
+
+/**
+ * The player connector class definition.
+ * This class is responsible to handle clients' sockets connections.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:nonemptyatclausedescriptioncheck" })
+public class PlayerConnectorRmi extends AbstractPlayerConnector {
+
+ /**
+ * The {@link Client} reference.
+ */
+ private IClient client;
+
+ /**
+ * Constructor.
+ *
+ * @param msgQueue The message queue instance.
+ * @throws NullBlockingQueueException If providing a null queue when building player connector.
+ *
+ */
+ public PlayerConnectorRmi(LinkedBlockingQueue<AbstractMessage> msgQueue, IClient client)
+ throws NullBlockingQueueException {
+ super(msgQueue);
+ this.client = client;
+ }
+
+ /**
+ * Retrive the {@link Client} reference.
+ *
+ * @return The client reference.
+ *
+ */
+ public IClient getClient() {
+ return client;
+ }
+
+ /**
+ * Set the {@link Client} reference.
+ *
+ * @param client The client reference.
+ *
+ */
+ public void setClient(IClient client) {
+ this.client = client;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void notify(AbstractMessage msg) throws InterruptedException, RemoteException {
+ if (msg == null) {
+ return;
+ }
+ client.showServerMessage(msg);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorSocket.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorSocket.html
new file mode 100644
index 00000000..feb806ad
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorSocket.html
@@ -0,0 +1 @@
+PlayerConnectorSocket
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorSocket.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorSocket.java.html
new file mode 100644
index 00000000..9aeaf8d3
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/PlayerConnectorSocket.java.html
@@ -0,0 +1,80 @@
+PlayerConnectorSocket.java
package it.polimi.is23am10.server.network.playerconnector;
+
+import java.net.Socket;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import it.polimi.is23am10.server.network.messages.AbstractMessage;
+import it.polimi.is23am10.server.network.playerconnector.exceptions.NullBlockingQueueException;
+import it.polimi.is23am10.server.network.playerconnector.exceptions.NullSocketConnectorException;
+
+/**
+ * The player connector class definition.
+ * This class is responsible to handle clients' sockets connections.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+@SuppressWarnings({ "checkstyle:nonemptyatclausedescriptioncheck" })
+public class PlayerConnectorSocket extends AbstractPlayerConnector {
+ /**
+ * The socket connection reference.
+ *
+ */
+ private transient Socket connector;
+
+ /**
+ * Constructor.
+ *
+ *
+ * @param connector The {@link Socket} instance linked to a player client.
+ * @param msgQueue The message queue instance.
+ * @throws NullSocketConnectorException
+ * @throws NullBlockingQueueException If providing a null queue when building player connector.
+ *
+ */
+ public PlayerConnectorSocket(Socket connector, LinkedBlockingQueue<AbstractMessage> msgQueue)
+ throws NullSocketConnectorException, NullBlockingQueueException {
+ super(msgQueue);
+ if (connector == null) {
+ throw new NullSocketConnectorException();
+ }
+ this.connector = connector;
+ }
+
+ /**
+ * Getter for {@link Socket}, the low level connector.
+ *
+ * @return The socket connector.
+ *
+ */
+ public synchronized Socket getConnector() {
+ return connector;
+ }
+
+ /**
+ * Setter for {@link Socket}, the low level connector.
+ *
+ * @param socket the socket I want to set.
+ *
+ * @throws NullSocketConnectorException
+ *
+ */
+ public synchronized void setConnector(Socket socket) throws NullSocketConnectorException {
+ if(socket == null){
+ throw new NullSocketConnectorException();
+ }
+ this.connector = socket;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void notify(AbstractMessage msg) throws InterruptedException {
+ if (msg == null) {
+ return;
+ }
+ msgQueue.put(msg);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/index.html
new file mode 100644
index 00000000..e29ef1ae
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.playerconnector
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/index.source.html
new file mode 100644
index 00000000..d1648dd9
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.playerconnector/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.playerconnector
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualPlayer.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualPlayer.html
new file mode 100644
index 00000000..dc8d3d6b
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualPlayer.html
@@ -0,0 +1 @@
+VirtualPlayer
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualPlayer.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualPlayer.java.html
new file mode 100644
index 00000000..89426f06
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualPlayer.java.html
@@ -0,0 +1,147 @@
+VirtualPlayer.java
package it.polimi.is23am10.server.network.virtualview;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.player.Player;
+import it.polimi.is23am10.server.model.score.Score;
+
+/**
+ * A virtual view with the state of a player, downscoped
+ * to what is essential for the client to view
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class VirtualPlayer implements Serializable {
+
+ /**
+ * Unique player identifier.
+ */
+ private UUID playerId;
+
+ /**
+ * Player's game name.
+ */
+ private String playerName;
+
+ /**
+ * Player's score.
+ */
+ private Score score;
+
+ /**
+ * Player's bookshelf with its tiles.
+ */
+ private Bookshelf bookshelf;
+
+ /**
+ * 1-12 number referencing the private card to show.
+ */
+ private Integer privateCardIndex;
+
+ /**
+ * Status of the player.
+ *
+ */
+ private boolean isConnected;
+
+ /**
+ * Public constructor. Builds VirtualPlayer out of {@link Player}
+ * @param p instance of {@link Player} to "virtualize".
+ */
+ public VirtualPlayer(Player p) {
+ if (p != null) {
+ this.playerId = p.getPlayerID();
+ this.playerName = p.getPlayerName();
+ this.score = new Score(p.getScore());
+ this.bookshelf = new Bookshelf(p.getBookshelf());
+ this.privateCardIndex = p.getPrivateCard().getPattern().getIndex();
+ this.isConnected = p.getIsConnected();
+ }
+ }
+
+ /**
+ * Getter for player id.
+ * @return player id
+ */
+ public UUID getPlayerId() {
+ return playerId;
+ }
+
+ /**
+ * Getter for score.
+ * @return score
+ */
+ public Score getScore() {
+ return score;
+ }
+
+ /**
+ * Getter for bookshelf.
+ * @return bookshelf
+ */
+ public Bookshelf getBookshelf() {
+ return bookshelf;
+ }
+
+ /**
+ * Getter for private card index.
+ * @return private card index
+ */
+ public Integer getPrivateCardIndex() {
+ return privateCardIndex;
+ }
+
+ /**
+ * Getter for player name.
+ * @return player name
+ */
+ public String getPlayerName() {
+ return playerName;
+ }
+
+ /**
+ * Getter for connected status.
+ * @return connected status.
+ */
+ public boolean getIsConnected() {
+ return isConnected;
+ }
+
+ /**
+ * Void method used when pushing state
+ * to all players, in order to keep secret
+ * each player's card to other players.
+ */
+ public void obfuscatePrivateCard() {
+ privateCardIndex = 0;
+ score.obfuscatePrivatePoints();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof VirtualPlayer)) {
+ return false;
+ }
+
+ VirtualPlayer player = (VirtualPlayer) obj;
+ return (playerId.equals(player.getPlayerId())
+ && playerName.equals(player.getPlayerName()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return playerName.hashCode() * playerId.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualView.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualView.html
new file mode 100644
index 00000000..63aabeed
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualView.html
@@ -0,0 +1 @@
+VirtualView
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualView.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualView.java.html
new file mode 100644
index 00000000..e3b38fd2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/VirtualView.java.html
@@ -0,0 +1,208 @@
+VirtualView.java
package it.polimi.is23am10.server.network.virtualview;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import it.polimi.is23am10.server.model.game.Game;
+import it.polimi.is23am10.server.model.game.Game.GameStatus;
+import it.polimi.is23am10.server.model.items.board.Board;
+
+/**
+ * A virtual view with the state of the game, downscoped
+ * to what is essential for the client to view
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class VirtualView implements Serializable {
+
+ /**
+ * Unique Game identifier
+ */
+ private UUID gameId;
+
+ /**
+ * Maximum number of players for this game
+ */
+ private Integer maxPlayers;
+
+ /**
+ * List of virtual players in the game
+ */
+ private List<VirtualPlayer> players;
+
+ /**
+ * VirtualPlayer currently playing
+ */
+ private VirtualPlayer activePlayer;
+
+ /**
+ * VirtualPlayer choosen as first
+ */
+ private VirtualPlayer firstPlayer;
+
+ /**
+ * VirtualPlayer who won the game
+ */
+ private VirtualPlayer winnerPlayer;
+
+ /**
+ * Instance of board where tiles are
+ */
+ private Board gameBoard;
+
+ /**
+ * List of the two shared cards index for this game session.
+ */
+ private List<Integer> sharedCards;
+
+ /**
+ * Boolean flag signaling game is over
+ */
+ private GameStatus status;
+
+ /**
+ * Getter for game id
+ *
+ * @return game id
+ */
+ public UUID getGameId() {
+ return gameId;
+ }
+
+ /**
+ * Getter for player list ({@link VirtualPlayer})
+ *
+ * @return player list
+ */
+ public List<VirtualPlayer> getPlayers() {
+ return players;
+ }
+
+ /**
+ * Getter for max players
+ *
+ * @return max players
+ */
+ public Integer getMaxPlayers() {
+ return maxPlayers;
+ }
+
+ /**
+ * Getter for active player
+ *
+ * @return active player
+ */
+ public VirtualPlayer getActivePlayer() {
+ return activePlayer;
+ }
+
+ /**
+ * Getter for first player
+ *
+ * @return first player
+ */
+ public VirtualPlayer getFirstPlayer() {
+ return firstPlayer;
+ }
+
+ /**
+ * Getter for winner player
+ *
+ * @return winner player
+ */
+ public VirtualPlayer getWinnerPlayer() {
+ return winnerPlayer;
+ }
+
+ /**
+ * Getter for game board
+ *
+ * @return game board
+ */
+ public Board getGameBoard() {
+ return gameBoard;
+ }
+
+ /**
+ * Getter for shared cards indexes
+ *
+ * @return Pairs of shared cards indexes and descriptions.
+ */
+ public List<Integer> getSharedCards() {
+ return sharedCards;
+ }
+
+ /**
+ * Getter for status.
+ *
+ * @return game status.
+ */
+ public GameStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * Simple helper function to get the number of disconnected
+ * players to discount when looking for available games.
+ * @return disconnected player num.
+ */
+ public synchronized Integer getDisconnectedPlayersNum() {
+ return (int) players
+ .stream()
+ .filter(p -> !p.getIsConnected())
+ .count();
+ }
+
+
+ /**
+ * Public constructor. Builds VirtualView out of {@link Game}
+ *
+ * @param g instance of {@link Game} to "virtualize"
+ */
+ public VirtualView(Game g) {
+ this.gameId = g.getGameId();
+ this.activePlayer = g.getActivePlayer() == null ? null : new VirtualPlayer(g.getActivePlayer());
+ this.firstPlayer = g.getFirstPlayer() == null ? null : new VirtualPlayer(g.getFirstPlayer());
+ this.gameBoard = new Board(g.getGameBoard());
+ this.status = g.getStatus();
+ this.maxPlayers = g.getMaxPlayer();
+ this.players = g.getPlayers()
+ .stream()
+ .map(p -> new VirtualPlayer(p))
+ .collect(Collectors.toList());
+
+ this.sharedCards = g.getSharedCard()
+ .stream()
+ .map(c -> c.getPattern().getIndex())
+ .collect(Collectors.toList());
+
+ this.winnerPlayer = g.getWinnerPlayer() == null ? null : new VirtualPlayer(g.getWinnerPlayer());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof VirtualView)) {
+ return false;
+ }
+
+ VirtualView view = (VirtualView) obj;
+ return (gameId.equals(view.getGameId()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return gameId.hashCode();
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/index.html
new file mode 100644
index 00000000..194ce7cd
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.virtualview
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/index.source.html
new file mode 100644
index 00000000..0dade6e1
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server.network.virtualview/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server.network.virtualview
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server$ServerStatus.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server$ServerStatus.html
new file mode 100644
index 00000000..d802b7d6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server$ServerStatus.html
@@ -0,0 +1 @@
+Server.ServerStatus
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server.html
new file mode 100644
index 00000000..c95a4e40
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server.html
@@ -0,0 +1 @@
+Server
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server.java.html
new file mode 100644
index 00000000..b8e8b472
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server/Server.java.html
@@ -0,0 +1,167 @@
+Server.java
package it.polimi.is23am10.server;
+
+import it.polimi.is23am10.server.controller.ClientConnectionChecker;
+import it.polimi.is23am10.server.controller.ServerControllerAction;
+import it.polimi.is23am10.server.controller.ServerControllerRmiBindings;
+import it.polimi.is23am10.server.controller.ServerControllerSocket;
+import it.polimi.is23am10.server.controller.interfaces.IServerControllerAction;
+import it.polimi.is23am10.server.network.playerconnector.PlayerConnectorSocket;
+import it.polimi.is23am10.server.network.playerconnector.exceptions.NullBlockingQueueException;
+import it.polimi.is23am10.server.network.playerconnector.exceptions.NullSocketConnectorException;
+import it.polimi.is23am10.utils.config.AppConfigContext;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.RemoteException;
+import java.rmi.registry.Registry;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * The Server entry point class definition.
+ * This has only static methods and this class should not be instantiated.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class Server {
+
+ /**
+ * The serverSocket status.
+ *
+ */
+ public enum ServerStatus {
+ STARTED,
+ STOPPED
+ }
+
+ /**
+ * Logger instance.
+ *
+ */
+ protected final Logger logger = LogManager.getLogger(Server.class);
+
+ /**
+ * Socket serverSocket instance.
+ *
+ */
+ protected ServerSocket serverSocket;
+
+ /**
+ * Socket socket clients connected.
+ *
+ */
+ protected static int socketClientsConnected = 0;
+
+ /**
+ * Server thread executor service manager.
+ *
+ */
+ protected ExecutorService executorService;
+
+ /**
+ * Constructor.
+ *
+ * @param serverSocket The server socket reference of a newly
+ * connected client.
+ * @param executorService The built thread executor service.
+ * @param rmiServerControllerActionServer An built instance of the implementing
+ * class.
+ * @param rmiRegistry A built instance of the RMI registry.
+ * @throws RemoteException On rebind failure.
+ *
+ */
+ public Server(ServerSocket serverSocket, ExecutorService executorService,
+ IServerControllerAction rmiServerControllerActionServer, Registry rmiRegistry) throws RemoteException {
+ this.executorService = executorService;
+ this.serverSocket = serverSocket;
+ ServerControllerRmiBindings.setRmiRegistry(rmiRegistry);
+ ServerControllerRmiBindings.rebindServerControllerAction(rmiServerControllerActionServer);
+ }
+
+ /**
+ * Server entry point.
+ * A new {@link ServerSocket} instance is spawned and in a
+ * infinity loop listens for clients connections.
+ *
+ * @param ctx An instance of the server configuration.
+ *
+ */
+ public void start(AppConfigContext ctx) {
+ logger.info("Starting Spurious Dragon, try to kill me...");
+ // https://www.youtube.com/watch?v=Jo6fKboqfMs&ab_channel=memesammler
+
+ executorService.execute(new ClientConnectionChecker(ctx.getMaxInactivityTime()));
+
+ // start the socket server
+ while (!serverSocket.isClosed()) {
+ try {
+ if (getSocketClientsConnected() < ctx.getMaxConnections()) {
+ Socket client = serverSocket.accept();
+ client.setKeepAlive(ctx.getKeepAlive());
+ setSocketClientConnected(getSocketClientsConnected() + 1);
+ executorService.execute(new ServerControllerSocket(
+ new PlayerConnectorSocket(client,
+ new LinkedBlockingQueue<>()),
+ new ServerControllerAction()));
+ logger.info("Received new connection " + "(" + getSocketClientsConnected() + "/" + ctx.getMaxConnections() + ")" );
+ } else {
+ serverSocket.accept();
+ logger.error("Socket connection cannot be established as the server has reached its maximum socket client connections capacity.");
+ }
+ } catch (IOException | NullSocketConnectorException | NullBlockingQueueException e) {
+ logger.error("Failed to process connection", e);
+ }
+ }
+ }
+
+ /**
+ * Stop the serverSocket.
+ *
+ * @throws IOException On socket closure failing.
+ *
+ */
+ public void stop() throws IOException {
+ if (serverSocket != null && !serverSocket.isClosed()) {
+ serverSocket.close();
+ }
+ }
+
+ /**
+ * Check the current serverSocket status.
+ *
+ * @return The an enum {@link ServerStatus} stating the current status.
+ *
+ */
+ public ServerStatus status() {
+ return serverSocket == null || serverSocket.isClosed()
+ ? ServerStatus.STOPPED
+ : ServerStatus.STARTED;
+ }
+
+ /**
+ * Get the current number of clients connected to the socket.
+ *
+ * @return The connect clients number.
+ *
+ */
+ public static int getSocketClientsConnected(){
+ return socketClientsConnected;
+ }
+
+ /**
+ * Set the current number of clients connected to the socket.
+ *
+ * @param scc The connect clients number.
+ *
+ */
+ public static void setSocketClientConnected(int scc){
+ socketClientsConnected = scc;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server/index.html
new file mode 100644
index 00000000..73f7b0c0
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.server/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.server/index.source.html
new file mode 100644
index 00000000..62cd54a6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.server/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.server
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfig.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfig.html
new file mode 100644
index 00000000..9a4aa385
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfig.html
@@ -0,0 +1 @@
+AppConfig
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfig.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfig.java.html
new file mode 100644
index 00000000..b0dde649
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfig.java.html
@@ -0,0 +1,323 @@
+AppConfig.java
package it.polimi.is23am10.utils.config;
+
+import it.polimi.is23am10.utils.config.exceptions.InvalidMaxConnectionsNumberException;
+import it.polimi.is23am10.utils.config.exceptions.InvalidPortNumberException;
+
+/**
+ * The application config class definition.
+ * If not set otherwise all the configuration parameters for the application
+ * are set to default values here.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public final class AppConfig {
+
+ /**
+ * Private constructor.
+ *
+ */
+ private AppConfig() {
+
+ }
+
+ /**
+ * The lower bound of port numbers.
+ *
+ */
+ public static final Integer MIN_PORT_NUMBER = 1024;
+
+ /**
+ * The upper bound of port numbers.
+ *
+ */
+ public static final Integer MAX_PORT_NUMBER = 65535;
+
+ /**
+ * The lower bound of connection numbers.
+ *
+ */
+ public static final Integer MIN_CONNECTIONS_NUMBER = 0;
+
+ /**
+ * The upper bound of connection numbers.
+ *
+ */
+ public static final Integer MAX_CONNECTIONS_NUMBER = 10;
+
+ /**
+ * The server config server port.
+ *
+ */
+ private static Integer serverSocketPort = 9001;
+
+ /**
+ * The server config rmi port.
+ *
+ */
+ private static Integer serverRmiPort = 9002;
+
+ /**
+ * The max allowed connection for this server instance.
+ * This is a momentary max value.
+ *
+ */
+ private static Integer maxConnection = 8;
+
+ /**
+ * The socket SO_KEEPALIVE flag.
+ *
+ */
+ private static boolean keepAlive = true;
+
+ /**
+ * The kind of app to launch. If false launches a client.
+ *
+ */
+ private static boolean isServer = false;
+
+ /**
+ * The interface to show if app is launched in Client mode.
+ * If false launches client in CLI mode.
+ * If {@link AppConfigContext#isServer} is true this flag is ignored,
+ * as server mode only supports CLI as interface, and for logging purpose only.
+ */
+ private static boolean showGUI = false;
+
+ /**
+ * The communication method to use if app is launched is Client Mode.
+ * If false launches client over Socket connection.
+ * If {@link AppConfigContext#isServer} is true this flag is ignored,
+ * as server mode both receives RMI and socket connections
+ */
+ private static boolean useRMI = false;
+
+ /**
+ * A simple flag that if enabled shows all verbose messages
+ * in console logs.
+ */
+ private static boolean showDebug = false;
+
+ /**
+ * The network address where the server is running.
+ *
+ */
+ private static String serverAddress = "localhost";
+
+ /**
+ * The maximum inactivity time allowed to a player when playing a game turn.
+ */
+ private static long maxInactivityTimeMs = 1000 * 60 * 2;
+
+ /**
+ * Server port setter.
+ *
+ * @param p port number.
+ * @throws InvalidPortNumberException invalid port number.
+ */
+ public static void setServerSocketPort(Integer p) throws InvalidPortNumberException {
+ if (p < MIN_PORT_NUMBER || p > MAX_PORT_NUMBER) {
+ throw new InvalidPortNumberException();
+ }
+ serverSocketPort = p;
+ }
+
+ /**
+ * Server port setter.
+ *
+ * @param p port number.
+ * @throws InvalidPortNumberException invalid port number.
+ */
+ public static void setServerRmiPort(Integer p) throws InvalidPortNumberException {
+ if (p < MIN_PORT_NUMBER || p > MAX_PORT_NUMBER) {
+ throw new InvalidPortNumberException();
+ }
+ serverRmiPort = p;
+ }
+
+ /**
+ * Max connections setter.
+ *
+ * @param maxConn number of max connections.
+ * @throws InvalidMaxConnectionsNumberException invalid number of maxConn.
+ */
+ public static void setMaxConnections(Integer maxConn)
+ throws InvalidMaxConnectionsNumberException {
+ if (maxConn < MIN_CONNECTIONS_NUMBER || maxConn > MAX_CONNECTIONS_NUMBER){
+ throw new InvalidMaxConnectionsNumberException();
+ }
+ maxConnection = maxConn;
+ }
+
+ /**
+ * Keep alive setter.
+ *
+ * @param k keep alive status.
+ *
+ */
+ public static void setKeepAlive(boolean k) {
+ keepAlive = k;
+ }
+
+ /**
+ * Keep alive setter.
+ *
+ * @param is launch server app
+ *
+ */
+ public static void setIsServer(boolean is) {
+ isServer = is;
+ }
+
+ /**
+ * Show GUI setter.
+ *
+ * @param sg show GUI if client
+ *
+ */
+ public static void setShowGUI(boolean sg) {
+ showGUI = sg;
+ }
+
+ /**
+ * Use RMI setter.
+ *
+ * @param rmi use RMI or not (socket).
+ *
+ */
+ public static void setUseRMI(boolean rmi) {
+ useRMI = rmi;
+ }
+
+ /**
+ * Show verbose debug.
+ *
+ * @param debug show debug or not.
+ *
+ */
+ public static void setShowDebug(boolean debug) {
+ showDebug = debug;
+ }
+
+ /**
+ * Server address setter.
+ *
+ * @param address Server address.
+ *
+ */
+ public static void setServerAddress(String address) {
+ serverAddress = address;
+ }
+
+ /**
+ * Max inactivity time setter.
+ *
+ * @param time max inactivity time in ms.
+ */
+ public static void setMaxInactivityTimeMs(long time) {
+ maxInactivityTimeMs = time;
+ }
+
+ /**
+ * Server port getter.
+ *
+ * @return The instantiated server port.
+ *
+ */
+ public static Integer getServerSocketPort() {
+ return serverSocketPort;
+ }
+
+ /**
+ * Server port getter.
+ *
+ * @return The instantiated server port.
+ *
+ */
+ public static Integer getServerRmiPort() {
+ return serverRmiPort;
+ }
+
+ /**
+ * Max connections getter.
+ *
+ * @return The maximum allowed connections.
+ *
+ */
+ public static Integer getMaxConnections() {
+ return maxConnection;
+ }
+
+ /**
+ * Keep alive getter.
+ *
+ * @return The keep alive flag.
+ *
+ */
+ public static boolean getKeepAlive() {
+ return keepAlive;
+ }
+
+ /**
+ * Is server getter.
+ *
+ * @return The isServer flag.
+ *
+ */
+ public static boolean getIsServer() {
+ return isServer;
+ }
+
+ /**
+ * Show GUI getter.
+ *
+ * @return The show GUI flag.
+ *
+ */
+ public static boolean getShowGUI() {
+ return showGUI;
+ }
+
+ /**
+ * Use RMI getter.
+ *
+ * @return The use RMI flag.
+ *
+ */
+ public static boolean getUseRMI() {
+ return useRMI;
+ }
+
+ /**
+ * Server address getter.
+ *
+ * @return The server address.
+ *
+ */
+ public static String getServerAddress() {
+ return serverAddress;
+ }
+
+ /**
+ * Show debug getter.
+ *
+ * @return The debug flag.
+ *
+ */
+ public static boolean getShowDebug() {
+ return showDebug;
+ }
+
+ /**
+ * Max inactivity time getter.
+ *
+ * @return The max inactivity time.
+ *
+ */
+ public static long getMaxInactivityTime() {
+ return maxInactivityTimeMs;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfigContext.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfigContext.html
new file mode 100644
index 00000000..c80ba7cf
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfigContext.html
@@ -0,0 +1 @@
+AppConfigContext
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfigContext.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfigContext.java.html
new file mode 100644
index 00000000..46ffda91
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/AppConfigContext.java.html
@@ -0,0 +1,220 @@
+AppConfigContext.java
package it.polimi.is23am10.utils.config;
+
+/**
+ * The server config context class definition.
+ *
+ */
+public class AppConfigContext {
+
+ /**
+ * The server socket port.
+ *
+ */
+ private Integer serverSocketPort;
+
+
+ /**
+ * The server rmi port.
+ *
+ */
+ private Integer serverRmiPort;
+
+ /**
+ * The max allowed connection for this server instance.
+ * This is a momentary max value.
+ *
+ */
+ private Integer maxConnection;
+
+ /**
+ * The socket SO_KEEPALIVE flag.
+ *
+ */
+ private boolean keepAlive;
+
+ /**
+ * The kind of app to launch. If false launches a client.
+ *
+ */
+ private boolean isServer;
+
+ /**
+ * The interface to show if app is launched in Client mode.
+ * If false launches client in CLI mode.
+ * If {@link AppConfigContext#isServer} is true this flag is ignored,
+ * as server mode only supports CLI as interface, and for logging purpose only.
+ */
+ private boolean showGUI;
+
+ /**
+ * The communication method to use if app is launched is Client Mode.
+ * If false launches client over Socket connection.
+ * If {@link AppConfigContext#isServer} is true this flag is ignored,
+ * as server mode both receives RMI and socket connections
+ */
+ private boolean useRMI;
+
+ /**
+ * The network address where the server is running.
+ *
+ */
+ private String serverAddress;
+
+ /**
+ * A simple flag that if enabled shows all verbose messages
+ * in console logs.
+ */
+ private boolean showDebug;
+
+ /**
+ * The maximum inactivity time allowed to a player when playing a game turn.
+ */
+ private long maxInactivityTimeMs;
+
+ /**
+ * Constructor.
+ *
+ * @param serverSocketPort The server port number.
+ * @param maxConnections The maximum allowed connections.
+ * @param keepAlive The socket keep alive flag.
+ * @param serverRmiPort The server RMI port.
+ * @param isServer Run app in server mode flag.
+ * @param showGUI Show GUI client flag.
+ * @param useRMI Use RMI over socket flag.
+ * @param serverAddress Address where to find server.
+ * @param showDebug Show debug infos flag.
+ */
+ public AppConfigContext(Integer serverSocketPort, Integer serverRmiPort,
+ Integer maxConnections, boolean keepAlive, boolean isServer, boolean showGUI, boolean useRMI, String serverAddress, boolean showDebug, long maxInactivityTimeMs) {
+ this.serverSocketPort = serverSocketPort;
+ this.serverRmiPort = serverRmiPort;
+ this.maxConnection = maxConnections;
+ this.keepAlive = keepAlive;
+ this.isServer = isServer;
+ this.showGUI = showGUI;
+ this.useRMI = useRMI;
+ this.serverAddress = serverAddress;
+ this.showDebug = showDebug;
+ this.maxInactivityTimeMs = maxInactivityTimeMs;
+ }
+
+
+ /**
+ * Constructor with default values.
+ *
+ *
+ */
+ public AppConfigContext() {
+ this.serverSocketPort = AppConfig.getServerSocketPort();
+ this.serverRmiPort = AppConfig.getServerRmiPort();
+ this.maxConnection = AppConfig.getMaxConnections();
+ this.keepAlive = AppConfig.getKeepAlive();
+ this.isServer = AppConfig.getIsServer();
+ this.showGUI = AppConfig.getShowGUI();
+ this.useRMI = AppConfig.getUseRMI();
+ this.serverAddress = AppConfig.getServerAddress();
+ this.showDebug = AppConfig.getShowDebug();
+ this.maxInactivityTimeMs = AppConfig.getMaxInactivityTime();
+ }
+
+ /**
+ * Server socket port getter.
+ *
+ * @return The instantiated server socket port.
+ *
+ */
+ public Integer getServerSocketPort() {
+ return serverSocketPort;
+ }
+
+ /**
+ * Server rmi port getter.
+ *
+ * @return The instantiated server rmi port.
+ *
+ */
+ public Integer getServerRmiPort() {
+ return serverRmiPort;
+ }
+
+ /**
+ * Max connections getter.
+ *
+ * @return The maximum allowed connections.
+ *
+ */
+ public Integer getMaxConnections() {
+ return maxConnection;
+ }
+
+ /**
+ * Keep alive getter.
+ *
+ * @return The keep alive flag.
+ *
+ */
+ public boolean getKeepAlive() {
+ return keepAlive;
+ }
+
+ /**
+ * Is server getter.
+ *
+ * @return The isServer flag.
+ *
+ */
+ public boolean getIsServer() {
+ return isServer;
+ }
+
+ /**
+ * Show GUI getter.
+ *
+ * @return The show GUI flag.
+ *
+ */
+ public boolean getShowGUI() {
+ return showGUI;
+ }
+
+ /**
+ * Use RMI getter.
+ *
+ * @return The use RMI flag.
+ *
+ */
+ public boolean getUseRMI() {
+ return useRMI;
+ }
+
+ /**
+ * Server address getter.
+ *
+ * @return The server address.
+ *
+ */
+ public String getServerAddress() {
+ return serverAddress;
+ }
+
+ /**
+ * Show debug getter.
+ *
+ * @return The debug flag.
+ *
+ */
+ public boolean getShowDebug() {
+ return showDebug;
+ }
+
+ /**
+ * Max inactivity time getter.
+ *
+ * @return The max inactivity time value in ms.
+ *
+ */
+ public long getMaxInactivityTime() {
+ return maxInactivityTimeMs;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/index.html
new file mode 100644
index 00000000..1fa0ad2e
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.utils.config
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/index.source.html
new file mode 100644
index 00000000..f9eac0dc
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils.config/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.utils.config
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ArgParser.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ArgParser.html
new file mode 100644
index 00000000..e26a9499
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ArgParser.html
@@ -0,0 +1 @@
+ArgParser
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ArgParser.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ArgParser.java.html
new file mode 100644
index 00000000..429ff9c7
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ArgParser.java.html
@@ -0,0 +1,174 @@
+ArgParser.java
package it.polimi.is23am10.utils;
+
+import it.polimi.is23am10.utils.config.AppConfig;
+import it.polimi.is23am10.utils.config.exceptions.InvalidMaxConnectionsNumberException;
+import it.polimi.is23am10.utils.config.exceptions.InvalidPortNumberException;
+import it.polimi.is23am10.utils.exceptions.InvalidArgumentException;
+import it.polimi.is23am10.utils.exceptions.MissingParameterException;
+
+import java.util.regex.Pattern;
+
+/**
+ * Parser for argument from CLI.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class ArgParser {
+
+ /**
+ * Private constructor of argument parser.
+ */
+ private ArgParser() {
+ }
+
+ /**
+ * The socket port cli command.
+ */
+ public static final String SOCKET_PORT_CLI_COMMAND = "--socket-port";
+
+ /**
+ * The rmi port cli command.
+ */
+ public static final String RMI_PORT_CLI_COMMAND = "--rmi-port";
+
+ /**
+ * The max connections cli command.
+ */
+ public static final String MAX_CONNECTIONS_CLI_COMMAND = "--max-connections";
+
+ /**
+ * The keep alive cli command.
+ */
+ public static final String KEEP_ALIVE_CLI_COMMAND = "--keep-alive";
+
+ /**
+ * The is server command.
+ */
+ public static final String IS_SERVER_CLI_COMMAND = "--is-server";
+
+ /**
+ * The show gui cli command.
+ */
+ public static final String SHOW_GUI_CLI_COMMAND = "--show-gui";
+
+ /**
+ * The use rmi cli command.
+ */
+ public static final String USE_RMI_CLI_COMMAND = "--use-rmi";
+
+ /**
+ * The server address cli command.
+ */
+ public static final String SERVER_ADDRESS_CLI_COMMAND = "--address";
+
+ /**
+ * The debug command. If enabled shows all messages in console.
+ */
+ public static final String DEBUG_CLI_COMMAND = "--debug";
+
+ /**
+ * The max players inactivity time command.
+ */
+ public static final String MAX_CLIENT_INACTIVITY_TIME = "--max-player-inactive-ms";
+
+ /**
+ * Regex expression for validating ipv4 addresses
+ */
+ private static final String IPV4_REGEX =
+ "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
+
+ /**
+ * Argument parser method that checks commands.
+ *
+ * @param args arguments passed through cli.
+ * @throws InvalidArgumentException for invalid command.
+ * @throws MissingParameterException for missing param.
+ * @throws InvalidPortNumberException if port not in bounds.
+ * @throws NumberFormatException if int not found.
+ * @throws InvalidMaxConnectionsNumberException if the max number of connections is invalid.
+ *
+ */
+ public static void parse(String[] args)
+ throws InvalidArgumentException, MissingParameterException, NumberFormatException,
+ InvalidPortNumberException, InvalidMaxConnectionsNumberException {
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ switch (args[i]) {
+ case SOCKET_PORT_CLI_COMMAND:
+ if (i + 1 < args.length) {
+ AppConfig.setServerSocketPort(Integer.parseInt(args[i + 1]));
+ i++;
+ } else {
+ throw new MissingParameterException(args[i]);
+ }
+ break;
+ case RMI_PORT_CLI_COMMAND:
+ if (i + 1 < args.length) {
+ AppConfig.setServerRmiPort(Integer.parseInt(args[i + 1]));
+ i++;
+ } else {
+ throw new MissingParameterException(args[i]);
+ }
+ break;
+ case MAX_CONNECTIONS_CLI_COMMAND:
+ if (i + 1 < args.length) {
+ AppConfig.setMaxConnections(Integer.parseInt(args[i + 1]));
+ i++;
+ } else {
+ throw new MissingParameterException(args[i]);
+ }
+ break;
+ case KEEP_ALIVE_CLI_COMMAND:
+ if (i + 1 < args.length) {
+ AppConfig.setKeepAlive(Boolean.parseBoolean(args[i + 1]));
+ i++;
+ } else {
+ throw new MissingParameterException(args[i]);
+ }
+ break;
+ case SERVER_ADDRESS_CLI_COMMAND:
+ if (i + 1 < args.length) {
+ if (Pattern.matches(IPV4_REGEX, args[i + 1])){
+ AppConfig.setServerAddress(args[i + 1]);
+ i++;
+ } else {
+ throw new InvalidArgumentException("Address is not a valid IPV4. For localhost omit flag.");
+ }
+ } else {
+ throw new MissingParameterException(args[i]);
+ }
+ break;
+ case IS_SERVER_CLI_COMMAND:
+ AppConfig.setIsServer(true);
+ break;
+ case SHOW_GUI_CLI_COMMAND:
+ AppConfig.setShowGUI(true);
+ break;
+ case USE_RMI_CLI_COMMAND:
+ AppConfig.setUseRMI(true);
+ break;
+ case DEBUG_CLI_COMMAND:
+ AppConfig.setShowDebug(true);
+ break;
+ case MAX_CLIENT_INACTIVITY_TIME:
+ if (i + 1 < args.length) {
+ AppConfig.setMaxInactivityTimeMs(Long.parseLong(args[i + 1]));
+ i++;
+ } else {
+ throw new MissingParameterException(args[i]);
+ }
+ break;
+ default:
+ throw new InvalidArgumentException(args[i]);
+ }
+ }
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CommandSyntaxValidator.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CommandSyntaxValidator.html
new file mode 100644
index 00000000..c7cf48b7
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CommandSyntaxValidator.html
@@ -0,0 +1 @@
+CommandSyntaxValidator
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CommandSyntaxValidator.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CommandSyntaxValidator.java.html
new file mode 100644
index 00000000..a86229ee
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CommandSyntaxValidator.java.html
@@ -0,0 +1,118 @@
+CommandSyntaxValidator.java
package it.polimi.is23am10.utils;
+
+/**
+ * Helper class to validate user input strings.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class CommandSyntaxValidator {
+
+ /**
+ * Max number of players.
+ */
+ private final static int MAX_PLAYERS_LIMIT = 4;
+
+ /**
+ * Min number of players.
+ */
+ private final static int MIN_PLAYERS_LIMIT = 2;
+
+ /**
+ * Empty constructor.
+ */
+ private CommandSyntaxValidator() {
+ }
+
+ /**
+ * Method validator of game index input.
+ *
+ * @param s string with game index chosen.
+ * @param n index max.
+ * @return true if valid.
+ */
+ public static boolean validateGameIdx(String s, Integer n) {
+ // Null parameters
+ if (s == null || n == null) {
+ return false;
+ }
+ // Index out of bounds
+ try {
+ if (Integer.parseInt(s) < 0 || Integer.parseInt(s) >= n) {
+ return false;
+ }
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Method validator of max player input.
+ *
+ * @param s string with game index chosen.
+ * @return true if valid.
+ */
+ public static boolean validateMaxPlayer(String s) {
+ // Null parameter
+ if (s == null) {
+ return false;
+ }
+ // Not a number
+ if (!s.chars().allMatch(Character::isDigit)){
+ return false;
+ }
+ // Index out of bounds
+ if (Integer.parseInt(s) < MIN_PLAYERS_LIMIT || Integer.parseInt(s) > MAX_PLAYERS_LIMIT) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Method validator of coordinates input.
+ *
+ * @param s string with game index chosen.
+ * @return true if valid.
+ */
+ public static boolean validateCoord(String s) {
+
+ // Null parameter or invalid length
+ if (s == null || s.length() != 2) {
+ return false;
+ }
+
+ // First and second char are digits
+ if (s.substring(0, 0).chars().allMatch(Character::isDigit)
+ || s.substring(1, 1).chars().allMatch(Character::isDigit)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method validator of column index input.
+ *
+ * @param s string with col index chosen.
+ * @return true if valid.
+ */
+ public static boolean validateColIdx(String s) {
+
+ // Null parameter or invalid length
+ if (s == null || s.length() != 1) {
+ return false;
+ }
+
+
+ // Col index is one of the possible ones.
+ if ( s.charAt(0) >= 'A' &&
+ s.charAt(0) <= 'E' ) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/Coordinates.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/Coordinates.html
new file mode 100644
index 00000000..5a2423c6
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/Coordinates.html
@@ -0,0 +1 @@
+Coordinates
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/Coordinates.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/Coordinates.java.html
new file mode 100644
index 00000000..c3d9e114
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/Coordinates.java.html
@@ -0,0 +1,119 @@
+Coordinates.java
package it.polimi.is23am10.utils;
+
+import java.io.Serializable;
+
+/**
+ * The Coordinate helper class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ *
+ */
+public class Coordinates implements Serializable {
+
+ /**
+ * The row index of the coordinates.
+ *
+ */
+ private Integer row;
+
+ /**
+ * The column index of the coordinates.
+ *
+ */
+ private Integer col;
+
+ /**
+ * Constructor of Coordinates given the two indexes.
+ *
+ * @param r row index.
+ * @param c column index.
+ */
+ public Coordinates(Integer r, Integer c) {
+ this.row = r;
+ this.col = c;
+ }
+
+ /**
+ * Empty constructor that sets the coordinates in the origin.
+ *
+ */
+ public Coordinates() {
+ this.row = 0;
+ this.col = 0;
+ }
+
+ /**
+ * Row getter.
+ *
+ * @return row index.
+ */
+ public Integer getRow() {
+ return row;
+ }
+
+ /**
+ * Column getter.
+ *
+ * @return column index.
+ */
+ public Integer getCol() {
+ return col;
+ }
+
+ /**
+ * Row setter.
+ *
+ * @param r row index.
+ */
+ public void setRow(Integer r) {
+ this.row = r;
+ }
+
+ /**
+ * Column setter
+ *
+ * @param c column index.
+ */
+ public void setCol(Integer c) {
+ this.col = c;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Coordinates)) {
+ return false;
+ }
+
+ Coordinates coordinates = (Coordinates) obj;
+ return (coordinates.getCol() == col && coordinates.getRow() == row);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ @Override
+ public int hashCode() {
+ /*
+ * A simple hashing function that gives me a unique number given two integers.
+ * https://stackoverflow.com/questions/22826326/good-hashcode-function-for-2d-
+ * coordinates
+ */
+
+ int tmp = (this.col + ((this.row + 1) / 2));
+ return this.row + (tmp * tmp);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s", row, col);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CustomKeyDeserializer.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CustomKeyDeserializer.html
new file mode 100644
index 00000000..c81178c2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CustomKeyDeserializer.html
@@ -0,0 +1 @@
+CustomKeyDeserializer
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CustomKeySerializer.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CustomKeySerializer.html
new file mode 100644
index 00000000..ac98fd7a
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/CustomKeySerializer.html
@@ -0,0 +1 @@
+CustomKeySerializer
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/IndexValidator.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/IndexValidator.html
new file mode 100644
index 00000000..08d5df86
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/IndexValidator.html
@@ -0,0 +1 @@
+IndexValidator
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/IndexValidator.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/IndexValidator.java.html
new file mode 100644
index 00000000..077a19dd
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/IndexValidator.java.html
@@ -0,0 +1,55 @@
+IndexValidator.java
package it.polimi.is23am10.utils;
+
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+
+/**
+ * Index validator class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ *
+ */
+public final class IndexValidator {
+
+ /**
+ * Private constructor.
+ *
+ */
+ private IndexValidator() {
+
+ }
+
+ /**
+ * Validate the row index.
+ *
+ * @param row The index value to be evaluated.
+ * @return The validation result.
+ * @throws NullIndexValueException If the index provided is null.
+ *
+ */
+ public static boolean validRowIndex(Integer row, Integer maxValue) throws NullIndexValueException {
+ if (row == null) {
+ throw new NullIndexValueException();
+ }
+ return row >= 0 && row < maxValue;
+ }
+
+ /**
+ * Validate the column index.
+ *
+ * @param col The index value to be evaluated.
+ * @return The validation result.
+ * @throws NullIndexValueException If the index provided is null.
+ *
+ */
+ public static boolean validColIndex(Integer col, Integer maxValue) throws NullIndexValueException {
+ if (col == null) {
+ throw new NullIndexValueException();
+ }
+ return col >= 0 && col < maxValue;
+ }
+
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveCommandHelper.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveCommandHelper.html
new file mode 100644
index 00000000..be01bf4a
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveCommandHelper.html
@@ -0,0 +1 @@
+MoveCommandHelper
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveCommandHelper.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveCommandHelper.java.html
new file mode 100644
index 00000000..66ab5ae2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveCommandHelper.java.html
@@ -0,0 +1,65 @@
+MoveCommandHelper.java
package it.polimi.is23am10.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.tile.Tile.TileType;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import it.polimi.is23am10.utils.exceptions.WrongBookShelfPicksException;
+
+/**
+ * Helper class to convert input move from index to coordinates.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ */
+public class MoveCommandHelper {
+
+ /**
+ * Private empty constructor for the class.
+ */
+ private MoveCommandHelper() {
+ }
+
+ /**
+ * Method that takes the column input of the player bookshelf and returns the list of the
+ * first available empty coordinates.
+ *
+ * @param idx column index.
+ * @param bs player bookshelf.
+ * @param nMoves number of moves made and of empty coords to check.
+ *
+ * @return list of first available empty coordinates of the bookshelf
+ * @throws BookshelfGridColIndexOutOfBoundsException
+ * @throws BookshelfGridRowIndexOutOfBoundsException
+ * @throws NullIndexValueException
+ * @throws WrongBookShelfPicksException
+ */
+ public static List<Coordinates> fromColIdxToCoord(String idx, Bookshelf bs , Integer nMoves) throws BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException, NullIndexValueException, WrongBookShelfPicksException{
+ List<Coordinates> bsCoord = new ArrayList<>();
+ Integer columnIdx = idx.charAt(0) - 'A';
+ Integer rowCount = 0;
+ // If I got to the tile on top and there's no space left
+ if(bs.getFreeRowsInCol(columnIdx) < nMoves){
+ throw new WrongBookShelfPicksException(
+ "Invalid bookshelf picks, selected a column with no sufficient space");
+ }
+
+ for(int row = bs.getBookshelfGrid()[0].length ; rowCount < nMoves && row >= 0 ; row--){
+ if(bs.getBookshelfGridAt(row, columnIdx).getType() == TileType.EMPTY){
+ bsCoord.add(new Coordinates(row, columnIdx));
+ rowCount++;
+ }
+ }
+
+ return bsCoord;
+ }
+
+
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveTileCommandTypeAdaptor.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveTileCommandTypeAdaptor.html
new file mode 100644
index 00000000..293294b2
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveTileCommandTypeAdaptor.html
@@ -0,0 +1 @@
+MoveTileCommandTypeAdaptor
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveTileCommandTypeAdaptor.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveTileCommandTypeAdaptor.java.html
new file mode 100644
index 00000000..3fcf3c6a
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MoveTileCommandTypeAdaptor.java.html
@@ -0,0 +1,76 @@
+MoveTileCommandTypeAdaptor.java
package it.polimi.is23am10.utils;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.util.HashMap;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+
+/**
+ * Internal used class.
+ *
+ */
+class CustomKeySerializer implements JsonSerializer<Coordinates> {
+ @Override
+ public JsonElement serialize(Coordinates src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("row", src.getRow());
+ obj.addProperty("col", src.getCol());
+ return obj;
+ }
+}
+
+/**
+ * Internal used class.
+ *
+ */
+class CustomKeyDeserializer implements JsonDeserializer<Coordinates> {
+ @Override
+ public Coordinates deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject obj = json.getAsJsonObject();
+ int row = obj.get("row").getAsInt();
+ int col = obj.get("col").getAsInt();
+ return new Coordinates(row, col);
+ }
+}
+
+/**
+ * Custom deserializer class definition for Gson usage.
+ * This works {@link Map} with {@link Coordinates} as keys.
+ *
+ */
+public class MoveTileCommandTypeAdaptor implements JsonSerializer<Map<Coordinates, Coordinates>>, JsonDeserializer<Map<Coordinates, Coordinates>> {
+ @Override
+ public JsonElement serialize(Map<Coordinates, Coordinates> src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ for (Map.Entry<Coordinates, Coordinates> entry : src.entrySet()) {
+ obj.add(context.serialize(entry.getKey()).toString(), context.serialize(entry.getValue()));
+ }
+ return obj;
+ }
+
+ @Override
+ public Map<Coordinates, Coordinates> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject obj = json.getAsJsonObject();
+ Map<Coordinates, Coordinates> map = new HashMap<>();
+ for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
+ String keyString = entry.getKey();
+ Coordinates key = context.deserialize((new JsonParser()).parse(keyString), Coordinates.class);
+ Coordinates value = context.deserialize(entry.getValue(), Coordinates.class);
+ map.put(key, value);
+ }
+ return map;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MovesValidator.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MovesValidator.html
new file mode 100644
index 00000000..18076d15
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MovesValidator.html
@@ -0,0 +1 @@
+MovesValidator
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MovesValidator.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MovesValidator.java.html
new file mode 100644
index 00000000..3aff310c
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/MovesValidator.java.html
@@ -0,0 +1,280 @@
+MovesValidator.java
package it.polimi.is23am10.utils;
+
+import it.polimi.is23am10.server.model.items.board.Board;
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.board.exceptions.BoardGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.Bookshelf;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridColIndexOutOfBoundsException;
+import it.polimi.is23am10.server.model.items.bookshelf.exceptions.BookshelfGridRowIndexOutOfBoundsException;
+import it.polimi.is23am10.utils.exceptions.NullIndexValueException;
+import it.polimi.is23am10.utils.exceptions.WrongBookShelfPicksException;
+import it.polimi.is23am10.utils.exceptions.WrongGameBoardPicksException;
+import it.polimi.is23am10.utils.exceptions.WrongMovesNumberException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * The game movement validator class definition.
+ *
+ * @author Alessandro Amandonico (alessandro.amandonico@mail.polimi.it)
+ * @author Francesco Buccoliero (francesco.buccoliero@mail.polimi.it)
+ * @author Kaixi Matteo Chen (kaiximatteo.chen@mail.polimi.it)
+ * @author Lorenzo Cavallero (lorenzo1.cavallero@mail.polimi.it)
+ *
+ */
+public class MovesValidator {
+
+ /**
+ * Private constructor.
+ *
+ */
+ private MovesValidator() {
+
+ }
+
+ /**
+ * Checks if the given coordinate is in the zero index.
+ *
+ * @param c the coordinate to be checked
+ * @return true if the coordinate is in the zero index, false otherwise
+ */
+ private static boolean isCoordinateInZeroIndex(Coordinates c) {
+ return c.getCol() == 0 || c.getRow() == 0;
+ }
+
+ /**
+ * Checks if the given coordinate is in the max index.
+ *
+ * @param c the coordinate to be checked
+ * @return true if the coordinate is in the max index, false otherwise
+ */
+ private static boolean isCoordinateInMaxIndex(Coordinates c) {
+ return c.getCol() == Board.BOARD_GRID_COLS - 1 || c.getRow() == Board.BOARD_GRID_ROWS - 1;
+ }
+
+ /**
+ * Checks if any side of the tile in the given coordinate is empty.
+ *
+ * @param c the coordinate to be checked
+ * @param board the game board to check for empty tiles
+ * @return true if there's an empty side, false otherwise
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds. if the row index is out of
+ * bounds
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds. if the column index is out of
+ * bounds
+ * @throws NullIndexValueException If the index provided is null. if the value at the given index
+ * is null
+ */
+ private static boolean hasEmptySide(Coordinates c, Board board)
+ throws BoardGridRowIndexOutOfBoundsException, BoardGridColIndexOutOfBoundsException,
+ NullIndexValueException {
+ return board.getTileAt(c.getRow() - 1, c.getCol()).isEmpty()
+ || board.getTileAt(c.getRow() + 1, c.getCol()).isEmpty()
+ || board.getTileAt(c.getRow(), c.getCol() - 1).isEmpty()
+ || board.getTileAt(c.getRow(), c.getCol() + 1).isEmpty();
+ }
+
+ /**
+ * Checks if the values in the given list are adjacent to each other.
+ *
+ * @param values the list of values to be checked. Must be pre-sorted for this to work.
+ * @return true if the values are adjacent, false otherwise
+ */
+ private static boolean areAdjacent(List<Integer> values) {
+ for (Integer i = 0; i < values.size() - 1; i++) {
+ if (values.get(i + 1) - values.get(i) != 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the lowest row value is greater than or equal to the number of rows
+ * in the bookshelf.
+ *
+ * @param lowestRow the lowest row value
+ * @return true if the lowest row value is greater than or equal to the number
+ * of rows in the
+ * bookshelf, false otherwise
+ */
+ private static boolean placeFromBottom(Integer lowestRow) {
+ return lowestRow >= Bookshelf.BOOKSHELF_ROWS - 1;
+ }
+
+ /**
+ * Validates the bookshelf picks by checking if they're valid.
+ *
+ * @param moves the map of coordinates representing the bookshelf picks
+ * @param bookShelf the bookshelf to check the picks against
+ * @throws WrongBookShelfPicksException If the game moves are invalid because of bookshelf placement. if the bookshelf picks are
+ * invalid
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds. if the column index is out
+ * of bounds
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds. if the row index is out of
+ * bounds
+ * @throws NullIndexValueException If the index provided is null. if the value at the given
+ * index is null
+ */
+ protected static void validateBookShelfPicks(
+ Collection<Coordinates> moves, Bookshelf bookShelf)
+ throws WrongBookShelfPicksException, BookshelfGridColIndexOutOfBoundsException,
+ BookshelfGridRowIndexOutOfBoundsException, NullIndexValueException {
+
+ // check different picked columns, they have to be 1
+ long columns = moves.stream().map(e -> e.getCol()).distinct().count();
+ if (columns > 1) {
+ throw new WrongBookShelfPicksException(
+ "Invalid bookshelf picks, selected " + columns + " columns. Only one allowed");
+ }
+
+ // we can safely get the first column value as they are all the same
+ final Integer columnValue = moves.iterator().next().getCol();
+ // check the empty rows inside the selected column
+ if (bookShelf.getFreeRowsInCol(columnValue) < moves.size()) {
+ throw new WrongBookShelfPicksException(
+ "Invalid bookshelf picks, selected a column with no sufficient space");
+ }
+
+ // check that the selected spots inside the bookshelf are adjacent
+ List<Integer> rowValues = moves
+ .stream()
+ .map(e -> e.getRow())
+ .sorted()
+ .collect(Collectors.toList());
+ if (!areAdjacent(rowValues)) {
+ throw new WrongBookShelfPicksException(
+ "Invalid bookshelf picks, selected not sequential rows");
+ }
+
+ // check that the lowest tile to put into the bookshelf has a non empty tile
+ // under it
+ // or if it is the first tile in the column, check that the lowest spot is empty
+ Integer lowestRow = rowValues.get(rowValues.size() - 1);
+ if (!placeFromBottom(lowestRow)
+ && bookShelf.getBookshelfGridAt(lowestRow + 1, columnValue).isEmpty()) {
+ throw new WrongBookShelfPicksException("Invalid bookshelf picks, gap between tiles");
+ } else if (placeFromBottom(lowestRow)
+ && !bookShelf.getBookshelfGridAt(lowestRow, columnValue).isEmpty()) {
+ throw new WrongBookShelfPicksException(
+ "Invalid bookshelf picks, bookshelf position not empty");
+ }
+ }
+
+ /**
+ * Validates the game board picks for the given moves and board.
+ *
+ * @param moves the map of coordinates to coordinates representing the moves
+ * made on the board
+ * @param board the game board to validate the moves on
+ * @throws WrongGameBoardPicksException If the game moves are invalid because of board picking. if the picks made on the board
+ * are invalid
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds. if a row index in the moves is
+ * out of bounds of the board
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds. if a column index in the moves
+ * is out of bounds of the board
+ * @throws NullIndexValueException If the index provided is null. if a null coordinate is present
+ * in the moves
+ */
+ protected static void validateGameBoardPicks(
+ Collection<Coordinates> moves, Board board)
+ throws WrongGameBoardPicksException, BoardGridRowIndexOutOfBoundsException,
+ BoardGridColIndexOutOfBoundsException, NullIndexValueException {
+ // perform pick up check
+ long rowCount = moves
+ .stream()
+ .map(c -> c.getRow())
+ .distinct()
+ .count();
+ long colCount = moves
+ .stream()
+ .map(c -> c.getCol())
+ .distinct()
+ .count();
+
+ if (!(rowCount == 1 || colCount == 1)) {
+ throw new WrongGameBoardPicksException("Invalid tile picks from the game board, detected "
+ + rowCount + "row selection and " + colCount + " column selection");
+ }
+
+ List<Integer> rowValues = moves
+ .stream()
+ .map(e -> e.getRow())
+ .sorted()
+ .collect(Collectors.toList());
+ List<Integer> colValues = moves
+ .stream()
+ .map(e -> e.getCol())
+ .sorted()
+ .collect(Collectors.toList());
+
+ if (!areAdjacent(colValues) && !areAdjacent(rowValues)) {
+ throw new WrongGameBoardPicksException("Invalid pick movement, gap between chosen tiles");
+ }
+
+ // perform checks, every tile must have an empty side
+ for (Coordinates c : moves) {
+ // handle empty tile picks
+ if (board.getTileAt(c.getRow(), c.getCol()).isEmpty()) {
+ throw new WrongGameBoardPicksException("Invalid pick movement, empty tile");
+ }
+
+ // tiles in the first row/col or last row/col have always a side with no tiles
+ if (isCoordinateInZeroIndex(c) || isCoordinateInMaxIndex(c)) {
+ continue;
+ }
+
+ // consumer should handle index out the bounds exception: malformed input
+ if (!hasEmptySide(c, board)) {
+ throw new WrongGameBoardPicksException("Invalid pick movement, the tile at row "
+ + c.getRow() + " and col " + c.getCol() + " has all its sides full");
+ }
+ }
+ }
+
+ /**
+ * Validates the game moves for the given moves, bookshelf and board.
+ *
+ * @param moves the map of coordinates to coordinates representing the moves
+ * made on the board
+ * @param bookshelf the bookshelf to validate the moves on
+ * @param board the game board to validate the moves on
+ * @throws WrongMovesNumberException If the game moves are in an illegal number. if the number of moves made
+ * is less than 1 or greater
+ * than 3
+ * @throws WrongGameBoardPicksException If the game moves are invalid because of board picking. if the picks made on the
+ * board are invalid
+ * @throws BoardGridRowIndexOutOfBoundsException If the board row index is out of bounds. if a row index in the moves
+ * is out of bounds of the
+ * board
+ * @throws BoardGridColIndexOutOfBoundsException If the board column index is out of bounds. if a column index in the
+ * moves is out of bounds of
+ * the board
+ * @throws NullIndexValueException If the index provided is null. if a null coordinate is
+ * present in the moves
+ * @throws BookshelfGridColIndexOutOfBoundsException If the bookshelf column index is out of bounds. if a column index in the
+ * moves is out of bounds of
+ * the bookshelf
+ * @throws BookshelfGridRowIndexOutOfBoundsException If the bookshelf row index is out of bounds. if a row index in the moves
+ * is out of bounds of the
+ * bookshelf
+ * @throws WrongBookShelfPicksException If the game moves are invalid because of bookshelf placement. if the picks made on the
+ * bookshelf are invalid
+ */
+ public static void validateGameMoves(
+ Map<Coordinates, Coordinates> moves, Bookshelf bookshelf, Board board)
+ throws WrongMovesNumberException, BoardGridRowIndexOutOfBoundsException,
+ BoardGridColIndexOutOfBoundsException, WrongGameBoardPicksException, NullIndexValueException,
+ BookshelfGridColIndexOutOfBoundsException, BookshelfGridRowIndexOutOfBoundsException,
+ WrongBookShelfPicksException {
+ if (moves.size() < 1 || moves.size() > 3) {
+ throw new WrongMovesNumberException();
+ }
+
+ validateGameBoardPicks(moves.keySet(), board);
+ validateBookShelfPicks(moves.values(), bookshelf);
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory$1.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory$1.html
new file mode 100644
index 00000000..2aa55d44
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory$1.html
@@ -0,0 +1 @@
+ThreadLocalTypeAdapterFactory.new TypeAdapter() {...}
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory.html
new file mode 100644
index 00000000..6de0b4e7
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory.html
@@ -0,0 +1 @@
+ThreadLocalTypeAdapterFactory
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory.java.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory.java.html
new file mode 100644
index 00000000..0728f042
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/ThreadLocalTypeAdapterFactory.java.html
@@ -0,0 +1,43 @@
+ThreadLocalTypeAdapterFactory.java
package it.polimi.is23am10.utils;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+public class ThreadLocalTypeAdapterFactory implements TypeAdapterFactory {
+ @Override
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+ if (ThreadLocal.class.isAssignableFrom(type.getRawType())) {
+ return new TypeAdapter<T>() {
+ @Override
+ public void write(JsonWriter out, T value) throws IOException {
+ // Write the thread-local value as a string
+ out.value(value.toString());
+ }
+
+ @Override
+ public T read(JsonReader in) throws IOException {
+ // Read the thread-local value as a string
+ String value = in.nextString();
+
+ // Create a new thread-local with the same value
+ ThreadLocal<String> threadLocal = new ThreadLocal<>();
+ threadLocal.set(value);
+
+ @SuppressWarnings("unchecked")
+ T result = (T) threadLocal;
+ return result;
+ }
+ };
+ }
+
+ // For other types, use the default adapter
+ return null;
+ }
+}
+
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/index.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/index.html
new file mode 100644
index 00000000..f4749d98
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/index.html
@@ -0,0 +1 @@
+it.polimi.is23am10.utils
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/it.polimi.is23am10.utils/index.source.html b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/index.source.html
new file mode 100644
index 00000000..1ee30985
--- /dev/null
+++ b/deliverables/coverage/jacoco/it.polimi.is23am10.utils/index.source.html
@@ -0,0 +1 @@
+it.polimi.is23am10.utils
\ No newline at end of file
diff --git a/deliverables/coverage/jacoco/jacoco-resources/branchfc.gif b/deliverables/coverage/jacoco/jacoco-resources/branchfc.gif
new file mode 100644
index 00000000..989b46d3
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/branchfc.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/branchnc.gif b/deliverables/coverage/jacoco/jacoco-resources/branchnc.gif
new file mode 100644
index 00000000..1933e07c
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/branchnc.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/branchpc.gif b/deliverables/coverage/jacoco/jacoco-resources/branchpc.gif
new file mode 100644
index 00000000..cbf711b7
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/branchpc.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/bundle.gif b/deliverables/coverage/jacoco/jacoco-resources/bundle.gif
new file mode 100644
index 00000000..fca9c53e
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/bundle.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/class.gif b/deliverables/coverage/jacoco/jacoco-resources/class.gif
new file mode 100644
index 00000000..eb348fb0
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/class.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/down.gif b/deliverables/coverage/jacoco/jacoco-resources/down.gif
new file mode 100644
index 00000000..440a14db
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/down.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/greenbar.gif b/deliverables/coverage/jacoco/jacoco-resources/greenbar.gif
new file mode 100644
index 00000000..0ba65672
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/greenbar.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/group.gif b/deliverables/coverage/jacoco/jacoco-resources/group.gif
new file mode 100644
index 00000000..a4ea580d
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/group.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/method.gif b/deliverables/coverage/jacoco/jacoco-resources/method.gif
new file mode 100644
index 00000000..7d24707e
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/method.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/package.gif b/deliverables/coverage/jacoco/jacoco-resources/package.gif
new file mode 100644
index 00000000..131c28da
Binary files /dev/null and b/deliverables/coverage/jacoco/jacoco-resources/package.gif differ
diff --git a/deliverables/coverage/jacoco/jacoco-resources/prettify.css b/deliverables/coverage/jacoco/jacoco-resources/prettify.css
new file mode 100644
index 00000000..be5166e0
--- /dev/null
+++ b/deliverables/coverage/jacoco/jacoco-resources/prettify.css
@@ -0,0 +1,13 @@
+/* Pretty printing styles. Used with prettify.js. */
+
+.str { color: #2A00FF; }
+.kwd { color: #7F0055; font-weight:bold; }
+.com { color: #3F5FBF; }
+.typ { color: #606; }
+.lit { color: #066; }
+.pun { color: #660; }
+.pln { color: #000; }
+.tag { color: #008; }
+.atn { color: #606; }
+.atv { color: #080; }
+.dec { color: #606; }
diff --git a/deliverables/coverage/jacoco/jacoco-resources/prettify.js b/deliverables/coverage/jacoco/jacoco-resources/prettify.js
new file mode 100644
index 00000000..b2766fe0
--- /dev/null
+++ b/deliverables/coverage/jacoco/jacoco-resources/prettify.js
@@ -0,0 +1,1510 @@
+// Copyright (C) 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview
+ * some functions for browser-side pretty printing of code contained in html.
+ *
+ *
+ * For a fairly comprehensive set of languages see the
+ * README
+ * file that came with this source. At a minimum, the lexer should work on a
+ * number of languages including C and friends, Java, Python, Bash, SQL, HTML,
+ * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk
+ * and a subset of Perl, but, because of commenting conventions, doesn't work on
+ * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.
+ *
+ * Usage:
+ *
include this source file in an html page via
+ * {@code }
+ *
define style rules. See the example page for examples.
+ *
mark the {@code
} and {@code } tags in your source with
+ * {@code class=prettyprint.}
+ * You can also use the (html deprecated) {@code } tag, but the pretty
+ * printer needs to do more substantial DOM manipulations to support that, so
+ * some css styles may not be preserved.
+ *
+ * That's it. I wanted to keep the API as simple as possible, so there's no
+ * need to specify which language the code is in, but if you wish, you can add
+ * another class to the {@code
} or {@code } element to specify the
+ * language, as in {@code
}. Any class that
+ * starts with "lang-" followed by a file extension, specifies the file type.
+ * See the "lang-*.js" files in this directory for code that implements
+ * per-language file handlers.
+ *
+ * Change log:
+ * cbeust, 2006/08/22
+ *
+ * Java annotations (start with "@") are now captured as literals ("lit")
+ *
+ * @requires console
+ */
+
+// JSLint declarations
+/*global console, document, navigator, setTimeout, window */
+
+/**
+ * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
+ * UI events.
+ * If set to {@code false}, {@code prettyPrint()} is synchronous.
+ */
+window['PR_SHOULD_USE_CONTINUATION'] = true;
+
+/** the number of characters between tab columns */
+window['PR_TAB_WIDTH'] = 8;
+
+/** Walks the DOM returning a properly escaped version of innerHTML.
+ * @param {Node} node
+ * @param {Array.} out output buffer that receives chunks of HTML.
+ */
+window['PR_normalizedHtml']
+
+/** Contains functions for creating and registering new language handlers.
+ * @type {Object}
+ */
+ = window['PR']
+
+/** Pretty print a chunk of code.
+ *
+ * @param {string} sourceCodeHtml code as html
+ * @return {string} code as html, but prettier
+ */
+ = window['prettyPrintOne']
+/** Find all the {@code
} and {@code } tags in the DOM with
+ * {@code class=prettyprint} and prettify them.
+ * @param {Function?} opt_whenDone if specified, called when the last entry
+ * has been finished.
+ */
+ = window['prettyPrint'] = void 0;
+
+/** browser detection. @extern @returns false if not IE, otherwise the major version. */
+window['_pr_isIE6'] = function () {
+ var ieVersion = navigator && navigator.userAgent &&
+ navigator.userAgent.match(/\bMSIE ([678])\./);
+ ieVersion = ieVersion ? +ieVersion[1] : false;
+ window['_pr_isIE6'] = function () { return ieVersion; };
+ return ieVersion;
+};
+
+
+(function () {
+ // Keyword lists for various languages.
+ var FLOW_CONTROL_KEYWORDS =
+ "break continue do else for if return while ";
+ var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
+ "double enum extern float goto int long register short signed sizeof " +
+ "static struct switch typedef union unsigned void volatile ";
+ var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
+ "new operator private protected public this throw true try typeof ";
+ var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
+ "concept concept_map const_cast constexpr decltype " +
+ "dynamic_cast explicit export friend inline late_check " +
+ "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
+ "template typeid typename using virtual wchar_t where ";
+ var JAVA_KEYWORDS = COMMON_KEYWORDS +
+ "abstract boolean byte extends final finally implements import " +
+ "instanceof null native package strictfp super synchronized throws " +
+ "transient ";
+ var CSHARP_KEYWORDS = JAVA_KEYWORDS +
+ "as base by checked decimal delegate descending event " +
+ "fixed foreach from group implicit in interface internal into is lock " +
+ "object out override orderby params partial readonly ref sbyte sealed " +
+ "stackalloc string select uint ulong unchecked unsafe ushort var ";
+ var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
+ "debugger eval export function get null set undefined var with " +
+ "Infinity NaN ";
+ var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
+ "goto if import last local my next no our print package redo require " +
+ "sub undef unless until use wantarray while BEGIN END ";
+ var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
+ "elif except exec finally from global import in is lambda " +
+ "nonlocal not or pass print raise try with yield " +
+ "False True None ";
+ var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
+ " defined elsif end ensure false in module next nil not or redo rescue " +
+ "retry self super then true undef unless until when yield BEGIN END ";
+ var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
+ "function in local set then until ";
+ var ALL_KEYWORDS = (
+ CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
+ PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
+
+ // token style names. correspond to css classes
+ /** token style for a string literal */
+ var PR_STRING = 'str';
+ /** token style for a keyword */
+ var PR_KEYWORD = 'kwd';
+ /** token style for a comment */
+ var PR_COMMENT = 'com';
+ /** token style for a type */
+ var PR_TYPE = 'typ';
+ /** token style for a literal value. e.g. 1, null, true. */
+ var PR_LITERAL = 'lit';
+ /** token style for a punctuation string. */
+ var PR_PUNCTUATION = 'pun';
+ /** token style for a punctuation string. */
+ var PR_PLAIN = 'pln';
+
+ /** token style for an sgml tag. */
+ var PR_TAG = 'tag';
+ /** token style for a markup declaration such as a DOCTYPE. */
+ var PR_DECLARATION = 'dec';
+ /** token style for embedded source. */
+ var PR_SOURCE = 'src';
+ /** token style for an sgml attribute name. */
+ var PR_ATTRIB_NAME = 'atn';
+ /** token style for an sgml attribute value. */
+ var PR_ATTRIB_VALUE = 'atv';
+
+ /**
+ * A class that indicates a section of markup that is not code, e.g. to allow
+ * embedding of line numbers within code listings.
+ */
+ var PR_NOCODE = 'nocode';
+
+ /** A set of tokens that can precede a regular expression literal in
+ * javascript.
+ * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
+ * list, but I've removed ones that might be problematic when seen in
+ * languages that don't support regular expression literals.
+ *
+ *
Specifically, I've removed any keywords that can't precede a regexp
+ * literal in a syntactically legal javascript program, and I've removed the
+ * "in" keyword since it's not a keyword in many languages, and might be used
+ * as a count of inches.
+ *
+ *
The link a above does not accurately describe EcmaScript rules since
+ * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
+ * very well in practice.
+ *
+ * @private
+ */
+ var REGEXP_PRECEDER_PATTERN = function () {
+ var preceders = [
+ "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
+ "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
+ "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
+ "<", "<<", "<<=", "<=", "=", "==", "===", ">",
+ ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
+ "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
+ "||=", "~" /* handles =~ and !~ */,
+ "break", "case", "continue", "delete",
+ "do", "else", "finally", "instanceof",
+ "return", "throw", "try", "typeof"
+ ];
+ var pattern = '(?:^^|[+-]';
+ for (var i = 0; i < preceders.length; ++i) {
+ pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
+ }
+ pattern += ')\\s*'; // matches at end, and matches empty string
+ return pattern;
+ // CAVEAT: this does not properly handle the case where a regular
+ // expression immediately follows another since a regular expression may
+ // have flags for case-sensitivity and the like. Having regexp tokens
+ // adjacent is not valid in any language I'm aware of, so I'm punting.
+ // TODO: maybe style special characters inside a regexp as punctuation.
+ }();
+
+ // Define regexps here so that the interpreter doesn't have to create an
+ // object each time the function containing them is called.
+ // The language spec requires a new object created even if you don't access
+ // the $1 members.
+ var pr_amp = /&/g;
+ var pr_lt = //g;
+ var pr_quot = /\"/g;
+ /** like textToHtml but escapes double quotes to be attribute safe. */
+ function attribToHtml(str) {
+ return str.replace(pr_amp, '&')
+ .replace(pr_lt, '<')
+ .replace(pr_gt, '>')
+ .replace(pr_quot, '"');
+ }
+
+ /** escapest html special characters to html. */
+ function textToHtml(str) {
+ return str.replace(pr_amp, '&')
+ .replace(pr_lt, '<')
+ .replace(pr_gt, '>');
+ }
+
+
+ var pr_ltEnt = /</g;
+ var pr_gtEnt = />/g;
+ var pr_aposEnt = /'/g;
+ var pr_quotEnt = /"/g;
+ var pr_ampEnt = /&/g;
+ var pr_nbspEnt = / /g;
+ /** unescapes html to plain text. */
+ function htmlToText(html) {
+ var pos = html.indexOf('&');
+ if (pos < 0) { return html; }
+ // Handle numeric entities specially. We can't use functional substitution
+ // since that doesn't work in older versions of Safari.
+ // These should be rare since most browsers convert them to normal chars.
+ for (--pos; (pos = html.indexOf('', pos + 1)) >= 0;) {
+ var end = html.indexOf(';', pos);
+ if (end >= 0) {
+ var num = html.substring(pos + 3, end);
+ var radix = 10;
+ if (num && num.charAt(0) === 'x') {
+ num = num.substring(1);
+ radix = 16;
+ }
+ var codePoint = parseInt(num, radix);
+ if (!isNaN(codePoint)) {
+ html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
+ html.substring(end + 1));
+ }
+ }
+ }
+
+ return html.replace(pr_ltEnt, '<')
+ .replace(pr_gtEnt, '>')
+ .replace(pr_aposEnt, "'")
+ .replace(pr_quotEnt, '"')
+ .replace(pr_nbspEnt, ' ')
+ .replace(pr_ampEnt, '&');
+ }
+
+ /** is the given node's innerHTML normally unescaped? */
+ function isRawContent(node) {
+ return 'XMP' === node.tagName;
+ }
+
+ var newlineRe = /[\r\n]/g;
+ /**
+ * Are newlines and adjacent spaces significant in the given node's innerHTML?
+ */
+ function isPreformatted(node, content) {
+ // PRE means preformatted, and is a very common case, so don't create
+ // unnecessary computed style objects.
+ if ('PRE' === node.tagName) { return true; }
+ if (!newlineRe.test(content)) { return true; } // Don't care
+ var whitespace = '';
+ // For disconnected nodes, IE has no currentStyle.
+ if (node.currentStyle) {
+ whitespace = node.currentStyle.whiteSpace;
+ } else if (window.getComputedStyle) {
+ // Firefox makes a best guess if node is disconnected whereas Safari
+ // returns the empty string.
+ whitespace = window.getComputedStyle(node, null).whiteSpace;
+ }
+ return !whitespace || whitespace === 'pre';
+ }
+
+ function normalizedHtml(node, out, opt_sortAttrs) {
+ switch (node.nodeType) {
+ case 1: // an element
+ var name = node.tagName.toLowerCase();
+
+ out.push('<', name);
+ var attrs = node.attributes;
+ var n = attrs.length;
+ if (n) {
+ if (opt_sortAttrs) {
+ var sortedAttrs = [];
+ for (var i = n; --i >= 0;) { sortedAttrs[i] = attrs[i]; }
+ sortedAttrs.sort(function (a, b) {
+ return (a.name < b.name) ? -1 : a.name === b.name ? 0 : 1;
+ });
+ attrs = sortedAttrs;
+ }
+ for (var i = 0; i < n; ++i) {
+ var attr = attrs[i];
+ if (!attr.specified) { continue; }
+ out.push(' ', attr.name.toLowerCase(),
+ '="', attribToHtml(attr.value), '"');
+ }
+ }
+ out.push('>');
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ normalizedHtml(child, out, opt_sortAttrs);
+ }
+ if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
+ out.push('<\/', name, '>');
+ }
+ break;
+ case 3: case 4: // text
+ out.push(textToHtml(node.nodeValue));
+ break;
+ }
+ }
+
+ /**
+ * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
+ * matches the union o the sets o strings matched d by the input RegExp.
+ * Since it matches globally, if the input strings have a start-of-input
+ * anchor (/^.../), it is ignored for the purposes of unioning.
+ * @param {Array.} regexs non multiline, non-global regexs.
+ * @return {RegExp} a global regex.
+ */
+ function combinePrefixPatterns(regexs) {
+ var capturedGroupIndex = 0;
+
+ var needToFoldCase = false;
+ var ignoreCase = false;
+ for (var i = 0, n = regexs.length; i < n; ++i) {
+ var regex = regexs[i];
+ if (regex.ignoreCase) {
+ ignoreCase = true;
+ } else if (/[a-z]/i.test(regex.source.replace(
+ /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
+ needToFoldCase = true;
+ ignoreCase = false;
+ break;
+ }
+ }
+
+ function decodeEscape(charsetPart) {
+ if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
+ switch (charsetPart.charAt(1)) {
+ case 'b': return 8;
+ case 't': return 9;
+ case 'n': return 0xa;
+ case 'v': return 0xb;
+ case 'f': return 0xc;
+ case 'r': return 0xd;
+ case 'u': case 'x':
+ return parseInt(charsetPart.substring(2), 16)
+ || charsetPart.charCodeAt(1);
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7':
+ return parseInt(charsetPart.substring(1), 8);
+ default: return charsetPart.charCodeAt(1);
+ }
+ }
+
+ function encodeEscape(charCode) {
+ if (charCode < 0x20) {
+ return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
+ }
+ var ch = String.fromCharCode(charCode);
+ if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
+ ch = '\\' + ch;
+ }
+ return ch;
+ }
+
+ function caseFoldCharset(charSet) {
+ var charsetParts = charSet.substring(1, charSet.length - 1).match(
+ new RegExp(
+ '\\\\u[0-9A-Fa-f]{4}'
+ + '|\\\\x[0-9A-Fa-f]{2}'
+ + '|\\\\[0-3][0-7]{0,2}'
+ + '|\\\\[0-7]{1,2}'
+ + '|\\\\[\\s\\S]'
+ + '|-'
+ + '|[^-\\\\]',
+ 'g'));
+ var groups = [];
+ var ranges = [];
+ var inverse = charsetParts[0] === '^';
+ for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
+ var p = charsetParts[i];
+ switch (p) {
+ case '\\B': case '\\b':
+ case '\\D': case '\\d':
+ case '\\S': case '\\s':
+ case '\\W': case '\\w':
+ groups.push(p);
+ continue;
+ }
+ var start = decodeEscape(p);
+ var end;
+ if (i + 2 < n && '-' === charsetParts[i + 1]) {
+ end = decodeEscape(charsetParts[i + 2]);
+ i += 2;
+ } else {
+ end = start;
+ }
+ ranges.push([start, end]);
+ // If the range might intersect letters, then expand it.
+ if (!(end < 65 || start > 122)) {
+ if (!(end < 65 || start > 90)) {
+ ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
+ }
+ if (!(end < 97 || start > 122)) {
+ ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
+ }
+ }
+ }
+
+ // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
+ // -> [[1, 12], [14, 14], [16, 17]]
+ ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); });
+ var consolidatedRanges = [];
+ var lastRange = [NaN, NaN];
+ for (var i = 0; i < ranges.length; ++i) {
+ var range = ranges[i];
+ if (range[0] <= lastRange[1] + 1) {
+ lastRange[1] = Math.max(lastRange[1], range[1]);
+ } else {
+ consolidatedRanges.push(lastRange = range);
+ }
+ }
+
+ var out = ['['];
+ if (inverse) { out.push('^'); }
+ out.push.apply(out, groups);
+ for (var i = 0; i < consolidatedRanges.length; ++i) {
+ var range = consolidatedRanges[i];
+ out.push(encodeEscape(range[0]));
+ if (range[1] > range[0]) {
+ if (range[1] + 1 > range[0]) { out.push('-'); }
+ out.push(encodeEscape(range[1]));
+ }
+ }
+ out.push(']');
+ return out.join('');
+ }
+
+ function allowAnywhereFoldCaseAndRenumberGroups(regex) {
+ // Split into character sets, escape sequences, punctuation strings
+ // like ('(', '(?:', ')', '^'), and runs of characters that do not
+ // include any of the above.
+ var parts = regex.source.match(
+ new RegExp(
+ '(?:'
+ + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]' // a character set
+ + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape
+ + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape
+ + '|\\\\[0-9]+' // a back-reference or octal escape
+ + '|\\\\[^ux0-9]' // other escape sequence
+ + '|\\(\\?[:!=]' // start of a non-capturing group
+ + '|[\\(\\)\\^]' // start/emd of a group, or line start
+ + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters
+ + ')',
+ 'g'));
+ var n = parts.length;
+
+ // Maps captured group numbers to the number they will occupy in
+ // the output or to -1 if that has not been determined, or to
+ // undefined if they need not be capturing in the output.
+ var capturedGroups = [];
+
+ // Walk over and identify back references to build the capturedGroups
+ // mapping.
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ var p = parts[i];
+ if (p === '(') {
+ // groups are 1-indexed, so max group index is count of '('
+ ++groupIndex;
+ } else if ('\\' === p.charAt(0)) {
+ var decimalValue = +p.substring(1);
+ if (decimalValue && decimalValue <= groupIndex) {
+ capturedGroups[decimalValue] = -1;
+ }
+ }
+ }
+
+ // Renumber groups and reduce capturing groups to non-capturing groups
+ // where possible.
+ for (var i = 1; i < capturedGroups.length; ++i) {
+ if (-1 === capturedGroups[i]) {
+ capturedGroups[i] = ++capturedGroupIndex;
+ }
+ }
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ var p = parts[i];
+ if (p === '(') {
+ ++groupIndex;
+ if (capturedGroups[groupIndex] === undefined) {
+ parts[i] = '(?:';
+ }
+ } else if ('\\' === p.charAt(0)) {
+ var decimalValue = +p.substring(1);
+ if (decimalValue && decimalValue <= groupIndex) {
+ parts[i] = '\\' + capturedGroups[groupIndex];
+ }
+ }
+ }
+
+ // Remove any prefix anchors so that the output will match anywhere.
+ // ^^ really does mean an anchored match though.
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
+ }
+
+ // Expand letters to groupts to handle mixing of case-sensitive and
+ // case-insensitive patterns if necessary.
+ if (regex.ignoreCase && needToFoldCase) {
+ for (var i = 0; i < n; ++i) {
+ var p = parts[i];
+ var ch0 = p.charAt(0);
+ if (p.length >= 2 && ch0 === '[') {
+ parts[i] = caseFoldCharset(p);
+ } else if (ch0 !== '\\') {
+ // TODO: handle letters in numeric escapes.
+ parts[i] = p.replace(
+ /[a-zA-Z]/g,
+ function (ch) {
+ var cc = ch.charCodeAt(0);
+ return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
+ });
+ }
+ }
+ }
+
+ return parts.join('');
+ }
+
+ var rewritten = [];
+ for (var i = 0, n = regexs.length; i < n; ++i) {
+ var regex = regexs[i];
+ if (regex.global || regex.multiline) { throw new Error('' + regex); }
+ rewritten.push(
+ '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
+ }
+
+ return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
+ }
+
+ var PR_innerHtmlWorks = null;
+ function getInnerHtml(node) {
+ // inner html is hopelessly broken in Safari 2.0.4 when the content is
+ // an html description of well formed XML and the containing tag is a PRE
+ // tag, so we detect that case and emulate innerHTML.
+ if (null === PR_innerHtmlWorks) {
+ var testNode = document.createElement('PRE');
+ testNode.appendChild(
+ document.createTextNode('\n'));
+ PR_innerHtmlWorks = !/)[\r\n]+/g, '$1')
+ .replace(/(?:[\r\n]+[ \t]*)+/g, ' ');
+ }
+ return content;
+ }
+
+ var out = [];
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ normalizedHtml(child, out);
+ }
+ return out.join('');
+ }
+
+ /** returns a function that expand tabs to spaces. This function can be fed
+ * successive chunks of text, and will maintain its own internal state to
+ * keep track of how tabs are expanded.
+ * @return {function (string) : string} a function that takes
+ * plain text and return the text with tabs expanded.
+ * @private
+ */
+ function makeTabExpander(tabWidth) {
+ var SPACES = ' ';
+ var charInLine = 0;
+
+ return function (plainText) {
+ // walk over each character looking for tabs and newlines.
+ // On tabs, expand them. On newlines, reset charInLine.
+ // Otherwise increment charInLine
+ var out = null;
+ var pos = 0;
+ for (var i = 0, n = plainText.length; i < n; ++i) {
+ var ch = plainText.charAt(i);
+
+ switch (ch) {
+ case '\t':
+ if (!out) { out = []; }
+ out.push(plainText.substring(pos, i));
+ // calculate how much space we need in front of this part
+ // nSpaces is the amount of padding -- the number of spaces needed
+ // to move us to the next column, where columns occur at factors of
+ // tabWidth.
+ var nSpaces = tabWidth - (charInLine % tabWidth);
+ charInLine += nSpaces;
+ for (; nSpaces >= 0; nSpaces -= SPACES.length) {
+ out.push(SPACES.substring(0, nSpaces));
+ }
+ pos = i + 1;
+ break;
+ case '\n':
+ charInLine = 0;
+ break;
+ default:
+ ++charInLine;
+ }
+ }
+ if (!out) { return plainText; }
+ out.push(plainText.substring(pos));
+ return out.join('');
+ };
+ }
+
+ var pr_chunkPattern = new RegExp(
+ '[^<]+' // A run of characters other than '<'
+ + '|<\!--[\\s\\S]*?--\>' // an HTML comment
+ + '|' // a CDATA section
+ // a probable tag that should not be highlighted
+ + '|<\/?[a-zA-Z](?:[^>\"\']|\'[^\']*\'|\"[^\"]*\")*>'
+ + '|<', // A '<' that does not begin a larger chunk
+ 'g');
+ var pr_commentPrefix = /^<\!--/;
+ var pr_cdataPrefix = /^) into their textual equivalent.
+ *
+ * @param {string} s html where whitespace is considered significant.
+ * @return {Object} source code and extracted tags.
+ * @private
+ */
+ function extractTags(s) {
+ // since the pattern has the 'g' modifier and defines no capturing groups,
+ // this will return a list of all chunks which we then classify and wrap as
+ // PR_Tokens
+ var matches = s.match(pr_chunkPattern);
+ var sourceBuf = [];
+ var sourceBufLen = 0;
+ var extractedTags = [];
+ if (matches) {
+ for (var i = 0, n = matches.length; i < n; ++i) {
+ var match = matches[i];
+ if (match.length > 1 && match.charAt(0) === '<') {
+ if (pr_commentPrefix.test(match)) { continue; }
+ if (pr_cdataPrefix.test(match)) {
+ // strip CDATA prefix and suffix. Don't unescape since it's CDATA
+ sourceBuf.push(match.substring(9, match.length - 3));
+ sourceBufLen += match.length - 12;
+ } else if (pr_brPrefix.test(match)) {
+ // tags are lexically significant so convert them to text.
+ // This is undone later.
+ sourceBuf.push('\n');
+ ++sourceBufLen;
+ } else {
+ if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) {
+ // A will start a section that should be
+ // ignored. Continue walking the list until we see a matching end
+ // tag.
+ var name = match.match(pr_tagNameRe)[2];
+ var depth = 1;
+ var j;
+ end_tag_loop:
+ for (j = i + 1; j < n; ++j) {
+ var name2 = matches[j].match(pr_tagNameRe);
+ if (name2 && name2[2] === name) {
+ if (name2[1] === '/') {
+ if (--depth === 0) { break end_tag_loop; }
+ } else {
+ ++depth;
+ }
+ }
+ }
+ if (j < n) {
+ extractedTags.push(
+ sourceBufLen, matches.slice(i, j + 1).join(''));
+ i = j;
+ } else { // Ignore unclosed sections.
+ extractedTags.push(sourceBufLen, match);
+ }
+ } else {
+ extractedTags.push(sourceBufLen, match);
+ }
+ }
+ } else {
+ var literalText = htmlToText(match);
+ sourceBuf.push(literalText);
+ sourceBufLen += literalText.length;
+ }
+ }
+ }
+ return { source: sourceBuf.join(''), tags: extractedTags };
+ }
+
+ /** True if the given tag contains a class attribute with the nocode class. */
+ function isNoCodeTag(tag) {
+ return !!tag
+ // First canonicalize the representation of attributes
+ .replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,
+ ' $1="$2$3$4"')
+ // Then look for the attribute we want.
+ .match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/);
+ }
+
+ /**
+ * Apply the given language handler to sourceCode and add the resulting
+ * decorations to out.
+ * @param {number} basePos the index of sourceCode within the chunk of source
+ * whose decorations are already present on out.
+ */
+ function appendDecorations(basePos, sourceCode, langHandler, out) {
+ if (!sourceCode) { return; }
+ var job = {
+ source: sourceCode,
+ basePos: basePos
+ };
+ langHandler(job);
+ out.push.apply(out, job.decorations);
+ }
+
+ /** Given triples of [style, pattern, context] returns a lexing function,
+ * The lexing function interprets the patterns to find token boundaries and
+ * returns a decoration list of the form
+ * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
+ * where index_n is an index into the sourceCode, and style_n is a style
+ * constant like PR_PLAIN. index_n-1 <= index_n, and style_n-1 applies to
+ * all characters in sourceCode[index_n-1:index_n].
+ *
+ * The stylePatterns is a list whose elements have the form
+ * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
+ *
+ * Style is a style constant like PR_PLAIN, or can be a string of the
+ * form 'lang-FOO', where FOO is a language extension describing the
+ * language of the portion of the token in $1 after pattern executes.
+ * E.g., if style is 'lang-lisp', and group 1 contains the text
+ * '(hello (world))', then that portion of the token will be passed to the
+ * registered lisp handler for formatting.
+ * The text before and after group 1 will be restyled using this decorator
+ * so decorators should take care that this doesn't result in infinite
+ * recursion. For example, the HTML lexer rule for SCRIPT elements looks
+ * something like ['lang-js', /<[s]cript>(.+?)<\/script>/]. This may match
+ * '
+
+
+
+
+
+
+