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..048d184 100644 --- a/app/src/main/java/org/toop/app/canvas/GameCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java @@ -95,7 +95,7 @@ public abstract class GameCanvas { } } - public void draw(Color color, int cell) { + public void drawDot(Color color, int cell) { final float x = cells[cell].x() + gapSize; final float y = cells[cell].y() + gapSize; @@ -103,7 +103,7 @@ public abstract class GameCanvas { final float height = cells[cell].height() - gapSize * 2; graphics.setFill(color); - graphics.fillRect(x, y, width, height); + graphics.fillOval(x, y, width, height); } public void resize(int width, int height) { diff --git a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java new file mode 100644 index 0000000..4ad51da --- /dev/null +++ b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java @@ -0,0 +1,36 @@ +package org.toop.app.canvas; + +import javafx.scene.layout.Background; +import javafx.scene.paint.Color; +import org.toop.game.Game; + + +import java.util.function.Consumer; + +public class ReversiCanvas extends GameCanvas{ + private Game.Move[] mostRecentLegalMoves; + public ReversiCanvas(Color color, int width, int height, Consumer onCellClicked) { + super(color, width, height, 8, 8, 10, true, onCellClicked); + drawStartingDots(); + } + public void drawStartingDots(){ + drawDot(Color.BLACK,28); + drawDot(Color.WHITE,36); + drawDot(Color.BLACK,35); + drawDot(Color.WHITE,27); + } + public void drawLegalMoves(Game.Move[] moves){ + mostRecentLegalMoves = moves; + for(Game.Move move : moves){ + drawDot(Color.RED,move.position()); + } + } + public void removeLegalMoves(){ + if (mostRecentLegalMoves != null){ + for(Game.Move move : mostRecentLegalMoves){ + drawDot(Color.GRAY,move.position()); //todo get current background color + } + } + mostRecentLegalMoves = null; + } +} diff --git a/app/src/main/java/org/toop/app/layer/layers/MainLayer.java b/app/src/main/java/org/toop/app/layer/layers/MainLayer.java index b183ebf..fa5ecdb 100644 --- a/app/src/main/java/org/toop/app/layer/layers/MainLayer.java +++ b/app/src/main/java/org/toop/app/layer/layers/MainLayer.java @@ -5,6 +5,7 @@ import org.toop.app.layer.Container; import org.toop.app.layer.Layer; import org.toop.app.layer.NodeBuilder; import org.toop.app.layer.containers.VerticalContainer; +import org.toop.app.layer.layers.game.ReversiLayer; import org.toop.local.AppContext; import javafx.geometry.Pos; @@ -24,7 +25,7 @@ public final class MainLayer extends Layer { }); final var othelloButton = NodeBuilder.button(AppContext.getString("othello"), () -> { - App.activate(new MultiplayerLayer()); + App.activate(new ReversiLayer()); }); final var creditsButton = NodeBuilder.button(AppContext.getString("credits"), () -> { diff --git a/app/src/main/java/org/toop/app/layer/layers/game/ReversiLayer.java b/app/src/main/java/org/toop/app/layer/layers/game/ReversiLayer.java new file mode 100644 index 0000000..1e524f3 --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/layers/game/ReversiLayer.java @@ -0,0 +1,59 @@ +package org.toop.app.layer.layers.game; +import javafx.geometry.Pos; +import javafx.scene.paint.Color; +import org.toop.app.App; +import org.toop.app.canvas.ReversiCanvas; +import org.toop.app.layer.*; +import org.toop.app.layer.containers.HorizontalContainer; +import org.toop.app.layer.containers.VerticalContainer; +import org.toop.app.layer.layers.MainLayer; +import org.toop.game.reversi.Reversi; +import org.toop.game.reversi.ReversiAI; +import org.toop.local.AppContext; + +public class ReversiLayer extends Layer{ + private ReversiCanvas canvas; + private Reversi reversi; + private ReversiAI reversiAI; + public ReversiLayer(){ + super("bg-secondary"); //make reversiboard background dark green + + canvas = new ReversiCanvas(Color.GREEN,(App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> { + IO.println("clicked reversi cell: "+cell); + }); + reversi = new Reversi() ; + reversiAI = new ReversiAI(); + + + reload(); + } + + @Override + public void reload() { + popAll(); + canvas.resize((App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75); + + for (int i = 0; i < reversi.board.length; i++) { + final char value = reversi.board[i]; + + if (value == 'B') { + canvas.drawDot(Color.BLACK, i); + } else if (value == 'W') { + canvas.drawDot(Color.WHITE, i); + } + } + + final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> { + App.activate(new MainLayer()); + }); + + final Container controlContainer = new VerticalContainer(5); + controlContainer.addNodes(backButton); + + final Container informationContainer = new HorizontalContainer(15); + + addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0); + addContainer(informationContainer, Pos.TOP_LEFT, 2, 2, 0, 0); + addGameCanvas(canvas, Pos.CENTER, 0, 0); + } +} diff --git a/game/src/main/java/org/toop/game/Game.java b/game/src/main/java/org/toop/game/Game.java index 71b2214..d9cb7b8 100644 --- a/game/src/main/java/org/toop/game/Game.java +++ b/game/src/main/java/org/toop/game/Game.java @@ -9,6 +9,8 @@ public abstract class Game { public record Move(int position, char value) {} + public record Score(int player1Score, int player2Score) {} + public static final char EMPTY = (char)0; public final int rowSize; diff --git a/game/src/main/java/org/toop/game/othello/Othello.java b/game/src/main/java/org/toop/game/othello/Othello.java deleted file mode 100644 index 3012eac..0000000 --- a/game/src/main/java/org/toop/game/othello/Othello.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.toop.game.othello; - -import org.toop.game.TurnBasedGame; - -public final class Othello extends TurnBasedGame { - Othello() { - super(8, 8, 2); - } - - @Override - public Move[] getLegalMoves() { - return new Move[0]; - } - - @Override - public State play(Move move) { - return null; - } -} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/othello/OthelloAI.java b/game/src/main/java/org/toop/game/othello/OthelloAI.java deleted file mode 100644 index 8957387..0000000 --- a/game/src/main/java/org/toop/game/othello/OthelloAI.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.toop.game.othello; - -import org.toop.game.AI; -import org.toop.game.Game; - -public final class OthelloAI extends AI { - @Override - public Game.Move findBestMove(Othello game, int depth) { - return null; - } -} diff --git a/game/src/main/java/org/toop/game/reversi/Reversi.java b/game/src/main/java/org/toop/game/reversi/Reversi.java new file mode 100644 index 0000000..59dcdae --- /dev/null +++ b/game/src/main/java/org/toop/game/reversi/Reversi.java @@ -0,0 +1,74 @@ +package org.toop.game.reversi; + +import org.toop.game.Game; +import org.toop.game.TurnBasedGame; +import org.toop.game.tictactoe.TicTacToe; + +public final class Reversi extends TurnBasedGame { + private int movesTaken; + public static final char FIRST_MOVE = 'B'; + + + public Reversi() { + super(8, 8, 2); + addStartPieces(); + } + + public Reversi(Reversi other) { + super(other); + this.movesTaken = other.movesTaken; + } + + + private void addStartPieces() { + board[27] = 'W'; + board[28] = 'B'; + board[35] = 'B'; + board[36] = 'W'; + + } + + @Override + public Move[] getLegalMoves() { + char[][] boardGrid = makeBoardAGrid(); + if(currentTurn == 1){ + + } + return new Move[0]; + } + + public char[][] makeBoardAGrid() { + char[][] boardGrid = new char[8][8]; + for (int i = 0; i < 64; i++) { + boardGrid[i / 8][i % 8] = board[i]; + } + return boardGrid; + } + @Override + public State play(Move move) { + return null; + } + + public char getCurrentPlayer() { + if (currentTurn == 0){ + return 'B'; + } + else { + return 'W'; + } + } + + public Game.Score getScore(){ + int player1Score = 0, player2Score = 0; + for (int count = 0; count < 63; count++) { + if (board[count] == 'W') { + player1Score += 1; + } + if (board[count] == 'B') { + player2Score += 1; + } + } + return new Game.Score(player1Score, player2Score); + } + +} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/reversi/ReversiAI.java b/game/src/main/java/org/toop/game/reversi/ReversiAI.java new file mode 100644 index 0000000..c859a3a --- /dev/null +++ b/game/src/main/java/org/toop/game/reversi/ReversiAI.java @@ -0,0 +1,11 @@ +package org.toop.game.reversi; + +import org.toop.game.AI; +import org.toop.game.Game; + +public final class ReversiAI extends AI { + @Override + public Game.Move findBestMove(Reversi game, int depth) { + return null; + } +} diff --git a/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java b/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java new file mode 100644 index 0000000..270f4cf --- /dev/null +++ b/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java @@ -0,0 +1,79 @@ +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 org.toop.game.reversi.Reversi; +import org.toop.game.reversi.ReversiAI; + +import static org.junit.jupiter.api.Assertions.*; + +class ReversiTest { + private Reversi game; + private ReversiAI ai; + + @BeforeEach + void setup() { + game = new Reversi(); + ai = new ReversiAI(); + } + + + @Test + void testCorrectStartPiecesPlaced() { + assertNotNull(game); + assertEquals('W',game.board[27]); + assertEquals('B',game.board[28]); + assertEquals('B',game.board[35]); + assertEquals('W',game.board[36]); + } + @Test + void testGetLegalMovesAtStart() { + Game.Move[] moves = game.getLegalMoves(); + assertNotNull(moves); + assertTrue(moves.length > 0); + assertEquals(new Game.Move(19,'B'),moves[0]); + assertEquals(new Game.Move(26,'B'),moves[0]); + assertEquals(new Game.Move(37,'B'),moves[0]); + assertEquals(new Game.Move(44,'B'),moves[0]); + } + @Test + void testMakeValidMoveFlipsPieces() { + game.play(new Game.Move(19, 'B')); + assertEquals('B', game.board[19]); + assertEquals('B', game.board[27], "Piece should have flipped to B"); + } + @Test + void testMakeInvalidMoveDoesNothing() { + char[] before = game.board.clone(); + game.play(new Game.Move(0, 'B')); + assertArrayEquals(before, game.board, "Board should not change on invalid move"); + } + @Test + void testTurnSwitchesAfterValidMove() { + char current = game.getCurrentPlayer(); + game.play(game.getLegalMoves()[0]); + assertNotEquals(current, game.getCurrentPlayer(), "Player turn should switch after a valid move"); + } + @Test + void testCountScoreCorrectly() { + Game.Score score = game.getScore(); + assertEquals(2, score.player1Score()); // Black + assertEquals(2, score.player2Score()); // White + } + @Test + void testAISelectsLegalMove() { + Game.Move move = ai.findBestMove(game,4); + assertNotNull(move); + assertTrue(containsMove(game.getLegalMoves(),move), "AI should always choose a legal move"); + } + private boolean containsMove(Game.Move[] moves, Game.Move move) { + for (Game.Move m : moves) { + if (m.equals(move)) return true; + } + return false; + } +}