From 5e3ba40bed74f41b6b72b387317469f2d07ae599 Mon Sep 17 00:00:00 2001 From: Bas de Jong Date: Tue, 16 Sep 2025 17:27:17 +0200 Subject: [PATCH 1/5] Refactored TicTacToe game to be able to run multiple instances on a server --- server.properties | 16 ------- src/main/java/org/toop/eventbus/Events.java | 2 + .../toop/server/backend/ServerManager.java | 15 ++++++ .../org/toop/server/backend/TcpServer.java | 11 ++++- .../backend/tictactoe/TicTacToeServer.java | 47 +++++++++++-------- .../backend/tictactoe/game/TicTacToe.java | 23 ++++++++- 6 files changed, 75 insertions(+), 39 deletions(-) delete mode 100644 server.properties diff --git a/server.properties b/server.properties deleted file mode 100644 index 90a0856..0000000 --- a/server.properties +++ /dev/null @@ -1,16 +0,0 @@ -#nl.hanze.newgameserver.GlobalSettings -#Mon Sep 15 21:02:16 CEST 2025 -challenges=true -chat=true -debugTournament=false -gameserver.homepagetext=Welcome to the web interface of the game server. -gameserver.port=7789 -gameserver.tournament.timebetweengames=5 -gameserver.turntimelimit=10 -gameserver.welcomemessage=New Game Server [Aplha version]\n(C) Copyright 2023 Hanzehogeschool Groningen -jsonwebtokensecret=SuperGeheimeJWTSecretKeyOmTokensteCreaten -logging.file.name=./gameserver.log -passwordhash=$2a$10$Yze2Ddu931DdzLQ3J08qGexwsp7dK2cJHFuA8YrZWS2RNyM5IkEeS -server.port=8081 -shuffleWhiteBlack=true -subscribe=true diff --git a/src/main/java/org/toop/eventbus/Events.java b/src/main/java/org/toop/eventbus/Events.java index eb2fc51..e594c74 100644 --- a/src/main/java/org/toop/eventbus/Events.java +++ b/src/main/java/org/toop/eventbus/Events.java @@ -90,6 +90,8 @@ public class Events implements IEvents { public record ServerStarted(String uuid, String port) {} + public record StartTicTacToeGame(String id, String port) {} + /** * * Triggers starting a server connection. diff --git a/src/main/java/org/toop/server/backend/ServerManager.java b/src/main/java/org/toop/server/backend/ServerManager.java index b18bab2..4e408ea 100644 --- a/src/main/java/org/toop/server/backend/ServerManager.java +++ b/src/main/java/org/toop/server/backend/ServerManager.java @@ -29,6 +29,7 @@ public class ServerManager { GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartServerRequest.class, this::handleStartServerRequest); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartServer.class, this::handleStartServer); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ForceCloseAllServers.class, _ -> shutdownAll()); + GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartTicTacToeGame.class, this::handleStartTicTacToeGameOnAServer); } private String startServer(String port) { @@ -56,6 +57,20 @@ public class ServerManager { )); } + private void handleStartTicTacToeGameOnAServer(Events.ServerEvents.StartTicTacToeGame event) { + TcpServer serverThing = this.servers.get(event.id()); + if (serverThing != null) { + try { + serverThing.runGame(); + logger.info("Started game on server {}", event.id()); + } + catch (Exception e) { + logger.info("Could not start game on server {}", event.id()); + } + } + + } + private void getAllServers(Events.ServerEvents.RequestsAllServers request) { ArrayList a = new ArrayList<>(this.servers.values()); request.future().complete(a.toString()); diff --git a/src/main/java/org/toop/server/backend/TcpServer.java b/src/main/java/org/toop/server/backend/TcpServer.java index 3bbeedb..bf36d44 100644 --- a/src/main/java/org/toop/server/backend/TcpServer.java +++ b/src/main/java/org/toop/server/backend/TcpServer.java @@ -5,7 +5,6 @@ import org.apache.logging.log4j.Logger; import java.io.*; import java.net.*; -import java.sql.Time; import java.util.concurrent.*; import static java.lang.Thread.sleep; @@ -49,6 +48,16 @@ public class TcpServer implements Runnable { } } + public void runGame() {} + + protected String sendServerMessage() { + try { return sendQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS); } + catch (InterruptedException e) { + logger.error("Interrupted", e); + return null; + } + } + protected String getNewestCommand() { try { return receivedQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { diff --git a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java index ae7c72d..6b78360 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java +++ b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java @@ -6,17 +6,18 @@ import org.toop.server.backend.TcpServer; import java.io.IOException; import java.io.ObjectStreamException; import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; public class TicTacToeServer extends TcpServer { - private TicTacToe game; + /** + * Map of gameId -> Game instances + */ + private final Map games = new ConcurrentHashMap<>(); - public TicTacToeServer(int port, String playerA, String playerB) throws IOException { + public TicTacToeServer(int port) throws IOException { super(port); - this.game = new TicTacToe(playerA, playerB); } @Override @@ -29,13 +30,32 @@ public class TicTacToeServer extends TcpServer { logger.info("Connected to client: {}", clientSocket.getInetAddress()); new Thread(() -> this.startWorkers(clientSocket)).start(); - new Thread(() -> this.gameThread()).start(); } } catch (IOException e) { e.printStackTrace(); } } + public void newGame(String playerA, String playerB) { + String gameId = UUID.randomUUID().toString(); + TicTacToe game = new TicTacToe(playerA, playerB); + this.games.put(gameId, game); + logger.info("Created a new game: {}. {} vs {}", gameId, playerA, playerB); + } + + public void runGame(String gameId) { + TicTacToe game = this.games.get(gameId); + game.run(); + logger.info("Running game: {}, players: {}", gameId, game.getPlayers()); + } + + public void endGame(String gameId) { + TicTacToe game = this.games.get(gameId); + this.games.remove(gameId); + logger.info("Removed game: {}", gameId); + // TODO: Multithreading, close game in a graceful matter, etc. + } + private static class ParsedCommand { public TicTacToeServerCommand command; public ArrayList arguments; @@ -121,17 +141,4 @@ public class TicTacToeServer extends TcpServer { return null; } - private void gameThread() { - - - while (true) { - String command = getNewestCommand(); - command = this.parseCommand(command).toString(); - if (command == null) { continue; } - - // TODO: Game - } - - } - } diff --git a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java index ae9aa1b..8eb7fd5 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java +++ b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java @@ -4,10 +4,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.toop.Main; -public class TicTacToe extends GameBase { - public int moveCount; +public class TicTacToe extends GameBase implements Runnable { private static final Logger logger = LogManager.getLogger(TicTacToe.class); + public int moveCount; + public Thread gameThread; + public TicTacToe(String player1, String player2) { super(3); // 3x3 Grid players = new Player[2]; @@ -17,6 +19,23 @@ public class TicTacToe extends GameBase { moveCount = 0; } + @Override + public void run() { + this.gameThread = new Thread(this::gameThread); + this.gameThread.start(); + } + + private void gameThread() { + while (true) { + String command = getNewestCommand(); + command = this.parseCommand(command).toString(); + if (command == null) { continue; } + + // TODO: Game + } + + } + @Override public boolean validateMove(int index) { if (index < 0 || index > (size * size - 1)) { From dcbdfc91857511e86c14076000a8c9bb5ab3c39b Mon Sep 17 00:00:00 2001 From: Bas de Jong Date: Tue, 16 Sep 2025 18:17:56 +0200 Subject: [PATCH 2/5] Is null, needs fix wip --- src/main/java/org/toop/Main.java | 16 ++++--- src/main/java/org/toop/eventbus/Events.java | 10 +++-- .../toop/server/backend/ServerManager.java | 45 ++++++++++++++----- .../org/toop/server/backend/TcpServer.java | 4 ++ .../backend/tictactoe/TicTacToeServer.java | 10 +++-- .../backend/tictactoe/game/TicTacToe.java | 6 +-- 6 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/toop/Main.java b/src/main/java/org/toop/Main.java index 4ad7cbe..2a5bb66 100644 --- a/src/main/java/org/toop/Main.java +++ b/src/main/java/org/toop/Main.java @@ -24,12 +24,20 @@ public class Main { initSystems(); - GlobalEventBus.post(new Events.ServerEvents.StartServer("5001")); + GlobalEventBus.post(new Events.ServerEvents.StartServer("5001", "tictactoe")); CompletableFuture future = new CompletableFuture<>(); GlobalEventBus.post(new Events.ServerEvents.StartConnectionRequest("127.0.0.1", "5001", future)); String serverId = future.get(); + CompletableFuture future2 = new CompletableFuture<>(); + GlobalEventBus.post(new Events.ServerEvents.CreateTicTacToeGame(serverId, "John", "Pim", future2)); + String gameId = future.get(); + + GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, gameId)); + GlobalEventBus.post(new Events.ServerEvents.Command(serverId, "MOVE", "0")); + GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, gameId)); + // for (int i = 0; i < 1; i++) { // Thread thread = new Thread(() -> { //// logger.info("Server ID: {}", serverId); @@ -38,10 +46,8 @@ public class Main { // thread.start(); // } - GlobalEventBus.post(new Events.ServerEvents.Command(serverId, "HELP", "TEST")); - - GlobalEventBus.post(new Events.ServerEvents.ForceCloseAllConnections()); - GlobalEventBus.post(new Events.ServerEvents.ForceCloseAllServers()); +// GlobalEventBus.post(new Events.ServerEvents.ForceCloseAllConnections()); +// GlobalEventBus.post(new Events.ServerEvents.ForceCloseAllServers()); // // CompletableFuture future2 = new CompletableFuture<>(); diff --git a/src/main/java/org/toop/eventbus/Events.java b/src/main/java/org/toop/eventbus/Events.java index e594c74..a72f92e 100644 --- a/src/main/java/org/toop/eventbus/Events.java +++ b/src/main/java/org/toop/eventbus/Events.java @@ -84,13 +84,17 @@ public class Events implements IEvents { public record ForceCloseAllServers() {} - public record StartServer(String port) {} + public record StartServer(String port, String gameType) {} - public record StartServerRequest(String port, CompletableFuture future) {} + public record StartServerRequest(String port, String gameType, CompletableFuture future) {} public record ServerStarted(String uuid, String port) {} - public record StartTicTacToeGame(String id, String port) {} + public record CreateTicTacToeGame(String serverUuid, String playerA, String playerB, CompletableFuture future) {} + + public record RunTicTacToeGame(String serverUuid, String gameUuid) {} + + public record EndTicTacToeGame(String serverUuid, String gameUuid) {} /** * diff --git a/src/main/java/org/toop/server/backend/ServerManager.java b/src/main/java/org/toop/server/backend/ServerManager.java index 4e408ea..6ba17e6 100644 --- a/src/main/java/org/toop/server/backend/ServerManager.java +++ b/src/main/java/org/toop/server/backend/ServerManager.java @@ -5,9 +5,11 @@ import org.toop.eventbus.GlobalEventBus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.server.backend.tictactoe.TicTacToeServer; import java.util.ArrayList; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -29,13 +31,23 @@ public class ServerManager { GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartServerRequest.class, this::handleStartServerRequest); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartServer.class, this::handleStartServer); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ForceCloseAllServers.class, _ -> shutdownAll()); - GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartTicTacToeGame.class, this::handleStartTicTacToeGameOnAServer); + GlobalEventBus.subscribeAndRegister(Events.ServerEvents.CreateTicTacToeGame.class, this::handleStartTicTacToeGameOnAServer); + GlobalEventBus.subscribeAndRegister(Events.ServerEvents.RunTicTacToeGame.class, this::handleRunTicTacToeGameOnAServer); + GlobalEventBus.subscribeAndRegister(Events.ServerEvents.EndTicTacToeGame.class, this::handleEndTicTacToeGameOnAServer); } - private String startServer(String port) { + private String startServer(String port, String gameType) { String serverId = UUID.randomUUID().toString(); + gameType = gameType.toLowerCase(); try { - TcpServer server = new TcpServer(Integer.parseInt(port)); + TcpServer server = null; + if (Objects.equals(gameType, "tictactoe")) { + server = new TicTacToeServer(Integer.parseInt(port)); + } + else { + logger.error("Manager could not create a TcpServer for {}", gameType); + return null; + } this.servers.put(serverId, server); new Thread(server, "Server-" + serverId).start(); logger.info("Connected to server {} at {}", serverId, port); @@ -47,28 +59,39 @@ public class ServerManager { } private void handleStartServerRequest(Events.ServerEvents.StartServerRequest request) { - request.future().complete(this.startServer(request.port())); // TODO: Maybe post StartServer event. + request.future().complete(this.startServer(request.port(), request.gameType())); // TODO: Maybe post StartServer event. } private void handleStartServer(Events.ServerEvents.StartServer event) { GlobalEventBus.post(new Events.ServerEvents.ServerStarted( - this.startServer(event.port()), + this.startServer(event.port(), event.gameType()), event.port() )); } - private void handleStartTicTacToeGameOnAServer(Events.ServerEvents.StartTicTacToeGame event) { - TcpServer serverThing = this.servers.get(event.id()); + private void handleStartTicTacToeGameOnAServer(Events.ServerEvents.CreateTicTacToeGame event) { + TicTacToeServer serverThing = (TicTacToeServer) this.servers.get(event.serverUuid()); + String gameId = null; if (serverThing != null) { try { - serverThing.runGame(); - logger.info("Started game on server {}", event.id()); + gameId = serverThing.newGame(event.playerA(), event.playerB()); + logger.info("Created game on server {}", event.serverUuid()); } - catch (Exception e) { - logger.info("Could not start game on server {}", event.id()); + catch (Exception e) { // TODO: Error handling + logger.info("Could not create game on server {}", event.serverUuid()); } } + event.future().complete(gameId); + } + private void handleRunTicTacToeGameOnAServer(Events.ServerEvents.RunTicTacToeGame event) { + TicTacToeServer gameServer = (TicTacToeServer) this.servers.get(event.serverUuid()); + gameServer.runGame(event.gameUuid()); + } + + private void handleEndTicTacToeGameOnAServer(Events.ServerEvents.EndTicTacToeGame event) { + TicTacToeServer gameServer = (TicTacToeServer) this.servers.get(event.serverUuid()); + gameServer.endGame(event.gameUuid()); } private void getAllServers(Events.ServerEvents.RequestsAllServers request) { diff --git a/src/main/java/org/toop/server/backend/TcpServer.java b/src/main/java/org/toop/server/backend/TcpServer.java index bf36d44..c32a4b9 100644 --- a/src/main/java/org/toop/server/backend/TcpServer.java +++ b/src/main/java/org/toop/server/backend/TcpServer.java @@ -50,6 +50,10 @@ public class TcpServer implements Runnable { public void runGame() {} + public void endGame() {} + + public void newGame() {} + protected String sendServerMessage() { try { return sendQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { diff --git a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java index 6b78360..d807609 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java +++ b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java @@ -1,16 +1,18 @@ package org.toop.server.backend.tictactoe; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.toop.server.backend.tictactoe.game.TicTacToe; import org.toop.server.backend.TcpServer; import java.io.IOException; -import java.io.ObjectStreamException; import java.net.Socket; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class TicTacToeServer extends TcpServer { + protected static final Logger logger = LogManager.getLogger(TicTacToeServer.class); /** * Map of gameId -> Game instances */ @@ -36,11 +38,13 @@ public class TicTacToeServer extends TcpServer { } } - public void newGame(String playerA, String playerB) { + public String newGame(String playerA, String playerB) { + logger.info("Creating a new game: {} vs {}", playerA, playerB); String gameId = UUID.randomUUID().toString(); TicTacToe game = new TicTacToe(playerA, playerB); this.games.put(gameId, game); logger.info("Created a new game: {}. {} vs {}", gameId, playerA, playerB); + return gameId; } public void runGame(String gameId) { @@ -52,7 +56,7 @@ public class TicTacToeServer extends TcpServer { public void endGame(String gameId) { TicTacToe game = this.games.get(gameId); this.games.remove(gameId); - logger.info("Removed game: {}", gameId); + logger.info("Ended game: {}", gameId); // TODO: Multithreading, close game in a graceful matter, etc. } diff --git a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java index 8eb7fd5..801c93a 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java +++ b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java @@ -27,9 +27,9 @@ public class TicTacToe extends GameBase implements Runnable { private void gameThread() { while (true) { - String command = getNewestCommand(); - command = this.parseCommand(command).toString(); - if (command == null) { continue; } +// String command = getNewestCommand(); +// command = this.parseCommand(command).toString(); +// if (command == null) { continue; } // TODO: Game } From 45159ec659ad91fcacdd2c0168e746076c0e33fe Mon Sep 17 00:00:00 2001 From: lieght Date: Tue, 16 Sep 2025 20:09:17 +0200 Subject: [PATCH 3/5] Fixed no game found bug --- src/main/java/org/toop/Main.java | 23 +++++++++++-------- src/main/java/org/toop/eventbus/Events.java | 2 +- .../toop/server/backend/ServerManager.java | 10 ++++---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/toop/Main.java b/src/main/java/org/toop/Main.java index 2a5bb66..f4ff8fd 100644 --- a/src/main/java/org/toop/Main.java +++ b/src/main/java/org/toop/Main.java @@ -24,19 +24,22 @@ public class Main { initSystems(); - GlobalEventBus.post(new Events.ServerEvents.StartServer("5001", "tictactoe")); + CompletableFuture serverIdFuture = new CompletableFuture<>(); + GlobalEventBus.post(new Events.ServerEvents.StartServerRequest("5001", "tictactoe", serverIdFuture)); + String serverId = serverIdFuture.get(); - CompletableFuture future = new CompletableFuture<>(); - GlobalEventBus.post(new Events.ServerEvents.StartConnectionRequest("127.0.0.1", "5001", future)); - String serverId = future.get(); + CompletableFuture connectionIdFuture = new CompletableFuture<>(); + GlobalEventBus.post(new Events.ServerEvents.StartConnectionRequest("127.0.0.1", "5001", connectionIdFuture)); + String connectionId = connectionIdFuture.get(); - CompletableFuture future2 = new CompletableFuture<>(); - GlobalEventBus.post(new Events.ServerEvents.CreateTicTacToeGame(serverId, "John", "Pim", future2)); - String gameId = future.get(); + CompletableFuture ticTacToeGame = new CompletableFuture<>(); + GlobalEventBus.post(new Events.ServerEvents.CreateTicTacToeGameRequest(serverId, "John", "Pim", ticTacToeGame)); + String ticTacToeGameId = ticTacToeGame.get(); - GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, gameId)); - GlobalEventBus.post(new Events.ServerEvents.Command(serverId, "MOVE", "0")); - GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, gameId)); + + GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, ticTacToeGameId)); + GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "0")); + GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId)); // for (int i = 0; i < 1; i++) { // Thread thread = new Thread(() -> { diff --git a/src/main/java/org/toop/eventbus/Events.java b/src/main/java/org/toop/eventbus/Events.java index a72f92e..17172b0 100644 --- a/src/main/java/org/toop/eventbus/Events.java +++ b/src/main/java/org/toop/eventbus/Events.java @@ -90,7 +90,7 @@ public class Events implements IEvents { public record ServerStarted(String uuid, String port) {} - public record CreateTicTacToeGame(String serverUuid, String playerA, String playerB, CompletableFuture future) {} + public record CreateTicTacToeGameRequest(String serverUuid, String playerA, String playerB, CompletableFuture future) {} public record RunTicTacToeGame(String serverUuid, String gameUuid) {} diff --git a/src/main/java/org/toop/server/backend/ServerManager.java b/src/main/java/org/toop/server/backend/ServerManager.java index 6ba17e6..f0f487e 100644 --- a/src/main/java/org/toop/server/backend/ServerManager.java +++ b/src/main/java/org/toop/server/backend/ServerManager.java @@ -31,7 +31,7 @@ public class ServerManager { GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartServerRequest.class, this::handleStartServerRequest); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartServer.class, this::handleStartServer); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ForceCloseAllServers.class, _ -> shutdownAll()); - GlobalEventBus.subscribeAndRegister(Events.ServerEvents.CreateTicTacToeGame.class, this::handleStartTicTacToeGameOnAServer); + GlobalEventBus.subscribeAndRegister(Events.ServerEvents.CreateTicTacToeGameRequest.class, this::handleStartTicTacToeGameOnAServer); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.RunTicTacToeGame.class, this::handleRunTicTacToeGameOnAServer); GlobalEventBus.subscribeAndRegister(Events.ServerEvents.EndTicTacToeGame.class, this::handleEndTicTacToeGameOnAServer); } @@ -69,18 +69,18 @@ public class ServerManager { )); } - private void handleStartTicTacToeGameOnAServer(Events.ServerEvents.CreateTicTacToeGame event) { + private void handleStartTicTacToeGameOnAServer(Events.ServerEvents.CreateTicTacToeGameRequest event) { TicTacToeServer serverThing = (TicTacToeServer) this.servers.get(event.serverUuid()); String gameId = null; if (serverThing != null) { try { gameId = serverThing.newGame(event.playerA(), event.playerB()); - logger.info("Created game on server {}", event.serverUuid()); + logger.info("Created game on server: {}", event.serverUuid()); } catch (Exception e) { // TODO: Error handling - logger.info("Could not create game on server {}", event.serverUuid()); + logger.error("Could not create game on server: {}", event.serverUuid()); } - } + } else { logger.warn("Could not find server: {}", event.serverUuid()); } event.future().complete(gameId); } From c52311bedcda7ec367473cf6dcd6c3f3faca60bb Mon Sep 17 00:00:00 2001 From: lieght Date: Tue, 16 Sep 2025 22:35:26 +0200 Subject: [PATCH 4/5] Game receives commands --- src/main/java/org/toop/Main.java | 7 +- src/main/java/org/toop/eventbus/Events.java | 2 + .../org/toop/server/backend/TcpServer.java | 32 ++-- .../backend/tictactoe/ParsedCommand.java | 144 ++++++++++++++++++ .../backend/tictactoe/TicTacToeServer.java | 127 +++++---------- .../backend/tictactoe/game/TicTacToe.java | 16 +- 6 files changed, 231 insertions(+), 97 deletions(-) create mode 100644 src/main/java/org/toop/server/backend/tictactoe/ParsedCommand.java diff --git a/src/main/java/org/toop/Main.java b/src/main/java/org/toop/Main.java index f4ff8fd..1d1284a 100644 --- a/src/main/java/org/toop/Main.java +++ b/src/main/java/org/toop/Main.java @@ -39,7 +39,12 @@ public class Main { GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, ticTacToeGameId)); GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "0")); - GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId)); + GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "1")); + GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "2")); + GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "3")); + GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "4")); + GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "5")); +// GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId)); // for (int i = 0; i < 1; i++) { // Thread thread = new Thread(() -> { diff --git a/src/main/java/org/toop/eventbus/Events.java b/src/main/java/org/toop/eventbus/Events.java index 17172b0..63b7528 100644 --- a/src/main/java/org/toop/eventbus/Events.java +++ b/src/main/java/org/toop/eventbus/Events.java @@ -115,6 +115,8 @@ public class Events implements IEvents { */ public record StartConnectionRequest(String ip, String port, CompletableFuture future) {} +// public record StartGameConnectionRequest(String ip, String port, CompletableFuture future) {} + /** * Triggers when a connection to a server is established. * diff --git a/src/main/java/org/toop/server/backend/TcpServer.java b/src/main/java/org/toop/server/backend/TcpServer.java index c32a4b9..20c8cc0 100644 --- a/src/main/java/org/toop/server/backend/TcpServer.java +++ b/src/main/java/org/toop/server/backend/TcpServer.java @@ -2,9 +2,12 @@ package org.toop.server.backend; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.server.backend.tictactoe.ParsedCommand; import java.io.*; import java.net.*; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.*; import static java.lang.Thread.sleep; @@ -14,10 +17,13 @@ public class TcpServer implements Runnable { protected static final Logger logger = LogManager.getLogger(TcpServer.class); private final ExecutorService executor = Executors.newFixedThreadPool(2); - private final BlockingQueue receivedQueue = new LinkedBlockingQueue<>(); - private final BlockingQueue sendQueue = new LinkedBlockingQueue<>(); - private final int WAIT_TIME = 500; // MS - private final int RETRY_ATTEMPTS = 3; + public final BlockingQueue receivedQueue = new LinkedBlockingQueue<>(); + public final BlockingQueue commandQueue = new LinkedBlockingQueue<>(); + public final BlockingQueue sendQueue = new LinkedBlockingQueue<>(); + public final Map knownPlayers = new HashMap<>(); + public final Map playersGames = new HashMap<>(); + public final int WAIT_TIME = 500; // MS + public final int RETRY_ATTEMPTS = 3; protected int port; protected ServerSocket serverSocket = null; @@ -62,17 +68,23 @@ public class TcpServer implements Runnable { } } - protected String getNewestCommand() { - try { return receivedQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS); } + protected ParsedCommand getNewestCommand() { + try { + String rec = receivedQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS); + if (rec != null) { + return new ParsedCommand(rec); + } + } catch (InterruptedException e) { logger.error("Interrupted", e); return null; } + return null; } - - protected void sendMessage(String message) throws InterruptedException { - sendQueue.put(message); - } +// +// protected void sendMessage(String message) throws InterruptedException { +// sendQueue.put(message); +// } protected void startWorkers(Socket clientSocket) { running = true; diff --git a/src/main/java/org/toop/server/backend/tictactoe/ParsedCommand.java b/src/main/java/org/toop/server/backend/tictactoe/ParsedCommand.java new file mode 100644 index 0000000..b22a5f6 --- /dev/null +++ b/src/main/java/org/toop/server/backend/tictactoe/ParsedCommand.java @@ -0,0 +1,144 @@ +package org.toop.server.backend.tictactoe; + +import java.util.ArrayList; + +public class ParsedCommand { + public TicTacToeServerCommand command; + public ArrayList arguments; + public boolean isValidCommand; + public boolean isServerCommand; + public TicTacToeServerMessage returnMessage; + public String errorMessage; + public String originalCommand; + + public ParsedCommand(String receivedCommand) { + + if (receivedCommand.isEmpty()) { + this.command = null; + this.arguments = null; + this.isValidCommand = false; + this.isServerCommand = true; + this.returnMessage = TicTacToeServerMessage.ERR; + this.errorMessage = "The received command is empty"; + this.originalCommand = receivedCommand; + return; + } + + String[] segments = receivedCommand.split(" "); + if (segments[0].isEmpty()) { + this.command = null; + this.arguments = null; + this.isValidCommand = false; + this.isServerCommand = true; + this.returnMessage = TicTacToeServerMessage.ERR; + this.errorMessage = "The received command is empty or couldn't be split"; + this.originalCommand = receivedCommand; + return; + } + + TicTacToeServerCommand commandEnum = TicTacToeServerCommand.getCommand(segments[0]); + + switch (commandEnum) { + case MOVE -> { + if (segments.length == 2 && !segments[1].isEmpty()) { + this.command = commandEnum; + this.arguments = new ArrayList<>(1); + this.arguments.add(segments[1]); + this.returnMessage = TicTacToeServerMessage.OK; + this.isValidCommand = true; + this.isServerCommand = false; + this.errorMessage = null; + this.originalCommand = receivedCommand; + return; + } + } + case MESSAGE -> { + if (segments.length == 3 && !segments[2].isEmpty()) { + this.command = commandEnum; + this.arguments = new ArrayList<>(2); + this.arguments.add(segments[2]); + this.returnMessage = TicTacToeServerMessage.OK; + this.isValidCommand = true; + this.isServerCommand = true; + this.errorMessage = null; + this.originalCommand = receivedCommand; + return; + } + } + case CHALLENGE -> { + if (!segments[1].isEmpty() && segments[1].equals("accept") && + !segments[2].isEmpty()) { + this.command = commandEnum; + this.arguments = new ArrayList<>(2); + this.arguments.add(segments[1]); + this.arguments.add(segments[2]); // TODO: Needs to be a number. + this.returnMessage = TicTacToeServerMessage.OK; + this.isValidCommand = true; + this.isServerCommand = true; + this.errorMessage = null; + this.originalCommand = receivedCommand; + return; + } else { + this.command = commandEnum; + this.arguments = null; + this.returnMessage = TicTacToeServerMessage.ERR; + this.isValidCommand = false; + this.isServerCommand = true; + this.errorMessage = "The challenge was not parsable"; + this.originalCommand = receivedCommand; + return; + } + } + case LOGIN -> { // TODO: Challenge needs to accept different game types later. + if (!segments[1].isEmpty()) { + this.command = commandEnum; + this.arguments = new ArrayList<>(1); + this.arguments.add(segments[1]); + this.returnMessage = TicTacToeServerMessage.OK; + this.isValidCommand = true; + this.isServerCommand = true; + this.errorMessage = null; + this.originalCommand = receivedCommand; + return; + } else { + this.command = commandEnum; + this.arguments = null; + this.returnMessage = TicTacToeServerMessage.ERR; + this.isValidCommand = false; + this.isServerCommand = true; + this.errorMessage = "The received name is empty or couldn't be understood"; + this.originalCommand = receivedCommand; + return; + } + } +// case GET -> { // TODO: Get needs to accept different game types later. And get the players +// +// } + case BYE, DISCONNECT, LOGOUT, QUIT, EXIT, FORFEIT, SUBSCRIBE -> { + this.command = commandEnum; + this.arguments = null; + this.returnMessage = TicTacToeServerMessage.OK; + this.isValidCommand = true; + this.isServerCommand = true; + this.errorMessage = null; + this.originalCommand = receivedCommand; + return; + } + case null, default -> { + this.command = null; + this.arguments = null; + this.returnMessage = TicTacToeServerMessage.ERR; + this.isValidCommand = false; + this.isServerCommand = true; + this.errorMessage = segments[0] + " is not a supported command"; + this.originalCommand = receivedCommand; + return; + } + } + } +// +// public ParsedCommand parseCommand(String command) { +// return null; +// } + +} \ No newline at end of file diff --git a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java index d807609..92be98d 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java +++ b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.net.Socket; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; public class TicTacToeServer extends TcpServer { @@ -32,17 +33,54 @@ public class TicTacToeServer extends TcpServer { logger.info("Connected to client: {}", clientSocket.getInetAddress()); new Thread(() -> this.startWorkers(clientSocket)).start(); + new Thread(this::gameManagerThread).start(); } } catch (IOException e) { e.printStackTrace(); } } + @Override + protected ParsedCommand getNewestCommand() { + try { + String rec = receivedQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS); + if (rec != null) { + return new ParsedCommand(rec); + } + } + catch (InterruptedException e) { + logger.error("Interrupted", e); + return null; + } + return null; + } + + public void gameManagerThread() { + while (true) { // TODO: Very heavy on thread + try { + wait(250); + } catch (InterruptedException e) { + logger.error("Interrupted", e); + } + ParsedCommand command = getNewestCommand(); + if (command != null && !command.isServerCommand) { + TicTacToe testGame = games.values().iterator().next(); // TODO: Is to get first for testing, must be done a different way later. + testGame.addCommandToQueue(command); + logger.info("Added command to the game queue: {}", command); + return; + } + } + } + public String newGame(String playerA, String playerB) { logger.info("Creating a new game: {} vs {}", playerA, playerB); String gameId = UUID.randomUUID().toString(); TicTacToe game = new TicTacToe(playerA, playerB); this.games.put(gameId, game); +// this.knownPlayers.put(sockA, playerA); // TODO: For remembering players and validation. +// this.knownPlayers.put(sockB, playerB); +// this.playersGames.put(playerA, gameId); +// this.playersGames.put(playerB, gameId); logger.info("Created a new game: {}. {} vs {}", gameId, playerA, playerB); return gameId; } @@ -56,93 +94,12 @@ public class TicTacToeServer extends TcpServer { public void endGame(String gameId) { TicTacToe game = this.games.get(gameId); this.games.remove(gameId); +// this.knownPlayers.put(sockA, playerA); // TODO: Remove players when game is done. +// this.knownPlayers.put(sockB, playerB); +// this.playersGames.put(playerA, gameId); +// this.playersGames.put(playerB, gameId); logger.info("Ended game: {}", gameId); // TODO: Multithreading, close game in a graceful matter, etc. } - private static class ParsedCommand { - public TicTacToeServerCommand command; - public ArrayList arguments; - public boolean isValidCommand; - public TicTacToeServerMessage returnMessage; - public String errorMessage; - public String originalCommand; - - ParsedCommand(String receivedCommand) { - - if (receivedCommand.isEmpty()) { - this.command = null; - this.arguments = null; - this.isValidCommand = false; - this.returnMessage = TicTacToeServerMessage.ERR; - this.errorMessage = "The received command is empty"; - this.originalCommand = receivedCommand; - return; - } - - String[] segments = receivedCommand.split(" "); - if (segments[0].isEmpty()) { - this.command = null; - this.arguments = null; - this.isValidCommand = false; - this.returnMessage = TicTacToeServerMessage.ERR; - this.errorMessage = "The received command is empty or couldn't be split"; - this.originalCommand = receivedCommand; - return; - }; - - TicTacToeServerCommand commandEnum = TicTacToeServerCommand.getCommand(segments[0]); - switch (commandEnum) { - case MOVE -> { - if (segments.length == 2 && !segments[1].isEmpty()) { - this.command = commandEnum; - this.arguments = new ArrayList(1); - this.arguments.add(segments[1]); - this.returnMessage = TicTacToeServerMessage.OK; - this.isValidCommand = true; - this.errorMessage = null; - this.originalCommand = receivedCommand; - return; - } - } - case FORFEIT -> { - this.command = commandEnum; - this.arguments = null; - this.returnMessage = TicTacToeServerMessage.OK; - this.isValidCommand = true; - this.errorMessage = null; - this.originalCommand = receivedCommand; - return; - } - case MESSAGE -> { - if (segments.length == 3 && !segments[2].isEmpty()) { - this.command = commandEnum; - this.arguments = new ArrayList(2); - this.arguments.add(segments[2]); - this.returnMessage = TicTacToeServerMessage.OK; - this.isValidCommand = true; - this.errorMessage = null; - this.originalCommand = receivedCommand; - return; - } - } - case BYE, DISCONNECT, LOGOUT, QUIT, EXIT -> { - this.command = commandEnum; - this.arguments = null; - this.returnMessage = TicTacToeServerMessage.OK; - this.isValidCommand = true; - this.errorMessage = null; - this.originalCommand = receivedCommand; - return; - } - } - this.command = command; - this.arguments = arguments; - } - } - - private ParsedCommand parseCommand(String command) { - return null; - } - } diff --git a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java index 801c93a..d6f0956 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java +++ b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java @@ -2,13 +2,19 @@ package org.toop.server.backend.tictactoe.game; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.toop.Main; +import org.toop.server.backend.tictactoe.ParsedCommand; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + public class TicTacToe extends GameBase implements Runnable { private static final Logger logger = LogManager.getLogger(TicTacToe.class); public int moveCount; public Thread gameThread; + public BlockingQueue commandQueue = new LinkedBlockingQueue<>(); + public BlockingQueue sendQueue = new LinkedBlockingQueue<>(); public TicTacToe(String player1, String player2) { super(3); // 3x3 Grid @@ -19,6 +25,10 @@ public class TicTacToe extends GameBase implements Runnable { moveCount = 0; } + public void addCommandToQueue(ParsedCommand command) { + commandQueue.add(command); + } + @Override public void run() { this.gameThread = new Thread(this::gameThread); @@ -31,6 +41,10 @@ public class TicTacToe extends GameBase implements Runnable { // command = this.parseCommand(command).toString(); // if (command == null) { continue; } + if (commandQueue.poll() != null) { + logger.info(commandQueue.poll()); + } + // TODO: Game } From c6bf97aaa3d6f6fe5989aa999a60975f7a25bb63 Mon Sep 17 00:00:00 2001 From: lieght Date: Tue, 16 Sep 2025 23:20:54 +0200 Subject: [PATCH 5/5] Fixed multithreading locking issues with posting commands --- src/main/java/org/toop/Main.java | 45 ++----------------- .../org/toop/server/backend/TcpServer.java | 7 --- .../java/org/toop/server/backend/Testsss.java | 27 ----------- .../backend/tictactoe/TicTacToeServer.java | 4 +- .../backend/tictactoe/game/TicTacToe.java | 6 +-- 5 files changed, 9 insertions(+), 80 deletions(-) delete mode 100644 src/main/java/org/toop/server/backend/Testsss.java diff --git a/src/main/java/org/toop/Main.java b/src/main/java/org/toop/Main.java index 1d1284a..bbdc66d 100644 --- a/src/main/java/org/toop/Main.java +++ b/src/main/java/org/toop/Main.java @@ -18,10 +18,6 @@ public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException, IOException { -// TcpServer server = new TcpServer(5001); -// Thread serverThread = new Thread(server); -// serverThread.start(); - initSystems(); CompletableFuture serverIdFuture = new CompletableFuture<>(); @@ -36,46 +32,11 @@ public class Main { GlobalEventBus.post(new Events.ServerEvents.CreateTicTacToeGameRequest(serverId, "John", "Pim", ticTacToeGame)); String ticTacToeGameId = ticTacToeGame.get(); - GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, ticTacToeGameId)); - GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "0")); - GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "1")); - GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "2")); - GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "3")); - GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "4")); - GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "5")); -// GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId)); - -// for (int i = 0; i < 1; i++) { -// Thread thread = new Thread(() -> { -//// logger.info("Server ID: {}", serverId); -// GlobalEventBus.post(new Events.ServerEvents.Command(serverId, "HELP", "TEST")); -// }); -// thread.start(); -// } - -// GlobalEventBus.post(new Events.ServerEvents.ForceCloseAllConnections()); -// GlobalEventBus.post(new Events.ServerEvents.ForceCloseAllServers()); - -// -// CompletableFuture future2 = new CompletableFuture<>(); -// GlobalEventBus.post(new Events.ServerEvents.StartConnectionRequest("127.0.0.1", "5001", future2)); -// String serverId2 = future.get(); -// logger.info("Server ID: {}", serverId2); -// GlobalEventBus.post(new Events.ServerEvents.Command(serverId2, "HELP", "TEST2")); - -// GlobalEventBus.post(new Events.ServerEvents.StartConnection("127.0.0.1", "5001")); - - -// Server.startNew("127.0.0.1", "5001"); -// Testsss.start(""); // Used for testing server. -// Window.start(""); - -// CompletableFuture future6 = new CompletableFuture<>(); -// GlobalEventBus.post(new Events.ServerEvents.RequestsAllConnections(future6)); -// String serverConnections = future6.get(); -// logger.info("Running connections: {}", serverConnections); + for (int x = 0; x < 100; x++) { + GlobalEventBus.post(new Events.ServerEvents.Command(connectionId, "MOVE", "" + x)); + } } public static void initSystems() { diff --git a/src/main/java/org/toop/server/backend/TcpServer.java b/src/main/java/org/toop/server/backend/TcpServer.java index 20c8cc0..d5e5525 100644 --- a/src/main/java/org/toop/server/backend/TcpServer.java +++ b/src/main/java/org/toop/server/backend/TcpServer.java @@ -10,8 +10,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.*; -import static java.lang.Thread.sleep; - public class TcpServer implements Runnable { protected static final Logger logger = LogManager.getLogger(TcpServer.class); @@ -120,11 +118,6 @@ public class TcpServer implements Runnable { new Thread(() -> { for (int i = 0; i < this.RETRY_ATTEMPTS; i++) { if (this.receivedQueue.offer(finalMessage)) break; - try { - sleep(this.WAIT_TIME); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } } }).start(); } diff --git a/src/main/java/org/toop/server/backend/Testsss.java b/src/main/java/org/toop/server/backend/Testsss.java deleted file mode 100644 index af00056..0000000 --- a/src/main/java/org/toop/server/backend/Testsss.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.toop.server.backend; - -import org.toop.eventbus.Events; -import org.toop.eventbus.GlobalEventBus; - -public class Testsss extends Thread { - - public Testsss() {} - -// public void run() { -// while (true) { -// try { -// sleep(100); -// GlobalEventBus.post(new Events.ServerEvents.command("HELP", "TEST")); -// sleep(1000); -// GlobalEventBus.post(new Events.ServerEvents.ChangeConnection("127.0.0.1", "5001")); -// } catch (InterruptedException e) { -// throw new RuntimeException(e); -// } -// } -// } - - public static void start(String keepEmpty) { - new Testsss().start(); - } - -} diff --git a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java index 92be98d..7cddfe2 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java +++ b/src/main/java/org/toop/server/backend/tictactoe/TicTacToeServer.java @@ -58,7 +58,9 @@ public class TicTacToeServer extends TcpServer { public void gameManagerThread() { while (true) { // TODO: Very heavy on thread try { - wait(250); + synchronized (this) { + wait(250); + } // Fixes current thread is not owner. } catch (InterruptedException e) { logger.error("Interrupted", e); } diff --git a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java index d6f0956..4b24544 100644 --- a/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java +++ b/src/main/java/org/toop/server/backend/tictactoe/game/TicTacToe.java @@ -41,11 +41,11 @@ public class TicTacToe extends GameBase implements Runnable { // command = this.parseCommand(command).toString(); // if (command == null) { continue; } - if (commandQueue.poll() != null) { - logger.info(commandQueue.poll()); + if (commandQueue.poll() == null) { + continue; } - // TODO: Game + // TODO: Game use the commandQueue to get the commands. } }