Building the Main Cellular Automaton Program
Polymorphism is all about shielding parts of the program from each other. In this case, the ancestor class called CellularAutomataGame declares the base functionality required to display the cells in a type of cellular automaton and to tick the giant clock. During each tick, it’s up to the inner workings of the individual descendant class to determine the new state of each cell, based on whatever rules that class defines. Because of this design, the main program doesn’t need to know about what each game class is doing during each timer tick. It only has to instantiate the game class, pass it a control upon which drawing should be done, and tell it when to run a clock tick. The ancestor class handles all of these actions.The form for the program contains a panel on the bottom that serves as the parent for a few controls. Two buttons will allow the cellular automaton to run a single clock tick or to start and stop a timer that runs the clock ticks in succession. Two labels report the number of total clock ticks that have occurred, as well as the time elapsed during the previous clock tick. Finally, a series of radio buttons on the lower right allow users to change which automaton game they’re playing. Figure 5-6 shows the design-time version of the form.
Figure 5-6: Design-time view of the cellular automaton executor
All of the game drawing happens in a class used earlier in the book—the FlickerFreePanel (called the PaintPanel back in Chapter 1, “Developing Your First Game”). Now, you can give it a more descriptive name and place it into a common folder so that you can share it between programs. There are two ways to use a visual control on a form. The first way is to add the control to the Toolbox (demonstrated in Chapter 2, “Writing Your First Game, Again”) and then drag it onto the form. The second method is to instantiate the control and add it to the form in code, as in this example. Listing 5-16 shows two private variables declared on the form, the Form_Load event and an important method called CreateGame.
Listing 5.16: Setting Up the Cellular Automaton Executor
Public Class fLife
Inherits System.Windows.Forms.Form
Private oP As FlickerFreePanel
Private oCell As CellularAutomata.CellularAutomataGame
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
oP = New FlickerFreePanel
oP.Dock = DockStyle.Fill
Me.Controls.Add(oP)
CreateGame()
End Sub
Private Sub CreateGame()
If rbGame0.Checked Then
oCell = New CellularAutomata.ConwaysLife(oP)
oCell.CellRadius = 8
ElseIf rbGame1.Checked Then
oCell = New CellularAutomata.RainbowLife(oP)
oCell.CellRadius = 8
Else
oCell = New CellularAutomata.TheVotingGame(oP)
oCell.CellRadius = 32
End If
End Sub
The form-level FlickerFreePanel control is declared private and attached to the form in the form’s Load event. To make sure it conforms to the size of the parent form (less the size of the lower panel that houses the buttons and other controls), set the Dock property to Fill on this panel. The last line in the form’s Load event is to call a method named CreateGame. This method is responsible for creating the correct CellularAutomataGame descendant based on which radio button the user clicks. The variable holding the current game is named oCell. The only difference between the game classes from this level of the program is the cell size. Set the cell size much larger for the Voting Game than for the Life games (32 pixels instead of 8 pixels), which makes it easier to see the patterns emerge for this particular game. The larger cell size also makes it possible to see one party or the other completely take over the board after several thousand generations.
Note | A cooler way to create the individual cellular automaton game descendants is to use a form of reflection to iterate through the program assembly, look for all subclasses of the CellularAutomata game class, and create a radio button dynamically for each game type found. Chapter 4, “More OOPing Around,” used a similar approach when generating random clues for the DeducTile Reasoning game. |
The only remaining functionality left to code is the event handlers for the two buttons, a timer control, and the radio buttons that control the game. Listing 5-17 shows all of these event handlers.Listing 5.17: Event Handlers for the Cellular Automaton Executor Form
Private Sub rbGame_CheckedChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles rbGame0.CheckedChanged, _
rbGame1.CheckedChanged, rbGame2.CheckedChanged
If oP Is Nothing Then Exit Sub
CreateGame()
End Sub
Private Sub RunOne(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles aTimer.Tick, _
cbSingle.Click
oCell.Tick()
lbTime.Text = oCell.TimerTicksElapsed
lbGen.Text = oCell.GenerationCount
End Sub
Private Sub cbGo_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cbGo.Click
If aTimer.Enabled Then
aTimer.Enabled = False
cbGo.Text = "Go"
rbGame0.Enabled = True
rbGame1.Enabled = True
rbGame2.Enabled = True
Else
aTimer.Enabled = True
cbGo.Text = "Stop"
rbGame0.Enabled = False
rbGame1.Enabled = False
rbGame2.Enabled = False
End If
End Sub
The event handler for the radio buttons, named rbGame_CheckedChanged, is trivial—they simply call the CreateGame method that loads one of the game classes based on the value of those same radio buttons. The program first checks to make sure the FlickerFreePanel variable oP has already been created. This is necessary because a radio button CheckChanged event fires as the form is being constructed by the .NET Framework, but you don’t want to start the cellular automaton game at this early stage (without the FlickerFreePanel, there would be nowhere to render the cell lattice).
The method RunOne serves a dual purpose. It runs when the user clicks the Single Step button, and it serves as the Tick event of a timer control named aTimer. You’ve seen demonstrations of a single method serving as the event handler for more than one control (the event handler for the radio buttons just discussed is one such example), but this is the first example you’ve seen where a single event handler is attached to different events on different types of controls. This is allowable as long as the event handler signature is the same for both events. For example, you couldn’t use the same event handler to handle a Click event and a Paint event because one event expects a System.EventArgs variable as a parameter and the other expects a System.Windows.Forms.PaintEventArgs as a parameter.The final event handler runs when the user clicks the Go button. This code turns the Go button into a toggle. The first time the user clicks this button, the timer starts ticking, the radio buttons are disabled (so a game can’t be changed midtick), and the button itself changes its text to Stop. When the user clicks the button again, this process is reversed—the timer is disabled, the radio buttons enabled, and the text changes once again to Go.