From 7109ed113fd987f4d81bf9de3af4c4b4d3a855d2 Mon Sep 17 00:00:00 2001 From: Seraina Date: Sat, 30 Apr 2022 16:52:04 +0200 Subject: [PATCH] Commits maybe need to be added to merge with GUI_MilestoneIV is possible --- OgGhostWinners.txt | 1 + .../unibas/dmi/dbis/cs108/BudaLogConfig.java | 2 +- .../unibas/dmi/dbis/cs108/gamelogic/Game.java | 31 +++- .../gamelogic/ServerGameInfoHandler.java | 12 +- .../klassenstruktur/GhostPlayer.java | 9 +- .../klassenstruktur/HumanPlayer.java | 9 +- .../gamelogic/klassenstruktur/Spectator.java | 2 +- .../dbis/cs108/multiplayer/client/Client.java | 104 +++++++++--- .../client/JClientProtocolParser.java | 13 +- .../client/gui/game/GameController.java | 155 +++++++++++++++--- .../multiplayer/helpers/GuiParameters.java | 45 +++++ .../cs108/multiplayer/helpers/Protocol.java | 13 +- .../multiplayer/server/ClientHandler.java | 33 +++- .../server/JServerProtocolParser.java | 8 + .../client/gui/game/DayOpen/bell.png | Bin 0 -> 13596 bytes .../client/gui/game/GameDayAll.fxml | 10 ++ 16 files changed, 386 insertions(+), 61 deletions(-) create mode 100644 OgGhostWinners.txt create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/GuiParameters.java create mode 100644 src/main/resources/ch/unibas/dmi/dbis/cs108/multiplayer/client/gui/game/DayOpen/bell.png diff --git a/OgGhostWinners.txt b/OgGhostWinners.txt new file mode 100644 index 0000000..223b783 --- /dev/null +++ b/OgGhostWinners.txt @@ -0,0 +1 @@ +B 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 2da3348..0ee0b70 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.ERROR); // 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/Game.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/Game.java index 27f3f04..670ec53 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 @@ -8,6 +8,8 @@ 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.highscore.OgGhostHighScore; +import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.GuiParameters; +import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; import ch.unibas.dmi.dbis.cs108.multiplayer.server.Lobby; import java.util.HashSet; @@ -76,6 +78,29 @@ public class Game implements Runnable { return null; } + public Game getGame() { + return this; + } + + /** + * Initializes new thread that constantly sends a gameState update to all clients in this game + */ + public void gameStateModelUpdater(){ + new Thread(() -> { + while (getGame().isOngoing) { + for (Passenger passenger : getGameState().getPassengerTrain()) { + passenger.send(GuiParameters.updateGameState, getGame()); + } + try { + Thread.sleep(4000); //TODO: Is this a good intervall? + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + + } + /** * 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 @@ -119,23 +144,27 @@ public class Game implements Runnable { i++; } LOGGER.info(gameState.toString()); + gameStateModelUpdater(); //TODO: does that work? i = 0; - while (isOngoing) {//game cycle + while (isOngoing) {//game cycle TODO: maybe check that more often inside game loop?! if (!isDay) { LOGGER.info("NIGHT"); gameOverCheck = voteHandler.ghostVote(gameState.getPassengerTrain(), this); setDay(true); + lobby.getAdmin().sendMsgToClientsInLobby(Protocol.printToGUI + "$" + ClientGameInfoHandler.itsDayTime + "$"); } else { LOGGER.info("DAY"); gameOverCheck = voteHandler.humanVote(gameState.getPassengerTrain(), this); setDay(false); + lobby.getAdmin().sendMsgToClientsInLobby(Protocol.printToGUI + "$" + ClientGameInfoHandler.itsNightTime + "$"); } if (gameOverCheck.equals(ClientGameInfoHandler.gameOverGhostsWin) || gameOverCheck.equals( ClientGameInfoHandler.gameOverHumansWin)) { if (gameOverCheck.equals(ClientGameInfoHandler.gameOverGhostsWin) && getOgGhost().getIsPlayer()) { OgGhostHighScore.addOgGhostWinner(getOgGhost().getName()); } + lobby.getAdmin().sendMsgToClientsInLobby(Protocol.printToGUI + "$" + GuiParameters.viewChangeToLobby + "$"); lobby.getAdmin().broadcastAnnouncementToLobby(gameOverCheck); lobby.removeGameFromRunningGames(this); lobby.addGameToFinishedGames(this); 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 773672b..54eb4de 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 @@ -4,6 +4,7 @@ import ch.unibas.dmi.dbis.cs108.BudaLogConfig; 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.GuiParameters; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -48,11 +49,10 @@ public class ServerGameInfoHandler { * won't get bored Formartiert Nachrichten die für einen Spectator gedacht sind. * * @param msg the message to be formatted - * @param passenger the passenger getting the message * @param game the game in wich the passenger lives * @return a message in a protocol format */ - public static String spectatorFormat(String msg, Passenger passenger, Game game) { + public static String spectatorFormat(String msg, Game game) { switch (msg) { case ClientGameInfoHandler.ghostVoteRequest: case ClientGameInfoHandler.itsNightTime: @@ -62,6 +62,9 @@ public class ServerGameInfoHandler { case ClientGameInfoHandler.itsDayTime: msg = Protocol.printToClientConsole + "$Humans are voting:" + game.gameState.toString(); break; + case GuiParameters.updateGameState: + msg = Protocol.printToGUI + "$" + GuiParameters.updateGameState + game.getGameState().toString(); + break; default: msg = Protocol.printToClientConsole + "$" + msg; } @@ -110,7 +113,10 @@ public class ServerGameInfoHandler { case ClientGameInfoHandler.noiseNotification + 4 + " time(s)": case ClientGameInfoHandler.noiseNotification + 5 + " time(s)": String outMsg = npc.getName() + ": " + noiseRandomizer(); + //TODO: add likelyhood game.getLobby().getAdmin().broadcastNpcChatMessageToLobby(outMsg); + game.getLobby().getAdmin().sendMsgToClientsInLobby(Protocol.printToGUI + GuiParameters.noiseHeardAtPosition + + "$" + npc.getPosition() + "$"); break; case ClientGameInfoHandler.ghostVoteRequest: npc.vote(game); @@ -133,6 +139,8 @@ public class ServerGameInfoHandler { case ClientGameInfoHandler.noiseNotification + 5 + " time(s)": String outMsg = npc.getName() + ": " + noiseRandomizer(); game.getLobby().getAdmin().broadcastNpcChatMessageToLobby(outMsg); + game.getLobby().getAdmin().sendMsgToClientsInLobby(Protocol.printToGUI + GuiParameters.noiseHeardAtPosition + + "$" + npc.getPosition() + "$"); break; case ClientGameInfoHandler.humanVoteRequest: npc.vote(game); 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 a9aaa32..ff7e783 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 @@ -4,6 +4,8 @@ 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.helpers.GuiParameters; +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; @@ -41,7 +43,12 @@ public class GhostPlayer extends Ghost { */ @Override public void send(String msg, Game game) { - String formattedMsg = ServerGameInfoHandler.format(msg, this, game); + String formattedMsg; + if (msg.equals(GuiParameters.updateGameState)) { + formattedMsg = Protocol.printToGUI + "$" + GuiParameters.updateGameState + game.getGameState().toString(); + } else { + formattedMsg = ServerGameInfoHandler.format(msg, this, game); + } clientHandler.sendMsgToClient(formattedMsg); } 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 f306d8d..5695d0b 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 @@ -4,6 +4,8 @@ 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.helpers.GuiParameters; +import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; import java.util.Arrays; import org.apache.logging.log4j.LogManager; @@ -42,7 +44,12 @@ public class HumanPlayer extends Human { */ @Override public void send(String msg, Game game) { - String formattedMsg = ServerGameInfoHandler.format(msg,this, game); + String formattedMsg; + if (msg.equals(GuiParameters.updateGameState)) { + formattedMsg = Protocol.printToGUI + "$" + GuiParameters.updateGameState + game.getGameState().humanToString(); + } else { + formattedMsg = ServerGameInfoHandler.format(msg, this, game); + } clientHandler.sendMsgToClient(formattedMsg); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Spectator.java b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Spectator.java index 9569714..338b8db 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Spectator.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/gamelogic/klassenstruktur/Spectator.java @@ -29,6 +29,6 @@ public class Spectator extends Passenger{ */ @Override public void send(String msg, Game game) { - clientHandler.sendMsgToClient(ServerGameInfoHandler.spectatorFormat(msg, this, game)); + clientHandler.sendMsgToClient(ServerGameInfoHandler.spectatorFormat(msg, game)); } } 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 03274f1..e8b919a 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 @@ -11,6 +11,7 @@ import ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game.GameController; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.ClientPinger; +import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.GuiParameters; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import java.net.InetAddress; @@ -309,35 +310,92 @@ public class Client { } /** - * funnels a message to the gui, where depending on the message different functions/controls/methods - * of the gui are targeted. The message contains information on what to do, which are extracted - * @param msg a message of the form {@code parameter$msg} - * + * funnels a message to the gui, where depending on the parameter different functions/controls/methods + * of the gui are targeted. The data contains more information the gui needs + * @param parameter a string according to {@link GuiParameters} and {@link ClientGameInfoHandler} can be empty + * @param data some information in a string, separators can be $ or : + *TODO(Seraina&Sebi): evtl. auslagern? */ - public void sendToGUI(String msg) { - int indexFirstDollar = msg.indexOf('$'); - String header = ""; + public void sendToGUI(String parameter, String data) { try { - header = msg.substring(0,indexFirstDollar); - } catch (IndexOutOfBoundsException e) { - LOGGER.info(e.getMessage()); - } - - switch (header) { - case ClientGameInfoHandler.itsNightTime: - gameStateModel.setDayClone(false); - break; - case ClientGameInfoHandler.itsDayTime: - gameStateModel.setDayClone(true); - break; - - default: - gameController.addMessageToNotificationText(msg); //TODO(Sebi,Seraina): should the gameController be in the Application just like the ChatController? - + switch (parameter) { + case ClientGameInfoHandler.itsNightTime: //ClientGameInfoHandler + gameStateModel.setDayClone(false); + break; + case ClientGameInfoHandler.itsDayTime: //ClientGameInfoHandler + gameStateModel.setDayClone(true); + break; + case GuiParameters.updateGameState: + gameStateModel.setGSFromString(data); + gameController.updateRoomLabels(); + break; + case GuiParameters.noiseHeardAtPosition: + try { + int position = Integer.parseInt(data); + determineNoiseDisplay(position); + } catch (Exception e) { + LOGGER.warn("Not a position given for noise"); + } + break; + case GuiParameters.listOfLobbies: + //TODO + break; + case GuiParameters.listOfPLayers: + //TODO + break; + case GuiParameters.viewChangeToGame: + //TODO + break; + case GuiParameters.viewChangeToStart: + //TODO + break; + case GuiParameters.viewChangeToLobby: + //TODO + break; + default: + notificationTextDisplay(data); + //TODO(Sebi,Seraina): should the gameController be in the Application just like the ChatController? + } + } catch (Exception e) { + LOGGER.warn("Communication with GUI currently not possible: " + e.getMessage()); } + } + /** + * Starts a new thread, thad adds a message to notificationText in the gameController, + * waits 3 seconds and deletes it again. + * @param data the message to be added + */ + public void notificationTextDisplay(String data) { + new Thread(() -> { + try { + gameController.addMessageToNotificationText(data); + Thread.sleep(3000); + gameController.clearNotificationText(); + } catch (InterruptedException e) { + LOGGER.warn(e.getMessage()); + } + }).start(); + + } + + public void determineNoiseDisplay(int position) { + switch (position) { + case 0: + gameController.noiseDisplay0(); + case 1: + gameController.noiseDisplay1(); + case 2: + gameController.noiseDisplay2(); + case 3: + gameController.noiseDisplay3(); + case 4: + gameController.noiseDisplay4(); + case 5: + gameController.noiseDisplay5(); + } } 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 58db92d..2ad6979 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 @@ -49,7 +49,6 @@ public class JClientProtocolParser { break; case Protocol.serverRequestsGhostVote: LOGGER.debug("Ghost received Vote request"); - //c.sendToGUI(ClientGameInfoHandler.ghostVoteRequest); c.positionSetter(msg.substring(6)); break; case Protocol.serverRequestsHumanVote: @@ -61,7 +60,17 @@ public class JClientProtocolParser { c.changeUsername(msg.substring(6)); break; case Protocol.printToGUI: - c.sendToGUI(msg.substring(6)); + String substring = msg.substring(6); + int index = msg.indexOf("$"); + String parameter = ""; + String data = substring; + try { + parameter = msg.substring(0,index); + data = msg.substring(index+1); + } catch (Exception e) { + LOGGER.warn("No parameter in PTGUI"); + } + c.sendToGUI(parameter,data); break; default: System.out.println("Received unknown command"); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/gui/game/GameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/gui/game/GameController.java index 73d5cfd..2cfde99 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/gui/game/GameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/gui/game/GameController.java @@ -1,5 +1,8 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game; +import ch.unibas.dmi.dbis.cs108.multiplayer.client.Client; +import ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.GameStateModel; +import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.GuiParameters; import ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.GameStateModel; import javafx.event.EventHandler; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; @@ -23,6 +26,8 @@ import javafx.scene.control.Label; import javafx.scene.control.SplitPane; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Background; import javafx.scene.layout.HBox; @@ -31,13 +36,19 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class GameController { + public static final Logger LOGGER = LogManager.getLogger(GameController.class); + public static final BudaLogConfig l = new BudaLogConfig(LOGGER); private static ClientModel client; private static GameStateModel gameStateModel; + + //TODO(Seraina, Sebi): Same issue as ChatController? do with setters? public GameController(ClientModel c, GameStateModel g) { client = c; @@ -76,6 +87,20 @@ public class GameController { @FXML private TextFlow lableRoom5; @FXML + private HBox notificationHBox; + @FXML + private ImageView noiseImage0; + @FXML + private ImageView noiseImage1; + @FXML + private ImageView noiseImage2; + @FXML + private ImageView noiseImage3; + @FXML + private ImageView noiseImage4; + @FXML + private ImageView noiseImage5; + @FXML private Button noiseButton; @FXML private TextFlow notificationText; @@ -132,22 +157,29 @@ public class GameController { * Sends a noise message, to the server, should be a gui message? */ public void noise() { - client.getClient().sendMsgToServer("noise"); //TODO: Add message + client.getClient().sendMsgToServer( + Protocol.sendMessageToAllClients + "$" + Protocol.printToGUI + GuiParameters.noiseHeardAtPosition + "$" + + client.getClient().getPosition()); //TODO: Test!! } /** * Takes a given message and displays it in the notificationText Flow in the game Scene * @param msg the message to be displayed */ - public void addMessageToNotificationText(String msg) { + public void addMessageToNotificationText(String msg){ Text notification = new Text(msg); - notificationText.getChildren().clear(); - notificationText.getChildren().add(notification); + try { + notificationText.getChildren().clear(); + notificationText.getChildren().add(notification); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } //TODO: Wait for a certain time, then clear all again } /** - * Updates the labels of the rooms accordingly to the datastructures in GameStateModel + * Adds a msg to the room Lable at the specified position + * @param names a String array containing all the names */ public void updateRoomLabels() { String[] names = gameStateModel.getPassengerTrainClone()[0]; @@ -165,26 +197,103 @@ public class GameController { Text role4 = new Text(roles[4]); Text role5 = new Text(roles[5]); - lableRoom0.getChildren().clear(); - lableRoom0.getChildren().add(name0); - lableRoom0.getChildren().add(role0); - lableRoom1.getChildren().clear(); - lableRoom1.getChildren().add(name1); - lableRoom1.getChildren().add(role1); - lableRoom2.getChildren().clear(); - lableRoom2.getChildren().add(name2); - lableRoom2.getChildren().add(role2); - lableRoom3.getChildren().clear(); - lableRoom3.getChildren().add(name3); - lableRoom3.getChildren().add(role3); - lableRoom4.getChildren().clear(); - lableRoom4.getChildren().add(name4); - lableRoom4.getChildren().add(role4); - lableRoom5.getChildren().clear(); - lableRoom5.getChildren().add(name5); - lableRoom5.getChildren().add(role5); + try { + lableRoom0.getChildren().clear(); + lableRoom0.getChildren().add(name0); + lableRoom0.getChildren().add(role0); + lableRoom1.getChildren().clear(); + lableRoom1.getChildren().add(name1); + lableRoom1.getChildren().add(role1); + lableRoom2.getChildren().clear(); + lableRoom2.getChildren().add(name2); + lableRoom2.getChildren().add(role2); + lableRoom3.getChildren().clear(); + lableRoom3.getChildren().add(name3); + lableRoom3.getChildren().add(role3); + lableRoom4.getChildren().clear(); + lableRoom4.getChildren().add(name4); + lableRoom4.getChildren().add(role4); + lableRoom5.getChildren().clear(); + lableRoom5.getChildren().add(name5); + lableRoom5.getChildren().add(role5); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } } + /** + * Adds an image of a bell on top of button0 + */ + public void noiseDisplay0(){ + Image bell = new Image("ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game.DayOpen.bell.png"); + try { + noiseImage0.setImage(bell); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } + } + + /** + * Adds an image of a bell on top of button1 + */ + public void noiseDisplay1(){ + Image bell = new Image("ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game.DayOpen.bell.png"); + try { + noiseImage0.setImage(bell); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } + } + + /** + * Adds an image of a bell on top of button2 + */ + public void noiseDisplay2(){ + Image bell = new Image("ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game.DayOpen.bell.png"); + try { + noiseImage0.setImage(bell); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } + } + + /** + * Adds an image of a bell on top of button3 + */ + public void noiseDisplay3(){ + Image bell = new Image("ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game.DayOpen.bell.png"); + try { + noiseImage0.setImage(bell); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } + } + + /** + * Adds an image of a bell on top of button4 + */ + public void noiseDisplay4(){ + Image bell = new Image("ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game.DayOpen.bell.png"); + try { + noiseImage0.setImage(bell); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } + } + + /** + * Adds an image of a bell on top of button5 + */ + public void noiseDisplay5(){ + Image bell = new Image("ch.unibas.dmi.dbis.cs108.multiplayer.client.gui.game.DayOpen.bell.png"); + try { + noiseImage0.setImage(bell); + } catch (Exception e) { + LOGGER.trace("Not yet initialized"); + } + } + + public void setGameStateModel( GameStateModel gameStateModel) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/GuiParameters.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/GuiParameters.java new file mode 100644 index 0000000..2b5e0c2 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/GuiParameters.java @@ -0,0 +1,45 @@ +package ch.unibas.dmi.dbis.cs108.multiplayer.helpers; + +/** + * This class contains parameters for the PTGUI protocol message + */ +public class GuiParameters { + + /** + * Tells GUI to update the gameStateModel, in the form {@code UPDATE$name:role:kickedOff$name:role:kickedOff} ... usw. + */ + public static final String updateGameState = "UPDATE"; + /** + * Tells Gui, that the following statement after $, is a String containing the listOfLobbies + */ + public static final String listOfLobbies = "LOBBIES"; + + /** + * Tells Gui, that what follows is a list of players per Lobby + */ + public static final String listOfPLayers = "PLAYERS"; + + /** + * Tells Gui, that the passenger at position {@code position} has heard some noise + * Form: {@code NOISE$position$} + */ + public static final String noiseHeardAtPosition = "NOISE"; + + /** + * Tells Gui, that the start view should be displayed + */ + public static final String viewChangeToStart = "VCSTART"; + + /** + * Tells Gui, that the lobby view should be displayed + */ + public static final String viewChangeToLobby = "VCLOBBY"; + + /** + * Tells Gui, that the game view should be displayed + */ + public static final String viewChangeToGame = "VCGAME"; + + + +} 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 a4f5931..b9e3ae1 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 @@ -143,6 +143,12 @@ public class Protocol { */ public static final String highScoreList = "HSCOR"; + /** + * The client requests that a message in {@code STACL$msg} is sent to all clients but only the message + * without a specific Server message to be added. + */ + public static final String sendMessageToAllClients = "STACL"; + @@ -195,10 +201,9 @@ public class Protocol { public static final String changedUserName = "CHNAM"; /** - * Sends a message to a client containing information for the gui. The command is structured {@code PTGUI$parameter$msg} - * where the parameter specifies what exactly to do in de gui (i.e. change scene) and the optional - * message contains information the gui needs from the server to execute the command specified in the parameter - * + * Handles all information that the gui of the client needs. The Form is {@code PTGUI$parameters$msg} + * where the parameter tells the gui to do different things according to {@link GuiParameters} and the message + * contains a certain information i.e. who is where in the train */ public static final String printToGUI = "PTGUI"; 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 e5928c4..27a47ed 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 @@ -238,7 +238,7 @@ public class ClientHandler implements Runnable { * @param msg the Message to be broadcast. Does not have to be protocol-formatted, this method * will take care of that. */ - public static void broadcastAnnouncementToAll(String msg) { + public static void broadcastAnnouncementToAll(String msg) { //TODO: Adjust to GUI command? System.out.println(msg); for (ClientHandler client : connectedClients) { client.sendMsgToClient(Protocol.printToClientConsole + "$" + msg); @@ -254,7 +254,7 @@ public class ClientHandler implements Runnable { * @param msg the Message to be broadcast. Does not have to be protocol-formatted, this method * will take care of that. */ - public void broadcastAnnouncementToLobby(String msg) { + public void broadcastAnnouncementToLobby(String msg) { //TODO: Adjust to GUI command? Lobby l = getLobby(); if (l != null) { //System.out.println(msg); we can-comment this if you want lobby-announcements to print on the server console as well. @@ -305,6 +305,35 @@ public class ClientHandler implements Runnable { } } + /** + * Sends a given message to all connected client. The message has to already be protocol-formatted. + * + * @param msg the given message. Should already be protocol-formatted. + */ + public void sendMsgToAllClients(String msg) { + for (ClientHandler client : connectedClients) { + client.sendMsgToClient(msg); + } + } + + /** + * Sends a Message to all clients in the same lobby. The message has to already be protocol-formatted. + * @param msg the given message. Should already be protocol-formatted. + */ + public void sendMsgToClientsInLobby(String msg) { + Lobby l = getLobby(); + if (l != null) { + //System.out.println(msg); we can-comment this if you want lobby-announcements to print on the server console as well. + for (ClientHandler client : l.getLobbyClients()) { + client.sendMsgToClient(msg); + } + } else { + LOGGER.debug("Could not send announcements; probably client isn't in a lobby." + + "Will send across all lobbies now."); + sendMsgToAllClients(msg); + } + } + /** * Decode a whisper message * 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 c9029ba..a9f031e 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,7 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.server; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; +import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.GuiParameters; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; @@ -81,6 +82,7 @@ public class JServerProtocolParser { try { int i = Integer.parseInt(msg.substring(6, 7)); h.joinLobby(i); + h.sendMsgToClient(Protocol.printToGUI + "$" + GuiParameters.viewChangeToLobby + "$"); } catch (Exception e) { h.sendMsgToClient(Protocol.printToClientConsole + "$Invalid input. Please use JOINL$1 to join Lobby 1, for example."); @@ -88,6 +90,7 @@ public class JServerProtocolParser { break; case Protocol.createNewLobby: h.createNewLobby(); + h.sendMsgToClient(Protocol.printToGUI + "$" + GuiParameters.viewChangeToLobby+ "$"); break; case Protocol.listLobbies: h.listLobbies(); @@ -97,6 +100,7 @@ public class JServerProtocolParser { break; case Protocol.leaveLobby: h.leaveLobby(); + h.sendMsgToClient(Protocol.printToGUI + "$" + GuiParameters.viewChangeToStart + "$"); break; case Protocol.votedFor: LOGGER.debug("Made it here"); @@ -105,6 +109,7 @@ public class JServerProtocolParser { break; case Protocol.startANewGame: h.startNewGame(); + h.sendMsgToClientsInLobby(Protocol.printToGUI + "$" + GuiParameters.viewChangeToGame + "$"); break; case Protocol.listGames: h.listGames(); @@ -112,6 +117,9 @@ public class JServerProtocolParser { case Protocol.highScoreList: h.sendHighScoreList(); break; + case Protocol.sendMessageToAllClients: + msg = msg.substring(6); + h.sendMsgToClient(msg); default: System.out.println("Received unknown command"); } diff --git a/src/main/resources/ch/unibas/dmi/dbis/cs108/multiplayer/client/gui/game/DayOpen/bell.png b/src/main/resources/ch/unibas/dmi/dbis/cs108/multiplayer/client/gui/game/DayOpen/bell.png new file mode 100644 index 0000000000000000000000000000000000000000..3fa873e9b2987fb4594b58a4c9dc8b258ca946a3 GIT binary patch literal 13596 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 irMJcZpywM8v;PAHr|z0>A?INL0000 + + + + + + + + + +