diff --git a/src/main/java/dotsandboxes/DotsAndBoxesGrid.java b/src/main/java/dotsandboxes/DotsAndBoxesGrid.java index a9e7c5b6639e8a2a8728809d9b17c8d155baf9b6..f6f40ee2b31edb2c49271e343e566c08c63fb93f 100644 --- a/src/main/java/dotsandboxes/DotsAndBoxesGrid.java +++ b/src/main/java/dotsandboxes/DotsAndBoxesGrid.java @@ -1,60 +1,27 @@ package dotsandboxes; - import java.util.ArrayList; import java.util.Arrays; import java.util.function.Consumer; -/** - * The state of a dots and boxes grid. - * - * A (4, 3) dots and boxes grid looks like this: - * - * *-*-*-* - * | | | | - * *-*-*-* - * | | | | - * *-*-*-* - * - * Notice that: - * - * - for each row, there is one less horizontal than the number of corner dots - * - for each row, there are as many verticals as there are corner dots - * - for each row, there is one less box than the number of corner dots - * - for each column, there is one less vertical than the number of corner dots. - * - for each column, there are as many horizontals as there are corner dots. - * - for each column, there is one less box than the number of corner dots - * - * For example, in this (4, 3) grid, there are (3, 3) horizontal lines, and (4, 2) vertical lines, and (3, 2) boxes. - * - * We number all lines and boxes by their top-left coordinate. - * - * In Java 14+, we might use a Record class for this, but we're using 11+ as an LTS version, so we don't have that yet. - */ public class DotsAndBoxesGrid { final int width; final int height; - /** The horizontal lines in the grid. True if drawn. */ private boolean[][] horizontals; - - /** The vertical lines in the grid. True if drawn. */ private boolean[][] verticals; - - /** Which owner (if any) claimed any given box. */ private int[][] boxOwners; - /** A list of functions to notify when there is an update */ private ArrayList<Consumer<DotsAndBoxesGrid>> watchers = new ArrayList<>(); final int players; private int player = 1; + public int getPlayer() { return player; } - /** Moves to the next player */ private void nextPlayer() { player++; if (player > players) { @@ -78,44 +45,29 @@ public class DotsAndBoxesGrid { } } - /** Listens to this grid for changes */ public void addConsumer(Consumer<DotsAndBoxesGrid> consumer) { watchers.add(consumer); } - /** Returns whether a horizontal line has been drawn */ public boolean getHorizontal(int x, int y) { return horizontals[x][y]; } - /** Returns whether a vertical line has been drawn */ public boolean getVertical(int x, int y) { return verticals[x][y]; } - /** Returns which player owns a box. By convention, 0 is unowned. */ public int getBoxOwner(int x, int y) { return boxOwners[x][y]; } - - /** - * Checks whether a box has been fully drawn (all four sides) - * @param x coordinate of the left side of the box - * @param y coordinate of the top of the box - * @return true if all four sides have been drawn. - */ public boolean boxComplete(int x, int y) { if (x >= width - 1 || x < 0 || y >= height - 1 || y < 0) { return false; } - - // A box is complete if the north and south horizontals and the east and west verticals have all been drawn. - // FIXME: You'll need to fix this code (after writing a test first). - return true; + return getHorizontal(x, y) && getHorizontal(x, y + 1) && getVertical(x, y) && getVertical(x + 1, y); } - /** Tries to claim a box for a player. If the box is complete, sets the ownership and returns true. */ private boolean claimBox(int x, int y, int p) { if (boxComplete(x, y)) { boxOwners[x][y] = player; @@ -125,12 +77,6 @@ public class DotsAndBoxesGrid { } } - /** - * "Draws" a horizontal line, from grid point (x, y) to (x + 1, y). (i.e. sets that line to true) - * @param x - * @param y - * @return true if it completes a box - */ public boolean drawHorizontal(int x, int y, int player) { if (x >= width - 1 || x < 0) { throw new IndexOutOfBoundsException(String.format("x was %d, which is out of range. Range is 0 to %d", x, width - 1)); @@ -138,13 +84,13 @@ public class DotsAndBoxesGrid { if (y >= height || y < 0) { throw new IndexOutOfBoundsException(String.format("y was %d, which is out of range. Range is 0 to %d", y, height)); } - - // FIXME: You need to throw an exception if the line was already drawn. + if (horizontals[x][y]) { + throw new IllegalStateException(String.format("Horizontal line at (%d, %d) is already drawn", x, y)); + } this.horizontals[x][y] = true; - // Try to claim the north or south boxes - boolean claimN = claimBox(x, y-1, player); + boolean claimN = claimBox(x, y - 1, player); boolean claimS = claimBox(x, y, player); if (claimN || claimS) { notifyObservers(); @@ -156,12 +102,6 @@ public class DotsAndBoxesGrid { } } - /** - * "Draws" a vertical line, from grid point (x, y) to (x, y + 1). (i.e. sets that line to true) - * @param x - * @param y - * @return true if it completes a box - */ public boolean drawVertical(int x, int y, int player) { if (x >= width || x < 0) { throw new IndexOutOfBoundsException(String.format("x was %d, which is out of range. Range is 0 to %d", x, width)); @@ -169,13 +109,14 @@ public class DotsAndBoxesGrid { if (y >= height - 1 || y < 0) { throw new IndexOutOfBoundsException(String.format("y was %d, which is out of range. Range is 0 to %d", y, height - 1)); } - - // You need to throw an exception if the line was already drawn. + if (verticals[x][y]) { + throw new IllegalStateException(String.format("Vertical line at (%d, %d) is already drawn", x, y)); + } this.verticals[x][y] = true; - // Try to claim the north or south boxes + boolean claimE = claimBox(x, y, player); - boolean claimW = claimBox(x-1, y, player); + boolean claimW = claimBox(x - 1, y, player); if (claimE || claimW) { notifyObservers(); return true; @@ -184,14 +125,9 @@ public class DotsAndBoxesGrid { notifyObservers(); return false; } - } public boolean gameComplete() { - // Students who took COSC250 might recognise this style of code. This is Java's version of higher order functions. - // The grid is complete if "for all rows, all the boxes in that row have a non-zero owner" return Arrays.stream(boxOwners).allMatch((row) -> Arrays.stream(row).allMatch((owner) -> owner > 0)); } - - } diff --git a/src/test/java/dotsandboxes/DotsAndBoxesGridTest.java b/src/test/java/dotsandboxes/DotsAndBoxesGridTest.java index eccd78b06ca91551acb920e8a6eb457bef18b206..8c4ba7eddcd91fc0d4138151415a178f8f95b8f3 100644 --- a/src/test/java/dotsandboxes/DotsAndBoxesGridTest.java +++ b/src/test/java/dotsandboxes/DotsAndBoxesGridTest.java @@ -14,7 +14,62 @@ public class DotsAndBoxesGridTest { logger.info("Dummy test to show the test suite runs"); assertTrue(true); } + package dotsandboxes; + import org.junit.jupiter.api.*; + import static org.junit.jupiter.api.Assertions.*; + + public class DotsAndBoxesGridTest { + + @Test + public void testTestSuiteRuns() { + assertTrue(true); + } + + @Test + public void testBoxComplete() { + DotsAndBoxesGrid grid = new DotsAndBoxesGrid(3, 3, 2); + + // Drawing the lines around the (0, 0) box + grid.drawHorizontal(0, 0, 1); + grid.drawHorizontal(0, 1, 1); + grid.drawVertical(0, 0, 1); + grid.drawVertical(1, 0, 1); + + assertTrue(grid.boxComplete(0, 0)); + } + + @Test + public void testDrawHorizontalLineAlreadyDrawn() { + DotsAndBoxesGrid grid = new DotsAndBoxesGrid(3, 3, 2); + grid.drawHorizontal(0, 0, 1); + + Exception exception = assertThrows(IllegalStateException.class, () -> { + grid.drawHorizontal(0, 0, 1); + }); + + String expectedMessage = "Horizontal line at (0, 0) is already drawn"; + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + public void testDrawVerticalLineAlreadyDrawn() { + DotsAndBoxesGrid grid = new DotsAndBoxesGrid(3, 3, 2); + grid.drawVertical(0, 0, 1); + + Exception exception = assertThrows(IllegalStateException.class, () -> { + grid.drawVertical(0, 0, 1); + }); + + String expectedMessage = "Vertical line at (0, 0) is already drawn"; + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + } + } + // Test for Bug 1: Check if a square is correctly marked as complete @Test public void testSquareCompletion() {