Lab 6: HugLife

Pre-lab

None this week

Introduction

In this lab, you'll create a package named creature that will implement two creatures (or more, if you'd like) that will inhabit a world simulated by the huglife package. Along the way we'll learn how to debug small pieces of a much larger system, even if those small pieces happen to live inside another package.

Start the lab by booting up the HugLife simulator. To do this, use the following commands (make sure you're in the lab6 directory):

$ javac -g huglife/*.java creatures/*.java
$ java huglife.HugLife samplesolo

This starts up a world called samplesolo. You should see a little red square wandering around randomly.

Your job for this assignment is to add two classes to the creatures directory: Plip.java and Clorus.java. Eventually these two types of creatures will also inhabit the world, and unlike this red guy, they actually do something interesting.

These classes will extend the huglife.Creature package, which provides a template that all creatures should follow.

How the Simulator Works

Creatures live on an NxN grid, with no wraparound. Each square may be empty, impassible, or contain exactly one creature. At each tic, exactly one creature takes a single action. Creatures choose actions in a round-robin fashion.

There is a global queue of all creatures in the world, waiting their turn to take an action. When a Creature is next in line to move, the world simulator tells that creature who its four neighbors are, and requests a choice of action from the creature (more specifically, the world simulator calls the Creature's chooseAction method, which takes as an argument a collection of all four neighbors).

Based on the identity of the four neighbors, the Creature chooses one of exactly five actions: MOVE, REPLICATE, ATTACK, STAY, or DIE. MOVE, REPLICATE, and ATTACK are directional actions, and STAY and DIE are stationary actions. For example, if the acting Creature sees a creature to its LEFT that it can eat, it might choose to ATTACK LEFT. If a creature takes a directional action, it also specifies either a direction or a location. One of your main tasks for this lab is to write the code that makes Creature decisions. Actions are returned as objects of type Action, which are fully described in Action.java.

The chosen Action is given to the simulator which enacts the changes to the world grid. You'll be responsible for writing the code that tracks the state of each Creature. For example, after the acting Creature eats another Creature, the acting Creature might become stronger, die, change colors, etc.

This will be accomplished by a callback to the creature, which (as described below) is required to provide move(), replicate(), attack(Creature c), and stay() methods that describe how the acting Creature's physical state will evolve after each of these respective actions. For example, if your creature chooses to replicate upwards by returning new Action(Action.ActionType.REPLICATE, Direction.TOP), then the game simulator is guaranteed to later call the .replicate() method of the creature that made this choice. There is no die() method since the Creature is simply removed from the world entirely.

Experimenting with the Sample Creature

Open up Occupant.java, Creature.java, and SampleCreature.java, which you'll find in the directory for huglife package.

Try making some changes to the sample creature, and see how your changes affect how things change when you run the HugLife simulator. As one of your experiments, you might have the SampleCreature react in some observable way when it sees a wall. You can do this by requesting a list of all neighbors of type "impassible" from the getNeighborsOfType method.

Important: After you've experimented to your hearts content, use the git checkout command to revert your project directory to its original state. If you don't know how to do this, see the documentation for git. If you get stuck on this, get a neighbor or a lab TA to help you. Maybe someone will even write something on the board (I've intentionally not given you the exact command here).

Developing the Plip Class

Basic Plip functionality

Now it's time to add a new type of creature to the world. Go into the creatures directory, and you'll see there is a class file named Plip there, waiting to be filled out.

Plips will be lazy (but motile) photosynthesizing creatures that mostly stand around and grow and replicate, but will flee if they happen to see their mortal enemy, the Clorus.

Let's start with just a few of the properties that we'll eventually need for our Plip class.

It would be theoretically possible to test our Plip class by sticking them on a HugLife world grid and watching what they do (with gjdb or print statements) as they run amok. However, this would be a terrible idea. Instead, it's better to perform testing on the Plip class directly.

Note on testing: It's not necessarily desirable to test everything! Use tests only when you think they might reveal something useful, i.e. there is some chance you'll get something wrong. Figuring out what to test is a bit of an art

To test the Plip class, which is part of the creatures package, we can create a test class TestPlip that is also part of the creatures package. You'll see that a skeleton containing a few simple tests is provided.

One way to run this test file is as follows:

$ javac huglife/*.java creatures/*.java
$ java creatures.TestPlip

Try it out and you'll see that our test fails. Now after all that reading you can finally do something! Modify the Plip class according to the specifications above until all tests pass. Make sure you don't forget to recompile in between calls to the test.

Once you're done, you're well on your way to having a fully functional Plip.

The Plip replicate method

Do not start this part until your Plip class passes all the provided tests. Once you've done so, we'll work on adding the correct replication property to our Plips, namely:

The replicate test doesn't do anything yet. Fill in the test for the replicate method. Make sure to test that the returned Plip is not the same Plip as the Plip whose replicate method was called. You can use the JUnit assertNotSame method for this purpose (do not confuse assertNotEquals with assertNotSame. see the JUnit documentation if the distinction is unclear)!

The Plip chooseAction method

All that's left is giving the Plip a brain. To do this, you'll be filling out the chooseAction method as follows.

These rules must be obeyed in this strict order! Example: If it has energy greater than 1, it will ALWAYS replicate before trying to run from a clorus.

Writing testChoose

Uncomment the @Test annotation tag for the testChoose method. This will allow the testChoose method to run when you invoke the $ java creatures.TestPlip command. The existing test checks the first rule, namely that if there are no empty spaces next to the plip, then it should stay.

Add tests for the choose method to your TestPlip class. Everything might look complicated, however, if you use SampleCreature as a guide, you should be able to figure out the syntax.

You might find it useful to look at the code for the Action class to see the various constructors for Actions.

Don't worry (yet) about testing the 50% rule if a Clorus is nearby. This isn't possible since you haven't created a Clorus class yet, and thus you won't be able to create a HashMap that involves Cloruses. Also this test is pretty tricky to write.

Later, once you write Clorus, you might find it interesting to come back and try to write a randomness test. One possibility is to might simply test that both choices are possible by making many calls and ensuring that each happens at least once. Performing a statistical test is probably a bit too much for lab today (though you're welcome to try).

Writing ChooseAction

After writing a set of tests that you feel happy about, edit the Plip class so that it makes the right choices. You'll want to look carefully at the SampleCreature class as a guide.

Running the Plip Class

Assuming your tests worked, you can now see how your Plips fare in the real HugLife world. Use the commands:

$ javac huglife/*.java creatures/*.java
$ java huglife.HugLife sampleplip

You should see your plips happily growing along. If something goes wrong, it's probably because your tests are not thorough enough. If this is the case, using the error messages, add new tests to TestPlip until something finally breaks. If you're still stuck, let a TA or a lab assistant know!

Introducing the Clorus

We'll now add another Creature and corresponding test to the creatures package. This time, we'll be implementing the Clorus, a fierce blue colored box that enjoys nothing more than snacking on hapless Plips.

This time, you'll create your TestClorus and Clorus classes from scratch (using what you've got so far as a guide).

The Clorus should obey the following rules exactly:

As before, write a TestClorus class. You probably don't need to test the move(), stay(), or color() methods, but you're welcome to. Instead, it's probably only necessary to test the Choose() action. Your tests for TestClorus should involve at least one of each type of action.

Once you've written tests, write the Clorus class itself, again from scratch.

After you've written and tested the class _thoroughly_, go into HugLife.java and uncomment the lines in readWorld()

Showtime

We did it.

Now it's time to watch Cloruses and Plips battling it out. Use the following command to kick off a Manichaean struggle that will end either in eternal oscillations or in lonely immortals wandering the wastes forever.

Did you remember to edit HugLife.java?

$ javac huglife/*.java creatures/*.java
$ java huglife.HugLife strugggz

If you did everything right, it should hopefully look cool. You might consider tweaking the HugLife simulator parameters, including the world size and the pause time between simulation steps. Be warned that world sizes above ~50x50 are probably going to run fairly slowly.

Building new things: Reading and Writing Worlds

You can write the world state in several ways:

  1. You can do a call to StdDraw.save(), which will save a picture of the world for you.
  2. You can call the the Grid class's writeWorld() method. The method prints out the the name of the creature and its x and y positions. This is handy if you want to later read in the current state of the world, though it's not as pretty as saving a picture.

You can read a world state by calling the readWorld() method in the HugLife class. This takes in a .world file. Look at some of the ones provided to see how you can write one yourself.

Submission

Don't forget to git add Clorus.java TestClorus.java before comitting!

There is no autograder for this lab. If your simulation looks mostly right, you've done enough work to receive full credit.

Enrichment

There's a lot one could do to improve the simulation. Possibilities include: