The purpose of this lab was to learn file writing while implementing BroccoliSim on the planar landscape. Since this almost completes the series of simulations we are implementing, I decided to make a GUI that allows the user to select any of the simulations: BroccoliSim, SocialSim, QueueSim, BorderSecuritySim, or PongSim.
In implementing BroccoliSim, we wanted to see how different conditions would affect the evolution of BroccoliFiends' traits of metabolism and vision. Every fiend is constructed with traits that are distributed about a given average. When it reproduces, its child is given traits close to its traits. Vision determines how close Broccoli must be for the fiend to see it, and metabolism determines how quickly the fiend uses energy. To choose where to move to, fiends use Landscape.getNeighbors, passing in their vision, to get a list of possible destinations. It looks at all of the fields except for the one it is currently on and assesses their desirablility. For a fiend, desirablility is determined by how much energy it will have after moving to and harvesting from the field. Numerically, desirability is
Math.min(maxHarvest, b.getQuantity()) - distance*metabolism+Math.max(b.getQuantity()-maxHarvest, 0)
where the first term is how much energy the fiend will gain, the next term is how much energy it takes to get there, and the third term considers how much Broccoli will be left afterward. If there is no field around that will give a positive desirability, then the fiend moves randomly, hoping that a field will grow soon. Fiends reproduce if their energy level is greater than ten times their metabolism. Also, their color reflects how much energy they have -- bright blue means energetic, bright red is starving.
All Broccoli grows at the same rate, a given percentage by which it is increased every iteration if it was not harvested the previous turn. Every field has a maximum size above which it will not grow. If fiends reduce its quantity to 0, a Broccoli will set its quantity to .1 the next turn it can grow.
BroccoliSim initializes a simulation using many parameters supplied by the user. The rate by which broccoli grows and fiends harvest are static, so it sets these values first. Then it adds broccoli, with varying maximum sizes about a given mean multiplied by a random Gaussian and a constant (I used 4 to make a wide range of Broccoli patches). Afterward it adds BroccoliFiends, generating metabolisms and visions the same way it generated growth rate (with constants of .5 and 1.5, respectively).
In addition to adding agents and resources, the initialize method instantiates a FileWriter that is a class variable. In order to record data every iteration, BroccoliSim overrides iterate(). First it calls super.iterate() to make all the agents/resources update, but then it iterates through them again, counting the number of entities and calculating the average metabolism and vision of the fiends. Then it saves this data through the FileWriter, delimiting with commas.
Since a FileWriter must be closed to save the data, a finalize method was created that is called at the end of the main method, after the loop that calls iterate. Simulation implements an empty finalize method, but BroccoliSim overrides it to close the FileWriter.
Since I wrote the data with delimiting commas, it was easy to use Microsoft Excel to create graphs of how the populations rose and fell, and how the traits changed over time. You can tell from these graphs, though they are small, that the Fiends evolve until they reach a good vision and metabolic rate. The ideal values are different for different amounts of Broccoli. These graphs were generated from 100X100 simulations with a growth rate of .5, harvest rate of 2 and , initially, 200 Fiends with an average vision of 5 and metabolism of 1. The three graphs show the results for 600, 800, and 1000 fields. The top is population over time, the middle shows the average metabolic rate, and the bottom graph is the vision. They show trends over 10,000 iterations
Making the simulation an interactive GUI required some fundamental changes to the way a Simulation is created. Most notably, I moved the main method into LandscapeDisplay (though it doesn't matter where it is) and start by creating a display instead of a Simulation -- this means that the LandscapeDisplay creates a Simulation and Landscape instead of a Simulation creating a Landscape and LandscapeDisplay. This change enables my GUI to allow the user to select different Simulations without restarting the program.
The next major change I made was the method by which Simulations deal with parameters. In previous assignments, command line arguments or hard-coded data was passed into the constructor, but now I want more flexibility to be able to know what parameters to allow the user to change. PongSim doesn't have any parameters other than height and width, BroccoliSim has many integer and double parameters, and QueueSim even has a String parameter that should be one of three values. I decided that the simplest way to deal with parameters was to create a class Parameter that accepts as constructer arguments (a) a string that labels the value and (b) either a default value(as a string) or an array of Strings. Parameters create a JLabel out of the first string and either a JTextField or a JList out of the second value. This is explicitly a JComponent, so when my GUI adds the Parameters to its paramPanel, it simply visits each Parameter and adds both the Label and JComponent without knowing what type it is. I gave each of my Simulation subclasses a static Parameter and static accessor method that the LandscapeDisplay calls whenever a new simulation type is selected. When a user changes the vaules in the fields, the objects in the Parameter array are of course changed as well. This means that if the simulation accesses the values it will get the updated versions without any specific parameter passing -- so much easier than figuring out how to extract all the data from the textFields to pass into a constructor! One change I did have to make, however, was to eliminate almost all of the instance variables, and initialize now gets the values by calling params.getValue() and parsing the String into an int or double. These changes were all in the child Simulation classes (plus the creation of a new constructor for Simulation itself that just receives a Landscape and LandscapeDisplay; these are actually all that any of the new simulation constructors want), so they were fairly simple to make.
Here are some snapshots of various simulations of my new GUI.
Notice that every simulation has different parameters, and produce graphs for different values -- and PongSim works without having a graph. The graphs work in much the same way that they did back in Lab 4 (BroccoliSim round 1), but I altered it so that it would receive any number of data sets; now DisplayPopulation's update accepts ArrayList<ArrayList<Integer>> so that no other method has to step through all the sets. Simulation now has a getData() method that returns null,but can be overriden to return the list of lists. Unfortuately, there is no key, but you can probably tell that the darker line in the Broccolengeti is the number of fiends and the lighter shows the change in amount of Broccoli; the darker line in Social is the number of nervous agents and the lighter is the number of content agents; and the various lines in CheckOut show the average wait times for all of the checkouts (there are 12 of them, though some are hidden).
It was exciting to pull most of my simulations together into one versitile GUI. It is far from perfect, as my PongSim is virtually impossible to play for long since the balls always begin to richochet around at tremendous speeds, but they all respond to changes in their parameters as they should. The file writing was a slightly different method from what I have used before, so that was interesting to see as well.