diff --git a/src/main/java/dotsandboxes/DotsAndBoxesGrid.java b/src/main/java/dotsandboxes/DotsAndBoxesGrid.java index 60e7943057b6d7c6180753028c0f75667255ba26..4f774b198f230ca82e5cc94d51967846aa05d5f5 100644 --- a/src/main/java/dotsandboxes/DotsAndBoxesGrid.java +++ b/src/main/java/dotsandboxes/DotsAndBoxesGrid.java @@ -1,7 +1,10 @@ package dotsandboxes; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Observer; +import java.util.function.Consumer; /** * The state of a dots and boxes grid. @@ -43,15 +46,60 @@ public class DotsAndBoxesGrid { /** Which owner (if any) claimed any given box. */ private int[][] boxOwners; - public DotsAndBoxesGrid(int width, int height) { + /** 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) { + player = 1; + } + } + + public DotsAndBoxesGrid(int width, int height, int players) { this.width = width; this.height = height; + this.players = players; this.horizontals = new boolean[width - 1][height]; this.verticals = new boolean[width][height - 1]; this.boxOwners = new int[width - 1][height - 1]; } + private void notifyObservers() { + for (Consumer<DotsAndBoxesGrid> consumer : watchers) { + consumer.accept(this); + } + } + + /** 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 @@ -59,11 +107,8 @@ public class DotsAndBoxesGrid { * @return true if all four sides have been drawn. */ public boolean boxComplete(int x, int y) { - 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)); - } - 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)); + 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. @@ -71,6 +116,16 @@ public class DotsAndBoxesGrid { return true; } + /** 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; + return true; + } else { + return false; + } + } + /** * "Draws" a horizontal line, from grid point (x, y) to (x + 1, y). (i.e. sets that line to true) * @param x @@ -88,10 +143,16 @@ public class DotsAndBoxesGrid { // FIXME: You need to throw an exception if the line was already drawn. this.horizontals[x][y] = true; - if (boxComplete(x, y)) { - boxOwners[x][y] = player; + + // Try to claim the north or south boxes + boolean claimN = claimBox(x, y-1, player); + boolean claimS = claimBox(x, y, player); + if (claimN || claimS) { + notifyObservers(); return true; } else { + nextPlayer(); + notifyObservers(); return false; } } @@ -113,12 +174,18 @@ public class DotsAndBoxesGrid { // You need to throw an exception if the line was already drawn. this.verticals[x][y] = true; - if (boxComplete(x, y)) { - boxOwners[x][y] = player; + // Try to claim the north or south boxes + boolean claimE = claimBox(x, y, player); + boolean claimW = claimBox(x-1, y, player); + if (claimE || claimW) { + notifyObservers(); return true; } else { + nextPlayer(); + notifyObservers(); return false; } + } public boolean gameComplete() { diff --git a/src/main/java/dotsandboxes/DotsAndBoxesUI.java b/src/main/java/dotsandboxes/DotsAndBoxesUI.java index 3dd173259636dbd4f09971fce1b266498cbe35d7..154cb8826db5cf89be1bf5644a4c53e431f82614 100644 --- a/src/main/java/dotsandboxes/DotsAndBoxesUI.java +++ b/src/main/java/dotsandboxes/DotsAndBoxesUI.java @@ -1,9 +1,11 @@ package dotsandboxes; +import javafx.scene.control.Label; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; +import javafx.scene.shape.Rectangle; public class DotsAndBoxesUI { @@ -14,17 +16,48 @@ public class DotsAndBoxesUI { final DotsAndBoxesGrid grid; final AnchorPane anchorPane; + final Label label; - public DotsAndBoxesUI(DotsAndBoxesGrid grid) { + /** Colours for the different players. Only goes up to 5. */ + final Color[] playerColours = { Color.WHITE, Color.RED, Color.BLUE, Color.GREEN, Color.PURPLE, Color.ORANGE }; + + private void updateLabel() { + label.setTextFill(playerColours[grid.getPlayer()]); + label.setText(String.format("Player %d's turn", grid.getPlayer())); + } + + public DotsAndBoxesUI(final DotsAndBoxesGrid grid) { this.grid = grid; anchorPane = new AnchorPane(); + label = new Label(""); + updateLabel(); + grid.addConsumer((g) -> updateLabel()); + // Size the anchorPane to just contain the elements int width = margin + dotDiameter + gap + (grid.width - 1) * (gap + lineLength + gap + dotDiameter) + gap + margin; int height = margin + dotDiameter + gap + (grid.height - 1) * (gap + lineLength + gap + dotDiameter) + gap + margin; anchorPane.setPrefSize(width, height); + // Lay out the boxes + for (int row = 0; row < grid.height - 1; row++) { + for (int col = 0; col < grid.width - 1; col++) { + final int x = col; + final int y = row; + Rectangle box = new Rectangle(gap, gap, lineLength, lineLength); + box.setFill(Color.WHITE); + + grid.addConsumer((g) -> { + box.setFill(playerColours[g.getBoxOwner(x, y)]); + }); + + AnchorPane.setLeftAnchor(box, gap + dotDiameter + col * (gap + lineLength + gap + dotDiameter) + dotDiameter/2.0); + AnchorPane.setTopAnchor(box, gap + dotDiameter + row * (gap + lineLength + gap + dotDiameter) + dotDiameter/2.0); + anchorPane.getChildren().add(box); + } + } + // Lay out the horizontals for (int row = 0; row < grid.height; row++) { for (int col = 0; col < grid.width - 1; col++) { @@ -32,7 +65,21 @@ public class DotsAndBoxesUI { line.setStrokeWidth(dotDiameter); line.setStroke(Color.DARKGREY); - line.setOnMouseClicked((evt) -> line.setStroke(Color.RED)); + final int x = col; + final int y = row; + grid.addConsumer((g) -> { + if (g.getHorizontal(x, y)) { + line.setStroke(Color.BLACK); + } else { + line.setStroke(Color.LIGHTGRAY); + } + }); + + line.setOnMouseClicked((evt) -> {try { + grid.drawHorizontal(x, y, grid.getPlayer()); + } catch (IllegalStateException ex) { + // do nothing + }}); AnchorPane.setLeftAnchor(line, 0.0 + gap + dotDiameter + col * (gap + lineLength + gap + dotDiameter)); AnchorPane.setTopAnchor(line, -dotDiameter/2.0 + gap + dotDiameter + row * (gap + lineLength + gap + dotDiameter)); @@ -47,7 +94,21 @@ public class DotsAndBoxesUI { line.setStrokeWidth(dotDiameter); line.setStroke(Color.DARKGREY); - line.setOnMouseClicked((evt) -> line.setStroke(Color.RED)); + final int x = col; + final int y = row; + grid.addConsumer((g) -> { + if (g.getVertical(x, y)) { + line.setStroke(Color.BLACK); + } else { + line.setStroke(Color.LIGHTGRAY); + } + }); + + line.setOnMouseClicked((evt) -> {try { + grid.drawVertical(x, y, grid.getPlayer()); + } catch (IllegalStateException ex) { + // do nothing + }}); AnchorPane.setTopAnchor(line, 0.0 + gap + dotDiameter + row * (gap + lineLength + gap + dotDiameter)); AnchorPane.setLeftAnchor(line, -dotDiameter/2.0 + gap + dotDiameter + col * (gap + lineLength + gap + dotDiameter)); @@ -67,8 +128,6 @@ public class DotsAndBoxesUI { } } - - } diff --git a/src/main/java/dotsandboxes/Main.java b/src/main/java/dotsandboxes/Main.java index 1acd717d2ce2bb788b0a11912a74bb924498442d..997d03b58b4becdd83ad226d469457ef2d751773 100644 --- a/src/main/java/dotsandboxes/Main.java +++ b/src/main/java/dotsandboxes/Main.java @@ -9,7 +9,7 @@ import javafx.stage.Stage; /** Our main class that launches the app. */ public class Main extends Application { - DotsAndBoxesGrid grid = new DotsAndBoxesGrid(15, 8); + DotsAndBoxesGrid grid = new DotsAndBoxesGrid(15, 8, 2); @Override public void start(Stage primaryStage) throws Exception { @@ -18,11 +18,12 @@ public class Main extends Application { Label label = new Label("My label"); BorderPane borderPane = new BorderPane(); - borderPane.setTop(label); + borderPane.setBottom(label); Scene scene = new Scene(borderPane, 600, 400); DotsAndBoxesUI dbUi = new DotsAndBoxesUI(grid); borderPane.setCenter(dbUi.anchorPane); + borderPane.setTop(dbUi.label); primaryStage.setScene(scene);