GridWorld User Guide

Cay S. Horstmann

Introduction

GridWorld is a graphical environment for helping students visualize the behavior of objects. Students implement the behavior of actors, add actor instances to the grid, and see whether the actual behavior conforms to their expecations. GridWorld provides an engaging user interface that makes this exploration easy and fun. . GridWorld is intended as an adjunct to a regular introductory course, not a replacement. I expect that instructors use a standard textbook and a standard compilation environment in the usual way, using GridWorld for lab assignments and homework projects when appropriate. The GridWorld user interface can be used to visualize other grid-like arrangements such as board games, artificial life, mazes, and so on. See the section Other Worlds for more information. GridWorld began as a stripped-down version of the AP Computer Science Marine Biology Case Study . I ruthlessly eliminated as much of the gratuitous complexity as I could. I started tinkering with the GUI, allowing users to drop in GIFfiles for personalized actors. Then I wanted to integrate GridWorld with BlueJ so that I could call methods on the actors and see the result immediately. It worked poorly, but I realized that I could implement that behavior directly inside GridWorld. At this point, GridWorld started taking on a life of its own. The GridWorld code is licensed under the GNU General Public License , as was its predecessor. That means that you can do anything you want with the code, with the "viral" condition that you must give others the same rights to your modifications. Had the Marine Biology Case Study been covered by a more restrictive license, I would not have put in the time and effort to make GridWorld. Perhaps someone will be be able to use this code to come up with better educational tools, and then we all will be able to enjoy the benefits of our collective labor. It is this free and open process, and not a restrictive "intellectual property regime", that truly promotes the progress of science and the useful arts.

Software Installation

Prerequisites:

Download the GridWorld distribution. Unzip it in a directory of your choice. As always, it is best to avoid directories with spaces such as My Documents . I suggest you unzip into c:\GridWorld (Windows) or ~/GridWorld (Linux, Mac). Look inside the directory that you just created and populated with the GridWorld files. Locate the file gridworld.jar . It is the most important file of the GridWorld distribution. You use this file for two separate purposes: You need to tell your compiler or development environment about the gridworld.jar file. Each development environment has its own user interface for this task; you will find information about popular environments at the end of this document. You need to know how to launch a Java program that is distributed as a JAR file. On some operating systems, you may be able to double-click on the gridworld.jar file. Alternatively, you can open a command shell and type java -jar   /path/to/ gridworld.jar Replace /path/to with the path such as c:\GridWorld\ or ~/GridWorld .  Try it out: Launch GridWorld now. You should see a "starter world" like this: .

Your Starter World

Prerequisites:

Activity 1: Step and Run

When you launch GridWorld, you see a "starter world" containing a critter and a rock . A critter is a simulated creature that moves in the grid. When it moves, it drops a flower at its old position, letting you watch the critter's path. A rock has no purpose other than to block the critter. Critters don't pretend to be useful. They don't simulate any real entities, and their behavior is of no interest to biologists or social scientists. They were simply invented to make programming more intuitive and enjoyable. To see the critter in action, click the Step button. Click it a few more times. What happens when the critter runs to an edge of the grid or the rock? . Now click the Run button. The critter keeps moving, as if you had clicked Step many times. You can crank up the speed by moving the slider to the right. After a while, the critter will have settled into a fixed path. Click Stop to stop it.

Activity 2: Constructing an Object

Click into one of the empty cells of the grid. You will see a menu that shows constructors of the Critter and the Rock class. The Critter class has two constructors. The default constructor makes a green critter, and the second constructor lets you specify a color. The Rock class only has one constructor. . Select the Rock constructor. A rock is inserted at the cursor location. Add a few more rocks, run the simulation and watch the critter bump into the rocks. You can add another critter in the same way. Try adding a pink critter into another empty location. Choose the constructor labeled com.collegeboard.gridworld.actor.Critter(java.awt.Color) A color dialog pops up. . The first entry signifies a random color. Move down until you see a hot pink bar and select it. Click Ok . A pink critter is added. Click Step , and both critters move. Note that the pink critter drops pink flowers.

Activity 3: Calling a Method

Click on one of the critters. You will see a menu that shows the methods of the Critter class. The menu is divided into two parts. The top part contains the Critter methods
act
canMove
move
setTracing
turn
. The bottom part contains a number of methods that are inherited from the Actor class. We will not need these methods when working with critters. (However, you may find the removeFromGrid method useful to remove excess critters or rocks.) Select the act method. You will see the critter act as if you had clicked the Step button. In fact, the Step button simply calls act for all grid occupants. Flowers and rocks define act to do nothing, but the Critter class has a more sophisticated definition of acting. If the critter can move, it does. Otherwise, it turns. Call the turn method. You can see the critter making a half-turn to the right. Call turn and move a few times until you get a feel for these methods. You will use them a lot when programming critters. Some methods return values. For example, the canMove method returns true if the critter can move in the current direction. When you call canMove , a dialog displays the result. . Some methods take inputs. For example, the setTracing method lets you turn tracing on or off. When you call setTracing(false) , the critter stops dropping flowers. Call setTracing and set the input to false . . Now move the critter and see that it no longer leaves a trace.

Your First Critter

Prerequisites:

The critter in the starter grid is pretty boring. In this section, you will learn how to modify the behavior of a critter. To make your own critter, follow these steps:
  1. Make a new directory
  2. In that directory, define a class that extends Critter
  3. Implement the act method
  4. Optionally, supply a GIF image
  5. In GridWorld, select your project directory
Here is a typical example. Make a directory to hold your project. I suggest c:\GridWorld\projects\myFirstCritter (Windows) or ~/GridWorld/projects/ myFirstCritter (Linux, Mac), but any directory will do. Type in the following class:
import com.collegeboard.gridworld.actor.Critter;
public class MyCritter extends Critter
{
  
public void act()
   {
      move();
      move();
      turn();
      turn();
   }
}

Compile the class. You need to use your own compiler or development environment. GridWorld can not edit or compile Java source code.

NOTE:  If the compiler can't find the Critter class, you need to add the gridworld.jar file to your compiler's class path.

If you like, you can add a file MyCritter.gif into the same directory. Any GIF image will do. If you don't supply your own image, the basic critter image is used instead.

Start GridWorld, select the World -> Load Project menu option, and select the directory that you created. A MyCritter object is automatically inserted. Click on Step a few times to see it act.

.

That's all there is to it. To get more sophisticated critters, you need to put more sophisticated code into the act method.

NOTE: You use two separate tools for your programming:
  1. Your compiler or development environment, to compile your program
  2. The GridWorld GUI, to run your program
Some people prefer to launch the GridWorld GUI from their development environment. That is easily done with a small launcher class, such as the following.
import com.collegeboard.gridworld.actor.ActorWorld;

public class MyWorld extends ActorWorld
{
public MyWorld()
{
// configure my world
add(new MyCritter());
}

public static void main(String[] args)
{
// construct and show my world
new MyWorld().show();
}
}

Exploring Object State

Prerequisites:

Activity 1: Tracing a Square

In this section, we want to make a critter that carries out a specific task: to trace out a square of a given size.

.
In each step, the critter should either move (and drop a flower) or turn. If the square has sides of length n , then the critter needs to turn every n steps. Therefore, the critter needs to keep track of the number of steps that it has already taken.

An instance field is used for this purpose:
import com.collegeboard.gridworld.actor.Critter;
public class SquareDancer extends Critter
{
  
public void act()
   {
      . . .
   }

   private int steps;
}

If the number of steps is less than the side length, then the critter should move. Otherwise it should make a quarter turn.

        
      public void act()
   {
      if (steps < SIDE_LENGTH)
{
move();
steps++;
}
else
{
turn();
turn();
steps = 0;
}
   }
Note that we reset the step count to zero at each corner.

Here, we define SIDE_LENGTH as a constant:

public class SquareDancer extends Critter
{
  
public void act()
   {
      . . .
   }

   private int steps;
   private static final int SIDE_LENGTH = 5;
}

Activity 2: Supplying a Construction Parameter

How can we draw a square with a different side length? We'll add a second instance field to the  SquareDancer and supply a constructor that sets it.
import com.collegeboard.gridworld.actor.Critter;
public class SquareDancer extends Critter
{
   public SquareDancer(int n)
   {
      sideLength = n;
   }

  
public void act()
   {
      . . .
   }

   private int steps;
   private int sideLength;
}

When you compile this class and select World -> Load Project, you don't get a SquareDancer object. The GridWorld doesn't know how to construct one since it doesn't know which value of n you might want. 

This is not a major obstacle. Simply click on an empty cell and select the SquareDancer(int) constructor. Then specify a value.

.


Here we constructed two square dancers and changed the color of one of them. Then we ran them until they traced out their squares.

.

Activity 3: Making a World

When you design new actor classes, you may want to specify a world for them to live in. Extend the ActorWorld class. In the constructor of your class, add actors at the desired locations.

import com.collegeboard.gridworld.actor.ActorWorld;
import com.collegeboard.gridworld.grid.Location;
import java.awt.Color;

public class SquareDancerWorld extends ActorWorld
{
public SquareDancerWorld()
{
SquareDancer alice = new SquareDancer(6);
alice.setColor(Color.ORANGE);
SquareDancer bob = new SquareDancer(3);
add(new Location(7, 8), alice);
add(new Location(5, 5), bob);
}
}
Compile and select World -> Load Project in GridWorld. Your customized world will be loaded.

.

If you like, you can select a different grid. For example,
import com.collegeboard.grid.BoundedGrid;
import com.collegeboard.gridworld.actor.ActorWorld;

public class SquareDancerWorld extends ActorWorld
{
public SquareDancerWorld()
{
setGrid(new BoundedGrid(20, 20));
. . .
}
}
You can also set an UnboundedGrid ; then your critters never bump against an edge.

import com.collegeboard.grid.UnboundedGrid;
import com.collegeboard.gridworld.actor.ActorWorld;

public class SquareDancerWorld extends ActorWorld
{
public SquareDancerWorld()
{
setGrid(new UnboundedGrid());
. . .
}
}

Exploring Loops

Prerequisites:

You can use GridWorld to visualize the actions of certain for loops. Many students find this visualization helpful for practicing their loop authoring skills.

Activity 1: Placing Rocks

Consider this typical loop exercise. You are supposed to display a triangle with n rows, like this:

.

You need two nested loops. The outer loop traverses the n rows:

for (int i = 1; i <= n; i++)

For each row, you place 2n + 1 rocks.

for (int i = 1; i <= n; i++)
for (int j = 1; j <= 2 * n + 1; j++)
Now you need to place the rocks. There is no need for a critter in this example. Simply extend ActorWorld :
public class TriangleWorld extends ActorWorld
{
public TriangleWorld()
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= 2 * n + 1; j++) }
{
int row = i;
int col = n - i + j;
add(new Location(row, col), new Rock());
}
}
}
When you load this project, the world constructor is executed, and you immediately see a pile of rocks. You can mouse over the rocks to check their locations.

.

Activity 2: Stepping through a Loop

Maybe you want to see how the rocks are added, a step at a time? If so, extend a different world, the StepWorld . Place your code in the run method and call pause whenever you want the action to pause.
public class TriangleWorld extends StepWorld
{
public void run()
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= 2 * n + 1; j++) }
{
int row = i;
int col = n - i + j;
add(new Location(row, col), new Rock());
pause();
}
}
}
When you load the world, its run method executes until the first call to pause . Then it waits for you to click the Step button. Each time you click Step , the run method runs up to the next pause call. You can also click the Run button and see the program run in slow motion.

If you like, you can also add a message to the pause method. The message is displayed above the grid. For example,
pause("i=" + i + ",j=" + j);
displays the current values of i and j .

.

Exploring Inheritance

Prerequisites:

Activity 1: Eaters

In this section, we build up an inheritance hierachy of actors that look for food in the grid. We start with a class Eater that defines trivial methods eat and move . These methods will be redefined in subclasses:

public class Eater extends Actor
{
   public boolean eat() { return false; }
   public void move() {}
   . . .
}


We expect eat to return true whenever the eater was successful in consuming a food item.

The act method is defined as follows:

      public void act()
   {
      if (eat())
         count++;
      else
         move();
   }
  
   private int count;


As you can see, a basic Eater isn't very successful. It neither eats nor moves. We will define subclasses that do a better job consuming the food that is available in the grid.

When you form subclasses, you should follow these rules. (These are not rules of Java, but merely rules of the eater game.)
The Eater class has two convenience methods that get the neighboring actors (for eating) and the empty neighbor locations (for moving).

We provide a simple implementation of the Eater class, RockHound . A RockHound likes to eat rocks. In its eat method, it looks at its neighbors and consumes the first one that is a Rock . In its move method, it moves to a random empty neighbor location.

The EaterWorld is populated with some rocks and a RockHound . Load it and see the RockHound randomly move about and consume the rocks.

.

Activity 2: Smart Eaters

How can a rock hound to a better job without cheating? (Cheating is easy--just get the whole grid and remove all rocks. We discuss measures against cheating in the next activity.)

The key is to explore the grid more systematically. A SmartRockHound remembers the locations that it has already visited, and it doesn't visit them again if it has a choice.
public class SmartRockHound extends RockHound
{
public SmartRockHound(Color color)
{
super(color);
visitedLocations = new ArrayList<Location>();
}

public void move()
{
visitedLocations.add(getLocation());
for (Location loc : getEmptyNeighborLocations())
{
if (!visitedLocations.contains(loc))
{
moveTo(loc);
return;
}
}
super.move();
}

private ArrayList<Location> visitedLocations;
}

Try it out: Add a SmartRockHound to the EaterWorld and see how it out-eats the RockHound .

Food for thought: The SmartRockHound does rather well in a BoundedGrid . Try putting it into an UnboundedGrid . What goes wrong? How can you fix it?

Activity 3: Cheaters and Honest Eaters

Food for thought: Can you design an eater that is even more efficient than a SmartRockHound ? Can you do it without cheating?

When faced with this problem, the temptation to cheat can be overwhelming. Let's try to prevent cheating. The HonestEater class contains anti-cheating measures. To prevent cheating, we require all students to extend HonestEater rather than Eater .

The act method of the HonestEater class calls the act method of Eater , but it first records
After the act method returns (which, as you recall, invokes eat and move ), the following checks are carried out:
If any of the checks show cheating, the penalty is cruel: this actor is removed from the grid.

The act method has been defined as final so that a dishonest eater can't override it.

There is another check, to prevent eaters from peeking beyond their neighbors: The getGrid method returns null . This way, an eater can't map out an optimal path to all food sources.

Food for thought: Can a subclass of HonestEater defeat the greediness check by adding worthless trinkets into the grid after consuming multiple rocks?

Food for thought: There is one nefarious practice that the HonestEater does not detect. An eater can move to multiple locations and peek, thereby gaining an unfair advantage. How can you prevent that?

Exploring Recursion

Activity 1: A triangle of rocks

We want to create an actor that drops rocks in a triangle shape, like this:

.

One rock should be dropped in each call to act .

The size of the triangle (number of rocks at the bottom) is passed as a construction parameter:
TriangleWalker(int size)
But we don't want to work very hard. Here is an interesting approach.

Make the triangle walker lay a row of rocks. That's easy:
public void act()
{
if (steps > 0)
{
Grid<Actor> gr = getGrid();
Location loc = getLocation();
moveTo(gr.getNeighborLocation(loc, Grid.EAST));
new Rock().putInGrid(gr, loc);
steps--;
}
}
TODO: This would be easier if the actor didn't store the grid, or if there was a convenience method moveInDirection(Grid.EAST)

Now the triangle walker could go back up and lay a shorter row of rocks and a shorter one again. But that's tedious. Instead, it will just construct a buddy:
buddy = new TriangleWalker(size - 1);
When it is done laying a row of rocks, it adds the buddy to the grid and removes itself.

   public void act()
   {
      if (steps > 0)
      {
         . . .
      }
      else
      {
         if (buddy != null) buddy.putInGrid(gr, buddyLocation);
         removeFromGrid();
      }

   }

This is a recursive solution. To do its job, the TriangleWalker asks another TriangleWalker to carry out a simpler instance of the job.

Here is the complete class: TriangleWalker.java

Exercise: Implement a triangle walker that makes a triangle like this:

*
***
*****

Activity 2: One-eyed pyramids

One-eyed pyramids (like the one on the dollar bill ) want to know where rocks are, but they can only see their direct neighbors, and they can't move.

Recursion to the rescue: If a pyramid doesn't see a rock, it creates a buddy pyramid in a neighboring location. In each call to act , it asks its buddies if they have seen a rock. Once a pyramid has been enlightened, it turns yellow.

.

Here is the complete class: RockFinder.java

Exercise: In this solution, a RockFinder keeps making buddies. What would happen if each RockFinder makes just one buddy? Try it out.

Other Worlds

Prerequisites:

  • Inheritance
  • Interfaces
It is easy to build other worlds, for example board games that live as a grid. Here is an example: a tile game to practice one's memory.

The game board contains covered tiles. The player's job is to uncover matching pairs. Click on a tile to flip it. Then click on another. If both tiles have the same color, they both stay exposed. If not, they are both covered again.

.

To implement this game, we don't need to subclass Actor . Simply provide a Tile class with a getColor and a flip method. The getColor method is called by the GridWorld framework to apply the correct color. Return black if the tile is covered and the actual color when it is exposed.
public class Tile
{
private Color color;
private boolean up;

public Tile(Color color)
{
up = false;
this.color = color;
}

public Color getColor()
{
if (up)
return color;
else
return Color.BLACK;
}

public void setColor(Color color)
{
this.color = color;
}

public void flip()
{
up = !up;
}
}

The game class extends AbstractWorld<Tile> since the world contains a grid of tiles. The constructor populates the grid, and the locationClicked method is called whenever the user clicks on a location. 

public class TileGame extends AbstractWorld<Tile>
{
public TileGame()
{
Color[] colors =
{
Color.RED, Color.BLUE, Color.GREEN, Color.CYAN,
Color.PINK, Color.ORANGE, Color.GRAY, Color.MAGENTA,
Color.WHITE, Color.YELLOW
};
for (Color color : colors)
{
add(new Tile(color));
add(new Tile(color));
}
setMessage("Click on the first tile");
}

public boolean locationClicked(Location loc)
{
Grid<Tile> gr = getGrid();
Tile t = gr.get(loc);
if (t != null)
{
t.flip();
if (up == null)
{
setMessage("Click on the second tile");
up = loc;
}
else
{
Tile first = gr.get(up);
if (!first.getColor().equals(t.getColor()))
{
first.flip();
t.flip();
}
setMessage("Click on the first tile");
up = null;
}

}
return true;
}

private Location up;
}
The locationClicked method returns true to indicate to the framework that the game itself handles clicks. (Otherwise the framework would try to manipulate or add tiles.)

Note that the Step and Run buttons don't do anything in this game. 

Appendix: Installing the JAR file

Eclipse

You need to do the following for every project.
  1. Start a project as usual
  2. Select the project in the "Package explorer" window
  3. Select Project -> Properties from the menu
  4. Click on Java Build Path and select the Libraries tab
    .
  5. Click the Add External JARs... button
  6. Navigate to the directory containing gridworld.jar. Select gridworld.jar.

BlueJ

You need to do the following once .
  1. Select the menu option Tools -> Preferences
  2. In the resulting dialog, click on the Libraries tab
    .
  3. Click the Add button
  4. Navigate to the directory containing gridworld.jar. Select gridworld.jar.
  5. Restart BlueJ

Command-line Compiler

When you compile or run programs, add both the current directory and gridworld.jar to the class path. On Windows, use a semicolon to separate . (i.e. the current directory) from the path to the JAR file:

javac -classpath .;\path\to\gridworld.jar
    *.java
Replace \path\to\ with the path such as c:\GridWorld\

On Linux/Unix/Mac OS X, use a colon as a separator instead:

javac -classpath .: /path/to/ gridworld.jar *.java
Replace /path/to with the path such as  ~/GridWorld