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