diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/resourceBundles.xml b/.idea/resourceBundles.xml
deleted file mode 100644
index af8f6fc..0000000
--- a/.idea/resourceBundles.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- localization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- localization
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java
index fe06b87..64513d5 100644
--- a/app/src/main/java/org/toop/app/App.java
+++ b/app/src/main/java/org/toop/app/App.java
@@ -1,15 +1,8 @@
package org.toop.app;
-import java.util.Stack;
-import javafx.application.Application;
-import javafx.application.Platform;
-import javafx.scene.Scene;
-import javafx.scene.layout.StackPane;
-import javafx.stage.Stage;
-import org.toop.app.layer.Layer;
-import org.toop.app.layer.layers.MainLayer;
-import org.toop.app.layer.layers.QuitPopup;
-import org.toop.framework.audio.VolumeControl;
+import org.toop.app.view.ViewStack;
+import org.toop.app.view.views.MainView;
+import org.toop.app.view.views.QuitView;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.resource.ResourceManager;
@@ -17,154 +10,110 @@ import org.toop.framework.resource.resources.CssAsset;
import org.toop.local.AppContext;
import org.toop.local.AppSettings;
-public final class App extends Application {
- private static Stage stage;
- private static Scene scene;
- private static StackPane root;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+
+public final class App extends Application {
+ private static Stage stage;
+ private static Scene scene;
- private static Stack stack;
private static int height;
private static int width;
- private static boolean isQuitting;
+ private static boolean isQuitting;
- public static void run(String[] args) {
- launch(args);
- }
+ public static void run(String[] args) {
+ launch(args);
+ }
- @Override
- public void start(Stage stage) throws Exception {
+ @Override
+ public void start(Stage stage) throws Exception {
final StackPane root = new StackPane();
- final Scene scene = new Scene(root);
+ final Scene scene = new Scene(root);
+ ViewStack.setup(scene);
- stage.setTitle(AppContext.getString("appTitle"));
- stage.setWidth(1080);
- stage.setHeight(720);
+ stage.setTitle(AppContext.getString("app-title"));
+ stage.setWidth(1080);
+ stage.setHeight(720);
- stage.setOnCloseRequest(
- event -> {
- event.consume();
+ stage.setOnCloseRequest(event -> {
+ event.consume();
+ startQuit();
+ });
- if (!isQuitting) {
- quitPopup();
- }
- });
+ stage.setScene(scene);
+ stage.setResizable(false);
- stage.setScene(scene);
- stage.setResizable(false);
+ stage.show();
- stage.show();
+ App.stage = stage;
+ App.scene = scene;
- App.stage = stage;
- App.scene = scene;
- App.root = root;
+ App.width = (int)stage.getWidth();
+ App.height = (int)stage.getHeight();
- App.stack = new Stack<>();
+ App.isQuitting = false;
- App.width = (int) stage.getWidth();
- App.height = (int) stage.getHeight();
+ AppSettings.applySettings();
+ new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
- App.isQuitting = false;
+ ViewStack.push(new MainView());
+ }
- new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).postEvent();
+ public static void startQuit() {
+ if (isQuitting) {
+ return;
+ }
- final AppSettings settings = new AppSettings();
- settings.applySettings();
+ ViewStack.push(new QuitView());
+ isQuitting = true;
+ }
- activate(new MainLayer());
- }
+ public static void stopQuit() {
+ ViewStack.pop();
+ isQuitting = false;
+ }
- public static void activate(Layer layer) {
- Platform.runLater(
- () -> {
- popAll();
- push(layer);
- });
- }
+ public static void quit() {
+ ViewStack.cleanup();
+ stage.close();
+ }
- public static void push(Layer layer) {
- Platform.runLater(
- () -> {
- root.getChildren().addLast(layer.getLayer());
- stack.push(layer);
- });
- }
+ public static void reload() {
+ stage.setTitle(AppContext.getString("app-title"));
+ ViewStack.reload();
+ }
- public static void pop() {
- Platform.runLater(
- () -> {
- root.getChildren().removeLast();
- stack.pop();
+ public static void setFullscreen(boolean fullscreen) {
+ stage.setFullScreen(fullscreen);
- isQuitting = false;
- });
- }
+ width = (int) stage.getWidth();
+ height = (int) stage.getHeight();
- public static void popAll() {
- Platform.runLater(
- () -> {
- final int childrenCount = root.getChildren().size();
+ reload();
+ }
- for (int i = 0; i < childrenCount; i++) {
- try {
- root.getChildren().removeLast();
- } catch (Exception e) {
- IO.println(e); // TODO: Use logger
- }
- }
+ public static void setStyle(String theme, String layoutSize) {
+ final int stylesCount = scene.getStylesheets().size();
- stack.removeAllElements();
- });
- }
+ for (int i = 0; i < stylesCount; i++) {
+ scene.getStylesheets().removeLast();
+ }
- public static void quitPopup() {
- Platform.runLater(
- () -> {
- push(new QuitPopup());
- isQuitting = true;
- });
- }
+ scene.getStylesheets().add(ResourceManager.get("general.css").getUrl());
+ scene.getStylesheets().add(ResourceManager.get(theme + ".css").getUrl());
+ scene.getStylesheets().add(ResourceManager.get(layoutSize + ".css").getUrl());
- public static void quit() {
- new EventFlow().addPostEvent(new AudioEvents.StopAudioManager()).postEvent();
- stage.close();
- }
+ reload();
+ }
- public static void reloadAll() {
- stage.setTitle(AppContext.getString("appTitle"));
+ public static int getWidth() {
+ return width;
+ }
- for (final Layer layer : stack) {
- layer.reload();
- }
- }
-
- public static void setFullscreen(boolean fullscreen) {
- stage.setFullScreen(fullscreen);
-
- width = (int) stage.getWidth();
- height = (int) stage.getHeight();
-
- reloadAll();
- }
-
- public static void setStyle(String theme, String layoutSize) {
- final int stylesCount = scene.getStylesheets().size();
-
- for (int i = 0; i < stylesCount; i++) {
- scene.getStylesheets().removeLast();
- }
-
- scene.getStylesheets().add(ResourceManager.get(theme + ".css").getUrl());
- scene.getStylesheets().add(ResourceManager.get(layoutSize + ".css").getUrl());
-
- reloadAll();
- }
-
- public static int getWidth() {
- return width;
- }
-
- public static int getHeight() {
- return height;
- }
-}
+ public static int getHeight() {
+ return height;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/GameInformation.java b/app/src/main/java/org/toop/app/GameInformation.java
index 2a3e56f..5380003 100644
--- a/app/src/main/java/org/toop/app/GameInformation.java
+++ b/app/src/main/java/org/toop/app/GameInformation.java
@@ -1,10 +1,41 @@
package org.toop.app;
-public record GameInformation(
- String[] playerName,
- boolean[] isPlayerHuman,
- int[] computerDifficulty,
- int[] computerThinkTime,
- boolean isConnectionLocal,
- String serverIP,
- String serverPort) {}
+public class GameInformation {
+ public enum Type {
+ TICTACTOE,
+ REVERSI;
+
+ public static int playerCount(Type type) {
+ return switch (type) {
+ case TICTACTOE -> 2;
+ case REVERSI -> 2;
+ };
+ }
+
+ public static int maxDepth(Type type) {
+ return switch (type) {
+ case TICTACTOE -> 5; // Todo. 5 seems to always draw or win. could increase to 9 but that might affect performance
+ case REVERSI -> 10; // Todo. 10 is a guess. might be too slow or too bad.
+ };
+ }
+ }
+
+ public static class Player {
+ public String name = "";
+ public boolean isHuman = true;
+ public int computerDifficulty = 1;
+ public int computerThinkTime = 1;
+ }
+
+ public final Type type;
+ public final Player[] players;
+
+ public GameInformation(Type type) {
+ this.type = type;
+ players = new Player[Type.playerCount(type)];
+
+ for (int i = 0; i < players.length; i++) {
+ players[i] = new Player();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java
new file mode 100644
index 0000000..8ec565a
--- /dev/null
+++ b/app/src/main/java/org/toop/app/Server.java
@@ -0,0 +1,210 @@
+package org.toop.app;
+
+import org.toop.app.game.ReversiGame;
+import org.toop.app.game.TicTacToeGame;
+import org.toop.app.view.ViewStack;
+import org.toop.app.view.views.ChallengeView;
+import org.toop.app.view.views.ErrorView;
+import org.toop.app.view.views.OnlineView;
+import org.toop.app.view.views.SendChallengeView;
+import org.toop.app.view.views.ServerView;
+import org.toop.framework.eventbus.EventFlow;
+import org.toop.framework.networking.events.NetworkEvents;
+import org.toop.local.AppContext;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public final class Server {
+ private String user = "";
+
+ private long clientId = -1;
+ private List onlinePlayers = new CopyOnWriteArrayList();
+
+ private ServerView view;
+
+ private boolean isPolling = true;
+
+ public static GameInformation.Type gameToType(String game) {
+ if (game.equalsIgnoreCase("tic-tac-toe")) {
+ return GameInformation.Type.TICTACTOE;
+ } else if (game.equalsIgnoreCase("reversi")) {
+ return GameInformation.Type.REVERSI;
+ }
+
+ return null;
+ }
+
+ public Server(String ip, String port, String user) {
+ if (ip.split("\\.").length < 4) {
+ ViewStack.push(new ErrorView("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address")));
+ return;
+ }
+
+ int parsedPort = -1;
+
+ try {
+ parsedPort = Integer.parseInt(port);
+ } catch (NumberFormatException _) {
+ ViewStack.push(new ErrorView("\"" + port + "\" " + AppContext.getString("is-not-a-valid-port")));
+ return;
+ }
+
+ if (user.isEmpty()) {
+ ViewStack.push(new ErrorView(AppContext.getString("invalid-username")));
+ return;
+ }
+
+ new EventFlow()
+ .addPostEvent(NetworkEvents.StartClient.class, ip, parsedPort)
+ .onResponse(NetworkEvents.StartClientResponse.class, e -> {
+ this.user = user;
+ clientId = e.clientId();
+
+ new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, user)).postEvent();
+
+ view = new ServerView(user, this::sendChallenge, this::disconnect);
+ ViewStack.push(view);
+
+ startPopulateThread();
+ }).postEvent();
+
+ new EventFlow().listen(this::handleReceivedChallenge);
+ }
+
+ private void populatePlayerList() {
+ new EventFlow().listen(NetworkEvents.PlayerlistResponse.class, e -> {
+ if (e.clientId() == clientId) {
+ onlinePlayers = new ArrayList(List.of(e.playerlist()));
+ onlinePlayers.removeIf(name -> name.equalsIgnoreCase(user));
+
+ view.update(onlinePlayers);
+ }
+ });
+
+ final EventFlow sendGetPlayerList = new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(clientId));
+
+ while (isPolling) {
+ sendGetPlayerList.postEvent();
+
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException _) {}
+ }
+ }
+
+ private void sendChallenge(String opponent) {
+ if (!isPolling) {
+ return;
+ }
+
+ ViewStack.push(new SendChallengeView(this, opponent, (playerInformation, gameType) -> {
+ new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType))
+ .listen(NetworkEvents.GameMatchResponse.class, e -> {
+ if (e.clientId() == clientId) {
+ isPolling = false;
+ onlinePlayers.clear();
+
+ final GameInformation.Type type = gameToType(gameType);
+ final int myTurn = e.playerToMove().equalsIgnoreCase(e.opponent())? 1 : 0;
+
+ final GameInformation information = new GameInformation(type);
+ information.players[0] = playerInformation;
+ information.players[0].name = user;
+ information.players[1].name = opponent;
+
+ switch (type) {
+ case TICTACTOE: new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
+ case REVERSI: new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
+ }
+ }
+ }).postEvent();
+ }));
+ }
+
+ private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
+ if (!isPolling) {
+ return;
+ }
+
+ String challengerName = response.challengerName();
+ challengerName = challengerName.substring(challengerName.indexOf("\"") + 1);
+ challengerName = challengerName.substring(0, challengerName.indexOf("\""));
+
+ String gameType = response.gameType();
+ gameType = gameType.substring(gameType.indexOf("\"") + 1);
+ gameType = gameType.substring(0, gameType.indexOf("\""));
+
+ final String finalGameType = gameType;
+
+ ViewStack.push(new ChallengeView(challengerName, gameType, (playerInformation) -> {
+ final int challengeId = Integer.parseInt(response.challengeId().substring(18, response.challengeId().length() - 2));
+ new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientId, challengeId)).postEvent();
+
+ ViewStack.pop();
+
+ new EventFlow().listen(NetworkEvents.GameMatchResponse.class, e -> {
+ if (e.clientId() == clientId) {
+ isPolling = false;
+ onlinePlayers.clear();
+
+ final GameInformation.Type type = gameToType(finalGameType);
+ final int myTurn = e.playerToMove().equalsIgnoreCase(e.opponent())? 1 : 0;
+
+ final GameInformation information = new GameInformation(type);
+ information.players[0] = playerInformation;
+ information.players[0].name = user;
+ information.players[1].name = e.opponent();
+
+ switch (type) {
+ case TICTACTOE: new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
+ case REVERSI: new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
+ }
+ }
+ });
+ }));
+ }
+
+ private void sendMessage(String message) {
+ new EventFlow().addPostEvent(new NetworkEvents.SendMessage(clientId, message)).postEvent();
+ }
+
+ private void disconnect() {
+ new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent();
+ ViewStack.push(new OnlineView());
+ }
+
+ private void forfeitGame() {
+ new EventFlow().addPostEvent(new NetworkEvents.SendForfeit(clientId)).postEvent();
+ }
+
+ private void exitGame() {
+ forfeitGame();
+
+ ViewStack.push(view);
+ startPopulateThread();
+ }
+
+ private void startPopulateThread() {
+ isPolling = true;
+
+ final Thread populateThread = new Thread(this::populatePlayerList);
+ populateThread.setDaemon(false);
+ populateThread.start();
+ }
+
+ public List getGamesList() {
+ final List list = new ArrayList();
+ list.add("tic-tac-toe"); // Todo: get games list from server and check if the game is supported
+ list.add("reversi");
+
+ new EventFlow().addPostEvent(new NetworkEvents.SendGetGamelist(clientId))
+ .listen(NetworkEvents.GamelistResponse.class, e -> {
+ System.out.println(Arrays.toString(e.gamelist()));
+ }).postEvent();
+
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/canvas/GameCanvas.java b/app/src/main/java/org/toop/app/canvas/GameCanvas.java
index 5ed5775..52d0521 100644
--- a/app/src/main/java/org/toop/app/canvas/GameCanvas.java
+++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java
@@ -1,130 +1,122 @@
package org.toop.app.canvas;
-import java.util.function.Consumer;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
+import java.util.function.Consumer;
+
public abstract class GameCanvas {
- protected record Cell(float x, float y, float width, float height) {}
+ protected record Cell(float x, float y, float width, float height) {
+ public boolean isInside(double x, double y) {
+ return x >= this.x && x <= this.x + width &&
+ y >= this.y && y <= this.y + height;
+ }
+ }
- protected final Canvas canvas;
- protected final GraphicsContext graphics;
+ protected final Canvas canvas;
+ protected final GraphicsContext graphics;
- protected final Color color;
+ protected final Color color;
- protected int width;
- protected int height;
+ protected final int width;
+ protected final int height;
- protected final int rows;
- protected final int columns;
+ protected final int rowSize;
+ protected final int columnSize;
- protected final int gapSize;
- protected final boolean edges;
+ protected final int gapSize;
+ protected final boolean edges;
- protected final Cell[] cells;
+ protected final Cell[] cells;
- protected GameCanvas(
- Color color,
- int width,
- int height,
- int rows,
- int columns,
- int gapSize,
- boolean edges,
- Consumer onCellClicked) {
- canvas = new Canvas(width, height);
- graphics = canvas.getGraphicsContext2D();
+ protected GameCanvas(Color color, int width, int height, int rowSize, int columnSize, int gapSize, boolean edges, Consumer onCellClicked) {
+ canvas = new Canvas(width, height);
+ graphics = canvas.getGraphicsContext2D();
- this.color = color;
+ this.color = color;
- this.width = width;
- this.height = height;
+ this.width = width;
+ this.height = height;
- this.rows = rows;
- this.columns = columns;
+ this.rowSize = rowSize;
+ this.columnSize = columnSize;
- this.gapSize = gapSize;
- this.edges = edges;
+ this.gapSize = gapSize;
+ this.edges = edges;
- cells = new Cell[rows * columns];
+ cells = new Cell[rowSize * columnSize];
- final float cellWidth = ((float) width - (rows - 1) * gapSize) / rows;
- final float cellHeight = ((float) height - (columns - 1) * gapSize) / columns;
+ final float cellWidth = ((float)width - gapSize * rowSize - gapSize) / rowSize;
+ final float cellHeight = ((float)height - gapSize * columnSize - gapSize) / columnSize;
- for (int y = 0; y < columns; y++) {
- final float startY = y * cellHeight + y * gapSize;
+ for (int y = 0; y < columnSize; y++) {
+ final float startY = y * cellHeight + y * gapSize + gapSize;
- for (int x = 0; x < rows; x++) {
- final float startX = x * cellWidth + x * gapSize;
- cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight);
- }
- }
+ for (int x = 0; x < rowSize; x++) {
+ final float startX = x * cellWidth + x * gapSize + gapSize;
+ cells[x + y * rowSize] = new Cell(startX, startY, cellWidth, cellHeight);
+ }
+ }
- canvas.setOnMouseClicked(
- event -> {
- if (event.getButton() != MouseButton.PRIMARY) {
- return;
- }
+ canvas.setOnMouseClicked(event -> {
+ if (event.getButton() != MouseButton.PRIMARY) {
+ return;
+ }
- final int column = (int) ((event.getX() / width) * rows);
- final int row = (int) ((event.getY() / height) * columns);
+ final int column = (int)((event.getX() / this.width) * rowSize);
+ final int row = (int)((event.getY() / this.height) * columnSize);
- event.consume();
- onCellClicked.accept(row * rows + column);
- });
+ final Cell cell = cells[column + row * rowSize];
- render();
- }
+ if (cell.isInside(event.getX(), event.getY())) {
+ event.consume();
+ onCellClicked.accept(column + row * rowSize);
+ }
+ });
- public void clear() {
- graphics.clearRect(0, 0, width, height);
- }
+ render();
+ }
- public void render() {
- graphics.setFill(color);
+ public void clear() {
+ graphics.clearRect(0, 0, width, height);
+ }
- for (int x = 1; x < rows; x++) {
- graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height);
- }
+ public void render() {
+ graphics.setFill(color);
- for (int y = 1; y < columns; y++) {
- graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize);
- }
+ for (int x = 0; x < rowSize - 1; x++) {
+ final float start = cells[x].x + cells[x].width;
+ graphics.fillRect(start, gapSize, gapSize, height - gapSize * 2);
+ }
- if (edges) {
- graphics.fillRect(-gapSize, 0, gapSize, height);
- graphics.fillRect(0, -gapSize, width, gapSize);
+ for (int y = 0; y < columnSize - 1; y++) {
+ final float start = cells[y * rowSize].y + cells[y * rowSize].height;
+ graphics.fillRect(gapSize, start, width - gapSize * 2, gapSize);
+ }
- graphics.fillRect(width - gapSize, 0, gapSize, height);
- graphics.fillRect(0, height - gapSize, width, gapSize);
- }
- }
+ if (edges) {
+ graphics.fillRect(0, 0, width, gapSize);
+ graphics.fillRect(0, 0, gapSize, height);
- public void draw(Color color, int cell) {
- final float x = cells[cell].x() + gapSize;
- final float y = cells[cell].y() + gapSize;
+ graphics.fillRect(width - gapSize, 0, gapSize, height);
+ graphics.fillRect(0, height - gapSize, width, gapSize);
+ }
+ }
- final float width = cells[cell].width() - gapSize * 2;
- final float height = cells[cell].height() - gapSize * 2;
+ public void fill(Color color, int cell) {
+ final float x = cells[cell].x();
+ final float y = cells[cell].y();
- graphics.setFill(color);
- graphics.fillRect(x, y, width, height);
- }
+ final float width = cells[cell].width();
+ final float height = cells[cell].height();
- public void resize(int width, int height) {
- canvas.setWidth(width);
- canvas.setHeight(height);
+ graphics.setFill(color);
+ graphics.fillRect(x, y, width, height);
+ }
- this.width = width;
- this.height = height;
-
- clear();
- render();
- }
-
- public Canvas getCanvas() {
- return canvas;
- }
-}
+ public Canvas getCanvas() {
+ return canvas;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java
new file mode 100644
index 0000000..277937c
--- /dev/null
+++ b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java
@@ -0,0 +1,34 @@
+package org.toop.app.canvas;
+
+import javafx.scene.paint.Color;
+
+import java.util.function.Consumer;
+
+public final class ReversiCanvas extends GameCanvas {
+ public ReversiCanvas(Color color, int width, int height, Consumer onCellClicked) {
+ super(color, width, height, 8, 8, 10, true, onCellClicked);
+ drawStartingDots();
+ }
+
+ public void drawDot(Color color, int cell) {
+ final float x = cells[cell].x() + gapSize;
+ final float y = cells[cell].y() + gapSize;
+
+ final float width = cells[cell].width() - gapSize * 2;
+ final float height = cells[cell].height() - gapSize * 2;
+
+ graphics.setFill(color);
+ graphics.fillOval(x, y, width, height);
+ }
+
+ public void drawStartingDots() {
+ drawDot(Color.BLACK, 28);
+ drawDot(Color.WHITE, 36);
+ drawDot(Color.BLACK, 35);
+ drawDot(Color.WHITE, 27);
+ }
+
+ public void drawLegalPosition(int cell) {
+ drawDot(new Color(1.0f, 0.0f, 0.0f, 0.5f), cell);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
index 1838335..06d0b31 100644
--- a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
+++ b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
@@ -1,37 +1,38 @@
package org.toop.app.canvas;
-import java.util.function.Consumer;
import javafx.scene.paint.Color;
-public class TicTacToeCanvas extends GameCanvas {
- public TicTacToeCanvas(Color color, int width, int height, Consumer onCellClicked) {
- super(color, width, height, 3, 3, 10, false, onCellClicked);
- }
+import java.util.function.Consumer;
- public void drawX(Color color, int cell) {
- graphics.setStroke(color);
- graphics.setLineWidth(gapSize);
+public final class TicTacToeCanvas extends GameCanvas {
+ public TicTacToeCanvas(Color color, int width, int height, Consumer onCellClicked) {
+ super(color, width, height, 3, 3, 30, false, onCellClicked);
+ }
- final float x = cells[cell].x() + gapSize;
- final float y = cells[cell].y() + gapSize;
+ public void drawX(Color color, int cell) {
+ graphics.setStroke(color);
+ graphics.setLineWidth(gapSize);
- final float width = cells[cell].width() - gapSize * 2;
- final float height = cells[cell].height() - gapSize * 2;
+ final float x = cells[cell].x() + gapSize;
+ final float y = cells[cell].y() + gapSize;
- graphics.strokeLine(x, y, x + width, y + height);
- graphics.strokeLine(x + width, y, x, y + height);
- }
+ final float width = cells[cell].width() - gapSize * 2;
+ final float height = cells[cell].height() - gapSize * 2;
- public void drawO(Color color, int cell) {
- graphics.setStroke(color);
- graphics.setLineWidth(gapSize);
+ graphics.strokeLine(x, y, x + width, y + height);
+ graphics.strokeLine(x + width, y, x, y + height);
+ }
- final float x = cells[cell].x() + gapSize;
- final float y = cells[cell].y() + gapSize;
+ public void drawO(Color color, int cell) {
+ graphics.setStroke(color);
+ graphics.setLineWidth(gapSize);
- final float width = cells[cell].width() - gapSize * 2;
- final float height = cells[cell].height() - gapSize * 2;
+ final float x = cells[cell].x() + gapSize;
+ final float y = cells[cell].y() + gapSize;
- graphics.strokeOval(x, y, width, height);
- }
-}
+ final float width = cells[cell].width() - gapSize * 2;
+ final float height = cells[cell].height() - gapSize * 2;
+
+ graphics.strokeOval(x, y, width, height);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/game/ReversiGame.java b/app/src/main/java/org/toop/app/game/ReversiGame.java
new file mode 100644
index 0000000..18527fe
--- /dev/null
+++ b/app/src/main/java/org/toop/app/game/ReversiGame.java
@@ -0,0 +1,257 @@
+package org.toop.app.game;
+
+import org.toop.app.App;
+import org.toop.app.GameInformation;
+import org.toop.app.canvas.ReversiCanvas;
+import org.toop.app.view.ViewStack;
+import org.toop.app.view.views.GameView;
+import org.toop.app.view.views.LocalMultiplayerView;
+import org.toop.framework.eventbus.EventFlow;
+import org.toop.framework.networking.events.NetworkEvents;
+import org.toop.game.Game;
+import org.toop.game.reversi.Reversi;
+import org.toop.game.reversi.ReversiAI;
+
+import javafx.geometry.Pos;
+import javafx.scene.paint.Color;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+public final class ReversiGame {
+ private final GameInformation information;
+
+ private final int myTurn;
+ private final BlockingQueue moveQueue;
+
+ private final Reversi game;
+ private final ReversiAI ai;
+
+ private final GameView view;
+ private final ReversiCanvas canvas;
+
+ private final AtomicBoolean isRunning;
+
+ public ReversiGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage) {
+ this.information = information;
+
+ this.myTurn = myTurn;
+ moveQueue = new LinkedBlockingQueue();
+
+ game = new Reversi();
+ ai = new ReversiAI();
+
+ isRunning = new AtomicBoolean(true);
+
+ if (onForfeit == null || onExit == null) {
+ view = new GameView(null, () -> {
+ isRunning.set(false);
+ ViewStack.push(new LocalMultiplayerView(information));
+ }, null);
+ } else {
+ view = new GameView(onForfeit, () -> {
+ isRunning.set(false);
+ onExit.run();
+ }, onMessage);
+ }
+
+ canvas = new ReversiCanvas(Color.GRAY,
+ (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,
+ (cell) -> {
+ if (onForfeit == null || onExit == null) {
+ if (information.players[game.getCurrentTurn()].isHuman) {
+ final char value = game.getCurrentTurn() == 0? 'B' : 'W';
+
+ try {
+ moveQueue.put(new Game.Move(cell, value));
+ } catch (InterruptedException _) {}
+ }
+ } else {
+ if (information.players[0].isHuman) {
+ final char value = myTurn == 0? 'B' : 'W';
+
+ try {
+ moveQueue.put(new Game.Move(cell, value));
+ } catch (InterruptedException _) {}
+ }
+ }
+ });
+
+ view.add(Pos.CENTER, canvas.getCanvas());
+ ViewStack.push(view);
+
+ if (onForfeit == null || onExit == null) {
+ new Thread(this::localGameThread).start();
+ } else {
+ new EventFlow()
+ .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
+ .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse)
+ .listen(NetworkEvents.ReceivedMessage.class, this::onReceivedMessage);
+
+ setGameLabels(myTurn == 0);
+ }
+
+ updateCanvas();
+ }
+
+ public ReversiGame(GameInformation information) {
+ this(information, 0, null, null, null);
+ }
+
+ private void localGameThread() {
+ while (isRunning.get()) {
+ final int currentTurn = game.getCurrentTurn();
+ final char currentValue = currentTurn == 0? 'B' : 'W';
+ final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
+
+ view.nextPlayer(information.players[currentTurn].isHuman,
+ information.players[currentTurn].name,
+ String.valueOf(currentValue),
+ information.players[nextTurn].name);
+
+ Game.Move move = null;
+
+ if (information.players[currentTurn].isHuman) {
+ try {
+ final Game.Move wants = moveQueue.take();
+ final Game.Move[] legalMoves = game.getLegalMoves();
+
+ for (final Game.Move legalMove : legalMoves) {
+ if (legalMove.position() == wants.position() &&
+ legalMove.value() == wants.value()) {
+ move = wants;
+ break;
+ }
+ }
+ } catch (InterruptedException _) {}
+ } else {
+ final long start = System.currentTimeMillis();
+
+ move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty);
+
+ if (information.players[currentTurn].computerThinkTime > 0) {
+ final long elapsedTime = System.currentTimeMillis() - start;
+ final long sleepTime = information.players[currentTurn].computerThinkTime * 1000L - elapsedTime;
+
+ try {
+ Thread.sleep((long) (sleepTime * Math.random()));
+ } catch (InterruptedException _) {}
+ }
+ }
+
+ if (move == null) {
+ continue;
+ }
+
+ final Game.State state = game.play(move);
+ updateCanvas();
+
+ if (state != Game.State.NORMAL) {
+ if (state == Game.State.WIN) {
+ view.gameOver(true, information.players[currentTurn].name);
+ } else if (state == Game.State.DRAW) {
+ view.gameOver(false, "");
+ }
+
+ isRunning.set(false);
+ }
+ }
+ }
+
+ private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
+ if (!isRunning.get()) {
+ return;
+ }
+
+ char playerChar;
+
+ if (response.player().equalsIgnoreCase(information.players[0].name)) {
+ playerChar = myTurn == 0? 'B' : 'W';
+ } else {
+ playerChar = myTurn == 0? 'W' : 'B';
+ }
+
+ final Game.Move move = new Game.Move(Integer.parseInt(response.move()), playerChar);
+ final Game.State state = game.play(move);
+
+ if (state != Game.State.NORMAL) {
+ if (state == Game.State.WIN) {
+ if (response.player().equalsIgnoreCase(information.players[0].name)) {
+ view.gameOver(true, information.players[0].name);
+ } else {
+ view.gameOver(false, information.players[1].name);
+ }
+ } else if (state == Game.State.DRAW) {
+ view.gameOver(false, "");
+ }
+ }
+
+ updateCanvas();
+ setGameLabels(game.getCurrentTurn() == myTurn);
+ }
+
+ private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) {
+ if (!isRunning.get()) {
+ return;
+ }
+
+ moveQueue.clear();
+
+ int position = -1;
+
+ if (information.players[0].isHuman) {
+ try {
+ position = moveQueue.take().position();
+ } catch (InterruptedException _) {}
+ } else {
+ final Game.Move move = ai.findBestMove(game, information.players[0].computerDifficulty);
+
+ assert move != null;
+ position = move.position();
+ }
+
+ new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) position))
+ .postEvent();
+ }
+
+ private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
+ if (!isRunning.get()) {
+ return;
+ }
+
+ view.updateChat(msg.message());
+ }
+
+ private void updateCanvas() {
+ // Todo: this is very inefficient. still very fast but if the grid is bigger it might cause issues. improve.
+
+ canvas.clear();
+ canvas.render();
+
+ for (int i = 0; i < game.board.length; i++) {
+ if (game.board[i] == 'B') {
+ canvas.drawDot(Color.BLACK, i);
+ } else if (game.board[i] == 'W') {
+ canvas.drawDot(Color.WHITE, i);
+ }
+ }
+
+ final Game.Move[] legalMoves = game.getLegalMoves();
+
+ for (final Game.Move legalMove : legalMoves) {
+ canvas.drawLegalPosition(legalMove.position());
+ }
+ }
+
+ private void setGameLabels(boolean isMe) {
+ final int currentTurn = game.getCurrentTurn();
+ final char currentValue = currentTurn == 0? 'B' : 'W';
+
+ view.nextPlayer(isMe,
+ information.players[isMe? 0 : 1].name,
+ String.valueOf(currentValue),
+ information.players[isMe? 1 : 0].name);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/game/TicTacToeGame.java b/app/src/main/java/org/toop/app/game/TicTacToeGame.java
new file mode 100644
index 0000000..cddef0a
--- /dev/null
+++ b/app/src/main/java/org/toop/app/game/TicTacToeGame.java
@@ -0,0 +1,244 @@
+package org.toop.app.game;
+
+import org.toop.app.App;
+import org.toop.app.GameInformation;
+import org.toop.app.canvas.TicTacToeCanvas;
+import org.toop.app.view.ViewStack;
+import org.toop.app.view.views.GameView;
+import org.toop.app.view.views.LocalMultiplayerView;
+import org.toop.framework.eventbus.EventFlow;
+import org.toop.framework.networking.events.NetworkEvents;
+import org.toop.game.Game;
+import org.toop.game.tictactoe.TicTacToe;
+import org.toop.game.tictactoe.TicTacToeAI;
+
+import javafx.geometry.Pos;
+import javafx.scene.paint.Color;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+public final class TicTacToeGame {
+ private final GameInformation information;
+
+ private final int myTurn;
+ private final BlockingQueue moveQueue;
+
+ private final TicTacToe game;
+ private final TicTacToeAI ai;
+
+ private final GameView view;
+ private final TicTacToeCanvas canvas;
+
+ private AtomicBoolean isRunning;
+
+ public TicTacToeGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage) {
+ this.information = information;
+
+ this.myTurn = myTurn;
+ moveQueue = new LinkedBlockingQueue();
+
+ game = new TicTacToe();
+ ai = new TicTacToeAI();
+
+ isRunning = new AtomicBoolean(true);
+
+ if (onForfeit == null || onExit == null) {
+ view = new GameView(null, () -> {
+ isRunning.set(false);
+ ViewStack.push(new LocalMultiplayerView(information));
+ }, null);
+ } else {
+ view = new GameView(onForfeit, () -> {
+ isRunning.set(false);
+ onExit.run();
+ }, onMessage);
+ }
+
+ canvas = new TicTacToeCanvas(Color.GRAY,
+ (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,
+ (cell) -> {
+ if (onForfeit == null || onExit == null) {
+ if (information.players[game.getCurrentTurn()].isHuman) {
+ final char value = game.getCurrentTurn() == 0? 'X' : 'O';
+
+ try {
+ moveQueue.put(new Game.Move(cell, value));
+ } catch (InterruptedException _) {}
+ }
+ } else {
+ if (information.players[0].isHuman) {
+ final char value = myTurn == 0? 'X' : 'O';
+
+ try {
+ moveQueue.put(new Game.Move(cell, value));
+ } catch (InterruptedException _) {}
+ }
+ }
+ });
+
+ view.add(Pos.CENTER, canvas.getCanvas());
+ ViewStack.push(view);
+
+ if (onForfeit == null || onExit == null) {
+ new Thread(this::localGameThread).start();
+ } else {
+ new EventFlow()
+ .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
+ .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse)
+ .listen(NetworkEvents.ReceivedMessage.class, this::onReceivedMessage);
+
+ setGameLabels(myTurn == 0);
+ }
+ }
+
+ public TicTacToeGame(GameInformation information) {
+ this(information, 0, null, null, null);
+ }
+
+ private void localGameThread() {
+ while (isRunning.get()) {
+ final int currentTurn = game.getCurrentTurn();
+ final char currentValue = currentTurn == 0? 'X' : 'O';
+ final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
+
+ view.nextPlayer(information.players[currentTurn].isHuman,
+ information.players[currentTurn].name,
+ String.valueOf(currentValue),
+ information.players[nextTurn].name);
+
+ Game.Move move = null;
+
+ if (information.players[currentTurn].isHuman) {
+ try {
+ final Game.Move wants = moveQueue.take();
+ final Game.Move[] legalMoves = game.getLegalMoves();
+
+ for (final Game.Move legalMove : legalMoves) {
+ if (legalMove.position() == wants.position() &&
+ legalMove.value() == wants.value()) {
+ move = wants;
+ break;
+ }
+ }
+ } catch (InterruptedException _) {}
+ } else {
+ final long start = System.currentTimeMillis();
+
+ move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty);
+
+ if (information.players[currentTurn].computerThinkTime > 0) {
+ final long elapsedTime = System.currentTimeMillis() - start;
+ final long sleepTime = information.players[currentTurn].computerThinkTime * 1000L - elapsedTime;
+
+ try {
+ Thread.sleep((long)(sleepTime * Math.random()));
+ } catch (InterruptedException _) {}
+ }
+ }
+
+ if (move == null) {
+ continue;
+ }
+
+ final Game.State state = game.play(move);
+
+ if (move.value() == 'X') {
+ canvas.drawX(Color.INDIANRED, move.position());
+ } else if (move.value() == 'O') {
+ canvas.drawO(Color.ROYALBLUE, move.position());
+ }
+
+ if (state != Game.State.NORMAL) {
+ if (state == Game.State.WIN) {
+ view.gameOver(true, information.players[currentTurn].name);
+ } else if (state == Game.State.DRAW) {
+ view.gameOver(false, "");
+ }
+
+ isRunning.set(false);
+ }
+ }
+ }
+
+ private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
+ if (!isRunning.get()) {
+ return;
+ }
+
+ char playerChar;
+
+ if (response.player().equalsIgnoreCase(information.players[0].name)) {
+ playerChar = myTurn == 0? 'X' : 'O';
+ } else {
+ playerChar = myTurn == 0? 'O' : 'X';
+ }
+
+ final Game.Move move = new Game.Move(Integer.parseInt(response.move()), playerChar);
+ final Game.State state = game.play(move);
+
+ if (state != Game.State.NORMAL) {
+ if (state == Game.State.WIN) {
+ if (response.player().equalsIgnoreCase(information.players[0].name)) {
+ view.gameOver(true, information.players[0].name);
+ } else {
+ view.gameOver(false, information.players[1].name);
+ }
+ } else if (state == Game.State.DRAW) {
+ view.gameOver(false, "");
+ }
+ }
+
+ if (move.value() == 'X') {
+ canvas.drawX(Color.RED, move.position());
+ } else if (move.value() == 'O') {
+ canvas.drawO(Color.BLUE, move.position());
+ }
+
+ setGameLabels(game.getCurrentTurn() == myTurn);
+ }
+
+ private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) {
+ if (!isRunning.get()) {
+ return;
+ }
+
+ moveQueue.clear();
+
+ int position = -1;
+
+ if (information.players[0].isHuman) {
+ try {
+ position = moveQueue.take().position();
+ } catch (InterruptedException _) {}
+ } else {
+ final Game.Move move = ai.findBestMove(game, information.players[0].computerDifficulty);
+
+ assert move != null;
+ position = move.position();
+ }
+
+ new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position))
+ .postEvent();
+ }
+
+ private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
+ if (!isRunning.get()) {
+ return;
+ }
+
+ view.updateChat(msg.message());
+ }
+
+ private void setGameLabels(boolean isMe) {
+ final int currentTurn = game.getCurrentTurn();
+ final char currentValue = currentTurn == 0? 'X' : 'O';
+
+ view.nextPlayer(isMe,
+ information.players[isMe? 0 : 1].name,
+ String.valueOf(currentValue),
+ information.players[isMe? 1 : 0].name);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/layer/Container.java b/app/src/main/java/org/toop/app/layer/Container.java
deleted file mode 100644
index 89e6436..0000000
--- a/app/src/main/java/org/toop/app/layer/Container.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.toop.app.layer;
-
-import javafx.scene.Node;
-import javafx.scene.layout.Region;
-
-public abstract class Container {
- public abstract Region getContainer();
-
- public abstract void addNodes(Node... nodes);
-
- public abstract void addContainer(Container container, boolean fill);
-}
diff --git a/app/src/main/java/org/toop/app/layer/Layer.java b/app/src/main/java/org/toop/app/layer/Layer.java
deleted file mode 100644
index d357200..0000000
--- a/app/src/main/java/org/toop/app/layer/Layer.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.toop.app.layer;
-
-import javafx.geometry.Pos;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.StackPane;
-import org.toop.app.App;
-import org.toop.app.canvas.GameCanvas;
-
-public abstract class Layer {
- protected StackPane layer;
- protected Region background;
-
- protected Layer(String... backgroundStyles) {
- layer = new StackPane();
-
- background = new Region();
- background.getStyleClass().addAll(backgroundStyles);
- background.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
-
- layer.getChildren().addLast(background);
- }
-
- protected void addContainer(
- Container container,
- Pos position,
- int xOffset,
- int yOffset,
- int widthPercent,
- int heightPercent) {
- StackPane.setAlignment(container.getContainer(), position);
-
- final double widthUnit = App.getWidth() / 100.0;
- final double heightUnit = App.getHeight() / 100.0;
-
- if (widthPercent > 0) {
- container.getContainer().setMaxWidth(widthPercent * widthUnit);
- } else {
- container.getContainer().setMaxWidth(Region.USE_PREF_SIZE);
- }
-
- if (heightPercent > 0) {
- container.getContainer().setMaxHeight(heightPercent * heightUnit);
- } else {
- container.getContainer().setMaxHeight(Region.USE_PREF_SIZE);
- }
-
- container.getContainer().setTranslateX(xOffset * widthUnit);
- container.getContainer().setTranslateY(yOffset * heightUnit);
-
- layer.getChildren().addLast(container.getContainer());
- }
-
- protected void addGameCanvas(GameCanvas canvas, Pos position, int xOffset, int yOffset) {
- StackPane.setAlignment(canvas.getCanvas(), position);
-
- final double widthUnit = App.getWidth() / 100.0;
- final double heightUnit = App.getHeight() / 100.0;
-
- canvas.getCanvas().setTranslateX(xOffset * widthUnit);
- canvas.getCanvas().setTranslateY(yOffset * heightUnit);
-
- layer.getChildren().addLast(canvas.getCanvas());
- }
-
- protected void pop() {
- if (layer.getChildren().size() <= 1) {
- return;
- }
-
- layer.getChildren().removeLast();
- }
-
- protected void popAll() {
- final int containers = layer.getChildren().size();
-
- for (int i = 1; i < containers; i++) {
- layer.getChildren().removeLast();
- }
- }
-
- public StackPane getLayer() {
- return layer;
- }
-
- public abstract void reload();
-}
diff --git a/app/src/main/java/org/toop/app/layer/NodeBuilder.java b/app/src/main/java/org/toop/app/layer/NodeBuilder.java
deleted file mode 100644
index b55a70d..0000000
--- a/app/src/main/java/org/toop/app/layer/NodeBuilder.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package org.toop.app.layer;
-
-import java.util.function.Consumer;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.geometry.Orientation;
-import javafx.scene.Node;
-import javafx.scene.control.*;
-import javafx.scene.text.Text;
-import org.toop.framework.audio.events.AudioEvents;
-import org.toop.framework.eventbus.EventFlow;
-
-public final class NodeBuilder {
- public static void addCss(Node node, String... cssClasses) {
- node.getStyleClass().addAll(cssClasses);
- }
-
- public static void setCss(Node node, String... cssClasses) {
- node.getStyleClass().removeAll();
- node.getStyleClass().addAll(cssClasses);
- }
-
- public static Text header(String x) {
- final Text element = new Text(x);
- setCss(element, "text-primary", "text-header");
-
- return element;
- }
-
- public static Text text(String x) {
- final Text element = new Text(x);
- setCss(element, "text-secondary", "text-normal");
-
- return element;
- }
-
- public static Label button(String x, Runnable runnable) {
- final Label element = new Label(x);
- setCss(element, "button", "text-normal");
-
- element.setOnMouseClicked(
- _ -> {
- new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
- runnable.run();
- });
-
- return element;
- }
-
- public static Label toggle(String x1, String x2, boolean toggled, Consumer consumer) {
- final Label element = new Label(toggled ? x2 : x1);
- setCss(element, "toggle", "text-normal");
-
- final BooleanProperty checked = new SimpleBooleanProperty(toggled);
-
- element.setOnMouseClicked(
- _ -> {
- new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
- checked.set(!checked.get());
-
- if (checked.get()) {
- element.setText(x1);
- } else {
- element.setText(x2);
- }
-
- consumer.accept(checked.get());
- });
-
- return element;
- }
-
- public static Slider slider(int max, int initial, Consumer consumer) {
- final Slider element = new Slider(0, max, initial);
- setCss(element, "bg-slider-track");
-
- element.setMinorTickCount(0);
- element.setMajorTickUnit(1);
- element.setBlockIncrement(1);
-
- element.setSnapToTicks(true);
- element.setShowTickLabels(true);
-
- element.setOnMouseClicked(
- _ -> {
- new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
- });
-
- element.valueProperty()
- .addListener(
- (_, _, newValue) -> {
- consumer.accept(newValue.intValue());
- });
-
- return element;
- }
-
- public static TextField input(String x, Consumer consumer) {
- final TextField element = new TextField(x);
- setCss(element, "input", "text-normal");
-
- element.setOnMouseClicked(
- _ -> {
- new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
- });
-
- element.textProperty()
- .addListener(
- (_, _, newValue) -> {
- consumer.accept(newValue);
- });
-
- return element;
- }
-
- public static ChoiceBox choiceBox(Consumer consumer) {
- final ChoiceBox element = new ChoiceBox<>();
- setCss(element, "choice-box", "text-normal");
-
- element.setOnMouseClicked(
- _ -> {
- new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
- });
-
- element.valueProperty()
- .addListener(
- (_, _, newValue) -> {
- consumer.accept(newValue);
- });
-
- return element;
- }
-
- public static Separator separator() {
- final Separator element = new Separator(Orientation.HORIZONTAL);
- setCss(element, "separator");
-
- return element;
- }
-}
diff --git a/app/src/main/java/org/toop/app/layer/Popup.java b/app/src/main/java/org/toop/app/layer/Popup.java
deleted file mode 100644
index 7e498df..0000000
--- a/app/src/main/java/org/toop/app/layer/Popup.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.toop.app.layer;
-
-import org.toop.app.App;
-
-public abstract class Popup extends Layer {
- protected Popup(boolean popOnBackground, String... backgroundStyles) {
- super(backgroundStyles);
-
- if (popOnBackground) {
- background.setOnMouseClicked(
- _ -> {
- App.pop();
- });
- }
- }
-
- protected Popup(boolean popOnBackground) {
- this(popOnBackground, "bg-popup");
- }
-}
diff --git a/app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java b/app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java
deleted file mode 100644
index 2350216..0000000
--- a/app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.toop.app.layer.containers;
-
-import javafx.collections.ObservableList;
-import javafx.scene.Node;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.Region;
-import org.toop.app.layer.Container;
-
-public final class HorizontalContainer extends Container {
- private final HBox container;
-
- public HorizontalContainer(int spacing, String... cssClasses) {
- container = new HBox(spacing);
- container.getStyleClass().addAll(cssClasses);
- }
-
- public HorizontalContainer(int spacing) {
- this(spacing, "container");
- }
-
- @Override
- public Region getContainer() {
- return container;
- }
-
- @Override
- public void addNodes(Node... nodes) {
- container.getChildren().addAll(nodes);
- }
-
- @Override
- public void addContainer(Container container, boolean fill) {
- if (fill) {
- container.getContainer().setMinSize(0, 0);
- container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
- HBox.setHgrow(container.getContainer(), Priority.ALWAYS);
- } else {
- container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
- }
-
- this.container.getChildren().add(container.getContainer());
-
- if (fill) {
- balanceChildWidths();
- }
- }
-
- private void balanceChildWidths() {
- final ObservableList children = container.getChildren();
- final double widthPerChild = container.getWidth() / children.size();
-
- for (final Node child : children) {
- if (child instanceof Region) {
- ((Region) child).setPrefWidth(widthPerChild);
- }
- }
- }
-}
diff --git a/app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java b/app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java
deleted file mode 100644
index 56d610c..0000000
--- a/app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.toop.app.layer.containers;
-
-import javafx.collections.ObservableList;
-import javafx.scene.Node;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.VBox;
-import org.toop.app.layer.Container;
-
-public final class VerticalContainer extends Container {
- private final VBox container;
-
- public VerticalContainer(int spacing, String... cssClasses) {
- container = new VBox(spacing);
- container.getStyleClass().addAll(cssClasses);
- }
-
- public VerticalContainer(int spacing) {
- this(spacing, "container");
- }
-
- @Override
- public Region getContainer() {
- return container;
- }
-
- @Override
- public void addNodes(Node... nodes) {
- container.getChildren().addAll(nodes);
- }
-
- @Override
- public void addContainer(Container container, boolean fill) {
- if (fill) {
- container.getContainer().setMinSize(0, 0);
- container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
- VBox.setVgrow(container.getContainer(), Priority.ALWAYS);
- } else {
- container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
- }
-
- this.container.getChildren().add(container.getContainer());
-
- if (fill) {
- balanceChildHeights();
- }
- }
-
- private void balanceChildHeights() {
- final ObservableList children = container.getChildren();
- final double heightPerChild = container.getHeight() / children.size();
-
- for (final Node child : children) {
- if (child instanceof Region) {
- ((Region) child).setPrefHeight(heightPerChild);
- }
- }
- }
-}
diff --git a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
index c631932..e69de29 100644
--- a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
+++ b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
@@ -1,237 +0,0 @@
-package org.toop.app.layer.layers;
-
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-import javafx.application.Platform;
-import javafx.geometry.Pos;
-import javafx.scene.control.Label;
-import javafx.scene.control.ListView;
-import org.toop.app.App;
-import org.toop.app.GameInformation;
-import org.toop.app.layer.Container;
-import org.toop.app.layer.Layer;
-import org.toop.app.layer.NodeBuilder;
-import org.toop.app.layer.Popup;
-import org.toop.app.layer.containers.HorizontalContainer;
-import org.toop.app.layer.containers.VerticalContainer;
-import org.toop.app.layer.layers.game.TicTacToeLayer;
-import org.toop.framework.eventbus.EventFlow;
-import org.toop.framework.networking.clients.TournamentNetworkingClient;
-import org.toop.framework.networking.events.NetworkEvents;
-import org.toop.local.AppContext;
-
-public final class ConnectedLayer extends Layer {
- private static Timer pollTimer = new Timer();
-
- private static class ChallengePopup extends Popup {
- private final GameInformation information;
-
- private final String challenger;
- private final String game;
-
- private final long clientID;
- private final int challengeID;
-
- public ChallengePopup(
- GameInformation information,
- String challenger,
- String game,
- long clientID,
- String challengeID) {
- super(false, "bg-popup");
-
- this.information = information;
-
- this.challenger = challenger;
- this.game = game;
-
- this.clientID = clientID;
- this.challengeID =
- Integer.parseInt(challengeID.substring(18, challengeID.length() - 2));
-
- reload();
- }
-
- @Override
- public void reload() {
- popAll();
-
- final var challengeText = NodeBuilder.header(AppContext.getString("challengeText"));
- final var challengerNameText = NodeBuilder.header(challenger);
-
- final var gameText = NodeBuilder.text(AppContext.getString("gameIsText"));
- final var gameNameText = NodeBuilder.text(game);
-
- final var acceptButton =
- NodeBuilder.button(
- AppContext.getString("accept"),
- () -> {
- pollTimer.cancel();
-
- new EventFlow()
- .addPostEvent(
- new NetworkEvents.SendAcceptChallenge(
- clientID, challengeID))
- .postEvent();
- App.activate(new TicTacToeLayer(information, clientID));
- });
-
- final var denyButton =
- NodeBuilder.button(
- AppContext.getString("deny"),
- () -> {
- App.pop();
- });
-
- final Container controlContainer = new HorizontalContainer(30);
- controlContainer.addNodes(acceptButton, denyButton);
-
- final Container mainContainer = new VerticalContainer(30);
- mainContainer.addNodes(challengeText, challengerNameText);
- mainContainer.addNodes(gameText, gameNameText);
-
- mainContainer.addContainer(controlContainer, false);
-
- addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30);
- }
- }
-
- GameInformation information;
- long clientId;
- String user;
- List onlinePlayers = new CopyOnWriteArrayList<>();
-
- public ConnectedLayer(GameInformation information) {
- super("bg-primary");
-
- this.information = information;
-
- new EventFlow()
- .addPostEvent(
- NetworkEvents.StartClient.class,
- new TournamentNetworkingClient(),
- information.serverIP(),
- Integer.parseInt(information.serverPort()))
- .onResponse(
- NetworkEvents.StartClientResponse.class,
- e -> {
- clientId = e.clientId();
- user = information.playerName()[0].replaceAll("\\s+", "");
-
- new EventFlow()
- .addPostEvent(
- new NetworkEvents.SendLogin(this.clientId, this.user))
- .postEvent();
-
- Thread popThread = new Thread(this::populatePlayerList);
- popThread.setDaemon(false);
- popThread.start();
- })
- .postEvent();
-
- new EventFlow().listen(this::handleReceivedChallenge);
-
- reload();
- }
-
- private void populatePlayerList() {
- EventFlow sendGetPlayerList =
- new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId));
- new EventFlow()
- .listen(
- NetworkEvents.PlayerlistResponse.class,
- e -> {
- if (e.clientId() == this.clientId) {
- List playerList =
- new java.util.ArrayList<>(
- List.of(e.playerlist())); // TODO: Garbage,
- // but works
- playerList.removeIf(name -> name.equalsIgnoreCase(user));
- if (this.onlinePlayers != playerList) {
- this.onlinePlayers.clear();
- this.onlinePlayers.addAll(playerList);
- }
- }
- });
-
- TimerTask task =
- new TimerTask() {
- public void run() {
- sendGetPlayerList.postEvent();
- Platform.runLater(() -> reload());
- }
- };
-
- pollTimer.schedule(task, 0L, 5000L); // TODO: Block app exit, fix later
- }
-
- private void sendChallenge(String oppUsername, String gameType) {
- final AtomicInteger challengeId = new AtomicInteger(-1);
-
- if (onlinePlayers.contains(oppUsername)) {
- new EventFlow()
- .addPostEvent(
- new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType))
- .listen(
- NetworkEvents.ChallengeResponse.class,
- e -> {
- challengeId.set(
- Integer.parseInt(
- e.challengeId()
- .substring(
- 18, e.challengeId().length() - 2)));
- })
- .listen(
- NetworkEvents.GameMatchResponse.class,
- e -> {
- if (e.clientId() == this.clientId) {
- pollTimer.cancel();
- App.activate(new TicTacToeLayer(information, this.clientId));
- }
- },
- false)
- .postEvent();
- // ^
- // |
- // |
- // |
- }
- }
-
- private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
- App.push(
- new ChallengePopup(
- information,
- response.challengerName(),
- response.gameType(),
- clientId,
- response.challengeId()));
- }
-
- @Override
- public void reload() {
- popAll();
-
- ListView