diff --git a/Meilenstein II/Protocol.txt b/Meilenstein II/Protocol.txt index 77ecf57..283c592 100644 --- a/Meilenstein II/Protocol.txt +++ b/Meilenstein II/Protocol.txt @@ -6,6 +6,8 @@ Implemented: * CPING Ping from client to server. * PINGB Pingback from client to server. * NAMEC$name Change name to whatever is specified + * STGAM start the Game + * CVOTE$position$vote Client at position has voted for position Future / planned: * CRTGM Create a new game @@ -22,8 +24,8 @@ Server Commands: Implemented: * SPING Ping from server to client * PINGB Pingback from client to server. - * GVOTR Ghosts: Please vote now - * HVOTR Humans: Please vote now + * GVOTR$position$msg Ghosts: Please vote now + * HVOTR$position$msg Humans: Please vote now Future / planned: * MSGRS "Message received": Paramaters: a string detailing to the client that and what the server received as command. diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/BudaLogConfig.java b/src/main/java/ch/unibas/dmi/dbis/cs108/BudaLogConfig.java index 89f880f..0d809eb 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/BudaLogConfig.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/BudaLogConfig.java @@ -19,7 +19,7 @@ public class BudaLogConfig { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); Configuration config = ctx.getConfiguration(); LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); - loggerConfig.setLevel(Level.DEBUG); // change level here + loggerConfig.setLevel(Level.INFO); // change level here ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig. } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ClientGameInfoHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ClientGameInfoHandler.java index 3fafbdd..27d09fb 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ClientGameInfoHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ClientGameInfoHandler.java @@ -11,10 +11,23 @@ import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; public class ClientGameInfoHandler { /** - * sends a msg "" to Server stating who voted for who, this being the Client that votes - * @param position the position of the passenger that is voted for + * All messages that are used in VoteHandler + * TODO(Seraina&Alex): Adjust strings to be more meaningful */ - public void vote(int position) { + //relevant: + public static final String ghostVoteRequest = "Vote on who to ghostify!"; + public static final String humanVoteRequest = "Vote for a ghost to kick off!"; + public static final String noiseNotification = "You heard some noise"; + public static final String gameOverHumansWin = "Game over: humans win!"; + public static final String gameOverGhostsWin = "Game over: ghosts win!"; + + //just messages + public static final String itsNightTime = "Please wait, ghosts are active"; + public static final String youGotGhostyfied = "You are now a ghost!"; + public static final String itsDayTime = "Please wait, humans are active"; + public static final String humansVotedFor = "Humans voted for: "; + public static final String isAHuman = " but they're a human!"; + public static final String gotKickedOff = " is a Ghost and got kicked off"; + - } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ClientVoteData.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ClientVoteData.java new file mode 100644 index 0000000..bcf72a9 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ClientVoteData.java @@ -0,0 +1,47 @@ +package ch.unibas.dmi.dbis.cs108.gamelogic; + +import java.util.Arrays; + +/** + * Data structure that is used to store clientVotes in an array, where the index correponds to the + * position in the train of the client. + */ + +public class ClientVoteData { + + private int[] vote; //saves vote of clientHandler for later transmission to passenger, by default MAX_VALUE, index corresponds to Passenger position + private boolean[] hasVoted; //saves hasVoted status of clientHandler for later transmission to passenger, by default false, index corresponds to Passenger position + + public ClientVoteData() { + int[] h = new int[6]; + Arrays.fill(h,Integer.MAX_VALUE); + this.vote = h; + this.hasVoted = new boolean[6]; + } + + public int[] getVote() { + return vote; + } + + public boolean[] getHasVoted() { + return hasVoted; + } + + /** + * Sets a vote value at the right position in the vote array + * @param position the index of the array + * @param vote the vote value + */ + public void setVote(int position, int vote) { + this.vote[position] = vote; + } + + /** + * Sets true or false at the right position in the hasVoted array + * @param position the index of the array + * @param hasVoted the vote state value + */ + public void setHasVoted(int position, boolean hasVoted) { + this.hasVoted[position] = hasVoted; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Game.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Game.java index 7b11695..460af73 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Game.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Game.java @@ -2,19 +2,29 @@ package ch.unibas.dmi.dbis.cs108.gamelogic; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.GhostNPC; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.GhostPlayer; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.HumanNPC; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.HumanPlayer; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; +import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; +import java.util.HashSet; import org.apache.logging.log4j.*; -public class Game { +public class Game implements Runnable { public static final Logger LOGGER = LogManager.getLogger(); public static final BudaLogConfig l = new BudaLogConfig(LOGGER); /** * Can be extended for optional Game-settings **/ - protected int nrOfPlayers; //sets the length of the train - protected int nrOfGhosts; // sets how many Ghosts we start witch + protected final int nrOfPlayers; //sets the length of the train + protected final int nrOfGhosts; // sets how many Ghosts we start witch protected int nrOfUsers; // safes how many clients are active in this Game - protected GameFunctions gameFunctions; + protected GameState gameState; + protected boolean isDay = false; //false means it is night, it is night by default + protected VoteHandler voteHandler = new VoteHandler(); + private ClientHandler clientHandler; //TODO: Figure out where Day/Night game state is saved maybe think about a game state class or smt. /** * Constructs a Game instance where: @@ -23,26 +33,107 @@ public class Game { * @param nrOfGhosts is the number of OG Ghosts you want to start with and * @param nrOfUsers is the number of active users at the time (non NPCs) */ - Game(int nrOfPlayers, int nrOfGhosts, int nrOfUsers) + public Game(ClientHandler clientHandler, int nrOfPlayers, int nrOfGhosts, int nrOfUsers) throws TrainOverflow { //ToDo: Who handles Exception how and where this.nrOfPlayers = nrOfPlayers; this.nrOfGhosts = nrOfGhosts; this.nrOfUsers = nrOfUsers; - this.gameFunctions = new GameFunctions(nrOfPlayers, nrOfGhosts, nrOfUsers); + this.gameState = new GameState(nrOfPlayers, nrOfGhosts, nrOfUsers); + this.clientHandler = clientHandler; } + public GameState getGameState() { + return gameState; + } - public static void main(String[] args) { + public int getNrOfGhosts() { + return nrOfGhosts; + } - try { + public int getNrOfPlayers() { + return nrOfPlayers; + } - Game game1 = new Game(6, 1, 1); + public int getNrOfUsers() { + return nrOfUsers; + } - } catch (TrainOverflow e) { - System.out.println(e.getMessage()); + public ClientHandler getClientHandler() { + return clientHandler; + } + + public boolean getIsDay() {return isDay;} + + public void setDay(boolean day) { + isDay = day; + } + + /** + * Starts a new game, creates a passenger array and saves it in gameState, sets the OG + * currently at gameState.train[3] fills the passengerTrain moving from left to rigth in the + * gameState.train array it connects clientHandlers witch the passengers in those positions + * (Players) and fills the rest with NPC's + * TODO: set ghost in a random position(i), gameState.train[i] so that a lone player can also start as a Ghost maybe use Train class + */ + @Override + public void run() { + LOGGER.info("the run-method has been called"); + int i = 0; + HashSet clients = ClientHandler.getConnectedClients(); + String gameOverCheck = ""; + int[] order = gameState.getTrain().orderOfTrain; + Passenger[] passengerTrain = gameState.getPassengerTrain(); + + + LOGGER.info(gameState.toString()); + for (ClientHandler client : clients) { + int index = order[i]; + if (passengerTrain[index].getIsGhost()) { //if there is a ghost + GhostPlayer ghostPlayer = new GhostPlayer(passengerTrain[index].getPosition(), + client.getClientUserName(), client, passengerTrain[index].getIsOG()); + gameState.getPassengerTrain()[index] = ghostPlayer; + } else { + HumanPlayer humanPlayer = new HumanPlayer(passengerTrain[index].getPosition(), + client.getClientUserName(), client, passengerTrain[index].getIsOG()); + gameState.getPassengerTrain()[index] = humanPlayer; + } + i++; + } + while (i < order.length) { + int index = order[i]; + if (passengerTrain[index].getIsGhost()) { //if they are a ghost + GhostNPC ghostNPC = new GhostNPC(passengerTrain[index].getPosition(), "NPC" + passengerTrain[index].getPosition(),passengerTrain[index].getIsOG()); + gameState.getPassengerTrain()[index] = ghostNPC; + } else { + //ToDo: give NPC nice usernames + HumanNPC humanNPC = new HumanNPC(passengerTrain[index].getPosition(), "NPC" + passengerTrain[index].getPosition()); + gameState.getPassengerTrain()[index] = humanNPC; + } + i++; + } + LOGGER.info(gameState.toString()); + + i = 0; + while (true) { //ToDo: was ist die Abbruchbedingung? VoteHandler muss das schicken. + if (!isDay) { + LOGGER.info("NIGHT"); + voteHandler.ghostVote(gameState.getPassengerTrain(), this); + setDay(true); + } else { + LOGGER.info("DAY"); + gameOverCheck = voteHandler.humanVote(gameState.getPassengerTrain(), this); + setDay(false); + } + if (gameOverCheck.equals(ClientGameInfoHandler.gameOverGhostsWin) || gameOverCheck.equals( + ClientGameInfoHandler.gameOverHumansWin)) { + clientHandler.broadcastAnnouncement(gameOverCheck); + return; + } } } - } + + + diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GameFunctions.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GameFunctions.java deleted file mode 100644 index 0ca27de..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GameFunctions.java +++ /dev/null @@ -1,60 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.gamelogic; - -import ch.unibas.dmi.dbis.cs108.BudaLogConfig; -import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Human; -import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class GameFunctions { - public static final Logger LOGGER = LogManager.getLogger(); - public static final BudaLogConfig l = new BudaLogConfig(LOGGER); - - /** - * Can be extended for optional Game-settings - **/ - int nrOfPlayers; //sets the length of the train - int nrOfGhosts; // sets how many Ghosts we start witch - int nrOfUsers; // safes how many clients are active in this Game - Train train; // safes who sits where - public Passenger[] passengerTrain; - - /** - * Constructs a GameFunctions instance where nrOfPlayers >= nrOfUsers. Fills passengerTrain with - * only humans - * - * @param nrOfPlayers is the length of the Train - * @param nrOfGhosts is the number of OG Ghosts you want to start with and - * @param nrOfUsers is the number of active users at the time (non NPCs) - * @throws TrainOverflow if nrOfPlayers < nrOfUsers - */ - GameFunctions(int nrOfPlayers, int nrOfGhosts, int nrOfUsers) - throws TrainOverflow { //ToDo: where will Exception be handled? - this.nrOfPlayers = nrOfPlayers; - this.nrOfGhosts = nrOfGhosts; - this.nrOfUsers = nrOfUsers; - this.train = new Train(nrOfPlayers, nrOfUsers); - Passenger[] passengerTrain = new Passenger[nrOfPlayers]; //Creates an array with Passengers with correlation positions (Train) - for (int i = 0; i < nrOfPlayers; i++) { - Human h = new Human(); - h.setPosition(train.orderOfTrain[i]); - passengerTrain[train.orderOfTrain[i]] = h; - } - this.passengerTrain = passengerTrain; - } - - public int getNrOfGhosts() { - return nrOfGhosts; - } - - public int getNrOfPlayers() { - return nrOfPlayers; - } - - public int getNrOfUsers() { - return nrOfUsers; - } - - - -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GameState.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GameState.java new file mode 100644 index 0000000..7ea236b --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GameState.java @@ -0,0 +1,142 @@ +package ch.unibas.dmi.dbis.cs108.gamelogic; + +import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Ghost; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Human; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; +import java.util.Arrays; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class GameState { + public static final Logger LOGGER = LogManager.getLogger(); + public static final BudaLogConfig l = new BudaLogConfig(LOGGER); + + /** + * Can be extended for optional Game-settings + **/ + public final int nrOfPlayers; //sets the length of the train + public final int nrOfGhosts; // sets how many Ghosts we start witch + public final int nrOfUsers; // safes how many clients are active in this Game + public final Train train; // safes who sits where + /** + * contains all Passengers on train, needs to be updated + */ + private Passenger[] passengerTrain; + /** + * Saves ClientVoteData, might not be used + */ + private ClientVoteData clientVoteData; + + + + /** + * Constructs a GameState instance where nrOfPlayers >= nrOfUsers. Fills passengerTrain with + * only humans + * + * @param nrOfPlayers is the length of the Train + * @param nrOfGhosts is the number of OG Ghosts you want to start with and + * @param nrOfUsers is the number of active users at the time (non NPCs) + * @throws TrainOverflow if nrOfPlayers < nrOfUsers + */ + GameState(int nrOfPlayers, int nrOfGhosts, int nrOfUsers) + throws TrainOverflow { //ToDo: where will Exception be handled? + this.nrOfPlayers = nrOfPlayers; + this.nrOfGhosts = nrOfGhosts; + this.nrOfUsers = nrOfUsers; + this.train = new Train(nrOfPlayers, nrOfUsers); + clientVoteData = new ClientVoteData(); + Passenger[] passengerTrain = new Passenger[nrOfPlayers]; //Creates an array with Passengers with correlation positions (Train) + for (int i = 0; i < nrOfPlayers; i++) { + if (i == 3) { //TODO: randomize via Train.ghostposition + Ghost g = new Ghost(); + g.setPosition(train.orderOfTrain[i]); + g.setGhost(); + g.setIsOG(true); + passengerTrain[train.orderOfTrain[i]] = g; + } else { + Human h = new Human(); + h.setPosition(train.orderOfTrain[i]); + passengerTrain[train.orderOfTrain[i]] = h; + } + } + + this.passengerTrain = passengerTrain; + } + + public Passenger[] getPassengerTrain() { + return passengerTrain; + } + + public Train getTrain() { + return train; + } + + + + /** + * Takes a given Passenger and puts it into the passengerTrain at a certain position + * @param passenger the new passenger being put into the train + * @param position the position of the passenger + */ + public void addNewPassenger(Passenger passenger, int position) { + passengerTrain[position] = passenger; + + } + + /** + * Converts the data in this passengerTrain into a human-readable string, + * where one can see who is a ghost and who is a human, who is a player and who an NPC + * @return a String that displays passengerTrain + */ + public String toString() { + Passenger[] array = passengerTrain; + StringBuilder stringBuilder = new StringBuilder(); + String[] print = new String[6]; + for (int i = 0; i < array.length; i++) { + if (array[i].getKickedOff()) { + print[i] = "-| kicked off: " + array[i].getPosition() + "|-"; + } else { + if (array[i].getIsPlayer()) { + if (array[i].getIsGhost()) { + print[i] = "-| ghostPlayer: " + array[i].getPosition() + "|-"; + } else { + print[i] = "-| humanPlayer: " + array[i].getPosition() + "|"; + } + } else { + if (array[i].getIsGhost()) { + print[i] = "-| ghostNPC: " + array[i].getPosition() + "|-"; + } else { + print[i] = "-| humanNPC: " + array[i].getPosition() + "|-"; + } + } + } + } + + for (int i = 0; i < array.length; i++) { + stringBuilder.append(print[i]); + } + return stringBuilder.toString(); + } + + /** + * Converts the data in this passengerTrain into a human-readable string, but it is anonymised for + * human players, so it is not obvious who is a human and who a ghost, only names and positions + * are displayed + * @return the String displaying an anonymised passengerTrain + */ + public String humanToString() { + Passenger[] array = passengerTrain; + StringBuilder stringBuilder = new StringBuilder(); + String[] print = new String[6]; + for (int i = 0; i < array.length; i++) { + print[i] = "-| " + array[i].getName() + ": " + array[i].getPosition() + "|-"; + } + + for (int i = 0; i < array.length; i++) { + stringBuilder.append(print[i]); + } + return stringBuilder.toString(); + } + +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GhostifyHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GhostifyHandler.java index f3ea707..902e08c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GhostifyHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/GhostifyHandler.java @@ -2,6 +2,7 @@ package ch.unibas.dmi.dbis.cs108.gamelogic; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Ghost; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.GhostNPC; import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.GhostPlayer; import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; import org.apache.logging.log4j.LogManager; @@ -19,13 +20,25 @@ public class GhostifyHandler { public Ghost ghost(Passenger p, Game game) { //TODO: Adjust for not only players but also npcs LOGGER.debug("Passenger Position " + p.getPosition()); - p.setGhost(); Ghost g; - g = new Ghost(); - g.setGhost(); - g.setPosition(p.getPosition()); - game.gameFunctions.passengerTrain[g.getPosition()] = g; - LOGGER.info("Passenger at position " + p.getPosition() + "has been ghostified"); + if (p.getIsPlayer()) { + p.setGhost(); + GhostPlayer ghostPlayer; + ghostPlayer = new GhostPlayer(p.getPosition(), p.getName(), p.getClientHandler(),p.getIsOG()); + ghostPlayer.setGhost(); + ghostPlayer.setPosition(p.getPosition()); + g = ghostPlayer; + } else { + p.setGhost(); + GhostNPC ghostNPC; + ghostNPC = new GhostNPC(p.getPosition(), p.getName(),p.getIsOG()); + ghostNPC.setGhost(); + ghostNPC.setPosition(p.getPosition()); + g = ghostNPC; + + } + game.gameState.addNewPassenger(g, g.getPosition()); + LOGGER.info("Passenger at position " + p.getPosition() + " has been ghostified"); return g; } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/NoiseHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/NoiseHandler.java index 0b3fc50..d5c7bc0 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/NoiseHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/NoiseHandler.java @@ -1,10 +1,37 @@ package ch.unibas.dmi.dbis.cs108.gamelogic; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Ghost; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; + /** * Determines who heard something (via Passenger Array currently in GameFunctions 'passengerTrain') * and broadcasts noise message to them (via ServerGameInfoHandler) */ - public class NoiseHandler { - + /** + * Notifies passengers in the train about a ghost walking by them. Differentiates between two + * cases: if the active ghost (predator) is to the right of his victim, the Passenger array is + * being walked through from right to left (from the predator's position back to the victim's + * position), otherwise the other way around. One call of noiseNotifier only deals with one + * predator infecting a victim, so if there are already multiple ghosts in the game, the method + * should be called for each of them individually. + * + * @param passengers passengers of the train the game is played in + * @param predator ghost that has infected a human player during this night (called upon as + * passenger for convenience reasons) + * @param victim human player who has been turned into a ghost this night + * @param game current game instance + */ + public void noiseNotifier(Passenger[] passengers, Passenger predator, Ghost victim, Game game) { + if (predator.getPosition() - victim.getPosition() + > 0) { // if predator is to the right of victim + for (int i = predator.getPosition() - 1; i > victim.getPosition(); i--) { + passengers[i].send(ClientGameInfoHandler.noiseNotification, game); + } + } else { // if predator is to the left of victim + for (int i = predator.getPosition() + 1; i < victim.getPosition(); i++) { + passengers[i].send(ClientGameInfoHandler.noiseNotification, game); + } + } + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ServerGameInfoHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ServerGameInfoHandler.java index 63740eb..1279b73 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ServerGameInfoHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/ServerGameInfoHandler.java @@ -1,6 +1,14 @@ package ch.unibas.dmi.dbis.cs108.gamelogic; +import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Ghost; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.GhostNPC; +import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.HumanNPC; import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; +import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; +import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Handles all communications Server to Client concerning game state or game state related requests @@ -10,26 +18,89 @@ import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; */ public class ServerGameInfoHandler { + public static final Logger LOGGER = LogManager.getLogger(); + public static final BudaLogConfig l = new BudaLogConfig(LOGGER); /** - * TODO(Seraina): Handle NPC's Maybe do that in Passenger send methode! - * Send a message "GVOTR" to a passenger urging them to vote for a human to infect - * Currently only handles only Players, so send a message to corresponding client - * @param passenger the passenger the message is meant to, should be a Ghost + * Gets a string msg from somewhere and formats it into protocol messages + * @param msg the message to be formatted + * @return a message in a protocol format */ - public void sendVoteRequestGhosts(Passenger passenger){ - passenger.getClientHandler().sendMsgToClient("GVOTR"); + public static String format(String msg, Passenger p, Game game) { + switch (msg) { + case ClientGameInfoHandler.ghostVoteRequest: + msg = Protocol.serverRequestsGhostVote + "$" + p.getPosition() +"$" + game.gameState.toString(); + break; + case ClientGameInfoHandler.humanVoteRequest: + msg = Protocol.serverRequestsHumanVote + "$" + p.getPosition() +"$"+ game.gameState.humanToString(); + break; + default: + msg = Protocol.printToClientConsole + "$"+ msg; + } + LOGGER.debug(msg); + return msg; } - /** - * TODO(Seraina): Handle NPC's - * Send a message "HVOTR" to a passenger urging them to vote for sm to kick off the train. - * Currently only handles only Players, so send a message to corresponding client - * @param passenger the passenger the message is meant to, can be either human or ghost + * Chooses for an NPC what they want to say, so they don't sound the same all the time + * @return a String saying that sm heard sm noise */ - public void sendVoteRequestHumans(Passenger passenger){ - passenger.getClientHandler().sendMsgToClient("HVOTR"); + public static String noiseRandomizer() { + String a = "I heard some noise tonight"; + String b = "noise"; + String c = "I heard smt strange tonight"; + String d = "Me, noise!"; + String e = "Uuuuh, spoky noises"; + int number = (int)(Math.random()*4); + switch (number) { + case 0: + return a; + case 1: + return d; + case 2: + return c; + case 3: + return e; + default: + return b; + } } + /** + * decides which action an GhostNpc needs to take, based on a message + * @param npc the GhostNpc needing to do smt + * @param msg the msg containing the information on what to do + * @param game the game the GhostNpc lives in (in gameState.passengerTrain) + */ + public static void ghostNpcParser(GhostNPC npc, String msg, Game game) { + switch (msg) { + case ClientGameInfoHandler.noiseNotification: + String outMsg = npc.getName() + ": " + noiseRandomizer(); + game.getClientHandler().broadcastNpcChatMessage(outMsg); + break; + case ClientGameInfoHandler.ghostVoteRequest: + npc.vote(game); + } + } + + /** + * decides which action an HumanNpc needs to take, based on a message + * @param npc the HumanNpc needing to do smt + * @param msg the msg containing the information on what to do + * @param game the game the HumanNpc lives in (in gameState.passengerTrain) + */ + public static void humanNpcParser(HumanNPC npc, String msg, Game game) { + switch (msg) { + case ClientGameInfoHandler.noiseNotification: + String outMsg = npc.getName() + ": " + noiseRandomizer();; + game.getClientHandler().broadcastNpcChatMessage(outMsg); + break; + case ClientGameInfoHandler.humanVoteRequest: + npc.vote(); + } + + + } + + } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Train.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Train.java index 7449484..852d748 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Train.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Train.java @@ -9,7 +9,7 @@ public class Train { public static final BudaLogConfig l = new BudaLogConfig(LOGGER); int[] orderOfTrain; //gives the random order in which the passengers enter the train - int positionOfGhost; + int positionOfGhost; // useful for randomization of og ghost position /** * Constructs a Train with orderOfTrain of the size nrOfPlayers, filled with a Random order of the @@ -68,11 +68,7 @@ public class Train { } public static void main(String[] args) { - try { - Train t = new Train(6, 1); - } catch (TrainOverflow e) { - LOGGER.error(e.getMessage()); - } - + System.out.println("Hallo"); } + } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/TrainOverflow.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/TrainOverflow.java index 0eaaab1..a44cb91 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/TrainOverflow.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/TrainOverflow.java @@ -1,5 +1,8 @@ package ch.unibas.dmi.dbis.cs108.gamelogic; +/** + * An exception that is thrown, if for some reason to many clients want to start a game + */ public class TrainOverflow extends Exception { private static final String message = "Too many users are logged on"; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/VoteHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/VoteHandler.java index f2ed804..ff85517 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/VoteHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/VoteHandler.java @@ -2,9 +2,7 @@ package ch.unibas.dmi.dbis.cs108.gamelogic; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Ghost; -import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.GhostPlayer; import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; -import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -23,6 +21,20 @@ public class VoteHandler { public static final Logger LOGGER = LogManager.getLogger(); public static final BudaLogConfig l = new BudaLogConfig(LOGGER); + private static ClientVoteData clientVoteData = new ClientVoteData(); + + public static ClientVoteData getClientVoteData() { + return clientVoteData; + } + + + + public static void setClientVoteData(ClientVoteData clientVoteData) { + clientVoteData = clientVoteData; + } + + + /** * Handles the ghost vote during nighttime: passengers who are ghosts are being asked on who to @@ -31,7 +43,10 @@ public class VoteHandler { * * @param passengers: passengers on the train */ + public void ghostVote(Passenger[] passengers, Game game) { + LOGGER.debug("ghostVote has been called"); + LOGGER.info(game.getGameState().toString()); // array to collect votes for all players during voting, i.e. votes for player 1 (passengers[0]) // are saved in @@ -39,60 +54,60 @@ public class VoteHandler { int[] votesForPlayers = new int[6]; // Walk through entire train, ask ghosts to ghostify and humans to wait - // TODO(Seraina): Messages in for-loop should probably be handled by ServerGameInfoHandler for (Passenger passenger : passengers) { if (passenger.getIsGhost()) { - passenger.send("Vote on who to ghostify!"); + passenger.send(ClientGameInfoHandler.ghostVoteRequest, game); } else { passenger.send( - "Please wait, ghosts are active"); // TODO(Seraina): make sure whatever clients send in + ClientGameInfoHandler.itsNightTime, game); // this time, except chat is ignored } } - for (Passenger passenger : passengers) { - // collecting the votes - distribute them among the vote counters for all players - // Note: Each voting collects votes for all players even though some might not be concerned - // (i.e. ghosts during ghost vote). Those players will then get 0 votes so it doesn't matter. - // TODO: Perhaps the vote results should be handled by ClientGameInfoHandler - if (passenger.getHasVoted()) { - for (int i = 0; i < votesForPlayers.length; i++) { - if (passenger.getVote() == i) { - votesForPlayers[i]++; - LOGGER.info(passengers[i] + " has received the most votes"); - } - } - } + try { // waits 20 seconds before votes get collected + Thread.sleep(30*1000); + } catch (InterruptedException e) { + LOGGER.warn("Thread " + Thread.currentThread() + " was interrupted"); } - // count the votes - determine which player has the most votes by going through the - // votesForPlayers array - int currentMax = 0; - for (int votesForPlayer : votesForPlayers) { - if (votesForPlayer > currentMax) { - currentMax = votesForPlayer; - } - } - LOGGER.info("Most votes: " + currentMax + " vote"); + int currentMax = voteEvaluation(passengers, votesForPlayers, clientVoteData, game); + + LOGGER.debug("Most votes: " + currentMax + " vote"); // ghostify the player with most votes int ghostPosition = 0; for (int i = 0; i < votesForPlayers.length; i++) { if (votesForPlayers[i] == currentMax) { // if player at position i has most votes ghostPosition = i; - LOGGER.info("Most votes for Passenger " + i); + LOGGER.debug("Most votes for Passenger " + i); } } - LOGGER.debug("ghostPosition: " + ghostPosition); + LOGGER.info("Most votes for: " + ghostPosition); GhostifyHandler gh = new GhostifyHandler(); Ghost g = gh.ghost(passengers[ghostPosition], game); passengers[ghostPosition] = g; passengers[ghostPosition].send( - "You are now a ghost!"); // TODO: ServerGameInfoHandler might deal with this one + ClientGameInfoHandler.youGotGhostyfied, game); // TODO: ServerGameInfoHandler might deal with this one + try { // waits 20 seconds before votes get collected + Thread.sleep(10); + } catch (InterruptedException e) { + LOGGER.warn("Thread " + Thread.currentThread() + " was interrupted"); + } + /* notify passengers the ghosts passed by - for each ghost that ghostified a player, an instance of NoiseHandler + is being created and the passengers this ghost passed by are being notified. The player who's just been ghostified + is ignored since he didn't participate in this night's ghostification. */ + for (int i = 0; i < passengers.length; i++) { + if (passengers[i].getIsGhost() && i != ghostPosition) { + NoiseHandler n = new NoiseHandler(); + n.noiseNotifier(passengers, passengers[i], g, game); + } + } + + LOGGER.info(game.getGameState().toString()); // set hasVoted to false for all passengers for future votings for (Passenger passenger : passengers) { passenger.setHasVoted(false); @@ -104,11 +119,12 @@ public class VoteHandler { * ghosts are waiting. Votes are being collected, vote results are being handled in three possible * ways: if passenger who was voted for is a human, continue with next ghost vote; if it's a * normal ghost, kick him off; if it's the OG ghost, end game, humans win. - * - * @param passengers: train passengers + * @return Returns an empty String by default, returns a complex string when game is over: + * "Game over: ghosts win!" or "Game over: humans win!" + * @param passengers train passengers */ - public void humanVote(Passenger[] passengers, Game game) { - + public String humanVote(Passenger[] passengers, Game game) { + LOGGER.info(game.getGameState().toString()); // array to collect votes for all players during voting, i.e. votes for player 1 are saved in // votesForPlayers[0] @@ -118,51 +134,39 @@ public class VoteHandler { // TODO: Messages in for-loop should probably be handled by ServerGameInfoHandler for (Passenger passenger : passengers) { if (passenger.getIsGhost()) { - passenger.send("Please wait, humans are active"); + passenger.send(ClientGameInfoHandler.itsDayTime, game); } else { - passenger.send("Vote for a ghost to kick off!"); + passenger.send(ClientGameInfoHandler.humanVoteRequest, game); } } - for (Passenger passenger : passengers) { - // collecting the votes - distribute them among the vote counters for all players - // TODO: Perhaps the vote results should be handled by ClientGameInfoHandler - if (passenger.getHasVoted()) { - for (int i = 0; i < votesForPlayers.length; i++) { - if (passenger.getVote() == i) { - votesForPlayers[i]++; - } - } - } + try { // waits 20 seconds before votes get collected + Thread.sleep(20*1000); + } catch (InterruptedException e) { + LOGGER.warn("Thread " + Thread.currentThread() + " was interrupted"); } - // count the votes - determine which player has the most votes by going through the - // votesForPlayers array - int currentMax = 0; - for (int votesForPlayer : votesForPlayers) { - if (votesForPlayer > currentMax) { - currentMax = votesForPlayer; - LOGGER.info("Max amount of votes: " + currentMax); - } - } + int currentMax = voteEvaluation(passengers, votesForPlayers, clientVoteData, game); + // deal with voting results int voteIndex = 0; for (int i = 0; i < votesForPlayers.length; i++) { if (votesForPlayers[i] == currentMax) { // if player has most votes voteIndex = i; - LOGGER.info("Player " + voteIndex + " has the most votes"); } } + LOGGER.info("Player " + voteIndex + " has the most votes"); if (!passengers[voteIndex] .getIsGhost()) { // if player with most votes is human, notify everyone about it for (Passenger passenger : passengers) { passenger.send( - "You voted for a human!"); // TODO: ServerGameInfoHandler might be better to use here + ClientGameInfoHandler.humansVotedFor + voteIndex + ClientGameInfoHandler.isAHuman, game); // TODO: ServerGameInfoHandler might be better to use here } } if (passengers[voteIndex].getIsGhost()) { // if player is a ghost if (passengers[voteIndex].getIsOG()) { // if ghost is OG --> end game, humans win - System.out.println("Game over: humans win!"); // TODO: correctly handle end of game + System.out.println(ClientGameInfoHandler.gameOverHumansWin); // TODO: correctly handle end of game + return ClientGameInfoHandler.gameOverHumansWin; } else { /* Special case: if ghost is not OG and if only one human is left (--> last human didn't vote for OG ghost), ghosts win. @@ -174,20 +178,23 @@ public class VoteHandler { } } if (humans == 1) { - System.out.println("Game over: ghosts win!"); + System.out.println(ClientGameInfoHandler.gameOverGhostsWin); + return ClientGameInfoHandler.gameOverGhostsWin; } // Usual case: there is more than one human left and a normal ghost has been voted for --> // kick this ghost off passengers[voteIndex].setKickedOff(true); for (Passenger passenger : passengers) { - passenger.send("Player " + voteIndex + " has been kicked off!"); + passenger.send("Player " + voteIndex + ClientGameInfoHandler.gotKickedOff, game); } } } + LOGGER.info(game.getGameState().toString()); // set hasVoted to false for all passengers for future voting for (Passenger passenger : passengers) { passenger.setHasVoted(false); } + return ""; } /** @@ -216,38 +223,34 @@ public class VoteHandler { } - public static void main(String[] args) { - try { - Game game = new Game(6,1, 6); - VoteHandler voteHandler = new VoteHandler(); - - Passenger[] testArray = game.gameFunctions.passengerTrain; - Passenger ghost = new Ghost(); - testArray[3] = ghost; - testArray[3].setGhost(); - testArray[3].setIsOg(); - testArray[3].setPosition(3); - print(testArray); - LOGGER.info("NIGHT"); - voteHandler.ghostVote(testArray,game); - print(testArray); - - LOGGER.info("Day"); - voteHandler.humanVote(testArray, game); - print(testArray); - - LOGGER.info("NIGHT"); - voteHandler.ghostVote(testArray,game); - print(testArray); - - LOGGER.info("Day"); - voteHandler.humanVote(testArray, game); - print(testArray); - } catch (TrainOverflow e) { - LOGGER.warn(e.getMessage()); + /** + * Collecting the votes - distribute them among the vote counters for all players. Note: each voting collects + * votes for all players even though some might not be concerned (i.e. ghosts during ghost vote). Those players + * will then get 0 votes so it dosen't matter. Returns the max amount of votes a player received. + * @param passengers train passengers + * @param votesForPlayers array collecting the votes each player received during a voting + * @param data deals with Client votes + * @param game current game instance + */ + int voteEvaluation(Passenger[] passengers, int[] votesForPlayers, ClientVoteData data, Game game) { + for (Passenger passenger : passengers) { + passenger.getVoteFromGameState(data, game); + if (passenger.getHasVoted()) { + for (int i = 0; i < votesForPlayers.length; i++) { + if (passenger.getVote() == i) { + votesForPlayers[i]++; + } + } + } } - - - + /* count the votes - determine which player has the most votes by going through the + votesForPlayers array */ + int currentMax = 0; + for (int votesForPlayer : votesForPlayers) { + if (votesForPlayer > currentMax) { + currentMax = votesForPlayer; + } + } + return currentMax; } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Ghost.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Ghost.java index 2b7a53b..ad6e2e4 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Ghost.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Ghost.java @@ -1,6 +1,7 @@ package ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -8,6 +9,8 @@ public class Ghost extends Passenger { public static final Logger LOGGER = LogManager.getLogger(); public static final BudaLogConfig l = new BudaLogConfig(LOGGER); + + public boolean getIsOG() { return isOG; } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostNPC.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostNPC.java index 678659b..d4918d7 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostNPC.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostNPC.java @@ -1,6 +1,10 @@ package ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; +import ch.unibas.dmi.dbis.cs108.gamelogic.GameState; +import ch.unibas.dmi.dbis.cs108.gamelogic.ServerGameInfoHandler; +import ch.unibas.dmi.dbis.cs108.gamelogic.TrainOverflow; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -28,4 +32,43 @@ public class GhostNPC extends Ghost { this.name = name; } } + + @Override + public void send(String msg, Game game) { + ServerGameInfoHandler.ghostNpcParser(this, msg, game); + } + + /** + * Sets vote of this Ghost position on a number between 0 and 5, + * but only for positions where there aren't any ghosts and sets hasVoted to true + * TODO: Make NPC smarter + */ + public void vote(Game game){ + int ghostCounter = 0; + Passenger[] train = game.getGameState().getPassengerTrain(); + for(Passenger passenger : train) { + if(passenger.isGhost) { + ghostCounter++; + } + } + int[] humanPositions = new int[game.getNrOfPlayers() - ghostCounter ]; + int j = 0; + for (int i = 0; i < train.length; i++) { + if (!train[i].isGhost) { //is human + humanPositions[j] = train[i].getPosition(); + j++; + } + } + int randomPosition = (int) (Math.random()*humanPositions.length); + vote = humanPositions[randomPosition]; + hasVoted = true; + LOGGER.info("GhostNPC at Position: " + this.getPosition() + " has voted for: " + vote); + } + + /** + * Decides what to do when a noise ist heard, currently just always broadcasts it + * TODO: Make NPC smarter + */ + + } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostPlayer.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostPlayer.java index 86f7634..1d7133e 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostPlayer.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/GhostPlayer.java @@ -1,6 +1,9 @@ package ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.ClientVoteData; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; +import ch.unibas.dmi.dbis.cs108.gamelogic.ServerGameInfoHandler; import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -12,8 +15,7 @@ public class GhostPlayer extends Ghost { /** * Creates a new GhostPlayer. Should be used at game start or if a HumanPlayer is turned into a * ghost. - * - * @param position position on the train + * @param position position on the train * @param name name. if null, then a default name is used. * @param isOG true if the ghost is the original ghost. */ @@ -31,8 +33,36 @@ public class GhostPlayer extends Ghost { } } - - public void send(String msg) { - //todo(Jonas): pass message along to client. + /** + * Sends a message to the client handled bye this client handler + * TODO: does this also work with 2 clients? + * @param msg the message that is sent to this player. + * @param game the game the GhostPlayer lives on (in game.gameState.passengerTrain) + */ + @Override + public void send(String msg, Game game) { + String formattedMsg = ServerGameInfoHandler.format(msg, this, game); + clientHandler.sendMsgToClient(formattedMsg); } + + /** + * Gets the voting information vote and hasVoted from clientHandler and this values to those values. + * Sets clientHandler fields to default: vote = Integer.MAX_VALUE , hasVoted = false + */ + @Override + public void getVoteFromGameState(ClientVoteData clientVoteData, Game game) { + vote = clientVoteData.getVote()[position]; + hasVoted = clientVoteData.getHasVoted()[position]; + clientVoteData.setVote(position,Integer.MAX_VALUE); + clientVoteData.setHasVoted(position,false); + LOGGER.info("Ghost at Pos: " + position + " has voted for: " + vote); + /* + * if vote wasn't valid, make sure, the passenger field hasVoted == false, probably redundant but better be safe than sorry + */ + if(vote == Integer.MAX_VALUE) { + hasVoted = false; + } + } + } + diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Human.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Human.java index 32cf43c..ff86df7 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Human.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Human.java @@ -1,6 +1,7 @@ package ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -8,4 +9,5 @@ public class Human extends Passenger { public static final Logger LOGGER = LogManager.getLogger(); public static final BudaLogConfig l = new BudaLogConfig(LOGGER); + } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanNPC.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanNPC.java index de59581..04d2674 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanNPC.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanNPC.java @@ -1,10 +1,13 @@ package ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; +import ch.unibas.dmi.dbis.cs108.gamelogic.ServerGameInfoHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class HumanNPC extends Human { + public static final Logger LOGGER = LogManager.getLogger(); public static final BudaLogConfig l = new BudaLogConfig(LOGGER); @@ -26,4 +29,27 @@ public class HumanNPC extends Human { this.name = name; } } + + /** + * Sends a msg to the ServerGameInfoHandler.humanNpcParser to decide what has to happen now + * + * @param msg the message that is sent to this player. + * @param game the game the HumanNPC lives on (in game.gameState.passengerTrain) + */ + @Override + public void send(String msg, Game game) { + ServerGameInfoHandler.humanNpcParser(this, msg, game); + } + + /** + * Currently returns a random integer for voting + * + * @return integer between 0 and 5 + */ + public void vote() { + int randomNr = (int) (Math.random() * 6); + vote = randomNr; + hasVoted = true; + LOGGER.info("HumanNPC at Position: " + this.getPosition() + " has voted for: " + vote); + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanPlayer.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanPlayer.java index ae57655..2f257cb 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanPlayer.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/HumanPlayer.java @@ -1,7 +1,11 @@ package ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.ClientVoteData; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; +import ch.unibas.dmi.dbis.cs108.gamelogic.ServerGameInfoHandler; import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; +import java.util.Arrays; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -12,8 +16,7 @@ public class HumanPlayer extends Human { /** * Creates a new GhostPlayer. Should be used at game start or if a HumanPlayer is turned into a * ghost. - * - * @param position position on the train + * @param position position on the train * @param name name. if null, then a default name is used. */ public HumanPlayer(int position, String name, ClientHandler clientHandler, boolean isOG) { @@ -29,5 +32,39 @@ public class HumanPlayer extends Human { } } + /** + * Sends a message to the client handled bye this client handler + * TODO: does this also work with 2 clients? + * @param msg the message that is sent to this player. + * @param game the game the HumanPlayer lives on (in game.gameState.passengerTrain) + */ + @Override + public void send(String msg, Game game) { + String formattedMsg = ServerGameInfoHandler.format(msg,this, game); + clientHandler.sendMsgToClient(formattedMsg); + } + /** + * Gets the voting information vote and hasVoted from clientHandler and this values to those values. + * Sets clientHandler fields to default: vote = Integer.MAX_VALUE , hasVoted = false + */ + @Override + public void getVoteFromGameState(ClientVoteData clientVoteData, Game game) { + if(game.getIsDay()) { + LOGGER.debug(Arrays.toString(clientVoteData.getVote())); + LOGGER.debug("method was called by: " + position); + vote = clientVoteData.getVote()[position]; + LOGGER.info("Human at Pos: " + position + " has voted for: " + vote); + hasVoted = clientVoteData.getHasVoted()[position]; + LOGGER.debug(Arrays.toString(clientVoteData.getVote())); + clientVoteData.setVote(position, Integer.MAX_VALUE); + clientVoteData.setHasVoted(position, false); + /* + * if vote wasn't valid, make sure, the passenger field hasVoted == false, probably redundant but better be safe than sorry + */ + if (vote == Integer.MAX_VALUE) { + hasVoted = false; + } + } + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Passenger.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Passenger.java index aa52c83..c3d3462 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Passenger.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Passenger.java @@ -1,6 +1,8 @@ package ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.ClientVoteData; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; import ch.unibas.dmi.dbis.cs108.gamelogic.ServerGameInfoHandler; import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; import org.apache.logging.log4j.LogManager; @@ -19,32 +21,7 @@ public class Passenger { protected ClientHandler clientHandler;//the socket for the client associated with this Passenger, for NPCs, this can be null. protected boolean hasVoted; //true if the player gave his vote during voting time protected int vote; //saves the number of the player this passenger voted for during voting (0-5) - int sendcounter = 0; - /** - * Sends a protocol message to the respective player or NPC. - * @param msg the message that is sent to this player. - **/ - public void send(String msg) { - sendcounter++; - if (msg.equals("Vote on who to ghostify!")) { - vote = 1*sendcounter; - hasVoted = true; // for testing, when is it set to false again? - LOGGER.info("Voted for Position " + vote); - } else if(msg.equals("Vote for a ghost to kick off!")) { - vote = (int) (0.5*sendcounter); - hasVoted = true; // for testing, when is it set to false again? - LOGGER.info("Voted for Position " + vote); - } else { - LOGGER.debug(msg); - } - /*if (isPlayer) { - //TODO: maybe put a formatter here, so protocol msg are only send between sever-client - clientHandler.sendMsgToClient(msg); //ToDO(Seraina): Make sure only the right kind of messages are sent - } else { //is a NPC - //TODO: call a method that identifies message for NPC and calls respective methode NPCParser - }*/ - } /** * sets the Position of this passenger @@ -82,6 +59,10 @@ public class Passenger { hasVoted = voted; } + public void setVote(int vote) { + this.vote = vote; + } + public void setIsOg() { isOG = true; } @@ -116,4 +97,24 @@ public class Passenger { public ClientHandler getClientHandler() { return clientHandler; } + + /** + * When called by NPC nothing should happen, because clientHandler = null + */ + public void getVoteFromGameState(ClientVoteData clientVoteData,Game game) { + LOGGER.debug("a NPC called this method hopefully: " + position); + } + + /** + * Sends a protocol message to the respective player or NPC. + * @param msg the message that is sent to this player. + **/ + public void send(String msg, Game game) { + if (isPlayer) { + String formattedMsg = ServerGameInfoHandler.format(msg,this,game); + clientHandler.sendMsgToClient(formattedMsg); + } + LOGGER.warn("This object should not just be a passenger. Position:" + position); + } + } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java index 122c17a..85a783f 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java @@ -5,11 +5,14 @@ import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.ClientPinger; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; +import ch.unibas.dmi.dbis.cs108.multiplayer.server.JServerProtocolParser; import java.net.Socket; import java.io.*; import java.net.UnknownHostException; import java.util.Objects; import java.util.Scanner; +import java.util.Timer; +import java.util.TimerTask; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -23,6 +26,11 @@ public class Client { private BufferedWriter out; public ClientPinger clientPinger; + /** + * Saves the position of the client, gets refreshed everytime the client gets a vote request. + */ + int position = Integer.MAX_VALUE; + public Client(Socket socket) { try { this.socket = socket; @@ -58,7 +66,7 @@ public class Client { try { if (bfr.ready()) { String msg = bfr.readLine(); - String formattedMSG = MessageFormatter.formatMsg(msg); + String formattedMSG = MessageFormatter.formatMsg(msg, position); sendMsgToServer(formattedMSG); } Thread.sleep(5); @@ -73,6 +81,30 @@ public class Client { } + /** + * Tells user to enter a position to vote for passenger at that position + */ + public void positionSetter(String msg) { + + LOGGER.info("Im in thread:" + Thread.currentThread()); + int msgIndex = msg.indexOf('$'); + String pos = msg.substring(0, msgIndex); + try { + position = Integer.parseInt(pos); + } catch (NumberFormatException e) { + LOGGER.warn("Position got scrabbled on the way here"); + } + String justMsg = msg.substring(msgIndex + 1); + + System.out.println(justMsg); + System.out.println("Please enter your vote"); + + + //LOGGER.debug("just checked next line"); + } + + + /** * Starts a thread which listens for incoming chat messages / other messages that the user * has to see @@ -92,7 +124,7 @@ public class Client { } else { System.out.println("chatMsg is null"); throw new IOException();} } catch (IOException e) { //e.printStackTrace(); - LOGGER.debug("Exception while trying to read message: " + e.getMessage()); + LOGGER.warn("Exception while trying to read message: " + e.getMessage()); disconnectFromServer(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java index bb3a7ab..3af6d14 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java @@ -43,11 +43,15 @@ public class JClientProtocolParser { c.disconnectFromServer(); break; case Protocol.serverRequestsGhostVote: - System.out.println("Ghost received Vote request"); + LOGGER.debug("Ghost received Vote request"); + System.out.println("Ghost Vote:"); + c.positionSetter(msg.substring(6)); //TODO(Seraina): How can be enforced, that clients won't vote otherwise? Trigger a methode here that listens to input break; case Protocol.serverRequestsHumanVote: - System.out.println("Human received Vote request"); + LOGGER.debug("Human received Vote request"); + System.out.println("Human Vote:"); + c.positionSetter(msg.substring(6)); //TODO(Seraina): How can be enforced, that clients won't vote otherwise? Trigger a methode here that listens to input break; default: diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java index 5758e17..7679536 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java @@ -18,7 +18,7 @@ public class MessageFormatter { * @return the reformatted message in the form HEADR$msg */ - public static String formatMsg(String msg) { + public static String formatMsg(String msg, int position) { String header = ""; //header is first two characters StringBuilder stringBuilder = new StringBuilder(); String s = ""; // just a friendly helper to save message in @@ -59,10 +59,20 @@ public class MessageFormatter { stringBuilder.append(Protocol.listLobbies + "$"); s = ""; //Command has no parameters break; + case "/v": + try { + s = msg.substring(3); + LOGGER.debug("substring: " + s); + } catch (Exception e) { + System.out.println("invalid vote"); + } + stringBuilder.append(Protocol.votedFor + "$" + position + "$"); + break; default: s = msg; } stringBuilder.append(s); + LOGGER.debug(stringBuilder.toString()); return stringBuilder.toString(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ClientPinger.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ClientPinger.java index 6e0f2c2..d73b209 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ClientPinger.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ClientPinger.java @@ -35,6 +35,7 @@ public class ClientPinger implements Runnable { @Override public void run() { + Thread.currentThread().setPriority(10); try { Thread.sleep(20000); while (socket.isConnected() && !socket.isClosed()) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java index e845d8d..e40e3ff 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java @@ -92,6 +92,16 @@ public class Protocol { */ public static final String listLobbies = "LISTL"; + /** + * A Client decides to start the game. + */ + public static final String startANewGame = "STGAM"; + + /** + * Client informs server that they have voted and delivers this vote in the form of "CVOTE$position$vote" + */ + public static final String votedFor = "CVOTE"; + //SERVER TO CLIENT COMMANDS @@ -117,7 +127,8 @@ public class Protocol { public static final String serverConfirmQuit = "QUITC"; /** - * The server requests the client (who should be a ghost) to vote on the victim. + * The server requests the client (who should be a ghost) to vote on the victim. in the format GVOTR$string + * the current train will be shown as a string to the client */ public static final String serverRequestsGhostVote = "GVOTR"; @@ -128,4 +139,6 @@ public class Protocol { public static final String serverRequestsHumanVote = "HVOTR"; + + } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt index b050d31..550996a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt @@ -11,6 +11,7 @@ Implemented: * PINGB Pingback from client to server. * NAMEC$name Change name to whatever is specified * CRTGM Create a new game + * Future / planned: diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ServerPinger.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ServerPinger.java index 34da9e0..cf758c5 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ServerPinger.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/ServerPinger.java @@ -37,6 +37,7 @@ public class ServerPinger implements Runnable { @Override public void run() { + Thread.currentThread().setPriority(10); try { Thread.sleep(2000); while (socket.isConnected() && !socket.isClosed()) { @@ -55,9 +56,8 @@ public class ServerPinger implements Runnable { System.out.println( "Lost connection to user " + c.getClientUserName() + ". Waiting to reconnect..."); } else { - c.disconnectClient(); - LOGGER.debug( - "gotPingBack has not been set to true and isConnected has been set to false before"); + //c.disconnectClient(); TODO: is that ever necessary? + //LOGGER.debug("gotPingBack has not been set to true and isConnected has been set to false before"); } } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java index 60d6ee3..e6473ee 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java @@ -1,11 +1,15 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.server; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; +import ch.unibas.dmi.dbis.cs108.gamelogic.TrainOverflow; +import ch.unibas.dmi.dbis.cs108.gamelogic.VoteHandler; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.ServerPinger; import java.io.*; import java.net.InetAddress; import java.net.Socket; +import java.util.Arrays; import java.util.HashSet; import java.util.Scanner; import org.apache.logging.log4j.LogManager; @@ -21,6 +25,7 @@ public class ClientHandler implements Runnable { private Socket socket; private InetAddress ip; + /** * notes if the client has formally logged in yet. If connecting through the normal Client class, * the client is logged in automatically, if connecting though some external application, the @@ -70,6 +75,10 @@ public class ClientHandler implements Runnable { return socket; } + /** + * Needed to fill a train with client TODO: how do lobbies fit here? + * @return the HashSet of Connected Clients + */ public static HashSet getConnectedClients() { return connectedClients; } @@ -86,14 +95,14 @@ public class ClientHandler implements Runnable { return loggedIn; } - public void setLoggedIn(boolean loggedIn) { - this.loggedIn = loggedIn; - } - - //Setters: public String getClientUserName() { return clientUserName; } + //Setters: + + public void setLoggedIn(boolean loggedIn) { + this.loggedIn = loggedIn; + } @Override @@ -149,6 +158,17 @@ public class ClientHandler implements Runnable { } } + /** + * Broadcasts a pseudo chat Message from a NPC to all active clients + * + * @param msg the Message to be broadcast + */ + public void broadcastNpcChatMessage(String msg) { + for (ClientHandler client : connectedClients) { + client.sendMsgToClient(Protocol.printToClientConsole + "$" + msg); + } + } + /** * Broadcasts a non-chat Message to all active clients. This can be used for server * messages / announcements rather than chat messages. The message will be printed to the user @@ -180,6 +200,44 @@ public class ClientHandler implements Runnable { } } + /** + * Takes a msg of the form position$vote and extracts vote and position from it and saves it + * in VoteHandler.getClientVoteData + * @param msg the messaged to decode + */ + public void decodeVote(String msg){ + int msgIndex = msg.indexOf('$'); + int vote = Integer.MAX_VALUE; + int position = 0; + LOGGER.debug("Message is " + msg); + try { + position = Integer.parseInt(msg.substring(0,msgIndex)); + vote = Integer.parseInt(msg.substring(msgIndex + 1)); + LOGGER.debug("Vote is:" + vote); + } catch (Exception e) { + LOGGER.warn("Invalid vote " + e.getMessage()); + } + LOGGER.debug("Vote is:" + vote); + if(vote != Integer.MAX_VALUE) { //gets MAX_VALUE when the vote wasn't valid + VoteHandler.getClientVoteData().setVote(position,vote); + LOGGER.debug("Player vote: " + vote); + VoteHandler.getClientVoteData().setHasVoted(position,true); //TODO: move clientVoteData to gamestate + } + } + + /** + * Initializes a new Game instance and starts its run method in a new thread + */ + public void startNewGame() { + try { + Game game = new Game(this,6,1, ClientHandler.getConnectedClients().size()); + Thread t = new Thread(game); + t.start(); + } catch (TrainOverflow e) { + LOGGER.warn(e.getMessage()); + } + } + /** * Removes & disconnects the client. To be used if a severe connection loss is detected (i.e. if trying to * send / receive a message throws an exception, not just if ping-pong detects a connection loss). diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java index 0983bd4..df365a4 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java @@ -2,6 +2,11 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.server; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.gamelogic.ClientVoteData; +import ch.unibas.dmi.dbis.cs108.gamelogic.Game; +import ch.unibas.dmi.dbis.cs108.gamelogic.GameState; +import ch.unibas.dmi.dbis.cs108.gamelogic.TrainOverflow; +import ch.unibas.dmi.dbis.cs108.gamelogic.VoteHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; @@ -30,6 +35,9 @@ public class JServerProtocolParser { String header = ""; //"header" is the first 5 characters, i.e. the protocol part try { header = msg.substring(0, 5); + if(!header.equals(Protocol.pingBack) &&!header.equals(Protocol.pingFromClient)) { //for debuging without constant pings + LOGGER.debug("got message: " + msg + "."); + } } catch (IndexOutOfBoundsException e) { System.out.println("Received unknown command"); } @@ -65,7 +73,15 @@ public class JServerProtocolParser { break; case Protocol.listLobbies: //TODO: add action - LOGGER.debug(Protocol.listLobbies + " command recieved from: " + h.getClientUserName()); + LOGGER.debug(Protocol.listLobbies + " command received from: " + h.getClientUserName()); + break; + case Protocol.votedFor: + LOGGER.debug("Made it here"); + msg = msg.substring(6); + h.decodeVote(msg); + break; + case Protocol.startANewGame: + h.startNewGame(); break; default: System.out.println("Received unknown command");