GridWorld User Guide
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:
- Understanding files and directories
- Unzipping files
- Compiling Java programs
- Using JAR files
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:
- To tell your compiler about the GridWorld libraries
- To launch the GridWorld graphical user interface (GUI)
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:
- Objects and classes
- Constructors
- Method calls
- Parameters and return values.
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:
- Understanding directories
- Java syntax for defining classes and methods
- Compiling a Java class
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:
- Make a new directory
- In that directory, define a class that extends
Critter
- Implement the act method
- Optionally, supply a GIF image
- 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:
- Your compiler or development environment, to compile your
program
- 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:
- Instance fields
- The if statement
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:
- The ArrayList class
- Java syntax for inheritance
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.)
- You may override eat and move, but don't override
act.
- In eat, don't be greedy. You may remove at most one edible
object from the grid.
- In eat, don't move. Stay where you are, look around you,
and eat if you find something edible.
- In move, don't eat. Just move to another location.
- In eat and move, look at your neighboring
locations only. No peeking elsewhere . . .
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
- the number of objects in the grid before the call
- the position of this actor before the call
After the
act
method returns (which, as you recall, invokes
eat
and
move
), the following checks are carried out:
- If the number of objects in the grid has decreased by more than one,
this actor was too greedy.
- If the number of objects in the grid has decreased by one, and the
position has changed, this actor has both eaten and moved--a crime.
- If the actor has moved beyond the neighbors of the old location, it
went too far.
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:
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.
- Start a project as usual
- Select the project in the "Package explorer" window
- Select Project -> Properties from the menu
- Click on Java Build Path and select the Libraries
tab

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

- Click the Add button
- Navigate to the directory containing gridworld.jar.
Select gridworld.jar.
- 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
.