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"> <dictionary name="project">
<words> <words>
<w>aosp</w> <w>aosp</w>
<w>vmoptions</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

View File

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

View File

@@ -1,19 +1,15 @@
package org.toop; 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.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; 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 { public class ConsoleGui {
@@ -43,7 +39,8 @@ public class ConsoleGui {
2. player vs ai 2. player vs ai
3. ai vs player 3. ai vs player
4. ai v ai 4. ai v ai
Choose mode (default is 1):\s"""); Choose mode (default is 1):\s\
""");
String modeString = scanner.nextLine(); String modeString = scanner.nextLine();
try { try {
@@ -57,7 +54,8 @@ public class ConsoleGui {
switch (mode) { switch (mode) {
// player vs ai // player vs ai
case 2: { case 2:
{
System.out.print("Please enter your name: "); System.out.print("Please enter your name: ");
String name = scanner.nextLine(); String name = scanner.nextLine();
@@ -68,7 +66,8 @@ public class ConsoleGui {
} }
// ai vs player // ai vs player
case 3: { case 3:
{
System.out.print("Enter your name: "); System.out.print("Enter your name: ");
String name = scanner.nextLine(); String name = scanner.nextLine();
@@ -79,7 +78,8 @@ public class ConsoleGui {
} }
// ai vs ai // ai vs ai
case 4: { case 4:
{
ai1 = player1 = "AI#" + random.nextInt(); ai1 = player1 = "AI#" + random.nextInt();
ai2 = player2 = "AI2" + random.nextInt(); ai2 = player2 = "AI2" + random.nextInt();
@@ -88,7 +88,8 @@ public class ConsoleGui {
// player vs player // player vs player
case 1: case 1:
default: { default:
{
System.out.print("Player 1. Please enter your name: "); System.out.print("Player 1. Please enter your name: ");
String name1 = scanner.nextLine(); String name1 = scanner.nextLine();
@@ -104,15 +105,20 @@ public class ConsoleGui {
ai = new MinMaxTicTacToe(); ai = new MinMaxTicTacToe();
CompletableFuture<String> serverIdFuture = new CompletableFuture<>(); 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(); serverId = serverIdFuture.get();
CompletableFuture<String> connectionIdFuture = new CompletableFuture<>(); 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(); connectionId = connectionIdFuture.get();
CompletableFuture<String> ticTacToeGame = new CompletableFuture<>(); 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(); ticTacToeGameId = ticTacToeGame.get();
GlobalEventBus.post(new Events.ServerEvents.RunTicTacToeGame(serverId, ticTacToeGameId)); 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) { if (ai1 != null && current.name() == ai1 || ai2 != null && current.name() == ai2) {
move = ai.findBestMove(game); move = ai.findBestMove(game);
} else { } 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(); String input = scanner.nextLine();
try { try {
move = Integer.parseInt(input); move = Integer.parseInt(input);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
} }
} }
@@ -158,41 +165,51 @@ public class ConsoleGui {
boolean keepRunning = true; boolean keepRunning = true;
switch (state) { switch (state) {
case INVALID: { case INVALID:
{
System.out.println("Please select an empty cell. Between 0-8"); System.out.println("Please select an empty cell. Between 0-8");
return true; return true;
} }
case DRAW: { case DRAW:
{
System.out.println("Game ended in a draw."); System.out.println("Game ended in a draw.");
keepRunning = false; keepRunning = false;
break; break;
} }
case WIN: { case WIN:
{
System.out.printf("%s has won the game.\n", current.name()); System.out.printf("%s has won the game.\n", current.name());
keepRunning = false; keepRunning = false;
break; break;
} }
case NORMAL: case NORMAL:
default: { default:
{
keepRunning = true; keepRunning = true;
break; break;
} }
} }
GlobalEventBus.post(new Events.ServerEvents.SendCommand( GlobalEventBus.post(
new Events.ServerEvents.SendCommand(
connectionId, connectionId,
"gameid " + ticTacToeGameId, "player " + current.name(), "MOVE", String.valueOf(move) "gameid " + ticTacToeGameId,
)); "player " + current.name(),
"MOVE",
String.valueOf(move)));
if (!keepRunning) { if (!keepRunning) {
GlobalEventBus.post(new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId)); GlobalEventBus.post(
new Events.ServerEvents.EndTicTacToeGame(serverId, ticTacToeGameId));
} }
return keepRunning; 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.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.LoggerConfig;
import org.toop.eventbus.EventRegistry;
/** /** Options for logging. */
* Options for logging.
*/
public final class Logging { public final class Logging {
public static void disableAllLogs() { public static void disableAllLogs() {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false); LoggerContext ctx = (LoggerContext) LogManager.getContext(false);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
package org.toop.core; package org.toop.core;
import java.io.*; import java.io.*;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class FileSystem { 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); private static final Logger logger = LogManager.getLogger(FileSystem.class);

View File

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

View File

@@ -1,8 +1,6 @@
package org.toop.eventbus; 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> { public class EventMeta<T> {
private final Class<T> type; private final Class<T> type;
private final Object event; private final Object event;
@@ -32,10 +30,13 @@ public class EventMeta<T> {
@Override @Override
public String toString() { public String toString() {
return "ReadyEvent{" + return "ReadyEvent{"
"type=" + type.getSimpleName() + + "type="
", event=" + event + + type.getSimpleName()
", ready=" + ready + + ", event="
'}'; + event
+ ", ready="
+ ready
+ '}';
} }
} }

View File

@@ -1,16 +1,13 @@
package org.toop.eventbus; package org.toop.eventbus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; 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 { public class EventRegistry {
private static final Logger logger = LogManager.getLogger(EventRegistry.class); 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<>(); 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) { public static <T> void storeEvent(EventMeta<T> eventMeta) {
logger.info("Storing event: {}", eventMeta.toString()); logger.info("Storing event: {}", eventMeta.toString());
eventHistory eventHistory
@@ -30,41 +25,31 @@ public class EventRegistry {
.add(new EventEntry<>(eventMeta)); .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) { public static <T> void markReady(Class<T> type) {
logger.info("Marking event as ready: {}", type.toString()); logger.info("Marking event as ready: {}", type.toString());
readyStates.put(type, true); 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) { public static <T> void markNotReady(Class<T> type) {
logger.info("Marking event as not ready: {}", type.toString()); logger.info("Marking event as not ready: {}", type.toString());
readyStates.put(type, false); 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) { public static <T> boolean isReady(Class<T> type) {
return readyStates.getOrDefault(type, false); return readyStates.getOrDefault(type, false);
} }
/** /** Gets all stored events of a given type. */
* Gets all stored events of a given type.
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> List<EventEntry<T>> getEvents(Class<T> type) { public static <T> List<EventEntry<T>> getEvents(Class<T> type) {
return (List<EventEntry<T>>) (List<?>) eventHistory return (List<EventEntry<T>>)
.getOrDefault(type, new CopyOnWriteArrayList<>()); (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") @SuppressWarnings("unchecked")
public static <T> EventEntry<T> getLastEvent(Class<T> type) { public static <T> EventEntry<T> getLastEvent(Class<T> type) {
List<EventEntry<?>> entries = eventHistory.get(type); List<EventEntry<?>> entries = eventHistory.get(type);
@@ -74,26 +59,20 @@ public class EventRegistry {
return (EventEntry<T>) entries.getLast(); 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) { public static <T> void clearEvents(Class<T> type) {
logger.info("Clearing events: {}", type.toString()); logger.info("Clearing events: {}", type.toString());
eventHistory.remove(type); eventHistory.remove(type);
} }
/** /** Clears all events and resets readiness. */
* Clears all events and resets readiness.
*/
public static void reset() { public static void reset() {
logger.info("Resetting event registry events"); logger.info("Resetting event registry events");
eventHistory.clear(); eventHistory.clear();
readyStates.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> { public static class EventEntry<T> {
private final T event; private final T event;
private volatile boolean ready = false; private volatile boolean ready = false;
@@ -116,10 +95,7 @@ public class EventRegistry {
@Override @Override
public String toString() { public String toString() {
return "EventEntry{" + return "EventEntry{" + "event=" + event + ", ready=" + ready + '}';
"event=" + event +
", ready=" + ready +
'}';
} }
} }
} }

View File

@@ -1,19 +1,15 @@
package org.toop.eventbus; package org.toop.eventbus;
import org.toop.core.Window;
import org.toop.backend.tictactoe.TicTacToeServer;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.CompletableFuture; 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 { public class Events implements IEvents {
/** /**
*
* WIP, DO NOT USE! * WIP, DO NOT USE!
* *
* @param eventName * @param eventName
@@ -29,7 +25,6 @@ public class Events implements IEvents {
} }
/** /**
*
* WIP, DO NOT USE! * WIP, DO NOT USE!
* *
* @param eventCategory * @param eventCategory
@@ -38,15 +33,16 @@ public class Events implements IEvents {
* @return * @return
* @throws Exception * @throws Exception
*/ */
public static Object get(String eventCategory, String eventName, Object... args) throws Exception { public static Object get(String eventCategory, String eventName, Object... args)
Class<?> clazz = Class.forName("org.toop.eventbus.Events$" + eventCategory + "$" + eventName); throws Exception {
Class<?> clazz =
Class.forName("org.toop.eventbus.Events$" + eventCategory + "$" + eventName);
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new); Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
Constructor<?> constructor = clazz.getConstructor(paramTypes); Constructor<?> constructor = clazz.getConstructor(paramTypes);
return constructor.newInstance(args); return constructor.newInstance(args);
} }
/** /**
*
* WIP, DO NOT USE! * WIP, DO NOT USE!
* *
* @param eventName * @param eventName
@@ -77,29 +73,25 @@ public class Events implements IEvents {
public static class ServerEvents { public static class ServerEvents {
/** /**
* BLOCKING * BLOCKING Requests all active connections. The result is returned via the provided
* Requests all active connections. The result is returned via the provided CompletableFuture. * CompletableFuture.
* *
* @param future List of all connections in string form. * @param future List of all connections in string form.
*/ */
public record RequestsAllConnections(CompletableFuture<String> future) {} public record RequestsAllConnections(CompletableFuture<String> future) {}
/** /**
* BLOCKING * BLOCKING Requests all active servers. The result is returned via the provided
* Requests all active servers. The result is returned via the provided CompletableFuture. * CompletableFuture.
* *
* @param future List of all servers in string form. * @param future List of all servers in string form.
*/ */
public record RequestsAllServers(CompletableFuture<String> future) {} public record RequestsAllServers(CompletableFuture<String> future) {}
/** /** Forces closing all active connections immediately. */
* Forces closing all active connections immediately.
*/
public record ForceCloseAllConnections() {} public record ForceCloseAllConnections() {}
/** /** Forces closing all active servers immediately. */
* Forces closing all active servers immediately.
*/
public record ForceCloseAllServers() {} public record ForceCloseAllServers() {}
/** /**
@@ -111,15 +103,15 @@ public class Events implements IEvents {
public record StartServer(String port, String gameType) {} public record StartServer(String port, String gameType) {}
/** /**
* BLOCKING * BLOCKING Requests starting a server with a specific port and game type, and returns a
* Requests starting a server with a specific port and game type, and returns a CompletableFuture * CompletableFuture that completes when the server has started.
* that completes when the server has started.
* *
* @param port The port to open the server. * @param port The port to open the server.
* @param gameType Either "tictactoe" or ... * @param gameType Either "tictactoe" or ...
* @param future The uuid of the server. * @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. * Represents a server that has successfully started.
@@ -130,15 +122,18 @@ public class Events implements IEvents {
public record ServerStarted(String uuid, String port) {} public record ServerStarted(String uuid, String port) {}
/** /**
* BLOCKING * BLOCKING Requests creation of a TicTacToe game on a specific server.
* 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 serverUuid The unique identifier of the server where the game will be created.
* @param playerA The name of the first player. * @param playerA The name of the first player.
* @param playerB The name of the second player. * @param playerB The name of the second player.
* @param future The game UUID when the game is created. * @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. * 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) {} public record EndTicTacToeGame(String serverUuid, String gameUuid) {}
/** /**
*
* Triggers starting a server connection. * Triggers starting a server connection.
* *
* @param ip The IP address of the server to connect to. * @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) {} public record StartConnection(String ip, String port) {}
/** /**
* BLOCKING * BLOCKING Triggers starting a server connection and returns a future.
* Triggers starting a server connection and returns a future.
* *
* @param ip The IP address of the server to connect to. * @param ip The IP address of the server to connect to.
* @param port The port 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. * @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 * BLOCKING Triggers starting a server connection and returns a future.
* Triggers starting a server connection and returns a future.
* *
* @param ip The IP address of the server to connect to. * @param ip The IP address of the server to connect to.
* @param port The port 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) {} public record SendCommand(String connectionId, String... args) {}
/** /**
* WIP * WIP Triggers when a command is sent to a server.
* Triggers when a command is sent to a server.
* *
* @param command The TicTacToeServer instance that executed the command. * @param command The TicTacToeServer instance that executed the command.
* @param args The command arguments. * @param args The command arguments.
* @param result The result returned from executing the command. * @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. * Triggers when the server client receives a message.
@@ -249,55 +243,33 @@ public class Events implements IEvents {
*/ */
public record CouldNotConnect(Object connectionId) {} public record CouldNotConnect(Object connectionId) {}
/** /** WIP Triggers when a connection closes. */
* WIP
* Triggers when a connection closes.
*
*/
public record ClosedConnection() {} 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 record CellClicked(int cell) {}
} }
public static class EventBusEvents { public static class EventBusEvents {}
}
public static class WindowEvents { public static class WindowEvents {
/** /** Triggers when the window wants to quit. */
* Triggers when the window wants to quit.
*/
public record OnQuitRequested() {} public record OnQuitRequested() {}
/** /** Triggers when the window is resized. */
* Triggers when the window is resized.
*/
public record OnResize(Window.Size size) {} 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) {} 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) {} 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 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.EventBus;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import java.util.function.Consumer; 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 { public class GlobalEventBus {
/** /** Singleton event bus. */
* Singleton event bus.
*/
private static EventBus eventBus = new EventBus("global-bus"); private static EventBus eventBus = new EventBus("global-bus");
private GlobalEventBus() {} private GlobalEventBus() {}
@@ -35,16 +30,13 @@ public class GlobalEventBus {
eventBus = newBus; eventBus = newBus;
} }
/** /** Reset back to the default global EventBus. */
* Reset back to the default global EventBus.
*/
public static void reset() { public static void reset() {
eventBus = new EventBus("global-bus"); eventBus = new EventBus("global-bus");
} }
/** /**
* Wraps a Consumer into a Guava @Subscribe-compatible listener. * Wraps a Consumer into a Guava @Subscribe-compatible listener. TODO
* TODO
* *
* @param type The event to be used. (e.g. Events.ServerCommand.class) * @param type The event to be used. (e.g. Events.ServerCommand.class)
* @param action The function, or lambda to run when fired. * @param action The function, or lambda to run when fired.
@@ -74,7 +66,6 @@ public class GlobalEventBus {
return listener; return listener;
} }
/** /**
* Wrapper for registering a listener. * Wrapper for registering a listener.
* *
@@ -102,7 +93,8 @@ public class GlobalEventBus {
Class<T> type = (Class<T>) event.getClass(); Class<T> type = (Class<T>) event.getClass();
// if (!EventRegistry.isReady(type)) { // 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. // } TODO: Handling non ready events.
// store in registry // store in registry
@@ -112,5 +104,4 @@ public class GlobalEventBus {
// post to Guava EventBus // post to Guava EventBus
GlobalEventBus.get().post(event); GlobalEventBus.get().post(event);
} }
} }

View File

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

View File

@@ -1,13 +1,12 @@
package org.toop.frontend; 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.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.concurrent.*; 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 { 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. * Sends a command to the server.
* *
* @param args The arguments for the command. * @param args The arguments for the command.
@@ -86,7 +84,9 @@ public final class ServerConnection extends TcpClient implements Runnable {
if (received != null) { if (received != null) {
logger.info("Connection: {} received: '{}'", this.uuid, received); logger.info("Connection: {} received: '{}'", this.uuid, received);
// this.addReceivedMessageToQueue(received); // TODO: Will never go empty // 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 { } else {
break; break;
} }
@@ -114,7 +114,6 @@ public final class ServerConnection extends TcpClient implements Runnable {
} }
/** /**
*
* Connect to a new server. * Connect to a new server.
* *
* @param ip The ip to connect to. * @param ip The ip to connect to.
@@ -132,7 +131,6 @@ public final class ServerConnection extends TcpClient implements Runnable {
} }
/** /**
*
* Reconnects to previous address. * Reconnects to previous address.
* *
* @throws IOException wip * @throws IOException wip
@@ -141,14 +139,14 @@ public final class ServerConnection extends TcpClient implements Runnable {
this.connect(this.serverAddress, this.serverPort); this.connect(this.serverAddress, this.serverPort);
} }
/** /** Close connection to server. */
*
* Close connection to server.
*
*/
public void closeConnection() { public void closeConnection() {
this.stopWorkers(); 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 @Override
@@ -164,9 +162,6 @@ public final class ServerConnection extends TcpClient implements Runnable {
public String toString() { public String toString() {
return String.format( return String.format(
"Server {ip: \"%s\", port: \"%s\", running: %s}", "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.InetAddress;
import java.net.Socket; import java.net.Socket;
/** /** A simple wrapper for creating TCP clients. */
* A simple wrapper for creating TCP clients.
*/
public abstract class TcpClient { public abstract class TcpClient {
InetAddress serverAddress; InetAddress serverAddress;
@@ -61,5 +59,4 @@ public abstract class TcpClient {
public void close() throws IOException { public void close() throws IOException {
this.socket.close(); this.socket.close();
} }
} }

View File

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

View File

@@ -1,17 +1,11 @@
package org.toop.frontend.UI; package org.toop.frontend.UI;
import java.awt.*;
import javax.swing.*;
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.eventbus.Events;
import org.toop.eventbus.GlobalEventBus;
import org.toop.frontend.games.LocalTicTacToe; 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 { public class LocalGameSelector extends JFrame {
private static final Logger logger = LogManager.getLogger(LocalGameSelector.class); private static final Logger logger = LogManager.getLogger(LocalGameSelector.class);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,14 +17,23 @@ public class Bounds {
this.height = height; this.height = height;
} }
public int getX() { return x; } public int getX() {
public int getY() { return y; } return x;
public int getWidth() { return width; } }
public int getHeight() { return height; }
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public boolean check(int x, int y) { public boolean check(int x, int y) {
return return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height;
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; this.b = b;
} }
public float r() { return r; } public float r() {
public float g() { return g; } return r;
public float b() { return b; } }
public float g() {
return g;
}
public float b() {
return b;
}
} }

View File

@@ -1,10 +1,9 @@
package org.toop.frontend.platform.core.glfw; package org.toop.frontend.platform.core.glfw;
import org.toop.core.*;
import org.toop.eventbus.*;
import org.lwjgl.glfw.*; import org.lwjgl.glfw.*;
import org.lwjgl.system.*; import org.lwjgl.system.*;
import org.toop.core.*;
import org.toop.eventbus.*;
public class GlfwWindow extends Window { public class GlfwWindow extends Window {
private long window; private long window;
@@ -47,19 +46,28 @@ public class GlfwWindow extends Window {
GLFW.glfwMakeContextCurrent(window); GLFW.glfwMakeContextCurrent(window);
GLFW.glfwSwapInterval(1); GLFW.glfwSwapInterval(1);
GLFW.glfwSetWindowCloseCallback(window, (lwindow) -> { GLFW.glfwSetWindowCloseCallback(
window,
(lwindow) -> {
GlobalEventBus.post(new Events.WindowEvents.OnQuitRequested()); GlobalEventBus.post(new Events.WindowEvents.OnQuitRequested());
}); });
GLFW.glfwSetFramebufferSizeCallback(window, (lwindow, lwidth, lheight) -> { GLFW.glfwSetFramebufferSizeCallback(
GlobalEventBus.post(new Events.WindowEvents.OnResize(new Size(lwidth, lheight))); 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)); 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) { switch (laction) {
case GLFW.GLFW_PRESS: case GLFW.GLFW_PRESS:
GlobalEventBus.post(new Events.WindowEvents.OnMouseClick(lbutton)); GlobalEventBus.post(new Events.WindowEvents.OnMouseClick(lbutton));
@@ -69,14 +77,19 @@ public class GlfwWindow extends Window {
GlobalEventBus.post(new Events.WindowEvents.OnMouseRelease(lbutton)); GlobalEventBus.post(new Events.WindowEvents.OnMouseRelease(lbutton));
break; break;
default: break; default:
break;
} }
}); });
this.window = window; this.window = window;
GLFW.glfwShowWindow(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 @Override

View File

@@ -1,11 +1,10 @@
package org.toop.frontend.platform.graphics.opengl; 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.opengl.*;
import org.lwjgl.system.*; 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 { public class OpenglRenderer extends Renderer {
private Shader shader; private Shader shader;
@@ -15,7 +14,9 @@ public class OpenglRenderer extends Renderer {
GL.createCapabilities(); GL.createCapabilities();
GL45.glClearColor(0.65f, 0.9f, 0.65f, 1f); 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()); 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.glBindBuffer(GL45.GL_ELEMENT_ARRAY_BUFFER, ib);
GL45.glBufferData(GL45.GL_ELEMENT_ARRAY_BUFFER, indicies, GL45.GL_STATIC_DRAW); 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_vertex.glsl",
"src/main/resources/shaders/gui_fragment.glsl"); "src/main/resources/shaders/gui_fragment.glsl");
} }

View File

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

View File

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

View File

@@ -1,18 +1,17 @@
package org.toop.game.tictactoe; package org.toop.game.tictactoe;
import org.toop.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.game.*;
public class MinMaxTicTacToe { public class MinMaxTicTacToe {
private static final Logger logger = LogManager.getLogger(MinMaxTicTacToe.class); 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) { public int findBestMove(TicTacToe game) {
int bestVal = -100; // set bestval to something impossible int bestVal = -100; // set bestval to something impossible
int bestMove = 10; // set bestmove 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 // simulate all possible moves on the field
for (int i = 0; i < game.grid.length; i++) { for (int i = 0; i < game.grid.length; i++) {
if (game.validateMove(i)) { // check if the move is legal here if (game.validateMove(i)) { // check if the move is legal here
TicTacToe copyGame = game.copyBoard(); // make a copy of the game TicTacToe copyGame = game.copyBoard(); // make a copy of the game
GameBase.State result = copyGame.play(i); // play a move on the copy board 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 thisMoveValue =
if (thisMoveValue > bestVal) { // if better move than the current best, change the move 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; bestVal = thisMoveValue;
bestMove = i; 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) { public int doMinimax(TicTacToe game, int depth, boolean maximizing) {
boolean state = game.checkWin(); // check for a win (base case stuff) boolean state = game.checkWin(); // check for a win (base case stuff)
if (state) { if (state) {
if (maximizing) { 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; return -10 + depth;
} else { } 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; return 10 - depth;
} }
} } else {
else {
boolean empty = false; 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) { if (cell == GameBase.EMPTY) {
empty = true; // if a thing is empty, set to true empty = true; // if a thing is empty, set to true
break; // break the loop 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; return 0;
} }
} }
@@ -104,21 +108,28 @@ public class MinMaxTicTacToe {
if (game.validateMove(i)) { if (game.validateMove(i)) {
TicTacToe copyGame = game.copyBoard(); TicTacToe copyGame = game.copyBoard();
copyGame.play(i); // play the move on a copy board copyGame.play(i); // play the move on a copy board
int value = doMinimax(copyGame, depth - 1, false); // keep going with the minimax int value =
bestVal = Math.max(bestVal, value); // select the best value for the maximizing player (the AI) 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; 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 int bestVal = 100; // set the value to the highest possible
for (int i = 0; i < game.grid.length; i++) { // loop through the grid for (int i = 0; i < game.grid.length; i++) { // loop through the grid
if (game.validateMove(i)) { if (game.validateMove(i)) {
TicTacToe copyGame = game.copyBoard(); TicTacToe copyGame = game.copyBoard();
copyGame.play(i); // play the move on a copy board copyGame.play(i); // play the move on a copy board
int value = doMinimax(copyGame, depth - 1, true); // keep minimaxing 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; return bestVal;

View File

@@ -1,14 +1,13 @@
package org.toop.game.tictactoe; 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.toop.backend.tictactoe.ParsedCommand; import org.toop.backend.tictactoe.ParsedCommand;
import org.toop.backend.tictactoe.TicTacToeServerCommand; import org.toop.backend.tictactoe.TicTacToeServerCommand;
import org.toop.game.*; import org.toop.game.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class TicTacToe extends GameBase implements Runnable { public class TicTacToe extends GameBase implements Runnable {
protected static final Logger logger = LogManager.getLogger(TicTacToe.class); protected static final Logger logger = LogManager.getLogger(TicTacToe.class);
@@ -26,7 +25,6 @@ public class TicTacToe extends GameBase implements Runnable {
} }
/** /**
*
* Used for the server. * Used for the server.
* *
* @param player1 * @param player1
@@ -39,7 +37,6 @@ public class TicTacToe extends GameBase implements Runnable {
movesLeft = size * size; movesLeft = size * size;
} }
public void addCommandToQueue(ParsedCommand command) { public void addCommandToQueue(ParsedCommand command) {
commandQueue.add(command); commandQueue.add(command);
} }
@@ -80,16 +77,17 @@ public class TicTacToe extends GameBase implements Runnable {
// Do something based which command was given // Do something based which command was given
switch (cmd.command) { switch (cmd.command) {
case TicTacToeServerCommand.MOVE:{ case TicTacToeServerCommand.MOVE:
// TODO: Check if it is this player's turn, not required for local play (I think?). {
// TODO: Check if it is this player's turn, not required for local play (I
// think?).
// Convert given argument to integer // Convert given argument to integer
Object arg = cmd.arguments.getFirst(); Object arg = cmd.arguments.getFirst();
int index; int index;
try { try {
index = Integer.parseInt((String) arg); index = Integer.parseInt((String) arg);
} } catch (Exception e) {
catch (Exception e){
logger.error("Error parsing argument to String or Integer"); logger.error("Error parsing argument to String or Integer");
continue; continue;
} }
@@ -101,35 +99,44 @@ public class TicTacToe extends GameBase implements Runnable {
// Tell all players who made a move and what move was made // Tell all players who made a move and what move was made
// TODO: What is the reaction of the game? WIN, DRAW etc? // TODO: What is the reaction of the game? WIN, DRAW etc?
String player = getCurrentPlayer().name(); String player = getCurrentPlayer().name();
addSendToQueue("SVR GAME MOVE {PLAYER: \"" + addSendToQueue(
player + "SVR GAME MOVE {PLAYER: \""
"\", DETAILS: \"<reactie spel op zet>\",MOVE: \"" + + player
index + + "\", DETAILS: \"<reactie spel op zet>\",MOVE: \""
"\"}\n"); + index
+ "\"}\n");
} }
// Check move result // Check move result
switch (state) { switch (state) {
case State.WIN:{ case State.WIN:
{
// Win // Win
running = false; running = false;
addSendToQueue("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"" + addSendToQueue(
"<score speler2>\", COMMENT: \"<commentaar op resultaat>\"}\n"); "SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\","
+ " PLAYERTWOSCORE: \"<score speler2>\", COMMENT:"
+ " \"<commentaar op resultaat>\"}\n");
break; break;
} }
case State.DRAW:{ case State.DRAW:
{
// Draw // Draw
running = false; running = false;
addSendToQueue("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"" + addSendToQueue(
"<score speler2>\", COMMENT: \"<commentaar op resultaat>\"}\n"); "SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\","
+ " PLAYERTWOSCORE: \"<score speler2>\", COMMENT:"
+ " \"<commentaar op resultaat>\"}\n");
break; break;
} }
case State.NORMAL:{ case State.NORMAL:
{
// Valid move but not end of game // Valid move but not end of game
addSendToQueue("SVR GAME YOURTURN"); addSendToQueue("SVR GAME YOURTURN");
break; break;
} }
case State.INVALID:{ case State.INVALID:
{
// Invalid move // Invalid move
break; break;
} }
@@ -137,7 +144,6 @@ public class TicTacToe extends GameBase implements Runnable {
} }
} }
} }
} }
@Override @Override
@@ -170,7 +176,9 @@ public class TicTacToe extends GameBase implements Runnable {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
final int index = i * 3; 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; return true;
} }
} }
@@ -179,7 +187,9 @@ public class TicTacToe extends GameBase implements Runnable {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
int index = 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; return true;
} }
} }
@@ -197,16 +207,12 @@ public class TicTacToe extends GameBase implements Runnable {
return false; return false;
} }
/** /** For AI use only. */
* For AI use only.
*/
public void decrementMovesLeft() { public void decrementMovesLeft() {
movesLeft--; movesLeft--;
} }
/** /** This method copies the board, mainly for AI use. */
* This method copies the board, mainly for AI use.
*/
public TicTacToe copyBoard() { public TicTacToe copyBoard() {
TicTacToe clone = new TicTacToe(players[0].name(), players[1].name()); TicTacToe clone = new TicTacToe(players[0].name(), players[1].name());
System.arraycopy(this.grid, 0, clone.grid, 0, this.grid.length); 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.toop.eventbus.EventMeta; import org.toop.eventbus.EventMeta;
import org.toop.eventbus.EventRegistry; import org.toop.eventbus.EventRegistry;
import org.toop.eventbus.GlobalEventBus; 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 { public class GlobalEventBusTest {
// Sample event class // Sample event class
@@ -22,7 +21,6 @@ public class GlobalEventBusTest {
public String getMessage() { public String getMessage() {
return message; return message;
} }
} }
@BeforeEach @BeforeEach
@@ -36,7 +34,10 @@ public class GlobalEventBusTest {
AtomicReference<String> receivedMessage = new AtomicReference<>(); AtomicReference<String> receivedMessage = new AtomicReference<>();
// Subscribe and register listener // Subscribe and register listener
EventMeta<TestEvent> meta = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> { EventMeta<TestEvent> meta =
GlobalEventBus.subscribeAndRegister(
TestEvent.class,
e -> {
called.set(true); called.set(true);
receivedMessage.set(e.getMessage()); receivedMessage.set(e.getMessage());
}); });
@@ -49,7 +50,10 @@ public class GlobalEventBusTest {
GlobalEventBus.post(event); GlobalEventBus.post(event);
// Give Guava EventBus a moment (optional if single-threaded) // 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()); assertTrue(called.get());
assertEquals("Hello World", receivedMessage.get()); assertEquals("Hello World", receivedMessage.get());
@@ -59,7 +63,8 @@ public class GlobalEventBusTest {
public void testUnregister() { public void testUnregister() {
AtomicBoolean called = new AtomicBoolean(false); 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(meta.isReady());
assertTrue(EventRegistry.isReady(TestEvent.class)); assertTrue(EventRegistry.isReady(TestEvent.class));
@@ -71,7 +76,10 @@ public class GlobalEventBusTest {
// Post event — listener should NOT be called // Post event — listener should NOT be called
GlobalEventBus.post(new TestEvent("Test")); GlobalEventBus.post(new TestEvent("Test"));
try { Thread.sleep(50); } catch (InterruptedException ignored) {} try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
assertFalse(called.get()); assertFalse(called.get());
} }
@@ -81,12 +89,19 @@ public class GlobalEventBusTest {
AtomicBoolean listener1Called = new AtomicBoolean(false); AtomicBoolean listener1Called = new AtomicBoolean(false);
AtomicBoolean listener2Called = new AtomicBoolean(false); AtomicBoolean listener2Called = new AtomicBoolean(false);
EventMeta<TestEvent> l1 = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> listener1Called.set(true)); EventMeta<TestEvent> l1 =
EventMeta<TestEvent> l2 = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> listener2Called.set(true)); GlobalEventBus.subscribeAndRegister(
TestEvent.class, e -> listener1Called.set(true));
EventMeta<TestEvent> l2 =
GlobalEventBus.subscribeAndRegister(
TestEvent.class, e -> listener2Called.set(true));
GlobalEventBus.post(new TestEvent("Event")); GlobalEventBus.post(new TestEvent("Event"));
try { Thread.sleep(50); } catch (InterruptedException ignored) {} try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
assertTrue(listener1Called.get()); assertTrue(listener1Called.get());
assertTrue(listener2Called.get()); assertTrue(listener2Called.get());
@@ -96,14 +111,16 @@ public class GlobalEventBusTest {
// @Test // @Test
// public void testEventStoredInRegistry() { // public void testEventStoredInRegistry() {
// // Subscribe listener (marks type ready) // // Subscribe listener (marks type ready)
// EventMeta<TestEvent> meta = GlobalEventBus.subscribeAndRegister(TestEvent.class, e -> {}); // EventMeta<TestEvent> meta = GlobalEventBus.subscribeAndRegister(TestEvent.class, e ->
// {});
// //
// // Post the event // // Post the event
// TestEvent event = new TestEvent("StoreTest"); // TestEvent event = new TestEvent("StoreTest");
// GlobalEventBus.post(event); // GlobalEventBus.post(event);
// //
// // Retrieve the last stored EventEntry // // Retrieve the last stored EventEntry
// EventRegistry.EventEntry<TestEvent> storedEntry = EventRegistry.getLastEvent(TestEvent.class); // EventRegistry.EventEntry<TestEvent> storedEntry =
// EventRegistry.getLastEvent(TestEvent.class);
// //
// assertNotNull(storedEntry); // 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.BeforeEach;
import org.junit.jupiter.api.Test; 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.MinMaxTicTacToe;
import org.toop.game.tictactoe.TicTacToe; 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 { public class MinMaxTicTacToeTest {
private MinMaxTicTacToe ai; private MinMaxTicTacToe ai;
@@ -28,10 +25,17 @@ public class MinMaxTicTacToeTest {
// X | X | . // X | X | .
// O | O | . // O | O | .
// . | . | . // . | . | .
game.grid = new char[]{ game.grid =
'X', 'X', GameBase.EMPTY, new char[] {
'O', 'O', GameBase.EMPTY, 'X',
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY 'X',
GameBase.EMPTY,
'O',
'O',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
}; };
game.movesLeft = 4; game.movesLeft = 4;
@@ -47,10 +51,17 @@ public class MinMaxTicTacToeTest {
// O | O | . // O | O | .
// X | . | . // X | . | .
// . | . | . // . | . | .
game.grid = new char[]{ game.grid =
'O', 'O', GameBase.EMPTY, new char[] {
'X', GameBase.EMPTY, GameBase.EMPTY, 'O',
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY 'O',
GameBase.EMPTY,
'X',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
}; };
int bestMove = ai.findBestMove(game); int bestMove = ai.findBestMove(game);
@@ -71,10 +82,17 @@ public class MinMaxTicTacToeTest {
void testDoMinimaxScoresWinPositive() { void testDoMinimaxScoresWinPositive() {
// Simulate a game state where AI has already won // Simulate a game state where AI has already won
TicTacToe copy = game.copyBoard(); TicTacToe copy = game.copyBoard();
copy.grid = new char[]{ copy.grid =
'X', 'X', 'X', new char[] {
'O', 'O', GameBase.EMPTY, 'X',
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY 'X',
'X',
'O',
'O',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
}; };
int score = ai.doMinimax(copy, 5, false); int score = ai.doMinimax(copy, 5, false);
@@ -86,10 +104,17 @@ public class MinMaxTicTacToeTest {
void testDoMinimaxScoresLossNegative() { void testDoMinimaxScoresLossNegative() {
// Simulate a game state where human has already won // Simulate a game state where human has already won
TicTacToe copy = game.copyBoard(); TicTacToe copy = game.copyBoard();
copy.grid = new char[]{ copy.grid =
'O', 'O', 'O', new char[] {
'X', 'X', GameBase.EMPTY, 'O',
GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY 'O',
'O',
'X',
'X',
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY,
GameBase.EMPTY
}; };
int score = ai.doMinimax(copy, 5, true); int score = ai.doMinimax(copy, 5, true);
@@ -101,7 +126,8 @@ public class MinMaxTicTacToeTest {
void testDoMinimaxDrawReturnsZero() { void testDoMinimaxDrawReturnsZero() {
// Simulate a draw position // Simulate a draw position
TicTacToe copy = game.copyBoard(); TicTacToe copy = game.copyBoard();
copy.grid = new char[]{ copy.grid =
new char[] {
'X', 'O', 'X', 'X', 'O', 'X',
'X', 'O', 'O', 'X', 'O', 'O',
'O', 'X', 'X' 'O', 'X', 'X'