British mathematician John Conway invented the “Game of Life” in 1970. The rules of the game are simple, but the results of the game can be surprising. This combination made Conway and his creation famous, and stirred the debate over the ability of machines to simulate life. Although the “Game of Life” didn’t start as a computer game, programmers have written versions of the game in nearly every programming language and for nearly every platform. In this article we will implement the “Game of Life” for the Windows Presentation Foundation (WPF).
Rules of Life
The game of life plays out on a grid of cells. Each cell can be “alive” or “dead”. As time moves on, a dead cell can come to life, while a living cell can die. The rules for determining the state of a cell for the next time slice are the following:
- A cell that is alive stays alive if two or three of the neighboring cells are alive.
- A cell that is dead comes alive if three of the neighboring cells are alive.
Building a UI with Angled Brackets
Windows Presentation Foundation is one piece of the new WinFX framework. The goal of WPF is to provide a platform for building software with an enhanced user experience. WPF includes all of the user interface pieces we need to build a better presentation layer, including support for vector graphics, rich media, high-quality typography, and animations. Underneath the managed code exterior, WPF interfaces with Direct3D to take full advantage of specialized graphics hardware on a machine.
WPF is not just about UI widgets and anti-aliasing, however. WPF understands a declarative markup language called XAML. XAML is short for eXtensible Application Markup Language, and is pronounced “Zammel”. We can use XAML to specify a user interface in a declarative fashion. XAML files are valid XML files, so not only can humans read and write XAML, but software tools and utilities can easily create, transform, and parse XAML. XAML will enable artists and their artsy tools to work closely with developers and their nerdy tools (or at least there should be less blood shed than in the past).
Declaratively building a UI with angled brackets is nothing new in the world of .NET. ASP.NET developers have been building UIs for years like this:
<asp:Label runat="server" ID="Label1" Text="Name: "/>
<asp:TextBox runat="server" ID="TextBox1" />
</asp:Panel>
The ASP.NET runtime will parse the above markup and generate code to create Label, TextBox, and Panel objects, set the properties of those objects, and add the objects into a control tree. You can look at the markup as a textual representation of objects, their properties, and their relationship in a hierarchy. A tool will turn this textual representation into code, and the code, when compiled and executed, will carry out the necessary instructions to build the hierarchy of objects with their properties set. Think of XAML in a similar fashion.
A simple user interface for the game of life might have Stop and Start buttons at the bottom of a window, and use the rest of the window for visualizing the game. Describing the interface in XAML would look like the following:
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Title="Life" Background="PapayaWhip"
Height="400" Width="400">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom"
Orientation="Horizontal"
HorizontalAlignment="Center" >
<Button x:Name="startButton" Margin="5">Start</Button>
<Button x:Name="stopButton" Margin="5">Stop</Button>
</StackPanel>
<Grid x:Name="mainGrid" Background="Black" />
</DockPanel>
</Window>
We can see that in addition to Buttons, WPF also includes a number of controls to manage layout. We use three of these controls in this application: a DockPanel, a StackPanel, and a Grid. Our two Button controls live inside a StackPanel. A StackPanel will arrange children into a horizontal or vertical line. The StackPanel lives inside a DockPanel. A DockPanel arranges children to fill all of it’s available space. In our application we’ve instructed our StackPanel to dock to the bottom of the DockPanel (DockPanel.Dock="Bottom"). The only other control inside the DockPanel is a Grid, so the DockPanel will expand the Grid to fill all the remaining space. Our application’s interface will look like the picture to the right.
Notice we have not specified a specific size for any of the controls inside of our Window, instead we will rely on WPF to arrange and size all of the controls. Notice we also haven’t defined the contents of our Grid, and Grids require column and row definitions. The problem is, we don’t know what size we want the grid to be, but it should be relatively large, say 40 columns by 40 rows. Instead of putting 40 row and column definitions in XAML, we will put in the definitions with code.
The Code Of Life
Although a XAML file can include code from a .NET language, mixing the two gets messy. Fortunately, we can work beside XAML using a partial class specified by the x:Class attribute. The XAML compiler will keep the code it generates in one part of a partial class, and we will keep our code in another part. We can gain access to controls we’ve declared in the XAML when we give them an x:Name attribute. For example, we’ve given the two button controls names, so we can use them from code-behind (also, just so you know, we could have also wired up the events directly in XAML with the following attribute in the Button markup: Click="startButton_Click").
{
InitializeGrid();
startButton.Click += new RoutedEventHandler(startButton_Click);
stopButton.Click += new RoutedEventHandler(stopButton_Click);
_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
_timer.Interval = TimerInterval;
}
For the Grid, we can define a private variable (MaxGridSize) to hold the size of the grid – this approach will make it easier to make the number user configurable later. Once we know the size of the grid, we can tell the grid how many rows, and how many columns it should hold using RowDefinition and ColumnDefinition classes. Note these classes contain members to specify heights and widths, but again we will accept the defaults and let WPF figure out everything.
{
mainGrid.ColumnDefinitions.Add(new ColumnDefinition());
mainGrid.RowDefinitions.Add(new RowDefinition());
}
Life’s Events Don’t Pass Me By
Let’s start with a class to represent a cell of the game.
{
public bool IsAlive
{
get
{
return _isAlive;
}
set
{
_isAlive = value;
if (PropertyChanged != null)
{
PropertyChanged(
this,
new PropertyChangedEventArgs("IsAlive")
);
}
}
}
private bool _isAlive = false;
#region INotifyPropertyChanged Members
public event
PropertyChangedEventHandler PropertyChanged;
#endregion
}
Our class is simple and uses a single boolean field to keep it’s state. The only interesting feature of the class is the implementation of a special interface. The INotifyPropertyChange interface is a part of the System.ComponentModel namespace in .NET 2.0, and is a standard interface for property-change notification events. Windows Forms data binding already makes use of this interface to update controls when an underlying object changes state, and WPF follows suit. When life begins in a cell, our presentation window will know.
We also need to manage a collection of cells. The collection class can encapsulate the simple rules of life, like who lives, who dies, and who comes to life.
if (!_cells[row, column].IsAlive && neighbors == 3)
{
// a dead cell with three neighbors comes alive
_nextGeneration[row, column] = true;
}
else if (_cells[row, column].IsAlive &&
(neighbors == 3 || neighbors == 2))
{
// a live cell with two,three neighbors stay alive
_nextGeneration[row, column] = true;
}
else
{
// a cell dies because of loneliness or overcrowding
_nextGeneration[row, column] = false;
}
With the game’s data structures and rules in place, all we need to do is render each cell on the screen. Traditional approaches would use GDI+ drawing routines and painstaking calculations to determine what pixels we want to twiddle. With WPF, we have a different, cleaner, strategy.
We already have a Grid panel, all we need to do is provide content for each cell in the Grid. WPF can perform all the calculations to determine the size and placement of the content. Let’s tell WPF to place an ellipse inside each cell.
{
for (int column = 0; column < MaxGridSize; column++)
{
Ellipse ellipse = new Ellipse();
Grid.SetColumn(ellipse, column);
Grid.SetRow(ellipse, row);
ellipse.DataContext = _cells[row, column];
mainGrid.Children.Add(ellipse);
ellipse.Style = Resources["lifeStyle"] as Style;
}
}
In GDI+ we’d have to use a DrawEllipse method. In WPF we create a new Ellipse object, and tell WPF where to place the ellipse using the static SetColumn and SetRow static methods of the Grid class. SetColumn and SetRow write to an attached property of the Ellipse. Layout elements, like the Grid, StackPanel, and DockPanel, read the attached properties of their children to discover how the children wish to be presented. Attached properties allow extensibility in layout controls, and can be set in XAML with the following syntax.
With Grid position set, we then assign a CellOfLife object from our collection to the DataContext property of the Ellipse object. The DataContext property will allow us to perform one-way data binding against the CellOfLife object. Two-way data binding is also possible, as is one-time binding.
Finally, we add the Ellipse to the Children collection of the grid. Again, we have not specified a size for our ellipse – in fact we haven’t even specified a color. How will the object display itself? The answer is in the last line of the code where we apply a Style object. WPF allows for the styling of a user interface through resources, templates, and styles. Similar to cascading style sheets in web development, styling separates presentation and logic and makes it easy to design and maintain consistent visuals. The Style we are using is defined in a resource section of our XAML file.
<Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">
<Setter Property="Opacity" Value="{Binding Path=IsAlive}" />
<Setter Property="Fill" >
<Setter.Value>
<RadialGradientBrush>
<RadialGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0.0"/>
<GradientStop Color="Red" Offset="0.1" />
<GradientStop Color="DarkRed" Offset="0.9"/>
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Setter.Value>
</Setter>
</Style>
Our style targets a specific control type: the Ellipse. Setter elements will take care of assigning the property values we need. Notice we are using a data binding markup extension to keep the Opacity property of the Ellipse object in sync with the IsAlive property of our CellOfLife object. When the cell is dead, the ellipse will become transparent. When the cell is alive, the Ellipse will become completely opaque. Also notice how easy it is to apply a fancy gradient fill. We now have everything in place to play the Game of Life.
Goodbye, World
In this article we took a look at programming with WPF, both using XAML and C# code. We’ve seen data binding and layout elements, yet have barely scratched the surface of what is available in WPF and the WinFX API. Download the code, and try the Game of Life for yourself.
By Scott Allen. Questions? Comments? Bring them to my blog.