This week, I modified the simulation to make the entities survive based on nourishment. I created a new class called BroccoliFiend that extends Entity, and also implements a new interface, Agent, that makes its classes keep track of a storage of food. The locations on the grid are now represented by Tiles that always hold a Stuff object (in this simulation it is broccoli) and can also support an Agent. Stuff obects have an amount that can be incremented by growing and decremented by harvesting. Since we are interested in the population count of the simulation, I created a PopulationDisplay modeled after the LandscapeDisplay that draws a population versus time graph.
An iterate() method was given that made every square of broccoli not under an agent grow by 1, every agent harvest up to 10 cans of broccoli, and every agent move that borders a square with more broccoli (with a .01 chance otherwise) and can eat (if it cannot eat 8 cans, it dies). The first aspect of iterate() that I implemented differently from the given algorithm is that I made Agents need 4 cans every turn, regardless of whether they move or not, and then 4 more if they wanted to move. I also implemented a method Point2D getPrimeLoc(row, col) to get the neighboring location with the most broccoli. I decided to make Agents not look at occupied locations since that suggests coveting, and these can be nice Entities. Besides, if an Agent is sitting on a barren field, it should not stay put just because the best location is already taken like the original algorithm had it do. In order to reduce the tendency of Agents to drift in a certain direction, I gave a random chance (.3) that an Agent would choose a 'later' location with the same amount of food. I found the value by trying a few different values and determining that .3 does not result in a strong drift.
Once I had adjusted iterate(), I created my PopulationDisplay class. It extends JFrame, and has a private class GraphPanel that extends JPanel, just like LandscapeDisplay. The update method in PopulationDisplay takes an ArrayList as an argument that gives the y-values of the data to be graphed, in this case, the census. The PopPanel's paintComponent places a small black circle to plot each point, using different scales for the time and population axes which are passed in as arguments. I have my PopulationDisplay update in BroccoliSim's update method, which also calls my LandscapeDisplay's update method (or updateAndPrint() if I want it to save pictures of the iterations)
The graph for the initial rules(shown below) showed the need to make Agents reproduce.
I decided to view each agent as a small group of organisms, or maybe asexual beings, by making them reproduce based solely upon the availability of food. If an agent manages to accumulate a certain amount of food, it creates a new Agent in the prime adjacent location and gives it 8 cans of broccoli to sustain its life. I first had Agents reproduce when they had 16 cans, but this lead to rapid overpopulation and a subsequent extinction of the species. I changed it to require 21 cans, and this would sometimes create a simulation that would survive with around 400 (+ or - 200) Agents forever, and other times create a simulation that would die out within the first couple hundred iterations. There would often be cycling bands that drifted to one corner, and if none happened to turn back (where most of the food had just been eaten), then they would all die of starvation in the corner. Successful simulations did not develop such strong bands, or had two bands that drifted in different directions. This drifting is not due to the selection of a certain direction over another when there are equally good spots, but rather a random trend that began and self-perpetuated by the availability of food. A successful simulation is shown below.
In the first few iterations the grid is completely filled with Fiends, but this is quickly corrected, and almost leads to the species' demise. The graph appears to reach 0, but in reality it only very nearly reaches it.
The bands in this simulation threaten to cause extinction, but as long as some Agents resist the desire to drift, more are continuously being produced.
The next change I made was to introduce another agent -- the Predator. Predators eat BroccoliFiends, gaining a certain number of meat units per Fiend captured. It must eat a certain number of units per turn, and does not get to eat less if it doesn't move since it will move any time it has space. Like the BroccoliFiend, Predators age and die (initially, at 12), and they reproduce if they have extra food. At first, I had Predators gain 4 units per Fiend and they must eat 2 per turn. If it has more than 5, it will produce a new Predator, assuming there is an empty location around it. This is not advantageous to the predators, but it fixes the BroccoliFiends' problem as shown below. The triangles are the predators. Notice that the beginning stays green longer.
This time, instead of having a major BroccoliFiend population boom, the beginning is characterized by a small increase in agents followed by their plumment. Since the Fiends don't have time to eat all the food, however, they make a quick comback. The predators are now in the Fiends' old position, though, since they must stay alive until the Fiends can resurge as well as fight food drift.
By the way, the graph now shows the 'populations' of the broccoli/200, BroccoliFiends, and predators. As you can probably tell, the broccoli is the line that started on top, the Fiends is the one that ends on top, and the predators is the one that stays low. It has a black background now because for some unknown reason one time it started up that way and I decided I liked it, so I set the background color to black. Also, there is now a "Refresh" button. Remember that my LandscapeDisplay GUI has a reset button that starts a new simulation. When I do this, another set of lines are added to the graph in a different color (I achieved this by including a conditional statement in DisplayPopulation.paintComponent() that tests if the size of the ArrayList is 1, and if so it uses a new color). I also had to the way I called the PopulationDisplay's update method to enable the drawing of three populations. PredatorSim (which is very similar to BroccoliSim) keeps track of three census arrays and sends them individually to LandscapeDisplay to update the graph. Anyway, the refresh button clears all previous trials so that only the current trial is shown. To keep all the previous trials on the graph, I had to set my LandscapeDisplay to ignore repaint commands from the operating system. Unfortunately, this means that if the graph is not updating due to an active simulation and you drag a window over the graph it, it messes up the graph. Having the simulation run again restores it, however, so I decided that this is satisfactory.
Population Control Delimma
Another note about this simulation: although without predators the BroccoliFiends would become extinct a little over half the time, that only happened once with this simulation. A near wipeout seems inevitable to achieve a balance, and it is far better for the wipeout to occur when there is still a plentiful food source. Unfortunately, The Predators do not have a plentiful food source at the wipeout, so they do not always resurge, and even when they do it is a shaky balance. The BroccoliFiends' tendency to radiate to the borders kills the Predators since ALL the predators follow, whereas only most of the Fiends do. I did have a few simulations where the Predators got lucky and survived longer, but it is far too small of a chance to be satisfactory. The best solution would be do create a food web instead of my chain, and implement some omnivores, but instead I decided to take an easier route and try just changing the Predators' food needs. This didn't work, though. After becoming discouraged, I decided to try a little diversity. I didn't want to make a new growing food source since I've gotten the idea that more resources might be part of next week's simulation. I really didn't want to make a super predator because I'm afraid that such an approach would simply result in a really long food chain. A predator that eats both Predators and Fiends might work, but I decided instead to try to fight the drift.
A Serendipitous Discovery with BroccoliManiacs, a Temporary Entity
I had the idea that if I created a BroccoliManiac that acted exactly like a BroccoliFiend I could make group-forming occur, as in Lab 2. This seemed like a reasonable way to combat the Drift. To implement this idea, I made a new class BroccoliManiac extend BroccoliFiend and not override any methods but draw(). Then I modified getPrimeLoc() in BroccoliFiend. I decided that a location would be better than another if: (a)the other has under 10 cans and the new location has more than the old location; or (b) the new location has at least 10 cans and is more desirable than the old. Desirablility I wanted to define as bad if a location bordered a Broccoli eater of the other type and good if it does not. I made a mistake the first time, though. My method that looked at every neighbor called a location bad if a bordering entity other satisfied this expression: " (other instanceof BroccoliManiac) != (this instanceof BroccoliManiac) ". I used this since a BroccoliManic is an instance of BroccoliFiend, and that means that I can't easily check if something is only a BroccoliFiend. The expression would work if it weren't for the Predators. When I ran the simulation like this, the BroccoliFiends all died out, the BroccoliManiacs lived, and Predators actually lived longer than usual. When I realized that my BroccoliManiacs were running away from Predators and that this seemed to help the Predators, I reverted to a simulation without Maniacs, but with a getPrimeLoc() for BroccoliFiends that avoids Predators. As I expected from my serendipitous discovery, Predators did live longer. They no longer experience immediate extinction on a regular basis, and when they live they hang on for almost the whole graph. Nonetheless, they do still die. But isn't it amazing that having prey run from predators helps the predators?
Below is a sample simulation. I changed my draw methods so that BroccoliFiends are always blue circles and Predators are always red triangles, regardless of age, to help you see the difference.
Growing Rate, the Overlooked Factor
Watching this simulation, I realized that the predators die at the same time the green basically disappears. I had noticed this before, but always assumed that fewer predators led to more Fiends which led to less broccoli. I decided, however, to try making the broccoli more abundant by making Stuff grow at twice the rate (quantity increases by two instead of 1 each turn). This did the trick! I think it might help with the Drift, but maybe it just keeps the Fiend population more balanced so that it never becomes so sparse that Predators starve. My simulation would probably crash if I implemented a drought, but at least I've succeded in balancing three trophic levels! At this point, after the euphoria of success had diminished, I wondered whether the Fiends' avoidance tactics really did matter, or if the only important element was a faster growing broccoli. I tested the simulation using the old rules for getPrimeLoc(). I had changed the scale of my graph to view the trend of success over a longer period of time, so it was not easy to compare the latest simulation to earlier ones, but while it seemed to do better than previous non-avoidance simulation, it was not nearly as good as an avoidance with rapid growth simulation.
A succesful simulation is shown below. The graph shows 4 times as many simulations. I altered the PopulationDisplay.paintComponent() again to make the data different shades. I did this by creating a private int variable colorIntensity that increments each time the method is called. The color for the line is determined by colorIntensity%3 -- sometimes the base color is darkened four times, and sometimes it is darkened twice. To make this method more versitile, I should have some way to change a variable numTypes that I use instead of 3 to test colorIntensity. I could make the line color darken colorIntensity%numTypes times using a for loop.
The light color is predator population, the darkest is fiend population, and the middle color is broccoli/200.
A New GUI
Next I made a pretty awesome GUI. Of course, it was monotonous to make all the labels, textFields, and buttons, but I think it just might have been worth it. I made static variables in BroccoliFiend, Predator, and Stuff with static access and setter methods. Now the user can manipulate the eating rates, though I must admit that I've done just about enough of that! The graph shows 500 iterations, and you can have many trials overlapping as long as you don't change the settings in the NORTH panel. I sort of feel like it would be neat to have the computer create random values for either the prey or the predator and make a game where the user has to try to create life by adjusting the other values, but like I said, I'm tired of that game.
One interesting aspect of making the buttons work was deciding where their actionListeners should be set to. Pressing them needed to change variables in other classes using the data from the textFields. Since the program needs access to the textFields, I had no choice but to set the actionListeners to the display class. I added a bunch of getters and setters to the different agents' classes for the variables that the user could change (first I obviously had to make them variables in the classes, and alter the harvest method to accept an int maxHarvest). The getters were used to initialize the textFields, and the setters are used every time the setAttribute buttons are clicked. In processing the ActionEvent from the NORTH set button, I called AbstractSim's actionPerformed method, sending an Arraylist of the textField data as the "source." I have a feeling that this is not how it was meant to be used, and that maybe I should have just created a new method in AbstractSim, but it does work (just as efficiently) and I did get to explore the actionPerformed method more -- I understand actionEvents a little bit more, though I have absolutely no idea as to what the integer is supposed to represent (I know it is an "ID," but how is it supposed to be used?).
To put the DisplayPopulation graph on the same window, I changed DisplayPopulation from a JFrame to a JPanel. This didn't require many changes, just eliminating the super statement (so it would use the default panel constructor) and eliminating the pack() call since panels don't pack. Unfortunately, something about making it a panel in LandscapeDisplay means that previous runs can be accidently erased by moving another window on top of the graph. Setting the LandscapeDisplay to ignore repaints sort of fixes it, but causes MAJOR issues, like hiding the textFields and buttons.
At some point in the project, I made an abstract superclass to BroccoliSim called AbstractSim. Since the simulation class must deal with button clicks, I wanted to separate some of the work. It also houses the update method (which accepts a single ArrayList to graph). This way, I should be able to use a simulation class that I have already created for next week's project -- and that's what Java's all about, isn't it, flexibility? That's what my high school textbook said, anyway. Additionally, AbstractSim has an abstract method iterate() that all subclasses must implement. It also has getters and setters for some private variables that subclasses might need access to: a boolean isIterating, double fill, and a Landscape. Subclasses don't need their own Landscape or displays.
I'm pretty happy with what I created for this project. I reviewed more aspects of GUIs and used an interface, which I haven't done very much of. I'm not sure, though, why we couldn't have just added the new methods to Entity, maybe even making Entity an abstract class if we couldn't implement the methods in Entity. I guess it could have been useful if I had made more types of Entities.