// Class: Grid // // Author: Alyce Brady // // This class is based on the College Board's Environment interface // and SquareEnvironment class, as allowed by the GNU General Public // License. Environment and SquareEnvironment are components of // the AP(r) CS Marine Biology Simulation case study // (see http://www.collegeboard.com/student/testing/ap/compsci_a/case.html). // // License Information: // This class is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation. // // This class is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. package edu.kzoo.grid; import edu.kzoo.util.RandNumGenerator; import java.util.ArrayList; /** * Grid Container Package:
* * A Grid is a two-dimensional, rectangular container * data structure. It can contain any kind of object that can be * modeled using an extension of the GridObject class. * For example, a grid could be used to model a board for a * tic-tac-toe or chess game, an environment of fish for a marine * biology simulation, etc. * *

* The Grid class is based on the College Board's * Environment interface and SquareEnvironment * class, as allowed by the GNU General Public License. * * @author Alyce Brady * @version 13 December 2003 * @see Direction * @see Location * @see GridObject **/ public abstract class Grid { // constants /** A constant representing an unbounded (or infinite) number of * rows or columns in a grid. **/ public final static int UNBOUNDED = -1; // instance variables: encapsulated data for EACH Grid object /** Instance variable indicating whether the set of neighbors around * each cell in the grid should include the 4 cells on the diagonals * as well as the 4 cells with shared sides. **/ protected final boolean includeDiagonals; /** Instance variable containing the internal representation of the * grid, which could be implemented in a number of ways. **/ protected final InternalRepresentation internalRep; // constructors /** Constructs a Grid object with the specified internal * representation. Each cell in this grid will have at most four * adjacent neighbors, the cells to its north, south, east, and west. * If the grid is bounded, cells along the boundaries will have fewer * than four neighbors. * @param rep the internal representation for the grid and the * objects it contains **/ protected Grid(InternalRepresentation rep) { this(rep, false); } /** Constructs a Grid object with the specified internal * representation. Each cell in this grid will have at most four or * eight adjacent neighbors, depending on the value of the * includeDiagonalNeighbors parameter. If * includeDiagonalNeighbors is true, each * cell will have at most eight adjacent neighbors, the cells to * its north, south, east, and west and the cells on the diagonals, * to the northeast, southeast, northwest, and southwest. If * includeDiagonalNeighbors is false, * each cell will have at most the four neighbors to its north, * south, east, and west. If the grid is bounded, cells along * the boundaries will have fewer than the maximum four or eight * neighbors. * @param rep the internal representation for the grid and the * objects it contains * @param includedDiagonalNeighbors whether to include the four * diagonal locations as neighbors **/ protected Grid(InternalRepresentation rep, boolean includeDiagonalNeighbors) { internalRep = rep; includeDiagonals = includeDiagonalNeighbors; } // accessor methods dealing with grid dimensions /** Returns number of rows in this grid. * @return the number of rows, or UNBOUNDED if the grid * is unbounded **/ public abstract int numRows(); /** Returns number of columns in this grid. * @return the number of columns, or UNBOUNDED if the grid * is unbounded **/ public abstract int numCols(); // accessor methods for navigating around this grid /** Verifies whether a location is valid in this grid. * @param loc location to check * @return true if loc is valid; * false otherwise **/ public boolean isValid(Location loc) { return internalRep.isValid(loc); } /** Returns the number of adjacent neighbors around each cell. * @return the number of adjacent neighbors **/ public int numAdjacentNeighbors() { return (includeDiagonals ? 8 : 4); } /** Generates a random direction. The direction returned by * randomDirection reflects the direction from * a cell in the grid to one of its adjacent neighbors. * @return a direction **/ public Direction randomDirection() { RandNumGenerator randNumGen = RandNumGenerator.getInstance(); int randNum = randNumGen.nextInt(numAdjacentNeighbors()); return new Direction(randNum * Direction.FULL_CIRCLE/numAdjacentNeighbors()); } /** Returns the direction from one location to another. If * fromLoc and toLoc are the same, * getDirection arbitrarily returns Direction.NORTH. * @param fromLoc starting location for search * @param toLoc destination location * @return direction from fromLoc to toLoc **/ public Direction getDirection(Location fromLoc, Location toLoc) { if (fromLoc.equals(toLoc)) return Direction.NORTH; int rowDifference = fromLoc.row() - toLoc.row(); // our coord system is upside down int colDifference = toLoc.col() - fromLoc.col(); double inRads = Math.atan2(rowDifference, colDifference); double angle = 90 - Math.toDegrees(inRads); // convert to our sweep, North is 0 Direction d = new Direction((int)angle); return d.roundedDir(numAdjacentNeighbors(), Direction.NORTH); } /** Returns the adjacent neighbor (whether valid or invalid) of a location * in the specified direction. * @param fromLoc starting location for search * @param compassDir direction in which to look for adjacent neighbor * @return neighbor of fromLoc in given direction * (whether valid or not) **/ public Location getNeighbor(Location fromLoc, Direction compassDir) { Direction roundedDir = compassDir.roundedDir(numAdjacentNeighbors(), Direction.NORTH); // Calculate neighboring location using sines and cosines. // First have to adjust because our 0 is North, not East. // The row change is the opposite of what is expected because // our row numbers increase as they go down, not up. int adjustedDegrees = 90 - roundedDir.inDegrees(); double inRads = Math.toRadians(adjustedDegrees); int colDelta = (int)(Math.cos(inRads) * Math.sqrt(2)); int rowDelta = -(int)(Math.sin(inRads) * Math.sqrt(2)); return new Location(fromLoc.row() + rowDelta, fromLoc.col() + colDelta); } /** Returns the adjacent neighbors of a specified location. * Only neighbors that are valid locations in the grid will be * included. * @param ofLoc location whose neighbors to get * @return a list of locations that are neighbors of ofLoc **/ public ArrayList neighborsOf(Location ofLoc) { ArrayList nbrs = new ArrayList(); Direction d = Direction.NORTH; for (int i = 0; i < numAdjacentNeighbors(); i++) { Location neighbor = getNeighbor(ofLoc, d); if ( isValid(neighbor) ) nbrs.add(neighbor); d = d.toRight(Direction.FULL_CIRCLE/numAdjacentNeighbors()); } return nbrs; } // accessor methods that deal with objects in this grid /** Returns the number of objects in this grid. * @return the number of objects **/ public synchronized int numObjects() { return internalRep.numObjects(); } /** Returns all the objects in this grid. * @return an array of all the grid objects **/ public synchronized GridObject[] allObjects() { return internalRep.allObjects(); } /** Determines whether a specific location in this grid is empty. * @param loc the location to test * @return true if loc is a * valid location in the context of this grid * and is empty; false otherwise **/ public synchronized boolean isEmpty(Location loc) { return isValid(loc) && objectAt(loc) == null; } /** Returns the object at a specific location in this grid. * @param loc the location in which to look * @return the object at location loc; * null if loc is not * in the grid or is empty **/ public synchronized GridObject objectAt(Location loc) { return internalRep.objectAt(loc); } /** Creates a single string representing all the objects in this * environment (not necessarily in any particular order). * @return a string indicating all the objects in this environment **/ public synchronized String toString() { GridObject[] theObjects = allObjects(); String s = "Grid contains " + numObjects() + " objects: "; for ( int index = 0; index < theObjects.length; index++ ) s += theObjects[index].toString() + " "; return s; } // modifier methods /** Adds a new object to this grid at the specified location. * (Precondition: obj.grid() and * obj.location() are both null; * loc is a valid empty location in this grid.) * @param obj the new object to be added * @throws IllegalArgumentException if the precondition is not met **/ public void add(GridObject obj, Location loc) { obj.addToGrid(this, loc); } /** Adds the specified object to this grid at the location it specifies. * This method is meant to be called only by * GridObject.addToGrid because it assumes a * broken GridObject invariant: the grid and location * are set in the GridObject instance, but the object's * location in the internal representation of the grid does not yet * represent the object's own view of its location. * (Precondition: obj.grid() is this grid and * obj.location() is a valid empty location.) * @param obj the new object to be added * @throws IllegalArgumentException if the precondition is not met **/ final synchronized void internalAdd(GridObject obj) { // Verify precondition. Location loc = obj.location(); if ( obj.grid() != this || ! isEmpty(loc) ) throw new IllegalArgumentException("Location " + loc + " is not a valid empty location"); // Add object to the grid. internalRep.add(obj); } /** Removes the specified object from this grid. * (Precondition: obj is in this grid.) * @param obj the object to be removed * @throws IllegalArgumentException if the precondition is not met **/ public synchronized void remove(GridObject obj) { // Make sure that the object is not in another grid. if ( obj.grid() != this && obj.grid() != null ) throw new IllegalArgumentException("Cannot remove " + obj + " from another grid"); // The object should initiate its own removal from the grid so // that its object invariant is not violated. obj.removeFromGrid(); } /** Removes whatever object is at the specified location in this grid. * If there is no object at the specified location, this method does * nothing. * @param loc the location from which to remove an object **/ public void remove(Location loc) { // The object should initiate its own removal from the grid so // that its object invariant is not violated. GridObject obj = objectAt(loc); if ( obj != null ) obj.removeFromGrid(); } /** Removes the specified object from this grid. This method is meant to * be called only by GridObject.removeFromGrid; all object * removals should proceed through that method. * (Precondition: obj is in the process of being removed * via GridObject.removeFromGrid and, as a result, its * invariant does not hold. Specifically, * obj.grid() == null but obj.location() * correctly specifies the object's location in the grid.) * @param obj the object to be removed * @throws IllegalArgumentException if the precondition is not met **/ final synchronized void internalRemove(GridObject obj) { // Make sure that the object has initiated its own removal from // the grid. if ( obj.grid() != null || objectAt(obj.location()) != obj ) throw new IllegalArgumentException("Object " + obj + " is not in process of removing itself"); // The object is in the process of removing itself from the grid, // so we can remove it. internalRep.remove(obj); } /** Removes all objects from this grid. **/ public synchronized void removeAll() { // Loop through the objects in the grid and remove them. GridObject[] objectsToRemove = allObjects(); for ( int i = 0; i < objectsToRemove.length; i++ ) { remove(objectsToRemove[i]); } } /** The InternalRepresentation interface specifies * the methods that any internal representation of the * Grid class must implement. */ protected interface InternalRepresentation { // accessor methods /** Verifies whether a location is valid in this grid. * @param loc location to check * @return true if loc is valid; * false otherwise **/ boolean isValid(Location loc); /** Returns the number of objects in this grid. * @return the number of objects **/ int numObjects(); /** Returns all the objects in this grid. * @return an array of all the grid objects **/ GridObject[] allObjects(); /** Returns the object at a specific location in this grid. * @param loc the location in which to look * @return the object at location loc; * null if loc is not * in the grid or is empty **/ GridObject objectAt(Location loc); // modifier methods /** Adds a new object to this environment at the location it specifies. * (Precondition: obj.grid() is this grid and * obj.location() is a valid empty location; * verified by the Grid object.) * @param obj the new object to be added **/ void add(GridObject obj); /** Removes the object from this environment. * (Precondition: obj is in this environment; verified * by the Grid object.) * @param obj the object to be removed **/ void remove(GridObject obj); } /** A ValidityChecker specifies a strategy for determining * the validity of a location in a grid. Known implementing classes * include BoundedGridValidityChecker and * UnboundedGridValidityChecker. **/ public interface ValidityChecker { /** Verifies whether a location is valid. * @param loc location to check * @return true if loc is valid; * false otherwise **/ public boolean isValid(Location loc); } /** A BoundedGridValidityChecker implements a strategy for * determining the validity of a location in a bounded grid. **/ public static class BoundedGridValidityChecker implements ValidityChecker { private int numRows; private int numCols; /** Constructs a BoundedGridValidityChecker object. * (Precondition: rows > 0 and cols > 0.) * @param rows number of rows in the grid * @param cols number of columns in the grid **/ public BoundedGridValidityChecker(int rows, int cols) { numRows = rows; numCols = cols; } /** Verifies whether a location is valid. * @param loc location to check * @return true if loc is valid; * false otherwise **/ public boolean isValid(Location loc) { if ( loc == null ) return false; return (0 <= loc.row() && loc.row() < numRows) && (0 <= loc.col() && loc.col() < numCols); } } /** An UnboundedGridValidityChecker implements a strategy * for determining the validity of a location in an unbounded grid. **/ public static class UnboundedGridValidityChecker implements ValidityChecker { /** Verifies whether a location is valid. * @param loc location to check * @return true if loc is valid; * false otherwise **/ public boolean isValid(Location loc) { // All non-null locations are valid in an unbounded grid. return loc != null; } } }