Formatted code to follow google formatting guidelines using the Spotless formatter

This commit is contained in:
lieght
2025-09-20 15:11:57 +02:00
parent 323f401aad
commit d5b03976b7
45 changed files with 1569 additions and 1420 deletions

View File

@@ -2,6 +2,7 @@
<dictionary name="project">
<words>
<w>aosp</w>
<w>vmoptions</w>
</words>
</dictionary>
</component>

View File

@@ -135,9 +135,9 @@
<!-- apply a specific flavor of google-java-format and reflow long strings -->
<googleJavaFormat>
<version>1.28.0</version>
<style>AOSP</style>
<style>AOSP</style> <!-- GOOGLE (2 indents), AOSP (4 indents) -->
<reflowLongStrings>true</reflowLongStrings>
<formatJavadoc>false</formatJavadoc>
<formatJavadoc>true</formatJavadoc>
</googleJavaFormat>
<!-- make sure every file has the following copyright header.

View File

@@ -1,19 +1,15 @@
package org.toop;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.toop.game.*;
import org.toop.game.tictactoe.*;
import com.google.errorprone.annotations.Keep;
import org.toop.eventbus.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.*;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.toop.game.*;
import org.toop.game.tictactoe.*;
public class ConsoleGui {
@@ -43,7 +39,8 @@ public class ConsoleGui {
2. player vs ai
3. ai vs player
4. ai v ai
Choose mode (default is 1):\s""");
Choose mode (default is 1):\s\
""");
String modeString = scanner.nextLine();
try {
@@ -57,7 +54,8 @@ public class ConsoleGui {
switch (mode) {
// player vs ai
case 2: {
case 2:
{
System.out.print("Please enter your name: ");
String name = scanner.nextLine();
@@ -68,7 +66,8 @@ public class ConsoleGui {
}
// ai vs player
case 3: {
case 3:
{
System.out.print("Enter your name: ");
String name = scanner.nextLine();
@@ -79,7 +78,8 @@ public class ConsoleGui {
}
// ai vs ai
case 4: {
case 4:
{
ai1 = player1 = "AI#" + random.nextInt();
ai2 = player2 = "AI2" + random.nextInt();
@@ -88,7 +88,8 @@ public class ConsoleGui {
// player vs player
case 1:
default: {
default:
{
System.out.print("Player 1. Please enter your name: ");
String name1 = scanner.nextLine();
@@ -104,15 +105,20 @@ public class ConsoleGui {
ai = new MinMaxTicTacToe();
CompletableFuture<String> serverIdFuture = new CompletableFuture<>();
GlobalEventBus.post(new Events.ServerEvents.StartServerRequest("5001", "tictactoe", serverIdFuture));
GlobalEventBus.post(
new Events.ServerEvents.StartServerRequest("5001", "tictactoe", serverIdFuture));
serverId = serverIdFuture.get();
CompletableFuture<String> connectionIdFuture = new CompletableFuture<>();
GlobalEventBus.post(new Events.ServerEvents.StartConnectionRequest("127.0.0.1", "5001", connectionIdFuture));
GlobalEventBus.post(
new Events.ServerEvents.StartConnectionRequest(
"127.0.0.1", "5001", connectionIdFuture));
connectionId = connectionIdFuture.get();
CompletableFuture<String> ticTacToeGame = new CompletableFuture<>();
GlobalEventBus.post(new Events.ServerEvents.CreateTicTacToeGameRequest(serverId, player1, player2, ticTacToeGame));
GlobalEventBus.post(
new Events.ServerEvents.CreateTicTacToeGameRequest(
serverId, player1, player2, ticTacToeGame));
ticTacToeGameId = ticTacToeGame.get();
GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, ticTacToeGameId));
}
@@ -144,13 +150,14 @@ public class ConsoleGui {
if (ai1 != null && current.name() == ai1 || ai2 != null && current.name() == ai2) {
move = ai.findBestMove(game);
} else {
System.out.printf("%s's (%c) turn. Please choose an empty cell between 0-8: ", current.name(), current.move());
System.out.printf(
"%s's (%c) turn. Please choose an empty cell between 0-8: ",
current.name(), current.move());
String input = scanner.nextLine();
try {
move = Integer.parseInt(input);
}
catch (NumberFormatException e) {
} catch (NumberFormatException e) {
}
}
@@ -158,41 +165,51 @@ public class ConsoleGui {
boolean keepRunning = true;
switch (state) {
case INVALID: {
case INVALID:
{
System.out.println("Please select an empty cell. Between 0-8");
return true;
}
case DRAW: {
case DRAW:
{
System.out.println("Game ended in a draw.");
keepRunning = false;
break;
}
case WIN: {
case WIN:
{
System.out.printf("%s has won the game.\n", current.name());
keepRunning = false;
break;
}
case NORMAL:
default: {
default:
{
keepRunning = true;
break;
}
}
GlobalEventBus.post(new Events.ServerEvents.SendCommand(
GlobalEventBus.post(
new Events.ServerEvents.SendCommand(
connectionId,
"gameid " + ticTacToeGameId, "player " + current.name(), "MOVE", String.valueOf(move)
));
"gameid " + ticTacToeGameId,
"player " + current.name(),
"MOVE",
String.valueOf(move)));
if (!keepRunning) {
GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId));
GlobalEventBus.post(
new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId));
}
return keepRunning;
}
public GameBase getGame() { return game; }
public GameBase getGame() {
return game;
}
}

View File

@@ -5,11 +5,8 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.toop.eventbus.EventRegistry;
/**
* Options for logging.
*/
/** Options for logging. */
public final class Logging {
public static void disableAllLogs() {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);

View File

@@ -1,17 +1,13 @@
package org.toop;
import org.toop.frontend.UI.LocalServerSelector;
import org.toop.eventbus.EventRegistry;
import java.util.concurrent.ExecutionException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.backend.ServerManager;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.toop.backend.ServerManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.toop.frontend.ConnectionManager;
import org.toop.frontend.games.LocalTicTacToe;
import java.util.concurrent.ExecutionException;
import org.toop.frontend.UI.LocalServerSelector;
public class Main {
private static final Logger logger = LogManager.getLogger(Main.class);
@@ -42,12 +38,13 @@ public class Main {
}
private static void registerEvents() {
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnQuitRequested.class, event -> {
GlobalEventBus.subscribeAndRegister(
Events.WindowEvents.OnQuitRequested.class,
event -> {
quit();
});
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseMove.class, event -> {
});
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseMove.class, event -> {});
}
public static void initSystems() {

View File

@@ -1,17 +1,15 @@
package org.toop.backend;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.backend.tictactoe.TicTacToeServer;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.backend.tictactoe.TicTacToeServer;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
// TODO more methods.
@@ -19,21 +17,24 @@ public class ServerManager {
private static final Logger logger = LogManager.getLogger(ServerManager.class);
/**
* Map of serverId -> Server instances
*/
/** Map of serverId -> Server instances */
private final Map<String, TcpServer> servers = new ConcurrentHashMap<>();
/**
* Starts a server manager, to manage, servers.
*/
/** Starts a server manager, to manage, servers. */
public 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.CreateTicTacToeGameRequest.class, this::handleStartTicTacToeGameOnAServer);
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.RunTicTacToeGame.class, this::handleRunTicTacToeGameOnAServer);
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.EndTicTacToeGame.class, this::handleEndTicTacToeGameOnAServer);
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.CreateTicTacToeGameRequest.class,
this::handleStartTicTacToeGameOnAServer);
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.RunTicTacToeGame.class, this::handleRunTicTacToeGameOnAServer);
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.EndTicTacToeGame.class, this::handleEndTicTacToeGameOnAServer);
}
private String startServer(String port, String gameType) {
@@ -43,8 +44,7 @@ public class ServerManager {
TcpServer server = null;
if (Objects.equals(gameType, "tictactoe")) {
server = new TicTacToeServer(Integer.parseInt(port));
}
else {
} else {
logger.error("Manager could not create a server for game type: {}", gameType);
return null;
}
@@ -59,28 +59,33 @@ public class ServerManager {
}
private void handleStartServerRequest(Events.ServerEvents.StartServerRequest request) {
request.future().complete(this.startServer(request.port(), request.gameType())); // 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(), event.gameType()),
event.port()
));
GlobalEventBus.post(
new Events.ServerEvents.ServerStarted(
this.startServer(event.port(), event.gameType()), event.port()));
}
private void handleStartTicTacToeGameOnAServer(Events.ServerEvents.CreateTicTacToeGameRequest 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());
}
catch (Exception e) { // TODO: Error handling
} catch (Exception e) { // TODO: Error handling
logger.error("Could not create game on server: {}", event.serverUuid());
}
} else { logger.warn("Could not find server: {}", event.serverUuid()); }
} else {
logger.warn("Could not find server: {}", event.serverUuid());
}
event.future().complete(gameId);
}

View File

@@ -1,9 +1,5 @@
package org.toop.backend;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.backend.tictactoe.ParsedCommand;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -12,18 +8,18 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.backend.tictactoe.ParsedCommand;
/**
* Lightweight, thread-pool based TCP server base class.
*
* Responsibilities:
* - accept sockets
* - hand off socket I/O to connectionExecutor (pooled threads)
* - provide thread-safe queues (receivedQueue / sendQueue) to subclasses
* <p>Responsibilities: - accept sockets - hand off socket I/O to connectionExecutor (pooled
* threads) - provide thread-safe queues (receivedQueue / sendQueue) to subclasses
*
* Notes:
* - Subclasses should consume receivedQueue (or call getNewestCommand()) and
* use sendQueue to send messages to all clients (or per-client, if implemented).
* <p>Notes: - Subclasses should consume receivedQueue (or call getNewestCommand()) and use
* sendQueue to send messages to all clients (or per-client, if implemented).
*/
public abstract class TcpServer implements Runnable {
@@ -58,8 +54,8 @@ public abstract class TcpServer implements Runnable {
}
/**
* Default run: accept connections and hand off to connectionExecutor.
* Subclasses overriding run() should still call startWorkers(Socket) for each accepted socket.
* Default run: accept connections and hand off to connectionExecutor. Subclasses overriding
* run() should still call startWorkers(Socket) for each accepted socket.
*/
@Override
public void run() {
@@ -81,33 +77,41 @@ public abstract class TcpServer implements Runnable {
}
/**
* Listen/Write workers for an accepted client socket.
* This method submits two tasks to the connectionExecutor:
* - inputLoop: reads lines and enqueues them to receivedQueue
* - outputLoop: polls sendQueue and writes messages to the client
* Listen/Write workers for an accepted client socket. This method submits two tasks to the
* connectionExecutor: - inputLoop: reads lines and enqueues them to receivedQueue - outputLoop:
* polls sendQueue and writes messages to the client
*
* Note: This is a simple model where sendQueue is global; if you need per-client
* <p>Note: This is a simple model where sendQueue is global; if you need per-client
* send-queues, adapt this method to use one per socket.
*/
protected void startWorkers(Socket clientSocket) {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedReader in =
new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
// Input task: read lines and put them on receivedQueue
Runnable inputTask = () -> {
logger.info("Starting read loop for {}", clientSocket.getRemoteSocketAddress());
Runnable inputTask =
() -> {
logger.info(
"Starting read loop for {}", clientSocket.getRemoteSocketAddress());
try {
String line;
while (running && (line = in.readLine()) != null) {
if (line.isEmpty()) continue;
logger.debug("Received from {}: {}", clientSocket.getRemoteSocketAddress(), line);
logger.debug(
"Received from {}: {}",
clientSocket.getRemoteSocketAddress(),
line);
boolean offered = false;
for (int i = 0; i < RETRY_ATTEMPTS && !offered; i++) {
try {
// Use offer to avoid blocking indefinitely; adapt timeout/policy as needed
offered = this.receivedQueue.offer(line, 200, TimeUnit.MILLISECONDS);
// Use offer to avoid blocking indefinitely; adapt
// timeout/policy as needed
offered =
this.receivedQueue.offer(
line, 200, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
@@ -115,44 +119,68 @@ public abstract class TcpServer implements Runnable {
}
if (!offered) {
logger.warn("Backpressure: dropping line from {}: {}", clientSocket.getRemoteSocketAddress(), line);
// Policy choice: drop, notify, or close connection. We drop here.
logger.warn(
"Backpressure: dropping line from {}: {}",
clientSocket.getRemoteSocketAddress(),
line);
// Policy choice: drop, notify, or close connection. We drop
// here.
}
}
} catch (IOException e) {
logger.info("Connection closed by remote: {}", clientSocket.getRemoteSocketAddress());
logger.info(
"Connection closed by remote: {}",
clientSocket.getRemoteSocketAddress());
} finally {
try {
clientSocket.close();
} catch (IOException ignored) {}
logger.info("Stopped read loop for {}", clientSocket.getRemoteSocketAddress());
} catch (IOException ignored) {
}
logger.info(
"Stopped read loop for {}",
clientSocket.getRemoteSocketAddress());
}
};
// Output task: poll global sendQueue and write to this specific client.
// NOTE: With a single global sendQueue, every message is sent to every connected client.
// NOTE: With a single global sendQueue, every message is sent to every connected
// client.
// If you want per-client sends, change this to use per-client queue map.
Runnable outputTask = () -> {
logger.info("Starting write loop for {}", clientSocket.getRemoteSocketAddress());
Runnable outputTask =
() -> {
logger.info(
"Starting write loop for {}",
clientSocket.getRemoteSocketAddress());
try {
while (running && !clientSocket.isClosed()) {
String msg = sendQueue.poll(WAIT_TIME, TimeUnit.MILLISECONDS);
if (msg != null) {
out.println(msg);
out.flush();
logger.debug("Sent to {}: {}", clientSocket.getRemoteSocketAddress(), msg);
logger.debug(
"Sent to {}: {}",
clientSocket.getRemoteSocketAddress(),
msg);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.info("Writer interrupted for {}", clientSocket.getRemoteSocketAddress());
logger.info(
"Writer interrupted for {}",
clientSocket.getRemoteSocketAddress());
} catch (Exception e) {
logger.error("Writer error for {}: {}", clientSocket.getRemoteSocketAddress(), e.toString());
logger.error(
"Writer error for {}: {}",
clientSocket.getRemoteSocketAddress(),
e.toString());
} finally {
try {
clientSocket.close();
} catch (IOException ignored) {}
logger.info("Stopped write loop for {}", clientSocket.getRemoteSocketAddress());
} catch (IOException ignored) {
}
logger.info(
"Stopped write loop for {}",
clientSocket.getRemoteSocketAddress());
}
};
@@ -164,13 +192,14 @@ public abstract class TcpServer implements Runnable {
logger.error("Could not start workers for client: {}", e.toString());
try {
clientSocket.close();
} catch (IOException ignored) {}
} catch (IOException ignored) {
}
}
}
/**
* Convenience: wrapper to obtain the latest command (non-blocking poll).
* Subclasses can use this, but for blocking behavior consider using receivedQueue.take()
* Convenience: wrapper to obtain the latest command (non-blocking poll). Subclasses can use
* this, but for blocking behavior consider using receivedQueue.take()
*/
protected ParsedCommand getNewestCommand() {
try {
@@ -183,19 +212,20 @@ public abstract class TcpServer implements Runnable {
return null;
}
/**
* Stop server and cleanup executors/sockets.
*/
/** Stop server and cleanup executors/sockets. */
public void stop() {
running = false;
try {
serverSocket.close();
} catch (IOException ignored) {}
} catch (IOException ignored) {
}
connectionExecutor.shutdownNow();
logger.info("TcpServer stopped. receivedQueue size={}, sendQueue size={}",
receivedQueue.size(), sendQueue.size());
logger.info(
"TcpServer stopped. receivedQueue size={}, sendQueue size={}",
receivedQueue.size(),
sendQueue.size());
}
}

View File

@@ -1,11 +1,10 @@
package org.toop.backend.tictactoe;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ParsedCommand {
private static final Logger logger = LogManager.getLogger(ParsedCommand.class);
@@ -37,8 +36,10 @@ public class ParsedCommand {
}
// Case-insensitive regex to match: game_id {id} player {name}
Pattern pattern = Pattern.compile(
"(?i)\\bgame[_]?id\\s+(\\S+)\\s+player\\s+(\\S+)", Pattern.CASE_INSENSITIVE);
Pattern pattern =
Pattern.compile(
"(?i)\\bgame[_]?id\\s+(\\S+)\\s+player\\s+(\\S+)",
Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(receivedCommand);
String tempGameId = null;
@@ -129,8 +130,9 @@ public class ParsedCommand {
}
}
case CHALLENGE -> {
if (!segments[1].isEmpty() && segments[1].equals("accept") &&
!segments[2].isEmpty()) {
if (!segments[1].isEmpty()
&& segments[1].equals("accept")
&& !segments[2].isEmpty()) {
this.command = commandEnum;
this.arguments = new ArrayList<>(2);
this.arguments.add(segments[1]);
@@ -174,7 +176,8 @@ public class ParsedCommand {
return;
}
}
// case GET -> { // TODO: Get needs to accept different game types later. And get the players
// case GET -> { // TODO: Get needs to accept different game types later.
// And get the players
//
// }
case BYE, DISCONNECT, LOGOUT, QUIT, EXIT, FORFEIT, SUBSCRIBE -> {

View File

@@ -1,21 +1,21 @@
package org.toop.backend.tictactoe;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.game.tictactoe.TicTacToe;
import org.toop.backend.TcpServer;
import java.io.IOException;
import java.net.Socket;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.backend.TcpServer;
import org.toop.game.tictactoe.TicTacToe;
public class TicTacToeServer extends TcpServer {
protected static final Logger logger = LogManager.getLogger(TicTacToeServer.class);
private final ExecutorService connectionExecutor = Executors.newCachedThreadPool(); // socket I/O
private final ExecutorService connectionExecutor =
Executors.newCachedThreadPool(); // socket I/O
private final ExecutorService dispatcherExecutor;
private final ExecutorService forwarderExecutor = Executors.newSingleThreadExecutor();
@@ -26,7 +26,9 @@ public class TicTacToeServer extends TcpServer {
super(port);
int dispatchers = Math.max(2, Runtime.getRuntime().availableProcessors());
this.dispatcherExecutor = Executors.newFixedThreadPool(dispatchers + 1); // TODO: Magic number for forwardMessages
this.dispatcherExecutor =
Executors.newFixedThreadPool(
dispatchers + 1); // TODO: Magic number for forwardMessages
this.incomingCommands = new LinkedBlockingQueue<>(5_000);
forwarderExecutor.submit(this::forwardLoop);
@@ -52,9 +54,7 @@ public class TicTacToeServer extends TcpServer {
}
}
/**
* Forwards raw messages from TcpServer.receivedQueue into ParsedCommand objects.
*/
/** Forwards raw messages from TcpServer.receivedQueue into ParsedCommand objects. */
private void forwardLoop() {
logger.info("Forwarder loop started");
try {
@@ -74,9 +74,7 @@ public class TicTacToeServer extends TcpServer {
}
}
/**
* Dispatches parsed commands into the game logic.
*/
/** Dispatches parsed commands into the game logic. */
private void dispatchLoop() {
logger.info("Dispatcher thread started");
try {
@@ -91,9 +89,13 @@ public class TicTacToeServer extends TcpServer {
TicTacToe game = this.games.get(command.gameId);
if (game != null) {
game.addCommandToQueue(command);
logger.info("Dispatched command {} to game {}", command.toString(), command.gameId);
logger.info(
"Dispatched command {} to game {}", command.toString(), command.gameId);
} else {
logger.warn("No active game with ID {} for command {}", command.gameId, command.toString());
logger.warn(
"No active game with ID {} for command {}",
command.gameId,
command.toString());
// TODO: reply back
}
}
@@ -109,12 +111,18 @@ public class TicTacToeServer extends TcpServer {
}
if (command.command == TicTacToeServerCommand.CREATE_GAME) {
String gameId = this.newGame((String) command.arguments.getFirst(), (String) command.arguments.get(1));
String gameId =
this.newGame(
(String) command.arguments.getFirst(),
(String) command.arguments.get(1));
this.sendQueue.offer("game created successfully|gameid " + gameId);
} else if (command.command == TicTacToeServerCommand.START_GAME) {
boolean success = this.runGame((String) command.arguments.getFirst());
if (success) {this.sendQueue.offer("svr game is running successfully");}
else {this.sendQueue.offer("svr running game failed");}
if (success) {
this.sendQueue.offer("svr game is running successfully");
} else {
this.sendQueue.offer("svr running game failed");
}
} else if (command.command == TicTacToeServerCommand.END_GAME) {
this.endGame((String) command.arguments.getFirst());
this.sendQueue.offer("svr game ended successfully");
@@ -128,11 +136,15 @@ public class TicTacToeServer extends TcpServer {
}
public void forwardGameMessages(TicTacToe game) {
dispatcherExecutor.submit(() -> {
dispatcherExecutor.submit(
() -> {
try {
while (isRunning()) {
String msg = game.sendQueue.take(); // blocks until a message is added to the queue
logger.info("Games: {}, Adding: {} to the send queue", game.gameId, msg);
String msg =
game.sendQueue
.take(); // blocks until a message is added to the queue
logger.info(
"Games: {}, Adding: {} to the send queue", game.gameId, msg);
this.sendQueue.put(msg); // push to network layer
}
} catch (InterruptedException e) {

View File

@@ -6,13 +6,9 @@ public enum TicTacToeServerCommand {
CREATE_GAME,
START_GAME,
END_GAME,
/**
* Login, "username"
*/
/** Login, "username" */
LOGIN,
/**
* Logout, "username"
*/
/** Logout, "username" */
LOGOUT,
EXIT,
QUIT,
@@ -26,13 +22,21 @@ public enum TicTacToeServerCommand {
MESSAGE,
HELP;
private static final EnumSet<TicTacToeServerCommand> VALID_COMMANDS = EnumSet.of(
TicTacToeServerCommand.LOGIN, TicTacToeServerCommand.LOGOUT, TicTacToeServerCommand.EXIT,
TicTacToeServerCommand.QUIT, TicTacToeServerCommand.DISCONNECT, TicTacToeServerCommand.BYE,
TicTacToeServerCommand.GET, TicTacToeServerCommand.SUBSCRIBE, TicTacToeServerCommand.MOVE,
TicTacToeServerCommand.CHALLENGE, TicTacToeServerCommand.FORFEIT,
TicTacToeServerCommand.MESSAGE, TicTacToeServerCommand.HELP
);
private static final EnumSet<TicTacToeServerCommand> VALID_COMMANDS =
EnumSet.of(
TicTacToeServerCommand.LOGIN,
TicTacToeServerCommand.LOGOUT,
TicTacToeServerCommand.EXIT,
TicTacToeServerCommand.QUIT,
TicTacToeServerCommand.DISCONNECT,
TicTacToeServerCommand.BYE,
TicTacToeServerCommand.GET,
TicTacToeServerCommand.SUBSCRIBE,
TicTacToeServerCommand.MOVE,
TicTacToeServerCommand.CHALLENGE,
TicTacToeServerCommand.FORFEIT,
TicTacToeServerCommand.MESSAGE,
TicTacToeServerCommand.HELP);
public static EnumSet<TicTacToeServerCommand> getValidCommands() {
return VALID_COMMANDS;
@@ -65,5 +69,4 @@ public enum TicTacToeServerCommand {
}
return null;
}
}

View File

@@ -7,9 +7,11 @@ public enum TicTacToeServerMessage {
ERR,
SVR;
private static final EnumSet<TicTacToeServerMessage> VALID_COMMANDS = EnumSet.of(
TicTacToeServerMessage.OK, TicTacToeServerMessage.ERR, TicTacToeServerMessage.SVR
);
private static final EnumSet<TicTacToeServerMessage> VALID_COMMANDS =
EnumSet.of(
TicTacToeServerMessage.OK,
TicTacToeServerMessage.ERR,
TicTacToeServerMessage.SVR);
public static EnumSet<TicTacToeServerMessage> getValidCommands() {
return VALID_COMMANDS;
@@ -41,5 +43,4 @@ public enum TicTacToeServerMessage {
}
return null;
}
}

View File

@@ -1,12 +1,12 @@
package org.toop.core;
import java.io.*;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class FileSystem {
public record File(String path, CharSequence buffer) {};
public record File(String path, CharSequence buffer) {}
;
private static final Logger logger = LogManager.getLogger(FileSystem.class);

View File

@@ -1,9 +1,8 @@
package org.toop.core;
import org.toop.frontend.platform.core.glfw.GlfwWindow;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.frontend.platform.core.glfw.GlfwWindow;
public abstract class Window {
public enum API {

View File

@@ -1,8 +1,6 @@
package org.toop.eventbus;
/**
* Wraps an event with its type and a ready flag.
*/
/** Wraps an event with its type and a ready flag. */
public class EventMeta<T> {
private final Class<T> type;
private final Object event;
@@ -32,10 +30,13 @@ public class EventMeta<T> {
@Override
public String toString() {
return "ReadyEvent{" +
"type=" + type.getSimpleName() +
", event=" + event +
", ready=" + ready +
'}';
return "ReadyEvent{"
+ "type="
+ type.getSimpleName()
+ ", event="
+ event
+ ", ready="
+ ready
+ '}';
}
}

View File

@@ -1,16 +1,13 @@
package org.toop.eventbus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Thread-safe registry for storing events and tracking readiness of event types.
*/
/** Thread-safe registry for storing events and tracking readiness of event types. */
public class EventRegistry {
private static final Logger logger = LogManager.getLogger(EventRegistry.class);
@@ -20,9 +17,7 @@ public class EventRegistry {
private static final Map<Class<?>, Boolean> readyStates = new ConcurrentHashMap<>();
/**
* Stores an event in the registry. Safe for concurrent use.
*/
/** Stores an event in the registry. Safe for concurrent use. */
public static <T> void storeEvent(EventMeta<T> eventMeta) {
logger.info("Storing event: {}", eventMeta.toString());
eventHistory
@@ -30,41 +25,31 @@ public class EventRegistry {
.add(new EventEntry<>(eventMeta));
}
/**
* Marks a specific event type as ready (safe to post).
*/
/** Marks a specific event type as ready (safe to post). */
public static <T> void markReady(Class<T> type) {
logger.info("Marking event as ready: {}", type.toString());
readyStates.put(type, true);
}
/**
* Marks a specific event type as not ready (posting will fail).
*/
/** Marks a specific event type as not ready (posting will fail). */
public static <T> void markNotReady(Class<T> type) {
logger.info("Marking event as not ready: {}", type.toString());
readyStates.put(type, false);
}
/**
* Returns true if this event type is marked ready.
*/
/** Returns true if this event type is marked ready. */
public static <T> boolean isReady(Class<T> type) {
return readyStates.getOrDefault(type, false);
}
/**
* Gets all stored events of a given type.
*/
/** Gets all stored events of a given type. */
@SuppressWarnings("unchecked")
public static <T> List<EventEntry<T>> getEvents(Class<T> type) {
return (List<EventEntry<T>>) (List<?>) eventHistory
.getOrDefault(type, new CopyOnWriteArrayList<>());
return (List<EventEntry<T>>)
(List<?>) eventHistory.getOrDefault(type, new CopyOnWriteArrayList<>());
}
/**
* Gets the most recent event of a given type, or null if none exist.
*/
/** Gets the most recent event of a given type, or null if none exist. */
@SuppressWarnings("unchecked")
public static <T> EventEntry<T> getLastEvent(Class<T> type) {
List<EventEntry<?>> entries = eventHistory.get(type);
@@ -74,26 +59,20 @@ public class EventRegistry {
return (EventEntry<T>) entries.getLast();
}
/**
* Clears the stored events for a given type.
*/
/** Clears the stored events for a given type. */
public static <T> void clearEvents(Class<T> type) {
logger.info("Clearing events: {}", type.toString());
eventHistory.remove(type);
}
/**
* Clears all events and resets readiness.
*/
/** Clears all events and resets readiness. */
public static void reset() {
logger.info("Resetting event registry events");
eventHistory.clear();
readyStates.clear();
}
/**
* Wrapper for stored events, with a ready flag for per-event state.
*/
/** Wrapper for stored events, with a ready flag for per-event state. */
public static class EventEntry<T> {
private final T event;
private volatile boolean ready = false;
@@ -116,10 +95,7 @@ public class EventRegistry {
@Override
public String toString() {
return "EventEntry{" +
"event=" + event +
", ready=" + ready +
'}';
return "EventEntry{" + "event=" + event + ", ready=" + ready + '}';
}
}
}

View File

@@ -1,19 +1,15 @@
package org.toop.eventbus;
import org.toop.core.Window;
import org.toop.backend.tictactoe.TicTacToeServer;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import org.toop.backend.tictactoe.TicTacToeServer;
import org.toop.core.Window;
/**
* Events that are used in the GlobalEventBus class.
*/
/** Events that are used in the GlobalEventBus class. */
public class Events implements IEvents {
/**
*
* WIP, DO NOT USE!
*
* @param eventName
@@ -29,7 +25,6 @@ public class Events implements IEvents {
}
/**
*
* WIP, DO NOT USE!
*
* @param eventCategory
@@ -38,15 +33,16 @@ public class Events implements IEvents {
* @return
* @throws Exception
*/
public static Object get(String eventCategory, String eventName, Object... args) throws Exception {
Class<?> clazz = Class.forName("org.toop.eventbus.Events$" + eventCategory + "$" + eventName);
public static Object get(String eventCategory, String eventName, Object... args)
throws Exception {
Class<?> clazz =
Class.forName("org.toop.eventbus.Events$" + eventCategory + "$" + eventName);
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
Constructor<?> constructor = clazz.getConstructor(paramTypes);
return constructor.newInstance(args);
}
/**
*
* WIP, DO NOT USE!
*
* @param eventName
@@ -77,29 +73,25 @@ public class Events implements IEvents {
public static class ServerEvents {
/**
* BLOCKING
* Requests all active connections. The result is returned via the provided CompletableFuture.
* BLOCKING Requests all active connections. The result is returned via the provided
* CompletableFuture.
*
* @param future List of all connections in string form.
*/
public record RequestsAllConnections(CompletableFuture<String> future) {}
/**
* BLOCKING
* Requests all active servers. The result is returned via the provided CompletableFuture.
* BLOCKING Requests all active servers. The result is returned via the provided
* CompletableFuture.
*
* @param future List of all servers in string form.
*/
public record RequestsAllServers(CompletableFuture<String> future) {}
/**
* Forces closing all active connections immediately.
*/
/** Forces closing all active connections immediately. */
public record ForceCloseAllConnections() {}
/**
* Forces closing all active servers immediately.
*/
/** Forces closing all active servers immediately. */
public record ForceCloseAllServers() {}
/**
@@ -111,15 +103,15 @@ public class Events implements IEvents {
public record StartServer(String port, String gameType) {}
/**
* BLOCKING
* Requests starting a server with a specific port and game type, and returns a CompletableFuture
* that completes when the server has started.
* BLOCKING Requests starting a server with a specific port and game type, and returns a
* CompletableFuture that completes when the server has started.
*
* @param port The port to open the server.
* @param gameType Either "tictactoe" or ...
* @param future The uuid of the server.
*/
public record StartServerRequest(String port, String gameType, CompletableFuture<String> future) {}
public record StartServerRequest(
String port, String gameType, CompletableFuture<String> future) {}
/**
* Represents a server that has successfully started.
@@ -130,15 +122,18 @@ public class Events implements IEvents {
public record ServerStarted(String uuid, String port) {}
/**
* BLOCKING
* Requests creation of a TicTacToe game on a specific server.
* BLOCKING Requests creation of a TicTacToe game on a specific server.
*
* @param serverUuid The unique identifier of the server where the game will be created.
* @param playerA The name of the first player.
* @param playerB The name of the second player.
* @param future The game UUID when the game is created.
*/
public record CreateTicTacToeGameRequest(String serverUuid, String playerA, String playerB, CompletableFuture<String> future) {}
public record CreateTicTacToeGameRequest(
String serverUuid,
String playerA,
String playerB,
CompletableFuture<String> future) {}
/**
* Requests running a TicTacToe game on a specific server.
@@ -157,7 +152,6 @@ public class Events implements IEvents {
public record EndTicTacToeGame(String serverUuid, String gameUuid) {}
/**
*
* Triggers starting a server connection.
*
* @param ip The IP address of the server to connect to.
@@ -166,20 +160,20 @@ public class Events implements IEvents {
public record StartConnection(String ip, String port) {}
/**
* BLOCKING
* Triggers starting a server connection and returns a future.
* BLOCKING Triggers starting a server connection and returns a future.
*
* @param ip The IP address of the server to connect to.
* @param port The port of the server to connect to.
* @param future Returns the UUID of the connection, when connection is established.
*/
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) {}
// public record StartGameConnectionRequest(String ip, String port,
// CompletableFuture<String> future) {}
/**
* BLOCKING
* Triggers starting a server connection and returns a future.
* BLOCKING Triggers starting a server connection and returns a future.
*
* @param ip The IP address of the server to connect to.
* @param port The port of the server to connect to.
@@ -195,14 +189,14 @@ public class Events implements IEvents {
public record SendCommand(String connectionId, String... args) {}
/**
* WIP
* Triggers when a command is sent to a server.
* WIP Triggers when a command is sent to a server.
*
* @param command The TicTacToeServer instance that executed the command.
* @param args The command arguments.
* @param result The result returned from executing the command.
*/
public record OnCommand(TicTacToeServer command, String[] args, String result) {} // TODO old
public record OnCommand(
TicTacToeServer command, String[] args, String result) {} // TODO old
/**
* Triggers when the server client receives a message.
@@ -249,55 +243,33 @@ public class Events implements IEvents {
*/
public record CouldNotConnect(Object connectionId) {}
/**
* WIP
* Triggers when a connection closes.
*
*/
/** WIP Triggers when a connection closes. */
public record ClosedConnection() {}
/**
* Triggers when a cell is clicked in one of the game boards.
*/
/** Triggers when a cell is clicked in one of the game boards. */
public record CellClicked(int cell) {}
}
public static class EventBusEvents {
}
public static class EventBusEvents {}
public static class WindowEvents {
/**
* Triggers when the window wants to quit.
*/
/** Triggers when the window wants to quit. */
public record OnQuitRequested() {}
/**
* Triggers when the window is resized.
*/
/** Triggers when the window is resized. */
public record OnResize(Window.Size size) {}
/**
* Triggers when the mouse is moved within the window.
*/
/** Triggers when the mouse is moved within the window. */
public record OnMouseMove(int x, int y) {}
/**
* Triggers when the mouse is clicked within the window.
*/
/** Triggers when the mouse is clicked within the window. */
public record OnMouseClick(int button) {}
/**
* Triggers when the mouse is released within the window.
*/
/** Triggers when the mouse is released within the window. */
public record OnMouseRelease(int button) {}
}
public static class TttEvents {
public static class TttEvents {}
}
public static class AiTttEvents {
}
public static class AiTttEvents {}
}

View File

@@ -2,17 +2,12 @@ package org.toop.eventbus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import java.util.function.Consumer;
/**
* A singleton Event Bus to be used for creating, triggering and activating events.
*/
/** A singleton Event Bus to be used for creating, triggering and activating events. */
public class GlobalEventBus {
/**
* Singleton event bus.
*/
/** Singleton event bus. */
private static EventBus eventBus = new EventBus("global-bus");
private GlobalEventBus() {}
@@ -35,16 +30,13 @@ public class GlobalEventBus {
eventBus = newBus;
}
/**
* Reset back to the default global EventBus.
*/
/** Reset back to the default global EventBus. */
public static void reset() {
eventBus = new EventBus("global-bus");
}
/**
* Wraps a Consumer into a Guava @Subscribe-compatible listener.
* TODO
* Wraps a Consumer into a Guava @Subscribe-compatible listener. TODO
*
* @param type The event to be used. (e.g. Events.ServerCommand.class)
* @param action The function, or lambda to run when fired.
@@ -74,7 +66,6 @@ public class GlobalEventBus {
return listener;
}
/**
* Wrapper for registering a listener.
*
@@ -102,7 +93,8 @@ public class GlobalEventBus {
Class<T> type = (Class<T>) event.getClass();
// if (!EventRegistry.isReady(type)) {
// throw new IllegalStateException("Event type not ready: " + type.getSimpleName());
// throw new IllegalStateException("Event type not ready: " +
// type.getSimpleName());
// } TODO: Handling non ready events.
// store in registry
@@ -112,5 +104,4 @@ public class GlobalEventBus {
// post to Guava EventBus
GlobalEventBus.get().post(event);
}
}

View File

@@ -1,38 +1,40 @@
package org.toop.frontend;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
public class ConnectionManager {
private static final Logger logger = LogManager.getLogger(ConnectionManager.class);
/**
* Map of serverId -> Server instances
*/
/** Map of serverId -> Server instances */
private final Map<String, ServerConnection> serverConnections = new ConcurrentHashMap<>();
/**
* Starts a connection manager, to manage, connections.
*/
/** Starts a connection manager, to manage, connections. */
public ConnectionManager() {
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartConnectionRequest.class, this::handleStartConnectionRequest);
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.StartConnection.class, this::handleStartConnection);
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.SendCommand.class, this::handleCommand);
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.Reconnect.class, this::handleReconnect);
// GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ChangeConnection.class, this::handleChangeConnection);
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ForceCloseAllConnections.class, _ -> shutdownAll());
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.RequestsAllConnections.class, this::getAllConnections);
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.StartConnectionRequest.class,
this::handleStartConnectionRequest);
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.StartConnection.class, this::handleStartConnection);
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.SendCommand.class, this::handleCommand);
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.Reconnect.class, this::handleReconnect);
// GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ChangeConnection.class,
// this::handleChangeConnection);
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.ForceCloseAllConnections.class, _ -> shutdownAll());
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.RequestsAllConnections.class, this::getAllConnections);
}
private String startConnectionRequest(String ip, String port) {
@@ -49,18 +51,24 @@ public class ConnectionManager {
}
private void handleStartConnectionRequest(Events.ServerEvents.StartConnectionRequest request) {
request.future().complete(this.startConnectionRequest(request.ip(), request.port())); // TODO: Maybe post ConnectionEstablished event.
request.future()
.complete(
this.startConnectionRequest(
request.ip(),
request.port())); // TODO: Maybe post ConnectionEstablished event.
}
private void handleStartConnection(Events.ServerEvents.StartConnection event) {
GlobalEventBus.post(new Events.ServerEvents.ConnectionEstablished(
GlobalEventBus.post(
new Events.ServerEvents.ConnectionEstablished(
this.startConnectionRequest(event.ip(), event.port()),
event.ip(),
event.port()
));
event.port()));
}
private void handleCommand(Events.ServerEvents.SendCommand event) { // TODO: Move this to ServerConnection class, keep it internal.
private void handleCommand(
Events.ServerEvents.SendCommand
event) { // TODO: Move this to ServerConnection class, keep it internal.
ServerConnection serverConnection = this.serverConnections.get(event.connectionId());
if (serverConnection != null) {
serverConnection.sendCommandByString(event.args());
@@ -87,10 +95,13 @@ public class ConnectionManager {
// if (serverConnection != null) {
// try {
// serverConnection.connect(event.ip(), event.port());
// logger.info("Server {} changed connection to {}:{}", event.connectionId(), event.ip(), event.port());
// logger.info("Server {} changed connection to {}:{}", event.connectionId(),
// event.ip(), event.port());
// } catch (Exception e) {
// logger.error("Server {} failed to change connection", event.connectionId(), e);
// GlobalEventBus.post(new Events.ServerEvents.CouldNotConnect(event.connectionId()));
// logger.error("Server {} failed to change connection", event.connectionId(),
// e);
// GlobalEventBus.post(new
// Events.ServerEvents.CouldNotConnect(event.connectionId()));
// }
// }
// } TODO

View File

@@ -1,13 +1,12 @@
package org.toop.frontend;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
public final class ServerConnection extends TcpClient implements Runnable {
@@ -28,7 +27,6 @@ public final class ServerConnection extends TcpClient implements Runnable {
}
/**
*
* Sends a command to the server.
*
* @param args The arguments for the command.
@@ -86,7 +84,9 @@ public final class ServerConnection extends TcpClient implements Runnable {
if (received != null) {
logger.info("Connection: {} received: '{}'", this.uuid, received);
// this.addReceivedMessageToQueue(received); // TODO: Will never go empty
GlobalEventBus.post(new Events.ServerEvents.ReceivedMessage(this.uuid, received)); // TODO: mb change
GlobalEventBus.post(
new Events.ServerEvents.ReceivedMessage(
this.uuid, received)); // TODO: mb change
} else {
break;
}
@@ -114,7 +114,6 @@ public final class ServerConnection extends TcpClient implements Runnable {
}
/**
*
* Connect to a new server.
*
* @param ip The ip to connect to.
@@ -132,7 +131,6 @@ public final class ServerConnection extends TcpClient implements Runnable {
}
/**
*
* Reconnects to previous address.
*
* @throws IOException wip
@@ -141,14 +139,14 @@ public final class ServerConnection extends TcpClient implements Runnable {
this.connect(this.serverAddress, this.serverPort);
}
/**
*
* Close connection to server.
*
*/
/** Close connection to server. */
public void closeConnection() {
this.stopWorkers();
logger.info("Closed connection: {}, to server {}:{}", this.uuid, this.serverAddress, this.serverPort);
logger.info(
"Closed connection: {}, to server {}:{}",
this.uuid,
this.serverAddress,
this.serverPort);
}
@Override
@@ -164,9 +162,6 @@ public final class ServerConnection extends TcpClient implements Runnable {
public String toString() {
return String.format(
"Server {ip: \"%s\", port: \"%s\", running: %s}",
this.serverAddress, this.serverPort, this.running
);
this.serverAddress, this.serverPort, this.running);
}
}

View File

@@ -7,9 +7,7 @@ import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
* A simple wrapper for creating TCP clients.
*/
/** A simple wrapper for creating TCP clients. */
public abstract class TcpClient {
InetAddress serverAddress;
@@ -61,5 +59,4 @@ public abstract class TcpClient {
public void close() throws IOException {
this.socket.close();
}
}

View File

@@ -1,7 +1,7 @@
package org.toop.frontend.UI;
import javax.swing.*;
import java.awt.*;
import javax.swing.*;
public class BackgroundPanel extends JPanel {
private Image backgroundImage;

View File

@@ -1,17 +1,11 @@
package org.toop.frontend.UI;
import java.awt.*;
import javax.swing.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.toop.frontend.games.LocalTicTacToe;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
public class LocalGameSelector extends JFrame {
private static final Logger logger = LogManager.getLogger(LocalGameSelector.class);

View File

@@ -18,7 +18,6 @@ public class LocalServerSelector {
serverButton.addActionListener(e -> onServerClicked());
localButton.addActionListener(e -> onLocalClicked());
}
private void onServerClicked() {
@@ -30,5 +29,4 @@ public class LocalServerSelector {
frame.dispose();
new LocalGameSelector();
}
}

View File

@@ -1,15 +1,14 @@
package org.toop.frontend.UI;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
public class RemoteGameSelector {
private static final Logger logger = LogManager.getLogger(RemoteGameSelector.class);
@@ -38,22 +37,28 @@ public class RemoteGameSelector {
init();
frame.add(mainMenu);
frame.setVisible(true);
//GlobalEventBus.subscribeAndRegister() Todo add game panel to frame when connection succeeds
// GlobalEventBus.subscribeAndRegister() Todo add game panel to frame when connection
// succeeds
}
private void init() {
connectButton.addActionListener((ActionEvent e) -> {
if( !nameTextField.getText().isEmpty() &&
!name2TextField.getText().isEmpty() &&
!ipTextField.getText().isEmpty() &&
!portTextField.getText().isEmpty()) {
connectButton.addActionListener(
(ActionEvent e) -> {
if (!nameTextField.getText().isEmpty()
&& !name2TextField.getText().isEmpty()
&& !ipTextField.getText().isEmpty()
&& !portTextField.getText().isEmpty()) {
CompletableFuture<String> serverIdFuture = new CompletableFuture<>();
GlobalEventBus.post(new Events.ServerEvents.StartServerRequest(
GlobalEventBus.post(
new Events.ServerEvents.StartServerRequest(
portTextField.getText(),
Objects.requireNonNull(gameSelectorBox.getSelectedItem()).toString().toLowerCase().replace(" ", ""),
serverIdFuture
));
Objects.requireNonNull(gameSelectorBox.getSelectedItem())
.toString()
.toLowerCase()
.replace(" ", ""),
serverIdFuture));
String serverId;
try {
serverId = serverIdFuture.get();
@@ -62,11 +67,11 @@ public class RemoteGameSelector {
} // TODO: Better error handling to not crash the system.
CompletableFuture<String> connectionIdFuture = new CompletableFuture<>();
GlobalEventBus.post(new Events.ServerEvents.StartConnectionRequest(
GlobalEventBus.post(
new Events.ServerEvents.StartConnectionRequest(
ipTextField.getText(),
portTextField.getText(),
connectionIdFuture
));
connectionIdFuture));
String connectionId;
try {
connectionId = connectionIdFuture.get();
@@ -74,24 +79,36 @@ public class RemoteGameSelector {
throw new RuntimeException(ex);
} // TODO: Better error handling to not crash the system.
GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ReceivedMessage.class,
GlobalEventBus.subscribeAndRegister(
Events.ServerEvents.ReceivedMessage.class,
event -> {
if (event.message().equalsIgnoreCase("ok")) {
logger.info("received ok from server.");
} else if (event.message().toLowerCase().startsWith("gameid")) {
String gameId = event.message().toLowerCase().replace("gameid ", "");
GlobalEventBus.post(new Events.ServerEvents.SendCommand("start_game " + gameId));
}
else {
String gameId =
event.message()
.toLowerCase()
.replace("gameid ", "");
GlobalEventBus.post(
new Events.ServerEvents.SendCommand(
"start_game " + gameId));
} else {
logger.info("{}", event.message());
}
}
);
});
GlobalEventBus.post(new Events.ServerEvents.SendCommand(connectionId, "create_game", nameTextField.getText(), name2TextField.getText()));
GlobalEventBus.post(
new Events.ServerEvents.SendCommand(
connectionId,
"create_game",
nameTextField.getText(),
name2TextField.getText()));
// CompletableFuture<String> ticTacToeGame = new CompletableFuture<>();
// GlobalEventBus.post(new Events.ServerEvents.CreateTicTacToeGameRequest( // TODO: Make this happen through commands send through the connection, instead of an event.
// CompletableFuture<String> ticTacToeGame = new
// CompletableFuture<>();
// GlobalEventBus.post(new
// Events.ServerEvents.CreateTicTacToeGameRequest( // TODO: Make this happen
// through commands send through the connection, instead of an event.
// serverId,
// nameTextField.getText(),
// name2TextField.getText(),
@@ -104,9 +121,9 @@ public class RemoteGameSelector {
// throw new RuntimeException(ex);
// } // TODO: Better error handling to not crash the system.
frame.remove(mainMenu);
// UIGameBoard ttt = new UIGameBoard("tic tac toe", "test", "test",this); // TODO: Fix later
// UIGameBoard ttt = new UIGameBoard("tic tac toe", "test",
// "test",this); // TODO: Fix later
// frame.add(ttt.getTTTPanel()); // TODO: Fix later
frame.revalidate();
frame.repaint();
@@ -115,6 +132,7 @@ public class RemoteGameSelector {
}
});
}
public void returnToMainMenu() {
frame.removeAll();
frame.add(mainMenu);

View File

@@ -2,5 +2,4 @@ package org.toop.frontend.UI;
import javax.swing.*;
public class Services {
}
public class Services {}

View File

@@ -1,13 +1,9 @@
package org.toop.frontend.UI;
import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.toop.frontend.games.LocalTicTacToe;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Objects;
import javax.swing.*;
import org.toop.frontend.games.LocalTicTacToe;
public class UIGameBoard {
private static final int TICTACTOE_SIZE = 3;
@@ -32,10 +28,10 @@ public class UIGameBoard {
// Back button
backToMainMenuButton = new JButton("Back to Main Menu");
tttPanel.add(backToMainMenuButton, BorderLayout.SOUTH);
backToMainMenuButton.addActionListener(e ->
backToMainMenuButton.addActionListener(
_ ->
// TODO reset game and connections
parent.showMainMenu()
);
parent.showMainMenu());
// Game grid
JPanel gameGrid = createGridPanel(TICTACTOE_SIZE, TICTACTOE_SIZE);
@@ -59,10 +55,16 @@ public class UIGameBoard {
panel.add(cells[i]);
final int index = i;
cells[i].addActionListener((ActionEvent e) -> {
cells[i].addActionListener(
(ActionEvent _) -> {
int cp = this.localTicTacToe.getCurrentPlayersTurn();
if (cp == 0) { this.currentPlayer = "X"; currentPlayerIndex = 0; }
else if (cp == 1) { this.currentPlayer = "O"; currentPlayerIndex = 1; }
if (cp == 0) {
this.currentPlayer = "X";
currentPlayerIndex = 0;
} else if (cp == 1) {
this.currentPlayer = "O";
currentPlayerIndex = 1;
}
this.localTicTacToe.move(index);
cells[index].setText(currentPlayer);
});
@@ -70,6 +72,7 @@ public class UIGameBoard {
return panel;
}
public void setCell(int index, String move) {
System.out.println(cells[index].getText());
cells[index].setText(move);

View File

@@ -1,5 +1,6 @@
package org.toop.frontend.games;
import java.util.concurrent.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.Events;
@@ -9,12 +10,9 @@ import org.toop.game.GameBase;
import org.toop.game.tictactoe.MinMaxTicTacToe;
import org.toop.game.tictactoe.TicTacToe;
import java.util.concurrent.*;
/**
* A representation of a local tic-tac-toe game.
* Calls are made to a server for information about current game state.
* MOST OF THIS CODE IS TRASH, THROW IT OUT OF THE WINDOW AFTER DEMO.
* A representation of a local tic-tac-toe game. Calls are made to a server for information about
* current game state. MOST OF THIS CODE IS TRASH, THROW IT OUT OF THE WINDOW AFTER DEMO.
*/
public class LocalTicTacToe { // TODO: Implement runnable
private static final Logger logger = LogManager.getLogger(LocalTicTacToe.class);
@@ -36,23 +34,23 @@ public class LocalTicTacToe { // TODO: Implement runnable
private TicTacToe ticTacToe;
private UIGameBoard ui;
/**
* Is either 0 or 1.
*/
/** Is either 0 or 1. */
private int playersTurn = 0;
/**
* @return The current players turn.
*/
public int getCurrentPlayersTurn() { return this.playersTurn; }
public int getCurrentPlayersTurn() {
return this.playersTurn;
}
// LocalTicTacToe(String gameId, String connectionId, String serverId) {
// this.gameId = gameId;
// this.connectionId = connectionId;
// this.serverId = serverId;
// this.receivedMessageListener = GlobalEventBus.subscribe(Events.ServerEvents.ReceivedMessage.class, this::receiveMessageAction);
// this.receivedMessageListener =
// GlobalEventBus.subscribe(Events.ServerEvents.ReceivedMessage.class,
// this::receiveMessageAction);
// GlobalEventBus.register(this.receivedMessageListener);
//
//
@@ -60,14 +58,15 @@ public class LocalTicTacToe { // TODO: Implement runnable
// } TODO: If remote server
/**
*
* Starts a connection with a remote server.
*
* @param ip The IP of the server to connect to.
* @param port The port of the server to connect to.
*/
private LocalTicTacToe(String ip, String port) {
this.receivedMessageListener = GlobalEventBus.subscribe(Events.ServerEvents.ReceivedMessage.class, this::receiveMessageAction);
this.receivedMessageListener =
GlobalEventBus.subscribe(
Events.ServerEvents.ReceivedMessage.class, this::receiveMessageAction);
GlobalEventBus.register(this.receivedMessageListener);
this.connectionId = this.createConnection(ip, port);
this.createGame(ip, port);
@@ -100,7 +99,8 @@ public class LocalTicTacToe { // TODO: Implement runnable
private String createServer(String port) {
CompletableFuture<String> serverIdFuture = new CompletableFuture<>();
GlobalEventBus.post(new Events.ServerEvents.StartServerRequest(port, "tictactoe", serverIdFuture));
GlobalEventBus.post(
new Events.ServerEvents.StartServerRequest(port, "tictactoe", serverIdFuture));
try {
return serverIdFuture.get();
} catch (Exception e) {
@@ -111,7 +111,11 @@ public class LocalTicTacToe { // TODO: Implement runnable
private String createConnection(String ip, String port) {
CompletableFuture<String> connectionIdFuture = new CompletableFuture<>();
GlobalEventBus.post(new Events.ServerEvents.StartConnectionRequest(ip, port, connectionIdFuture)); // TODO: what if server couldn't be started with port.
GlobalEventBus.post(
new Events.ServerEvents.StartConnectionRequest(
ip,
port,
connectionIdFuture)); // TODO: what if server couldn't be started with port.
try {
return connectionIdFuture.get();
} catch (InterruptedException | ExecutionException e) {
@@ -127,7 +131,9 @@ public class LocalTicTacToe { // TODO: Implement runnable
}
private void startGame() {
if (this.gameId == null) { return; }
if (this.gameId == null) {
return;
}
this.sendCommand("start_game", this.gameId);
}
@@ -176,9 +182,7 @@ public class LocalTicTacToe { // TODO: Implement runnable
return ticTacToe.getGrid();
}
/**
* End the current game.
*/
/** End the current game. */
public void endGame() {
sendCommand("gameid", "end_game"); // TODO: Command is a bit wrong.
}
@@ -187,12 +191,23 @@ public class LocalTicTacToe { // TODO: Implement runnable
* @param moveIndex The index of the move to make.
*/
public void move(int moveIndex) {
this.executor.submit(() -> {
this.executor.submit(
() -> {
try {
if (this.playersTurn == 0 && !isAiPlayer[0]) { this.moveQueuePlayerA.put(moveIndex); logger.info("Adding player's {}, move: {}", this.playersTurn, moveIndex); }
else if (this.playersTurn == 1 && !isAiPlayer[1]) { this.moveQueuePlayerB.put(moveIndex); logger.info("Adding player's {}, move: {}", this.playersTurn, moveIndex); }
if (this.playersTurn == 0 && !isAiPlayer[0]) {
this.moveQueuePlayerA.put(moveIndex);
logger.info(
"Adding player's {}, move: {}", this.playersTurn, moveIndex);
} else if (this.playersTurn == 1 && !isAiPlayer[1]) {
this.moveQueuePlayerB.put(moveIndex);
logger.info(
"Adding player's {}, move: {}", this.playersTurn, moveIndex);
}
} catch (InterruptedException e) {
logger.error("Could not add player: {}'s, move {}", this.playersTurn, moveIndex); // TODO: Error handling instead of crash.
logger.error(
"Could not add player: {}'s, move {}",
this.playersTurn,
moveIndex); // TODO: Error handling instead of crash.
}
});
}
@@ -208,7 +223,11 @@ public class LocalTicTacToe { // TODO: Implement runnable
}
try {
logger.info("Received message from " + this.connectionId + ": " + receivedMessage.message());
logger.info(
"Received message from "
+ this.connectionId
+ ": "
+ receivedMessage.message());
this.receivedQueue.put(receivedMessage.message());
} catch (InterruptedException e) {
logger.error("Error waiting for received Message", e);

View File

@@ -1,9 +1,8 @@
package org.toop.frontend.graphics;
import org.toop.frontend.platform.graphics.opengl.OpenglRenderer;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.frontend.platform.graphics.opengl.OpenglRenderer;
public abstract class Renderer {
public enum API {
@@ -46,5 +45,6 @@ public abstract class Renderer {
}
public abstract void clear();
public abstract void render();
}

View File

@@ -22,5 +22,6 @@ public abstract class Shader {
public abstract void cleanup();
public abstract void start();
public abstract void stop();
}

View File

@@ -7,7 +7,14 @@ public class Button extends Node {
ICallable<Boolean> onHover;
ICallable<Boolean> onClick;
public Button(int x, int y, int width, int height, Color color, ICallable<Boolean> onHover, ICallable<Boolean> onClick) {
public Button(
int x,
int y,
int width,
int height,
Color color,
ICallable<Boolean> onHover,
ICallable<Boolean> onClick) {
super(x, y, width, height, color);
this.onHover = onHover;

View File

@@ -17,5 +17,6 @@ public abstract class Node {
}
public void hover() {}
public void click() {}
}

View File

@@ -1,13 +1,11 @@
package org.toop.frontend.graphics.node;
import java.util.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.eventbus.*;
import org.toop.frontend.graphics.Shader;
import java.util.*;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class NodeManager {
private static final Logger logger = LogManager.getLogger(NodeManager.class);
@@ -28,13 +26,16 @@ public class NodeManager {
private Node active;
private NodeManager() {
shader = Shader.create(
shader =
Shader.create(
"src/main/resources/shaders/gui_vertex.glsl",
"src/main/resources/shaders/gui_fragment.glsl");
nodes = new ArrayList<Node>();
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseMove.class, event -> {
GlobalEventBus.subscribeAndRegister(
Events.WindowEvents.OnMouseMove.class,
event -> {
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
@@ -47,20 +48,20 @@ public class NodeManager {
}
});
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseClick.class, event -> {
GlobalEventBus.subscribeAndRegister(
Events.WindowEvents.OnMouseClick.class,
event -> {
if (active != null) {
active.click();
}
});
}
public void cleanup() {
}
public void cleanup() {}
public void add(Node node) {
nodes.add(node);
}
public void render() {
}
public void render() {}
}

View File

@@ -1,8 +1,7 @@
package org.toop.frontend.graphics.node;
import org.toop.frontend.math.Bounds;
import java.util.*;
import org.toop.frontend.math.Bounds;
public class Widget {
Bounds bounds;

View File

@@ -17,14 +17,23 @@ public class Bounds {
this.height = height;
}
public int getX() { return x; }
public int getY() { return y; }
public int getWidth() { return width; }
public int getHeight() { return height; }
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public boolean check(int x, int y) {
return
x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.height;
return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height;
}
}

View File

@@ -11,7 +11,15 @@ public class Color {
this.b = b;
}
public float r() { return r; }
public float g() { return g; }
public float b() { return b; }
public float r() {
return r;
}
public float g() {
return g;
}
public float b() {
return b;
}
}

View File

@@ -1,10 +1,9 @@
package org.toop.frontend.platform.core.glfw;
import org.toop.core.*;
import org.toop.eventbus.*;
import org.lwjgl.glfw.*;
import org.lwjgl.system.*;
import org.toop.core.*;
import org.toop.eventbus.*;
public class GlfwWindow extends Window {
private long window;
@@ -47,19 +46,28 @@ public class GlfwWindow extends Window {
GLFW.glfwMakeContextCurrent(window);
GLFW.glfwSwapInterval(1);
GLFW.glfwSetWindowCloseCallback(window, (lwindow) -> {
GLFW.glfwSetWindowCloseCallback(
window,
(lwindow) -> {
GlobalEventBus.post(new Events.WindowEvents.OnQuitRequested());
});
GLFW.glfwSetFramebufferSizeCallback(window, (lwindow, lwidth, lheight) -> {
GlobalEventBus.post(new Events.WindowEvents.OnResize(new Size(lwidth, lheight)));
GLFW.glfwSetFramebufferSizeCallback(
window,
(lwindow, lwidth, lheight) -> {
GlobalEventBus.post(
new Events.WindowEvents.OnResize(new Size(lwidth, lheight)));
});
GLFW.glfwSetCursorPosCallback(window, (lwindow, lx, ly) -> {
GLFW.glfwSetCursorPosCallback(
window,
(lwindow, lx, ly) -> {
GlobalEventBus.post(new Events.WindowEvents.OnMouseMove((int) lx, (int) ly));
});
GLFW.glfwSetMouseButtonCallback(window, (lwindow, lbutton, laction, lmods) -> {
GLFW.glfwSetMouseButtonCallback(
window,
(lwindow, lbutton, laction, lmods) -> {
switch (laction) {
case GLFW.GLFW_PRESS:
GlobalEventBus.post(new Events.WindowEvents.OnMouseClick(lbutton));
@@ -69,14 +77,19 @@ public class GlfwWindow extends Window {
GlobalEventBus.post(new Events.WindowEvents.OnMouseRelease(lbutton));
break;
default: break;
default:
break;
}
});
this.window = window;
GLFW.glfwShowWindow(window);
logger.info("Glfw window setup. Title: {}. Width: {}. Height: {}.", title, size.width(), size.height());
logger.info(
"Glfw window setup. Title: {}. Width: {}. Height: {}.",
title,
size.width(),
size.height());
}
@Override

View File

@@ -1,11 +1,10 @@
package org.toop.frontend.platform.graphics.opengl;
import org.toop.eventbus.*;
import org.toop.frontend.graphics.Renderer;
import org.toop.frontend.graphics.Shader;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
import org.toop.eventbus.*;
import org.toop.frontend.graphics.Renderer;
import org.toop.frontend.graphics.Shader;
public class OpenglRenderer extends Renderer {
private Shader shader;
@@ -15,7 +14,9 @@ public class OpenglRenderer extends Renderer {
GL.createCapabilities();
GL45.glClearColor(0.65f, 0.9f, 0.65f, 1f);
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnResize.class, event -> {
GlobalEventBus.subscribeAndRegister(
Events.WindowEvents.OnResize.class,
event -> {
GL45.glViewport(0, 0, event.size().width(), event.size().height());
});
@@ -51,7 +52,8 @@ public class OpenglRenderer extends Renderer {
GL45.glBindBuffer(GL45.GL_ELEMENT_ARRAY_BUFFER, ib);
GL45.glBufferData(GL45.GL_ELEMENT_ARRAY_BUFFER, indicies, GL45.GL_STATIC_DRAW);
shader = Shader.create(
shader =
Shader.create(
"src/main/resources/shaders/gui_vertex.glsl",
"src/main/resources/shaders/gui_fragment.glsl");
}

View File

@@ -1,10 +1,9 @@
package org.toop.frontend.platform.graphics.opengl;
import org.lwjgl.opengl.*;
import org.toop.core.*;
import org.toop.frontend.graphics.Shader;
import org.lwjgl.opengl.*;
public class OpenglShader extends Shader {
private int programID;

View File

@@ -36,11 +36,21 @@ public abstract class GameBase {
return index >= 0 && index < size * size;
}
public int getSize() { return size; }
public char[] getGrid() { return grid; }
public int getSize() {
return size;
}
public Player[] getPlayers() { return players; }
public Player getCurrentPlayer() { return players[currentPlayer]; }
public char[] getGrid() {
return grid;
}
public Player[] getPlayers() {
return players;
}
public Player getCurrentPlayer() {
return players[currentPlayer];
}
public abstract State play(int index);
}

View File

@@ -1,18 +1,17 @@
package org.toop.game.tictactoe;
import org.toop.game.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.game.*;
public class MinMaxTicTacToe {
private static final Logger logger = LogManager.getLogger(MinMaxTicTacToe.class);
/**
* This method tries to find the best move by seeing if it can set a winning move, if not, it will do a minimax.
* This method tries to find the best move by seeing if it can set a winning move, if not, it
* will do a minimax.
*/
public int findBestMove(TicTacToe game) {
int bestVal = -100; // set bestval to something impossible
int bestMove = 10; // set bestmove to something impossible
@@ -34,7 +33,6 @@ public class MinMaxTicTacToe {
// simulate all possible moves on the field
for (int i = 0; i < game.grid.length; i++) {
if (game.validateMove(i)) { // check if the move is legal here
TicTacToe copyGame = game.copyBoard(); // make a copy of the game
GameBase.State result = copyGame.play(i); // play a move on the copy board
@@ -55,8 +53,10 @@ public class MinMaxTicTacToe {
}
}
thisMoveValue = doMinimax(copyGame, game.movesLeft, false); // else look at other moves
if (thisMoveValue > bestVal) { // if better move than the current best, change the move
thisMoveValue =
doMinimax(copyGame, game.movesLeft, false); // else look at other moves
if (thisMoveValue
> bestVal) { // if better move than the current best, change the move
bestVal = thisMoveValue;
bestMove = i;
}
@@ -69,31 +69,35 @@ public class MinMaxTicTacToe {
}
/**
* This method simulates all the possible future moves in the game through a copy in search of the best move.
* This method simulates all the possible future moves in the game through a copy in search of
* the best move.
*/
public int doMinimax(TicTacToe game, int depth, boolean maximizing) {
boolean state = game.checkWin(); // check for a win (base case stuff)
if (state) {
if (maximizing) {
// it's the maximizing players turn and someone has won. this is not good, so return a negative value
// it's the maximizing players turn and someone has won. this is not good, so return
// a negative value
return -10 + depth;
} else {
// it is the turn of the AI and it has won! this is good for us, so return a positive value above 0
// it is the turn of the AI and it has won! this is good for us, so return a
// positive value above 0
return 10 - depth;
}
}
else {
} else {
boolean empty = false;
for (char cell : game.grid) { // else, look at draw conditions. we check per cell if it's empty or not
for (char cell :
game.grid) { // else, look at draw conditions. we check per cell if it's empty
// or not
if (cell == GameBase.EMPTY) {
empty = true; // if a thing is empty, set to true
break; // break the loop
}
}
if (!empty || depth == 0) { // if the grid is full or the depth is 0 (both meaning game is over) return 0 for draw
if (!empty
|| depth == 0) { // if the grid is full or the depth is 0 (both meaning game is
// over) return 0 for draw
return 0;
}
}
@@ -104,21 +108,28 @@ public class MinMaxTicTacToe {
if (game.validateMove(i)) {
TicTacToe copyGame = game.copyBoard();
copyGame.play(i); // play the move on a copy board
int value = doMinimax(copyGame, depth - 1, false); // keep going with the minimax
bestVal = Math.max(bestVal, value); // select the best value for the maximizing player (the AI)
int value =
doMinimax(copyGame, depth - 1, false); // keep going with the minimax
bestVal =
Math.max(
bestVal,
value); // select the best value for the maximizing player (the
// AI)
}
}
return bestVal;
}
else { // it's the minimizing players turn, the player
} else { // it's the minimizing players turn, the player
int bestVal = 100; // set the value to the highest possible
for (int i = 0; i < game.grid.length; i++) { // loop through the grid
if (game.validateMove(i)) {
TicTacToe copyGame = game.copyBoard();
copyGame.play(i); // play the move on a copy board
int value = doMinimax(copyGame, depth - 1, true); // keep minimaxing
bestVal = Math.min(bestVal, value); // select the lowest score for the minimizing player, they want to make it hard for us
bestVal =
Math.min(
bestVal,
value); // select the lowest score for the minimizing player,
// they want to make it hard for us
}
}
return bestVal;

View File

@@ -1,14 +1,13 @@
package org.toop.game.tictactoe;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.backend.tictactoe.ParsedCommand;
import org.toop.backend.tictactoe.TicTacToeServerCommand;
import org.toop.game.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class TicTacToe extends GameBase implements Runnable {
protected static final Logger logger = LogManager.getLogger(TicTacToe.class);
@@ -26,7 +25,6 @@ public class TicTacToe extends GameBase implements Runnable {
}
/**
*
* Used for the server.
*
* @param player1
@@ -39,7 +37,6 @@ public class TicTacToe extends GameBase implements Runnable {
movesLeft = size * size;
}
public void addCommandToQueue(ParsedCommand command) {
commandQueue.add(command);
}
@@ -80,16 +77,17 @@ public class TicTacToe extends GameBase implements Runnable {
// Do something based which command was given
switch (cmd.command) {
case TicTacToeServerCommand.MOVE:{
// TODO: Check if it is this player's turn, not required for local play (I think?).
case TicTacToeServerCommand.MOVE:
{
// TODO: Check if it is this player's turn, not required for local play (I
// think?).
// Convert given argument to integer
Object arg = cmd.arguments.getFirst();
int index;
try {
index = Integer.parseInt((String) arg);
}
catch (Exception e){
} catch (Exception e) {
logger.error("Error parsing argument to String or Integer");
continue;
}
@@ -101,35 +99,44 @@ public class TicTacToe extends GameBase implements Runnable {
// Tell all players who made a move and what move was made
// TODO: What is the reaction of the game? WIN, DRAW etc?
String player = getCurrentPlayer().name();
addSendToQueue("SVR GAME MOVE {PLAYER: \"" +
player +
"\", DETAILS: \"<reactie spel op zet>\",MOVE: \"" +
index +
"\"}\n");
addSendToQueue(
"SVR GAME MOVE {PLAYER: \""
+ player
+ "\", DETAILS: \"<reactie spel op zet>\",MOVE: \""
+ index
+ "\"}\n");
}
// Check move result
switch (state) {
case State.WIN:{
case State.WIN:
{
// Win
running = false;
addSendToQueue("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"" +
"<score speler2>\", COMMENT: \"<commentaar op resultaat>\"}\n");
addSendToQueue(
"SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\","
+ " PLAYERTWOSCORE: \"<score speler2>\", COMMENT:"
+ " \"<commentaar op resultaat>\"}\n");
break;
}
case State.DRAW:{
case State.DRAW:
{
// Draw
running = false;
addSendToQueue("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"" +
"<score speler2>\", COMMENT: \"<commentaar op resultaat>\"}\n");
addSendToQueue(
"SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\","
+ " PLAYERTWOSCORE: \"<score speler2>\", COMMENT:"
+ " \"<commentaar op resultaat>\"}\n");
break;
}
case State.NORMAL:{
case State.NORMAL:
{
// Valid move but not end of game
addSendToQueue("SVR GAME YOURTURN");
break;
}
case State.INVALID:{
case State.INVALID:
{
// Invalid move
break;
}
@@ -137,7 +144,6 @@ public class TicTacToe extends GameBase implements Runnable {
}
}
}
}
@Override
@@ -170,7 +176,9 @@ public class TicTacToe extends GameBase implements Runnable {
for (int i = 0; i < 3; i++) {
final int index = i * 3;
if (grid[index] != EMPTY && grid[index] == grid[index + 1] && grid[index] == grid[index + 2]) {
if (grid[index] != EMPTY
&& grid[index] == grid[index + 1]
&& grid[index] == grid[index + 2]) {
return true;
}
}
@@ -179,7 +187,9 @@ public class TicTacToe extends GameBase implements Runnable {
for (int i = 0; i < 3; i++) {
int index = i;
if (grid[index] != EMPTY && grid[index] == grid[index + 3] && grid[index] == grid[index + 6]) {
if (grid[index] != EMPTY
&& grid[index] == grid[index + 3]
&& grid[index] == grid[index + 6]) {
return true;
}
}
@@ -197,16 +207,12 @@ public class TicTacToe extends GameBase implements Runnable {
return false;
}
/**
* For AI use only.
*/
/** For AI use only. */
public void decrementMovesLeft() {
movesLeft--;
}
/**
* This method copies the board, mainly for AI use.
*/
/** This method copies the board, mainly for AI use. */
public TicTacToe copyBoard() {
TicTacToe clone = new TicTacToe(players[0].name(), players[1].name());
System.arraycopy(this.grid, 0, clone.grid, 0, this.grid.length);

View File

@@ -1,14 +1,13 @@
import static org.junit.jupiter.api.Assertions.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.toop.eventbus.EventMeta;
import org.toop.eventbus.EventRegistry;
import org.toop.eventbus.GlobalEventBus;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
public class GlobalEventBusTest {
// Sample event class
@@ -22,7 +21,6 @@ public class GlobalEventBusTest {
public String getMessage() {
return message;
}
}
@BeforeEach
@@ -36,7 +34,10 @@ public class GlobalEventBusTest {
AtomicReference<String> receivedMessage = new AtomicReference<>();
// Subscribe and register listener
EventMeta<TestEvent> meta = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> {
EventMeta<TestEvent> meta =
GlobalEventBus.subscribeAndRegister(
TestEvent.class,
e -> {
called.set(true);
receivedMessage.set(e.getMessage());
});
@@ -49,7 +50,10 @@ public class GlobalEventBusTest {
GlobalEventBus.post(event);
// Give Guava EventBus a moment (optional if single-threaded)
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
assertTrue(called.get());
assertEquals("Hello World", receivedMessage.get());
@@ -59,7 +63,8 @@ public class GlobalEventBusTest {
public void testUnregister() {
AtomicBoolean called = new AtomicBoolean(false);
EventMeta<TestEvent> meta = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> called.set(true));
EventMeta<TestEvent> meta =
GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> called.set(true));
assertTrue(meta.isReady());
assertTrue(EventRegistry.isReady(TestEvent.class));
@@ -71,7 +76,10 @@ public class GlobalEventBusTest {
// Post event — listener should NOT be called
GlobalEventBus.post(new TestEvent("Test"));
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
assertFalse(called.get());
}
@@ -81,12 +89,19 @@ public class GlobalEventBusTest {
AtomicBoolean listener1Called = new AtomicBoolean(false);
AtomicBoolean listener2Called = new AtomicBoolean(false);
EventMeta<TestEvent> l1 = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> listener1Called.set(true));
EventMeta<TestEvent> l2 = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> listener2Called.set(true));
EventMeta<TestEvent> l1 =
GlobalEventBus.subscribeAndRegister(
TestEvent.class, e -> listener1Called.set(true));
EventMeta<TestEvent> l2 =
GlobalEventBus.subscribeAndRegister(
TestEvent.class, e -> listener2Called.set(true));
GlobalEventBus.post(new TestEvent("Event"));
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
assertTrue(listener1Called.get());
assertTrue(listener2Called.get());
@@ -96,14 +111,16 @@ public class GlobalEventBusTest {
// @Test
// public void testEventStoredInRegistry() {
// // Subscribe listener (marks type ready)
// EventMeta<TestEvent> meta = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> {});
// EventMeta<TestEvent> meta = GlobalEventBus.subscribeAndRegister(TestEvent.class, e ->
// {});
//
// // Post the event
// TestEvent event = new TestEvent("StoreTest");
// GlobalEventBus.post(event);
//
// // Retrieve the last stored EventEntry
// EventRegistry.EventEntry<TestEvent> storedEntry = EventRegistry.getLastEvent(TestEvent.class);
// EventRegistry.EventEntry<TestEvent> storedEntry =
// EventRegistry.getLastEvent(TestEvent.class);
//
// assertNotNull(storedEntry);
//

View File

@@ -1,3 +1,4 @@
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -5,11 +6,7 @@ import org.toop.game.GameBase;
import org.toop.game.tictactoe.MinMaxTicTacToe;
import org.toop.game.tictactoe.TicTacToe;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for MinMaxTicTacToe AI.
*/
/** Unit tests for MinMaxTicTacToe AI. */
public class MinMaxTicTacToeTest {
private MinMaxTicTacToe ai;
@@ -28,10 +25,17 @@ public class MinMaxTicTacToeTest {
// X | X | .
// O | O | .
// . | . | .
game.grid = new char[]{
'X', 'X', GameBase.EMPTY,
'O', 'O', GameBase.EMPTY,
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
game.grid =
new char[] {
'X',
'X',
GameBase.EMPTY,
'O',
'O',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
};
game.movesLeft = 4;
@@ -47,10 +51,17 @@ public class MinMaxTicTacToeTest {
// O | O | .
// X | . | .
// . | . | .
game.grid = new char[]{
'O', 'O', GameBase.EMPTY,
'X', GameBase.EMPTY, GameBase.EMPTY,
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
game.grid =
new char[] {
'O',
'O',
GameBase.EMPTY,
'X',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
};
int bestMove = ai.findBestMove(game);
@@ -71,10 +82,17 @@ public class MinMaxTicTacToeTest {
void testDoMinimaxScoresWinPositive() {
// Simulate a game state where AI has already won
TicTacToe copy = game.copyBoard();
copy.grid = new char[]{
'X', 'X', 'X',
'O', 'O', GameBase.EMPTY,
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
copy.grid =
new char[] {
'X',
'X',
'X',
'O',
'O',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
};
int score = ai.doMinimax(copy, 5, false);
@@ -86,10 +104,17 @@ public class MinMaxTicTacToeTest {
void testDoMinimaxScoresLossNegative() {
// Simulate a game state where human has already won
TicTacToe copy = game.copyBoard();
copy.grid = new char[]{
'O', 'O', 'O',
'X', 'X', GameBase.EMPTY,
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
copy.grid =
new char[] {
'O',
'O',
'O',
'X',
'X',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
};
int score = ai.doMinimax(copy, 5, true);
@@ -101,7 +126,8 @@ public class MinMaxTicTacToeTest {
void testDoMinimaxDrawReturnsZero() {
// Simulate a draw position
TicTacToe copy = game.copyBoard();
copy.grid = new char[]{
copy.grid =
new char[] {
'X', 'O', 'X',
'X', 'O', 'O',
'O', 'X', 'X'