Tuesday, July 31, 2012

Game of Life Exercise and Extension Methods

A quick attempt in writing the Game of Life simulator using WPF. As a side goal, I wanted to better understand the extension methods in C# so I tried to move as much code as possible into the Helper class without sacrificing readability.

PopulateGrid adds 50 rows and columns to a WPF Grid, creating a 50x50 matrix of cell. It then adds a rectangle to each cell so coloring could be applied. RePaintCell changes the background color of the cell at position i,j. InitializeArray just fills a 50x50 array of booleans, each value representing either a live or dead cell. CheckCell checks a single cell to find out if it will be live or dead in the next iteration. FillArray uses CheckCell to analyse the current array and construct the array of the next iteration. DrawArray compares the current and next iterations. If there is difference in the color of the cell at i,j, this cell is painted appropriately, otherwise it is skipped. Finally, AddGlider adds a glider element to the empty array so it was easy to test that the game runs correctly. The full listing is below.

MainWindow.xaml

<Window x:Class="GameOfLife.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="386" Width="525">
    <Grid Name="MainGrid">
        <Grid Name="DynamicGrid" ShowGridLines="True">
        </Grid>
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="25,316,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System;

namespace GameOfLife
{
    public partial class MainWindow : Window
    {
        private bool[,] arrOne = new bool[Helper.x, Helper.y];
        private bool[,] arrTwo = new bool[Helper.x, Helper.y];
        private DispatcherTimer dt = new DispatcherTimer();
        private int count;

        public MainWindow()
        {
            InitializeComponent();
            dt.Interval = TimeSpan.FromMilliseconds(100);
            dt.Tick += dt_Tick;
            dt.Start();
            Loaded += MainWindow_Loaded;
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            DynamicGrid.PopulateGrid(new SolidColorBrush(Colors.Blue));
            InitializeGame();
        }

        void InitializeGame()
        {
            arrOne.InitializeArray(false);
            arrTwo.InitializeArray(false);
            arrOne.AddGlider(20, 30);
            arrOne.DrawArray(arrTwo, DynamicGrid);
        }

        void dt_Tick(object sender, EventArgs e)
        {
            if(true)
            {
                bool[,] arrCurrent = count%2 == 0 ? arrOne : arrTwo;
                bool[,] arrNext = count%2 == 0 ? arrTwo : arrOne;

                arrNext.FillArray(arrCurrent);
                arrNext.DrawArray(arrCurrent, DynamicGrid);
                count++;
            }
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            if(dt.IsEnabled)
                dt.Stop();
            else
                dt.Start();
        }
    }
}

Helper.cs

using System.Linq;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;

namespace GameOfLife
{
    public static class Helper
    {
        public const int x = 50;
        public const int y = 50;

        public static void PopulateGrid(this Grid grid, SolidColorBrush brush)
        {
            for (int i = 0; i < y; i++)
            {
                grid.RowDefinitions.Add(new RowDefinition());
            }

            for (int j = 0; j < x; j++)
            {
                grid.ColumnDefinitions.Add(new ColumnDefinition());
            }

            for (int i = 0; i < y; i++)
            {
                for (int j = 0; j < x; j++)
                {
                    Rectangle rect = new Rectangle();
                    rect.SetValue(Grid.RowProperty, i);
                    rect.SetValue(Grid.ColumnProperty, j);
                    rect.Fill = brush;
                    grid.Children.Add(rect);
                }
            }
        }

        public static void RePaintCell(this Grid grid, int i, int j, SolidColorBrush brush)
        {
            Rectangle cell = grid.Children.Cast<Rectangle>().First(r => Grid.GetRow(r) == j && Grid.GetColumn(r) == i);
            cell.Fill = brush;
        }

        public static void InitializeArray<T>(this T[,] arr, T value)
        {
            int iDim = arr.GetLength(0);
            int jDim = arr.GetLength(1);
            for (int i = 0; i < iDim; i++)
            {
                for (int j = 0; j < jDim; j++)
                {
                    arr[i, j] = value;
                }
            }
        }

        public static void AddGlider(this bool[,] arr, int x, int y)
        {
            arr[x - 1, y] = false;
            arr[x - 1, y + 1] = false;
            arr[x - 1, y + 2] = true;

            arr[x, y] = true;
            arr[x, y + 1] = false;
            arr[x, y + 2] = true;

            arr[x + 1, y] = false;
            arr[x + 1, y + 1] = true;
            arr[x + 1, y + 2] = true;
        }

        public static void FillArray(this bool[,] arr, bool[,]arrCurrent)
        {
            for (int i = 0; i < y; i++)
            {
                for (int j = 0; j < x; j++)
                {
                    arr[i, j] = arrCurrent.CheckCell(i, j);
                }
            }
        }

        public static void DrawArray(this bool[,] arr, bool[,] arrCurrent, Grid grid)
        {
            SolidColorBrush redBrush = new SolidColorBrush(Colors.Red);
            SolidColorBrush blueBrush = new SolidColorBrush(Colors.Blue);

            for (int i = 0; i < y; i++)
            {
                for (int j = 0; j < x; j++)
                {
                    if (arr[i, j] != arrCurrent[i, j])
                    {
                        SolidColorBrush brush = arr[i, j] ? redBrush : blueBrush;
                        grid.RePaintCell(i, j, brush);
                    }
                }
            }
        }

        public static bool CheckCell(this bool[,] arr, int i, int j)
        {
            int nextI = i == (x - 1) ? 0 : i + 1;
            int prevI = i == 0 ? x - 1 : i - 1;
            int nextJ = j == (y - 1) ? 0 : j + 1;
            int prevJ = j == 0 ? y - 1 : j - 1;

            bool[] neighbours = new[]{
                                        arr[prevI, prevJ],   arr[i, prevJ],   arr[nextI, prevJ],
                                        arr[prevI, j],       arr[nextI, j],   arr[prevI, nextJ],
                                        arr[i, nextJ],       arr[nextI, nextJ]
                                    };

            int val = neighbours.Count(c => c);

            if (arr[i, j])
                return (val >= 2 && val <= 3) ? true : false;
            else
                return (val == 3) ? true : false;
        }
    }
}

Reference

Conway's Game of Life

The results are impressive

by . Also posted on my website

No comments: