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 b24fe15..048d184 100644 --- a/app/src/main/java/org/toop/app/canvas/GameCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java @@ -104,7 +104,6 @@ public abstract class GameCanvas { graphics.setFill(color); graphics.fillOval(x, y, width, height); - IO.println("gothere"); } 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 index 64703a9..97630dc 100644 --- a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java @@ -8,7 +8,6 @@ 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(); @@ -20,18 +19,8 @@ public class ReversiCanvas extends GameCanvas{ drawDot(Color.WHITE,27); } public void drawLegalMoves(Game.Move[] moves){ - mostRecentLegalMoves = moves; for(Game.Move move : moves){ - IO.println("Legal Moves:" + move.position()); drawDot(new Color(1f,0,0,0.25f),move.position()); } } - public void removeLegalMoves(){ - if (mostRecentLegalMoves != null){ - for(Game.Move move : mostRecentLegalMoves){ - drawDot(Color.GRAY,move.position()); //todo get current background color or make this redraw the entire board - } - } - mostRecentLegalMoves = null; - } } 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 index e0db80c..11e8ad8 100644 --- 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 @@ -7,6 +7,7 @@ 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.Game; import org.toop.game.reversi.Reversi; import org.toop.game.reversi.ReversiAI; import org.toop.local.AppContext; @@ -19,7 +20,9 @@ public class ReversiLayer extends Layer{ 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.play(new Game.Move(cell,reversi.getCurrentPlayer())); + reload(); + canvas.drawLegalMoves(reversi.getLegalMoves()); }); reversi = new Reversi() ; reversiAI = new ReversiAI(); diff --git a/game/src/main/java/org/toop/game/reversi/Reversi.java b/game/src/main/java/org/toop/game/reversi/Reversi.java index 7e94d0a..a30f363 100644 --- a/game/src/main/java/org/toop/game/reversi/Reversi.java +++ b/game/src/main/java/org/toop/game/reversi/Reversi.java @@ -6,6 +6,7 @@ import org.toop.game.tictactoe.TicTacToe; import java.awt.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -13,7 +14,7 @@ import java.util.Set; public final class Reversi extends TurnBasedGame { private int movesTaken; public static final char FIRST_MOVE = 'B'; - private Set filledCells = new HashSet(); + private Set filledCells = new HashSet<>(); public Reversi() { super(8, 8, 2); @@ -48,8 +49,10 @@ public final class Reversi extends TurnBasedGame { char[][] boardGrid = makeBoardAGrid(); char currentPlayer = (currentTurn==0) ? 'B' : 'W'; Set adjCell = getAdjacentCells(boardGrid); + for (Point point : adjCell){ - int score = getScoreForPotentialMove(point,boardGrid,currentPlayer); + Move[] moves = getFlipsForPotentialMove(point,boardGrid,currentPlayer); + int score = moves.length; if (score > 0){ legalMoves.add(new Move(point.x + point.y * 8, currentPlayer)); } @@ -63,11 +66,11 @@ public final class Reversi extends TurnBasedGame { for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++){ //check adjacent cells for (int deltaRow = -1; deltaRow <= 1; deltaRow++){ //orthogonally and diagonally int newX = point.x + deltaColumn, newY = point.y + deltaRow; - if (deltaColumn == 0 || deltaRow == 0 //continue if out of bounds + if (deltaColumn == 0 && deltaRow == 0 //continue if out of bounds || !isOnBoard(newX, newY)) { continue; } - if (boardGrid[newX][newY] == Game.EMPTY) { //check if the cell is empty + if (boardGrid[newY][newX] == Game.EMPTY) { //check if the cell is empty possibleCells.add(new Point(newX, newY)); //and then add it to the set of possible moves } } @@ -76,66 +79,75 @@ public final class Reversi extends TurnBasedGame { return possibleCells; } - public int getScoreForPotentialMove(Point point, char[][] boardGrid, char currentPlayer) { - int score = 0; + public Move[] getFlipsForPotentialMove(Point point, char[][] boardGrid, char currentPlayer) { + final ArrayList movesToFlip = new ArrayList<>(); for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) { for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { if (deltaColumn == 0 && deltaRow == 0){ continue; } - score += getScoreInDirection(point,boardGrid,currentPlayer,deltaColumn,deltaRow); + Move[] moves = getFlipsInDirection(point,boardGrid,currentPlayer,deltaColumn,deltaRow); + if (moves != null) { + movesToFlip.addAll(Arrays.asList(moves)); + } } } - return score; + return movesToFlip.toArray(new Move[0]); } - private int getScoreInDirection(Point point, char[][] boardGrid, char currentPlayer, int dirX, int dirY) { + private Move[] getFlipsInDirection(Point point, char[][] boardGrid, char currentPlayer, int dirX, int dirY) { char opponent = getOpponent(currentPlayer); + final ArrayList movesToFlip = new ArrayList<>(); int x = point.x + dirX; int y = point.y + dirY; - int count = 0; if (!isOnBoard(x, y) || boardGrid[y][x] != opponent) { - return 0; + return null; } while (isOnBoard(x, y) && boardGrid[y][x] == opponent) { - count++; + + movesToFlip.add(new Move(x+y*8, currentPlayer)); x += dirX; y += dirY; } - if (isOnBoard(x, y) && boardGrid[y][x] == currentPlayer) { - return count; + return movesToFlip.toArray(new Move[0]); } - - return 0; + return null; } private boolean isOnBoard(int x, int y) { return x >= 0 && x < 8 && y >= 0 && y < 8; } - private char getOpponent(char currentPlayer){ - if (currentPlayer == 'B') { - return 'W'; - } - else { - return 'B'; - } - } - public char[][] makeBoardAGrid() { char[][] boardGrid = new char[8][8]; for (int i = 0; i < 64; i++) { - boardGrid[i / 8][i % 8] = board[i]; + boardGrid[i / 8][i % 8] = board[i]; //boardGrid[y / row] [x / column] } return boardGrid; } @Override public State play(Move move) { - filledCells.add(new Point(move.position() / 8, move.position() % 8)); - nextTurn(); + Move[] legalMoves = getLegalMoves(); + boolean moveIsLegal = false; + for (Move legalMove : legalMoves) { + if (move.equals(legalMove)) { + moveIsLegal = true; + break; + } + } + if (moveIsLegal) { + Move[] moves = getFlipsForPotentialMove(new Point(move.position()%8,move.position()/8), makeBoardAGrid(), move.value()); + board[move.position()] = move.value(); + for (Move m : moves) { + board[m.position()] = m.value(); + } + updateFilledCellsSet(); + nextTurn(); + return State.NORMAL; + } return null; } @@ -152,6 +164,15 @@ public final class Reversi extends TurnBasedGame { } } + private char getOpponent(char currentPlayer){ + if (currentPlayer == 'B') { + return 'W'; + } + else { + return 'B'; + } + } + public Game.Score getScore(){ int player1Score = 0, player2Score = 0; for (int count = 0; count < 63; count++) { diff --git a/game/src/main/java/org/toop/game/reversi/ReversiAI.java b/game/src/main/java/org/toop/game/reversi/ReversiAI.java index c859a3a..4f0865e 100644 --- a/game/src/main/java/org/toop/game/reversi/ReversiAI.java +++ b/game/src/main/java/org/toop/game/reversi/ReversiAI.java @@ -6,6 +6,6 @@ import org.toop.game.Game; public final class ReversiAI extends AI { @Override public Game.Move findBestMove(Reversi game, int depth) { - return null; + return game.getLegalMoves()[0]; } } diff --git a/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java b/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java index 270f4cf..6dfa903 100644 --- a/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java +++ b/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java @@ -2,7 +2,7 @@ package org.toop.game.tictactoe; import org.toop.game.Game; -import java.util.Set; +import java.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,46 +30,88 @@ class ReversiTest { assertEquals('B',game.board[35]); assertEquals('W',game.board[36]); } + @Test void testGetLegalMovesAtStart() { Game.Move[] moves = game.getLegalMoves(); + List expectedMoves = List.of( + new Game.Move(19,'B'), + new Game.Move(26,'B'), + new Game.Move(37,'B'), + new Game.Move(44,'B') + ); 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]); + assertMovesMatchIgnoreOrder(expectedMoves, Arrays.asList(moves)); } + + private void assertMovesMatchIgnoreOrder(List expected, List actual) { + assertEquals(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) { + assertTrue(actual.contains(expected.get(i))); + assertTrue(expected.contains(actual.get(i))); + } + } + @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() { + void testCountScoreCorrectlyAtStart() { + long start = System.nanoTime(); Game.Score score = game.getScore(); assertEquals(2, score.player1Score()); // Black assertEquals(2, score.player2Score()); // White + long end = System.nanoTime(); + IO.println((end-start)); } + + @Test + void testCountScoreCorrectlyAtEnd() { + for (int i = 0; i < 10; i++){ + game = new Reversi(); + Game.Move[] legalMoves = game.getLegalMoves(); + while(legalMoves.length > 0) { + game.play(legalMoves[(int)(Math.random()*legalMoves.length)]); + legalMoves = game.getLegalMoves(); + } + Game.Score score = game.getScore(); + IO.println(score.player1Score()); + IO.println(score.player2Score()); + char[][] grid = game.makeBoardAGrid(); + for (char[] chars : grid) { + IO.println(Arrays.toString(chars)); + } + + } + + } + @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;