Coverage Summary for Class: Game (it.polimi.ingsw.controller)

Class Method, % Line, %
Game 0% (0/44) 0% (0/563)
Game$1 0% (0/1) 0% (0/1)
Total 0% (0/45) 0% (0/564)


 package it.polimi.ingsw.controller;
 
 
 import it.polimi.ingsw.model.*;
 import it.polimi.ingsw.view.IP;
 import it.polimi.ingsw.view.Player;
 import it.polimi.ingsw.view.PlayerI;
 import it.polimi.ingsw.view.PlayerSend;
 
 import java.io.*;
 import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketException;
 import java.rmi.RemoteException;
 import java.rmi.registry.LocateRegistry;
 import java.rmi.server.UnicastRemoteObject;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * class which represent the instance of the current game
  * @author Ettori Faccincani
  * in theory it is mutable, but it is only instanced one time, at the start of the server
  */
 public class Game extends UnicastRemoteObject implements Serializable, GameI {
     /** variable that represent the standard timer of the app for advancing the state of the game */
     public static final double waitTimer = 2.5;
     /** variable that represent the fast timer of the app for small waiting task */
     public static final double fastTimer = 1;
     /** variable that represent the timer for the new turn (changing) interaction */
     public static final double passTimer = 1;
     /** variable that represent the standard timer of the app for showing events */
     public static final double showTimer = 2.5;
     /** variable that represent if we want to run or debug our application */
     public static boolean showErrors = false;
     private final int targetPlayers;
     private int numPlayers;
     private int activePlayer = 0;
     private ArrayList<PlayerSend> players = new ArrayList<>();
     private ArrayList<String> names = new ArrayList<>();
     private final transient ArrayList<Socket> playersSocket = new ArrayList<>();
     private final transient ArrayList<ObjectOutputStream> outStreams = new ArrayList<>();
     private final transient ArrayList<ObjectInputStream> inStreams = new ArrayList<>();
     private final ArrayList<CommonObjective> bucketOfCO = Initializer.setBucketOfCO();
     private final ArrayList<PrivateObjective> bucketOfPO = Initializer.setBucketOfPO();
     private boolean endGameSituation = false;
     private boolean timeExp = true;
     private transient ArrayList<Thread> chatThreads = new ArrayList<>();
     private transient ServerSocket serverSocket;
     private transient boolean closed = false;
     private final transient HashMap<String, PlayerI> rmiClients = new HashMap<>();
     private transient Game gameTemp = null;
     private final transient ArrayList<String> disconnectedPlayers = new ArrayList<>();
     private boolean advance = false; //true iif the server has to force a new turn after resilience activation
     private final transient Object disconnectionLock = new Object();
     private transient String playerNoChat = "";
 
     /**
      * normal constructor for this type of object, this class is also the main process on the server
      * @param maxP the number of players for this game, chosen before by the user
      * @param old contains yes/no, used to determine if the player wants to load and older game
      */
     public Game(int maxP, String old) throws RemoteException {
         super();
         System.setProperty("java.rmi.server.hostname", IP.activeIP);
         try {
             LocateRegistry.createRegistry(Initializer.PORT_RMI).rebind("Server", this); // host the server on the network
         }catch (Exception e){
             System.out.println("\nPort 5555 is already used\n");
             System.exit(0);
         }
 
         targetPlayers = maxP;
         if(old.equals("yes")){
             if(FILEHelper.havaCachedServer()) {// check if there's a server in the cache, if so, use it
                 gameTemp = FILEHelper.loadServer();
                 FILEHelper.writeFail();
                 if(gameTemp.numPlayers != maxP) {
                     System.out.println("\nThe old game is not compatible, starting a new game...");
                     gameTemp = null;
                 }
                 else {
                     System.out.println("\nLoading the old game...");
                 }
             }
             else {
                 System.out.println("\nThere is no game to load, starting a new game...");
             }
         }
         FILEHelper.writeFail();
         shuffleObjBucket();
         numPlayers = 0;
         new Thread(() -> { // start a timer that wait for clients connections
             double minutes = 2.5;
             Game.waitForSeconds(60 * minutes);
             if(!timeExp)
                 return;
             System.out.println("\nTime limit exceeded, not enough players connected\n");
             System.exit(0);
         }).start();
 
         try{serverSocket = new ServerSocket(Initializer.PORT, 10, InetAddress.getByName(IP.activeIP));}
         catch(Exception e){connectionLost(e);}
         System.out.println("\nServer listening... (@exit for closing the server)\n");
 
         new Thread(() ->{
             BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
             String s;
             while(true){
                 try {
                     s = br.readLine();
                 } catch (IOException e) {
                     throw new RuntimeException(e);
                 }
                 if(!s.equals("@exit"))
                     continue;
                 System.out.println("\nServer exiting...\n");
                 Game.waitForSeconds(Game.fastTimer);
                 System.exit(0);
             }
         }).start();
 
         listenForPlayersConnections();
 
         if(gameTemp != null){
             if(gameTemp.names.containsAll(names)) {
                 initializeOldClients();
                 if(gameTemp.endGameSituation){
                     boolean temp; //variable used to check if the rmi is ready
                     for(int i = 0; i < numPlayers; i++) {
                         try {
                             temp = (boolean)inStreams.get(i).readObject();
                         } catch (IOException | ClassNotFoundException e) {
                             connectionLost(e);
                         }
                     }
                     Game.waitForSeconds(Game.waitTimer);
                     sendFinalScoresToAll();
                 }
             }
             else{
                 System.out.println("\nThe names of the clients do not match the old ones, starting a new game...");
                 initializeAllClients();
             }
             gameTemp = null;
         }
         else
             initializeAllClients();
 
         new Thread(() ->{
             while (true){
                 Game.waitForSeconds(Game.waitTimer);
                 if(getActivePlayersNumber() != 0)
                     continue;
                 System.out.println("\nThe server il closing because there are no connected players\n");
                 System.exit(0);
             }
         }).start();
 
         boolean temp; //variable used to check if the rmi is ready
         for(int i = 0; i < numPlayers; i++) {
             try {
                 temp = (boolean)inStreams.get(i).readObject();
             } catch (IOException | ClassNotFoundException e) {
                 connectionLost(e);
             }
         }
         for(int i = 0; i < numPlayers; i++){
             if(rmiClients.containsKey(names.get(i)))
                 continue;
             try {
                 playersSocket.get(i).setSoTimeout(Player.pingTimeout);
             } catch (SocketException e) {
                 connectionLost(e);
             }
         }
         new Thread(this::pingRMI).start();
         new Thread(this::listenForReconnection).start();
         if(!rmiClients.containsKey(names.get(0)))
             new Thread(this::waitMoveFromClient).start();
         else
             startChatServerThread();
     }
     /**
      * method that get an old client status by his name
      * @param n the name of the old client
      * @param playerList the list of all the old clients
      * @author Ettori
      * @return the client that was playing previously and needs to be alive again
      */
     private Player getClientByName(String n, ArrayList<PlayerSend> playerList){
         for(int i = 0; i < playerList.size(); i++){
             if(playerList.get(i).name.equals(n)) {
                 try {
                     return new Player(playerList.get(i));
                 } catch (RemoteException e) {
                     connectionLost(e);
                 }
             }
         }
         System.out.println("\nAncient client not found...");
         System.exit(0);
         return null;
     }
     /**
      * get the only player connected to the game currently
      * @return the index of the only player left active
      * @author Ettori
      */
     private int getLastPlayer(){
         for(int i = 0; i < numPlayers; i++){
             if(!disconnectedPlayers.contains(names.get(i)))
                 return i;
         }
         return -1;
     }
     /**
      * helper method for initializing the old clients that were playing in the previous game
      * @author Ettori
      */
     private void initializeOldClients(){
         for(int i = 0; i < numPlayers; i++){
             try {
                 gameTemp.players.get(i).activeName = gameTemp.names.get(0);
                 outStreams.get(i).writeObject(new Player(getClientByName(names.get(i), gameTemp.players)));
                 players.add(new PlayerSend(getClientByName(names.get(i), gameTemp.players)));
             }catch (Exception e){connectionLost(e);}
         }
         int temp = names.indexOf(gameTemp.names.get(0));
         String n = names.get(0);
         names.set(0, names.get(temp));
         names.set(temp, n);
 
         ObjectOutputStream outTemp = outStreams.get(0);
         outStreams.set(0, outStreams.get(temp));
         outStreams.set(temp, outTemp);
 
         ObjectInputStream inTemp = inStreams.get(0);
         inStreams.set(0, inStreams.get(temp));
         inStreams.set(temp, inTemp);
     }
     /**
      * helper method for initializing all the clients (players) with the same board state
      * @author Ettori
      */
     private void initializeAllClients(){
         randomizeChairman();
         Player p = null;
         for(int i = 0; i < names.size(); i++){
             try {
                 p = new Player();
             } catch (RemoteException e) {
                 connectionLost(e);
             }
             for(int j = 0; j < numPlayers; j++){
                 if(i == j)
                     continue;
                 try {
                     p.pointsMap.put(names.get(j), 0);
                 }catch (Exception e){
                     System.out.println("\nServer unable to start...\n");
                     System.exit(0);
                 }
             }
             p.setName(names.get(i));
             p.setIsChairMan(i == 0);
             p.board = new Board(numPlayers, bucketOfCO.get(0), bucketOfCO.get(1));
             p.board.name = names.get(i);
             if(i == 0)
                 p.board.initBoard(numPlayers);
             else
                 p.board = new Board(getChairman().board);
             p.library = new Library(names.get(i));
             p.setPrivateObjective(getPrivateObjective());
             p.pointsUntilNow = 0;
             p.activeName = getChairmanName();
             p.chairmanName = getChairmanName();
             try {
                 for (int j = 0; j < numPlayers; j++) {
                     if (!names.get(j).equals(names.get(i)))
                         p.librariesOfOtherPlayers.add(new Library(names.get(j)));
                 }
             }catch (Exception e){connectionLost(e);}
             p.numPlayers = numPlayers;
             try {
                 outStreams.get(i).writeObject(new Player(p));
             }catch (Exception e){connectionLost(e);}
             players.add(new PlayerSend(p));
         }
     }
 
     /**
      * choose a random chairman from all the players who connected to the game
      * @author Ettori
      */
     private void randomizeChairman(){
         if(playersSocket.size() != numPlayers){
             System.out.println("\nNot enough players connected to the game, server closing...\n");
             System.exit(0);
         }
 
         int temp = new Random().nextInt(numPlayers);
         String n = null;
         try {
             n = names.get(0);
             names.set(0, names.get(temp));
             names.set(temp, n);
         }catch (Exception e){connectionLost(e);}
 
         ObjectOutputStream outTemp = outStreams.get(0);
         outStreams.set(0, outStreams.get(temp));
         outStreams.set(temp, outTemp);
 
         ObjectInputStream inTemp = inStreams.get(0);
         inStreams.set(0, inStreams.get(temp));
         inStreams.set(temp, inTemp);
     }
 
     /**
      * helper function which waits for client's connection to the server socket, when all are connected the game starts
      * @author Ettori
      */
     private void listenForPlayersConnections(){
         ArrayList<Thread> ths = new ArrayList<>();
         ObjectInputStream clientIn;
         ObjectOutputStream clientOut;
         Thread th;
 
         while(numPlayers < targetPlayers){
             try{
                 playersSocket.add(serverSocket.accept());
                 clientOut = new ObjectOutputStream(playersSocket.get(playersSocket.size() - 1).getOutputStream());
                 clientIn = new ObjectInputStream(playersSocket.get(playersSocket.size() - 1).getInputStream());
                 clientOut.flush();
                 boolean isFake = (boolean) clientIn.readObject();
                 if(isFake) {
                     playersSocket.remove(playersSocket.size() - 1);
                     continue;
                 }
                 ObjectInputStream finalClientIn = clientIn;
                 ObjectOutputStream finalClientOut = clientOut;
                 th = new Thread(() ->{
                     try{getUserName(finalClientIn, finalClientOut);}
                     catch(Exception e){connectionLost(e);}
                 });
                 th.start();
                 ths.add(th);
                 numPlayers++;
             }
             catch(Exception e){
                 try {
                     playersSocket.get(playersSocket.size() - 1).close();
                 } catch (IOException ex) {
                     throw new RuntimeException(ex);
                 }
                 playersSocket.remove(playersSocket.size() - 1);
             }
         }
         timeExp = false;
         for(Thread t: ths){
             try{t.join();}
             catch(Exception e){connectionLost(e);}
         }
         if(numPlayers < targetPlayers){
             System.out.println("\nPlayer number not sufficient\n");
             System.exit(0);
         }
         System.out.println("\nThe game started\n");
     }
 
     /**
      * method that listen for an old client to restart his previous game, in the same old state
      * @param s the socket of the player
      * @param out the output stream of the player
      * @param in the input stream of the player
      * @author Ettori
      */
     synchronized private void tryToReconnectClient(Socket s, ObjectOutputStream out, ObjectInputStream in){
         try {
             String name = (String) in.readObject();
             if(disconnectedPlayers.contains(name)) {
                 rmiClients.remove(name);
                 out.writeObject(NameStatus.FOUND);
                 PlayerSend p = new PlayerSend(players.get(names.indexOf(name)));
                 p.activeName = names.get(activePlayer);
                 out.writeObject(new Player(p));
                 inStreams.set(names.indexOf(name), in);
                 outStreams.set(names.indexOf(name), out);
                 playersSocket.set(names.indexOf(name), s);
                 boolean temp = (boolean) in.readObject(); //variable used to check if the rmi is ready
                 synchronized (disconnectionLock) {
                     disconnectedPlayers.remove(name);
                     if (!rmiClients.containsKey(name)) {
                         try {
                             s.setSoTimeout(Player.pingTimeout);
                         } catch (SocketException e) {
                             connectionLost(e);
                         }
                         if (getActivePlayersNumber() >= 3) // if there are only 2 players, the turn will change, so there is no need to listen to the chat
                             new ChatBroadcast(this, names.indexOf(name)).start();
                     }
                     if (getActivePlayersNumber() == 2) {
                         disconnectedPlayers.remove(name);
                         if (advance) {
                             sendToClient(activePlayer, new Message(MessageType.SHOW_EVENT, null, "Player " + name + " reconnected, the game is resuming..."));
                             Game.waitForSeconds(Game.fastTimer * 1.5);
                             advance = false;
                             new Thread(this::advanceTurn).start();
                         } else {
                             sendToClient(activePlayer, new Message(MessageType.SHOW_EVENT, null, "Player " + name + " reconnected to the game"));
                             new ChatBroadcast(this, names.indexOf(name)).start(); // the chat thread will be stopped naturally after the next UPDATE_GAME
                         }
                     } else {
                         disconnectedPlayers.remove(name);
                         for (int i = 0; i < numPlayers; i++) {
                             if (names.get(i).equals(name))
                                 continue;
                             sendToClient(i, new Message(MessageType.SHOW_EVENT, null, "Player " + name + " reconnected to the game"));
                         }
                     }
                 }
             }
                 else
                     out.writeObject(NameStatus.NOT_FOUND);
         }catch (Exception e){
             try {
                 s.close();
                 out.close();
                 in.close();
             } catch (IOException ignored) {}
         }
     }
 
     /**
      * method that wait permanently for a new client to connect to the existing game
      * @author Ettori
      */
     private void listenForReconnection(){
         Socket s = null;
         while(true){
             try {
                 s = serverSocket.accept();
             } catch (IOException e) {
                 continue;
             }
             Socket finalS = s;
             new Thread(() -> {
                 ObjectOutputStream out = null;
                 ObjectInputStream in = null;
                 try {
                     out = new ObjectOutputStream(finalS.getOutputStream());
                     out.flush();
                     in = new ObjectInputStream(finalS.getInputStream());
                 } catch (IOException ignored) {}
                 tryToReconnectClient(finalS, out, in);
             }).start();
         }
     }
 
     /**
      * Check if the name that the client choose is already TAKEN
      * @param in the input stream of the socket
      * @param out the output stream of the socket
      * @author Ettori
      */
     synchronized private void getUserName(ObjectInputStream in, ObjectOutputStream out){
         try {
             outStreams.add(out);
             inStreams.add(in);
             while (true) {
                 String name = (String) inStreams.get(inStreams.size() - 1).readObject();
                 if (isNameTaken(name)) {
                     outStreams.get(outStreams.size() - 1).writeObject(NameStatus.TAKEN);
                     continue;
                 }
                 if(gameTemp != null && gameTemp.names.contains(name))
                     outStreams.get(outStreams.size() - 1).writeObject(NameStatus.OLD);
                 else
                     outStreams.get(outStreams.size() - 1).writeObject(NameStatus.NOT_TAKEN);
                 names.add(name);
                 break;
             }
         }catch(Exception e){
             try {
                 playersSocket.get(playersSocket.size() - 1).close();
                 outStreams.get(outStreams.size() - 1).close();
                 inStreams.get(inStreams.size() - 1).close();
             } catch (IOException ignored) {}
             outStreams.remove(outStreams.size() - 1);
             inStreams.remove(inStreams.size() - 1);
             playersSocket.remove(playersSocket.size() - 1);
         }
     }
 
     /**
      * Make a random choose of the objective (Common and Private)
      * @author Ettori
      */
     private void shuffleObjBucket(){
         Random rand = new Random();
         CommonObjective temp_1;
         PrivateObjective temp_2;
         int j;
         for(int i = 0; i < bucketOfCO.size(); i++){
             j = rand.nextInt(bucketOfCO.size());
             temp_1 = bucketOfCO.get(i);
             bucketOfCO.set(i, bucketOfCO.get(j));
             bucketOfCO.set(j, temp_1);
         }
         for(int i = 0; i < bucketOfPO.size(); i++){
             j = rand.nextInt(bucketOfPO.size());
             temp_2 = bucketOfPO.get(i);
             bucketOfPO.set(i, bucketOfPO.get(j));
             bucketOfPO.set(j, temp_2);
         }
     }
 
     /**
      * Wait the move of the client that are playing and set the chat,
      * when the client made the move and send it to server update Board and Library
      * @author Ettori Faccincani
      */
     private void waitMoveFromClient(){
             startChatServerThread();
             while (true) {
                 Message msg = null;
                 try {
                     msg = (Message) inStreams.get(activePlayer).readObject();
                 } catch (IOException | ClassNotFoundException e) {
                     playerDisconnected(activePlayer, e);
                     return;
                 }
                 try {
                     if (msg == null)
                         continue;
                     if (msg.getType() == MessageType.PING)
                         continue;
                     if (msg.getType() == MessageType.CHAT) {
                         sendChatToClients(names.get(activePlayer), msg.getAuthor(), (String) msg.getContent());
                         continue;
                     }
                     if(msg.getType() == MessageType.STOP){
                         continue;
                     }
                     for (int i = 0; i < numPlayers; i++) {
                         if (i != activePlayer)
                             sendToClient(i, msg);
                     }
                     if (msg.getType() == MessageType.LIB_FULL && !endGameSituation) {
                         endGameSituation = true;
                         continue;
                     }
                     if (msg.getType() == MessageType.UPDATE_UNPLAYABLE) {
                         Board b = (Board) msg.getContent();
                         for (int i = 0; i < numPlayers; i++)
                             players.get(i).board = new Board(b);
                         FILEHelper.writeServer(this);
                     }
                     if (msg.getType() == MessageType.UPDATE_GAME) {
                         PlayerSend p = (PlayerSend) msg.getContent();
                         for (int i = 0; i < numPlayers; i++) {
                             if (i == activePlayer)
                                 continue;
                             players.get(i).board = p.board;
                             players.get(i).pointsMap.put(names.get(activePlayer), p.pointsUntilNow);
                             for (int j = 0; j < numPlayers - 1; j++) {
                                 if (players.get(i).librariesOfOtherPlayers.get(j).name.equals(names.get(activePlayer)))
                                     players.get(i).librariesOfOtherPlayers.set(j, p.library);
                             }
                         }
                         players.set(activePlayer, p);
                         players.get(activePlayer).activeName = names.get(activePlayer);
                         FILEHelper.writeServer(this);
                         if (!rmiClients.containsKey(names.get(activePlayer)))
                             sendToClient(activePlayer, new Message(MessageType.STOP, null, null));
                         break;
                     }
                 } catch (Exception e) {
                     connectionLost(e);
                 }
             }
         Game.waitForSeconds(Game.passTimer);
         advanceTurn();
     }
 
     /**
      * Set the status of the players for the next turn and assign activePlayer to who will play this turn
      * @author Ettori Faccincani
      */
     public void advanceTurn(){
         synchronized (disconnectionLock) {
             if(getActivePlayersNumber() == 0)
                 connectionLost(new RuntimeException("All players disconnected"));
             if (getActivePlayersNumber() == 1 && disconnectedPlayers.size() > 0) {
                 Game.waitForSeconds(Game.passTimer);
                 sendToClient(getLastPlayer(), new Message(MessageType.SHOW_EVENT, null, "The game is temporarily paused because you are the only connected player"));
                 advance = true;
                 activePlayer = getLastPlayer();
                 new ChatBroadcast(this, activePlayer).start();
                 playerNoChat = names.get(activePlayer);
                 return;
             }
         }
         do{
             activePlayer = (activePlayer + 1) % numPlayers;
         }
         while(disconnectedPlayers.contains(names.get(activePlayer)));
         if(activePlayer == 0 && endGameSituation) {
             System.out.println("\nThe game is ending...");
             Game.waitForSeconds(Game.fastTimer);
             sendFinalScoresToAll();
             return;
         }
         notifyNewTurn();
     }
 
     /**
      * Send the message to the client that a new turn start (two cases, if is the turn of the client or is the turn of another client)
      * @author Ettori Faccincani
      */
     private void notifyNewTurn(){
         for(int i = 0; i < numPlayers; i++){
             try {
                 if (i != activePlayer)
                     sendToClient(i, new Message(MessageType.CHANGE_TURN, "server", names.get(activePlayer)));
             }catch (Exception e){connectionLost(e);}
         }
         new Thread(() -> sendToClient(activePlayer, new Message(MessageType.YOUR_TURN, "server", ""))).start();
         if(!rmiClients.containsKey(names.get(activePlayer)))
             new Thread(this::waitMoveFromClient).start();
         else
             startChatServerThread();
     }
 
     /**
      * start all the threads that listen for chat messages from the clients (and sends the messages back to the players)
      * @author Ettori
      */
     private void startChatServerThread(){
         chatThreads = new ArrayList<>();
         for(int i = 0; i < numPlayers; i++){
             if(i == activePlayer || rmiClients.containsKey(names.get(i)) || disconnectedPlayers.contains(names.get(i)) || names.get(i).equals(playerNoChat))
                 continue;
             chatThreads.add(new ChatBroadcast(this, i));
             chatThreads.get(chatThreads.size() - 1).start();
         }
         playerNoChat = "";
     }
 
     /**
      * Send message in the chat to other client
      * @param from who send the message
      * @param to who receive the message
      * @param msg text inside the message
      * @author Ettori
      */
     public void sendChatToClients(String from, String to, String msg){
         try {
             if (to.equals("all")) {
                 for (int i = 0; i < numPlayers; i++) {
                     if (!names.get(i).equals(from))
                         sendToClient(i, new Message(MessageType.CHAT, "", msg));
                     players.get(i).fullChat += msg;
                 }
             }
             else if(getNameIndex(to) != -1){
                 sendToClient(getNameIndex(to) ,new Message(MessageType.CHAT, "", msg));
                 players.get(getNameIndex(to)).fullChat += msg;
                 players.get(getNameIndex(from)).fullChat += msg;
             }
         }catch (Exception e){connectionLost(e);}
     }
 
     /**
      * find and return the name of the chairman of this game
      * @return the name of the chairman (String)
      */
     private String getChairmanName(){return names.get(0);}
 
     /**
      * find and return the chairman Player
      * @return the chairman Object (Player)
      */
     private PlayerSend getChairman(){return players.get(0);}
 
     /**
      * check if the name is already taken by other players
      * @param name the name to check
      * @return true iff the name is already taken
      */
     private boolean isNameTaken(String name){return names.contains(name);}
 
     /**
      * get the index of a certain player, by the name
      * @param name the name of the player
      * @return the index of the player having that name, -1 if not found
      */
     private int getNameIndex(String name){
         for(int i = 0; i < names.size(); i++){
             if(names.get(i).equals(name))
                 return i;
         }
         return -1;
     }
 
     /**
      * choose the private objective, one for every player
      * @return the chosen private objective
      */
     private PrivateObjective getPrivateObjective(){
         PrivateObjective res = bucketOfPO.get(0);
         bucketOfPO.remove(0);
         return res;
     }
 
     /**
      * Count the points at the end of the game (not private or common objective)
      * and sum to the points made until now
      * @return the order of the player each one with his score
      * @author Ettori
      */
     private String getFinalScore(){
         ArrayList<Integer> scores = new ArrayList<>();
         StringBuilder res = new StringBuilder();
         PlayerSend p;
         for(int i = 0; i < numPlayers; i++){
             p = players.get(i);
             scores.add(p.pointsUntilNow + p.library.countGroupedPoints() + p.objective.countPoints(p.library.gameLibrary));
         }
         String tempName;
         int tempScore;
         for(int i = 0; i < numPlayers; i++){
             for(int j = i; j < numPlayers; j++){
                 if(scores.get(j) < scores.get(i)){
                     tempName = names.get(i);
                     tempScore = scores.get(i);
                     names.set(i, names.get(j));
                     scores.set(i, scores.get(j));
                     names.set(j, tempName);
                     scores.set(j, tempScore);
                 }
             }
         }
         Collections.reverse(names);
         Collections.reverse(scores);
         for(int i = 0; i < numPlayers; i++)
             res.append("Place number ").append(i + 1).append(": ").append(names.get(i)).append(" with ").append(scores.get(i)).append(" points\n");
         return res.toString();
     }
     /**
      * Send the final score to all the clients
      * @author Ettori
      */
     private void sendFinalScoresToAll(){
         String finalScores = getFinalScore();
         FILEHelper.writeSucc(); // the server finished with success, so nothing has to be written in the cache
         Thread finalTh = new Thread(() ->{
             for(int i = 0; i < numPlayers; i++)
                 sendToClient(i, new Message(MessageType.FINAL_SCORE, "server", finalScores));
         });
         finalTh.start();
         try {
             finalTh.join();
         } catch (InterruptedException e) {
             connectionLost(e);
         }
         System.out.println("The game is finished successfully");
         Game.waitForSeconds(Game.waitTimer * 5);
         System.exit(0);
     }
 
     /**
      * getter for the input streams from the server to all the clients
      * @return the ArrayList containing all the input streams
      */
     public ArrayList<ObjectInputStream> getInStreams(){return inStreams;}
 
     /**
      * getter for the list of names of the players active in this game
      * @return the ArrayList containing all the names of the connected players
      */
     public ArrayList<String> getNames(){return names;}
 
     /**
      * shortcut for the Thread.sleep(int) function, it accepts SECONDS, NOT MILLISECONDS
      * @param n the (decimal) number of seconds to wait
      */
     public static void waitForSeconds(double n){
         try {
             Thread.sleep((long) (n * 1000));
         } catch (InterruptedException ignored) {}
     }
 
     /**
      * function that handle the eventual disconnection
      * @param e the exception to throw
      * @author Ettori
      */
     public void connectionLost(Exception e){
         if(closed)
             return;
         //e.printStackTrace();
         if(Game.showErrors)
             throw new RuntimeException(e);
         else{
             closed = true;
             System.out.println("\nConnection lost, the server is closing...\n");
             new Thread(() ->{
                 try {
                     for(Socket s: playersSocket)
                         s.close();
                     for(ObjectOutputStream out: outStreams)
                         out.close();
                     serverSocket.close();
                 } catch (Exception ignored) {}
             }).start();
             Game.waitForSeconds(Game.fastTimer * 1.5);
             System.exit(0);
         }
     }
 
     /**
      * method which acknowledge that one of the client disconnected and set the game to continue without the lost client
      * @param i the index of the lost client
      * @author Ettori
      */
      public void playerDisconnected(int i, Exception exc) {
          synchronized (disconnectionLock) {
              if (Game.showErrors)
                  connectionLost(exc);
              if (disconnectedPlayers.contains(names.get(i)))
                  return;
              //System.out.println("disco " + names.get(i));
              try {
                  playersSocket.get(i).setSoTimeout(0);
                  outStreams.get(i).flush();
              } catch (IOException ignored) {}
              disconnectedPlayers.add(names.get(i));
              rmiClients.remove(names.get(i));
          }
          if (getActivePlayersNumber() == 1)
              new Thread(this::disconnectedTimer).start();
          if(getActivePlayersNumber() == 0){
              System.out.println("The server is closing because there are no connected players...");
              System.exit(0);
          }
          if (i == activePlayer){
              for (int j = 0; j < numPlayers; j++) {
                  int finalJ = j;
                  new Thread(() -> sendToClient(finalJ, new Message(MessageType.DISCONNECTED, names.get(i), null))).start();
              }
              if(getActivePlayersNumber() > 1){
                  Game.waitForSeconds(Game.fastTimer);
                  advanceTurn();
              }
              else{
                  activePlayer = getLastPlayer();
                  Game.waitForSeconds(Game.fastTimer);
                  notifyNewTurn();
              }
          }
          else{
              for(int x = 0; x < numPlayers; x++){
                  if(!disconnectedPlayers.contains(names.get(x))) {
                      sendToClient(x, new Message(MessageType.LOST_CLIENT, names.get(i), null));
                  }
              }
          }
      }
 
     /**
      * method that checks if one player has been alone for more than 1 minute, in that case that player is declared winner and the game end
      * @author Ettori
      */
     private void disconnectedTimer(){
         int name = getLastPlayer();
         Game.waitForSeconds(30);
         if(getActivePlayersNumber() != 1)
             return;
         Game.waitForSeconds(30);
         if(getActivePlayersNumber() != 1)
             return;
         Game.waitForSeconds(30);
         if(getActivePlayersNumber() == 1 && getLastPlayer() == name){
             FILEHelper.writeSucc();
             sendToClient(getLastPlayer(), new Message(MessageType.SHOW_EVENT, "win", "You have won because all the other players have disconnected"));
             Game.waitForSeconds(Game.waitTimer * 3);
             System.exit(0);
         }
     }
 
     /**
      * method that find the number of players which are currently connected to the game
      * @author Ettori
      * @return the number of connected players
      */
     private int getActivePlayersNumber(){return numPlayers - disconnectedPlayers.size();}
 
     /**
      * general method to respond to a client, it chooses the right network connection of the player
      * @author Ettori
      * @param i the index of the player to contact
      * @param msg the message that must be sent
      */
     public void sendToClient(int i, Message msg){
         if(i < 0 || i >= names.size())
             throw new RuntimeException("Sending to a NON existing player");
         if (disconnectedPlayers.contains(names.get(i)))
             return;
         //System.out.println("Sending " + msg.getType() + " to " + names.get(i));
         if (!rmiClients.containsKey(names.get(i)) || msg.getType() == MessageType.FINAL_SCORE) {
             try {
                 outStreams.get(i).writeObject(msg);
             } catch (IOException e) {
                 playerDisconnected(i, e);
             }
         } else {
             try {
                 rmiClients.get(names.get(i)).receivedEventRMI(msg);
             } catch (RemoteException e) {
                 playerDisconnected(i, e);
             }
         }
     }
 
     /******************************************** RMI ***************************************************************/
 
     /**
      * method called from remote used to add a client to the store of all the RMI clients
      * @author Ettori
      * @param name the nickname of the player
      * @param p the player object, passed as the remote interface
      */
     public void addClient(String name, PlayerI p){
         rmiClients.put(name, p);
     }
     /**
      * method called from remote which is equivalent to the waitMoveFromClient() method for the socket
      * @author Ettori
      * @param msg the message that the client want to send to the remote server
      */
     public void redirectToClientRMI(Message msg){
         switch (msg.getType()){
             case CHAT -> {
                 String from = (String)msg.getContent();
                 from = from.substring(0, from.indexOf(" "));
                 sendChatToClients(from, msg.getAuthor(), (String)msg.getContent());
             }
             case UPDATE_GAME -> {
                     for (int i = 0; i < numPlayers; i++) {
                         if (i != activePlayer)
                             sendToClient(i, msg);
                     }
                     PlayerSend p = (PlayerSend) msg.getContent();
                     for (int i = 0; i < numPlayers; i++) {
                         if (i == activePlayer)
                             continue;
                         players.get(i).board = p.board;
                         players.get(i).pointsMap.put(names.get(activePlayer), p.pointsUntilNow);
                         for (int j = 0; j < numPlayers - 1; j++) {
                             if (players.get(i).librariesOfOtherPlayers.get(j).name.equals(names.get(activePlayer)))
                                 players.get(i).librariesOfOtherPlayers.set(j, p.library);
                         }
                     }
                     players.set(activePlayer, p);
                     players.get(activePlayer).activeName = names.get(activePlayer);
                     FILEHelper.writeServer(this);
                     if (!rmiClients.containsKey(names.get(activePlayer)))
                         sendToClient(activePlayer, new Message(MessageType.STOP, null, null));
                 Game.waitForSeconds(Game.passTimer);
                 advanceTurn();
             }
             case PING, STOP ->{}
             default -> {
                 if(msg.getType() == MessageType.LIB_FULL && !endGameSituation)
                     endGameSituation = true;
                 if(msg.getType() == MessageType.UPDATE_UNPLAYABLE){
                     Board b = (Board) msg.getContent();
                     for (int i = 0; i < numPlayers; i++)
                         players.get(i).board = new Board(b);
                     FILEHelper.writeServer(this);
                 }
                 for (int i = 0; i < numPlayers; i++) {
                     if (i != activePlayer)
                         sendToClient(i,msg);
                 }
             }
         }
     }
 
     /**
      * method that allow the server to be pinged from an RMI client
      * @author Ettori
      */
     public void ping(){}
 
     /**
      * method that periodically pings all the current client connected with RMI
      * @author Ettori
      */
     public void pingRMI(){
         AtomicBoolean flag = new AtomicBoolean(false);  //particulat type of boolean: we use because is recommended for connections
         while(true){
             Game.waitForSeconds(Game.waitTimer * 2);
             for(String n: names){
                 if(!rmiClients.containsKey(n) || disconnectedPlayers.contains(n) || (endGameSituation && activePlayer == 0))
                     continue;
                 flag.set(false);
                 new Thread(() ->{
                     try {
                         rmiClients.get(n).pingClient();
                         flag.set(true);
                     } catch (RemoteException e) {
                         playerDisconnected(names.indexOf(n), e);
                     }
                 }).start();
                 Game.waitForSeconds(Game.fastTimer);
                 if(!flag.get())
                     playerDisconnected(names.indexOf(n), new RuntimeException("Player Disconnected"));
             }
         }
     }
 }