Conway's Game of Life: An Exercise in WPF, MVVM and C#

This blog post was written for the Lockheed Martin Insight blog, sharing here for the external audience.
Last month I started the Pluralsight summer camp by watching an interesting video in which the presenter implements Conway's Game of Life using HTML, CSS and JavaScript. The video inspired me to write an MVVM based implementation of the game using Windows Presentation Foundation (WPF) and C#. There were two aims to this exercise. The primary was for me to reacquaint myself with WPF, which is a framework I've been wanting to spend some time with for a while. And the secondary was to implement something fun.

The purpose of this blog post is to document the exercise. We'll cover what Conway's Game of Life is, a bit about the design and then see how some WPF and .NET framework features help in implementing an MVVM solution. The source code for the implementation is available on GitHub.

So, what is Conway’s Game of Life?

The Game of Life is a popular cell automaton algorithm that simulates the evolution of life. The algorithm was designed by British mathematician John Conway and has gained popularity for its simplicity. In its purest form, the universe for the Game of Life is represented by a grid which has an infinite number of cells. Each grid cell can contain a dead or living cell. The game starts with the user providing a seed generation of living cells. Based on the initial generation of cells, the universe evolves through generations to simulate evolution. For a generation to evolve, the following four rules are applied to the current generation:

Any live cell with fewer than two live neighbours dies (due to under-population).
Any live cell with two or three live neighbours lives on to the next generation.
Any live cell with more than three live neighbours dies, (due to overcrowding).
Any dead cell with exactly three live neighbours becomes a live cell (by reproduction).
And that's it. By applying these four rules, it is surprising to see how a universe can evolve to create complex symmetrical patterns, even when there is no symmetry or pattern in the seed generation.

Arriving at an Object-Oriented Design
The MVVM architectural design pattern was adopted to achieve a clear separation of concerns. I started by identifying the models:

Cell Model: Represents a single cell in the universe. Encapsulates data for the cell's position and whether it is dead or alive.
Generation Model: Represents a generation of life. Encapsulates data on the size of the universe, the cells within the universe and provides functionality to alter the living state of cells.
The EvolutionEngine class was identified to hold the current generation and take on the responsibilities of:

Evolving the current generation to the next generation, as per the rules of evolution.
Detecting if the current generation can no longer evolve.
Keeping track of the number of generations that the current generation has evolved through.
Resetting the generation back to an empty state.
A single view-model was identified, namely, GenerationViewModel. The view-model uses the EvolutionEngine class to:

React to actions that originate from the view.
Provide information for the view to display.
Provide notifications to the view if the observed information changes.
In summary, we have a couple of models, an engine class to work with the models and a view-model to keep the view in sync with the models.

Writing the MVVM WPF Implementation
The following diagram illustrates how the components of an MVVM solution are related in WPF (image source).

A view is built using the eXtensible Application Markup Language (XAML). The association between a view and its view-model is accomplished through the DataContext property of the view. Controls in a view are bound to properties in the view-model using the data binding features of WPF. In a scenario where the view-model is only serving as a proxy between the view and model, you may consider the use of a view-model superfluous and let the view-model expose the underlying model (see the "Why Use a ViewModel?" section in an MSDN article). Actions performed on the user interface are encapsulated in commands that are exposed by the view-model (using the Command design pattern). We will see how a view-model exposes its supported commands using the ICommand interface. Finally, notifications are raised as C# events from the view-model. The INotifyPropertyChanged interface is used primarily for this purpose.

As I’ve alluded to, there are a number of useful features in WPF and .NET that assist in the implementation of an MVVM solution. The particular features that I’m referring to are:

The FrameworkElement.DataContext property.
Using the Binding class to associate parts of the view to the view-model.
Raising notifications through the INotifyPropertyChanged interface.
Converting incompatible data between bound properties using the IValueConverter interface.
Encapsulating and executing actions using the ICommand interface.
Each of these features were used in the implementation for the game. In the sections below, we will discuss what each of these features are and see examples of how they were adopted in the solution.

Setting the Data Context and specifying a Binding
The Grid control was a natural choice to visually represent the universe. Each cell of life within the universe is represented by a TextBlock control. The code snippet below shows how the TextBlock is created:

private TextBlock CreateCellTextBlock(Cell cell)
{
TextBlock cellTextBlock = new TextBlock();
cellTextBlock.DataContext = cell;
cellTextBlock.InputBindings.Add(CreateMouseClickInputBinding(cell));

cellTextBlock.SetBinding( 
    TextBlock.BackgroundProperty,  
    CreateCellAliveBinding() 
); 

return cellTextBlock; 

}
In WPF, user interface controls derive from the FrameworkElement class which contains an important property named DataContext. The DataContext property of a control is set to an object that the control uses to pull its data from and/or push its data to. This object is typically a view-model or model instance. By setting the TextBlock.DataContext property to an instance of the Cell model we can bind properties of the TextBlock control to properties of the Cell model.

Aside: As a WPF view is hierarchical, a nice feature of using the DataContext property is that it can be inherited from a parent user interface control.

In our Game of Life implementation, we use the Background (colour) property of the TextBlock control to indicate whether the cell is dead or alive. On lines 7-10 of the CreateCellTextBlock method, you’ll see that we call the SetBinding method. The first parameter to this method specifies which property we want to set the binding for (made possible with Dependency Properties) and the second parameter accepts a Binding instance.

The Binding instance is created by the following method:

private Binding CreateCellAliveBinding()
{
return new Binding
{
Path = new PropertyPath("Alive"),
Mode = BindingMode.TwoWay,
Converter = new LifeToColourConverter(
aliveColour: Brushes.Black,
deadColour: Brushes.White
)
};
}
There are a few things to note here. Firstly, the Binding object supports a Source property which we do not explicitly set. The reason for this is that if no Source property is set on a binding, the binding defaults to using the DataContext property on the control that the binding is set on. In this case, the TextBlock control’s DataContext property is already set to a Cell model instance. We can therefore set the Path property to point to a particular property within the model, in this case the boolean Cell.Alive property.

The Mode property is set to the enumeration value of BindingMode.TwoWay, meaning that any change to the TextBlock control through the user interface updates the model and conversely any change to the model updates the user interface. To support two-way binding, the Cell model will need to implement the INotifyPropertyChanged interface so that any change to the Alive property is notified to the view.

Implementing Notifications with INotifyPropertyChanged
The INotifyPropertyChanged interface is implemented by classes that need to notify clients (in our case, the view) that an observed property value has changed. Typically, this interface is implemented by view-models or models that want to broadcast changes to certain property values.

INotifyPropertyChanged supports one member, the PropertyChanged event. If you have a two-way binding setup between a user interface control property and a view-model property, programmatic changes to the view-model property will not be reflected in the user interface unless the view-model implements INotifyPropertyChanged and the observed property’s setter raises the PropertyChanged event.

In our solution, we wrote a reusable ObservableBase class that implements INotifyPropertyChanged. The Cell and GenerationViewModel classes both inherit from ObservableBase.

public class ObservableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged([CallerMemberName] string memberName = "") 
{ 
    if (PropertyChanged != null) 
    { 
        PropertyChanged( 
            this, 
            new PropertyChangedEventArgs(memberName) 
        ); 
    } 
} 

}
The PropertyChanged event is raised from the OnPropertyChanged method which accepts a single parameter – the name of the property that has changed. The CallerMemberName attribute is used so that we can call this method without having to specify a hardcoded string with the property name. The code below shows the implementation for the Cell model, which inherits from ObservableBase.

public class Cell : ObservableBase
{
public int Row { get; private set; }
public int Column { get; private set; }

private bool alive; 
public bool Alive 
{ 
    get { return alive; } 
    set 
    { 
        alive = value; 
        OnPropertyChanged(); 
    } 
} 

public Cell(int row, int column, bool alive) 
{ 
    Row = row; 
    Column = column; 
    Alive = alive; 
} 

public override string ToString() 
{ 
    return string.Format( 
        "Cell ({0},{1}) - {2}", Row, Column, Alive ? "Alive" : "Dead" 
    ); 
} 

}
Notice how the Alive property setter calls the OnPropertyChanged event. An unfortunate side effect of implementing INotifyPropertyChanged is that you’re unable to use automatic properties for the properties that need to raise the notification event.

Converting Between Bound Properties Using IValueConverter
In our earlier code snippet for the CreateCellAliveBinding method, you may have noticed that we also set the Binding.Converter property. The Converter property is used when we bind two properties that have a type mismatch or expect data in different formats. To recap, we are using the Background (colour) property of the TextBlock to indicate if the cell represented by the TextBlock is dead or alive. The Alive property on our Cell model is of boolean type, therefore, we need our binding to convert the boolean value to a colour. This is where the IValueConverter interface is used to take on the role of an adapter.

The implementation of the IValueConverter is in the class LifeToColourConverter, shown below:

public class LifeToColourConverter : IValueConverter
{
public SolidColorBrush AliveColour { get; private set; }
public SolidColorBrush DeadColour { get; private set; }

public LifeToColourConverter(SolidColorBrush aliveColour,  
    SolidColorBrush deadColour) 
{ 
    AliveColour = aliveColour; 
    DeadColour = deadColour; 
} 

public object Convert(object value,  
    Type targetType, object parameter, CultureInfo culture) 
{ 
    bool alive = false; 

    if (value is bool) 
        alive = (bool) value; 
         
    return alive ? AliveColour : DeadColour; 
} 

public object ConvertBack(object value,  
    Type targetType, object parameter, CultureInfo culture) 
{ 
    if (value is SolidColorBrush) 
        return ((SolidColorBrush) value) == AliveColour; 

    return false; 
} 

}
The IValueConverter interface supports two methods, namely, Convert and ConvertBack. The Convert method is used when binding the value from the source property (Cell.Alive) to the target property (TextBlock.Background). Conversely, the ConvertBack method is used when updating the source from the target. If you are only using one-way binding, then you are only required to provide an implementation for the Convert method (the ConvertBack method can safely throw the NotImplementedException).

Handling User Interface Actions Using the Command Design Pattern
A view-model exposes a set of actions that are encapsulated within commands. Commands are exposed as properties on the view-model so that user interface controls can bind to them. The logic for a command is encapsulated within a class that implements ICommand. The ICommand interface supports two methods; CanExecute and Execute. The CanExecute method returns a boolean value which determines whether the command can execute. The Execute method contains the command logic to run.

In our solution, the view's DataContext is set to an instance of the GenerationViewModel class. In this view-model, we expose the following three commands:

public RelayCommand EvolveCommand { get; private set; }
public RelayCommand ResetCommand { get; private set; }
public RelayCommand ToggleCellLifeCommand { get; private set; }
RelayCommand is a popular implementation of the ICommand interface which has been available as open source for some while. We are using a particular variant of RelayCommand which enables a single parameter to be sent to the method that ultimately executes the command. You can view how the three command properties are initialised and the implementations for the CanExecute/Execute methods in the GenerationViewModel class.

An advantage of the RelayCommand implementation for ICommand is that it achieves reusability by using the Predicate and Action delegates to reference the CanExecute and Execute methods respectively. This avoids the need for writing a separate ICommand implementation for every command that the game supports.

In the user interface for the game, we have two buttons. One button binds to the view-model EvolveCommand property and the other binds to the ResetCommand property. The XAML snippet below shows how the command bindings are created:



If the ICommand.CanExecute method returns false for a command that is bound to a button, the button is automatically disabled.

The ToggleCellLifeCommand is executed when the user is setting up the initial generation of living cells, the binding for this command is setup programmatically by the following method (called by the CreateCellTextBlock method shown earlier):

private InputBinding CreateMouseClickInputBinding(Cell cell)
{
InputBinding cellTextBlockInputBinding = new InputBinding(
generationViewModel.ToggleCellLifeCommand,
new MouseGesture(MouseAction.LeftClick)
);
cellTextBlockInputBinding.CommandParameter =
string.Format("{0},{1}", cell.Row, cell.Column);

return cellTextBlockInputBinding; 

}
We are using the InputBinding class to specify that a left mouse click on the TextBlock should execute the ToggleCellLifeCommand and pass it a formatted string that contains the cell position. The ToggleCellLifeCommand method implementation in the GenerationViewModel class looks up the cell and toggles the Alive property boolean value. As we have an active binding between each TextBlock and its Cell.Alive property, the TextBlock colour will automatically change to a colour that represents the current value for the Alive property.

Summary
In this post, we’ve introduced and described some of the main features that WPF and the .NET framework provide for implementing MVVM based solutions. Conway’s Game of Life served as a fun exercise to use these features with.

We saw how the FrameworkElement.DataContext property enables seamless binding between user interface controls and properties on view-models. Two-way binding was shown to be possible using the INotifyPropertyChanged interface as a mechanism to notify the view of changes in the view-model. We introduced a useful ObservableBase class which implements INotifyPropertyChanged and can be reused in future WPF projects that need to support two-way binding. To bind two properties that work with different data types, we saw how an implementation of the IValueConverter interface can be used as an adapter. Finally, we saw how the command pattern is used to encapsulate logic in an ICommand implementation and how commands are exposed as properties on a view-model that can be bound-to in the view.

The animated graphic below shows the finished game. In the animation, we see the evolution for a simple L-shaped seed pattern in a universe of 25x25 cells. Surprisingly, this basic seed pattern lives through eighteen generations of life with some fascinating symmetrical patterns before it stops evolving.

Full source code is available in a GitHub repository

posted @ 2015-09-10 23:11  东风125  阅读(645)  评论(0编辑  收藏  举报