Add client host capability
This commit is contained in:
		@@ -0,0 +1,174 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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<HostedConnection>, 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<ReceivedMessage> 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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the Battleships server.
 | 
			
		||||
     */
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        new BattleshipServer().run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.simsilica.lemur.Checkbox;
 | 
			
		||||
import com.simsilica.lemur.Container;
 | 
			
		||||
import com.simsilica.lemur.Label;
 | 
			
		||||
import com.simsilica.lemur.TextField;
 | 
			
		||||
@@ -37,6 +38,7 @@ class NetworkDialog extends SimpleDialog {
 | 
			
		||||
    private int portNumber;
 | 
			
		||||
    private Future<Object> connectionFuture;
 | 
			
		||||
    private Dialog progressDialog;
 | 
			
		||||
    private boolean hostServer = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new NetworkDialog.
 | 
			
		||||
@@ -50,6 +52,11 @@ class NetworkDialog extends SimpleDialog {
 | 
			
		||||
        host.setPreferredWidth(400f);
 | 
			
		||||
        port.setSingleLine(true);
 | 
			
		||||
 | 
			
		||||
        Checkbox serverHost = new Checkbox(lookup("menu.host"));
 | 
			
		||||
        serverHost.setChecked(false);
 | 
			
		||||
        serverHost.addClickCommands(s -> toggleServerHost());
 | 
			
		||||
        addChild(serverHost);
 | 
			
		||||
 | 
			
		||||
        final BattleshipApp app = network.getApp();
 | 
			
		||||
        final Container input = new Container(new SpringGridLayout());
 | 
			
		||||
        input.addChild(new Label(lookup("host.name") + ":  "));
 | 
			
		||||
@@ -60,7 +67,7 @@ class NetworkDialog extends SimpleDialog {
 | 
			
		||||
        DialogBuilder.simple(app.getDialogManager())
 | 
			
		||||
                     .setTitle(lookup("server.dialog"))
 | 
			
		||||
                     .setExtension(d -> d.addChild(input))
 | 
			
		||||
                     .setOkButton(lookup("button.connect"), d -> connect())
 | 
			
		||||
                     .setOkButton(lookup("button.connect"), d -> connectLocally())
 | 
			
		||||
                     .setNoButton(lookup("button.cancel"), app::closeApp)
 | 
			
		||||
                     .setOkClose(false)
 | 
			
		||||
                     .setNoClose(false)
 | 
			
		||||
@@ -84,6 +91,35 @@ private void connect() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void connectLocally() {
 | 
			
		||||
        if(hostServer){
 | 
			
		||||
            startServer();
 | 
			
		||||
            try {
 | 
			
		||||
                Thread.sleep(1000);
 | 
			
		||||
            } catch (InterruptedException e) {
 | 
			
		||||
                LOGGER.log(Level.WARNING, e.getMessage(), e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        connect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        new Thread(() -> {
 | 
			
		||||
            try{
 | 
			
		||||
                BattleshipServer battleshipServer = new BattleshipServer();
 | 
			
		||||
                battleshipServer.run();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LOGGER.log(Level.ERROR,e);
 | 
			
		||||
            }
 | 
			
		||||
        }).start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void toggleServerHost(){
 | 
			
		||||
        hostServer = !hostServer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a dialog indicating that the connection is in progress.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -33,6 +33,7 @@ menu.map.load=Load map from file...
 | 
			
		||||
menu.map.save=Save map in file...
 | 
			
		||||
menu.music.toggle=Toggle music on/off
 | 
			
		||||
menu.music.slider=Volumeslider
 | 
			
		||||
menu.host=Host a server
 | 
			
		||||
label.file=File:
 | 
			
		||||
label.connecting=Connecting...
 | 
			
		||||
dialog.error=Error
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ menu.map.load=Karte von Datei laden...
 | 
			
		||||
menu.map.save=Karte in Datei speichern...
 | 
			
		||||
menu.music.toggle=Musik an/ausschalten
 | 
			
		||||
menu.music.slider=Lautstärkeregler
 | 
			
		||||
menu.host=Server hosten
 | 
			
		||||
label.file=Datei:
 | 
			
		||||
label.connecting=Verbindung wird aufgebaut...
 | 
			
		||||
dialog.error=Fehler
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user