Game receives commands

This commit is contained in:
lieght
2025-09-16 22:35:26 +02:00
parent 45159ec659
commit c52311bedc
6 changed files with 231 additions and 97 deletions

View File

@@ -39,7 +39,12 @@ public class Main {
GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, ticTacToeGameId)); 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", "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++) { // for (int i = 0; i < 1; i++) {
// Thread thread = new Thread(() -> { // Thread thread = new Thread(() -> {

View File

@@ -115,6 +115,8 @@ public class Events implements IEvents {
*/ */
public record StartConnectionRequest(String ip, String port, CompletableFuture<String> future) {} public record StartConnectionRequest(String ip, String port, CompletableFuture<String> future) {}
// public record StartGameConnectionRequest(String ip, String port, CompletableFuture<String> future) {}
/** /**
* Triggers when a connection to a server is established. * Triggers when a connection to a server is established.
* *

View File

@@ -2,9 +2,12 @@ package org.toop.server.backend;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.toop.server.backend.tictactoe.ParsedCommand;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*; import java.util.concurrent.*;
import static java.lang.Thread.sleep; import static java.lang.Thread.sleep;
@@ -14,10 +17,13 @@ public class TcpServer implements Runnable {
protected static final Logger logger = LogManager.getLogger(TcpServer.class); protected static final Logger logger = LogManager.getLogger(TcpServer.class);
private final ExecutorService executor = Executors.newFixedThreadPool(2); private final ExecutorService executor = Executors.newFixedThreadPool(2);
private final BlockingQueue<String> receivedQueue = new LinkedBlockingQueue<>(); public final BlockingQueue<String> receivedQueue = new LinkedBlockingQueue<>();
private final BlockingQueue<String> sendQueue = new LinkedBlockingQueue<>(); public final BlockingQueue<ParsedCommand> commandQueue = new LinkedBlockingQueue<>();
private final int WAIT_TIME = 500; // MS public final BlockingQueue<String> sendQueue = new LinkedBlockingQueue<>();
private final int RETRY_ATTEMPTS = 3; public final Map<Socket, String> knownPlayers = new HashMap<>();
public final Map<String, String> playersGames = new HashMap<>();
public final int WAIT_TIME = 500; // MS
public final int RETRY_ATTEMPTS = 3;
protected int port; protected int port;
protected ServerSocket serverSocket = null; protected ServerSocket serverSocket = null;
@@ -62,17 +68,23 @@ public class TcpServer implements Runnable {
} }
} }
protected String getNewestCommand() { protected ParsedCommand getNewestCommand() {
try { return receivedQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS); } try {
String rec = receivedQueue.poll(this.WAIT_TIME, TimeUnit.MILLISECONDS);
if (rec != null) {
return new ParsedCommand(rec);
}
}
catch (InterruptedException e) { catch (InterruptedException e) {
logger.error("Interrupted", e); logger.error("Interrupted", e);
return null; return null;
} }
return null;
} }
//
protected void sendMessage(String message) throws InterruptedException { // protected void sendMessage(String message) throws InterruptedException {
sendQueue.put(message); // sendQueue.put(message);
} // }
protected void startWorkers(Socket clientSocket) { protected void startWorkers(Socket clientSocket) {
running = true; running = true;

View File

@@ -0,0 +1,144 @@
package org.toop.server.backend.tictactoe;
import java.util.ArrayList;
public class ParsedCommand {
public TicTacToeServerCommand command;
public ArrayList<Object> 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;
// }
}

View File

@@ -9,6 +9,7 @@ import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class TicTacToeServer extends TcpServer { public class TicTacToeServer extends TcpServer {
@@ -32,17 +33,54 @@ public class TicTacToeServer extends TcpServer {
logger.info("Connected to client: {}", clientSocket.getInetAddress()); logger.info("Connected to client: {}", clientSocket.getInetAddress());
new Thread(() -> this.startWorkers(clientSocket)).start(); new Thread(() -> this.startWorkers(clientSocket)).start();
new Thread(this::gameManagerThread).start();
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); 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) { public String newGame(String playerA, String playerB) {
logger.info("Creating a new game: {} vs {}", playerA, playerB); logger.info("Creating a new game: {} vs {}", playerA, playerB);
String gameId = UUID.randomUUID().toString(); String gameId = UUID.randomUUID().toString();
TicTacToe game = new TicTacToe(playerA, playerB); TicTacToe game = new TicTacToe(playerA, playerB);
this.games.put(gameId, game); 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); logger.info("Created a new game: {}. {} vs {}", gameId, playerA, playerB);
return gameId; return gameId;
} }
@@ -56,93 +94,12 @@ public class TicTacToeServer extends TcpServer {
public void endGame(String gameId) { public void endGame(String gameId) {
TicTacToe game = this.games.get(gameId); TicTacToe game = this.games.get(gameId);
this.games.remove(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); logger.info("Ended game: {}", gameId);
// TODO: Multithreading, close game in a graceful matter, etc. // TODO: Multithreading, close game in a graceful matter, etc.
} }
private static class ParsedCommand {
public TicTacToeServerCommand command;
public ArrayList<Object> 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<Object>(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<Object>(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;
}
} }

View File

@@ -2,13 +2,19 @@ package org.toop.server.backend.tictactoe.game;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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 { public class TicTacToe extends GameBase implements Runnable {
private static final Logger logger = LogManager.getLogger(TicTacToe.class); private static final Logger logger = LogManager.getLogger(TicTacToe.class);
public int moveCount; public int moveCount;
public Thread gameThread; public Thread gameThread;
public BlockingQueue<ParsedCommand> commandQueue = new LinkedBlockingQueue<>();
public BlockingQueue<String> sendQueue = new LinkedBlockingQueue<>();
public TicTacToe(String player1, String player2) { public TicTacToe(String player1, String player2) {
super(3); // 3x3 Grid super(3); // 3x3 Grid
@@ -19,6 +25,10 @@ public class TicTacToe extends GameBase implements Runnable {
moveCount = 0; moveCount = 0;
} }
public void addCommandToQueue(ParsedCommand command) {
commandQueue.add(command);
}
@Override @Override
public void run() { public void run() {
this.gameThread = new Thread(this::gameThread); this.gameThread = new Thread(this::gameThread);
@@ -31,6 +41,10 @@ public class TicTacToe extends GameBase implements Runnable {
// command = this.parseCommand(command).toString(); // command = this.parseCommand(command).toString();
// if (command == null) { continue; } // if (command == null) { continue; }
if (commandQueue.poll() != null) {
logger.info(commandQueue.poll());
}
// TODO: Game // TODO: Game
} }