Ant Farm Gridworld Project

posted by: Ms. Martin 5 April 2010 No Comment

Thanks to Robert Glen Martin for the project idea and writeup. Any errors are my own!

You will need to turn in whatever you have by 11:30pm on Monday April 11th. No late work will be accepted — not turning anything in will result in a 0.

Introduction

This assignment is designed to get you thinking about the design of larger systems, to get a small introduction to artificial intelligence and to deepen your familiarity with GridWorld. Hopefully you’ll find it pretty cool — by the end, your ants should be acting in a coordinated fashion to collect food and bring it back to their queen. I think it’s a great illustration of the power of object-oriented design.

This document does a lot more “hand holding” than the previous assignment descriptions have and I’m giving you a lot more starter code. In a way, that’s more of a realistic programming situation than those we’ve experienced so far — many more people work on modifying and adapting systems than start them from scratch! I also think this is a pretty good way of illustrating some core concepts without having you spin your wheels too much. 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.

Begin by downloading AntFarmStudent, a zip file containing all the starter code. Make sure you take a look at each class briefly to get a big-picture view of what the system should be doing and how the various pieces are connected together.

Overview

The project utilizes four new types of objects (see Figure 1), two kinds of food (ACookie and BCake) and two kinds of ants (CWorkerAnt and DQueenAnt). 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.

Figure 1 – Ant Farm (Initial Sate) – Worker ants hunt for 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 (see Figure 3 below).

Figure 2 – Intermediate State – worker ants start learning locations of food & queen.
Figure 3 – Final State – worker ants know locations of food & queen.

Program Organization

Figure 4 shows the organization of the Ant Farm classes and interface.

Figure 4 – Ant Farm Classes

Recall that 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 through their immediate parent, the abstract class Food.

Also recall that Critter is a GridWorld type derived from Actor. Critters have a particular pattern for their act 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 requires a Processable interface. This interface has a single process method that is the key to communication between worker ants and the other Actors. By implementing a common interface, QueenAnt, Cake, Cookie, and WorkerAnt objects will be able to be used interchangeably when they need to communicate.

Starter Code

AntFarmRunner (the application) is complete and requires no changes. The needed imports, class headings, method headings, block comments, and image (gif) files are provided for the remaining classes. Place the gif files in the same directory as your .class files are stored. The interface heading and comments are provided for Processable.

Compiling early and often is a good programming practice. It helps identify errors when they are easiest to fix. Compile and execute the project. You should see all the Actors on the screen. All the Actors are blue at this point. Why? (Hint: think about which parent classes are involved)

Note: Clicking the Step or Run button at this point will cause a NullPointerException since many of the method bodies just return null for the time being. This is another great design tactic: writing headers for all methods before implementation really helps catch problems with a project’s design early on. Of course, a header that includes a return type will keep the method from compiling unless something really is returned. It’s typical to return “” as a placeholder for a method that must return a String or 0 for int or double methods. The corresponding value for objects is null. Unsurprisingly this practice is likely to cause run-time NullPointerExceptions. At this point, that’s totally fine — we’re just interested in making sure there are no compile-time errors.

Processable Interface

The Processable interface is a key component of the Ant Farm design and a great opportunity for you to better understand the value of interfaces as well as some of the details of polymorphism. Examine the Processable.java file. You should see that the interface contains a single method: process. The process method is used to communicate with the WorkerAnt object passed in as a parameter. This allows for communication between your Ant Farm’s objects. Since QueenAnt, Food, and WorkerAnt all implement the Processable interface, they must all implement the process method. The beauty of this is that all classes which implement the Processable interface can be initialized as or cast into Processable objects. Here are examples of how this works:

	WorkerAnt andy = new WorkerAnt();
	Processable c = new Cake();
	c.process(andy); // no problem since Processable requires process method
	c.act(); // error because the reference type (Processable) does not implement an act method.
	// It doesn't matter that Cake actually implements act!  The variable c can only use methods declared in the Processable interface.

	((Processable)andy).process(new WorkerAnt()); // an example of casting to Processable 

	ArrayList l = new ArrayList();
	l.add(new Cake());
	l.add(new WorkerAnt());
	l.add(new QueenAnt());
	for(Processable p : l) {
		p.process(andy);
	}

Notice that the process method can be called polymorphically on any object which implements Processable. Sweet!!

As described, the Processable interface should contain a single void process method which takes one parameter of type WorkerAnt. Add the abstract process method in the Processable interface.

Compile the project and correct any errors.

You will later implement the process method in each class which implements the interface. The individual process methods in each class will do the following (repeated later):

  • 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.

QueenAnt Class

Figure 5 shows the QueenAnt class. Queen ants are the simplest of the new Ant Farm objects.

Figure 5 QueenAnt Class.

Start updating QueenAnt by adding the new foodQuantity instance field. It is used to contain the total amount of food that has been given to the queen by the worker ants. You will make all instance fields private to preserve encapsulation.

Write the constructor body. It needs to initialize foodQuantity to 0 and use the inherited setColor method to set the queen’s color to Color.MAGENTA.

Since QueenAnt implements Processable, you need to write the process method. process needs to get food from the passed worker ant using the WorkerAnt giveFood method. This method, which is shown in Figure 7 below, 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. Write the process method.

The Actor act method needs to be overridden with an empty “do nothing” method (QueenAnts don’t act). Look at QueenAnt to see how this was accomplished.  Note the use of the @Override annotation (on the line preceding the act method heading).  You don’t need to remember this, but @Override is very helpful.  If you accidentally misspell the method name, @Override will cause a compile error telling you about this mistake. This error can be very difficult to find otherwise. Compiler errors may cause you irritation, but they’re much, much better than silent mistakes!! Many of you experienced problems that didn’t cause errors but just made your program fail with ShoppingCart.

The Actor toString method also needs to be overridden to add additional information to the string returned by Actor‘s toString. Notice that the Actor toString() already provides useful information: the Actor type, its location, its direction and its color. We’d like to retain this behavior so this provides a good example of using super to call a superclass method. Here is the code you will need:

	return super.toString() + ", FQty=" + foodQuantity;

Compile and execute your project. The queen ant at location (15, 9) should now be magenta. Hover your mouse over the queen — you should get “QueenAnt[location=(9, 15),direction=0,color=java.awt.Color[r=255,g=0,b=255]], FQty=0″.

Foods – Food, Cake, and Cookie Classes

Figure 6 shows the Food, Cake, and Cookie classes. Foods act like queens, but they give food instead of getting it.

Figure 6 – Food, Cake, and Cookie Classes.

Different kinds of food are very similar. They differ only by the size of a bite and the displayed image (note that the image displayed is simply the name of the class with .gif at the end). 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 can not be instantiated. The use of abstract classes is important for you to understand! It doesn’t make sense to have a thing which is a ‘food’ — that’s not specific enough. So in this case, we’re deliberately blocking instantiation of Foods even though the class contains complete code.

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:

  • BITE_SIZE – a constant that determines how much food is given to a worker ant when it gets food.
  • foodEaten – keeps track of the total amount of food “given” to worker ants.

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.

Update Food to include the two new instance fields and complete the constructor as discussed above. You will need to uncomment the constructor heading and brackets. Note that Java allows constant (final) instance fields to be initialized in a constructor but not to be modified anywhere else.

Compile your Ant Farm project again. Food should have no errors, but Cake and Cookie now have compile errors. Why? What change caused these errors? Recall that subclasses do not inherit their superclass’ constructors. We will fix these errors later.

All foods implement the process method (from Processable) to give food to the passed worker ant and to provide it the food’s location. Foods need to override the Actor act method with an empty “do nothing” method (foods don’t act). Foods also need to override the toString method to include the BITE_SIZE and foodEaten information. Since all three of these methods are the same for all foods, they are placed in Food. Otherwise they would have to be written in both Cake and Cookie.

Write the process method (use WorkerAnt‘s takeFood and shareFoodLocation methods). Also replace the body of the toString method as discussed above. Don’t forget to include the Actor super class toString information like you did with QueenAnt. Make sure that Food compiles without error.

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.

Complete the Cake and Cookie classes by adding the BITE class constants (see Figure 6 for the appropriate values).

Complete the Cake and Cookie constructors by adding a single statement – super(BITE); This causes the one parameter Food constructor to be used when a Cake or Cookie is created.

Compile and execute the project. The cake and cookie should now display properly. They should not be tinted. Hover your mouse above the cake and cookie images to make sure that the toString methods are working properly. For example, the cookie should display “(2, 2) contains Cookie[location=(2, 2),direction=0,color=null], BSize=1, FEaten=0″

WorkerAnt Class

WorkerAnt (Figure 7) is the most complex Ant Farm class. This is to be expected,
since Critters interact with other Actors in the grid.

Figure 7 – WorkerAnt Class.

Worker ants have instance fields to keep track of the amount of food they currently have as well as known locations of the food and the queen.

The constructor initializes these instance fields (to 0, null, and null), makes the ant black, and uses Math.random to randomly point the ant in one of the eight valid compass directions.

Add the WorkerAnt instance fields and complete the constructor as discussed above and in Figure 7. Use the Location constants HALF_RIGHT and FULL_CIRCLE when writing your constructor. Do not “hard code” constants like 45 and 8. Compile and execute the project to check your code.

WorkerAnt implements the process method to share queen and food locations with other worker ants. WorkerAnt has four methods that do the actual “processing.” They are takeFood , giveFood , shareFoodLocation, and shareQueenLocation. These methods are called from the process methods of the QueenAnt, Food, and WorkerAnt classes.

Complete the five processing methods as follows. Make sure that the project compiles after every change.

  1. process – call the passed worker ant’s shareFoodLocation and shareQueenLocation methods to
    share the food and queen locations with the other ant.
  2. takeFood – add the amount of food passed in fQty to the food quantity instance field.
  3. giveFood – replace the method body to return the current food quantity to the caller
    (queen). Before giveFood exits, the food quantity needs to be reset to zero (all the food is
    being given to the queen).
  4. shareFoodLocation – Foods and worker ants call shareFoodLocation to share the food location.
    If the current saved food location is null, then set it to the value of fLoc.
  5. shareQueenLocation – 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.

You are about ready to override several of the Critter act methods, but first you will need to complete the useful getDesiredDirecton private helper method. This method returns the general direction that the ant wants to go.

Replace the 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.

The Critter act method calls the following methods in this order:

  1. getActors – gets a list of Actors for interaction.
  2. processActors – interacts with each of the Actors in the list from getActors.
  3. getMoveLocations – gets a list of possible locations for moving this critter.
  4. selectMoveLocation – chooses one of the possible move locations for this critter.
  5. makeMove – moves this critter.

WorkerAnt inherits the Critter act method which does the following:

  1. Uses the inherited getActors to get all the adjacent neighboring Actors.
  2. processActors processes each of the neighboring ant farm actors. This method should be very short.
    It needs a loop to traverse (loop through) the actors ArrayList. An enhanced for loop works well for this.
    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. This polymorphic
    processing is allowed because Processable contains the process abstract method. The Java Run
    Time Environment (JRE) determines the actual type of object at runtime and calls the appropriate process method.

    Complete the processActors method as discussed in the preceding paragraphs.

  3. getMoveLocations does the following:
    1. Calls the private getDesiredDirecton method to get the general direction the ant wants to move.
    2. 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 45 degrees to the left of the desired direction, or 45 degrees to the right
        of the desired direction.
      3. Valid (in the grid).
      4. Empty.
    3. Returns the list of locations.Replace the getMoveLocations body as discussed above. For part b, use Location ‘s HALF_LEFT and HALF_RIGHT
      constants. You may also want to use the getAdjacentLocation method, Grid‘s isValid to see if a given location
      is valid (is in the grid) and get to help see if the location is empty (get returns null). There are other possible implementations, though.
  4. Uses the inherited selectMoveLocation to randomly select one of the possible locations. If the list of
    possible locations is empty, it returns the current location.
  5. If the selected move location is different from the current location, makeMove moves to the selected
    location and changes its direction to match the direction it moved. Otherwise it stays put and changes its
    direction by randomly choosing between the two directions 45 degrees to the left or right (use Location.HALF_LEFT
    and Location.HALF_RIGHT ). Then, in either case, it sets its color based on if it has food (red) or not (black).Write the body of the makeMove method as discussed above.

    Complete the toString body to include Critter‘s toString result as well as the values of the WorkerAnt instance
    fields.

    Compile and thoroughly test your project. Make sure that your actors behave properly as described in the
    Overview section at the beginning of this assignment. You can learn a lot about the the state of your actors
    by viewing their toString information (hover over the object). Make sure this information changes appropriately
    as your Actors interact with each other.

Congratulations, your Ant Farm should now be exhibiting intelligent behavior! You have completed what you need to do for this assignment. If you’re done, I expect you to complete the following extensions!

Extensions

Now that you know how all the classes interact change and expand your Ant Farm classes to do the following.

  1. Give Food and WorkerAnt weight. Start each food item with the weight of 5,000,000 micrograms and
    each ant with a weight of 1,000 micrograms plus or minus 300 micrograms.
    Ants can carry up to 20 times their own body weight.
    When an ant takes a bite from the food, the food weight
    is decrease by 20 times the ant’s body weight, which is the amount carried off by the ant.
    When the food weight gets to zero have the food removed from the grid.

    You do not need the constants BITE_SIZE nor the instance variable bite any more. Remove all reference from the Food,
    Cake and Cookie classes.

    Remember to use constants for values you create and to update the toString methods!

  2. When WorkerAnts come back to a food location they remembered, and the food has disappeared, change the remembered food
    location to inidcate the food is gone. This information should be propagated to the rest of the ants.

    This is tricky to figure out. Do not add any additional instance fields. When a Food‘s weight becomes zero, it removes itself
    from the grid. Make sure the food object only shares it’s location if it is in the grid!
    The AntWorker makeMove method now checks to see if the food is not at the location where it is suppose to be. If this is the case,
    then change the food location to a bogus location that is not found in the grid! The method
    getDesiredDirection will have to be altered slightly for this process to work, plus the method shareFoodLocation will
    have to be altered . It might be helpful to write private
    help methods isFoodLocationInGrid(Location) and isBogusLocation(Location) that determines if a food location is still
    in the grid or is a bogus location. To get the logic correct for shareFoodLocation create a matrix with all the possiblities:

    null Good Bogus
    null
    Good
    Bogus
  3. Add energy to the WorkerAnt class.
    Ants should start with 100 units of energy and for each step the ant should lose energy by:

    • If the ant cannot move it looses 1 unit of energy and 1 microgram of weight.
    • If the ant can move and it is not carrying any food it looses 2 units of energy and 2 micrograms of weight.
    • If the ant can move and it is carrying food it looses 2 units of energy plus the amount it is carrying divided by 10,000
      units of energy. It also losses 3 micrograms of weight.
  4. Ants gain energy when they find food. They should eat 1/4 of their body weight of food, which in turn is converted into
    (food eaten) / 1000 units of energy. Weight is increased by
    (food eaten) / 200 micrograms. Remember the ant’s bite of food is still 20 times the ant’s body weight.
  5. Ants die when their energy or weight is reduced to 0. If they are carrying food when they die a food item should be placed in the cell that
    the ant dies in. The food created should be randomly selected (cake or cookie) with the weight of food being carried by the ant. You
    could add what kind of food the ant is carrying, then drop that same kind of food when the ant dies.
1 Star2 Stars3 Stars4 Stars5 Stars (8 votes, average: 3.50 out of 5)
Loading ... Loading ...

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>