Assignment 5: Zombies: A METHOD to their Madness

Assignment Setup

To create your repository go here. Then follow the same accept/import process described in the setup instructions.

Zombies

There are a few reasons for this choice of subject matter:

  • These assignments show a real-world application of computing. The structure we will create is used in many simulations and games, and computer scientists often do similar work to visualize data and understand natural phenomena.

    • Games and simulations often have a loop that simulates time steps. The typical flow of this loop is:

    1. Check if the game/simulation should continue

    2. Update all the items being simulated for the current time step

    3. Show or record any progress

  • Simulating biological systems can be fascinating but complex. Using zombies allows us to simplify the rules of the system, which means shorter assignments more focused on the CS concepts we want you to learn.

Questions to ask if you get stuck

Like all problems, this one can be tricky. Here are some common questions that we get from students regarding this assignment. Use these questions to gauge your own understanding of what we are asking you to do. Please ask these questions to a TA or an instructor if you are feeling stuck on a certain part of the assignment.

  • In programming, what are “magic numbers” and why shouldn’t they be used?

  • In Java, what does declaring and initializing constants using the final keyword do?

  • What is double buffering?

  • What are unit tests? How are they used?

  • How are methods declared in Java?

  • What are the parts of a method declaration in Java?

  • What does it mean for a method to “return”?

  • What does it mean to “call” a method? How is a method call performed?

  • How can I debug programs that use many methods?

Information Needed

In order to simulate how a zombie infection can spread, we will simulate:

  • Zombies, which have a 2D location. The x and y values can range from 0.0 to 1.0.

  • Non-Zombies, which also have a 2D location with values ranging from 0.0 to 1.0.

In other words, for every entity (zombie or non-zombie), we will want to keep track of the entity’s current coordinates and whether the entity is a zombie. We can use a boolean for the entity type, with true to indicate a zombie and false to indicate a non-zombie.

Data Management

There are several ways to manage information like this. For this assignment, we’ll use an approach sometimes called parallel arrays. The basic idea is that we will have two arrays to keep track of N entities:

  1. An array of length N that contains whether or not each entity is a zombie. Call this the areZombies array.

  2. A 2D array with N rows (the number of columns is for you to decide) that contains the x and y coordinates of all the entities. Call this the positions array.

Each thing being simulated will be associated with an index. For example, areZombies[0], positions[0] represent the type and coordinates of a single entity in our simulation. In a sense you can think about areZombies, positions as being columns in a table. Each individual row of the table represents a specific thing being simulated. The two arrays are considered “in parallel” since the i-th items in each array represent different aspects of a single composite thing.

Data Encoding

The initial locations of zombies and non-zombies will be provided in a file. The file will have a very specific format:

  • The first line of the file will indicate how many entities are in the file (N)

  • The next N lines will each contain information for a single entity:

    • Each line will start with a String. "Zombie" indicates a zombie and "Nonzombie" indicates a non-zombie.

    • Second will be a real number (double) indicating the initial x position of the entity.

    • Third will be another real number (double) indicating the initial y position of the entity.

We’ve included several example files. When you run the program, you will see a file dialog box that will allow you to pick a .sim file. This is the file that your code will read from (click to enlargen examples below).

File View
  • 1_nonzombie.sim:


1_nonzombie

  • 1_on_1.sim:


1_on_1

  • 1_zombie.sim:


1_zombie

  • 5_nonzombies.sim:


5_nonzombies

  • 5_zombies.sim:


5_zombies

  • all_alone.sim:


all_alone

  • bubbles.sim:


bubbles

  • contagion.sim:


contagion

  • cse131_vs_zombies.sim:


cse131_vs_zombies

  • in_the_house.sim:


in_the_house

  • surrounded.sim:


surrounded

New Techniques & Topics

APIs

This assignment will utilize two different APIs:

  • Scanner: This allows us to read data from a file. We’ll get the type and location of all entities from a file. This is slightly different from how we have used Scanner in the past, as we will not be prompting the user for input values.

  • StdDraw: This will allow us to display the location of the zombies and non-zombies as our simulation progresses.

Using Scanner to read from a file

  • When Scanner is connected to a file, nextDouble(), will get the next value in the selected file if it’s a double. If the next value is not a double, it will ask the user for a double instead.

  • Each time you call nextDouble(), Scanner will process that value, and the next call to nextDouble() will return the next double in the file, whether it’s on the same line or the next.

  • Make sure you use the method that asks for the data type that corresponds to the next value in the file. For example, if the next value in the file is a double, make sure to call nextDouble(), not next().

  • Also be careful with next() and nextDouble(). The first method reads in the next word, whereas the second reads in the entire next line. Both are useful, make sure you are choosing the correct one!

Methods!

Methods are a fundamental part of computing because:

  • They allow us to break complex problems into smaller, more manageable parts. It makes it possible for a single person to write a complex program by working on one small part at a time and ensuring that the small parts can be combined together.

  • They allow code to be re-used. In this case we will do some operations repeatedly and rather than copying/pasting code you can just write a method once (one copy of the code) and call it as-needed.

“Magic” Numbers and Constants

The term Magic Number is often used to represent a constant value whose significance isn’t clear from the value and its context. For example, we will be storing the entities’ y-coordinates in the second column of an array, so the number 1 indicates the column containing the y-coordinates. The number 1 would be considered a “magic number” because it’s an arbitrary choice and may not be clear to someone who reads your code.

In order to make our code more readable, we’ll use special variables for the indices rather than the “Magic Numbers”. The starter code provided in ZombieSimulator.java includes:

static final int X = 0;
static final int Y = 1;

These two lines declare variables that represent the column that will contain the x coordinate and the column that will contain the y coordinate. Every time a location in the 2D array is used, these variables should be used to make your code easier to read. For example, when someone reads:

double v = positions[i][1];

it isn’t very clear that the 1 the Y coordinate (it is a magic number). The following is easier to read and less prone to errors:

double v = positions[i][Y];   // More clearly conveys reading the Y coordinate.

Of course, using a better variable name makes it even more readable:

double yCoordinate = positions[i][Y];

The lab assignment also includes:

static final String ZOMBIE_TOKEN_VALUE = "Zombie";

You should prefer the use of ZOMBIE_TOKEN_VALUE over the String "Zombie". "Zombie" could be misspelled, for example, resulting in diffilcult to debug errors. If you misspell ZOMBIE_TOKEN_VALUE, however, Eclipse and the Java Compiler will alert you to the problem, which makes it easier to debug.

The constants below will be used in drawEntities(). You may change the values, but you should reference these identifiers in your code.

static final Color ZOMBIE_COLOR = new Color(146, 0, 0);
static final Color NONZOMBIE_COLOR = new Color(0, 0, 0);
static final Color TEXT_COLOR = new Color(73, 0, 146);
static final double ENTITY_RADIUS = 0.008;

Note: collision detection in touchingZombie() will also use ENTITY_RADIUS.

Finally, updateEntities() will use RANDOM_DELTA_HALF_RANGE:

static final double RANDOM_DELTA_HALF_RANGE = 0.006;

Double Buffering

Double Buffering is a technique used to make animations look smooth. The basic idea is to have two different “frames” (the two buffers). At any given time, one frame is being shown on the screen. New drawings are placed on the other frame, which isn’t being shown. When these two frames are switched, it looks like a lot of changes have taken place simultaneously. A sequence of these changes can show an animation in the same way a flip book works (Sample Video on Wikipedia). Much like a flip book, all visible items are drawn in each frame, but the positions of items that are moving change a tiny bit from one frame to another.

StdDraw supports double buffering by the following approach:

  1. Prior to drawing anything (for example, when the the program first starts in main) call StdDraw.enableDoubleBuffering()

  2. Whenever it is time to change frames:

StdDraw.clear();  // Clear the non-shown frame
// Draw *all* objects in their locations (which may have changed from the last frame)
StdDraw.show();  // Swap the non-shown frame with the one being shown on screen.

Unit Testing

Unit testing can help find problems and add confidence that certain aspects of your program are functioning correctly. As you complete each part of this assignment, you will test that part. If all the parts work in the intended way, there’s a greater chance that they will work when combined together.

Special Note: Unit tests help developers make sure the code works, but they usually only test a relatively small number of possible conditions. NEVER assume that code that passes unit tests “must work”. The test only ensures that it did what those tests expected. The tests themselves could be flawed and they don’t test everything!

If you fail any unit test cases, you should try to read through the test case and see what it’s testing. In this assignment, almost all tests cases are either looking for a particular value (via assertEquals() or assertNotEquals() ) or for a boolean condition (via assertTrue() or assertFalse()). Doubling clicking on a failing test will take you to the code for that test, where you can probably figure out what the test case is expecting and then try to identify why your code didn’t pass it. The comments in and above the test cases may also provide some guidance.

Example Run

Here’s an example run (note: there is no audio):

Submitting your work

Get your assignment graded by bringing it to lab on Wednesday/Thursday or going to office hours and signing up for a demo via wustl-cse.help.

Confirm that your score is recorded in Canvas. Mistakes can happen and you should always confirm credit is recorded before leaving class!

You have attempted of activities on this page