John Conway's Game of Life in XAML/WPF using embedded Python

Following on from my series on embedding DLR scripts in XAML, I present an implementation of John Conway's Game of Life in XAML/WPF using embedded Python scripts. The game is loaded completely from loose XAML. Even the initial game state is defined in dynamically loaded XAML files!

The game is hosted in the very dynamic application described in a previous post. Below is a screen shot of the hosted Game of Life applicaiton.

The Game of Life

The Game of Life is played in a two dimensional array of cells, herein called the board. Each cell can be in one of two states - alive or dead. From an initial board state (generation zero), subsequent board states (generation 'n') can be calculated. The board state for generation 'n+1' is a calculated from the board state of generation 'n' following a few simple rules (taken from the Wikipedia article):

  • Any live cell with fewer than two live neighbours dies, as if by needs caused by underpopulation.
  • Any live cell with more than three live neighbours dies, as if by overcrowding.
  • Any live cell with two or three live neighbours lives, unchanged, to the next generation.
  • Any tile with exactly three live neighbours cells will be populated with a living cell.

Where the neighbours of a cell are the 8 cells that are adjacent to the cell in question.

Modeling the Board

The model of the board for this game is very simple if we do not allow for an infinite board (as is required by the game in its ideal form). To keep things really simple, we will use a two dimensional array of booleans to represent the board.

Of course, the use of data binding in WPF requires that the data source (ie, the board) send change notifications, so a simple two dimensional array of booleans will not suffice. To make the board observable, we introduce a class called ObservableBool. This class uses a boolean dependency variable to hold the boolean, and is inherently observable thanks to the dependency property system.

  1. public class ObservableBool : DependencyObject
  2. {
  3. public bool Value
  4. {
  5. get { return (bool)GetValue(ValueProperty); }
  6. set { SetValue(ValueProperty, value); }
  7. }
  8. public static readonly DependencyProperty ValueProperty =
  9. DependencyProperty.Register("Value", typeof(bool), typeof(ObservableBool), null);
  10. }

The model for our board is now simply a two dimensional array of ObservableBool.

The initial board for the game can easily be defined in XAML. The following is a simple Blinker (two-phase oscillator); a 3x3 board with the only the cells in the middle "row" being alive:

  1. <x:Array Type="{x:Type system:Array}">
  2. <x:Array Type="{x:Type local:ObservableBool}">
  3. <local:ObservableBool Value="False" />
  4. <local:ObservableBool Value="False" />
  5. <local:ObservableBool Value="False" />
  6. </x:Array>
  7. <x:Array Type="{x:Type local:ObservableBool}">
  8. <local:ObservableBool Value="True" />
  9. <local:ObservableBool Value="True" />
  10. <local:ObservableBool Value="True" />
  11. </x:Array>
  12. <x:Array Type="{x:Type local:ObservableBool}">
  13. <local:ObservableBool Value="False" />
  14. <local:ObservableBool Value="False" />
  15. <local:ObservableBool Value="False" />
  16. </x:Array>
  17. </x:Array>

A View of the Board

The board is being drawn as a ListBox, where each item in the list is a row of the board:

  1. <ListBox BorderThickness="0"
  2. ItemsSource="{DynamicResource Board}"
  3. ItemTemplate="{StaticResource RowTemplate}" />

The ItemsSource property of the ListBox is bound to the board. It is bound using a DynamicResource markup extension as the board will be loaded and added to the resource dictionary after the program is running.

We use an item template to present each row of the board as a horizontal ListBox:

  1. <DataTemplate x:Key="RowTemplate">
  2. <ListBox BorderThickness="0"
  3. ItemsSource="{Binding}"
  4. Style="{StaticResource HorizontalListBox}"
  5. ItemTemplate="{StaticResource CellTemplate}" />
  6. </DataTemplate>

The HorizontalListBox style simply sets the ListBox's ItemsPanel to be horizontal StackPanel:

  1. <Style x:Key="HorizontalListBox">
  2. <Setter Property="ListBox.ItemsPanel">
  3. <Setter.Value>
  4. <ItemsPanelTemplate>
  5. <StackPanel Orientation="Horizontal" />
  6. </ItemsPanelTemplate>
  7. </Setter.Value>
  8. </Setter>
  9. </Style>

The CellTemplate determines how the cell draws - finally we get to see some embedded DLR scripts:

  1. <local:ScriptValueConverter x:Key="ColorConverter" Language="Python">
  2. <local:ScriptValueConverter.ForwardExpression>
  3. parameter[0] if value == 0 else parameter[1]
  4. </local:ScriptValueConverter.ForwardExpression>
  5. </local:ScriptValueConverter>
  6.  
  7. <DataTemplate x:Key="CellTemplate">
  8. <Border BorderThickness="2" BorderBrush="DarkGray"
  9. CornerRadius="2" Width="20" Height="20" >
  10. <Rectangle Width="16">
  11. <Rectangle.Fill>
  12. <Binding Path="Value" Converter="{StaticResource ColorConverter}">
  13. <Binding.ConverterParameter>
  14. <x:ArrayExtension Type="{x:Type media:Brush}">
  15. <x:StaticExtension Member="media:Brushes.White" />
  16. <x:StaticExtension Member="media:Brushes.HotPink" />
  17. </x:ArrayExtension>
  18. </Binding.ConverterParameter>
  19. </Binding>
  20. </Rectangle.Fill>
  21. </Rectangle>
  22. </Border>
  23. </DataTemplate>

The CellTemplate is quite simple - it draws a dark gray border that contains a filled rectangle. The rectangle is either pink or white depending on the cell state (pink if alive, white if dead). To perform the conversion from boolean to Brush, most WPF experts will have you resort to writing a class in your code-behind file, which seems crazy when the value conversion is a simple expression.

It should be noted that the ScriptValueConverter is passed two values in an array, overcoming the usual limitation of the single ConverterParameter object. The Python expression evaluates to one of these inputs based on the state of the cell that it is bound to.

Loading a Board

Boards are defined in XAML files that are loaded at runtime. Once loaded, the board is added to the window's resource dictionary. Given that the board view binds to the board as a DynamicResource, the view will update when the resource is changed. This is achieved with the following button and Python script:

  1. <Button Content="Load New Board" DataContext="{Binding ElementName=Root}">
  2. <local:ScriptEventHandler.Handler>
  3. <local:ScriptEventHandler Language="Python" RoutedEvent="Button.Click">
  4. <local:ScriptEventHandler.Script>
  5. <system:String xml:space="preserve">
  6. <![CDATA[
  7. import clr
  8.  
  9. clr.AddReference('mscorlib')
  10. clr.AddReference('System.Windows.Forms')
  11. clr.AddReference('PresentationFramework')
  12.  
  13. from System.Windows import Forms
  14. from System.Windows import Controls
  15. from System.Windows import Markup
  16. from System import IO
  17.  
  18. dlg = Forms.OpenFileDialog();
  19. dlg.Filter = "XAML FIles (*.xaml)|*.xaml";
  20. if dlg.ShowDialog() == Forms.DialogResult.OK:
  21. board = Markup.XamlReader.Load( IO.File.OpenRead(dlg.FileName))
  22. sender.DataContext.Resources.Remove( "Board" )
  23. sender.DataContext.Resources.Add( "Board", board )
  24. ]]>
  25. </system:String>
  26. </local:ScriptEventHandler.Script>
  27. </local:ScriptEventHandler>
  28. </local:ScriptEventHandler.Handler>
  29. </Button>

Playing the Game

The Game of Life is played by calculating successive generations of the board. We add a button to calculate the next generation. A simple implementation of the Game of Life is written in the Python script. The button's DataContext is bound to the board, allowing the script to retrieve the board from the button:

  1. <Button Content="Next Generation" DataContext="{DynamicResource Board}">
  2. <local:ScriptEventHandler.Handler>
  3. <local:ScriptEventHandler Language="Python" RoutedEvent="Button.Click">
  4. <local:ScriptEventHandler.Script>
  5. <system:String xml:space="preserve">
  6. <![CDATA[
  7. numRows = sender.DataContext.Length
  8. numCols = sender.DataContext[0].Length
  9.  
  10. def countLiveNeighbours( data, row, col ):
  11. count = 0
  12. for j in range(max(0, row-1), min(numRows, row+2)):
  13. for i in range(max(0, col-1), min(numCols, col+2)):
  14. if (not ( j == row and i == col ) ) and data[j][i].Value != 0:
  15. count = count + 1
  16. return count
  17.  
  18. def calcNextGen( data, row, col ):
  19. n = countLiveNeighbours( data, row, col )
  20. isCellDead = data[row][col].Value == 0
  21. if isCellDead:
  22. # become alive if there are 3 Neighbours
  23. if n == 3:
  24. return 1
  25. else:
  26. return 0
  27. else:
  28. # die if there are not 2 or 3 Neighbours
  29. if n == 2 or n == 3:
  30. return 1
  31. else:
  32. return 0
  33.  
  34. nextGen = [[0 for c in range(numCols)] for r in range(numRows)]
  35.  
  36. for j in range( numRows ):
  37. for i in range( numCols ):
  38. nextGen[j][i] = calcNextGen( sender.DataContext, j, i )
  39.  
  40. for j in range( numRows ):
  41. for i in range( numCols ):
  42. if sender.DataContext[j][i].Value != nextGen[j][i]:
  43. sender.DataContext[j][i].Value = nextGen[j][i]
  44.  
  45. ]]>
  46. </system:String>
  47. </local:ScriptEventHandler.Script>
  48. </local:ScriptEventHandler>
  49. </local:ScriptEventHandler.Handler>
  50. </Button>

Getting it Running

The Game of Life XAML file and a couple of sample boards are attached to this post. Load GameOfLife.xaml with the very dynamic application described in a previous post.

The ObservableBool class needs to be present in a referenced DLL. To get moving quickly, you can simply add the class definition to the application and rebuild.

AttachmentSize
Binary Data GameOfLife.xaml5.13 KB
Binary Data blinker.xaml987 bytes
Binary Data glider.xaml8.5 KB