In this project, I used the Entity and Landscape classes from last week to emulate Conway's Game of Life. A DisplayLandscape class was provided that produces a JFrame that graphically displays the simulation, using colored squares to represent each Entity. After writing an Iterate() method in Life, an extension of Simulation that successfully acted like Life, I modified it to use an iterator and then made two more extensions of Simulation. One, AgingLife, has Entities die if they live more than 11 iterations and the other, SickLife, introduces infected cells that infect others, and make them die like an AgingLife Entity would.
Unlike last week's task, the implementation for Life is particular about the order in which positions are evaluated. Every position must make its decision about how it will change before any of those changes are made. My solution to this problem was to create an ArrayList<Entity> that holds the Entities that will be present at the end of the iteration -- both those that already exist and those that are being created. Entities that should die are not added to this list. This list is automatically sorted by position since I fill it using nested loops that traverse the Landscape in a logical fashion. Therefore, once the list is complete, my program traverses the Landscape again, in the same order. It compares each position to that of the 'next' Entity in the ArrayList. If the positions are the same, then the Entity is added if an Entity is not already on the Landscape. If the position is not equal to the 'next' Entity's, then any Entity that is there is removed. In my first implementation, I keep track of the 'next' Entity by using an index variable that is incremented after I find each Entity in the Landscape. This works fine, but using an Iterator makes it easier since I don't have to keep track of an index variable. Additionally, it is easier to tell when I reach the end of the ArrayList using an Iterator.
The only other changes I made before my extensions were to add a getNeighbors(row, col) method to Landscape that returns the number of locations with Entities that surround the given position and change the statements that printed the Landscape to the terminal to display statements that updated the display.
My first extension, AgingLife, gave Entities a lifespan of 12 iterations. Since the DisplayLandscape class only had 10 colors, I added three more so that there would be enough colors to support the long life (with one left over, for no particular reason). The only change I had to make in Iterate() was to increment the Entities' types every time and check to see if they had gotten too old, in which case I would not add them to my ArrayList.
My second extension, SickLife, starts with 10 randomly placed Entities that have the type 0, with the rest of the Entities starting with type 11. The healthy entities are type 11 so that sick entities can get sicker and sicker as they change from type 0 to type 10. Since I didn't know to begin with how quickly the infection would spread, I made a private static final variable HEALTHY that represents the healthy Entities' type, so when I create the healthy Entities, I give them type HEALTHY. Through experimentation, I decided to make sick Entities count as neighbors, but did place the constraint that a location will not produce an Entitiy if it is surrounded by 3 sick Entities. If it has 1 or 2 sick and 2 or 1 healthy Entities as neighbors, it will produce an Entity, but that Entity will immediatly get sick because of the sick Entities around it. Sick Entities, like all Entities, will die of loneliness or overpopulation, but if it lives to be 10, then it will die anyway. As a helper method for Iterate, I added a private method numNeighborsIll() to my SickEntity simulation class. I used this number to check if an Entity was neighbored by an ill Entity and to make sure that, for an empty location, all 3 surrounding Entities were not sick.
Once I completed my extensions, I moved some code around. All the Iterate methods used the same algorithm for putting the Entities in the ArrayList onto the Landscape, so I made a new method keepOnly(ArrayList<Entity> forKeeps) in Landscape that would do the task for all 3 simulations. Also, I removed the main() methods from my Life classes, and modified Simulation to enable it to perform a simulation for any of the three Life simulations. Since the Life simulations populate the grid differently, I added a method to Simulation that I overrride in my two Life extensions called populateLandscape(). The main method accepts a parameter specifying the type of simulation, but if it does not receive any, it asks the user to enter them afterward. It then creates a simulation of the appropriate kind, and can call populateLandscape() and the correct method is executed. Then a for-loop runs through a set number of iterations at a certain speed which are provided by variables that are not user accessable.
Later, I decided to review what I learned two years ago about GUIs by adding some buttons to the LandscapeDisplay JFrame. I added buttons to make the simulation "go," "stop," "restart," go "faster," and go "slower." I added them to a JPanel using the FlowLayout, which I then added to the SOUTH side of the frame. Since the simulation runs in the Simulation class, I set the buttons' actionListeners to sim, the name I gave the Simulation in LandscapeDisplay so that I could do this. I also had to make Simulation implement ActionListener. I altered my main() method so that rather than use a for loop to run iterations, I ran a while(true) loop that included a conditional to see if a private boolean, iterating, is true. If it is not, as it is at the start and whenever the user has pressed "stop," then the program waits for a second before testing if it should start again. The faster and slower buttons altered a static variable. This was probably not the best solutions, since it means that if I had two simulations running at the same time in different windows, they would both be affected by both pairs of speed buttons. The restart button clears the Landscape using keepOnly(new ArrayList<Entity), then calls populateLandscape() to reset.
If you don't provide any arguments when you execute the program from the terminal, you get a second chance, as shown.
If you enter a string other than "aging" or "sick," either as an
argument or in response to the direct question, you will default
to a normal simulation.
My normal simulations act just like Conway's game, though they are more colorful. In this animated gif, you can tell near the end that some appearantly stationary cells flicker, while others do not. This is because the gif only shows every 14th iteration, so the flickering cells are actually being removed and reborn. This also explains why the rows of three are not oscillating -- they only point the other direction on odd iterations.
My aging simulations result in fewer remaining cells since any 'stable' form that has a cell that never dies to be reborn will not be stable when that cell ages. These also tend to reach their stable forms quicker, although, as with the normal implementation, there is a lot of variation. It begins entirely black since black is the first color in the array. The bricks that remain flicker since the cells take turns dying.
My sick simulations have more complecated results. The simulation has two parts; in the first, the infected cells infect others so that much of the population is wiped out. This takes less than 40 iterations, as shown below with a step of two. The reddish cells are healthy, black cells are newly infected, and other colors are dying with infection.
In the second part, the remaining population makes a comeback. It does not spread quite enough to match the normal configuration, and sometimes hardly spreads at all, but usually it is fairly close to a normal result. An entire simulation is shown below. Like the normal simulation, the oscilators only appear stationary; every 14th iteration is shown.
This is what the GUI looks like after I added buttons
In this project, I learned to use an Iterator to access every element in a list without having to keeping track of an index (it may also be faster, but I used it here on an ArrayList, so it wasn't). Since I was not working on a Mac when I finished my alternate simulations, I found out how to make Java save a graphic to a png file. I made a new method in the DisplayLandscape class that would do this when I wanted to save a simulation. I also utilized the args for the first time, and learned a fair bit about the behavior of Conway's life entities. Adding the Buttons was a great review, and since I needed Simulation to know about pressing buttons, I know have a much better understanding of what implementing ActionListener is. I always knew basically what it did, but I now have a much better grasp on what button.addActionListener(this); does (specifically the 'this' portion).