diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index dcffce8..d801bf4 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -7,6 +7,7 @@
+
diff --git a/app/pom.xml b/app/pom.xml
index 962806c..ca89a53 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -6,12 +6,12 @@
0.1
- org.toop.Main
25
25
UTF-8
+
org.toop
@@ -25,6 +25,12 @@
0.1
compile
+
+
+ org.openjfx
+ javafx-controls
+ 25
+
@@ -38,24 +44,6 @@
25
25
UTF-8
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java
index 30d8bb3..9d9bbfb 100644
--- a/app/src/main/java/org/toop/Main.java
+++ b/app/src/main/java/org/toop/Main.java
@@ -1,18 +1,16 @@
package org.toop;
-import org.toop.app.gui.LocalServerSelector;
+import org.toop.app.App;
import org.toop.framework.networking.NetworkingClientManager;
import org.toop.framework.networking.NetworkingInitializationException;
-
public class Main {
- public static void main(String[] args) {
+ static void main(String[] args) {
initSystems();
- javax.swing.SwingUtilities.invokeLater(LocalServerSelector::new);
+ App.run(args);
}
private static void initSystems() throws NetworkingInitializationException {
new NetworkingClientManager();
}
-
}
\ 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
new file mode 100644
index 0000000..da9a955
--- /dev/null
+++ b/app/src/main/java/org/toop/app/App.java
@@ -0,0 +1,62 @@
+package org.toop.app;
+
+import org.toop.app.menu.MainMenu;
+import org.toop.app.menu.Menu;
+import org.toop.app.menu.QuitMenu;
+
+import javafx.application.Application;
+import javafx.scene.layout.StackPane;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+public class App extends Application {
+ private static Stage stage;
+ private static Scene scene;
+ private static StackPane root;
+
+ public static void run(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(Stage stage) throws Exception {
+ final StackPane root = new StackPane(new MainMenu().getPane());
+ final Scene scene = new Scene(root);
+
+ stage.setTitle("pism");
+ stage.setMinWidth(1080);
+ stage.setMinHeight(720);
+
+ stage.setOnCloseRequest(event -> {
+ event.consume();
+ push(new QuitMenu());
+ });
+
+ stage.setScene(scene);
+ stage.setResizable(false);
+
+ stage.show();
+
+ App.stage = stage;
+ App.scene = scene;
+ App.root = root;
+ }
+
+ public static void activate(Menu menu) {
+ scene.setRoot(menu.getPane());
+ }
+
+ public static void push(Menu menu) {
+ root.getChildren().add(menu.getPane());
+ }
+
+ public static void pop() {
+ root.getChildren().removeLast();
+ }
+
+ public static void quit() {
+ stage.close();
+ }
+
+ public static StackPane getRoot() { return root; }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/gui/BackgroundPanel.java b/app/src/main/java/org/toop/app/gui/BackgroundPanel.java
deleted file mode 100644
index bd5c6d2..0000000
--- a/app/src/main/java/org/toop/app/gui/BackgroundPanel.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.toop.app.gui;
-
-import java.awt.*;
-import javax.swing.*;
-
-public class BackgroundPanel extends JPanel {
- private Image backgroundImage;
-
- public void setBackgroundImage(Image image) {
- this.backgroundImage = image;
- repaint();
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- if (backgroundImage != null) {
- g.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this);
- }
- }
-}
diff --git a/app/src/main/java/org/toop/app/gui/LocalGameSelector.form b/app/src/main/java/org/toop/app/gui/LocalGameSelector.form
deleted file mode 100644
index 0816519..0000000
--- a/app/src/main/java/org/toop/app/gui/LocalGameSelector.form
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
diff --git a/app/src/main/java/org/toop/app/gui/LocalGameSelector.java b/app/src/main/java/org/toop/app/gui/LocalGameSelector.java
deleted file mode 100644
index cf4a44b..0000000
--- a/app/src/main/java/org/toop/app/gui/LocalGameSelector.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.toop.app.gui;
-
-import java.awt.*;
-import javax.swing.*;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.toop.tictactoe.LocalTicTacToe;
-import org.toop.tictactoe.gui.UIGameBoard;
-
-public class LocalGameSelector extends JFrame {
- private static final Logger logger = LogManager.getLogger(LocalGameSelector.class);
-
- private JPanel panel1;
- private JComboBox gameSelectionComboBox;
- private JButton startGame;
- private JComboBox playerTypeSelectionBox;
- private JButton deleteSave;
-
- private JPanel cards; // CardLayout panel
- private CardLayout cardLayout;
-
- private UIGameBoard tttBoard;
-
- public LocalGameSelector() {
- setTitle("Local Game Selector");
- setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- setSize(1920, 1080);
- setLocationRelativeTo(null);
-
- // Setup CardLayout
- cardLayout = new CardLayout();
- cards = new JPanel(cardLayout);
- setContentPane(cards);
-
- // --- Main menu panel ---
- panel1 = new JPanel();
- panel1.setLayout(new FlowLayout());
- gameSelectionComboBox = new JComboBox<>();
- gameSelectionComboBox.addItem("Tic Tac Toe");
- gameSelectionComboBox.addItem("Reversi");
-
- playerTypeSelectionBox = new JComboBox<>();
- playerTypeSelectionBox.addItem("Player vs Player");
- playerTypeSelectionBox.addItem("Player vs AI");
- playerTypeSelectionBox.addItem("AI vs Player");
-
- panel1.add(gameSelectionComboBox);
- panel1.add(playerTypeSelectionBox);
-
- startGame = new JButton("Start Game");
- panel1.add(startGame);
-
- deleteSave = new JButton("Delete Save");
- panel1.add(deleteSave);
- deleteSave.setEnabled(false);
- deleteSave.addActionListener(
- e -> {
- tttBoard = null;
- deleteSave.setEnabled(false);
- });
-
- cards.add(panel1, "MainMenu");
-
- // Start button action
- startGame.addActionListener(e -> startGameClicked());
-
- setVisible(true);
- }
-
- private void startGameClicked() {
- String playerTypes = (String) playerTypeSelectionBox.getSelectedItem();
- String selectedGame = (String) gameSelectionComboBox.getSelectedItem();
-
- LocalTicTacToe lttt = null;
-
- if (playerTypes.equals("Player vs Player")) {
- logger.info("Player vs Player");
- lttt = LocalTicTacToe.createLocal(new boolean[] {false, false});
- } else {
- if (playerTypes.equals("Player vs AI")) {
- logger.info("Player vs AI");
- lttt = LocalTicTacToe.createLocal(new boolean[] {false, true});
- } else {
- logger.info("AI vs Player");
- lttt = LocalTicTacToe.createLocal(new boolean[] {true, false});
- }
- }
-
- if ("Tic Tac Toe".equalsIgnoreCase(selectedGame)) {
- if (tttBoard == null) {
- tttBoard = new UIGameBoard(lttt, this);
- cards.add(tttBoard.getTTTPanel(), "TicTacToe");
- }
- cardLayout.show(cards, "TicTacToe");
- }
- lttt.startThreads();
- }
-
- public void showMainMenu() {
- cardLayout.show(cards, "MainMenu");
- gameSelectionComboBox.setSelectedIndex(0);
- playerTypeSelectionBox.setSelectedIndex(0);
- if (tttBoard != null) {
- deleteSave.setEnabled(true);
- }
- }
-}
diff --git a/app/src/main/java/org/toop/app/gui/LocalServerSelector.form b/app/src/main/java/org/toop/app/gui/LocalServerSelector.form
deleted file mode 100644
index 68623a2..0000000
--- a/app/src/main/java/org/toop/app/gui/LocalServerSelector.form
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
diff --git a/app/src/main/java/org/toop/app/gui/LocalServerSelector.java b/app/src/main/java/org/toop/app/gui/LocalServerSelector.java
deleted file mode 100644
index 4ba4c86..0000000
--- a/app/src/main/java/org/toop/app/gui/LocalServerSelector.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.toop.app.gui;
-
-import org.toop.events.WindowEvents;
-import org.toop.framework.eventbus.EventFlow;
-import org.toop.local.AppContext;
-
-import javax.swing.*;
-import java.util.Locale;
-import java.util.ResourceBundle;
-
-public class LocalServerSelector {
- private JPanel panel1;
- private JButton serverButton;
- private JButton localButton;
- private final JFrame frame;
- Locale locale = AppContext.getLocale();
- ResourceBundle resourceBundle = ResourceBundle.getBundle("Localization", locale);
-
- public LocalServerSelector() {
- frame = new JFrame(resourceBundle.getString("windowTitleServerSelector"));
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setContentPane(panel1);
- frame.setSize(1920, 1080);
- frame.setLocationRelativeTo(null); // Sets to center
- frame.setVisible(true);
-
- serverButton.addActionListener(e -> onServerClicked());
- serverButton.setText(resourceBundle.getString("buttonSelectServer"));
- localButton.addActionListener(e -> onLocalClicked());
- localButton.setText(resourceBundle.getString("buttonSelectLocal"));
- new EventFlow().listen(WindowEvents.LanguageChanged.class, this::changeLanguage);
- }
- private void changeLanguage(WindowEvents.LanguageChanged event) {
- locale = AppContext.getLocale();
- resourceBundle = ResourceBundle.getBundle("Localization", locale);
- }
- private void onServerClicked() {
- frame.dispose();
- new RemoteGameSelector();
- }
-
- private void onLocalClicked() {
- frame.dispose();
- new LocalGameSelector();
- }
-}
diff --git a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.form b/app/src/main/java/org/toop/app/gui/RemoteGameSelector.form
deleted file mode 100644
index df43ead..0000000
--- a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.form
+++ /dev/null
@@ -1,147 +0,0 @@
-
-
diff --git a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.java b/app/src/main/java/org/toop/app/gui/RemoteGameSelector.java
deleted file mode 100644
index 0d13a98..0000000
--- a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.toop.app.gui;
-
-import java.awt.event.ActionEvent;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Supplier;
-import javax.swing.*;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.toop.framework.eventbus.EventFlow;
-import org.toop.framework.networking.events.NetworkEvents;
-import org.toop.tictactoe.LocalTicTacToe;
-import org.toop.framework.networking.NetworkingGameClientHandler;
-import org.toop.tictactoe.gui.UIGameBoard;
-
-public class RemoteGameSelector {
- private static final Logger logger = LogManager.getLogger(RemoteGameSelector.class);
-
- private JPanel mainMenu;
- private JTextField nameTextField;
- private JTextField name2TextField;
- private JTextField ipTextField;
- private JTextField portTextField;
- private JButton connectButton;
- private JComboBox gameSelectorBox;
- private JPanel cards;
- private JPanel gameSelector;
- private JFrame frame;
- private JLabel fillAllFields;
-
- private LocalTicTacToe localTicTacToe;
-
- public RemoteGameSelector() {
- gameSelectorBox.addItem("Tic Tac Toe");
- gameSelectorBox.addItem("Reversi");
- // todo get supported games from server and add to gameSelectorBox
- frame = new JFrame("Game Selector");
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setSize(1920, 1080);
- frame.setResizable(true);
-
- init();
- frame.add(mainMenu);
- frame.setVisible(true);
- // GlobalEventBus.subscribeAndRegister() Todo add game panel to frame when connection
- // succeeds
-
- }
-
- private void init() {
- connectButton.addActionListener(
- (ActionEvent e) -> {
- if (!nameTextField.getText().isEmpty()
- && !name2TextField.getText().isEmpty()
- && !ipTextField.getText().isEmpty()
- && !portTextField.getText().isEmpty()) {
-
- AtomicReference clientId = new AtomicReference<>();
- new EventFlow().addPostEvent(
- NetworkEvents.StartClient.class,
- (Supplier) NetworkingGameClientHandler::new,
- "127.0.0.1",
- 5001
- ).onResponse(
- NetworkEvents.StartClientSuccess.class,
- (response) -> {
- clientId.set(response.clientId());
- }
- ).asyncPostEvent();
-
-// GlobalEventBus.subscribeAndRegister(
-// NetworkEvents.ReceivedMessage.class,
-// event -> {
-// if (event.message().equalsIgnoreCase("ok")) {
-// logger.info("received ok from server.");
-// } else if (event.message().toLowerCase().startsWith("gameid")) {
-// String gameId =
-// event.message()
-// .toLowerCase()
-// .replace("gameid ", "");
-// GlobalEventBus.post(
-// new NetworkEvents.SendCommand(
-// "start_game " + gameId));
-// } else {
-// logger.info("{}", event.message());
-// }
-// });
- frame.remove(mainMenu);
- UIGameBoard ttt = new UIGameBoard(localTicTacToe, this);
- localTicTacToe.startThreads();
- frame.add(ttt.getTTTPanel()); // TODO: Fix later
- frame.revalidate();
- frame.repaint();
- } else {
- fillAllFields.setVisible(true);
- }
- });
- }
-
- public void showMainMenu() {
- frame.removeAll();
- frame.add(mainMenu);
- frame.revalidate();
- frame.repaint();
- }
-}
diff --git a/app/src/main/java/org/toop/app/menu/CreditsMenu.java b/app/src/main/java/org/toop/app/menu/CreditsMenu.java
new file mode 100644
index 0000000..96c77cd
--- /dev/null
+++ b/app/src/main/java/org/toop/app/menu/CreditsMenu.java
@@ -0,0 +1,6 @@
+package org.toop.app.menu;
+
+public final class CreditsMenu extends Menu {
+ public CreditsMenu() {
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/menu/MainMenu.java b/app/src/main/java/org/toop/app/menu/MainMenu.java
new file mode 100644
index 0000000..d4e1b70
--- /dev/null
+++ b/app/src/main/java/org/toop/app/menu/MainMenu.java
@@ -0,0 +1,31 @@
+package org.toop.app.menu;
+
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.*;
+
+public final class MainMenu extends Menu {
+ public MainMenu() {
+ final ImageView background = new ImageView();
+
+ final Button tictactoe = createButton("Tic Tac Toe", () -> {});
+ final Button reversi = createButton("Reversi", () -> {});
+ final Button sudoku = createButton("Sudoku", () -> {});
+ final Button battleship = createButton("Battleship", () -> {});
+ final Button other = createButton("Other", () -> {});
+
+ final VBox gamesBox = new VBox(tictactoe, reversi, sudoku, background, other);
+ gamesBox.setAlignment(Pos.TOP_CENTER);
+
+ final Button credits = createButton("Credits", () -> {});
+ final Button options = createButton("Options", () -> {});
+ final Button quit = createButton("Quit", () -> {});
+
+ final VBox creditsBox = new VBox(10, credits, options, quit);
+ creditsBox.setAlignment(Pos.BOTTOM_CENTER);
+
+ pane = new StackPane(background, grid);
+ pane.getStylesheets().add(getClass().getResource("/style/main.css").toExternalForm());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/menu/Menu.java b/app/src/main/java/org/toop/app/menu/Menu.java
new file mode 100644
index 0000000..fc0627f
--- /dev/null
+++ b/app/src/main/java/org/toop/app/menu/Menu.java
@@ -0,0 +1,27 @@
+package org.toop.app.menu;
+
+import org.toop.app.App;
+
+import javafx.animation.FadeTransition;
+import javafx.scene.control.Button;
+import javafx.scene.layout.Pane;
+import javafx.util.Duration;
+
+public abstract class Menu {
+ protected Pane pane;
+ public Pane getPane() { return pane; }
+
+ public void fadeBackgroundImage(String imagePath, float from, float to, float milliseconds) {
+ final FadeTransition fade = new FadeTransition(Duration.millis(milliseconds), App.getRoot());
+ fade.setFromValue(from);
+ fade.setToValue(to);
+ fade.play();
+ }
+
+ public Button createButton(String text, Runnable runnable) {
+ final Button button = new Button(text);
+ button.setOnAction(_ -> runnable.run());
+ button.getStyleClass().add("button");
+ return button;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/menu/OptionsMenu.java b/app/src/main/java/org/toop/app/menu/OptionsMenu.java
new file mode 100644
index 0000000..541bb73
--- /dev/null
+++ b/app/src/main/java/org/toop/app/menu/OptionsMenu.java
@@ -0,0 +1,6 @@
+package org.toop.app.menu;
+
+public final class OptionsMenu extends Menu {
+ public OptionsMenu() {
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/app/menu/QuitMenu.java b/app/src/main/java/org/toop/app/menu/QuitMenu.java
new file mode 100644
index 0000000..fe359da
--- /dev/null
+++ b/app/src/main/java/org/toop/app/menu/QuitMenu.java
@@ -0,0 +1,48 @@
+package org.toop.app.menu;
+
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Text;
+import org.toop.app.App;
+
+public final class QuitMenu extends Menu {
+ public QuitMenu() {
+ final Region background = new Region();
+ background.getStyleClass().add("quit-background");
+ background.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
+
+ final Text sure = new Text("Are you sure?");
+ sure.getStyleClass().add("quit-text");
+
+ final Button yes = new Button("Yes");
+ yes.getStyleClass().add("quit-button");
+ yes.setOnAction(_ -> {
+ App.quit();
+ });
+
+ final Button no = new Button("No");
+ no.getStyleClass().add("quit-button");
+ no.setOnAction(_ -> {
+ App.pop();
+ });
+
+ final HBox buttons = new HBox(10, yes, no);
+ buttons.setAlignment(Pos.CENTER);
+
+ VBox box = new VBox(43, sure, buttons);
+ box.setAlignment(Pos.CENTER);
+ box.getStyleClass().add("quit-box");
+ box.setMaxWidth(350);
+ box.setMaxHeight(200);
+
+ StackPane modalContainer = new StackPane(background, box);
+ StackPane.setAlignment(box, Pos.CENTER);
+
+ pane = modalContainer;
+ pane.getStylesheets().add(getClass().getResource("/style/quit.css").toExternalForm());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/events/WindowEvents.java b/app/src/main/java/org/toop/events/WindowEvents.java
deleted file mode 100644
index 50d3f2d..0000000
--- a/app/src/main/java/org/toop/events/WindowEvents.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.toop.events;
-
-import org.toop.framework.eventbus.events.EventWithoutSnowflake;
-import org.toop.framework.eventbus.events.EventsBase;
-
-public class WindowEvents extends EventsBase {
-
- /** Triggers when a cell is clicked in one of the game boards. */
- public record CellClicked(int cell) implements EventWithoutSnowflake {}
-
- /** Triggers when the window wants to quit. */
- public record OnQuitRequested() implements EventWithoutSnowflake {}
-
- /** Triggers when the window is resized. */
-// public record OnResize(Window.Size size) implements EventWithoutSnowflake {}
-
- /** Triggers when the mouse is moved within the window. */
- public record OnMouseMove(int x, int y) implements EventWithoutSnowflake {}
-
- /** Triggers when the mouse is clicked within the window. */
- public record OnMouseClick(int button) implements EventWithoutSnowflake {}
-
- /** Triggers when the mouse is released within the window. */
- public record OnMouseRelease(int button) implements EventWithoutSnowflake {}
-
- /** Triggers when the language is changed. */
- public record LanguageChanged() implements EventWithoutSnowflake {}
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/toop/tictactoe/LocalTicTacToe.java b/app/src/main/java/org/toop/tictactoe/LocalTicTacToe.java
deleted file mode 100644
index f0903b2..0000000
--- a/app/src/main/java/org/toop/tictactoe/LocalTicTacToe.java
+++ /dev/null
@@ -1,257 +0,0 @@
-package org.toop.tictactoe;
-
-import java.util.concurrent.*;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.toop.framework.eventbus.EventFlow;
-import org.toop.framework.networking.events.NetworkEvents;
-import org.toop.game.GameBase;
-import org.toop.tictactoe.gui.UIGameBoard;
-import org.toop.framework.networking.NetworkingGameClientHandler;
-
-import java.util.function.Supplier;
-
-import static java.lang.Thread.sleep;
-
-/**
- * A representation of a local tic-tac-toe game. Calls are made to a server for information about
- * current game state. MOST OF THIS CODE IS TRASH, THROW IT OUT OF THE WINDOW AFTER DEMO.
- */
-// Todo: refactor
-public class LocalTicTacToe { // TODO: Implement runnable
- private static final Logger logger = LogManager.getLogger(LocalTicTacToe.class);
-
- private final ExecutorService executor = Executors.newFixedThreadPool(3);
- private final BlockingQueue receivedQueue = new LinkedBlockingQueue<>();
- private final BlockingQueue moveQueuePlayerA = new LinkedBlockingQueue<>();
- private final BlockingQueue moveQueuePlayerB = new LinkedBlockingQueue<>();
-
- private Object receivedMessageListener = null;
-
- private boolean isLocal;
- private String gameId;
- private String connectionId = null;
- private String serverId = null;
-
- private boolean isAiPlayer[] = new boolean[2];
- private TicTacToeAI[] aiPlayers = new TicTacToeAI[2];
- private TicTacToe ticTacToe;
- private UIGameBoard ui;
-
- /** Is either 0 or 1. */
- private int playersTurn = 0;
-
- /**
- * @return The current players turn.
- */
- public int getCurrentPlayersTurn() {
- return this.playersTurn;
- }
-
- // LocalTicTacToe(String gameId, String connectionId, String serverId) {
- // this.gameId = gameId;
- // this.connectionId = connectionId;
- // this.serverId = serverId;
- // this.receivedMessageListener =
- // GlobalEventBus.subscribe(Events.ServerEvents.ReceivedMessage.class,
- // this::receiveMessageAction);
- // GlobalEventBus.register(this.receivedMessageListener);
- //
- //
- // this.executor.submit(this::gameThread);
- // } TODO: If remote server
-
- /**
- * Starts a connection with a remote server.
- *
- * @param ip The IP of the server to connect to.
- * @param port The port of the server to connect to.
- */
- private LocalTicTacToe(String ip, int port) {
-// this.receivedMessageListener =
-// GlobalEventBus.subscribe(this::receiveMessageAction);
-// GlobalEventBus.subscribe(this.receivedMessageListener);
- this.connectionId = this.createConnection(ip, port);
- this.createGame("X", "O");
- this.isLocal = false;
- //this.executor.submit(this::remoteGameThread);
- }
-
- private LocalTicTacToe(boolean[] aiFlags) {
- this.isAiPlayer = aiFlags; // store who is AI
-
- for (int i = 0; i < aiFlags.length && i < this.aiPlayers.length; i++) {
- if (aiFlags[i]) {
- this.aiPlayers[i] = new TicTacToeAI(); // create AI for that player
- } else {
- this.aiPlayers[i] = null; // not an AI player
- }
- }
-
- this.isLocal = true;
- //this.executor.submit(this::localGameThread);
- }
- public void startThreads(){
- if (isLocal) {
- this.executor.submit(this::localGameThread);
- }else {
- this.executor.submit(this::remoteGameThread);
- }
- }
-
- public static LocalTicTacToe createLocal(boolean[] aiPlayers) {
- return new LocalTicTacToe(aiPlayers);
- }
-
- public static LocalTicTacToe createRemote(String ip, int port) {
- return new LocalTicTacToe(ip, port);
- }
-
- private String createConnection(String ip, int port) {
- CompletableFuture connectionIdFuture = new CompletableFuture<>();
- new EventFlow().addPostEvent(NetworkEvents.StartClientRequest.class,
- (Supplier) NetworkingGameClientHandler::new,
- ip, port, connectionIdFuture).asyncPostEvent(); // TODO: what if server couldn't be started with port.
- try {
- return connectionIdFuture.get();
- } catch (InterruptedException | ExecutionException e) {
- logger.error("Error getting connection ID", e);
- }
- return null;
- }
-
- private void createGame(String nameA, String nameB) {
- nameA = nameA.trim().replace(" ", "-");
- nameB = nameB.trim().replace(" ", "-");
- this.sendCommand("create_game", nameA, nameB);
- }
-
- private void startGame() {
- if (this.gameId == null) {
- return;
- }
- this.sendCommand("start_game", this.gameId);
- }
-
- private void localGameThread() {
- boolean running = true;
- this.ticTacToe = new TicTacToe("X", "O");
- while (running) {
- try {
- GameBase.State state;
- if (!isAiPlayer[0]) {
- state = this.ticTacToe.play(this.moveQueuePlayerA.take());
- } else {
- int bestMove = aiPlayers[0].findBestMove(this.ticTacToe);
- state = this.ticTacToe.play(bestMove);
- if (state != GameBase.State.INVALID) {
- ui.setCell(bestMove, "X");
- }
- }
- if (state == GameBase.State.WIN || state == GameBase.State.DRAW) {
- ui.setState(state, "X");
- running = false;
- }
- this.setNextPlayersTurn();
- if (!isAiPlayer[1]) {
- state = this.ticTacToe.play(this.moveQueuePlayerB.take());
- } else {
- int bestMove = aiPlayers[1].findBestMove(this.ticTacToe);
- state = this.ticTacToe.play(bestMove);
- if (state != GameBase.State.INVALID) {
- ui.setCell(bestMove, "O");
- }
- }
- if (state == GameBase.State.WIN || state == GameBase.State.DRAW) {
- ui.setState(state, "O");
- running = false;
- }
- this.setNextPlayersTurn();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private void remoteGameThread() {
- // TODO: If server start this.
- }
-
- public void setNextPlayersTurn() {
- if (this.playersTurn == 0) {
- this.playersTurn += 1;
- } else {
- this.playersTurn -= 1;
- }
- }
-
- public char[] getCurrentBoard() {
- return ticTacToe.getGrid();
- }
-
- /** End the current game. */
- public void endGame() {
- sendCommand("gameid", "end_game"); // TODO: Command is a bit wrong.
- }
-
- /**
- * @param moveIndex The index of the move to make.
- */
- public void move(int moveIndex) {
- this.executor.submit(
- () -> {
- try {
- if (this.playersTurn == 0 && !isAiPlayer[0]) {
- this.moveQueuePlayerA.put(moveIndex);
- logger.info(
- "Adding player's {}, move: {} to queue A",
- this.playersTurn,
- moveIndex);
- } else if (this.playersTurn == 1 && !isAiPlayer[1]) {
- this.moveQueuePlayerB.put(moveIndex);
- logger.info(
- "Adding player's {}, move: {} to queue B",
- this.playersTurn,
- moveIndex);
- }
- } catch (InterruptedException e) {
- logger.error(
- "Could not add player: {}'s, move {}",
- this.playersTurn,
- moveIndex); // TODO: Error handling instead of crash.
- }
- });
- }
-
- private void endTheGame() {
- this.sendCommand("end_game", this.gameId);
-// this.endListeners();
- }
-
- private void receiveMessageAction(NetworkEvents.ReceivedMessage receivedMessage) {
- if (!receivedMessage.ConnectionUuid().equals(this.connectionId)) {
- return;
- }
-
- try {
- logger.info(
- "Received message from {}: {}", this.connectionId, receivedMessage.message());
- this.receivedQueue.put(receivedMessage.message());
- } catch (InterruptedException e) {
- logger.error("Error waiting for received Message", e);
- }
- }
-
- private void sendCommand(String... args) {
- new EventFlow().addPostEvent(NetworkEvents.SendCommand.class, this.connectionId, args).asyncPostEvent();
- }
-
-// private void endListeners() {
-// GlobalEventBus.unregister(this.receivedMessageListener);
-// } TODO
-
- public void setUIReference(UIGameBoard uiGameBoard) {
- this.ui = uiGameBoard;
- }
-}
diff --git a/app/src/main/java/org/toop/tictactoe/gui/UIGameBoard.form b/app/src/main/java/org/toop/tictactoe/gui/UIGameBoard.form
deleted file mode 100644
index b69fad4..0000000
--- a/app/src/main/java/org/toop/tictactoe/gui/UIGameBoard.form
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
diff --git a/app/src/main/java/org/toop/tictactoe/gui/UIGameBoard.java b/app/src/main/java/org/toop/tictactoe/gui/UIGameBoard.java
deleted file mode 100644
index 68e0b9f..0000000
--- a/app/src/main/java/org/toop/tictactoe/gui/UIGameBoard.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package org.toop.tictactoe.gui;
-
-import java.awt.*;
-import java.awt.event.ActionEvent;
-import java.util.Locale;
-import java.util.ResourceBundle;
-import javax.swing.*;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.toop.app.gui.LocalGameSelector;
-import org.toop.app.gui.RemoteGameSelector;
-import org.toop.events.WindowEvents;
-import org.toop.framework.eventbus.EventFlow;
-import org.toop.local.AppContext;
-import org.toop.tictactoe.LocalTicTacToe;
-import org.toop.game.GameBase;
-
-public class UIGameBoard {
- private static final int TICTACTOE_SIZE = 3;
-
- private static final Logger logger = LogManager.getLogger(LocalGameSelector.class);
-
- private JPanel tttPanel; // Root panel for this game
- private JButton backToMainMenuButton;
- private JButton[] cells;
- private String currentPlayer = "X";
- private int currentPlayerIndex = 0;
-
- private Object parentSelector;
- private boolean parentLocal;
- private LocalTicTacToe localTicTacToe;
-
- private boolean gameOver = false;
- Locale locale = AppContext.getLocale();
- ResourceBundle resourceBundle = ResourceBundle.getBundle("Localization", locale);
-
- public UIGameBoard(LocalTicTacToe lttt, Object parent) {
- if (!(parent == null)) {
- if (parent instanceof LocalGameSelector) {
- parentLocal = true;
- } else if (parent instanceof RemoteGameSelector) {
- parentLocal = false;
- }
- }
- this.parentSelector = parent;
- this.localTicTacToe = lttt;
- lttt.setUIReference(this);
-
- // Root panel
- tttPanel = new JPanel(new BorderLayout());
-
- // Back button
- backToMainMenuButton = new JButton(resourceBundle.getString("buttonBackToMainMenu"));
- tttPanel.add(backToMainMenuButton, BorderLayout.SOUTH);
- backToMainMenuButton.addActionListener(
- _ -> {
- // TODO reset game and connections
- // Game now gets reset in local
- if (parentLocal) {
- ((LocalGameSelector) parent).showMainMenu();
- } else {
- ((RemoteGameSelector) parent).showMainMenu();
- }
- });
-
- // Game grid
- JPanel gameGrid = createGridPanel(TICTACTOE_SIZE, TICTACTOE_SIZE);
- tttPanel.add(gameGrid, BorderLayout.CENTER);
-
- // localTicTacToe.setMoveListener((playerIndex, moveIndex, symbol) -> {
- // SwingUtilities.invokeLater(() -> {
- // cells[moveIndex].setText(String.valueOf(symbol));
- // });
- // });
- new EventFlow().listen(WindowEvents.LanguageChanged.class, this::changeLanguage);
- }
- private void changeLanguage(WindowEvents.LanguageChanged event) {
- locale = AppContext.getLocale();
- resourceBundle = ResourceBundle.getBundle("Localization", locale);
- }
-
- private JPanel createGridPanel(int sizeX, int sizeY) {
- JPanel panel = new JPanel(new GridLayout(sizeX, sizeY));
- cells = new JButton[sizeX * sizeY];
-
- for (int i = 0; i < sizeX * sizeY; i++) {
- cells[i] = new JButton(" ");
- cells[i].setFont(new Font("Arial", Font.BOLD, 400 / sizeX));
- panel.add(cells[i]);
- cells[i].setFocusable(false);
-
- final int index = i;
- cells[i].addActionListener(
- (ActionEvent _) -> {
- if (!gameOver) {
- if (cells[index].getText().equals(" ")) {
- int cp = this.localTicTacToe.getCurrentPlayersTurn();
- if (cp == 0) {
- this.currentPlayer = "X";
- currentPlayerIndex = 0;
- } else if (cp == 1) {
- this.currentPlayer = "O";
- currentPlayerIndex = 1;
- }
- this.localTicTacToe.move(index);
- cells[index].setText(currentPlayer);
- } else {
- logger.info(
- "Player "
- + currentPlayerIndex
- + " attempted invalid move at: "
- + cells[index].getText());
- }
- } else {
- logger.info(
- "Player "
- + currentPlayerIndex
- + " attempted to move after the game has ended.");
- }
- });
- }
-
- return panel;
- }
-
- public void setCell(int index, String move) {
- System.out.println(cells[index].getText());
- cells[index].setText(move);
- }
-
- public void setState(GameBase.State state, String playerMove) {
- Color color;
- if (state == GameBase.State.WIN && playerMove.equals(currentPlayer)) {
- color = new Color(160, 220, 160);
- } else if (state == GameBase.State.WIN) {
- color = new Color(220, 160, 160);
- } else if (state == GameBase.State.DRAW) {
- color = new Color(220, 220, 160);
- } else {
- color = new Color(220, 220, 220);
- }
- for (JButton cell : cells) {
- cell.setBackground(color);
- }
- if (state == GameBase.State.DRAW || state == GameBase.State.WIN) {
- gameOver = true;
- }
- }
-
- public JPanel getTTTPanel() {
- return tttPanel;
- }
-}
diff --git a/app/src/main/resources/image/game/battleship.png b/app/src/main/resources/image/game/battleship.png
new file mode 100644
index 0000000..813893f
Binary files /dev/null and b/app/src/main/resources/image/game/battleship.png differ
diff --git a/app/src/main/resources/image/game/other.png b/app/src/main/resources/image/game/other.png
new file mode 100644
index 0000000..6bd4167
Binary files /dev/null and b/app/src/main/resources/image/game/other.png differ
diff --git a/app/src/main/resources/image/game/reversi.png b/app/src/main/resources/image/game/reversi.png
new file mode 100644
index 0000000..bd9252f
Binary files /dev/null and b/app/src/main/resources/image/game/reversi.png differ
diff --git a/app/src/main/resources/image/game/sudoku.png b/app/src/main/resources/image/game/sudoku.png
new file mode 100644
index 0000000..ec88234
Binary files /dev/null and b/app/src/main/resources/image/game/sudoku.png differ
diff --git a/app/src/main/resources/image/game/tictactoe.png b/app/src/main/resources/image/game/tictactoe.png
new file mode 100644
index 0000000..2a81e05
Binary files /dev/null and b/app/src/main/resources/image/game/tictactoe.png differ
diff --git a/app/src/main/resources/style/main.css b/app/src/main/resources/style/main.css
new file mode 100644
index 0000000..99e8087
--- /dev/null
+++ b/app/src/main/resources/style/main.css
@@ -0,0 +1,33 @@
+.main-button {
+ -fx-background-color: transparent;
+ -fx-background-image: url("card-default.jpg"); /* fallback image */
+ -fx-background-size: cover;
+ -fx-background-position: center;
+ -fx-pref-width: 250px;
+ -fx-pref-height: 180px;
+ -fx-border-radius: 15;
+ -fx-background-radius: 15;
+ -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 15, 0.4, 0, 4);
+ -fx-cursor: hand;
+ -fx-padding: 0;
+}
+
+.card-label {
+ -fx-background-color: rgba(0, 0, 0, 0.5);
+ -fx-font-size: 20px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+ -fx-padding: 10px;
+ -fx-alignment: top-center;
+ -fx-background-radius: 15 15 0 0;
+ -fx-opacity: 0;
+ -fx-transition: all 0.3s ease;
+}
+
+.main-button:hover {
+ -fx-effect: dropshadow(gaussian, #00ffff, 15, 0.5, 0, 0);
+}
+
+.main-button:hover .card-label {
+ -fx-opacity: 1;
+}
\ No newline at end of file
diff --git a/app/src/main/resources/style/quit.css b/app/src/main/resources/style/quit.css
new file mode 100644
index 0000000..59c72a3
--- /dev/null
+++ b/app/src/main/resources/style/quit.css
@@ -0,0 +1,33 @@
+.quit-background {
+ -fx-background-color: rgba(0, 0, 0, 0.6);
+}
+
+.quit-box {
+ -fx-background-color: rgba(30, 30, 30, 0.95);
+ -fx-background-radius: 15;
+ -fx-padding: 30;
+ -fx-effect: dropshadow(gaussian, black, 20, 0.6, 0, 4);
+}
+
+.quit-text {
+ -fx-fill: white;
+ -fx-font-size: 28px;
+ -fx-font-weight: 600;
+ -fx-font-family: "Segoe UI", sans-serif;
+}
+
+.quit-button {
+ -fx-font-size: 16px;
+ -fx-text-fill: white;
+ -fx-background-color: transparent;
+ -fx-border-color: white;
+ -fx-border-radius: 5;
+ -fx-padding: 8 20;
+ -fx-cursor: hand;
+}
+
+.quit-button:hover {
+ -fx-text-fill: #00ffff;
+ -fx-border-color: #00ffff;
+ -fx-effect: dropshadow(gaussian, #00ffff, 8, 0.5, 0, 0);
+}
\ No newline at end of file
diff --git a/app/src/main/resources/style/style.css b/app/src/main/resources/style/style.css
new file mode 100644
index 0000000..c09d516
--- /dev/null
+++ b/app/src/main/resources/style/style.css
@@ -0,0 +1,20 @@
+.root {
+ -fx-background-color: #2d2d2d;
+
+ -fx-font-size: 28px;
+ -fx-font-weight: 600;
+ -fx-font-family: "Segoe UI", sans-serif;
+}
+
+.button {
+ -fx-background-color: transparent;
+ -fx-text-fill: white;
+ -fx-border-color: transparent;
+ -fx-padding: 10 20;
+ -fx-cursor: hand;
+ -fx-effect: null;
+}
+
+.button:hover {
+ -fx-effect: dropshadow(gaussian, #00ffff, 10, 0.3, 0, 0);
+}
\ No newline at end of file
diff --git a/game/pom.xml b/game/pom.xml
index 3297948..c82a815 100644
--- a/game/pom.xml
+++ b/game/pom.xml
@@ -13,6 +13,50 @@
+
+ org.junit
+ junit-bom
+ 5.13.4
+ pom
+ import
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.13.4
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.13.4
+ test
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.4
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.4
+
+
+ org.mockito
+ mockito-core
+ 5.19.0
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.19.0
+ test
+
+
org.apache.logging.log4j
log4j-api
diff --git a/game/src/main/java/org/toop/game/AI.java b/game/src/main/java/org/toop/game/AI.java
new file mode 100644
index 0000000..0506b10
--- /dev/null
+++ b/game/src/main/java/org/toop/game/AI.java
@@ -0,0 +1,5 @@
+package org.toop.game;
+
+public abstract class AI {
+ public abstract Game.Move findBestMove(T game, int depth);
+}
\ No newline at end of file
diff --git a/game/src/main/java/org/toop/game/Game.java b/game/src/main/java/org/toop/game/Game.java
new file mode 100644
index 0000000..b37bd73
--- /dev/null
+++ b/game/src/main/java/org/toop/game/Game.java
@@ -0,0 +1,57 @@
+package org.toop.game;
+
+import java.util.Arrays;
+
+public abstract class Game {
+ public enum State {
+ NORMAL, LOSE, DRAW, WIN,
+ }
+
+ public record Move(int position, char value) {}
+
+ public static final char EMPTY = (char)0;
+
+ protected final int rowSize;
+ protected final int columnSize;
+ protected final char[] board;
+
+ protected final Player[] players;
+ protected int currentPlayer;
+
+ protected Game(int rowSize, int columnSize, Player... players) {
+ assert rowSize > 0 && columnSize > 0;
+ assert players.length >= 1;
+
+ this.rowSize = rowSize;
+ this.columnSize = columnSize;
+
+ board = new char[rowSize * columnSize];
+ Arrays.fill(board, EMPTY);
+
+ this.players = players;
+ currentPlayer = 0;
+ }
+
+ protected Game(Game other) {
+ rowSize = other.rowSize;
+ columnSize = other.columnSize;
+ board = Arrays.copyOf(other.board, other.board.length);
+
+ players = Arrays.copyOf(other.players, other.players.length);
+ currentPlayer = other.currentPlayer;
+ }
+
+ public int getRowSize() { return rowSize; }
+ public int getColumnSize() { return columnSize; }
+ public char[] getBoard() { return board; }
+
+ public Player[] getPlayers() { return players; }
+ public Player getCurrentPlayer() { return players[currentPlayer]; }
+
+ protected void nextPlayer() {
+ currentPlayer = (currentPlayer + 1) % players.length;
+ }
+
+ public abstract Move[] getLegalMoves();
+ public abstract State play(Move move);
+}
\ No newline at end of file
diff --git a/game/src/main/java/org/toop/game/GameBase.java b/game/src/main/java/org/toop/game/GameBase.java
deleted file mode 100644
index c6de8c3..0000000
--- a/game/src/main/java/org/toop/game/GameBase.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.toop.game;
-
-// Todo: refactor
-public abstract class GameBase {
- public enum State {
- INVALID,
-
- NORMAL,
- DRAW,
- WIN,
- }
-
- public static char EMPTY = '-';
-
- protected int size;
- public char[] grid;
-
- protected Player[] players;
- public int currentPlayer;
-
- public GameBase(int size, Player player1, Player player2) {
- this.size = size;
- grid = new char[size * size];
-
- for (int i = 0; i < grid.length; i++) {
- grid[i] = EMPTY;
- }
-
- players = new Player[2];
- players[0] = player1;
- players[1] = player2;
-
- currentPlayer = 0;
- }
-
- public boolean isInside(int index) {
- return index >= 0 && index < size * size;
- }
-
- public int getSize() {
- return size;
- }
-
- public char[] getGrid() {
- return grid;
- }
-
- public Player[] getPlayers() {
- return players;
- }
-
- public Player getCurrentPlayer() {
- return players[currentPlayer];
- }
-
- public abstract State play(int index);
-}
\ No newline at end of file
diff --git a/game/src/main/java/org/toop/game/Player.java b/game/src/main/java/org/toop/game/Player.java
index 8f71c40..2dc4a2f 100644
--- a/game/src/main/java/org/toop/game/Player.java
+++ b/game/src/main/java/org/toop/game/Player.java
@@ -1,20 +1,3 @@
package org.toop.game;
-// Todo: refactor
-public class Player {
- String name;
- char symbol;
-
- public Player(String name, char symbol) {
- this.name = name;
- this.symbol = symbol;
- }
-
- public String getName() {
- return this.name;
- }
-
- public char getSymbol() {
- return this.symbol;
- }
-}
\ No newline at end of file
+public record Player(String name, char... values) {}
\ No newline at end of file
diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java
new file mode 100644
index 0000000..4b39df2
--- /dev/null
+++ b/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java
@@ -0,0 +1,84 @@
+package org.toop.game.tictactoe;
+
+import org.toop.game.Game;
+import org.toop.game.Player;
+
+import java.util.ArrayList;
+
+public final class TicTacToe extends Game {
+ private int movesLeft;
+
+ public TicTacToe(String player1, String player2) {
+ super(3, 3, new Player(player1, 'X'), new Player(player2, 'O'));
+ movesLeft = board.length;
+ }
+
+ public TicTacToe(TicTacToe other) {
+ super(other);
+ movesLeft = other.movesLeft;
+ }
+
+ @Override
+ public Move[] getLegalMoves() {
+ final ArrayList legalMoves = new ArrayList<>();
+
+ for (int i = 0; i < board.length; i++) {
+ if (board[i] == EMPTY) {
+ legalMoves.add(new Move(i, getCurrentPlayer().values()[0]));
+ }
+ }
+
+ return legalMoves.toArray(new Move[0]);
+ }
+
+ @Override
+ public State play(Move move) {
+ assert move != null;
+ assert move.position() >= 0 && move.position() < board.length;
+ assert move.value() == getCurrentPlayer().values()[0];
+
+ board[move.position()] = move.value();
+ movesLeft--;
+
+ if (checkForWin()) {
+ return State.WIN;
+ }
+
+ if (movesLeft <= 0) {
+ return State.DRAW;
+ }
+
+ nextPlayer();
+ return State.NORMAL;
+ }
+
+ private boolean checkForWin() {
+ // Horizontal
+ for (int i = 0; i < 3; i++) {
+ final int index = i * 3;
+
+ if (board[index] != EMPTY
+ && board[index] == board[index + 1]
+ && board[index] == board[index + 2]) {
+ return true;
+ }
+ }
+
+ // Vertical
+ for (int i = 0; i < 3; i++) {
+ if (board[i] != EMPTY
+ && board[i] == board[i + 3]
+ && board[i] == board[i + 6]) {
+ return true;
+ }
+ }
+
+ // B-Slash
+ if (board[0] != EMPTY && board[0] == board[4] && board[0] == board[8]) {
+ return true;
+ }
+
+ // F-Slash
+ return board[2] != EMPTY && board[2] == board[4] && board[2] == board[6];
+ }
+}
\ No newline at end of file
diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToeAI.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToeAI.java
new file mode 100644
index 0000000..afc61b8
--- /dev/null
+++ b/game/src/main/java/org/toop/game/tictactoe/TicTacToeAI.java
@@ -0,0 +1,68 @@
+package org.toop.game.tictactoe;
+
+import org.toop.game.AI;
+import org.toop.game.Game;
+
+public final class TicTacToeAI extends AI {
+ @Override
+ public Game.Move findBestMove(TicTacToe game, int depth) {
+ assert game != null;
+ assert depth >= 0;
+
+ final Game.Move[] legalMoves = game.getLegalMoves();
+
+ if (legalMoves.length <= 0) {
+ return null;
+ }
+
+ if (legalMoves.length == 9) {
+ return switch ((int)(Math.random() * 4)) {
+ case 1 -> legalMoves[2];
+ case 2 -> legalMoves[6];
+ case 3 -> legalMoves[8];
+ default -> legalMoves[0];
+ };
+ }
+
+ int bestScore = -depth;
+ Game.Move bestMove = null;
+
+ for (final Game.Move move : legalMoves) {
+ final int score = getMoveScore(game, depth, move, true);
+
+ if (score > bestScore) {
+ bestMove = move;
+ bestScore = score;
+ }
+ }
+
+ return bestMove != null? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)];
+ }
+
+ private int getMoveScore(TicTacToe game, int depth, Game.Move move, boolean maximizing) {
+ final TicTacToe copy = new TicTacToe(game);
+ final Game.State state = copy.play(move);
+
+ switch (state) {
+ case Game.State.DRAW: return 0;
+ case Game.State.WIN: return maximizing? depth + 1 : -depth - 1;
+ }
+
+ if (depth <= 0) {
+ return 0;
+ }
+
+ final Game.Move[] legalMoves = copy.getLegalMoves();
+ int score = maximizing? depth + 1 : -depth - 1;
+
+ for (final Game.Move next : legalMoves) {
+ if (maximizing) {
+ score = Math.min(score, getMoveScore(copy, depth - 1, next, false));
+ } else {
+ score = Math.max(score, getMoveScore(copy, depth - 1, next, true));
+ }
+ }
+
+ return score;
+ }
+}
\ No newline at end of file
diff --git a/game/src/main/java/org/toop/tictactoe/TicTacToe.java b/game/src/main/java/org/toop/tictactoe/TicTacToe.java
deleted file mode 100644
index 70d1598..0000000
--- a/game/src/main/java/org/toop/tictactoe/TicTacToe.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.toop.tictactoe;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.toop.game.GameBase;
-import org.toop.game.Player;
-
-// Todo: refactor
-public class TicTacToe extends GameBase {
-
- protected static final Logger logger = LogManager.getLogger(TicTacToe.class);
-
- public Thread gameThread;
- public String gameId;
-
- public int movesLeft;
-
- public TicTacToe(String player1, String player2) {
- super(3, new Player(player1, 'X'), new Player(player2, 'O'));
- movesLeft = size * size;
- }
-
- /**
- * Used for the server.
- *
- * @param player1
- * @param player2
- * @param gameId
- */
- public TicTacToe(String player1, String player2, String gameId) {
- super(3, new Player(player1, 'X'), new Player(player2, 'O'));
- this.gameId = gameId;
- movesLeft = size * size;
- }
-
- @Override
- public State play(int index) {
- if (!validateMove(index)) {
- return State.INVALID;
- }
-
- grid[index] = getCurrentPlayer().getSymbol();
- movesLeft--;
-
- if (checkWin()) {
- return State.WIN;
- }
-
- if (movesLeft <= 0) {
- return State.DRAW;
- }
-
- currentPlayer = (currentPlayer + 1) % players.length;
- return State.NORMAL;
- }
-
- public boolean validateMove(int index) {
- return movesLeft > 0 && isInside(index) && grid[index] == EMPTY;
- }
-
- public boolean checkWin() {
- // Horizontal
- for (int i = 0; i < 3; i++) {
- final int index = i * 3;
-
- if (grid[index] != EMPTY
- && grid[index] == grid[index + 1]
- && grid[index] == grid[index + 2]) {
- return true;
- }
- }
-
- // Vertical
- for (int i = 0; i < 3; i++) {
- int index = i;
-
- if (grid[index] != EMPTY
- && grid[index] == grid[index + 3]
- && grid[index] == grid[index + 6]) {
- return true;
- }
- }
-
- // B-Slash
- if (grid[0] != EMPTY && grid[0] == grid[4] && grid[0] == grid[8]) {
- return true;
- }
-
- // F-Slash
- if (grid[2] != EMPTY && grid[2] == grid[4] && grid[2] == grid[6]) {
- return true;
- }
-
- return false;
- }
-
- /** For AI use only. */
- public void decrementMovesLeft() {
- movesLeft--;
- }
-
- /** This method copies the board, mainly for AI use. */
- public TicTacToe copyBoard() {
- TicTacToe clone = new TicTacToe(players[0].getName(), players[1].getName());
- System.arraycopy(this.grid, 0, clone.grid, 0, this.grid.length);
- clone.movesLeft = this.movesLeft;
- clone.currentPlayer = this.currentPlayer;
- return clone;
- }
-}
diff --git a/game/src/main/java/org/toop/tictactoe/TicTacToeAI.java b/game/src/main/java/org/toop/tictactoe/TicTacToeAI.java
deleted file mode 100644
index 3eb475d..0000000
--- a/game/src/main/java/org/toop/tictactoe/TicTacToeAI.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package org.toop.tictactoe;
-
-import org.toop.game.GameBase;
-
-// Todo: refactor
-public class TicTacToeAI {
- /**
- * This method tries to find the best move by seeing if it can set a winning move, if not, it
- * will do a minimax.
- */
- public int findBestMove(TicTacToe game) {
- int bestVal = -100; // set bestVal to something impossible
- int bestMove = 10; // set bestMove to something impossible
-
- int winningMove = -5;
-
- boolean empty = true;
- for (char cell : game.grid) {
- if (!(cell == GameBase.EMPTY)) {
- empty = false;
- break;
- }
- }
-
- if (empty) { // start in a random corner
- return switch ((int) (Math.random() * 4)) {
- case 1 -> 2;
- case 2 -> 6;
- case 3 -> 8;
- default -> 0;
- };
- }
-
- // simulate all possible moves on the field
- for (int i = 0; i < game.grid.length; i++) {
-
- if (game.validateMove(i)) { // check if the move is legal here
- TicTacToe copyGame = game.copyBoard(); // make a copy of the game
- GameBase.State result = copyGame.play(i); // play a move on the copy board
-
- int thisMoveValue;
-
- if (result == GameBase.State.WIN) {
- return i; // just return right away if you can win on the next move
- }
-
- for (int index = 0; index < game.grid.length; index++) {
- if (game.validateMove(index)) {
- TicTacToe opponentCopy = copyGame.copyBoard();
- GameBase.State opponentResult = opponentCopy.play(index);
- if (opponentResult == GameBase.State.WIN) {
- winningMove = index;
- }
- }
- }
-
- thisMoveValue =
- doMinimax(copyGame, game.movesLeft, false); // else look at other moves
- if (thisMoveValue
- > bestVal) { // if better move than the current best, change the move
- bestVal = thisMoveValue;
- bestMove = i;
- }
- }
- }
- if (winningMove > -5) {
- return winningMove;
- }
- return bestMove; // return the best move when we've done everything
- }
-
- /**
- * This method simulates all the possible future moves in the game through a copy in search of
- * the best move.
- */
- public int doMinimax(TicTacToe game, int depth, boolean maximizing) {
- boolean state = game.checkWin(); // check for a win (base case stuff)
-
- if (state) {
- if (maximizing) {
- // it's the maximizing players turn and someone has won. this is not good, so return
- // a negative value
- return -10 + depth;
- } else {
- // it is the turn of the AI and it has won! this is good for us, so return a
- // positive value above 0
- return 10 - depth;
- }
- } else {
- boolean empty = false;
- for (char cell :
- game.grid) { // else, look at draw conditions. we check per cell if it's empty
- // or not
- if (cell == GameBase.EMPTY) {
- empty = true; // if a thing is empty, set to true
- break; // break the loop
- }
- }
- if (!empty
- || depth == 0) { // if the grid is full or the depth is 0 (both meaning game is
- // over) return 0 for draw
- return 0;
- }
- }
-
- int bestVal; // set the value to the highest possible
- if (maximizing) { // it's the maximizing players turn, the AI
- bestVal = -100;
- for (int i = 0; i < game.grid.length; i++) { // loop through the grid
- if (game.validateMove(i)) {
- TicTacToe copyGame = game.copyBoard();
- copyGame.play(i); // play the move on a copy board
- int value =
- doMinimax(copyGame, depth - 1, false); // keep going with the minimax
- bestVal =
- Math.max(
- bestVal,
- value); // select the best value for the maximizing player (the
- // AI)
- }
- }
- } else { // it's the minimizing players turn, the player
- bestVal = 100;
- for (int i = 0; i < game.grid.length; i++) { // loop through the grid
- if (game.validateMove(i)) {
- TicTacToe copyGame = game.copyBoard();
- copyGame.play(i); // play the move on a copy board
- int value = doMinimax(copyGame, depth - 1, true); // keep miniMaxing
- bestVal =
- Math.min(
- bestVal,
- value); // select the lowest score for the minimizing player,
- // they want to make it hard for us
- }
- }
- }
- return bestVal;
- }
-}
\ No newline at end of file
diff --git a/game/src/test/java/org/toop/game/PlayerTest.java b/game/src/test/java/org/toop/game/PlayerTest.java
new file mode 100644
index 0000000..3a3f14b
--- /dev/null
+++ b/game/src/test/java/org/toop/game/PlayerTest.java
@@ -0,0 +1,48 @@
+package org.toop.game;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class PlayerTest {
+ private Player playerA;
+ private Player playerB;
+ private Player playerC;
+
+ @BeforeEach
+ void setup() {
+ playerA = new Player("test A", 'x', 'Z', 'i');
+ playerB = new Player("test B", 'O', (char)12, (char)-34, 's');
+ playerC = new Player("test C", (char)9, '9', (char)-9, '0', 'X', 'O');
+ }
+
+ @Test
+ void testNameGetter_returnsTrueForValidName() {
+ assertEquals("test A", playerA.name());
+ assertEquals("test B", playerB.name());
+ assertEquals("test C", playerC.name());
+ }
+
+ @Test
+ void testValuesGetter_returnsTrueForValidValues() {
+ final char[] valuesA = playerA.values();
+ assertEquals('x', valuesA[0]);
+ assertEquals('Z', valuesA[1]);
+ assertEquals('i', valuesA[2]);
+
+ final char[] valuesB = playerB.values();
+ assertEquals('O', valuesB[0]);
+ assertEquals(12, valuesB[1]);
+ assertEquals((char)-34, valuesB[2]);
+ assertEquals('s', valuesB[3]);
+
+ final char[] valuesC = playerC.values();
+ assertEquals((char)9, valuesC[0]);
+ assertEquals('9', valuesC[1]);
+ assertEquals((char)-9, valuesC[2]);
+ assertEquals('0', valuesC[3]);
+ assertEquals('X', valuesC[4]);
+ assertEquals('O', valuesC[5]);
+ }
+}
\ No newline at end of file
diff --git a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java
new file mode 100644
index 0000000..a320631
--- /dev/null
+++ b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java
@@ -0,0 +1,83 @@
+package org.toop.game.tictactoe;
+
+import org.toop.game.Game;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TicTacToeAITest {
+ private TicTacToe game;
+ private TicTacToeAI ai;
+
+ @BeforeEach
+ void setup() {
+ game = new TicTacToe("AI", "AI");
+ ai = new TicTacToeAI();
+ }
+
+ @Test
+ void testBestMove_returnWinningMoveWithDepth1() {
+ // X X -
+ // O O -
+ // - - -
+ game.play(new Game.Move(0, 'X'));
+ game.play(new Game.Move(3, 'O'));
+ game.play(new Game.Move(1, 'X'));
+ game.play(new Game.Move(4, 'O'));
+
+ final Game.Move move = ai.findBestMove(game, 1);
+
+ assertNotNull(move);
+ assertEquals('X', move.value());
+ assertEquals(2, move.position());
+ }
+
+ @Test
+ void testBestMove_blockOpponentWinDepth1() {
+ // - - -
+ // O - -
+ // X X -
+ game.play(new Game.Move(6, 'X'));
+ game.play(new Game.Move(3, 'O'));
+ game.play(new Game.Move(7, 'X'));
+
+ final Game.Move move = ai.findBestMove(game, 1);
+
+ assertNotNull(move);
+ assertEquals('O', move.value());
+ assertEquals(8, move.position());
+ }
+
+ @Test
+ void testBestMove_preferCornerOnEmpty() {
+ final Game.Move move = ai.findBestMove(game, 0);
+
+ assertNotNull(move);
+ assertEquals('X', move.value());
+ assertTrue(Set.of(0, 2, 6, 8).contains(move.position()));
+ }
+
+ @Test
+ void testBestMove_findBestMoveDraw() {
+ // O X -
+ // - O X
+ // X O X
+ game.play(new Game.Move(1, 'X'));
+ game.play(new Game.Move(0, 'O'));
+ game.play(new Game.Move(5, 'X'));
+ game.play(new Game.Move(4, 'O'));
+ game.play(new Game.Move(6, 'X'));
+ game.play(new Game.Move(7, 'O'));
+ game.play(new Game.Move(8, 'X'));
+
+ final Game.Move move = ai.findBestMove(game, game.getLegalMoves().length);
+
+ assertNotNull(move);
+ assertEquals('O', move.value());
+ assertEquals(2, move.position());
+ }
+}
\ No newline at end of file
diff --git a/game/src/test/java/org/toop/tictactoe/GameBaseTest.java b/game/src/test/java/org/toop/tictactoe/GameBaseTest.java
deleted file mode 100644
index ff025c4..0000000
--- a/game/src/test/java/org/toop/tictactoe/GameBaseTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package java.org.toop.tictactoe;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.toop.game.GameBase;
-import org.toop.game.Player;
-
-class GameBaseTest {
-
- private static class TestGame extends GameBase {
- public TestGame(int size, Player p1, Player p2) {
- super(size, p1, p2);
- }
-
- @Override
- public State play(int index) {
- if (!isInside(index)) return State.INVALID;
- grid[index] = getCurrentPlayer().getSymbol();
- // Just alternate players for testing
- currentPlayer = (currentPlayer + 1) % 2;
- return State.NORMAL;
- }
- }
-
- private GameBase game;
- private Player player1;
- private Player player2;
-
- @BeforeEach
- void setUp() {
- player1 = new Player("A", 'X');
- player2 = new Player("B", 'O');
- game = new TestGame(3, player1, player2);
- }
-
- @Test
- void testConstructor_initializesGridAndPlayers() {
- assertEquals(3, game.getSize());
- assertEquals(9, game.getGrid().length);
-
- for (char c : game.getGrid()) {
- assertEquals(GameBase.EMPTY, c);
- }
-
- assertEquals(player1, game.getPlayers()[0]);
- assertEquals(player2, game.getPlayers()[1]);
- assertEquals(player1, game.getCurrentPlayer());
- }
-
- @Test
- void testIsInside_returnsTrueForValidIndices() {
- for (int i = 0; i < 9; i++) {
- assertTrue(game.isInside(i));
- }
- }
-
- @Test
- void testIsInside_returnsFalseForInvalidIndices() {
- assertFalse(game.isInside(-1));
- assertFalse(game.isInside(9));
- assertFalse(game.isInside(100));
- }
-
- @Test
- void testPlay_alternatesPlayersAndMarksGrid() {
- // First move
- assertEquals(GameBase.State.NORMAL, game.play(0));
- assertEquals('X', game.getGrid()[0]);
- assertEquals(player2, game.getCurrentPlayer());
-
- // Second move
- assertEquals(GameBase.State.NORMAL, game.play(1));
- assertEquals('O', game.getGrid()[1]);
- assertEquals(player1, game.getCurrentPlayer());
- }
-
- @Test
- void testPlay_invalidIndexReturnsInvalid() {
- assertEquals(GameBase.State.INVALID, game.play(-1));
- assertEquals(GameBase.State.INVALID, game.play(9));
- }
-}
diff --git a/game/src/test/java/org/toop/tictactoe/PlayerTest.java b/game/src/test/java/org/toop/tictactoe/PlayerTest.java
deleted file mode 100644
index ca6151a..0000000
--- a/game/src/test/java/org/toop/tictactoe/PlayerTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.toop.game.tictactoe;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.toop.game.Player;
-
-class PlayerTest {
-
- private Player playerA;
- private Player playerB;
-
- @BeforeEach
- void setup() {
- playerA = new Player("testA", 'X');
- playerB = new Player("testB", 'O');
- }
-
- @Test
- void testNameGetter_returnsTrueForValidName() {
- assertEquals("testA", playerA.getName());
- assertEquals("testB", playerB.getName());
- }
-
- @Test
- void testSymbolGetter_returnsTrueForValidSymbol() {
- assertEquals('X', playerA.getSymbol());
- assertEquals('O', playerB.getSymbol());
- }
-}
diff --git a/game/src/test/java/org/toop/tictactoe/ai/MinMaxTicTacToeTest.java b/game/src/test/java/org/toop/tictactoe/ai/MinMaxTicTacToeTest.java
deleted file mode 100644
index 9cb083f..0000000
--- a/game/src/test/java/org/toop/tictactoe/ai/MinMaxTicTacToeTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.toop.game.tictactoe.ai;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.util.Set;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.toop.game.GameBase;
-import org.toop.tictactoe.TicTacToe;
-
-/** Unit tests for MinMaxTicTacToe AI. */
-public class MinMaxTicTacToeTest {
-
- private MinMaxTicTacToe ai;
- private TicTacToe game;
-
- @BeforeEach // called before every test is done to make it work
- void setUp() {
- ai = new MinMaxTicTacToe();
- game = new TicTacToe("AI", "Human");
- }
-
- @Test
- void testBestMoveWinningMoveAvailable() {
- // Setup board where AI can win immediately
- // X = AI, O = player
- // X | X | .
- // O | O | .
- // . | . | .
- game.grid =
- new char[] {
- 'X',
- 'X',
- GameBase.EMPTY,
- 'O',
- 'O',
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY
- };
- game.movesLeft = 4;
-
- int bestMove = ai.findBestMove(game);
-
- // Ai is expected to place at index 2 to win
- assertEquals(2, bestMove);
- }
-
- @Test
- void testBestMoveBlocksOpponentWin() {
- // Setup board where player could win next turn
- // O | O | .
- // X | . | .
- // . | . | .
- game.grid =
- new char[] {
- 'O',
- 'O',
- GameBase.EMPTY,
- 'X',
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY
- };
-
- int bestMove = ai.findBestMove(game);
-
- // AI block at index 2 to continue the game
- assertEquals(2, bestMove);
- }
-
- @Test
- void testBestMoveCornerPreferredOnEmptyBoard() {
- // On empty board, center (index 4) is strongest
- int bestMove = ai.findBestMove(game);
-
- assertTrue(Set.of(0, 2, 6, 8).contains(bestMove));
- }
-
- @Test
- void testDoMinimaxScoresWinPositive() {
- // Simulate a game state where AI has already won
- TicTacToe copy = game.copyBoard();
- copy.grid =
- new char[] {
- 'X',
- 'X',
- 'X',
- 'O',
- 'O',
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY
- };
-
- int score = ai.doMinimax(copy, 5, false);
-
- assertTrue(score > 0, "AI win should yield positive score");
- }
-
- @Test
- void testDoMinimaxScoresLossNegative() {
- // Simulate a game state where human has already won
- TicTacToe copy = game.copyBoard();
- copy.grid =
- new char[] {
- 'O',
- 'O',
- 'O',
- 'X',
- 'X',
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY,
- GameBase.EMPTY
- };
-
- int score = ai.doMinimax(copy, 5, true);
-
- assertTrue(score < 0, "Human win should yield negative score");
- }
-
- @Test
- void testDoMinimaxDrawReturnsZero() {
- // Simulate a draw position
- TicTacToe copy = game.copyBoard();
- copy.grid =
- new char[] {
- 'X', 'O', 'X',
- 'X', 'O', 'O',
- 'O', 'X', 'X'
- };
-
- int score = ai.doMinimax(copy, 0, true);
-
- assertEquals(0, score, "Draw should return 0 score");
- }
-}