From e1c55ee6055fee8047ced1d2e941fb2ed2e0a732 Mon Sep 17 00:00:00 2001 From: William Billingsley <wbilling@une.edu.au> Date: Wed, 31 Jul 2024 20:37:43 +1000 Subject: [PATCH] Fix bugs in square completion and line drawing --- .../java/dotsandboxes/DotsAndBoxesGrid.java | 148 ++++------- .../java/dotsandboxes/DotsAndBoxesUI.java | 236 +++++------------- src/main/java/dotsandboxes/Main.java | 5 +- 3 files changed, 112 insertions(+), 277 deletions(-) diff --git a/src/main/java/dotsandboxes/DotsAndBoxesGrid.java b/src/main/java/dotsandboxes/DotsAndBoxesGrid.java index a9e7c5b..554698a 100644 --- a/src/main/java/dotsandboxes/DotsAndBoxesGrid.java +++ b/src/main/java/dotsandboxes/DotsAndBoxesGrid.java @@ -1,6 +1,5 @@ package dotsandboxes; - import java.util.ArrayList; import java.util.Arrays; import java.util.function.Consumer; @@ -66,132 +65,69 @@ public class DotsAndBoxesGrid { 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); + public void addWatcher(Consumer<DotsAndBoxesGrid> watcher) { + this.watchers.add(watcher); } - /** 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; + private void notifyWatchers() { + for (Consumer<DotsAndBoxesGrid> watcher : watchers) { + watcher.accept(this); } - - // 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; } - /** 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; + public boolean drawLine(int x1, int y1, int x2, int y2) { + if (x1 == x2 && Math.abs(y1 - y2) == 1) { + if (verticals[x1][Math.min(y1, y2)]) { + throw new IllegalStateException("This vertical line is already drawn."); + } + verticals[x1][Math.min(y1, y2)] = true; + } else if (y1 == y2 && Math.abs(x1 - x2) == 1) { + if (horizontals[Math.min(x1, x2)][y1]) { + throw new IllegalStateException("This horizontal line is already drawn."); + } + horizontals[Math.min(x1, x2)][y1] = true; } else { - return false; + throw new IllegalArgumentException("Lines must be vertical or horizontal and of length 1."); } - } - /** - * "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)); - } - 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)); + boolean completedBox = false; + + // Check for any completed boxes + for (int x = 0; x < width - 1; x++) { + for (int y = 0; y < height - 1; y++) { + if (boxOwners[x][y] == 0 && + horizontals[x][y] && + horizontals[x][y + 1] && + verticals[x][y] && + verticals[x + 1][y]) { + boxOwners[x][y] = player; + completedBox = true; + } + } } - // FIXME: You need to throw an exception if the line was already drawn. - - this.horizontals[x][y] = true; - - // 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; - } - } - - /** - * "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)); - } - 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. - - 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); - if (claimE || claimW) { - notifyObservers(); - return true; - } else { + if (!completedBox) { nextPlayer(); - notifyObservers(); - return false; } + notifyWatchers(); + return completedBox; } - 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)); + public int getBoxOwner(int x, int y) { + return boxOwners[x][y]; } + public boolean isHorizontalLineDrawn(int x, int y) { + return horizontals[x][y]; + } + public boolean isVerticalLineDrawn(int x, int y) { + return verticals[x][y]; + } } diff --git a/src/main/java/dotsandboxes/DotsAndBoxesUI.java b/src/main/java/dotsandboxes/DotsAndBoxesUI.java index eee385a..1eb36eb 100644 --- a/src/main/java/dotsandboxes/DotsAndBoxesUI.java +++ b/src/main/java/dotsandboxes/DotsAndBoxesUI.java @@ -1,200 +1,100 @@ package dotsandboxes; -import java.awt.*; -import java.awt.event.*; import javax.swing.*; -import javax.swing.event.MouseInputAdapter; -import java.util.ArrayList; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; public class DotsAndBoxesUI { - static final int lineLength = 32; - static final int margin = 10; - static final int gap = 0; - static final int dotDiameter = 6; - - // The coordinate of the top or left of a the painting area for this row/col - private static int corner(int col) { - return margin + col * (gap + lineLength + gap + dotDiameter); - } - - /** Colours for the different players. Only goes up to 5. */ - static final Color[] playerColours = { Color.WHITE, Color.RED, Color.BLUE, Color.GREEN, Color.PINK, Color.ORANGE }; - final DotsAndBoxesGrid grid; - final JPanel anchorPane; - final Canvas canvas; final JLabel label; + final JPanel anchorPane; - private void updateLabel() { - label.setForeground(playerColours[grid.getPlayer()]); - label.setText(String.format("Player %d's turn", grid.getPlayer())); - } + final int lineThickness = 4; + final int dotDiameter = 12; + final int cellSize = 40; - public DotsAndBoxesUI(final DotsAndBoxesGrid grid) { + public DotsAndBoxesUI(DotsAndBoxesGrid grid) { this.grid = grid; - anchorPane = new JPanel(new BorderLayout()); - - label = new JLabel(""); - updateLabel(); - - canvas = new DABCanvas(); - - anchorPane.add(canvas, BorderLayout.CENTER); - anchorPane.add(label, BorderLayout.NORTH); - - grid.addConsumer((g) -> { - updateLabel(); - canvas.repaint(); - }); - } - /** A component that paints and handles clicks on the game */ - class DABCanvas extends Canvas { - - final ArrayList<Horizontal> horizontals = new ArrayList<>(); - final ArrayList<Vertical> verticals = new ArrayList<>(); - final ArrayList<Box> boxes = new ArrayList<>(); - - /** Represents a horizontal line. */ - record Horizontal(int col, int row) { - Rectangle rect() { - int x = corner(col) + dotDiameter + gap; - int y = corner(row); - return new Rectangle(x, y, lineLength, dotDiameter); - } - - /** Whether or not this line contains this point */ - boolean contains(int x, int y) { - return rect().contains(x, y); + anchorPane = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + paintGrid(g); } + }; + anchorPane.setPreferredSize(new Dimension(cellSize * grid.width, cellSize * grid.height)); - /** Paints this element, based on the passed in grid */ - public void draw(DotsAndBoxesGrid grid, Graphics2D g2d) { - g2d.setColor(grid.getHorizontal(col, row) ? Color.DARK_GRAY : Color.LIGHT_GRAY); - g2d.fill(this.rect()); - } - } - - /** Represents a horizontal line. */ - record Vertical(int col, int row) { - Rectangle rect() { - int x = corner(col); - int y = corner(row) + dotDiameter + gap; - return new Rectangle(x, y, dotDiameter, lineLength); - } + label = new JLabel(); + label.setHorizontalAlignment(SwingConstants.CENTER); + label.setPreferredSize(new Dimension(cellSize * grid.width, 30)); - /** Whether or not this line contains this point */ - boolean contains(int x, int y) { - return rect().contains(x, y); - } - - /** Paints this element, based on the passed in grid */ - public void draw(DotsAndBoxesGrid grid, Graphics2D g2d) { - g2d.setColor(grid.getVertical(col, row) ? Color.DARK_GRAY : Color.LIGHT_GRAY); - g2d.fill(this.rect()); - } - } - - /** represents a box */ - record Box(int col, int row) { - Rectangle rect() { - int x = corner(col) + dotDiameter + gap; - int y = corner(row) + dotDiameter + gap; - return new Rectangle(x, y, lineLength, lineLength); - } + // Add a watcher to update the UI when the grid changes + grid.addWatcher((g) -> { + updateStatus(); + anchorPane.repaint(); + }); - /** Whether or not this line contains this point */ - boolean contains(int x, int y) { - return rect().contains(x, y); + anchorPane.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + + int gridX = x / cellSize; + int gridY = y / cellSize; + + if (x % cellSize < lineThickness && gridX < grid.width && gridY < grid.height - 1) { + // Vertical line + try { + grid.drawLine(gridX, gridY, gridX, gridY + 1); + } catch (IllegalStateException ex) { + JOptionPane.showMessageDialog(anchorPane, "This line is already drawn!"); + } + } else if (y % cellSize < lineThickness && gridY < grid.height && gridX < grid.width - 1) { + // Horizontal line + try { + grid.drawLine(gridX, gridY, gridX + 1, gridY); + } catch (IllegalStateException ex) { + JOptionPane.showMessageDialog(anchorPane, "This line is already drawn!"); + } + } } + }); - /** Paints this element, based on the passed in grid */ - public void draw(DotsAndBoxesGrid grid, Graphics2D g2d) { - g2d.setColor(playerColours[grid.getBoxOwner(col, row)]); - g2d.fill(this.rect()); - } - } + updateStatus(); + } + private void updateStatus() { + label.setText("Current player: " + grid.getPlayer()); + } - public DABCanvas() { - // Size the canvas to just contain the elements - int width = corner(grid.width) + margin; - int height = corner(grid.height) + margin; - this.setPreferredSize(new Dimension(width, height)); + private void paintGrid(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + g2d.setColor(Color.BLACK); - // Create records for the boxes - for (int row = 0; row < grid.height - 1; row++) { - for (int col = 0; col < grid.width - 1; col++) { - boxes.add(new Box(col, row)); - } - } + for (int x = 0; x < grid.width; x++) { + for (int y = 0; y < grid.height; y++) { + int xPos = x * cellSize; + int yPos = y * cellSize; + g2d.fillOval(xPos - dotDiameter / 2, yPos - dotDiameter / 2, dotDiameter, dotDiameter); - // Create records for horizontals - for (int row = 0; row < grid.height; row++) { - for (int col = 0; col < grid.width - 1; col++) { - horizontals.add(new Horizontal(col, row)); + if (x < grid.width - 1 && grid.isHorizontalLineDrawn(x, y)) { + g2d.fillRect(xPos + dotDiameter / 2, yPos - lineThickness / 2, cellSize - dotDiameter, lineThickness); } - } - // Create records for verticals - for (int row = 0; row < grid.height - 1; row++) { - for (int col = 0; col < grid.width; col++) { - verticals.add(new Vertical(col, row)); + if (y < grid.height - 1 && grid.isVerticalLineDrawn(x, y)) { + g2d.fillRect(xPos - lineThickness / 2, yPos + dotDiameter / 2, lineThickness, cellSize - dotDiameter); } - } - - addMouseListener(new MouseInputAdapter() { - @Override public void mousePressed(MouseEvent e) { - for (Horizontal h : horizontals) { - if (h.contains(e.getX(), e.getY())) { - grid.drawHorizontal(h.col(), h.row(), grid.getPlayer()); - } - } - - for (Vertical v : verticals) { - if (v.contains(e.getX(), e.getY())) { - grid.drawVertical(v.col(), v.row(), grid.getPlayer()); - } - } - } - }); - - } - - @Override public void paint(Graphics g) { - Graphics2D g2d = (Graphics2D)g; - g.clearRect(0, 0, this.getWidth(), this.getHeight()); - g2d.setColor(Color.WHITE); - g.fillRect(0, 0, this.getWidth(), this.getHeight()); - - // Paint the boxes - for (Box b : boxes) { - b.draw(grid, g2d); - } - - // Paint the horizontals - for (Horizontal h : horizontals) { - h.draw(grid, g2d); - } - // Paint the boxes - for (Vertical v : verticals) { - v.draw(grid, g2d); - } - - // Draw the dots - for (int row = 0; row < grid.height; row++) { - for (int col = 0; col < grid.width; col++) { + if (x < grid.width - 1 && y < grid.height - 1 && grid.getBoxOwner(x, y) != 0) { + g2d.setColor(grid.getBoxOwner(x, y) == 1 ? Color.RED : Color.BLUE); + g2d.fillRect(xPos + dotDiameter / 2, yPos + dotDiameter / 2, cellSize - dotDiameter, cellSize - dotDiameter); g2d.setColor(Color.BLACK); - g2d.fillOval(corner(col), corner(row), dotDiameter, dotDiameter); } } } - } - - - - } diff --git a/src/main/java/dotsandboxes/Main.java b/src/main/java/dotsandboxes/Main.java index 6c21233..b10ce3a 100644 --- a/src/main/java/dotsandboxes/Main.java +++ b/src/main/java/dotsandboxes/Main.java @@ -5,14 +5,14 @@ import java.awt.*; /** Our main class that launches the app. */ public class Main { - + public static void main(String... args) throws Exception { JFrame mainWindow = new JFrame("Dots and Boxes"); DotsAndBoxesGrid grid = new DotsAndBoxesGrid(15, 8, 2); // FIXME: Update this label to show your name: Chandan(Gerry) and student id: 220278631 - JLabel label = new JLabel("Name:Chandan Bikram Shah, student number:220278631"); + JLabel label = new JLabel("Name: Chandan Bikram Shah, student number: 220278631"); JPanel borderPane = new JPanel(new BorderLayout()); borderPane.add(label, BorderLayout.SOUTH); @@ -28,5 +28,4 @@ public class Main { // This sets what to do when we close the main window. mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } - } -- GitLab