Overview of the assignment problem
Motivation: Computer Science, Computer Engineering, and Software Engineering fields have contributed to advances in other sciences and engineering disciplines. Computer programs are used to model and forecast the weather, control robots for surveillance applications, predict drug interactions, predict cancer growth, predict the spreading of a certain disease (e.g., virus) around the world, etc.
In this assignment, we ask you to write a simplified simulator to compute the strength of a disease after it has been planted in a virtual world and the accumulated disease strength of all the diseases in the world.
For simplicity, we assume that the disease cannot move around, but grows stronger after each time unit (e.g., 1 day) when it is in the region of the world with the temperature suitable for its growth. Initially, it has the strength of 1. After each time unit has passed, its strength is multiplied by its growth rate for that temperature range (e.g., 2 for the temperature between say 15 and 25 temperature units) if the disease is located in the region of the world with the average temperature within that range. The simulated world is divided into four equal non-overlapping regions (quadrants) and each quadrant has the same average temperature. At each time unit, the program reports the accumulated strength of all the diseases in the simulated world.
You are asked to implement 5 classes. Fig. 1 shows how these classes are related. See image.
Fig. 1. Simplified class diagram: classes are shown in rectangles. Interfaces are marked with the term “<>.” The solid arrows with white arrow heads indicate a single inheritance hierarchy (or the superclass-subclass relationship). The solid black arrows indicate interactions between classes. The Simulator class calls the methods of the MyWorld class and the Disease class. The MyWorld class and the Disease classes call the methods of each other.
The MyWorld class is a subclass of the World class and also implements the methods specified in the IWorld interface. In Java terminology, this is “MyWorld extends World implements IWorld.” The Disease class is a subclass of the Actor class and implements the methods specified in the IDisease interface. The Simulator class has a public static main method that first instantiates one MyWorld object. The MyWorld object reads a configuration file “simulation.config.” It instantiates Disease objects and adds the objects to different locations in the MyWorld object. It sets up the temperature in each quadrant of its world. The Simulator then goes into a loop, calling the act() method of the MyWorld object to report the accumulated strength of the diseases around the world at that time unit and calling the act() method of each Disease object to calculate the new disease strength after the time unit has passed. The Simulator also instantiates a World object and adds two Actor objects into this world. It then performs a similar simulation. You can see the difference in the simulation results between using MyWorld and World objects. Next, let’s get to the details of each class. All the classes are in the package edu.iastate.cs228.hw1.
Method Specification
public class World
- public World(int worldWidth, int worldHeight): Construct a new world. The world is represented by a 2 dimensional array of cells with the specified width and height. See Fig. 2. One cell can keep at most 5 Actor objects. There are several ways to keep Actor objects in the world such as using Java Collections Class. We ask you to implement a specific one described below. Implementation Requirement Use a 3D array of Actor objects. The maximum number of elements in the 1st dimension is equal to worldHeight; the maximum number of elements in the 2nd dimension is equal to worldWidth; and the maximum number of elements in the 3rd dimension is 5. After you instantiate the array of Actor objects, initialize each array element to null. private Actor[][][] grid;
- public void act(): This method does not do anything. Leave the body of the method blank.
- public void addObject(Actor object, int x, int y): Add the Actor object at the cell (x,y) of the world after the most recently added object for that cell if there are less than 5 objects in this cell. Implementation After the Actor object reference is stored in the grid, be sure to make the added object know that it is in this world and it is at this cell. Think about which methods of the Actor class to call. Comments: Checking the validity of each input of the method before using it is a good programming practice. You can debug easier, especially in recursive algorithms. Users of your method also know the cause of the error and can quickly check the calling method.
- public int getHeight(): Return the height of the world in number of cells.
- public int getWidth(): Return the width of the world in number of cells.
- public int numberOfObject(): Return the total number of objects in the world.
- public Object[] getObjects(): Return an array of Actor objects in an array of Java Object objects. Implementation For each row and each column, add a non-null object reference to the Object array. For example, in Fig. 2, the returned Object array has references of Objects 0, 4, 2, 1, and 3 in this order. Comments: Each class in Java is a subclass of the Object class. Observe that you use the implicit upcast where you assign an Actor object (sub-class) in an element of the Object array.
- public void setGrid(Actor[][][] aGrid) Assign aGrid to grid. Set the width of the grid to aGrid.getWidth(). Set the height of the grid to aGrid.getHeight(). Set the number of Actor objects to aGrid.numberOfObject().
Checkpoint:
- After you have implemented this class successfully, you should understand how to access each element of a 3 dimensional array better.
- Do you understand better now how upcast works?
public class Actor
- public Actor(): Construct a new Actor object. It sets the initial values for its member variables. It sets the unique ID for the object and initializes the reference to the World object to which this Actor object belongs to null. The ID of the first Actor object is 0. The ID gets incremented by one each time a new Actor object is created. Next, it sets the iteration counter to zero and initializes the location of the object to cell (0,0). Suggestions: You may use a static member variable to assign a unique ID to each object. A static member variable is shared/seen by all objects in the same class. private static int ID =0;
- public void act(): Print on screen in the format “Iteration : Actor .” The is replaced by the current iteration number. is replaced by the unique ID of the Actor object that performs the act() method. For instance, the actor with ID 1 shows the following result on the output screen after its act() method has been called twice. Iteration 0: Actor 1 Iteration 1: Actor 1
- public void setLocation(int x, int y): Set the location of this object at the cell(x,y).
- protected void addedToWorld(World world): The Actor object remembers that it belongs to the given World object.
- public World getWorld(): Return the reference to the World object to which this Actor belongs.
- public int getX(): Return the x-coordinate of the object’s current cell.
- public int getY(): Return the y-coordinate of the object’s current cell.
See the provided template code for cases to throw exceptions.
Checkpoint:
- By implementing this class, do you understand the difference between a static variable and a non-static variable better?
public interface IDisease
- public void setGrowthCondition(double lTemp, double hTemp, double gRate): Set the growth condition of a Disease object to gRate. The value of gRate gets multiplied to the current disease strength only when the disease is located in the world region with the average temperature in between the values of lTemp and hTemp.
- public double getStrength(): Return the disease strength of the object implements this interface.
- public void prepare(): Prepare the world. Open a text file named “simulation.config” in the current path (directly under the project directory). Parse the configuration file for the number of Disease objects, the cell locations of these objects, the growth rates, and the temperature ranges associated with individual growth rates. Read Section 1.3 on the content of the configuration file before reading the rest. Suggestions: You may use FileReader together with Scanner class or BufferReadered class to read the content of the file. We provide some information to help you below. FileReader inputFile = new FileReader(".\simulation.config"); Scanner sc = new Scanner(inputFile); … line = sc.nextLine(); // read the next line into a String object … sArr = line.split("="); // split the string at “=”; sArr is an array of String … sArr[0].equalsIgnoreCase("NumDiseases"); // check whether the string on the left hand side is NumDiseases Once you know the keyword, call a corresponding method to process each right hand side of each line according to the value of the keyword. If the file simulation.config is not found or there is an error such as there is not enough information in each line to run the simulation, print the last message on screen “Terminating the program.” and terminates the program. This method should be the only method that calls System.exit(-1) to terminate the program gracefully.
- public void setTemp(int quadID, double temp): Set the temperature of the region of the world to the value of temp. The quadID indicates the region. The valid value is between [0, 3]. Any value of double is accepted for temp.
- public double getTemp(int quadID): Return the temperature of the world region with the ID of quadID. The valid value is between zero and three inclusive.
- public Disease[] initDiseases(String numDisStr): Create Disease objects; the number of the objects equals to the value passed in numDisStr. Return an array of object references to the created Disease objects. An example of a valid numDisStr is below. Ex: “2” If numDisStr is null or it cannot be converted to a positive integer, print a message on screen “Check the NumDiseases line in simulation.config.” and return null. No exceptions are thrown.
- public int initTemps(String tempStr): Set the temperature for each quadrant of the MyWord according to the value of the tempStr. An example of tempStr is below. The region temperatures for regions 0, 1, 2, and 3 are 12, 20, 50, and 100, respectively. Ex: “12;20;50;100” If tempStr is empty or not in the correct format or does not have all the temperatures of all the regions, print on screen “Check the Temperature line in simulation.config.” and return -1. Return 0 for a successful initialization of the quadrant temperatures. No exceptions are thrown.
- public int initLocations(String locationsStr, Disease[] diseaseArr): Add each Disease object into the MyWorld object implementing this method according to the information in locationStr. An example of a locationStr is “200,200;400,480”. This means that the first Disease is planted at cell (200,200) and the second Disease is at cell (400, 480). If the locationStr is empty or not in the correct format or does not have all the cell coordinates of all the Disease objects, print on screen “Check the Locations line in simulation.config” and return -1. Return 0 for a successful initialization of the Disease locations. No exceptions are thrown.
- public int initGrowthConditions(String growthStr, Disease[] diseaseArr): Set the lower bound and upper bound temperature and the growth rate for each disease according to the input growthStr. An example of a valid string for 2 Disease objects is below. Ex: “10.0,15.0,2.0;10.0,13.0,3.0” If growthStr is empty or not in the correct format or does not have all the growth for all the Disease objects in the Disease array, print on screen “Check the DiseasesGrowth line in simulation.config.” and return -1. Return 0 for a successful initialization of the Disease growth conditions. No exceptions are thrown.
- public Object[] getObjects(): Return the array of objects in the class implementing this interface.
- public double getSumStrength(): Return the total disease strength of all the diseases in the class implementing this interface.
Checkpoint
- Observe that initDiseases, initGrowthConditions, initTemp, and prepare methods do not throw any exception, but identify the incorrect line in the input file. We try to help the user of the program to understand better on what they do wrong. In practice, we should give more details on the cause of the problem for each incorrect input line, but for ease of grading, we keep it simple.
public class MyWorld extends World implements IWorld
This class has its default instructor, inherited methods from the World class, and the methods specified in the IWorld interface. You may introduce other private methods, but there must be no other public methods.
- public MyWorld(): Call the constructor of the World class with the width and height of 720 and 640 cells, respectively. Initialize an array to keep the average temperature of each world region (quadrant). Call the prepare() method.
- public void act(): This method overrides the act() method in the World class. This method prints "Iteration < ITRID >: World disease strength is < WorldDisease >” where < ITRID > is replaced by the current iteration number and < WorldDisease > is replaced by the returned value of getSumStrength() in 2 decimal places. An example is below. Iteration 0: World disease strength is 2.00 Iteration 1: World disease strength is 3.00
public class Disease extends Actor implements IDisease
This class has its default constructor, inherited methods from the Actor class, and the methods specified in IDisease.
- public Disease(): Call its superclass’s default constructor. Initialize the lower bound and the upper bound temperatures for the growth rate to 0. Set the growth rate to 0. Set the disease strength to 1.
- public act(): This method overrides the act() method in the Actor class. Check whether the object is in the region where the region temperature is within the lower bound and the upper bound temperatures for the object’s growth rate. If it is the case, multiply its strength with the growth rate.
See the provided template for this class.
public class Simulator
- public static main(String[] args): This is the main method that sets up a virtual world and simulates the growth of the diseases in the world. If the number of iterations is given in the command line argument, run the simulation for that many number of iterations. Otherwise, use the default number of iterations of 5. Some pseudo code is given below. int numItr = 5; if (args.length > 0) { numItr = Integer.parseInt(args[0]); } Print on screen “Simulation of MyWorld” new MyWorld object. for (int i=0; i< numItr; i++) call the act() method of the MyWorld object. call the getObjects() method to get all objects in the MyWorld object for each object in the Object array, call the act() method of that object. Print on screen “Simulation of World.” new World(100,100,5) Add 2 Actor objects into the world at the location (10,10), (90,90). for (int i=0; i< numItr; i++) call the act() method of the World object. call the getObjects() method to get all objects in the World object for each of the object in the Object array, call the act() method of that object. Suggestions: You will need to use explicit casting to be able to call the act() method. Observe whether the results of the second simulation on the World and Actor objects are different from those using the MyWorld object and the Disease objects.
Notes: In Eclipse Run Configurations, you can pass command line arguments to the program in the Argument tab. In this assignment, you can pass the number of iterations to run the simulation.
Fig. 2 shows an example of a MyWorld object with four regions/quadrants. There are three Disease objects (with IDs of 0, 2, and 4) in region 0 (Quadrant ID=0). There is one Disease object with ID 1 in region 2 and one with ID 3 in region 3. See image.
Fig. 2. Diagram illustrates a MyWorld object of 4x4 cells. The Disease objects with their unique ID are represented using circles. Each region/quadrant of the world has a unique quadrant ID.
Content of simulation.config
NumDiseases=2
Locations=200,200;400,480
DiseasesGrowth=10.0,15.0,2.0;10.0,13.0,3.0
Temperature=12;20;50;100
Fig. 3. Example of simulation.config.
The simulation.config is a text file that keeps important information for our simulation. In practice, it is common to find programs that read input from files, process them, and generate output. We take this opportunity to practice this.
For each line in this file, the left hand side of “=” is a keyword that is case insensitive. The right side of the “=” lists one or more value depending on the keyword on the left hand side.
For example, in Fig. 3, the first line indicates that the number of Disease objects is 2. The second line indicates that the location of the first Disease object is at the cell (200,200) and the second object is at the cell (400,480). The Simulator instantiates a MyWorld object and adds two Disease objects at the specified locations.
The third line indicates the lower bound, the upper bound, and the growth rate for each Disease object separated by a semicolon. For instance, in Fig. 3, the first Disease object grows at the rate of 2.0 only when it is in the region with the temperature between 10.0 and 15.0. The second Disease object grows at the rate of 3.0 when it is located in the region with the temperature between 10.0 and 13.0. Any value of type double is accepted for the temperature. There is no restriction on the number of decimal places for the temperature values.
The fourth line indicates the temperatures of the world regions starting from region 0 to region 3. In this example, region 0 has the temperature of 12; region 1 has the temperature of 20; region 2 has the temperature of 50. The last region has the temperature of 100.
Given the above configuration and the fixed world size at 720 in width and 640 in height, the simulation result on screen is shown in Fig. 4.
Simulation of MyWorld
Iteration 0: World disease strength is 2.00
Iteration 1: World disease strength is 3.00
Iteration 2: World disease strength is 5.00
Iteration 3: World disease strength is 9.00
Iteration 4: World disease strength is 17.00
Simulation of World
Iteration 0: Actor 2
Iteration 0: Actor 3
Iteration 1: Actor 2
Iteration 1: Actor 3
Iteration 2: Actor 2
Iteration 2: Actor 3
Iteration 3: Actor 2
Iteration 3: Actor 3
Iteration 4: Actor 2
Iteration 4: Actor 3
Fig. 4: The output of the simulation given the content of the configuration file shown in Fig. 3 and the default number of iterations of 5.
There are two Disease objects added to a MyWorld object. The cell coordinates of the two Disease objects indicate that they are in regions 0 and 3, respectively. The region temperatures for region 0 and region 3 are 12 and 100, respectively. Only the first Disease object in region 0 grows at the rate of 2.0 per iteration after it prints out its strength. This is because the region temperature of 12 is between 10 and 15. The initial strength for each Disease object is 1. Therefore, in Iteration 0, we see 2 as the sum of the disease strength of 1 for each object. The Disease 0 in region 0 multiplies its strength with the growth rate. In Iteration 1, the world disease strength is 2+1. In Iteration 2, the world disease strength is 4+1=5. In Iteration 3, the disease strength is 8+1=9. In the last iteration, the disease strength is 16+1=17. The simulation of the MyWorld object is done. The simulation of the World object started. We added 2 Actor objects to the World object. Because the object reference of each element of the returned Object[] refers to the Actor object, it uses the act() method of the Actor object. The result is the Actor object ID in each iteration.
We provide template code for World.java, Actor.java, Disease.java, IDisease.java, and IWorld.java to help you get started. Fill the missing pieces of these files and add additional comments if needed. We ask you to write MyWorld.java and Simulator.Java from scratch. Be sure to provide Javadoc comments (@author, @param, @throws, and @return) in these classes.
Documentation
All public methods must have Javadoc comments following the standard Javadoc format, including descriptions of each parameter (@param) and exceptions (@throws). Any private methods should have either Javadoc or normal style comments explaining what the methods do. Your name must appear after the @author in each class.