From c52311bedcda7ec367473cf6dcd6c3f3faca60bb Mon Sep 17 00:00:00 2001 From: lieght Date: Tue, 16 Sep 2025 22:35:26 +0200 Subject: [PATCH] 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 }