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 38a4873..96980b1 100644
--- a/app/src/main/java/org/toop/app/App.java
+++ b/app/src/main/java/org/toop/app/App.java
@@ -1,29 +1,24 @@
package org.toop.app;
-import javafx.application.Platform;
-import org.toop.app.layer.Layer;
-import org.toop.app.layer.layers.MainLayer;
-import org.toop.app.layer.layers.QuitPopup;
+import org.toop.app.view.ViewStack;
+import org.toop.app.view.views.MainView;
+import org.toop.app.view.views.QuitView;
import org.toop.framework.asset.ResourceManager;
import org.toop.framework.asset.resources.CssAsset;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext;
+import org.toop.local.AppSettings;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
-import org.toop.local.AppSettings;
-
-import java.util.Stack;
public final class App extends Application {
private static Stage stage;
private static Scene scene;
- private static StackPane root;
- private static Stack stack;
private static int height;
private static int width;
@@ -37,17 +32,15 @@ public final class App extends Application {
public void start(Stage stage) throws Exception {
final StackPane root = new StackPane();
final Scene scene = new Scene(root);
+ ViewStack.setup(scene);
- stage.setTitle(AppContext.getString("appTitle"));
+ stage.setTitle(AppContext.getString("app-title"));
stage.setWidth(1080);
stage.setHeight(720);
stage.setOnCloseRequest(event -> {
event.consume();
-
- if (!isQuitting) {
- quitPopup();
- }
+ startQuit();
});
stage.setScene(scene);
@@ -57,78 +50,40 @@ public final class App extends Application {
App.stage = stage;
App.scene = scene;
- App.root = root;
- App.stack = new Stack<>();
-
- App.width = (int) stage.getWidth();
- App.height = (int) stage.getHeight();
+ App.width = (int)stage.getWidth();
+ App.height = (int)stage.getHeight();
App.isQuitting = false;
- final AppSettings settings = new AppSettings();
- settings.applySettings();
-
+ AppSettings.applySettings();
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
- activate(new MainLayer());
+
+ ViewStack.push(new MainView());
}
- public static void activate(Layer layer) {
- Platform.runLater(() -> {
- popAll();
- push(layer);
- });
+ public static void startQuit() {
+ if (isQuitting) {
+ return;
+ }
+
+ ViewStack.push(new QuitView());
+ isQuitting = true;
}
- public static void push(Layer layer) {
- Platform.runLater(() -> {
- root.getChildren().addLast(layer.getLayer());
- stack.push(layer);
- });
- }
-
- public static void pop() {
- Platform.runLater(() -> {
- root.getChildren().removeLast();
- stack.pop();
-
- isQuitting = false;
- });
- }
-
- public static void popAll() {
- Platform.runLater(() -> {
- final int childrenCount = root.getChildren().size();
-
- for (int i = 0; i < childrenCount; i++) {
- try {
- root.getChildren().removeLast();
- } catch (Exception e) {
- IO.println(e);
- }
- }
-
- stack.removeAllElements();
- });
- }
-
- public static void quitPopup() {
- Platform.runLater(() -> {
- push(new QuitPopup());
- isQuitting = true;
- });
+ public static void stopQuit() {
+ ViewStack.pop();
+ isQuitting = false;
}
public static void quit() {
+ ViewStack.cleanup();
stage.close();
}
- public static void reloadAll() {
- stage.setTitle(AppContext.getString("appTitle"));
-
- for (final Layer layer : stack) {
- layer.reload();
- }
+ public static void reload() {
+ stage.setTitle(AppContext.getString("app-title"));
+ ViewStack.reload();
}
public static void setFullscreen(boolean fullscreen) {
@@ -137,7 +92,7 @@ public final class App extends Application {
width = (int) stage.getWidth();
height = (int) stage.getHeight();
- reloadAll();
+ reload();
}
public static void setStyle(String theme, String layoutSize) {
@@ -147,10 +102,11 @@ public final class App extends Application {
scene.getStylesheets().removeLast();
}
+ scene.getStylesheets().add(ResourceManager.get("general.css").getUrl());
scene.getStylesheets().add(ResourceManager.get(theme + ".css").getUrl());
scene.getStylesheets().add(ResourceManager.get(layoutSize + ".css").getUrl());
- reloadAll();
+ reload();
}
public static int getWidth() {
diff --git a/app/src/main/java/org/toop/app/GameInformation.java b/app/src/main/java/org/toop/app/GameInformation.java
index fd7c56e..0a847fb 100644
--- a/app/src/main/java/org/toop/app/GameInformation.java
+++ b/app/src/main/java/org/toop/app/GameInformation.java
@@ -1,6 +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;
+ case REVERSI -> 0; // Todo
+ };
+ }
+ }
+
+ public static class Player {
+ public String name = "";
+ public boolean isHuman = true;
+ public int computerDifficulty = 0;
+ 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..f3ec468
--- /dev/null
+++ b/app/src/main/java/org/toop/app/Server.java
@@ -0,0 +1,188 @@
+package org.toop.app;
+
+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.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.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) {
+ 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;
+
+ new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame);
+ }
+ }).postEvent();
+ }));
+ }
+
+ private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
+ 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);
+ break;
+ case REVERSI:
+ break;
+ }
+ }
+ });
+ }));
+ }
+
+ private void disconnect() {
+ // Todo
+ }
+
+ private void forfeitGame() {
+ // Todo
+ }
+
+ 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
+
+ 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 5d4b56d..b742be7 100644
--- a/app/src/main/java/org/toop/app/canvas/GameCanvas.java
+++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java
@@ -16,8 +16,8 @@ public abstract class GameCanvas {
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;
@@ -28,6 +28,9 @@ public abstract class GameCanvas {
protected final Cell[] cells;
protected GameCanvas(Color color, int width, int height, int rows, int columns, int gapSize, boolean edges, Consumer onCellClicked) {
+ width += gapSize * 2;
+ height += gapSize * 2;
+
canvas = new Canvas(width, height);
graphics = canvas.getGraphicsContext2D();
@@ -44,14 +47,14 @@ public abstract class GameCanvas {
cells = new Cell[rows * columns];
- final float cellWidth = ((float) width - (rows - 1) * gapSize) / rows;
- final float cellHeight = ((float) height - (columns - 1) * gapSize) / columns;
+ final float cellWidth = ((float) width - gapSize * rows) / rows;
+ final float cellHeight = ((float) height - gapSize * columns) / columns;
for (int y = 0; y < columns; y++) {
- final float startY = y * cellHeight + y * gapSize;
+ final float startY = gapSize + y * cellHeight + y * gapSize;
for (int x = 0; x < rows; x++) {
- final float startX = x * cellWidth + x * gapSize;
+ final float startX = gapSize + x * cellWidth + x * gapSize;
cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight);
}
}
@@ -61,11 +64,11 @@ public abstract class GameCanvas {
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) * rows);
+ final int row = (int)((event.getY() / this.height) * columns);
event.consume();
- onCellClicked.accept(row * rows + column);
+ onCellClicked.accept(column + row * rows);
});
render();
@@ -79,44 +82,33 @@ public abstract class GameCanvas {
graphics.setFill(color);
for (int x = 1; x < rows; x++) {
- graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height);
+ graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height + gapSize);
}
for (int y = 1; y < columns; y++) {
- graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize);
+ graphics.fillRect(0, cells[y * rows].y() - gapSize, width + gapSize, gapSize);
}
if (edges) {
- graphics.fillRect(-gapSize, 0, gapSize, height);
- graphics.fillRect(0, -gapSize, width, gapSize);
+ graphics.fillRect(0, 0, gapSize, height + gapSize);
+ graphics.fillRect(0, 0, width + gapSize, gapSize);
- graphics.fillRect(width - gapSize, 0, gapSize, height);
- graphics.fillRect(0, height - gapSize, width, gapSize);
+ graphics.fillRect(width + gapSize, 0, gapSize, height + gapSize * 2);
+ graphics.fillRect(0, height + gapSize, width + gapSize * 2, gapSize);
}
}
- public void draw(Color color, int cell) {
- final float x = cells[cell].x() + gapSize;
- final float y = cells[cell].y() + gapSize;
+ public void fill(Color color, int cell) {
+ final float x = cells[cell].x();
+ final float y = cells[cell].y();
- final float width = cells[cell].width() - gapSize * 2;
- final float height = cells[cell].height() - gapSize * 2;
+ final float width = cells[cell].width();
+ final float height = cells[cell].height();
graphics.setFill(color);
graphics.fillRect(x, y, width, height);
}
- public void resize(int width, int height) {
- canvas.setWidth(width);
- canvas.setHeight(height);
-
- this.width = width;
- this.height = height;
-
- clear();
- render();
- }
-
public Canvas getCanvas() {
return canvas;
}
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 0f7cbb9..06d0b31 100644
--- a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
+++ b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
@@ -4,9 +4,9 @@ import javafx.scene.paint.Color;
import java.util.function.Consumer;
-public class TicTacToeCanvas extends GameCanvas {
+public final class TicTacToeCanvas extends GameCanvas {
public TicTacToeCanvas(Color color, int width, int height, Consumer onCellClicked) {
- super(color, width, height, 3, 3, 10, false, onCellClicked);
+ super(color, width, height, 3, 3, 30, false, onCellClicked);
}
public void drawX(Color color, int cell) {
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..07f589a
--- /dev/null
+++ b/app/src/main/java/org/toop/app/game/TicTacToeGame.java
@@ -0,0 +1,220 @@
+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;
+
+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;
+
+ public TicTacToeGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit) {
+ this.information = information;
+
+ this.myTurn = myTurn;
+ moveQueue = new LinkedBlockingQueue();
+
+ game = new TicTacToe();
+ ai = new TicTacToeAI();
+
+ if (onForfeit == null || onExit == null) {
+ view = new GameView(null, () -> {
+ ViewStack.push(new LocalMultiplayerView(information));
+ });
+ } else {
+ view = new GameView(onForfeit, onExit);
+ }
+
+ 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);
+ }
+ }
+
+ private void localGameThread() {
+ boolean isRunning = true;
+
+ while (isRunning) {
+ 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 = false;
+ }
+ }
+ }
+
+ private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
+ 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) {
+ 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) {
+ view.updateChat("anon", 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 409c0eb..0000000
--- a/app/src/main/java/org/toop/app/layer/Container.java
+++ /dev/null
@@ -1,11 +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);
-}
\ No newline at end of file
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 35034c9..0000000
--- a/app/src/main/java/org/toop/app/layer/Layer.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.toop.app.layer;
-
-import org.toop.app.App;
-import org.toop.app.canvas.GameCanvas;
-
-import javafx.geometry.Pos;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.StackPane;
-
-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();
-}
\ No newline at end of file
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 a0f2996..0000000
--- a/app/src/main/java/org/toop/app/layer/NodeBuilder.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.toop.app.layer;
-
-import org.toop.framework.audio.events.AudioEvents;
-import org.toop.framework.eventbus.EventFlow;
-
-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 java.util.function.Consumer;
-
-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;
- }
-}
\ No newline at end of file
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 6a54bec..0000000
--- a/app/src/main/java/org/toop/app/layer/Popup.java
+++ /dev/null
@@ -1,19 +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");
- }
-}
\ No newline at end of file
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 b3f00a5..0000000
--- a/app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.toop.app.layer.containers;
-
-import org.toop.app.layer.Container;
-
-import javafx.collections.ObservableList;
-import javafx.scene.Node;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.Region;
-
-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);
- }
- }
- }
-}
\ No newline at end of file
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 a8fb74d..0000000
--- a/app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.toop.app.layer.containers;
-
-import org.toop.app.layer.Container;
-
-import javafx.collections.ObservableList;
-import javafx.scene.Node;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.VBox;
-
-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);
- }
- }
- }
-}
\ No newline at end of file
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
deleted file mode 100644
index f9bf08e..0000000
--- a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package org.toop.app.layer.layers;
-
-import javafx.application.Platform;
-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.events.NetworkEvents;
-
-import javafx.geometry.Pos;
-import javafx.scene.control.Label;
-import javafx.scene.control.ListView;
-import org.toop.local.AppContext;
-
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-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, 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, e));
- }
- }, 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