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,198 +1,215 @@
package org.toop; package org.toop;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
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.*;
import org.toop.eventbus.Events; import org.toop.eventbus.Events;
import org.toop.eventbus.GlobalEventBus; import org.toop.eventbus.GlobalEventBus;
import org.toop.game.*; import org.toop.game.*;
import org.toop.game.tictactoe.*; import org.toop.game.tictactoe.*;
import com.google.errorprone.annotations.Keep;
import org.toop.eventbus.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ConsoleGui { public class ConsoleGui {
private static final Logger logger = LogManager.getLogger(ConsoleGui.class); private static final Logger logger = LogManager.getLogger(ConsoleGui.class);
private Scanner scanner; private Scanner scanner;
private TicTacToe game; private TicTacToe game;
private MinMaxTicTacToe ai; private MinMaxTicTacToe ai;
private String ai1 = null; private String ai1 = null;
private String ai2 = null; private String ai2 = null;
private String serverId = null; private String serverId = null;
private String connectionId = null; private String connectionId = null;
private String ticTacToeGameId = null; private String ticTacToeGameId = null;
public ConsoleGui() throws ExecutionException, InterruptedException { public ConsoleGui() throws ExecutionException, InterruptedException {
scanner = new Scanner(System.in); scanner = new Scanner(System.in);
Random random = new Random(3453498); Random random = new Random(3453498);
int mode = -1; int mode = -1;
System.out.print( System.out.print(
""" """
1. player vs player 1. player vs player
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 {
mode = Integer.parseInt(modeString); mode = Integer.parseInt(modeString);
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage()); logger.error(e.getMessage());
} }
String player1 = null; String player1 = null;
String player2 = null; String player2 = null;
switch (mode) { switch (mode) {
// player vs ai // player vs ai
case 2: { case 2:
System.out.print("Please enter your name: "); {
String name = scanner.nextLine(); System.out.print("Please enter your name: ");
String name = scanner.nextLine();
player1 = name; player1 = name;
ai2 = player2 = "AI#" + random.nextInt(); ai2 = player2 = "AI#" + random.nextInt();
break; break;
} }
// ai vs player // ai vs player
case 3: { case 3:
System.out.print("Enter your name: "); {
String name = scanner.nextLine(); System.out.print("Enter your name: ");
String name = scanner.nextLine();
ai1 = player1 = "AI#" + random.nextInt(); ai1 = player1 = "AI#" + random.nextInt();
player2 = name; player2 = name;
break; break;
} }
// ai vs ai // ai vs ai
case 4: { case 4:
ai1 = player1 = "AI#" + random.nextInt(); {
ai2 = player2 = "AI2" + random.nextInt(); ai1 = player1 = "AI#" + random.nextInt();
ai2 = player2 = "AI2" + random.nextInt();
break; break;
} }
// player vs player // player vs player
case 1: case 1:
default: { default:
System.out.print("Player 1. Please enter your name: "); {
String name1 = scanner.nextLine(); System.out.print("Player 1. Please enter your name: ");
String name1 = scanner.nextLine();
System.out.print("Player 2. Please enter your name: "); System.out.print("Player 2. Please enter your name: ");
String name2 = scanner.nextLine(); String name2 = scanner.nextLine();
player1 = name1; player1 = name1;
player2 = name2; player2 = name2;
} }
} }
game = new TicTacToe(player1, player2); game = new TicTacToe(player1, player2);
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));
} }
public void print() { public void print() {
char[] seperator = new char[game.getSize() * 4 - 1]; char[] seperator = new char[game.getSize() * 4 - 1];
Arrays.fill(seperator, '-'); Arrays.fill(seperator, '-');
for (int i = 0; i < game.getSize(); i++) { for (int i = 0; i < game.getSize(); i++) {
String buffer = " "; String buffer = " ";
for (int j = 0; j < game.getSize() - 1; j++) { for (int j = 0; j < game.getSize() - 1; j++) {
buffer += game.getGrid()[i * game.getSize() + j] + " | "; buffer += game.getGrid()[i * game.getSize() + j] + " | ";
} }
buffer += game.getGrid()[i * game.getSize() + game.getSize() - 1]; buffer += game.getGrid()[i * game.getSize() + game.getSize() - 1];
System.out.println(buffer); System.out.println(buffer);
if (i < game.getSize() - 1) { if (i < game.getSize() - 1) {
System.out.println(seperator); System.out.println(seperator);
} }
} }
} }
public boolean next() { public boolean next() {
Player current = game.getCurrentPlayer(); Player current = game.getCurrentPlayer();
int move = -1; int move = -1;
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(
String input = scanner.nextLine(); "%s's (%c) turn. Please choose an empty cell between 0-8: ",
current.name(), current.move());
String input = scanner.nextLine();
try { try {
move = Integer.parseInt(input); move = Integer.parseInt(input);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) { }
} }
}
GameBase.State state = game.play(move); GameBase.State state = game.play(move);
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"); {
return true; System.out.println("Please select an empty cell. Between 0-8");
} return true;
}
case DRAW: { case DRAW:
System.out.println("Game ended in a draw."); {
keepRunning = false; System.out.println("Game ended in a draw.");
break; keepRunning = false;
} break;
}
case WIN: { case WIN:
System.out.printf("%s has won the game.\n", current.name()); {
keepRunning = false; System.out.printf("%s has won the game.\n", current.name());
break; keepRunning = false;
} break;
}
case NORMAL: case NORMAL:
default: { default:
keepRunning = true; {
break; keepRunning = true;
} break;
} }
}
GlobalEventBus.post(new Events.ServerEvents.SendCommand( GlobalEventBus.post(
connectionId, new Events.ServerEvents.SendCommand(
"gameid " + ticTacToeGameId, "player " + current.name(), "MOVE", String.valueOf(move) connectionId,
)); "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,69 +1,66 @@
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);
private static boolean running = false; private static boolean running = false;
public static void main(String[] args) throws ExecutionException, InterruptedException { public static void main(String[] args) throws ExecutionException, InterruptedException {
// Logging.disableAllLogs(); // Logging.disableAllLogs();
// Logging.enableAllLogsForClass(LocalTicTacToe.class); // Logging.enableAllLogsForClass(LocalTicTacToe.class);
// Logging.enableLogsForClass(ServerManager.class, Level.ALL); // Logging.enableLogsForClass(ServerManager.class, Level.ALL);
// Logging.enableLogsForClass(TicTacToeServer.class, Level.ALL); // Logging.enableLogsForClass(TicTacToeServer.class, Level.ALL);
// Logging.enableLogsForClass(TcpClient.class, Level.ALL); // Logging.enableLogsForClass(TcpClient.class, Level.ALL);
// Logging.enableLogsForClass(ConnectionManager.class, Level.ALL); // Logging.enableLogsForClass(ConnectionManager.class, Level.ALL);
initSystems(); initSystems();
registerEvents(); registerEvents();
// JFrame frame = new JFrame("Server Settings"); // JFrame frame = new JFrame("Server Settings");
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// frame.setSize(800, 600); // frame.setSize(800, 600);
// frame.setLocationRelativeTo(null); // frame.setLocationRelativeTo(null);
// frame.setVisible(true); // frame.setVisible(true);
javax.swing.SwingUtilities.invokeLater(LocalServerSelector::new); javax.swing.SwingUtilities.invokeLater(LocalServerSelector::new);
// new Thread(() -> { // new Thread(() -> {
// LocalServerSelector window = new LocalServerSelector(); // LocalServerSelector window = new LocalServerSelector();
// }).start(); // }).start();
} }
private static void registerEvents() { private static void registerEvents() {
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnQuitRequested.class, event -> { GlobalEventBus.subscribeAndRegister(
quit(); Events.WindowEvents.OnQuitRequested.class,
}); event -> {
quit();
});
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseMove.class, event -> { GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseMove.class, event -> {});
}); }
}
public static void initSystems() { public static void initSystems() {
new ServerManager(); new ServerManager();
new ConnectionManager(); new ConnectionManager();
} }
private static void quit() { private static void quit() {
running = false; running = false;
} }
public static boolean isRunning() { public static boolean isRunning() {
return running; return running;
} }
public static void setRunning(boolean running) { public static void setRunning(boolean running) {
Main.running = running; Main.running = running;
} }
} }

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,80 +77,112 @@ 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()); () -> {
try { logger.info(
String line; "Starting read loop for {}", clientSocket.getRemoteSocketAddress());
while (running && (line = in.readLine()) != null) { try {
if (line.isEmpty()) continue; String line;
logger.debug("Received from {}: {}", clientSocket.getRemoteSocketAddress(), line); while (running && (line = in.readLine()) != null) {
if (line.isEmpty()) continue;
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
} catch (InterruptedException ie) { offered =
Thread.currentThread().interrupt(); this.receivedQueue.offer(
break; line, 200, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
if (!offered) {
logger.warn(
"Backpressure: dropping line from {}: {}",
clientSocket.getRemoteSocketAddress(),
line);
// Policy choice: drop, notify, or close connection. We drop
// here.
}
} }
} catch (IOException e) {
logger.info(
"Connection closed by remote: {}",
clientSocket.getRemoteSocketAddress());
} finally {
try {
clientSocket.close();
} catch (IOException ignored) {
}
logger.info(
"Stopped read loop for {}",
clientSocket.getRemoteSocketAddress());
} }
};
if (!offered) {
logger.warn("Backpressure: dropping line from {}: {}", clientSocket.getRemoteSocketAddress(), line);
// Policy choice: drop, notify, or close connection. We drop here.
}
}
} catch (IOException e) {
logger.info("Connection closed by remote: {}", clientSocket.getRemoteSocketAddress());
} finally {
try {
clientSocket.close();
} catch (IOException ignored) {}
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()); () -> {
try { logger.info(
while (running && !clientSocket.isClosed()) { "Starting write loop for {}",
String msg = sendQueue.poll(WAIT_TIME, TimeUnit.MILLISECONDS); clientSocket.getRemoteSocketAddress());
if (msg != null) { try {
out.println(msg); while (running && !clientSocket.isClosed()) {
out.flush(); String msg = sendQueue.poll(WAIT_TIME, TimeUnit.MILLISECONDS);
logger.debug("Sent to {}: {}", clientSocket.getRemoteSocketAddress(), msg); if (msg != null) {
out.println(msg);
out.flush();
logger.debug(
"Sent to {}: {}",
clientSocket.getRemoteSocketAddress(),
msg);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.info(
"Writer interrupted for {}",
clientSocket.getRemoteSocketAddress());
} catch (Exception e) {
logger.error(
"Writer error for {}: {}",
clientSocket.getRemoteSocketAddress(),
e.toString());
} finally {
try {
clientSocket.close();
} catch (IOException ignored) {
}
logger.info(
"Stopped write loop for {}",
clientSocket.getRemoteSocketAddress());
} }
} };
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.info("Writer interrupted for {}", clientSocket.getRemoteSocketAddress());
} catch (Exception e) {
logger.error("Writer error for {}: {}", clientSocket.getRemoteSocketAddress(), e.toString());
} finally {
try {
clientSocket.close();
} catch (IOException ignored) {}
logger.info("Stopped write loop for {}", clientSocket.getRemoteSocketAddress());
}
};
// Input and Output mappings // Input and Output mappings
connectionExecutor.submit(inputTask); connectionExecutor.submit(inputTask);
@@ -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;
@@ -46,8 +47,8 @@ public class ParsedCommand {
String tempPayload = receivedCommand; String tempPayload = receivedCommand;
if (matcher.find()) { if (matcher.find()) {
tempGameId = matcher.group(1); // first capture group → game_id tempGameId = matcher.group(1); // first capture group → game_id
tempPlayer = matcher.group(2); // second capture group → player tempPlayer = matcher.group(2); // second capture group → player
// Remove the matched part from the original command // Remove the matched part from the original command
tempPayload = matcher.replaceFirst("").trim(); tempPayload = matcher.replaceFirst("").trim();
} }
@@ -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,9 +176,10 @@ 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 -> {
this.command = commandEnum; this.command = commandEnum;
this.arguments = null; this.arguments = null;
@@ -199,9 +202,9 @@ public class ParsedCommand {
} }
} }
} }
// //
// public ParsedCommand parseCommand(String command) { // public ParsedCommand parseCommand(String command) {
// return null; // return null;
// } // }
} }

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,17 +136,21 @@ public class TicTacToeServer extends TcpServer {
} }
public void forwardGameMessages(TicTacToe game) { public void forwardGameMessages(TicTacToe game) {
dispatcherExecutor.submit(() -> { dispatcherExecutor.submit(
try { () -> {
while (isRunning()) { try {
String msg = game.sendQueue.take(); // blocks until a message is added to the queue while (isRunning()) {
logger.info("Games: {}, Adding: {} to the send queue", game.gameId, msg); String msg =
this.sendQueue.put(msg); // push to network layer game.sendQueue
} .take(); // blocks until a message is added to the queue
} catch (InterruptedException e) { logger.info(
Thread.currentThread().interrupt(); "Games: {}, Adding: {} to the send queue", game.gameId, msg);
} this.sendQueue.put(msg); // push to network layer
}); }
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
} }
public String newGame(String playerA, String playerB) { public String newGame(String playerA, String playerB) {
@@ -172,4 +184,4 @@ public class TicTacToeServer extends TcpServer {
logger.warn("Tried to end unknown game {}", gameId); logger.warn("Tried to end unknown game {}", gameId);
} }
} }
} }

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,34 +1,34 @@
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);
public static File read(String path) { public static File read(String path) {
File file; File file;
try (BufferedReader reader = new BufferedReader(new FileReader(path))) { try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
String line = reader.readLine(); String line = reader.readLine();
while (line != null) { while (line != null) {
buffer.append(line); buffer.append(line);
buffer.append(System.lineSeparator()); buffer.append(System.lineSeparator());
line = reader.readLine(); line = reader.readLine();
} }
file = new File(path, buffer); file = new File(path, buffer);
} catch (IOException e) { } catch (IOException e) {
logger.error("{}", e.getMessage()); logger.error("{}", e.getMessage());
return null; return null;
} }
return file; return file;
} }
} }

View File

@@ -1,5 +1,5 @@
package org.toop.core; package org.toop.core;
public interface ICallable<T> { public interface ICallable<T> {
public T call(); public T call();
} }

View File

@@ -1,51 +1,50 @@
package org.toop.core; package org.toop.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.frontend.platform.core.glfw.GlfwWindow; import org.toop.frontend.platform.core.glfw.GlfwWindow;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public abstract class Window { public abstract class Window {
public enum API { public enum API {
NONE, NONE,
GLFW, GLFW,
} }
public record Size(int width, int height) {} public record Size(int width, int height) {}
protected static final Logger logger = LogManager.getLogger(Window.class); protected static final Logger logger = LogManager.getLogger(Window.class);
private static API api = API.NONE; private static API api = API.NONE;
private static Window instance = null; private static Window instance = null;
public static Window setup(API api, String title, Size size) { public static Window setup(API api, String title, Size size) {
if (instance != null) { if (instance != null) {
logger.warn("Window is already setup."); logger.warn("Window is already setup.");
return instance; return instance;
} }
switch (api) { switch (api) {
case GLFW: case GLFW:
instance = new GlfwWindow(title, size); instance = new GlfwWindow(title, size);
break; break;
default: default:
logger.fatal("No valid window api chosen"); logger.fatal("No valid window api chosen");
return null; return null;
} }
Window.api = api; Window.api = api;
return instance; return instance;
} }
public static API getApi() { public static API getApi() {
return api; return api;
} }
public void cleanup() { public void cleanup() {
instance = null; instance = null;
logger.info("Window cleanup."); logger.info("Window cleanup.");
} }
public abstract void update(); public abstract void update();
} }

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.
@@ -192,17 +186,17 @@ public class Events implements IEvents {
* @param connectionId The UUID of the connection to send the command on. * @param connectionId The UUID of the connection to send the command on.
* @param args The command arguments. * @param args The command arguments.
*/ */
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.
* *
@@ -101,9 +92,10 @@ public class GlobalEventBus {
public static <T> void post(T event) { public static <T> void post(T event) {
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: " +
// } TODO: Handling non ready events. // type.getSimpleName());
// } TODO: Handling non ready events.
// store in registry // store in registry
EventMeta<T> eventMeta = new EventMeta<>(type, event); EventMeta<T> eventMeta = new EventMeta<>(type, event);
@@ -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(
this.startConnectionRequest(event.ip(), event.port()), new Events.ServerEvents.ConnectionEstablished(
event.ip(), this.startConnectionRequest(event.ip(), event.port()),
event.port() event.ip(),
)); 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());
@@ -82,18 +90,21 @@ public class ConnectionManager {
} }
} }
// private void handleChangeConnection(Events.ServerEvents.ChangeConnection event) { // private void handleChangeConnection(Events.ServerEvents.ChangeConnection event) {
// ServerConnection serverConnection = this.serverConnections.get(event.connectionId()); // ServerConnection serverConnection = this.serverConnections.get(event.connectionId());
// 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(),
// } catch (Exception e) { // event.ip(), event.port());
// logger.error("Server {} failed to change connection", event.connectionId(), e); // } catch (Exception e) {
// GlobalEventBus.post(new Events.ServerEvents.CouldNotConnect(event.connectionId())); // logger.error("Server {} failed to change connection", event.connectionId(),
// } // e);
// } // GlobalEventBus.post(new
// } TODO // Events.ServerEvents.CouldNotConnect(event.connectionId()));
// }
// }
// } TODO
private void getAllConnections(Events.ServerEvents.RequestsAllConnections request) { private void getAllConnections(Events.ServerEvents.RequestsAllConnections request) {
List<ServerConnection> a = new ArrayList<>(this.serverConnections.values()); List<ServerConnection> a = new ArrayList<>(this.serverConnections.values());
@@ -105,4 +116,4 @@ public class ConnectionManager {
this.serverConnections.clear(); this.serverConnections.clear();
logger.info("All servers shut down"); logger.info("All servers shut down");
} }
} }

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,21 +27,20 @@ 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.
*/ */
public void sendCommandByString(String... args) { public void sendCommandByString(String... args) {
// if (!TicTacToeServerCommand.isValid(command)) { // if (!TicTacToeServerCommand.isValid(command)) {
// logger.error("Invalid command: {}", command); // logger.error("Invalid command: {}", command);
// return; // return;
// } // TODO: DO I CARE? // } // TODO: DO I CARE?
// if (!this.running) { // if (!this.running) {
// logger.warn("Server has been stopped"); // logger.warn("Server has been stopped");
// return; // return;
// } // TODO: Server not running // } // TODO: Server not running
String command = String.join(" ", args); String command = String.join(" ", args);
@@ -69,11 +67,11 @@ public final class ServerConnection extends TcpClient implements Runnable {
private void stopWorkers() { private void stopWorkers() {
this.running = false; this.running = false;
this.sendQueue.clear(); this.sendQueue.clear();
try { try {
this.closeSocket(); this.closeSocket();
} catch (IOException e) { } catch (IOException e) {
logger.warn("Error closing client socket", e); // TODO: Better log logger.warn("Error closing client socket", e); // TODO: Better log
} }
this.executor.shutdownNow(); this.executor.shutdownNow();
} }
@@ -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;
@@ -18,4 +18,4 @@ public class BackgroundPanel extends JPanel {
g.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this); g.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this);
} }
} }
} }

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);
@@ -20,7 +14,7 @@ public class LocalGameSelector extends JFrame {
private JButton startGame; private JButton startGame;
private JComboBox playerTypeSelectionBox; private JComboBox playerTypeSelectionBox;
private JPanel cards; // CardLayout panel private JPanel cards; // CardLayout panel
private CardLayout cardLayout; private CardLayout cardLayout;
private UIGameBoard tttBoard; private UIGameBoard tttBoard;
@@ -70,14 +64,14 @@ public class LocalGameSelector extends JFrame {
if (playerTypes.equals("Player vs Player")) { if (playerTypes.equals("Player vs Player")) {
logger.info("Player vs Player"); logger.info("Player vs Player");
lttt = LocalTicTacToe.createLocal(new boolean[] { false, false }); lttt = LocalTicTacToe.createLocal(new boolean[] {false, false});
} else { } else {
if (playerTypes.equals("Player vs AI")) { if (playerTypes.equals("Player vs AI")) {
logger.info("Player vs AI"); logger.info("Player vs AI");
lttt = LocalTicTacToe.createLocal(new boolean[] { false, true }); lttt = LocalTicTacToe.createLocal(new boolean[] {false, true});
} else { } else {
logger.info("AI vs Player"); logger.info("AI vs Player");
lttt = LocalTicTacToe.createLocal(new boolean[] { true, false }); lttt = LocalTicTacToe.createLocal(new boolean[] {true, false});
} }
} }

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);
@@ -29,7 +28,7 @@ public class RemoteGameSelector {
public RemoteGameSelector() { public RemoteGameSelector() {
gameSelectorBox.addItem("Tic Tac Toe"); gameSelectorBox.addItem("Tic Tac Toe");
gameSelectorBox.addItem("Reversi"); gameSelectorBox.addItem("Reversi");
//todo get supported games from server and add to gameSelectorBox // todo get supported games from server and add to gameSelectorBox
frame = new JFrame("Game Selector"); frame = new JFrame("Game Selector");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1920, 1080); frame.setSize(1920, 1080);
@@ -38,83 +37,102 @@ 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(
portTextField.getText(), new Events.ServerEvents.StartServerRequest(
Objects.requireNonNull(gameSelectorBox.getSelectedItem()).toString().toLowerCase().replace(" ", ""), portTextField.getText(),
serverIdFuture Objects.requireNonNull(gameSelectorBox.getSelectedItem())
)); .toString()
String serverId; .toLowerCase()
try { .replace(" ", ""),
serverId = serverIdFuture.get(); serverIdFuture));
} catch (InterruptedException | ExecutionException ex) { String serverId;
throw new RuntimeException(ex); try {
} // TODO: Better error handling to not crash the system. serverId = serverIdFuture.get();
} catch (InterruptedException | ExecutionException ex) {
throw new RuntimeException(ex);
} // 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(
ipTextField.getText(), new Events.ServerEvents.StartConnectionRequest(
portTextField.getText(), ipTextField.getText(),
connectionIdFuture portTextField.getText(),
)); connectionIdFuture));
String connectionId; String connectionId;
try { try {
connectionId = connectionIdFuture.get(); connectionId = connectionIdFuture.get();
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
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(
event -> { Events.ServerEvents.ReceivedMessage.class,
if (event.message().equalsIgnoreCase("ok")) { event -> {
logger.info("received ok from server."); if (event.message().equalsIgnoreCase("ok")) {
} else if (event.message().toLowerCase().startsWith("gameid")) { logger.info("received ok from server.");
String gameId = event.message().toLowerCase().replace("gameid ", ""); } else if (event.message().toLowerCase().startsWith("gameid")) {
GlobalEventBus.post(new Events.ServerEvents.SendCommand("start_game " + gameId)); String gameId =
} event.message()
else { .toLowerCase()
logger.info("{}", event.message()); .replace("gameid ", "");
} GlobalEventBus.post(
new Events.ServerEvents.SendCommand(
"start_game " + gameId));
} else {
logger.info("{}", event.message());
}
});
GlobalEventBus.post(
new Events.ServerEvents.SendCommand(
connectionId,
"create_game",
nameTextField.getText(),
name2TextField.getText()));
// CompletableFuture<String> ticTacToeGame = new
// CompletableFuture<>();
// GlobalEventBus.post(new
// Events.ServerEvents.CreateTicTacToeGameRequest( // TODO: Make this happen
// through commands send through the connection, instead of an event.
// serverId,
// nameTextField.getText(),
// name2TextField.getText(),
// ticTacToeGame
// ));
// String ticTacToeGameId;
// try {
// ticTacToeGameId = ticTacToeGame.get();
// } catch (InterruptedException | ExecutionException ex) {
// throw new RuntimeException(ex);
// } // TODO: Better error handling to not crash the system.
frame.remove(mainMenu);
// UIGameBoard ttt = new UIGameBoard("tic tac toe", "test",
// "test",this); // TODO: Fix later
// frame.add(ttt.getTTTPanel()); // TODO: Fix later
frame.revalidate();
frame.repaint();
} else {
fillAllFields.setVisible(true);
} }
); });
GlobalEventBus.post(new Events.ServerEvents.SendCommand(connectionId, "create_game", nameTextField.getText(), name2TextField.getText()));
// CompletableFuture<String> ticTacToeGame = new CompletableFuture<>();
// GlobalEventBus.post(new Events.ServerEvents.CreateTicTacToeGameRequest( // TODO: Make this happen through commands send through the connection, instead of an event.
// serverId,
// nameTextField.getText(),
// name2TextField.getText(),
// ticTacToeGame
// ));
// String ticTacToeGameId;
// try {
// ticTacToeGameId = ticTacToeGame.get();
// } catch (InterruptedException | ExecutionException ex) {
// throw new RuntimeException(ex);
// } // TODO: Better error handling to not crash the system.
frame.remove(mainMenu);
// UIGameBoard ttt = new UIGameBoard("tic tac toe", "test", "test",this); // TODO: Fix later
// frame.add(ttt.getTTTPanel()); // TODO: Fix later
frame.revalidate();
frame.repaint();
} else {
fillAllFields.setVisible(true);
}
});
} }
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,18 +1,14 @@
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;
private JPanel tttPanel; // Root panel for this game private JPanel tttPanel; // Root panel for this game
private JButton backToMainMenuButton; private JButton backToMainMenuButton;
private JButton[] cells; private JButton[] cells;
private String currentPlayer = "X"; private String currentPlayer = "X";
@@ -32,20 +28,20 @@ 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 _ ->
parent.showMainMenu() // TODO reset game and connections
); parent.showMainMenu());
// Game grid // Game grid
JPanel gameGrid = createGridPanel(TICTACTOE_SIZE, TICTACTOE_SIZE); JPanel gameGrid = createGridPanel(TICTACTOE_SIZE, TICTACTOE_SIZE);
tttPanel.add(gameGrid, BorderLayout.CENTER); tttPanel.add(gameGrid, BorderLayout.CENTER);
// localTicTacToe.setMoveListener((playerIndex, moveIndex, symbol) -> { // localTicTacToe.setMoveListener((playerIndex, moveIndex, symbol) -> {
// SwingUtilities.invokeLater(() -> { // SwingUtilities.invokeLater(() -> {
// cells[moveIndex].setText(String.valueOf(symbol)); // cells[moveIndex].setText(String.valueOf(symbol));
// }); // });
// }); // });
} }
@@ -59,18 +55,25 @@ 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(
int cp = this.localTicTacToe.getCurrentPlayersTurn(); (ActionEvent _) -> {
if (cp == 0) { this.currentPlayer = "X"; currentPlayerIndex = 0; } int cp = this.localTicTacToe.getCurrentPlayersTurn();
else if (cp == 1) { this.currentPlayer = "O"; currentPlayerIndex = 1; } if (cp == 0) {
this.localTicTacToe.move(index); this.currentPlayer = "X";
cells[index].setText(currentPlayer); currentPlayerIndex = 0;
}); } else if (cp == 1) {
this.currentPlayer = "O";
currentPlayerIndex = 1;
}
this.localTicTacToe.move(index);
cells[index].setText(currentPlayer);
});
} }
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);
} }
@@ -78,4 +81,4 @@ public class UIGameBoard {
public JPanel getTTTPanel() { public JPanel getTTTPanel() {
return tttPanel; return tttPanel;
} }
} }

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,38 +34,39 @@ 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 =
// this.receivedMessageListener = GlobalEventBus.subscribe(Events.ServerEvents.ReceivedMessage.class, this::receiveMessageAction); // GlobalEventBus.subscribe(Events.ServerEvents.ReceivedMessage.class,
// GlobalEventBus.register(this.receivedMessageListener); // this::receiveMessageAction);
// // GlobalEventBus.register(this.receivedMessageListener);
// //
// this.executor.submit(this::gameThread); //
// } TODO: If remote server // this.executor.submit(this::gameThread);
// } 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,14 +191,25 @@ 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 { () -> {
if (this.playersTurn == 0 && !isAiPlayer[0]) { this.moveQueuePlayerA.put(moveIndex); logger.info("Adding player's {}, move: {}", this.playersTurn, moveIndex); } try {
else if (this.playersTurn == 1 && !isAiPlayer[1]) { this.moveQueuePlayerB.put(moveIndex); logger.info("Adding player's {}, move: {}", this.playersTurn, moveIndex); } if (this.playersTurn == 0 && !isAiPlayer[0]) {
} catch (InterruptedException e) { this.moveQueuePlayerA.put(moveIndex);
logger.error("Could not add player: {}'s, move {}", this.playersTurn, moveIndex); // TODO: Error handling instead of crash. logger.info(
} "Adding player's {}, move: {}", this.playersTurn, moveIndex);
}); } else if (this.playersTurn == 1 && !isAiPlayer[1]) {
this.moveQueuePlayerB.put(moveIndex);
logger.info(
"Adding player's {}, move: {}", this.playersTurn, moveIndex);
}
} catch (InterruptedException e) {
logger.error(
"Could not add player: {}'s, move {}",
this.playersTurn,
moveIndex); // TODO: Error handling instead of crash.
}
});
} }
private void endTheGame() { private void endTheGame() {
@@ -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);
@@ -226,4 +245,4 @@ public class LocalTicTacToe { // TODO: Implement runnable
public void setUIReference(UIGameBoard uiGameBoard) { public void setUIReference(UIGameBoard uiGameBoard) {
this.ui = uiGameBoard; this.ui = uiGameBoard;
} }
} }

View File

@@ -1,50 +1,50 @@
package org.toop.frontend.graphics; package org.toop.frontend.graphics;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.frontend.platform.graphics.opengl.OpenglRenderer; import org.toop.frontend.platform.graphics.opengl.OpenglRenderer;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public abstract class Renderer { public abstract class Renderer {
public enum API { public enum API {
NONE, NONE,
OPENGL, OPENGL,
}; };
protected static final Logger logger = LogManager.getLogger(Renderer.class); protected static final Logger logger = LogManager.getLogger(Renderer.class);
private static API api = API.NONE; private static API api = API.NONE;
private static Renderer instance = null; private static Renderer instance = null;
public static Renderer setup(API api) { public static Renderer setup(API api) {
if (instance != null) { if (instance != null) {
logger.warn("Renderer is already setup."); logger.warn("Renderer is already setup.");
return instance; return instance;
} }
switch (api) { switch (api) {
case OPENGL: case OPENGL:
instance = new OpenglRenderer(); instance = new OpenglRenderer();
break; break;
default: default:
logger.fatal("No valid renderer api chosen"); logger.fatal("No valid renderer api chosen");
return null; return null;
} }
Renderer.api = api; Renderer.api = api;
return instance; return instance;
} }
public static API getApi() { public static API getApi() {
return api; return api;
} }
public void cleanup() { public void cleanup() {
instance = null; instance = null;
logger.info("Renderer cleanup."); logger.info("Renderer cleanup.");
} }
public abstract void clear(); public abstract void clear();
public abstract void render();
public abstract void render();
} }

View File

@@ -3,24 +3,25 @@ package org.toop.frontend.graphics;
import org.toop.frontend.platform.graphics.opengl.OpenglShader; import org.toop.frontend.platform.graphics.opengl.OpenglShader;
public abstract class Shader { public abstract class Shader {
public static Shader create(String vertexPath, String fragmentPath) { public static Shader create(String vertexPath, String fragmentPath) {
Shader shader = null; Shader shader = null;
switch (Renderer.getApi()) { switch (Renderer.getApi()) {
case OPENGL: case OPENGL:
shader = new OpenglShader(vertexPath, fragmentPath); shader = new OpenglShader(vertexPath, fragmentPath);
break; break;
case NONE: case NONE:
default: default:
break; break;
} }
return shader; return 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

@@ -4,23 +4,30 @@ import org.toop.core.*;
import org.toop.frontend.math.Color; import org.toop.frontend.math.Color;
public class Button extends Node { 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(
super(x, y, width, height, color); int x,
int y,
int width,
int height,
Color color,
ICallable<Boolean> onHover,
ICallable<Boolean> onClick) {
super(x, y, width, height, color);
this.onHover = onHover; this.onHover = onHover;
this.onClick = onClick; this.onClick = onClick;
} }
@Override @Override
public void hover() { public void hover() {
onHover.call(); onHover.call();
} }
@Override @Override
public void click() { public void click() {
onClick.call(); onClick.call();
} }
} }

View File

@@ -4,18 +4,19 @@ import org.toop.frontend.math.Bounds;
import org.toop.frontend.math.Color; import org.toop.frontend.math.Color;
public abstract class Node { public abstract class Node {
protected Bounds bounds; protected Bounds bounds;
protected Color color; protected Color color;
public Node(int x, int y, int width, int height, Color color) { public Node(int x, int y, int width, int height, Color color) {
bounds = new Bounds(x, y, width, height); bounds = new Bounds(x, y, width, height);
this.color = color; this.color = color;
} }
public boolean check(int x, int y) { public boolean check(int x, int y) {
return bounds.check(x, y); return bounds.check(x, y);
} }
public void hover() {} public void hover() {}
public void click() {}
public void click() {}
} }

View File

@@ -1,66 +1,67 @@
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);
private static NodeManager instance = null; private static NodeManager instance = null;
public static NodeManager setup() {
if (instance != null) {
logger.warn("NodeManager is already setup.");
return instance;
}
instance = new NodeManager(); public static NodeManager setup() {
return instance; if (instance != null) {
} logger.warn("NodeManager is already setup.");
return instance;
}
private Shader shader; instance = new NodeManager();
private ArrayList<Node> nodes; return instance;
private Node active; }
private NodeManager() { private Shader shader;
shader = Shader.create( private ArrayList<Node> nodes;
"src/main/resources/shaders/gui_vertex.glsl", private Node active;
"src/main/resources/shaders/gui_fragment.glsl");
nodes = new ArrayList<Node>(); private NodeManager() {
shader =
Shader.create(
"src/main/resources/shaders/gui_vertex.glsl",
"src/main/resources/shaders/gui_fragment.glsl");
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseMove.class, event -> { nodes = new ArrayList<Node>();
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
if (node.check(event.x(), event.y())) { GlobalEventBus.subscribeAndRegister(
active = node; Events.WindowEvents.OnMouseMove.class,
node.hover(); event -> {
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
break; if (node.check(event.x(), event.y())) {
} active = node;
} node.hover();
});
GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseClick.class, event -> { break;
if (active != null) { }
active.click(); }
} });
});
}
public void cleanup() { GlobalEventBus.subscribeAndRegister(
} Events.WindowEvents.OnMouseClick.class,
event -> {
if (active != null) {
active.click();
}
});
}
public void add(Node node) { public void cleanup() {}
nodes.add(node);
}
public void render() { public void add(Node node) {
} nodes.add(node);
}
public void render() {}
} }

View File

@@ -1,49 +1,48 @@
package org.toop.frontend.graphics.node; package org.toop.frontend.graphics.node;
import java.util.*;
import org.toop.frontend.math.Bounds; import org.toop.frontend.math.Bounds;
import java.util.*;
public class Widget { public class Widget {
Bounds bounds; Bounds bounds;
private ArrayList<Node> nodes; private ArrayList<Node> nodes;
public Widget(Bounds bounds) { public Widget(Bounds bounds) {
this.bounds = bounds; this.bounds = bounds;
nodes = new ArrayList<Node>(); nodes = new ArrayList<Node>();
} }
public boolean check(int x, int y) { public boolean check(int x, int y) {
return bounds.check(x, y); return bounds.check(x, y);
} }
public void add(Node node) { public void add(Node node) {
nodes.add(node); nodes.add(node);
} }
public boolean hover(int x, int y) { public boolean hover(int x, int y) {
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);
if (node.check(x, y)) { if (node.check(x, y)) {
node.hover(); node.hover();
return true; return true;
} }
} }
return false; return false;
} }
public boolean click(int x, int y) { public boolean click(int x, int y) {
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);
if (node.check(x, y)) { if (node.check(x, y)) {
node.click(); node.click();
return true; return true;
} }
} }
return false; return false;
} }
} }

View File

@@ -1,30 +1,39 @@
package org.toop.frontend.math; package org.toop.frontend.math;
public class Bounds { public class Bounds {
private int x; private int x;
private int y; private int y;
private int width; private int width;
private int height; private int height;
public Bounds(int x, int y, int width, int height) { public Bounds(int x, int y, int width, int height) {
set(x, y, width, height); set(x, y, width, height);
} }
public void set(int x, int y, int width, int height) { public void set(int x, int y, int width, int height) {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.width = width; this.width = width;
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 boolean check(int x, int y) { public int getY() {
return return y;
x >= this.x && x <= this.x + this.width && }
y >= this.y && y <= this.y + this.height;
} public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public boolean check(int x, int y) {
return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height;
}
} }

View File

@@ -1,17 +1,25 @@
package org.toop.frontend.math; package org.toop.frontend.math;
public class Color { public class Color {
private float r; private float r;
private float g; private float g;
private float b; private float b;
public Color(float r, float g, float b) { public Color(float r, float g, float b) {
this.r = r; this.r = r;
this.g = g; this.g = g;
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,95 +1,108 @@
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;
public GlfwWindow(String title, Size size) { public GlfwWindow(String title, Size size) {
if (!GLFW.glfwInit()) { if (!GLFW.glfwInit()) {
logger.fatal("Failed to initialize glfw"); logger.fatal("Failed to initialize glfw");
return; return;
} }
GLFW.glfwDefaultWindowHints(); GLFW.glfwDefaultWindowHints();
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE); GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE);
GLFWVidMode videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor()); GLFWVidMode videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
int width = size.width(); int width = size.width();
int height = size.height(); int height = size.height();
if (width <= 0 || height <= 0 || width > videoMode.width() || height > videoMode.height()) { if (width <= 0 || height <= 0 || width > videoMode.width() || height > videoMode.height()) {
width = videoMode.width(); width = videoMode.width();
height = videoMode.height(); height = videoMode.height();
GLFW.glfwWindowHint(GLFW.GLFW_MAXIMIZED, GLFW.GLFW_TRUE); GLFW.glfwWindowHint(GLFW.GLFW_MAXIMIZED, GLFW.GLFW_TRUE);
} }
long window = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL); long window = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL);
if (window == MemoryUtil.NULL) { if (window == MemoryUtil.NULL) {
GLFW.glfwTerminate(); GLFW.glfwTerminate();
logger.fatal("Failed to create glfw window"); logger.fatal("Failed to create glfw window");
return; return;
} }
int[] widthBuffer = new int[1]; int[] widthBuffer = new int[1];
int[] heightBuffer = new int[1]; int[] heightBuffer = new int[1];
GLFW.glfwGetWindowSize(window, widthBuffer, heightBuffer); GLFW.glfwGetWindowSize(window, widthBuffer, heightBuffer);
GLFW.glfwMakeContextCurrent(window); GLFW.glfwMakeContextCurrent(window);
GLFW.glfwSwapInterval(1); GLFW.glfwSwapInterval(1);
GLFW.glfwSetWindowCloseCallback(window, (lwindow) -> { GLFW.glfwSetWindowCloseCallback(
GlobalEventBus.post(new Events.WindowEvents.OnQuitRequested()); window,
}); (lwindow) -> {
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(
GlobalEventBus.post(new Events.WindowEvents.OnMouseMove((int)lx, (int)ly)); window,
}); (lwindow, lx, ly) -> {
GlobalEventBus.post(new Events.WindowEvents.OnMouseMove((int) lx, (int) ly));
});
GLFW.glfwSetMouseButtonCallback(window, (lwindow, lbutton, laction, lmods) -> { GLFW.glfwSetMouseButtonCallback(
switch (laction) { window,
case GLFW.GLFW_PRESS: (lwindow, lbutton, laction, lmods) -> {
GlobalEventBus.post(new Events.WindowEvents.OnMouseClick(lbutton)); switch (laction) {
break; case GLFW.GLFW_PRESS:
GlobalEventBus.post(new Events.WindowEvents.OnMouseClick(lbutton));
break;
case GLFW.GLFW_RELEASE: case GLFW.GLFW_RELEASE:
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
public void cleanup() { public void cleanup() {
GLFW.glfwDestroyWindow(window); GLFW.glfwDestroyWindow(window);
GLFW.glfwTerminate(); GLFW.glfwTerminate();
super.cleanup(); super.cleanup();
} }
@Override @Override
public void update() { public void update() {
GLFW.glfwSwapBuffers(window); GLFW.glfwSwapBuffers(window);
GLFW.glfwPollEvents(); GLFW.glfwPollEvents();
} }
} }

View File

@@ -1,76 +1,78 @@
package org.toop.frontend.platform.graphics.opengl; package org.toop.frontend.platform.graphics.opengl;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
import org.toop.eventbus.*; import org.toop.eventbus.*;
import org.toop.frontend.graphics.Renderer; import org.toop.frontend.graphics.Renderer;
import org.toop.frontend.graphics.Shader; import org.toop.frontend.graphics.Shader;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
public class OpenglRenderer extends Renderer { public class OpenglRenderer extends Renderer {
private Shader shader; private Shader shader;
private int vao; private int vao;
public OpenglRenderer() { public OpenglRenderer() {
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 -> {
GL45.glViewport(0, 0, event.size().width(), event.size().height());
});
logger.info("Opengl renderer setup."); GlobalEventBus.subscribeAndRegister(
Events.WindowEvents.OnResize.class,
event -> {
GL45.glViewport(0, 0, event.size().width(), event.size().height());
});
// Form here on, everything is temporary logger.info("Opengl renderer setup.");
float vertices[] = {
-0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
};
int indicies[] = { // Form here on, everything is temporary
0, 1, 2, float vertices[] = {
2, 3, 0, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
}; -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
};
vao = GL45.glCreateVertexArrays(); int indicies[] = {
GL45.glBindVertexArray(vao); 0, 1, 2,
2, 3, 0,
};
int vbo = GL45.glCreateBuffers(); vao = GL45.glCreateVertexArrays();
GL45.glBindBuffer(GL45.GL_ARRAY_BUFFER, vbo); GL45.glBindVertexArray(vao);
GL45.glBufferData(GL45.GL_ARRAY_BUFFER, vertices, GL45.GL_STATIC_DRAW);
GL45.glVertexAttribPointer(0, 2, GL45.GL_FLOAT, false, 5 * 4, 0); int vbo = GL45.glCreateBuffers();
GL45.glVertexAttribPointer(1, 3, GL45.GL_FLOAT, false, 5 * 4, 2 * 4); GL45.glBindBuffer(GL45.GL_ARRAY_BUFFER, vbo);
GL45.glBufferData(GL45.GL_ARRAY_BUFFER, vertices, GL45.GL_STATIC_DRAW);
GL45.glEnableVertexAttribArray(0); GL45.glVertexAttribPointer(0, 2, GL45.GL_FLOAT, false, 5 * 4, 0);
GL45.glEnableVertexAttribArray(1); GL45.glVertexAttribPointer(1, 3, GL45.GL_FLOAT, false, 5 * 4, 2 * 4);
int ib = GL45.glCreateBuffers(); GL45.glEnableVertexAttribArray(0);
GL45.glBindBuffer(GL45.GL_ELEMENT_ARRAY_BUFFER, ib); GL45.glEnableVertexAttribArray(1);
GL45.glBufferData(GL45.GL_ELEMENT_ARRAY_BUFFER, indicies, GL45.GL_STATIC_DRAW);
shader = Shader.create( int ib = GL45.glCreateBuffers();
"src/main/resources/shaders/gui_vertex.glsl", GL45.glBindBuffer(GL45.GL_ELEMENT_ARRAY_BUFFER, ib);
"src/main/resources/shaders/gui_fragment.glsl"); GL45.glBufferData(GL45.GL_ELEMENT_ARRAY_BUFFER, indicies, GL45.GL_STATIC_DRAW);
}
@Override shader =
public void cleanup() { Shader.create(
super.cleanup(); "src/main/resources/shaders/gui_vertex.glsl",
} "src/main/resources/shaders/gui_fragment.glsl");
}
@Override @Override
public void clear() { public void cleanup() {
GL45.glClear(GL45.GL_COLOR_BUFFER_BIT); super.cleanup();
} }
@Override @Override
public void render() { public void clear() {
// temporary GL45.glClear(GL45.GL_COLOR_BUFFER_BIT);
// shader.start(); }
GL45.glBindVertexArray(vao);
GL45.glDrawElements(GL45.GL_TRIANGLES, 6, GL45.GL_UNSIGNED_INT, MemoryUtil.NULL); @Override
} public void render() {
// temporary
// shader.start();
GL45.glBindVertexArray(vao);
GL45.glDrawElements(GL45.GL_TRIANGLES, 6, GL45.GL_UNSIGNED_INT, MemoryUtil.NULL);
}
} }

View File

@@ -1,58 +1,57 @@
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;
public OpenglShader(String vertexPath, String fragmentPath) { public OpenglShader(String vertexPath, String fragmentPath) {
FileSystem.File vertexSource = FileSystem.read(vertexPath); FileSystem.File vertexSource = FileSystem.read(vertexPath);
FileSystem.File fragmentSource = FileSystem.read(fragmentPath); FileSystem.File fragmentSource = FileSystem.read(fragmentPath);
if (vertexSource == null || fragmentPath == null) { if (vertexSource == null || fragmentPath == null) {
return; return;
} }
programID = GL45.glCreateProgram(); programID = GL45.glCreateProgram();
int vertexShader = GL45.glCreateShader(GL45.GL_VERTEX_SHADER); int vertexShader = GL45.glCreateShader(GL45.GL_VERTEX_SHADER);
int fragmentShader = GL45.glCreateShader(GL45.GL_FRAGMENT_SHADER); int fragmentShader = GL45.glCreateShader(GL45.GL_FRAGMENT_SHADER);
GL45.glShaderSource(vertexShader, vertexSource.buffer()); GL45.glShaderSource(vertexShader, vertexSource.buffer());
GL45.glShaderSource(fragmentShader, fragmentSource.buffer()); GL45.glShaderSource(fragmentShader, fragmentSource.buffer());
GL45.glCompileShader(vertexShader); GL45.glCompileShader(vertexShader);
GL45.glCompileShader(fragmentShader); GL45.glCompileShader(fragmentShader);
GL45.glAttachShader(programID, vertexShader); GL45.glAttachShader(programID, vertexShader);
GL45.glAttachShader(programID, fragmentShader); GL45.glAttachShader(programID, fragmentShader);
GL45.glLinkProgram(programID); GL45.glLinkProgram(programID);
GL45.glValidateProgram(programID); GL45.glValidateProgram(programID);
GL45.glDetachShader(programID, vertexShader); GL45.glDetachShader(programID, vertexShader);
GL45.glDetachShader(programID, fragmentShader); GL45.glDetachShader(programID, fragmentShader);
GL45.glDeleteShader(vertexShader); GL45.glDeleteShader(vertexShader);
GL45.glDeleteShader(fragmentShader); GL45.glDeleteShader(fragmentShader);
} }
@Override @Override
public void cleanup() { public void cleanup() {
stop(); stop();
GL45.glDeleteProgram(programID); GL45.glDeleteProgram(programID);
} }
@Override @Override
public void start() { public void start() {
GL45.glUseProgram(programID); GL45.glUseProgram(programID);
} }
@Override @Override
public void stop() { public void stop() {
GL45.glUseProgram(0); GL45.glUseProgram(0);
} }
} }

View File

@@ -1,46 +1,56 @@
package org.toop.game; package org.toop.game;
public abstract class GameBase { public abstract class GameBase {
public enum State { public enum State {
INVALID, INVALID,
NORMAL, NORMAL,
DRAW, DRAW,
WIN, WIN,
} }
public static char EMPTY = '-'; public static char EMPTY = '-';
protected int size; protected int size;
public char[] grid; public char[] grid;
protected Player[] players; protected Player[] players;
public int currentPlayer; public int currentPlayer;
public GameBase(int size, Player player1, Player player2) { public GameBase(int size, Player player1, Player player2) {
this.size = size; this.size = size;
grid = new char[size * size]; grid = new char[size * size];
for (int i = 0; i < grid.length; i++) { for (int i = 0; i < grid.length; i++) {
grid[i] = EMPTY; grid[i] = EMPTY;
} }
players = new Player[2]; players = new Player[2];
players[0] = player1; players[0] = player1;
players[1] = player2; players[1] = player2;
currentPlayer = 0; currentPlayer = 0;
} }
public boolean isInside(int index) { public boolean isInside(int index) {
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 abstract State play(int index); public Player[] getPlayers() {
return players;
}
public Player getCurrentPlayer() {
return players[currentPlayer];
}
public abstract State play(int index);
} }

View File

@@ -1,18 +1,17 @@
package org.toop.game.tictactoe; 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
@@ -21,7 +20,7 @@ public class MinMaxTicTacToe {
boolean empty = true; boolean empty = true;
for (char cell : game.grid) { for (char cell : game.grid) {
if(!(cell == GameBase.EMPTY)) { if (!(cell == GameBase.EMPTY)) {
empty = false; empty = false;
break; break;
} }
@@ -34,8 +33,7 @@ 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
@@ -45,7 +43,7 @@ public class MinMaxTicTacToe {
return i; // just return right away if you can win on the next move return i; // just return right away if you can win on the next move
} }
for (int index = 0; index < game.grid.length; index++ ) { for (int index = 0; index < game.grid.length; index++) {
if (game.validateMove(index)) { if (game.validateMove(index)) {
TicTacToe opponentCopy = copyGame.copyBoard(); TicTacToe opponentCopy = copyGame.copyBoard();
GameBase.State opponentResult = opponentCopy.play(index); GameBase.State opponentResult = opponentCopy.play(index);
@@ -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,48 +1,45 @@
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);
public Thread gameThread; public Thread gameThread;
public String gameId; public String gameId;
public BlockingQueue<ParsedCommand> commandQueue = new LinkedBlockingQueue<>(); public BlockingQueue<ParsedCommand> commandQueue = new LinkedBlockingQueue<>();
public BlockingQueue<String> sendQueue = new LinkedBlockingQueue<>(); public BlockingQueue<String> sendQueue = new LinkedBlockingQueue<>();
public int movesLeft; public int movesLeft;
public TicTacToe(String player1, String player2) { public TicTacToe(String player1, String player2) {
super(3, new Player(player1, 'X'), new Player(player2, 'O')); super(3, new Player(player1, 'X'), new Player(player2, 'O'));
movesLeft = size * size; movesLeft = size * size;
} }
/** /**
* * Used for the server.
* Used for the server. *
* * @param player1
* @param player1 * @param player2
* @param player2 * @param gameId
* @param gameId */
*/ public TicTacToe(String player1, String player2, String gameId) {
public TicTacToe(String player1, String player2, String gameId) { super(3, new Player(player1, 'X'), new Player(player2, 'O'));
super(3, new Player(player1, 'X'), new Player(player2, 'O')); this.gameId = gameId;
this.gameId = gameId; movesLeft = size * size;
movesLeft = size * size; }
}
public void addCommandToQueue(ParsedCommand command) {
public void addCommandToQueue(ParsedCommand command) { commandQueue.add(command);
commandQueue.add(command); }
}
private ParsedCommand takeFromCommandQueue() { private ParsedCommand takeFromCommandQueue() {
try { try {
@@ -53,165 +50,174 @@ public class TicTacToe extends GameBase implements Runnable {
} }
} }
private void addSendToQueue(String send) { private void addSendToQueue(String send) {
try { try {
sendQueue.put(send); sendQueue.put(send);
} catch (InterruptedException e) { } catch (InterruptedException e) {
logger.error("Sending to queue interrupted, in game with id: {}", this.gameId); logger.error("Sending to queue interrupted, in game with id: {}", this.gameId);
} }
} }
@Override @Override
public void run() { public void run() {
this.gameThread = new Thread(this::gameThread); this.gameThread = new Thread(this::gameThread);
this.gameThread.start(); this.gameThread.start();
} }
private void gameThread() { private void gameThread() {
boolean running = true; boolean running = true;
while (running) { while (running) {
ParsedCommand cmd = takeFromCommandQueue(); ParsedCommand cmd = takeFromCommandQueue();
// Get next command if there was no command // Get next command if there was no command
if (cmd == null){ if (cmd == null) {
continue; continue;
} }
// 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;
}
// Attempt to play the move
State state = play(index);
if (state != State.INVALID){
// Tell all players who made a move and what move was made
// TODO: What is the reaction of the game? WIN, DRAW etc?
String player = getCurrentPlayer().name();
addSendToQueue("SVR GAME MOVE {PLAYER: \"" +
player +
"\", DETAILS: \"<reactie spel op zet>\",MOVE: \"" +
index +
"\"}\n");
}
// Check move result
switch (state){
case State.WIN:{
// Win
running = false;
addSendToQueue("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"" +
"<score speler2>\", COMMENT: \"<commentaar op resultaat>\"}\n");
break;
} }
case State.DRAW:{
// Draw // Attempt to play the move
running = false; State state = play(index);
addSendToQueue("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"" +
"<score speler2>\", COMMENT: \"<commentaar op resultaat>\"}\n"); if (state != State.INVALID) {
break; // Tell all players who made a move and what move was made
// TODO: What is the reaction of the game? WIN, DRAW etc?
String player = getCurrentPlayer().name();
addSendToQueue(
"SVR GAME MOVE {PLAYER: \""
+ player
+ "\", DETAILS: \"<reactie spel op zet>\",MOVE: \""
+ index
+ "\"}\n");
} }
case State.NORMAL:{
// Valid move but not end of game // Check move result
addSendToQueue("SVR GAME YOURTURN"); switch (state) {
break; case State.WIN:
} {
case State.INVALID:{ // Win
// Invalid move running = false;
break; addSendToQueue(
"SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\","
+ " PLAYERTWOSCORE: \"<score speler2>\", COMMENT:"
+ " \"<commentaar op resultaat>\"}\n");
break;
}
case State.DRAW:
{
// Draw
running = false;
addSendToQueue(
"SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\","
+ " PLAYERTWOSCORE: \"<score speler2>\", COMMENT:"
+ " \"<commentaar op resultaat>\"}\n");
break;
}
case State.NORMAL:
{
// Valid move but not end of game
addSendToQueue("SVR GAME YOURTURN");
break;
}
case State.INVALID:
{
// Invalid move
break;
}
} }
} }
}
} }
} }
}
} @Override
public State play(int index) {
if (!validateMove(index)) {
return State.INVALID;
}
@Override grid[index] = getCurrentPlayer().move();
public State play(int index) { movesLeft--;
if (!validateMove(index)) {
return State.INVALID;
}
grid[index] = getCurrentPlayer().move(); if (checkWin()) {
movesLeft--; return State.WIN;
}
if (checkWin()) { if (movesLeft <= 0) {
return State.WIN; return State.DRAW;
} }
if (movesLeft <= 0) { currentPlayer = (currentPlayer + 1) % players.length;
return State.DRAW; return State.NORMAL;
} }
currentPlayer = (currentPlayer + 1) % players.length; public boolean validateMove(int index) {
return State.NORMAL; return movesLeft > 0 && isInside(index) && grid[index] == EMPTY;
} }
public boolean validateMove(int index) { public boolean checkWin() {
return movesLeft > 0 && isInside(index) && grid[index] == EMPTY; // Horizontal
} for (int i = 0; i < 3; i++) {
final int index = i * 3;
public boolean checkWin() { if (grid[index] != EMPTY
// Horizontal && grid[index] == grid[index + 1]
for (int i = 0; i < 3; i++) { && grid[index] == grid[index + 2]) {
final int index = i * 3; return true;
}
}
if (grid[index] != EMPTY && grid[index] == grid[index + 1] && grid[index] == grid[index + 2]) { // Vertical
return true; for (int i = 0; i < 3; i++) {
} int index = i;
}
// Vertical if (grid[index] != EMPTY
for (int i = 0; i < 3; i++) { && grid[index] == grid[index + 3]
int index = i; && grid[index] == grid[index + 6]) {
return true;
}
}
if (grid[index] != EMPTY && grid[index] == grid[index + 3] && grid[index] == grid[index + 6]) { // B-Slash
return true; if (grid[0] != EMPTY && grid[0] == grid[4] && grid[0] == grid[8]) {
} return true;
} }
// B-Slash // F-Slash
if (grid[0] != EMPTY && grid[0] == grid[4] && grid[0] == grid[8]) { if (grid[2] != EMPTY && grid[2] == grid[4] && grid[2] == grid[6]) {
return true; return true;
} }
// F-Slash return false;
if (grid[2] != EMPTY && grid[2] == grid[4] && grid[2] == grid[6]) { }
return true;
}
return false; /** For AI use only. */
} public void decrementMovesLeft() {
movesLeft--;
}
/** /** This method copies the board, mainly for AI use. */
* For AI use only. public TicTacToe copyBoard() {
*/ TicTacToe clone = new TicTacToe(players[0].name(), players[1].name());
public void decrementMovesLeft() { System.arraycopy(this.grid, 0, clone.grid, 0, this.grid.length);
movesLeft--; clone.movesLeft = this.movesLeft;
} clone.currentPlayer = this.currentPlayer;
return clone;
/** }
* This method copies the board, mainly for AI use.
*/
public TicTacToe copyBoard() {
TicTacToe clone = new TicTacToe(players[0].name(), players[1].name());
System.arraycopy(this.grid, 0, clone.grid, 0, this.grid.length);
clone.movesLeft = this.movesLeft;
clone.currentPlayer = this.currentPlayer;
return clone;
}
} }

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,10 +34,13 @@ 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 =
called.set(true); GlobalEventBus.subscribeAndRegister(
receivedMessage.set(e.getMessage()); TestEvent.class,
}); e -> {
called.set(true);
receivedMessage.set(e.getMessage());
});
assertTrue(EventRegistry.isReady(TestEvent.class)); assertTrue(EventRegistry.isReady(TestEvent.class));
assertTrue(meta.isReady()); assertTrue(meta.isReady());
@@ -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,34 +89,43 @@ 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());
} }
// TODO: Fix registry // TODO: Fix registry
// @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 //
// TestEvent event = new TestEvent("StoreTest"); // // Post the event
// GlobalEventBus.post(event); // TestEvent event = new TestEvent("StoreTest");
// // GlobalEventBus.post(event);
// // Retrieve the last stored EventEntry //
// EventRegistry.EventEntry<TestEvent> storedEntry = EventRegistry.getLastEvent(TestEvent.class); // // Retrieve the last stored EventEntry
// // EventRegistry.EventEntry<TestEvent> storedEntry =
// assertNotNull(storedEntry); // EventRegistry.getLastEvent(TestEvent.class);
// //
// // Compare the inner event // assertNotNull(storedEntry);
// TestEvent storedEvent = storedEntry.getEvent(); //
// assertEquals(event, storedEvent); // // Compare the inner event
// } // TestEvent storedEvent = storedEntry.getEvent();
// assertEquals(event, storedEvent);
// }
} }

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,11 +25,18 @@ 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;
int bestMove = ai.findBestMove(game); int bestMove = ai.findBestMove(game);
@@ -47,11 +51,18 @@ 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,11 +82,18 @@ 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,11 +104,18 @@ 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,14 +126,15 @@ 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 =
'X', 'O', 'X', new char[] {
'X', 'O', 'O', 'X', 'O', 'X',
'O', 'X', 'X' 'X', 'O', 'O',
}; 'O', 'X', 'X'
};
int score = ai.doMinimax(copy, 0, true); int score = ai.doMinimax(copy, 0, true);
assertEquals(0, score, "Draw should return 0 score"); assertEquals(0, score, "Draw should return 0 score");
} }
} }

View File

@@ -1,39 +1,39 @@
// 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.server.Server; // import org.toop.server.Server;
// //
// import static org.junit.jupiter.api.Assertions.*; // import static org.junit.jupiter.api.Assertions.*;
// //
// public class ServerTest { // public class ServerTest {
// //
// private Server server; // private Server server;
// //
// @BeforeEach // @BeforeEach
// public void setUp() { // public void setUp() {
// server = new Server("127.0.0.1", "8080"); // server = new Server("127.0.0.1", "8080");
// } // }
// //
// @Test // @Test
// public void testConstructorSetsValues() { // public void testConstructorSetsValues() {
// assertEquals("127.0.0.1", server.getIp()); // assertEquals("127.0.0.1", server.getIp());
// assertEquals("8080", server.getPort()); // assertEquals("8080", server.getPort());
// } // }
// //
// @Test // @Test
// public void testSetIpUpdatesValue() { // public void testSetIpUpdatesValue() {
// server.setIp("192.168.1.1"); // server.setIp("192.168.1.1");
// assertEquals("192.168.1.1", server.getIp()); // assertEquals("192.168.1.1", server.getIp());
// } // }
// //
// @Test // @Test
// public void testSetPortUpdatesValue() { // public void testSetPortUpdatesValue() {
// server.setPort("9090"); // server.setPort("9090");
// assertEquals("9090", server.getPort()); // assertEquals("9090", server.getPort());
// } // }
// //
// @Test // @Test
// public void testNotNullAfterConstruction() { // public void testNotNullAfterConstruction() {
// assertNotNull(server); // assertNotNull(server);
// } // }
// //
// } // }