From e41d21584a914fe670092636b70bd525a535b818 Mon Sep 17 00:00:00 2001 From: Lukas Bauer <157071544+Heady045@users.noreply.github.com> Date: Fri, 4 Oct 2024 18:33:13 +0200 Subject: [PATCH] Aufgabe 11. added class BattleshipServer, ReceivedMessage to implement the feature that a client is able to start an server edited NetworkDialog to implement the logic to start an server and overhauled the GUI to be appropriate for the new features --- .../battleship/client/BattleshipServer.java | 168 ++++++++++++++++++ .../pp/battleship/client/NetworkDialog.java | 140 ++++++++++++--- .../pp/battleship/client/ReceivedMessage.java | 17 ++ .../src/main/resources/battleship.properties | 2 +- 4 files changed, 297 insertions(+), 30 deletions(-) create mode 100644 Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipServer.java create mode 100644 Projekte/battleship/client/src/main/java/pp/battleship/client/ReceivedMessage.java diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipServer.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipServer.java new file mode 100644 index 00000000..5f5b255c --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipServer.java @@ -0,0 +1,168 @@ +//////////////////////////////////////// +// Programming project code +// UniBw M, 2022, 2023, 2024 +// www.unibw.de/inf2 +// (c) Mark Minas (mark.minas@unibw.de) +//////////////////////////////////////// + +package pp.battleship.client; + +import com.jme3.network.*; +import com.jme3.network.serializing.Serializer; +import pp.battleship.BattleshipConfig; +import pp.battleship.game.server.Player; +import pp.battleship.game.server.ServerGameLogic; +import pp.battleship.game.server.ServerSender; +import pp.battleship.message.client.ClientMessage; +import pp.battleship.message.client.MapMessage; +import pp.battleship.message.client.ShootMessage; +import pp.battleship.message.server.EffectMessage; +import pp.battleship.message.server.GameDetails; +import pp.battleship.message.server.ServerMessage; +import pp.battleship.message.server.StartBattleMessage; +import pp.battleship.model.Battleship; +import pp.battleship.model.IntPoint; +import pp.battleship.model.Shot; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.LogManager; + +/** + * Server implementing the visitor pattern as MessageReceiver for ClientMessages + */ +public class BattleshipServer implements MessageListener, ConnectionListener, ServerSender { + private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName()); + private static final File CONFIG_FILE = new File("server.properties"); + + private final BattleshipConfig config = new BattleshipConfig(); + private Server myServer; + private final ServerGameLogic logic; + private final BlockingQueue pendingMessages = new LinkedBlockingQueue<>(); + + static { + // Configure logging + LogManager manager = LogManager.getLogManager(); + try { + manager.readConfiguration(new FileInputStream("logging.properties")); + LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS + } + catch (IOException e) { + LOGGER.log(Level.INFO, e.getMessage()); + } + } + + + /** + * Creates the server. + */ + BattleshipServer() { + config.readFromIfExists(CONFIG_FILE); + LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS + logic = new ServerGameLogic(this, config); + } + + public void run() { + startServer(); + while (true) + processNextMessage(); + } + + private void startServer() { + try { + LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS + myServer = Network.createServer(config.getPort()); + initializeSerializables(); + myServer.start(); + registerListeners(); + LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS + } + catch (IOException e) { + LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS + exit(1); + } + } + + private void processNextMessage() { + try { + pendingMessages.take().process(logic); + } + catch (InterruptedException ex) { + LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS + Thread.currentThread().interrupt(); + } + } + + private void initializeSerializables() { + Serializer.registerClass(GameDetails.class); + Serializer.registerClass(StartBattleMessage.class); + Serializer.registerClass(MapMessage.class); + Serializer.registerClass(ShootMessage.class); + Serializer.registerClass(EffectMessage.class); + Serializer.registerClass(Battleship.class); + Serializer.registerClass(IntPoint.class); + Serializer.registerClass(Shot.class); + } + + private void registerListeners() { + myServer.addMessageListener(this, MapMessage.class); + myServer.addMessageListener(this, ShootMessage.class); + myServer.addConnectionListener(this); + } + + @Override + public void messageReceived(HostedConnection source, Message message) { + LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS + if (message instanceof ClientMessage clientMessage) + pendingMessages.add(new ReceivedMessage(clientMessage, source.getId())); + } + + @Override + public void connectionAdded(Server server, HostedConnection hostedConnection) { + LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS + logic.addPlayer(hostedConnection.getId()); + } + + @Override + public void connectionRemoved(Server server, HostedConnection hostedConnection) { + LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS + final Player player = logic.getPlayerById(hostedConnection.getId()); + if (player == null) + LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS + else { //NON-NLS + LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS + exit(0); + } + } + + private void exit(int exitValue) { //NON-NLS + LOGGER.log(Level.INFO, "close request"); //NON-NLS + if (myServer != null) + for (HostedConnection client : myServer.getConnections()) //NON-NLS + if (client != null) client.close("Game over"); //NON-NLS + System.exit(exitValue); + } + + /** + * Send the specified message to the specified connection. + * + * @param id the connection id + * @param message the message + */ + public void send(int id, ServerMessage message) { + if (myServer == null || !myServer.isRunning()) { + LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS + return; + } + final HostedConnection connection = myServer.getConnection(id); + if (connection != null) + connection.send(message); + else + LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS + } +} \ No newline at end of file diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java index 3f2f5a65..2c949726 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/NetworkDialog.java @@ -7,9 +7,7 @@ package pp.battleship.client; -import com.simsilica.lemur.Container; -import com.simsilica.lemur.Label; -import com.simsilica.lemur.TextField; +import com.simsilica.lemur.*; import com.simsilica.lemur.component.SpringGridLayout; import pp.dialog.Dialog; import pp.dialog.DialogBuilder; @@ -37,6 +35,9 @@ class NetworkDialog extends SimpleDialog { private int portNumber; private Future connectionFuture; private Dialog progressDialog; + private BattleshipServer battleshipServer; + private boolean GUI = true; + private Container menu; /** * Constructs a new NetworkDialog. @@ -50,47 +51,131 @@ class NetworkDialog extends SimpleDialog { host.setPreferredWidth(400f); port.setSingleLine(true); - final BattleshipApp app = network.getApp(); - final Container input = new Container(new SpringGridLayout()); - input.addChild(new Label(lookup("host.name") + ": ")); - input.addChild(host, 1); - input.addChild(new Label(lookup("port.number") + ": ")); - input.addChild(port, 1); - - DialogBuilder.simple(app.getDialogManager()) - .setTitle(lookup("server.dialog")) - .setExtension(d -> d.addChild(input)) - .setOkButton(lookup("button.connect"), d -> connect()) - .setNoButton(lookup("button.cancel"), app::closeApp) - .setOkClose(false) - .setNoClose(false) - .build(this); + this.menu = new Container(); + menu(); + addChild(menu); } + /** + * Main Menu creation + */ + private void menu() { + + Button soloGame = new Button("Solo Game"); + soloGame.addClickCommands(source -> soloGame()); + Button preview = new Button("Preview"); + preview.addClickCommands(source -> preview()); + Button connectGame = new Button("Multiplayer"); + connectGame.addClickCommands(source -> hostServer()); + Button serverHost = new Button("Host own Game"); + serverHost.addClickCommands(source -> connect()); + + menu.addChild(preview); + menu.addChild(soloGame); + menu.addChild(serverHost); + menu.addChild(connectGame); + } + + /** + * work in progress + */ + private void soloGame() { + LOGGER.log(Level.INFO, "Starting Solo Game..."); + //TODO implement Game against KI + } + + /** + * work in progress + */ + private void preview() { + LOGGER.log(Level.INFO, "Starting Preview..."); + //TODO implement Preview of the Game like in the "Einführungsveranstaltung" + } + + /** + * Logic for the Menu Button Multiplayer + */ + private void hostServer() { + LOGGER.log(Level.INFO, "Hosting Server..."); + + if (GUI) { + host.setSingleLine(true); + host.setPreferredWidth(400f); + port.setSingleLine(true); + + final BattleshipApp app = network.getApp(); + final Container input = new Container(new SpringGridLayout()); + + input.addChild(new Label(lookup("host.name") + ": ")); + input.addChild(host, 1); + input.addChild(new Label(lookup("port.number") + ": ")); + input.addChild(port, 1); + + DialogBuilder.simple(app.getDialogManager()) + .setTitle(lookup("server.dialog")) + .setExtension(d -> d.addChild(input)) + .setOkButton(lookup("button.connect"), d -> connectServer()) + .setOkClose(false) + .build(this); + GUI = false; + } + } + + /** * Handles the action for the connect button in the connection dialog. * Tries to parse the port number and initiate connection to the server. */ - private void connect() { + private void connectServer() { LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS try { hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText(); portNumber = Integer.parseInt(port.getText()); openProgressDialog(); connectionFuture = network.getApp().getExecutor().submit(this::initNetwork); - } - catch (NumberFormatException e) { + } catch (NumberFormatException e) { network.getApp().errorDialog(lookup("port.must.be.integer")); } } + /** + * Checks if the Server is running or starts the Server and handles Exceptions * + */ + private void connect() { + if (battleshipServer == null) { + startServer(); // Starts the server in a new thread + try { + Thread.sleep(1000); // Wait for the server to start + } catch (InterruptedException e) { + LOGGER.log(Level.WARNING, e.getMessage(), e); + } + } + // Proceed with connecting to the server + connectServer(); + } + + /** + * Allows Client to Start a Server in a new Thread + */ + private void startServer() { + new Thread(() -> { + try { + // Initialize and run the server + battleshipServer = new BattleshipServer(); + battleshipServer.run(); + } catch (Exception e) { + LOGGER.log(Level.ERROR, e); + } + }).start(); + } + /** * Creates a dialog indicating that the connection is in progress. */ private void openProgressDialog() { progressDialog = DialogBuilder.simple(network.getApp().getDialogManager()) - .setText(lookup("label.connecting")) - .build(); + .setText(lookup("label.connecting")) + .build(); progressDialog.open(); } @@ -103,8 +188,7 @@ private Object initNetwork() { try { network.initNetwork(hostname, portNumber); return null; - } - catch (Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -119,11 +203,9 @@ public void update(float delta) { try { connectionFuture.get(); success(); - } - catch (ExecutionException e) { + } catch (ExecutionException e) { failure(e.getCause()); - } - catch (InterruptedException e) { + } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS Thread.currentThread().interrupt(); } diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/ReceivedMessage.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/ReceivedMessage.java new file mode 100644 index 00000000..767a802c --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/ReceivedMessage.java @@ -0,0 +1,17 @@ +//////////////////////////////////////// +// Programming project code +// UniBw M, 2022, 2023, 2024 +// www.unibw.de/inf2 +// (c) Mark Minas (mark.minas@unibw.de) +//////////////////////////////////////// + +package pp.battleship.client; + +import pp.battleship.message.client.ClientInterpreter; +import pp.battleship.message.client.ClientMessage; + +record ReceivedMessage(ClientMessage message, int from) { + void process(ClientInterpreter interpreter) { + message.accept(interpreter, from); + } +} diff --git a/Projekte/battleship/model/src/main/resources/battleship.properties b/Projekte/battleship/model/src/main/resources/battleship.properties index fac90826..c70480fc 100644 --- a/Projekte/battleship/model/src/main/resources/battleship.properties +++ b/Projekte/battleship/model/src/main/resources/battleship.properties @@ -37,4 +37,4 @@ label.connecting=Connecting... dialog.error=Error dialog.question=Question port.must.be.integer=Port must be an integer number -map.doesnt.fit=The map doesn't fit to this game +map.doesnt.fit=The map doesn't fit to this game \ No newline at end of file