From efca808e1cf3a5bb1ce6b1fbd6915b03972696fd Mon Sep 17 00:00:00 2001 From: ramollia <@> Date: Fri, 3 Oct 2025 01:33:49 +0200 Subject: [PATCH] small amount of refactoring. might break everything :). --- app/src/main/java/org/toop/app/App.java | 9 +- .../java/org/toop/app/canvas/GameCanvas.java | 77 ++++++++ .../org/toop/app/canvas/TicTacToeCanvas.java | 81 +++++++++ .../main/java/org/toop/app/menu/MainMenu.java | 4 +- app/src/main/java/org/toop/app/menu/Menu.java | 1 - .../org/toop/app/menu/game/TicTacToeMenu.java | 133 -------------- game/src/main/java/org/toop/game/Game.java | 31 +--- game/src/main/java/org/toop/game/Player.java | 3 - .../java/org/toop/game/TurnBasedGame.java | 26 +++ .../org/toop/game/tictactoe/TicTacToe.java | 56 +++--- .../test/java/org/toop/game/PlayerTest.java | 48 ----- .../toop/game/tictactoe/TicTacToeAITest.java | 166 +++++++++--------- 12 files changed, 302 insertions(+), 333 deletions(-) create mode 100644 app/src/main/java/org/toop/app/canvas/GameCanvas.java create mode 100644 app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java delete mode 100644 app/src/main/java/org/toop/app/menu/game/TicTacToeMenu.java delete mode 100644 game/src/main/java/org/toop/game/Player.java create mode 100644 game/src/main/java/org/toop/game/TurnBasedGame.java delete mode 100644 game/src/test/java/org/toop/game/PlayerTest.java diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index bf797b6..a3b29c7 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -1,5 +1,6 @@ package org.toop.app; +import org.toop.app.canvas.TicTacToeCanvas; import org.toop.app.menu.MainMenu; import org.toop.app.menu.Menu; @@ -47,7 +48,6 @@ public final class App extends Application { pane = new StackPane(background, box); pane.getStylesheets().add(ResourceManager.get(CssAsset.class, "quit.css").getUrl()); - } } @@ -60,7 +60,7 @@ public final class App extends Application { final StackPane root = new StackPane(new MainMenu().getPane()); final Scene scene = new Scene(root); - scene.getStylesheets().add(((CssAsset) ResourceManager.get("app.css")).getUrl()); + scene.getStylesheets().add(((CssAsset)ResourceManager.get("app.css")).getUrl()); stage.setTitle("pism"); stage.setMinWidth(1080); @@ -85,10 +85,13 @@ public final class App extends Application { App.width = (int)stage.getWidth(); App.height = (int)stage.getHeight(); + App.isQuitting = false; + new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(0.1)).asyncPostEvent(); - App.isQuitting = false; + TicTacToeCanvas canvas = new TicTacToeCanvas(); + root.getChildren().addLast(canvas.getCanvas()); } public static void quitPopup() { diff --git a/app/src/main/java/org/toop/app/canvas/GameCanvas.java b/app/src/main/java/org/toop/app/canvas/GameCanvas.java new file mode 100644 index 0000000..0255504 --- /dev/null +++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java @@ -0,0 +1,77 @@ +package org.toop.app.canvas; + +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.input.MouseButton; + +public abstract class GameCanvas { + protected record Cell(float x, float y, float width, float height) {} + + protected final int width; + protected final int height; + + protected final Canvas canvas; + protected final GraphicsContext graphics; + + protected final int rows; + protected final int columns; + + protected final int gapSize; + + protected final Cell[] cells; + + protected GameCanvas(int width, int height, int rows, int columns, int gapSize) { + final Canvas canvas = new Canvas(width, height); + final GraphicsContext graphics = canvas.getGraphicsContext2D(); + + final Cell[] cells = new Cell[rows * columns]; + + final float cellWidth = ((float)width - (rows - 1) * gapSize) / rows; + final float cellHeight = ((float)height - (columns - 1) * gapSize) / columns; + + for (int y = 0; y < columns; y++) { + final float startY = y * cellHeight + y * gapSize; + + for (int x = 0; x < rows; x++) { + final float startX = x * cellWidth + x * gapSize; + cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight); + } + } + + canvas.setOnMouseClicked(event -> { + final MouseButton button = event.getButton(); + + if (button != MouseButton.PRIMARY && button != MouseButton.SECONDARY) { + return; + } + + final int column = (int)((event.getX() / width) * rows); + final int row = (int)((event.getY() / height) * columns); + + event.consume(); + onCellClicked(row * rows + column, button == MouseButton.PRIMARY); + }); + + this.width = width; + this.height = height; + + this.canvas = canvas; + this.graphics = graphics; + + this.rows = rows; + this.columns = columns; + + this.gapSize = gapSize; + + this.cells = cells; + } + + protected void clearCell(int cell) { + assert cell >= 0 && cell < cells.length; + graphics.clearRect(cells[cell].x(), cells[cell].y(), cells[cell].width(), cells[cell].height()); + } + + protected abstract void onCellClicked(int cell, boolean primary); + + public Canvas getCanvas() { return canvas; } +} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java new file mode 100644 index 0000000..dfe7325 --- /dev/null +++ b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java @@ -0,0 +1,81 @@ +package org.toop.app.canvas; + +import javafx.scene.paint.Color; +import org.toop.app.App; +import org.toop.game.Game; +import org.toop.game.tictactoe.TicTacToe; + +public class TicTacToeCanvas extends GameCanvas { + private final TicTacToe game; + + public TicTacToeCanvas() { + super(App.getHeight(), App.getHeight(), 3, 3, 10); + game = new TicTacToe(); + + graphics.setFill(Color.CYAN); + + for (int x = 1; x < rows; x++) { + graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height); + } + + for (int y = 1; y < columns; y++) { + graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize); + } + } + + public void placeX(int cell) { + graphics.setStroke(Color.ORANGERED); + graphics.setLineWidth(gapSize); + + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; + + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + graphics.strokeLine(x, y, x + width, y + height); + graphics.strokeLine(x + width, y, x, y + height); + } + + public void placeO(int cell) { + graphics.setStroke(Color.DEEPSKYBLUE); + graphics.setLineWidth(gapSize); + + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; + + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + graphics.strokeOval(x, y, width, height); + } + + @Override + protected void onCellClicked(int cell, boolean primary) { + for (final Game.Move move : game.getLegalMoves()) { + if (move.position() == cell) { + if (move.value() == 'X') { + placeX(cell); + } else { + placeO(cell); + } + + final Game.State state = game.play(move); + + if (state == Game.State.WIN) { + graphics.setFill(Color.GREEN); + graphics.fillRect(cells[4].x(), cells[4].y(), cells[4].width(), cells[4].height()); + + for (int i = 0; i < game.board.length; i++) { + if (game.board[i] != move.value()) { + clearCell(i); + } + } + } else if (state == Game.State.DRAW) { + graphics.setFill(Color.DARKORANGE); + graphics.fillRect(cells[4].x(), cells[4].y(), cells[4].width(), cells[4].height()); + } + } + } + } +} \ 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 index c369806..f170ded 100644 --- a/app/src/main/java/org/toop/app/menu/MainMenu.java +++ b/app/src/main/java/org/toop/app/menu/MainMenu.java @@ -6,14 +6,12 @@ import org.toop.app.GameType; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.layout.*; -import org.toop.app.menu.game.TicTacToeMenu; -import org.toop.game.tictactoe.TicTacToe; public final class MainMenu extends Menu { public MainMenu() { final Region background = createBackground(); - final Button tictactoe = createButton("Tic Tac Toe", () -> { App.activate(new TicTacToeMenu(new TicTacToe("player 1", true, "player 2", true))); }); + final Button tictactoe = createButton("Tic Tac Toe", () -> { App.activate(new GameSelectMenu(GameType.TICTACTOE)); }); final Button reversi = createButton("Reversi", () -> { App.activate(new GameSelectMenu(GameType.REVERSI)); }); final VBox gamesBox = new VBox(10, tictactoe, reversi); diff --git a/app/src/main/java/org/toop/app/menu/Menu.java b/app/src/main/java/org/toop/app/menu/Menu.java index 82c12ee..9e9ac29 100644 --- a/app/src/main/java/org/toop/app/menu/Menu.java +++ b/app/src/main/java/org/toop/app/menu/Menu.java @@ -1,6 +1,5 @@ package org.toop.app.menu; -import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; diff --git a/app/src/main/java/org/toop/app/menu/game/TicTacToeMenu.java b/app/src/main/java/org/toop/app/menu/game/TicTacToeMenu.java deleted file mode 100644 index 6295b7f..0000000 --- a/app/src/main/java/org/toop/app/menu/game/TicTacToeMenu.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.toop.app.menu.game; - -import javafx.scene.paint.Color; -import org.toop.game.Game; -import org.toop.game.Player; -import org.toop.game.tictactoe.TicTacToe; -import org.toop.game.tictactoe.TicTacToeAI; - -import javax.management.RuntimeErrorException; -import java.util.concurrent.*; - -public final class TicTacToeMenu extends GameMenu { - private final TicTacToe game; - private final TicTacToeAI ai; - -// private final ExecutorService executor = Executors.newFixedThreadPool(1); - private final BlockingQueue moveQueue = new LinkedBlockingQueue<>(); - - public TicTacToeMenu(TicTacToe game) { - super(3, 3, 10); - - graphics.setFill(Color.CYAN); - - for (int x = 1; x < rows; x++) { - graphics.fillRect(cells[x].x - gapSize, 0, gapSize, size); - } - - for (int y = 1; y < columns; y++) { - graphics.fillRect(0, cells[y * rows].y - gapSize, size, gapSize); - } - - this.game = game; - ai = new TicTacToeAI(); - - canvas.setOnMouseClicked(event -> { - for (int i = 0; i < cells.length; i++) { - if (cells[i].check((float) event.getX(), (float) event.getY())) { - final Game.Move move = new Game.Move(i, game.getCurrentPlayer().values()[0]); - play(move); - } - } - }); - - new Thread(this::gameThread).start(); - } - - private void play(Game.Move move) { - final Game.Move[] legalMoves = game.getLegalMoves(); - - boolean isLegal = false; - - for (final Game.Move legalMove : legalMoves) { - if (legalMove.position() == move.position() && legalMove.value() == move.value()) { - isLegal = true; - break; - } - } - - if (!isLegal) { - return; - } - - try { moveQueue.put(move); } - catch (InterruptedException _) {} - } - - private void placeX(int cell) { - graphics.setStroke(Color.ORANGERED); - graphics.setLineWidth(gapSize); - - final float x = cells[cell].x + gapSize; - final float y = cells[cell].y + gapSize; - - final float width = cells[cell].width - gapSize * 2; - final float height = cells[cell].height - gapSize * 2; - - graphics.strokeLine(x, y, x + width, y + height); - graphics.strokeLine(x + width, y, x, y + height); - } - - private void placeO(int cell) { - graphics.setStroke(Color.DEEPSKYBLUE); - graphics.setLineWidth(gapSize); - - final float x = cells[cell].x + gapSize; - final float y = cells[cell].y + gapSize; - - final float width = cells[cell].width - gapSize * 2; - final float height = cells[cell].height - gapSize * 2; - - graphics.strokeOval(x, y, width, height); - } - - private void gameThread() { - boolean running = true; - - while(running) { - final Player currentPlayer = game.getCurrentPlayer(); - - try { - Game.Move move; - - if (!currentPlayer.isAI()) { - try { move = moveQueue.take(); } - catch (InterruptedException _) { return; } - } else { - move = ai.findBestMove(game, 9); - } - - assert move != null; - final Game.State state = game.play(move); - - if (move.value() == 'X') { - placeX(move.position()); - } else { - placeO(move.position()); - } - - switch (state) { - case NORMAL: break; - - case DRAW: - case LOSE: - case WIN: - running = false; - break; - } - } catch (RuntimeException e) { - return; - } - } - } -} \ 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 index a4220ed..71b2214 100644 --- a/game/src/main/java/org/toop/game/Game.java +++ b/game/src/main/java/org/toop/game/Game.java @@ -4,52 +4,31 @@ import java.util.Arrays; public abstract class Game { public enum State { - NORMAL, DRAW, LOSE, WIN, + NORMAL, 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; + public final int rowSize; + public final int columnSize; + public final char[] board; - protected final Player[] players; - protected int currentPlayer; - - protected Game(int rowSize, int columnSize, Player... players) { + protected Game(int rowSize, int columnSize) { 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(); diff --git a/game/src/main/java/org/toop/game/Player.java b/game/src/main/java/org/toop/game/Player.java deleted file mode 100644 index 22e46c1..0000000 --- a/game/src/main/java/org/toop/game/Player.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.toop.game; - -public record Player(String name, boolean isAI, char... values) {} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/TurnBasedGame.java b/game/src/main/java/org/toop/game/TurnBasedGame.java new file mode 100644 index 0000000..4800abd --- /dev/null +++ b/game/src/main/java/org/toop/game/TurnBasedGame.java @@ -0,0 +1,26 @@ +package org.toop.game; + +public abstract class TurnBasedGame extends Game { + public final int turns; + + protected int currentTurn; + + protected TurnBasedGame(int rowSize, int columnSize, int turns) { + assert turns >= 2; + + super(rowSize, columnSize); + this.turns = turns; + } + + protected TurnBasedGame(TurnBasedGame other) { + super(other); + turns = other.turns; + currentTurn = other.currentTurn; + } + + protected void nextTurn() { + currentTurn = (currentTurn + 1) % turns; + } + + public int getCurrentTurn() { return currentTurn; } +} \ 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 index dd50eeb..ee2a5b9 100644 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java +++ b/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java @@ -1,15 +1,14 @@ package org.toop.game.tictactoe; -import org.toop.game.Game; -import org.toop.game.Player; +import org.toop.game.TurnBasedGame; import java.util.ArrayList; -public final class TicTacToe extends Game { +public final class TicTacToe extends TurnBasedGame { private int movesLeft; - public TicTacToe(String player1, boolean isPlayer1AI, String player2, boolean isPlayer2AI) { - super(3, 3, new Player(player1, isPlayer1AI, 'X'), new Player(player2, isPlayer2AI, 'O')); + public TicTacToe() { + super(3, 3, 2); movesLeft = board.length; } @@ -20,11 +19,12 @@ public final class TicTacToe extends Game { @Override public Move[] getLegalMoves() { - final ArrayList legalMoves = new ArrayList<>(); + final ArrayList legalMoves = new ArrayList<>(); + final char currentValue = getCurrentValue(); for (int i = 0; i < board.length; i++) { if (board[i] == EMPTY) { - legalMoves.add(new Move(i, getCurrentPlayer().values()[0])); + legalMoves.add(new Move(i, currentValue)); } } @@ -35,7 +35,7 @@ public final class TicTacToe extends Game { public State play(Move move) { assert move != null; assert move.position() >= 0 && move.position() < board.length; - assert move.value() == getCurrentPlayer().values()[0]; + assert move.value() == getCurrentValue(); board[move.position()] = move.value(); movesLeft--; @@ -44,12 +44,12 @@ public final class TicTacToe extends Game { return State.WIN; } - nextPlayer(); + nextTurn(); if (movesLeft <= 2) { - if (checkDraw(new TicTacToe(this))) { - return State.DRAW; - } + if (movesLeft <= 0 || checkForEarlyDraw(this)) { + return State.DRAW; + } } return State.NORMAL; @@ -60,18 +60,14 @@ public final class TicTacToe extends Game { 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]) { + 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]) { + if (board[i] != EMPTY && board[i] == board[i + 3] && board[i] == board[i + 6]) { return true; } } @@ -85,25 +81,19 @@ public final class TicTacToe extends Game { return board[2] != EMPTY && board[2] == board[4] && board[2] == board[6]; } - public boolean checkDraw(TicTacToe game) { - if (game.checkForWin()) { - return false; - } - if (game.movesLeft == 0) { - return true; - } - // try every move on a legal copy - for (Move move : game.getLegalMoves()) { - TicTacToe copy = new TicTacToe(game); - State result = copy.play(move); + private boolean checkForEarlyDraw(TicTacToe game) { + for (final Move move : game.getLegalMoves()) { + final TicTacToe copy = new TicTacToe(game); - if (result == State.WIN) { - return false; - } - if (!checkDraw(copy)) { + if (copy.play(move) == State.WIN || !checkForEarlyDraw(copy)) { return false; } } + return true; } + + private char getCurrentValue() { + return currentTurn == 0? 'X' : 'O'; + } } \ 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 deleted file mode 100644 index 298c84b..0000000 --- a/game/src/test/java/org/toop/game/PlayerTest.java +++ /dev/null @@ -1,48 +0,0 @@ -//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 index fe15900..6be92f5 100644 --- a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java +++ b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java @@ -1,83 +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 +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 = 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