/*
* Copyright 2007-2013
* Licensed under GNU Lesser General Public License
*
* This file is part of EpochX: genetic programming software for research
*
* EpochX is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EpochX 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with EpochX. If not, see <http://www.gnu.org/licenses/>.
*
* The latest version is available from: http://www.epochx.org
*/
package org.epochx.tools.ant;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* An <code>AntLandscape</code> provides the environment an artificial ant
* operates in. The landscape is essentially a torus with a given width/height,
* over which the positions wrap back around. The landscape also incorporates a
* set of food locations which define the location of food pellets within
* that co-ordinate system.
*/
public class AntLandscape {
// The width/height dimensions of the landscape.
private final Dimension size;
// The locations of food pellets.
private List<Point> foodLocations;
// An optional ant which may be present on this landscape.
private Ant ant;
/**
* Constructs an AntLandscape with the given dimensions and food locations.
* To be reachable, FOOD_LOCATIONS should refer to points inside the size
* dimensions where co-ordinate points are indexed from 0.
*
* @param size the width/height dimensions of the landscape.
* @param foodLocations the location of food pellets upon the landscape.
*/
public AntLandscape(final Dimension size, final List<Point> foodLocations) {
this.size = size;
this.foodLocations = foodLocations;
}
/**
* Constructs an AntLandscape with the given dimensions but without any
* food locations.
*
* @param size the width/height dimensions of the landscape.
*/
public AntLandscape(final Dimension size) {
this(size, new ArrayList<Point>());
}
/**
* Adds a new food location to the landscape. The new point may <b>not</b>
* be tested to check it is a valid location within the landscape
* dimensions so care should be taken to provide valid locations.
*
* @param location the x/y co-ordinates of the new item of food.
*/
public void addFoodLocation(final Point location) {
foodLocations.add(location);
}
/**
* Removes a food location from the ant landscape. If there are multiple
* food pellets at the same <code>Point</code> location then only one will
* be removed. If no food pellet exists at the given location then this
* method will do nothing.
*
* @param location the location of a current food item to be removed.
*/
public void removeFoodLocation(final Point location) {
foodLocations.remove(location);
}
/**
* Tests whether a location contains an item of food.
*
* @param location The location in the landscape to test.
* @return true if food is present at the given location, false otherwise.
*/
public boolean isFoodLocation(final Point location) {
return foodLocations.contains(location);
}
/**
* Replaces the current set of food locations with a new set. To be
* reachable, foodLocations should refer to points inside the size
* dimensions of the landscape where co-ordinate points are indexed
* from 0.
*
* @param foodLocations the location of food pellets upon the landscape.
*/
public void setFoodLocations(final List<Point> foodLocations) {
this.foodLocations = foodLocations;
}
/**
* Clears all food locations on the landscape.
*/
public void clearFoodLocations() {
foodLocations.clear();
}
/**
* Returns the size of the ant landscape.
*
* @return the dimensions of the ant landscape.
*/
public Dimension getSize() {
return size;
}
/**
* Returns the width of the ant landscape.
*
* @return the width of the ant landscape.
*/
public int getWidth() {
return size.width;
}
/**
* Returns the height of the ant landscape.
*
* @return the height of the ant landscape.
*/
public int getHeight() {
return size.height;
}
/**
* Tests if a location is a valid position within the landscape's
* dimensions. Locations are indexed from 0.
*
* @param location the location to test.
* @return true if the location is a valid position on the landscape.
*/
public boolean isValidLocation(final Point location) {
return (location.x >= 0) && (location.x < size.width) && (location.y >= 0) && (location.y < size.height);
}
/**
* Returns the location of one move on from the given location in the
* direction of the provided orientation. The landscape is a torus so this
* method is required to calculate the necessary wrapping.
*
* @param location the current location to calculate the next move from.
* @param orientation the direction of the move.
* @return the next location one move on from the given location in the
* direction of the provided orientation.
*/
public Point getNextLocation(final Point location, final Orientation orientation) {
final Point newLocation = new Point(location);
switch (orientation) {
case NORTH:
newLocation.y = (location.y > 0) ? (location.y - 1) : (size.height - 1);
break;
case EAST:
newLocation.x = (location.x < size.width - 1) ? (location.x + 1) : 0;
break;
case SOUTH:
newLocation.y = (location.y < size.height - 1) ? (location.y + 1) : 0;
break;
case WEST:
newLocation.x = (location.x > 0) ? (location.x - 1) : (size.width - 1);
break;
default:
break;
}
return newLocation;
}
/**
* Sets an ant onto this landscape. It is optional for a landscape to
* contain a reference to an ant, but if an ant is set then the landscapes
* toString method will include the ants location.
*
* @param ant an ant which can be considered to be navigating this
* landscape.
*/
public void setAnt(final Ant ant) {
this.ant = ant;
}
/**
* Returns an ASCII representation of the landscape.
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
int antX = -1;
int antY = -1;
if (ant != null) {
antX = ant.getLocation().x;
antY = ant.getLocation().y;
}
for (int y = 0; y < size.height; y++) {
for (int x = 0; x < size.width; x++) {
if ((antX == x) && (antY == y)) {
switch (ant.getOrientation()) {
case NORTH:
buffer.append('^');
break;
case EAST:
buffer.append('>');
break;
case SOUTH:
buffer.append('V');
break;
case WEST:
buffer.append('<');
break;
}
} else if (isFoodLocation(new Point(x, y))) {
buffer.append('X');
} else {
buffer.append('.');
}
}
buffer.append('\n');
}
return buffer.toString();
}
}