// 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;
}
}
}