Ant Farm

<< Capture The Flag | GridworldTrailIndex | Make Custom Pictures For Gridworld Actors >>

See a video of it running here.

This project was based on Robert Glen Martin's Art Farm, which I adapted to make ants remember exhausted food supplies, so they effectively go back and forth between random "hunt" mode and "bring food the queen" mode. As you work through this project, you will complete an interface and both concrete and abstract classes. Your solution will demonstrate inheritance, encapsulation, and polymorphism. Prior to beginning this project, you must read and understand the first four chapters of the GridWorld Student Manual.

The project utilizes four new types of objects: two kinds of food ( Cookie and Cake) and two kinds of ants ( WorkerAnt and QueenAnt). Initially, the worker ants walk around in search of food. When they find food, they take a bite. Ants with food turn red. Then the worker ants go in search of a queen ant to give food. Once they give their food to a queen, they turn black and go back to get more food.

Food and queens remain stationary. Worker ants remember the locations of the food and queen. Additionally, they share those locations with other worker ants they meet. When the Ant Farm program starts, the worker ants are spread around the grid in random locations. Initially, they are disorganized as they search for food. As the worker ants start to find food and the queen, they get more organized (see Figure 2 below). After all the ants learn the locations, they exhibit an emergent behavior that is very organized.

GridWorld has a "built-in" Actor class for objects that "live" in the grid. Actors that have minimal interaction with other objects in the grid normally inherit from Actor. This is appropriate for QueenAnt and Food. Cake and Cookie inherit indirectly from Actor.

The other "built-in" Actor is Critter, which inherits from Actor. Critters have additional methods that are useful for interacting with other Actors. WorkerAnts need to "communicate" with the QueenAnt, Cake, Cookie, and other WorkerAnt objects. So inheriting from Critter is appropriate for them.

Ant Farm also has a new Processable interface. This interface has a single process method that is the key to communication between worker ants and the other Actors. It is critical that you understand the Processable interface and how it is used in Ant Farm.

Processable.java
public interface Processable 
{
	public void process(WorkerAnt ant);
}

The Processable interface, contains a single void process method. This process method takes one parameter of type WorkerAnt. Add the method heading for the process method in the Processable interface. Note: All interface methods are automatically public and abstract.

When implemented in QueenAnt, Food, and WorkerAnt, the process method processes (communicates with) a single WorkerAnt object (the one passed as a parameter). This interface allows worker ants to invoke the other Actor's process methods polymorphically. The individual process methods in each class will do the following:

  • QueenAnt
    • Get food from the worker ant.
    • Give the queen's location to the worker ant.
  • Food
    • Give food to the worker ant.
    • Give the food's location to the worker ant.
  • WorkerAnt
    • Give the saved food location to the other worker ant.
    • Give the saved queen location to the other worker ant.

Note that Ant Farm uses the Processable interface to implement an interface variant of the Template Design Pattern. The Template Design Pattern normally uses an abstract class to contain the abstract method(s). Then concrete classes (which inherit from the abstract class) implement the method(s) as appropriate. In Ant Farm, we use the Processable interface to hold the abstract process method. process methods are written in the QueenAnt, Food, and WorkerAnt classes, each of which implement Processable.

AntFarm.java
import info.gridworld.actor.*;
import info.gridworld.grid.*;
public class AntFarm {

	public static void main(String[] args) {
		ActorWorld farm=new ActorWorld();
		farm.setGrid(new BoundedGrid(20,20));
		farm.add(new Location(10,16), new Queen() );
		farm.add(new Location(2,2), new Cake() );
		farm.add(new Location(18,2), new Cookie() );
		for (int i=0; i<20;i++)
			farm.add(new WorkerAnt());
		farm.show();
	}

}

Food.java
import info.gridworld.actor.*;

/**
 * Foods act like queens, but they give food instead of getting it.
 * Different kinds of food are very similar. 
 * They differ only by the size of a bite and the displayed image. 
 * To take advantage of this similarity, the common instance fields 
 * and methods are placed in a Food super class. 
 * This class contains no abstract methods, but it is declared abstract 
 * so that it cannot be instantiated. 
 * All foods have both bite sizes and keep track of the total amount 
 * that has been eaten. So, instead of repeating this information in 
 * both Cake and Cookie, it is stored in Food instance fields:
 * (1) BITE_SIZE - a constant that determines how much food is given 
 *     to a worker ant when it gets food.
 * (2) food - keeps track of the total amount of food left
 */
public abstract class Food extends Actor implements Processable
{
	private final int BITE_SIZE;
	private int food;
	/**
	 * The constructor initializes BITE_SIZE to the bite value passed 
	 * in the parameter, initializes foodEaten to 0, and calls 
	 * setColor(null) so that the Cake.gif and Cookie.gif images display 
	 * with their original coloring.
	 * 
	 * Note that Java allows constant (final) instance fields to 
	 * be initialized in a constructor.
	 * 
	 * @param bite how much food is given to a worker ant when it gets food.
	 */
	public Food(int bite, int amount){
		// your code here
	}
	/**
	 * All foods implement the process method (from Processable) 
	 * to give food to the passed worker ant and to provide it the 
	 * food's location.
	 */
	public void process(WorkerAnt ant){
		// your code here
	}
	/**
	 * Foods need to override the Actor act method with an 
	 * and remove themselves when there is no food left.
	 */
	public void act(){
		// your code here
	}
}

Cookie.java
/**
 * Because of the Food class, the Cake and Cookie classes are very simple. 
 * They contain a single class constant BITE which contains the size 
 * of a bite. They each have a one statement constructor which passes 
 * the value of BITE to the Food constructor, and the Amount of food that
 * it can offer before disappearing.
 *
 */
public class Cookie extends Food 
{
	public Cookie(){
		super(1, 200);
	}
}

Cake.java
/**
 * Because of the Food class, the Cake and Cookie classes are very simple. 
 * They contain a single class constant BITE which contains the size 
 * of a bite. They each have a one statement constructor which passes 
 * the value of BITE to the Food constructor, and the Amount of food that
 * it can offer before disappearing.
 *
 */
public class Cake extends Food 
{
	public Cake(){
		super(2, 400);
	}
}

Queen.java
import java.awt.Color;

import info.gridworld.actor.*;

public class Queen extends Actor implements Processable
{
	int foodAmount;
	/**
	 * initialize foodQuantity to 0 and use the inherited setColor 
	 * method to set the queen's color to Color.MAGENTA.
	 */
	public Queen(){
		// your code here
	}
	/*
	 * process needs to get food from the passed worker ant using 
	 * the WorkerAnt giveFood method. This method returns an 
	 * int amount which should be added to foodQuantity. 
	 * 
	 * process also needs to provide the worker ant with queen's 
	 * location by calling the WorkerAnt shareQueenLocation method.
	 */
	@Override
	public void process(WorkerAnt ant) {
		//your code here
	}
	/**
	 * act method needs to be overridden with an empty 
	 * "do nothing" method (QueenAnts don't act)
	 */
	@Override
	public void act(){

	}
	public int getFoodAmount(){
		return foodAmount;
	}

}

WorkerAnt.java
import java.awt.Color;
import java.util.ArrayList;

import info.gridworld.actor.*;
import info.gridworld.grid.*;

public class WorkerAnt extends Critter implements Processable
{
	int food;
	Location foodLoc;
	Location queenLoc;
	ArrayList<Location> exhaustedFoodSupply;
	public WorkerAnt()
	{
		food=0;
		foodLoc=null;
		queenLoc=null;
		setColor(Color.BLACK);
		exhaustedFoodSupply = new ArrayList<Location>();
	}

	/**
	 * find out if the other ant knows about food or queen
	 * locations. call the passed worker ant's 
	 * shareFoodLocation and shareQueenLocation methods 
	 * to share the food and queen locations with the other ant.
	 */
	public void process(WorkerAnt ant) {
		// your code here
	}
	/**
	 * receive food from a food source
	 * @param amount
	 */
	public void takeFood(int amount){
		// your code here
	}
	/**
	 * give all your food (used by the queen)
	 * reset food=0
	 */
	public int giveFood(){
		// your code here
	}
	/**
	 * onList checks to see if the location
	 * is on out List of exhausted Food Locations
	 */
	public boolean onList(Location loc){
		boolean result=false;

		// your code here 

		return result;
	}

	/**
	 * Foods and fellow WorkerAnts call shareFoodLocation 
	 * 
	 * (1) If the fLoc is not null, see if
	 * that location is not null and if it isn't
	 * check to see if it is Food, if so, remove it from
	 * the exhaustedFoodSupply list (if it's there)
	 *  
	 * (2) If this.foodLoc is null and fLoc isn't on 
	 * the list of exhausted food locations, 
	 * then set this.foodLoc to the value of fLoc.
	 * 
	 * (3) return our foodLoc.
	 */
	public Location shareFoodLocation(Location fLoc){

                // your code here

	}

	/**
	 * Queens and worker ants call shareQueenLocation 
	 * to share the queen location. 
	 * If the current saved queen location is null, 
	 * then set it to the value of qLoc.
	 */
	public Location shareQueenLoc(Location qLoc)
	{
		// your code here
	}

	/**
	 * getDesiredDirecton body to return one of three directions:
	 * (1) If the queen location is not null and the food quantity is not zero, 
	 * then return the direction from this ant toward the known location 
	 * of the queen (use Location 's getDirectionToward method).
	 * (2) Otherwise, if the food location is not null and the food quantity 
	 * is zero, then return the direction from this ant toward the 
	 * known location of the food. 
	 * (3) Otherwise, return the current direction of this ant.
	 * 
	 */
	public int getDesiredDirection()
	{
		// your code here
	}
	/**
	 * Each actor in actors needs to call its process method. 
	 * The parameter for each call will be this, 
	 * the reference to the worker ant executing the processActors method.
	 * 
	 * An Actor could be a QueenAnt, a Cake, a Cookie, or a WorkerAnt. 
	 * Without the Processable interface, processActors would need to 
	 * determine the type of actor and then downcast the actor 
	 * reference before making the call to process. 
	 * But, since each of these classes implements Processable, 
	 * processActors only needs to cast the actor to Processable before 
	 * the call. 
	 */
	public void processActors(ArrayList<Actor> actors)
	{
		// your code here
	}
	/**
	 * Calls the private getDesiredDirecton method to get the general 
	 * direction the ant wants to move.
	 * Creates a list with up to three adjacent locations that are in 
	 * the general direction of the one returned by getDesiredDirection. 
	 * Locations are included if they meet all of the following criteria:
	 *  They must be: 
	 *  (1) Adjacent to the current location.
	 *  (2) In the desired direction, or HALF_LEFT of the desired direction, 
	 *  (3) or HALF_RIGHT of the desired direction.
	 *  (4) Valid (in the grid).
	 *  (5) Empty.
	 */
	public ArrayList<Location> getMoveLocations()
	{
		ArrayList<Location> result=new ArrayList<Location>();

                //your code here

		return result;
	}

	/**
	 * makeMove is a valid place to change the state of a Critter.
	 * Here we need to: 
	 * 
	 * (1) set its color based upon whether it has food (red) or not (black).
	 * 
	 * (2) if it is located at the Food Location and it is still black, 
	 * the food has disappeared, so the foodLocation should be set to null,
	 * and it should be added to the list of exhausted food locations.
	 * 
	 * (3) If the selected move location is different from the current location, 
	 * makeMove moves to the selected location after it changes its direction 
	 * to match the direction of where it is moving.
	 * 
	 * (4) If the move location is the same as its current location,
	 * it should randomly turn to either Location.HALF_LEFT or  
	 * Location.HALF_RIGHT
	 * 
	 */

	public void makeMove(Location loc)
	{
		//your code here
	}

}