Developing Rainbow Life
Conway’s Game of Life is popular enough that people have come up with variations of it. Some variations tinker with the neighbor counts that determine a cell’s state. Others take the two-dimensional lattice of cells into the third dimension and create a three-dimensional version of life with cubes representing the cells. One particularly interesting Life variation is a multicolored version, called Color Life or Rainbow Life. In this version, each living cell contains a color property, and the cell is rendered in that color. When a new cell is born, the color of that cell is determined by the average color of the three living neighbor cells that made its birth possible. This lends a sort of parent-child relationship to the game because the three “parent” cells give birth to a new “child” that inherits a trait from all three of them—the trait of color.An example will shed some further light on the issue. Figure 5-4 shows a grid of cells. In the next clock tick, the center cell will spring to life because it has three living neighbors. Suppose that neighbor 1 is pure red, neighbor 2 is pure blue, and neighbor 3 is white. The center cell’s color will inherit from its three parents by averaging their colors together. Table 5-1 shows the red/green/blue components of these cells.
Figure 5-4: The Rainbow Life game
NEIGHBOR | COLOR | RED | GREEN | BLUE |
---|---|---|---|---|
1 | Red | 255 | 0 | 0 |
2 | Blue | 0 | 0 | 255 |
3 | White | 255 | 255 | 255 |
Result | -- | 170 | 85 | 170 |
You obtain the result by adding the three color components and dividing by 3. RGB color 170, 85, 85 is a medium brown. This is the color that the newborn cell will have. Like its one-color cousin, watching Rainbow Life in action gives you far more insight into how it works than reading an explanation of it. Have fun exploring the sample program by simply running it before studying the code.
Creating the RainbowLifeGame Class
Because Rainbow Life is so similar to its monochromatic cousin, the best way to implement this game is to inherit directly off of the two Life classes discussed previously. The game class is the easier of the two because you need to change only one method to achieve the rainbow functionality. That member is the CreateOneCell method, which you’ll recall is the method that’s called polymorphically on the base class to return an instance of the game-specific cell descendant. Listing 5-10 shows the new code for the Rainbow Life variation of this method.
Listing 5.10: Rainbow Life CreateOneCell Method
Protected Overrides Function _
CreateOneCell(ByVal oPos As Point) As CellularAutomataCell
Dim oC As RainbowLifeCell
oC = New RainbowLifeCell(oPos, Me.CellRadius)
If oRand.Next(0, Int32.MaxValue) Mod 5 = 0 Then
oC.Alive = True
oC.SetRandomColor()
End If
Return oC
End Function
Like the single-color version, Rainbow Life creates a living cell 20 percent of the time. This newly born cell is also given a random color using the SetRandomColor method on the cell class.What’s cool about inheriting the rainbow version of the Life game class from the standard Life game class is that you don’t have to change the functionality of anything except the method you’ve just seen. The RunAGeneration method, for instance, needs no changing because you want the same actions to occur. Those actions (to give you a refresher) are to reset all the cells’ neighbor counts to 0, then to rip through all the living cells and update their neighbor counts, and finally to change the state of each cell according to its neighbors.What does change in the rainbow version is what’s going on in some of these subparts. For example, when updating neighbor counts, the program also needs to keep track of the colors of all the neighbors so that a newly born cell can determine what its new color should be. The cell class declares all of this new functionality, however.
Creating the RainbowLifeCell Class
The cell class for the rainbow version of Life also inherits from the “standard” Conway version of the CellularAutomataCell class, but there are quite a few more changes. The first change was already hinted at earlier—a method named SetRandomColor that picks a starting color for each cell. Listing 5-11 displays that method, which chooses the color of each cell at the start of the game, along with some other members.
Listing 5.11: The Method SetRandomColor
Private FColor As Color
Private FNeighborRTot As Integer
Private FNeighborGTot As Integer
Private FNeighborBTot As Integer
Public Sub SetRandomColor()
Dim c As Color
Select Case oRand.Next(0, Int32.MaxValue) Mod 10
Case 0 : c = Color.Yellow
Case 1 : c = Color.Green
Case 2 : c = Color.Blue
Case 3 : c = Color.Red
Case 4, 5 : c = Color.White
Case 6 : c = Color.Orange
Case 7 : c = Color.Violet
Case 8 : c = Color.DarkBlue
Case 9 : c = Color.Magenta
End Select
Me.SetColor(c)
End Sub
Overrides Function GetColor() As Color
Return IIf(Not Alive, Color.Black, FColor)
End Function
Private Sub SetColor(ByVal c As Color)
FColor = c
End Sub
This method doesn’t implement a truly random color (selecting random numbers from 0–255 for red, green, and blue and constructing a color from these components). The technique in this method, which is to choose from nine “base” colors (including white), gives a much more interesting starting state and much prettier results once the cells start on their life cycles.Also shown in Listing 5-11 is the GetColor method, which overrides the original member found in the CellularAutomataCell class (this method is declared MustOverride in the ancestor). This method returns the private FColor variable if the current cell is alive and black if the cell is dead. You’ve also implemented a SetColor method to set the private color variable. The more common way of implementing code to get and set a private variable is to declare a property, but you can’t do that in this case because you were already forced to implement a GetColor method by the ancestor class. Implementing a Color property would be redundant to this existing method.The remaining private variables, named FNeighborRTot, FNeighborGTot, and FBeighborBTot, store the color components of living neighbor cells for the purpose of creating a new offspring with the arithmetic mean of the new cell’s parents.Listing 5-12 shows how the program uses these color-counting variables. First, it resets them to the value 0 in the overridden PreCountReset method. This method is called before neighbor counting begins in each clock tick iteration of the game. Note how the method calls the ancestor method using the keyword MyBase, meaning that this method is extending the functionality defined in the ancestor class.
Listing 5.12: Overridden Neighbor-Counting Classes
Public Overrides Sub PreCountReset()
MyBase.PreCountReset()
FNeighborRTot = 0
FNeighborGTot = 0
FNeighborBTot = 0
End Sub
Public Overrides Sub UpdateNeighborsBasedOnMe()
Dim oC As RainbowLifeCell
If Me.Alive Then
For Each oC In FNeighbors
oC.NeighborCount += 1
oC.FNeighborRTot += Me.GetColor.R
oC.FNeighborGTot += Me.GetColor.G
oC.FNeighborBTot += Me.GetColor.B
Next
End If
End Sub
Public Overrides Sub UpdateAliveBasedonNeighbors()
Dim oC As ConwaysLifeCell
If Not Alive Then
If NeighborCount = 3 Then
Alive = True
Me.SetColor(Color.FromArgb(_
FNeighborRTot \ 3, _
FNeighborGTot \ 3, _
FNeighborBTot \ 3))
End If
Else
'alive now, stays alive w/ 2 or 3 neighbors
Alive = (NeighborCount = 2 Or NeighborCount = 3)
End If
End Sub
The actual neighbor counting happens in the method UpdateNeighborsBasedOnMe, which is also overridden from the ancestor class. Because no MyBase call exists here, this method totally replaces the functionality in the base class. If the cell is currently alive, then the neighbor count of all neighbor cells is incremented by 1 (like the standard Life class), and the color component’s counting variables are also incremented by the red, green, and blue components of this cell’s current color.
The final method, UpdateAliveBasedOnNeighbors, runs after all the neighbor counting. This method is similar to the version it overrides in the standard Life game. If a living cell has two or three neighbors, it remains alive. If a dead cell has exactly three neighbors, it springs to live and its starting color is set to the average color of the three neighbor cells. (The summed value of the three red, green, and blue components of the three neighbors are calculated during the counting process—all you need to do here is to divide those values by 3.)That wraps it up for the Rainbow Life variant. By inheriting off of the base Life class, you were able to change the functionality enough to create a whole new game.