MySQL Net Setup Problem

In the process of setting up MySQL 5.0.32 on Debian Etch recently I ran into a frustrating problem. The server was installed correctly. The users were set up correctly, with the right established roles. I could connect to the server fine from the machine it was installed on, but not from any other machine on the LAN. I had run through the configuration in the MySQL Administrator three or four times to make sure I wasn’t missing anything, principally to keep squinting at the “Disable networking” checkbox to make sure it was clear. It was clear. It WAS CLEAR, dammit. But no matter: I could connect from the local system, but not from the Windows box sitting next to it. Trying from the MySQL Administrator program yielded the following…

mysql_neterr.png

From the command line it looked like this…

ERROR 2003 (HY000): Can’t connect to MySQL server on ‘192.168.0.105’ (111)

I Googled every combination of terms I could think of. At one point I ran across a discussion where someone seemed to be having the same problem, and about halfway down the page someone else suggested editing something in a conf file. Figuring I didn’t want to mess with conf files just yet (there MUST be a checkbox I missed SOMEWHERE) I kept butting my head against the problem. Finally, in a flash of what you wouldn’t call inspiration, given that I had been making no progress for thirty minutes, I tried the actual IP address rather than ‘localhost’ from the local host. No go. Aha… so it certainly acts like networking is disabled.

I went back and reread that discussion, and that’s when I learned about the variable bind-address in /etc/mysql/my.cnf. Here’s what it looks like after a default install…

# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address = 127.0.0.1

What it means is that in the default install mysqld will only bind to localhost (127.0.0.1). That interface has no communication with the outside world. The solution is just to comment it out, and suddenly everything works. You would think that the default install would also show “Disable networking” as “true” in the administrator when networking is… uhm… disabled, but I’m probably just not getting it yet.

AvalonLife Listing 4 – ALMainWin.xaml.cs

using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Documents;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Shapes;

namespace AvalonLife
{
	/// <summary>
	/// UIStateChanges
	/// 
	/// This enum provides indicators for the user interface states
	/// that the program can be in. Used to call UIStateChange().
	/// </summary>
	enum UIStateChanges
	{
		ModelCreated,
		ModelSaved,
		ModelSavedAs,
		ModelLoadedFromFile,
		ModelLoadedFromNet,
		ModelCellEdited,
		ModelRun,
		ModelPaused,
		ModelHalted,
		ModelReset,
		ModelPropertiesEdited    
	}
    
	/// <summary>
	/// Used to indicate the current type of cell brush used to pain
	/// the grid
	/// </summary>
	enum CellBrushType
	{
		Radial,
		Linear,
		Solid
	}
    
	enum ALFileType
	{
		None,
		AVL,
		Cells
	}
    
	public partial class ALMainWin : System.Windows.Window
	{

		#region ReticleAdorner class
        
		/// <summary>
		/// ReticleAdorner
		/// 
		/// This class is derived from Adorner, and renders the reticle (crosshairs)
		/// over the main game grid.
		/// </summary>
		public class ReticleAdorner : Adorner
		{
			public ReticleAdorner( UIElement target )
			: base(target)
			{
				_reticleColor = ALSettings.Default.ReticleColor;
				_reticlePen = new Pen(new SolidColorBrush(_reticleColor), 1);
			}
            
			protected override void OnRender( DrawingContext dctxt )
			{
				double height = ((Grid)(this.AdornedElement)).ActualHeight;
				double width = ((Grid)(this.AdornedElement)).ActualWidth;

				Point start = new Point( 0, height / 2 );
				Point end = new Point( width, height / 2 );
				dctxt.DrawLine( _reticlePen, start, end );

				start = new Point( width / 2, 0 );
				end = new Point( width / 2, height );
				dctxt.DrawLine( _reticlePen, start, end );
			}           
        
			/// <summary>
			/// Set this property to change the color of the reticle
			/// </summary>
			private Color _reticleColor;
			public Color ReticleColor
			{
				get
				{
					return _reticleColor;
				}
				set
				{
					_reticleColor = value;
					ALSettings.Default.ReticleColor = _reticleColor;
					_reticlePen = new Pen(new SolidColorBrush(_reticleColor), 1);
				}
			}

			private Pen _reticlePen;
        }
        
        #endregion
        
		public ALMainWin()
		{
			InitializeComponent();
		}

		/// <summary>
		/// PrepMessage( string, int )
		/// 
		/// A simple function to insert line breaks into a string destined for a messagebox.
		/// MessageBox.Show() seems to work differently on XP and Vista. On Vista a long
		/// message is sensibly wrapped at word boundaries. On XP it is not. If you insert
		/// line breaks to look right in XP, they don't look right in Vista. So this is an
		/// attempt to get some consistency.
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="lineLen"></param>
		/// <returns></returns>
		public string PrepMessage(string msg, int lineLen)
		{
			string strout = "";
			int j = 0;
			for (int i = 0; i < msg.Length; i++)
			{
				if ((j >= lineLen && msg[i] == ' ') || i == msg.Length - 1)
				{
					strout += msg.Substring(i - j, j);
					strout += "\n";
					j = 0;
				}
				else j++;
			}
			return strout;
		}

		#region menu bar event handlers

		/// <summary>
		/// Menu_OnGameNew(Object, RoutedEventArgs)
		/// 
		/// Handles the click event for the game menu, new command. Prompts the user and
		/// on yes rebuilds the simulation and life models and resets the grid.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameNew(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if ( ExecNew(false) )
				oldCursor = LifeGrid.Cursor;
			else
				_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}
        
		/// <summary>
		/// Menu_OnGameReset(Object, RoutedEventArgs)
		/// 
		/// Handles a click event on the Game menu, Reset item. Prompts the user, and on 'Yes' 
		/// rebuilds the model and simulation controller, and resets various parameters.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameReset(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if (MessageBox.Show(this, Properties.Resources.UI_MB_PromptText2, Properties.Resources.UI_MB_CaptionText2,
			MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
			{
				_ls.ResetSim();
				UIStateChange(UIStateChanges.ModelReset);
				oldCursor = LifeGrid.Cursor;
			}
			else
				_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}
        
		/// <summary>
		/// Menu_OnGameSave(Object, RoutedEventArgs)
		/// 
		/// Handles the click event for the game menu, save command. Pauses the sim if
		/// it is not already, and then calls ExecSave()
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameSave(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ExecSave(false);

			_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnGameSaveAs(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the "save as" command in the Game menu. The only difference
		/// from the function above is that this one calls ExecSave with true, causing
		/// a new filename to be chosen.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameSaveAs(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ExecSave(true);

			_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnGameLoad(Object, RoutedEventArgs)
		/// 
		/// Handles the click event for the game menu, load command. Pauses the sim and
		/// then checks to see if the current game is dirty. If so it pops a save dialog 
		/// and calls ExecSave() if the user answers affirm. It then calls ExecLoad(). If
		/// load fails (ExecLoad returns false) the function unpauses the game (if it was
		/// paused on entry). 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameLoad(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;
			bool final = false;

			System.Windows.Forms.OpenFileDialog opendlg =
			new System.Windows.Forms.OpenFileDialog();

			opendlg.DefaultExt = ".avl";
			opendlg.Filter = "AvalonLife Saved Games (.avl)|*.avl|Life Lexicon Cells (.cells)|*.cells";
			opendlg.Title = "Load Saved Model";
			if (opendlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				if ( ExecLoadFile(opendlg.FileName) )
				{
					final = true;
				}
			}
			if ( !final )
				_ls.IsPaused = paused;

			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnGameExit(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the game menu, exit command, and shuts down the application
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnGameExit(Object sender, RoutedEventArgs e)
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if ( CheckSave() == true )
				Application.Current.Shutdown();

			_ls.IsPaused = paused;
		}

		/// <summary>
		/// Menu_OnSettingsGridLines(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, show grid command, and turns the
		/// grid lines on/off accordingly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridLines(Object sender, RoutedEventArgs e)
		{
			if (MenuSettingsGridLines.IsChecked == false)
			{
				ALSettings.Default.GridOn = true;
				LifeGrid.ShowGridLines = true;
				MenuSettingsGridLines.IsChecked = true;
			}
			else
			{
				ALSettings.Default.GridOn = false;
				LifeGrid.ShowGridLines = false;
				MenuSettingsGridLines.IsChecked = false;
			}
		}

		/// <summary>
		/// Menu_OnSettingsReticle(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, show reticle command, and turns the
		/// reticle (crosshairs) on/off accordingly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsReticle(Object sender, RoutedEventArgs e)
		{
			if ( MenuSettingsReticle.IsChecked == false )
			{
				ALSettings.Default.ReticleOn = true;
				_gridAdornerLayer.GetAdorners(LifeGrid)[0].Visibility = Visibility.Visible;
				MenuSettingsReticle.IsChecked = true;
			}
			else
			{
				ALSettings.Default.ReticleOn = false;
				_gridAdornerLayer.GetAdorners(LifeGrid)[0].Visibility = Visibility.Hidden;
				MenuSettingsReticle.IsChecked = false;
			}
		}

		/// <summary>
		/// Menu_OnSettingsHaltStable(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Halt Stable Model command. Flips the
		/// state of this property in ALSettings. Controls whether the simulation will
		/// halt when a model ceases evolving.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsHaltStable(Object sender, RoutedEventArgs e)
		{
			if (MenuSettingsHaltStable.IsChecked == false)
			{    
				MenuSettingsHaltStable.IsChecked = true;
				ALSettings.Default.HaltOnStability = true;
			}
			else
			{
				MenuSettingsHaltStable.IsChecked = false;
				ALSettings.Default.HaltOnStability = false;
			}
		}

		/// <summary>
		/// Menu_OnSettingsGridBackground(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, grid background command. Opens a color picker
		/// dialog to let the user choose the grid background color. I'm sure there must be an easier
		/// way to deal with the collision between System.Windows.Media.xxx, which is used by
		/// the WPF shapes and brushes, and System.Drawing.xxx which is used by the standard color
		/// dialog in System.Windows.Forms, but I haven't taken the time to research it yet. So
		/// for the moment this function is full of ugly conversions. Works, though.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridBackground(Object sender, RoutedEventArgs e)
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			System.Windows.Forms.ColorDialog clrDlg = new System.Windows.Forms.ColorDialog();

			clrDlg.AllowFullOpen = true;
			clrDlg.SolidColorOnly = true;
			System.Drawing.Color origColor = System.Drawing.Color.FromArgb(
			((SolidColorBrush)LifeGrid.Background).Color.A,
			((SolidColorBrush)LifeGrid.Background).Color.R,
			((SolidColorBrush)LifeGrid.Background).Color.G,
			((SolidColorBrush)LifeGrid.Background).Color.B );

			clrDlg.Color = origColor;
			if ( clrDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK )
			{
				Color newColor = Color.FromArgb(clrDlg.Color.A, clrDlg.Color.R, clrDlg.Color.G, clrDlg.Color.B);
				ALSettings.Default.GridBackground = newColor;
				SolidColorBrush gridBkgBrush = new SolidColorBrush( newColor );
				LifeGrid.Background = gridBkgBrush; 
			}
			_ls.IsPaused = paused;
		}
        
		/// <summary>
		/// Menu_OnSettingsReticleColor(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the settings menu, reticle color command. Opens a color
		/// picker and allows the user to choose a new color for drawing the reticle.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsReticleColor(Object sender, RoutedEventArgs e)
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;
			ReticleAdorner ad = _gridAdornerLayer.GetAdorners(LifeGrid)[0] as ReticleAdorner;

			System.Windows.Forms.ColorDialog clrDlg = new System.Windows.Forms.ColorDialog();

			clrDlg.AllowFullOpen = true;
			clrDlg.SolidColorOnly = true;

			if ( clrDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK )
			{
				Color newColor = Color.FromArgb(clrDlg.Color.A, clrDlg.Color.R, clrDlg.Color.G, clrDlg.Color.B);
				ad.ReticleColor = newColor;
				ALSettings.Default.ReticleColor = newColor;
				ad.InvalidateVisual();
			}
			_ls.IsPaused = paused;
		}

		/// <summary>
		/// Menu_OnSettingsCellBrush(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Cell Brush command, and pops the brush definer
		/// dialog. Calls ApplyCellBrush on return if the user accepted the dialog.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsCellBrush(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ALBrushDlg dlg = new ALBrushDlg();

			dlg.Owner = this;
			if ( dlg.ShowDialog() == true )
			{
				LifeGrid.Cursor = Cursors.Wait;
				ApplyRectStyle();
				LifeGrid.Cursor = oldCursor;
			}    
			_ls.IsPaused = paused;
		}
        
		/// <summary>
		/// Menu_OnSettingsGridType(Object, RoutedEventArgs)
		/// 
		/// Handles a click on one of the grid type checkable menuitems in Settings | Grid Type.
		/// The Tag property of these controls has been set to the appropriate value from
		/// the GridType enumeration, so the tag can just be cast and passed through to
		/// SetGridType.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridType(Object sender, RoutedEventArgs e)
		{
			GridType gridType = (GridType)((MenuItem)sender).Tag;
			if (_ls.Model.LifeGridType != gridType)
			{
				_ls.Model.LifeGridType = gridType;
				SetGridType(gridType);
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
			}
		}

		/// <summary>
		/// Menu_OnSettingsGridSize(Object, RoutedEventArgs)
		/// 
		/// Handles a click on one of the pre-defined grid sizes in the Settings | Grid Size
		/// | [predefined grid size] menu. Since the predefined model sizes are square we can
		/// just store one dimensionin the object tag at startup, grab it here, and use it to
		/// figure out what size was requested.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSize(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			int size = (int)(((MenuItem)sender).Tag);
			if ( _ls.Model.ResizeModel(size, size) )
			{
				InitUIState();
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
			}
			else
				MessageBox.Show(this, Properties.Resources.UI_MB_ResizeFailedMsg, Properties.Resources.UI_MB_ResizeFailedCaption,
					MessageBoxButton.OK, MessageBoxImage.Error);

			_ls.IsPaused = paused;
			LifeGrid.Cursor = oldCursor;    
		}

		/// <summary>
		/// Menu_OnSettingsGridSizeCustom(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Grid Size | Custom menu. The dialog enforces type
		/// constraints on the edit, so we know we're getting ints, but we need to check how
		/// large a model the user has asked for. You can ask the sim to make a 1000 x 1000 grid.
		/// I did, and by the time I killed the process five minutes later it had a 1.7 gig
		/// working set. A grid that size requires the system to create about 3 million objects,
		/// plus or minus a couple hundred thousand. So we check to see if the aggregate grid
		/// size is greater than 10k cells, and pop a warning dialog. If the user wants to go
		/// ahead we let them. I've done a 200 x 200 grid and it loads relatively quickly and will
		/// actually run, but not smoothly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSizeCustom(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			ALModelSizeDlg dlg = new ALModelSizeDlg();
			dlg.Owner = this;
			dlg.Tag = _ls.Model;
			dlg.Resize = true;

			if ( dlg.ShowDialog() == true )
			{
				int rows = dlg.Rows;
				int cols = dlg.Columns;
				if ( rows * cols > 10000 )
				{
					string msg = PrepMessage(Properties.Resources.UI_MB_LargeModelMsg, 80);
					msg += "\n\nRequested model size will cause the program to create approximately " 
					+ Convert.ToString((rows * cols) * 3) + " objects.";
					if ( MessageBox.Show(this, msg, Properties.Resources.UI_MB_LargeModelCaption, 
						MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.Cancel )
					{    
						LifeGrid.Cursor = oldCursor;
						return;
					}
				}

				if (_ls.Model.ResizeModel(rows, cols))
				{
					InitUIState();
					UIStateChange(UIStateChanges.ModelPropertiesEdited);
				}
				else
					MessageBox.Show(this, Properties.Resources.UI_MB_ResizeFailedMsg, Properties.Resources.UI_MB_ResizeFailedCaption,
						MessageBoxButton.OK, MessageBoxImage.Error);
			}
			_ls.IsPaused = paused;
			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnSettingsGridSizeShrink(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the Settings | Grid Size | Shrink to Model menu item. Calls
		/// the model to resize the grid to the model size. See LifeModel.ResizeModel() for
		/// failure conditions.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSizeShrink(Object sender, RoutedEventArgs e)
		{
			Cursor oldCursor = LifeGrid.Cursor;
			LifeGrid.Cursor = Cursors.Wait;

			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;

			if (_ls.Model.ResizeModel(0, 0))
			{
				InitUIState();
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
			}
			else
				MessageBox.Show(this, Properties.Resources.UI_MB_ResizeFailedMsg, Properties.Resources.UI_MB_ResizeFailedCaption,
					MessageBoxButton.OK, MessageBoxImage.Error);

			_ls.IsPaused = paused;
			LifeGrid.Cursor = oldCursor;
		}

		/// <summary>
		/// Menu_OnSettingsGridSettings(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the grid settings menuitem in Settings | Grid Settings. Pops
		/// the grid settings dialog window.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsGridSettings(Object sender, RoutedEventArgs e)
		{
			ALGridDlg dlg = new ALGridDlg();
			dlg.Owner = this;
			dlg.ShowDialog();
		}

		/// <summary>
		/// Menu_OnSettingsModelName(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the model name menuitem in Settings. Pops the model
		/// name dialog window. Tracks changes to the name of the model so it can
		/// update the window title bar after the dialog closes.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnSettingsModelName(Object sender, RoutedEventArgs e)
		{
			ALModelNameDlg dlg = new ALModelNameDlg();
			dlg.Owner = this;
			dlg.Tag = _ls;
			if ( dlg.ShowDialog() == true )
				UIStateChange(UIStateChanges.ModelPropertiesEdited);
		}

		/// <summary>
		/// Menu_OnHelpHowTo(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the help menu, How to Play command. 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnHelpHowTo(Object sender, RoutedEventArgs e)
		{
			System.Windows.Forms.HelpNavigator nav =
			System.Windows.Forms.HelpNavigator.TopicId;

			System.Windows.Forms.Help.ShowHelp(null, @"avalonlife.chm", nav, "1020");
		}

		/// <summary>
		/// Menu_OnHelpAbout(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the help menu, about AvalonLife command.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnHelpAbout(Object sender, RoutedEventArgs e)
		{
			System.Windows.Forms.HelpNavigator nav =
			System.Windows.Forms.HelpNavigator.TopicId;

			System.Windows.Forms.Help.ShowHelp(null, @"avalonlife.chm", nav, "1000");
		}

		/// <summary>
		/// Menu_OnHelpAboutLife(Object, RoutedEventArgs)
		/// 
		/// Handles a click on the help menu, about the Game of Life command.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Menu_OnHelpAboutLife(Object sender, RoutedEventArgs e)
		{
			System.Windows.Forms.HelpNavigator nav =
			System.Windows.Forms.HelpNavigator.TopicId;

			System.Windows.Forms.Help.ShowHelp(null, @"avalonlife.chm", nav, "1010");
		}
        
		#endregion

		#region other ui event handlers

		/// <summary>
		/// ALMainWin_OnLoaded()
		/// 
		/// Handles the loaded event for the main window. Initializes the simulation
		/// model, controller, and display grid.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void ALMainWin_OnLoaded(Object sender, RoutedEventArgs e)
		{
			ExecNew(true);
			RunSpeedSlider.ToolTip = Properties.Resources.UI_RunSpeedSlider_ToolTip;
			StatusGenCount.ToolTip = Properties.Resources.UI_StatusGenCount_ToolTip;
			CellBirthCount.ToolTip = Properties.Resources.UI_CellBirthCount_ToolTip;
			CellDeathCount.ToolTip = Properties.Resources.UI_CellDeathCount_ToolTip;
			PopulationCount.ToolTip = Properties.Resources.UI_PopulationCount_ToolTip;
			PeakPopulationCount.ToolTip = Properties.Resources.UI_PeakPopulationCount_ToolTip;
			MenuGridSizeText.ToolTip = Properties.Resources.UI_GridSize_ToolTip;

			_gridAdornerLayer = AdornerLayer.GetAdornerLayer(LifeGrid);
			ReticleAdorner ad = new ReticleAdorner(LifeGrid);
			_gridAdornerLayer.Add(ad);

			MenuSettingsReticle.IsChecked = ALSettings.Default.ReticleOn;
			if ( MenuSettingsReticle.IsChecked )
				ad.Visibility = Visibility.Visible;
			else    
				ad.Visibility = Visibility.Hidden;


			LifeGrid.Drop += new DragEventHandler( Grid_OnDrop );
			LifeGrid.MouseUp += new MouseButtonEventHandler( Grid_OnMouseUp );
			this.MouseLeave += new MouseEventHandler( Window_OnMouseLeave ); 
			this.Closing += new CancelEventHandler( ALMainWin_OnClosing );

			Application.Current.SessionEnding += new SessionEndingCancelEventHandler( ALMainWin_OnClosing );

			LifeGrid.Background = new SolidColorBrush(ALSettings.Default.GridBackground);
			LifeGrid.ShowGridLines = ALSettings.Default.GridOn;
			MenuSettingsGridLines.IsChecked = LifeGrid.ShowGridLines;

			MenuSettingsGridTorus.Tag = GridType.Torus;
			MenuSettingsGridXCyl.Tag = GridType.XCylinder;
			MenuSettingsGridYCyl.Tag = GridType.YCylinder;
			MenuSettingsGridFinite.Tag = GridType.Finite;

			MenuSettingsGrid40x40.Tag = 40;
			MenuSettingsGrid50x50.Tag = 50;
			MenuSettingsGrid60x60.Tag = 60;
			MenuSettingsGrid70x70.Tag = 70;

			if ( ALSettings.Default.HaltOnStability )
				MenuSettingsHaltStable.IsChecked = true;
			else
				MenuSettingsHaltStable.IsChecked = false;
		}
        
		/// <summary>
		/// ALMainWin_OnClosing(Object, CancelEventArgs)
		/// 
		/// This handles the closing event for the main window. If the game is not
		/// dirty or the user saves it using 'Ok' in CheckSave, then this handler
		/// will commit any settings changes and allow the close to continue. Other
		/// wise if the user clicks Cancel in CheckSave it will cancel.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void ALMainWin_OnClosing(Object sender, CancelEventArgs e )
		{
			bool paused = _ls.IsPaused;
			_ls.IsPaused = true;
			if ( CheckSave() == true )
			{
				if ( ALSettings.Default.Changed )
					ALSettings.Default.Save();
			}
			else
			{
				e.Cancel = true;
			}
			_ls.IsPaused = paused;
		}

		/// <summary>
		/// RunButton_OnClick()
		/// 
		/// Handles the click event for the run button on the main window. The button
		/// changes state when it is clicked. The behavior of the sim drives off of
		/// the IsPaused property of _ls (LifeSim). When the sim is paused the UI allows
		/// editing of the grid cells.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void RunButton_OnClick(Object sender, RoutedEventArgs e)
		{
			if ( _ls.IsPaused )
			{
				_ls.IsPaused = false;
				UIStateChange(UIStateChanges.ModelRun);
			}
			else
			{
				_ls.IsPaused = true;
				UIStateChange(UIStateChanges.ModelPaused);
			}
		}

		/// <summary>
		/// Rect_OnMouseDown(Object, MouseButtonEventArgs)
		/// 
		/// Handles the mouse down event on a Rectangle in the grid, and if the game is
		/// in the paused state (editable) flips the cell state. Since we allow dragging
		/// _lastMouseCell is used to avoid the effects of repeated MouseEnter events
		/// getting fired.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Rect_OnMouseDown(Object sender, MouseButtonEventArgs e)
		{
			if ( _ls.IsPaused && (_ls.Generation == 0) )
			{
				LifeCell lc = ((Rectangle)sender).DataContext as LifeCell;
				if ( lc != null )
				{
					_startEdit = true;
					_lastMouseCell = lc;
					lc.IsAlive = !lc.IsAlive;
					UIStateChange(UIStateChanges.ModelCellEdited);
				}
				else throw (new System.InvalidOperationException("Rect_OnMouseDown"));
			}
		}
        
		/// <summary>
		/// Rect_OnMouseEnter(Object, MouseEventArgs)
		/// 
		/// Handles the mouseover event for an Ellipse. If the game is paused and the mouse
		/// is entering a new cell, flip that cell's state.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Rect_OnMouseEnter(Object sender, MouseEventArgs e)
		{
			if ( _startEdit )
			{
				LifeCell lc = ((Rectangle)sender).DataContext as LifeCell;
				if ( lc != null && lc != _lastMouseCell )
				{
					_lastMouseCell = lc;
					lc.IsAlive = !lc.IsAlive;    
				}
				else if ( lc == null )
					throw (new System.InvalidOperationException("Rect_OnMouseEnter"));
			}
		}
        
		/// <summary>
		/// Grid_OnMouseUp(Object, MouseEventArgs)
		/// 
		/// Handles the mouse left button up event for the grid. We use it to check
		/// the grid state and enable/disable menu items accordingly.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Grid_OnMouseUp(Object sender, MouseButtonEventArgs e)
		{
			if ( _startEdit )
			{
				_startEdit = false;
				if ( _ls.Model.IsEmpty() )
				{
					UIStateChange(UIStateChanges.ModelCreated);
				}
			}
		}

		/// <summary>
		/// Window_OnMouseLeave(Object, MouseEventArgs)
		/// 
		/// This function makes sure we handle things correctly if the user drags
		/// the mouse out of the window while drawing cells. Ordinarily you would
		/// capture the mouse and not release it until mouse up, but that's
		/// cumbersome here, because we're getting events at the rectangle level
		/// but can't capture the mouse there. If we capture it at the grid the
		/// rectangles stop getting events. So this is the next best alternative.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Window_OnMouseLeave(Object sender, MouseEventArgs e)
		{
			if ( _startEdit )
			{
				_startEdit = false;
				if (_ls.Model.IsEmpty())
				{
					UIStateChange(UIStateChanges.ModelCreated);
				}
			}    
		}
        
		/// <summary>
		/// Grid_OnDrop(Object, DragEventArgs)
		/// 
		/// This function gets called when an object is dropped on the grid. It handles drops
		/// of .cells data from the Life Lexicon website, as well as .avl and .cells saved files.
		/// The first half of the function detects a drop of a link from the Lexicon, and calls
		/// ExecLoadWeb to get the data from the net. The second half detects file drops, and hands
		/// off to ExecFileLoad. 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Grid_OnDrop(Object sender, DragEventArgs e)
		{
			Cursor old = this.Cursor;
			bool force = this.ForceCursor;
			this.ForceCursor = true;
			this.Cursor = Cursors.Wait;

			if (e.Data.GetDataPresent(DataFormats.Text))
			{
				if ( (e.AllowedEffects & DragDropEffects.Link) == DragDropEffects.Link )
				{    
					string url = e.Data.GetData(DataFormats.Text) as string;
					if ( Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) )
					{
						ExecLoadWeb( url );
					}
				}
			}
			else if (e.Data.GetDataPresent(DataFormats.FileDrop) )
			{
				string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
				ExecLoadFile(files[files.Length - 1]);
			}
			this.Cursor = old;
			this.ForceCursor = force;
		}

		/// <summary>
		/// SimStatusCallback()
		/// 
		/// This function handles a callback from the simulation controller when it detects
		/// that model evolution has halted. Will only be called if ALSettings.HaltOnStability
		/// is true.
		/// </summary>
		/// <returns></returns>
		public void SimStatusCallback()
		{
			string msg = null;

			if ( _ls.Model.EvolutionHalted )
			{
				msg = Properties.Resources.UI_SimStatus_HaltMsg;
				msg += " " + _ls.Generation.ToString();
				msg += "\n\nCell births: " + _ls.Model.CellBirths.ToString();
				msg += "\nCell deaths: " + _ls.Model.CellDeaths.ToString();
				msg += "\nPopulation: " + _ls.Model.Population.ToString();
				msg += "\nPeak population: " + _ls.Model.PeakPopulation.ToString();
				msg += "\n\nThe simulation has been halted.";

				MessageBox.Show(this, msg, "Simulation Status", MessageBoxButton.OK, MessageBoxImage.Information);

				UIStateChange(UIStateChanges.ModelHalted);
			}
		}

		#endregion

		#region ALMainWin private methods

		/// <summary>
		/// UIStateChange(UIStateChanges)
		/// 
		/// This function wraps up all the UI state changes that the program goes through.
		/// The possible states are defined in the AvalonLIfe.UIStateChanges enum. Each
		/// case of the switch statement handles setting the UI controls and some variables
		/// to appropriate settings for a given state.
		/// </summary>
		/// <param name="uis"></param>
		private void UIStateChange(UIStateChanges uis)
		{
			switch (uis)
			{
				case UIStateChanges.ModelCreated:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					RunButton.IsEnabled = false;
					MenuGameSave.IsEnabled = false;
					MenuGameSaveAs.IsEnabled = false;
					MenuGameReset.IsEnabled = false;
					SetGameDirty(false);
					_currentModelFileBase = null;
					break;

				case UIStateChanges.ModelSaved:
				case UIStateChanges.ModelSavedAs:
					MenuGameSave.IsEnabled = false;
					SetGameDirty(false);
					break;

				case UIStateChanges.ModelLoadedFromFile:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = true;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					MenuGameSave.IsEnabled = false;
					MenuGameSaveAs.IsEnabled = true;
					SetGameDirty(false);
					break;

				case UIStateChanges.ModelLoadedFromNet:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = true;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					MenuGameSave.IsEnabled = true;
					MenuGameSaveAs.IsEnabled = true;
					SetGameDirty(true);
					break;

				case UIStateChanges.ModelCellEdited:
					RunButton.IsEnabled = true;
					MenuGameSave.IsEnabled = true;
					MenuGameSaveAs.IsEnabled = true;
					SetGameDirty(true);
					break;

				case UIStateChanges.ModelRun:
					RunButton.Content = Properties.Resources.UI_RunButton_Content2;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip2;
					LifeGrid.Cursor = Cursors.Arrow;
					LifeGrid.ToolTip = null;
					MenuGameReset.IsEnabled = true;
					break;

				case UIStateChanges.ModelPaused:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					break;

				case UIStateChanges.ModelHalted:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = false;
					break;

				case UIStateChanges.ModelReset:
					RunButton.Content = Properties.Resources.UI_RunButton_Content1;
					RunButton.ToolTip = Properties.Resources.UI_RunButton_ToolTip1;
					RunButton.IsEnabled = true;
					LifeGrid.ToolTip = Properties.Resources.UI_LifeGrid_ToolTip;
					LifeGrid.Cursor = Cursors.Hand;
					MenuGameReset.IsEnabled = false;
					break;

				case UIStateChanges.ModelPropertiesEdited:
					MenuGameSave.IsEnabled = true;
					SetGameDirty(true);
					break;
			}
		}

		/// <summary>
		/// UpdateWrapInidicators(bool, bool, bool, bool)
		/// 
		/// This method is called from SetGridType() to enable/disable the wrap indicator
		/// bars that border the grid.
		/// </summary>
		/// <param name="left">True if the left bar is on, else false</param>
		/// <param name="top">True if the top bar is on, else false</param>
		/// <param name="right">True if the right bar is on, else false</param>
		/// <param name="bottom">True if the bottom bar is on, else false</param>
		private void UpdateWrapIndicators( bool left, bool top, bool right, bool bottom )
		{
			if ( left )
				LeftWrapIndicator.Width = 4;
			else
				LeftWrapIndicator.Width = 0;

			if (top)
				TopWrapIndicator.Height = 5;
			else
				TopWrapIndicator.Height = 0;

			if (right)
				RightWrapIndicator.Width = 4;
			else
				RightWrapIndicator.Width = 0;

			if (bottom)
				BottomWrapIndicator.Height = 5;
			else
				BottomWrapIndicator.Height = 0;
		}
        
		/// <summary>
		/// SetGridType( GridType )
		/// 
		/// This method is called from ExecLoad or Menu_OnGridXXX to update the menu and UI to
		/// correspond with the grid type of the loaded model. 
		/// </summary>
		/// <param name="gridType"></param>
		private void SetGridType( GridType gridType )
		{
			MenuSettingsGridTorus.IsChecked = false;
			MenuSettingsGridXCyl.IsChecked = false;
			MenuSettingsGridYCyl.IsChecked = false;
			MenuSettingsGridFinite.IsChecked = false;

			switch ( gridType )
			{
				case GridType.Torus:
					MenuSettingsGridTorus.IsChecked = true;
					UpdateWrapIndicators(false, false, false, false);
					break;

				case GridType.XCylinder:
					MenuSettingsGridXCyl.IsChecked = true;
					UpdateWrapIndicators(false, true, false, true);
					break;

				case GridType.YCylinder:
					MenuSettingsGridYCyl.IsChecked = true;
					UpdateWrapIndicators(true, false, true, false);
					break;

				case GridType.Finite:
					MenuSettingsGridFinite.IsChecked = true;
					UpdateWrapIndicators(true, true, true, true);
					break;
			}
		}
        
		/// <summary>
		/// SetWindowTitle()
		/// 
		/// Called from one or two spots to set the proper window title.
		/// </summary>
		private void SetWindowTitle()
		{
			this.Title = _winTitleBase;
			if (_ls.Model.ModelName != null && _ls.Model.ModelName.Length > 0)
				this.Title += " [" + _ls.Model.ModelName;
			else
				this.Title += " [Untitled";

			if (_gameIsDirty)
				this.Title += "*";

			this.Title += "]";
		}

		/// <summary>
		/// SetGameDirty(bool)
		/// 
		/// Called from various places where the current game becomes "dirty", or in need
		/// of saving to disk. Called with true if the game has become dirty. Updates
		/// the _gameIsDirty flag and window title on a state change.
		/// </summary>
		/// <param name="dirty"></param>
		private void SetGameDirty( bool dirty )
		{
			_gameIsDirty = dirty;
			SetWindowTitle();
		}
        
		/// <summary>
		/// SetGridSizeMenu()
		/// 
		/// Called from InitUIState to update the state of the grid size menu
		/// to reflect the size of a model.
		/// </summary>
		private void SetGridSizeMenu()
		{
			MenuSettingsGrid40x40.IsChecked = false;
			MenuSettingsGrid50x50.IsChecked = false;
			MenuSettingsGrid60x60.IsChecked = false;
			MenuSettingsGrid70x70.IsChecked = false;
			MenuSettingsGridCustom.IsChecked = false;

			if ((_ls.Model.Columns != _ls.Model.Rows) ||
			(_ls.Model.Columns != 40 && _ls.Model.Columns != 50 && 
			_ls.Model.Columns != 60 && _ls.Model.Columns != 70))
			{
				MenuSettingsGridCustom.IsChecked = true;
			}
			else
			{
				switch (_ls.Model.Rows)
				{
					case 40:
						MenuSettingsGrid40x40.IsChecked = true;
						break;
					case 50:
						MenuSettingsGrid50x50.IsChecked = true;
						break;
					case 60:
						MenuSettingsGrid60x60.IsChecked = true;
						break;
					case 70:
						MenuSettingsGrid70x70.IsChecked = true;
						break;
				}
			}
			MenuGridSizeText.Text = "r:" + _ls.Model.Rows.ToString() + " c:" + _ls.Model.Columns.ToString();
		}
        
		/// <summary>
		/// InitUIState()
		/// 
		/// This function peforms common setup work when a game is created or loaded.
		/// Initializes the grid and populates it, sets up some data contexts, and sets
		/// the window title.
		/// </summary>
		private void InitUIState()
		{
			InitGrid();
			PopulateGrid();
			ApplyRectStyle();
			SetGridSizeMenu();
			SetWindowTitle();
			SetGridType(_ls.Model.LifeGridType);

			StatusGenCount.DataContext = _ls;
			RunSpeedSlider.DataContext = _ls;
			CellBirthCount.DataContext = _ls.Model;
			CellDeathCount.DataContext = _ls.Model;
			PopulationCount.DataContext = _ls.Model;
			PeakPopulationCount.DataContext = _ls.Model;
		}

		/// <summary>
		/// ExecNew()
		/// 
		/// Does the grunt work of initializing a new game with an empty grid. Initializes
		/// the model and controller, populates the grid, and wires up some UI fields by setting
		/// data contexts for items with property bindings.
		/// </summary>
		private bool ExecNew(bool defaults)
		{
			bool result = false;

			if ( CheckSave() == true )
			{
				if ( !defaults)
				{
					ALModelSizeDlg dlg = new ALModelSizeDlg();
					dlg.Owner = this;
					dlg.Tag = _ls.Model;
					if ( dlg.ShowDialog() == true )
					{
						int rows = dlg.Rows;
						int cols = dlg.Columns;
						if ( rows * cols > 10000 )
						{
							string msg = PrepMessage(Properties.Resources.UI_MB_LargeModelMsg, 80);
							msg += "\n\nRequested model size will cause the program to create approximately " 
							+ Convert.ToString((rows * cols) * 3) + " objects.";
							if ( MessageBox.Show(this, msg, Properties.Resources.UI_MB_LargeModelCaption, 
								MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.Cancel )
							{    
								return result;
							}
						}
						if (_ls == null)
						{
							_ls = new LifeSim(rows, cols);
							_ls.UICallback = SimStatusCallback;
						}
						else
							_ls.NewModel(rows, cols);
					}
					else return result;
				}
				else
				{
					if (_ls == null)
					{
						_ls = new LifeSim();
						_ls.UICallback = SimStatusCallback;
					}
					else
						_ls.NewModel();
				}
				InitUIState();
				UIStateChange(UIStateChanges.ModelCreated);
				result = true;
			}
			return result;
		}
        
		/// <summary>
		/// ExecLoadWeb(string)
		/// 
		/// This function handles loading a stream of cells from the Life Lexicon website.
		/// Most of the work is actually done in LifeModel.LifeModel(Stream), which is called
		/// from LifeSim.NewModel(Stream).
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		private bool ExecLoadWeb( string url )
		{
			bool result = false;
			if (CheckSave() == true)
			{
				Uri uri = new Uri(url);

				char[] sep = { '.' };
				string[] splitstr = uri.LocalPath.Split(sep);
				if (string.Compare("cells", splitstr[splitstr.Length - 1], true) == 0)
				{
					HttpWebRequest cellReq = (HttpWebRequest)HttpWebRequest.Create(uri);
					cellReq.Timeout = 10000;
					cellReq.UserAgent = "AvalonLife_1_0";
					try
					{
						HttpWebResponse cellRes = (HttpWebResponse)cellReq.GetResponse();
						if ( _ls.NewModel(cellRes.GetResponseStream()) )
						{
							InitUIState();
							UIStateChange(UIStateChanges.ModelLoadedFromNet);
							_currentModelFileType = ALFileType.None;
							result = true;
						}
						else
							MessageBox.Show(this, "Failed to load model data.", "Load Error",
								MessageBoxButton.OK, MessageBoxImage.Error);

						cellRes.Close();
					}
					catch (WebException wex)
					{
						string msg = "Failed to retrieve cell data. Response: ";
						msg += wex.Response;
						MessageBox.Show(this, msg, "Network Error",
							MessageBoxButton.OK, MessageBoxImage.Error);
					}
				}
				else
				{
					string msg = "Invalid data type: " + url;
					MessageBox.Show(this, msg, "Invalid Data",
						MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}
			return result;
		}
       
		/// <summary>
		/// ExecLoadFile(string)
		/// 
		/// Does the work of loading a game from a file. Handles saved .avl files as
		/// well as .cells files saved from Life Lexicon data.
		/// </summary>
		/// <returns></returns>
		private bool ExecLoadFile(string fileName)
		{
			bool result = false;

			if (CheckSave() == true)
			{
				ALFileType ft = GetFileType(fileName);
				if ( ft == ALFileType.AVL )
				{
					BinaryFormatter formatter = new BinaryFormatter();
					Stream str = File.OpenRead(fileName);
					try
					{
						_ls = formatter.Deserialize(str) as LifeSim;
						_ls.UICallback = SimStatusCallback;
						SetFileType(fileName);
						InitUIState();
						UIStateChange(UIStateChanges.ModelLoadedFromFile);
						result = true;
					}
					catch (System.Runtime.Serialization.SerializationException ex)
					{
						MessageBox.Show(this, Properties.Resources.UI_MB_LoadFailedMsg + " " + ex.Message,
							Properties.Resources.UI_MB_LoadFailedCaption,
							MessageBoxButton.OK, MessageBoxImage.Error);
					}
					finally
					{
						str.Close();
					}
					SetGameDirty(false);
				}
				else if ( ft == ALFileType.Cells )
				{
					Stream str = File.OpenRead(fileName);
					if ( _ls.NewModel(str) )
					{
						SetFileType(fileName);
						InitUIState();
						UIStateChange(UIStateChanges.ModelLoadedFromFile);
						result = true;
					}
					else
						MessageBox.Show(this, "Failed to load model data.", "Load Error",
							MessageBoxButton.OK, MessageBoxImage.Error);
					str.Close();
				}
				else
				{
					string msg = "Invalid file type: " + fileName;
					MessageBox.Show(this, msg, "Invalid File",
					MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}                      
			return result;
		}
        
		/// <summary>
		/// ExecSave()
		/// 
		/// Called from the menu savegame handler, or from the load game handler
		/// if necessary. Saves the current model to disk.
		/// </summary>
		private void ExecSave( bool saveAs )
		{
			System.Windows.Forms.SaveFileDialog savedlg =
				new System.Windows.Forms.SaveFileDialog();

			savedlg.AddExtension = true;
			savedlg.Filter = "AvalonLife Saved Games (.avl)|*.avl|Life Lexicon Cells (.cells)|*.cells";
			bool haveFile = false;

			if (_currentModelFileBase != null && _currentModelFileType != ALFileType.None)
			{
				haveFile = true;
				savedlg.FileName = _currentModelFileBase;
				if ( _currentModelFileType == ALFileType.Cells )
				{
					savedlg.DefaultExt = ".cells";
					savedlg.FilterIndex = 2;
				}
				else if ( _currentModelFileType == ALFileType.AVL )
				{    
					savedlg.DefaultExt = ".avl";
					savedlg.FilterIndex = 1;
				}
			}
			else
			{
				savedlg.FileName = "AvalonLife Saved Game";
				savedlg.DefaultExt = ".avl";
				savedlg.FilterIndex = 1;
			}

			if ( saveAs == true )
				savedlg.Title = "Save Model As";
			else
				savedlg.Title = "Save Model";

			if (savedlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{

				ALFileType ft = GetFileType(savedlg.FileName);

				if ( ft == ALFileType.AVL )
				{
					BinaryFormatter formatter = new BinaryFormatter();
					Stream str = File.OpenWrite(savedlg.FileName);
					try
					{
						formatter.Serialize(str, _ls);
						if ( !saveAs || !haveFile )
							SetFileType(savedlg.FileName);
						UIStateChange(UIStateChanges.ModelSaved);
					}
					catch (System.Exception ex)
					{
						MessageBox.Show(this, Properties.Resources.UI_MB_SaveFailedMsg + " " + ex.InnerException.Message,
						Properties.Resources.UI_MB_SaveFailedCaption,
						MessageBoxButton.OK, MessageBoxImage.Error);
					}
					finally
					{
						str.Close();
					}
				}
				else if ( ft == ALFileType.Cells )
				{
					Stream str = File.OpenWrite(savedlg.FileName);
					try
					{
						_ls.Model.StreamCells(str);
						if (!saveAs || !haveFile)
							SetFileType(savedlg.FileName);
						UIStateChange(UIStateChanges.ModelSaved);
					}
					catch (System.Exception)
					{
					}
					finally
					{
						str.Close();
					}
				}
				else
				{
					string msg = "Invalid file type: " + savedlg.FileName;
					MessageBox.Show(this, msg, "Invalid File",
						MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}
		}
        
		/// <summary>
		/// CheckSave()
		/// 
		/// This utility function checks to see if the current game is dirty. If it is
		/// it asks the user whether they want to save the game before continuing with
		/// whatever operation called this function. Returns true if the operation is
		/// good to proceed, false if the user clicks cancel.
		/// </summary>
		/// <returns></returns>
		private bool CheckSave()
		{
			bool result = true;

			if (_gameIsDirty)
			{
				MessageBoxResult mbres = MessageBox.Show( this, Properties.Resources.UI_MB_PromptText3, Properties.Resources.UI_MB_CaptionText3,
				MessageBoxButton.YesNoCancel, MessageBoxImage.Question );
				if ( mbres == MessageBoxResult.Yes )
					ExecSave(false);
				else if ( mbres == MessageBoxResult.No )
					SetGameDirty(false);
				else if ( mbres == MessageBoxResult.Cancel )
					result = false;
			}
			return result;
		}
        
		/// <summary>
		/// InitGrid()
		/// 
		/// Called from the OnLoaded event handler for the main window to initialize the
		/// UI display grid with the appropriate number of rows and columns based on the
		/// _lm.Rows and _lm.Columns properties. It then adds an ellipse to each cell and
		/// sets its style.
		/// </summary>
		private void InitGrid()
		{
			LifeGrid.Children.Clear();
			LifeGrid.RowDefinitions.Clear();
			LifeGrid.ColumnDefinitions.Clear();

			for (int i = 0; i < _ls.Model.Rows; i++)
			{
				LifeGrid.RowDefinitions.Add(new RowDefinition());
			}
			for (int i = 0; i < _ls.Model.Columns; i++)
			{
				LifeGrid.ColumnDefinitions.Add(new ColumnDefinition());
			}
		}
        
/// <summary>
/// GetCellBrush()
/// 
/// Constructs a brush from the settings in the config file.
/// </summary>
/// <returns></returns>
private Brush GetCellBrush()
{
	CellBrushType brushType = ALSettings.Default.LifeCellBrushType;

	if ( brushType == CellBrushType.Radial )
	{
		RadialGradientBrush brush = new RadialGradientBrush();
		brush.GradientOrigin = new Point(0.5, 0.5);
		brush.RadiusX = 0.5; 
		brush.RadiusY = 0.5;

		brush.GradientStops.Add( new GradientStop(ALSettings.Default.CellBrushC1, ALSettings.Default.CellBrushC1Off) );
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC2, ALSettings.Default.CellBrushC2Off));
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC3, ALSettings.Default.CellBrushC3Off));

		brush.Freeze();

		return brush;
	}
	else if ( brushType == CellBrushType.Linear )
	{
		LinearGradientBrush brush = new LinearGradientBrush();
		brush.StartPoint = new Point(0, 0);
		brush.EndPoint = new Point(1, 1);

		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC1, ALSettings.Default.CellBrushC1Off));
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC2, ALSettings.Default.CellBrushC2Off));
		brush.GradientStops.Add(new GradientStop(ALSettings.Default.CellBrushC3, ALSettings.Default.CellBrushC3Off));

		brush.Freeze();

		return brush;
	}
	else if ( brushType == CellBrushType.Solid )
	{
		SolidColorBrush brush = new SolidColorBrush(ALSettings.Default.CellBrushC1);
		brush.Freeze();

		return brush;
	}
	else
	{    
		SolidColorBrush brush = new SolidColorBrush(Colors.Red);
		brush.Freeze();

		return brush;
	}
}
        
		/// <summary>
		/// ApplyRectStyle()
		/// 
		/// If you look at the source you'll see that this function is only called when a new
		/// grid is being initialized, and when the cell brush has been changed. It builds a new
		/// style that sets the Fill property of a rectangle to the new brush, and then goes
		/// through the children of the grid setting this style. The style is based on an
		/// existing style that binds the opacity property to govern visibility. So why go to
		/// all this trouble to change fill brushes? Why not just set the fill property on the
		/// rects and be done with it? Here's the issue: again, opacity is controlled by a
		/// binding in a style. Element properties override style settings, and they happen at
		/// different times too. If in the process of creating a new grid I set the rectangle
		/// DataContext to point to a LifeCell, which will drive the opacity binding, and then
		/// set the Fill property directly, sometimes, depending on timing, I get a repaint
		/// before the opacity property is correctly set, and the grid renders all the cells
		/// visible. It looks messy, and I don't want the grid repainted until the state of all
		/// the cells is correct. I'm sure there must be other ways to handle suppressing the
		/// repaint, but the issue there is that the flash happens after I return control to
		/// the message pump. If I somehow surpress the repaint when will I unsurpress it? The
		/// best way around this that I have found so far is to do as I have below: change
		/// brushes by building a new style and then applying that style.
		/// </summary>
		private void ApplyRectStyle()
		{
			Brush cellBrush = GetCellBrush();

			Style style = new Style(typeof(Rectangle), (Style)LifeGrid.FindResource(typeof(Rectangle)));
			Setter setter = new Setter();
			setter.Property = Rectangle.FillProperty;
			setter.Value = cellBrush;
			style.Setters.Add(setter);
			LifeGrid.Resources.Remove("RectStyle");
			LifeGrid.Resources.Add("RectStyle", style);

			UIElementCollection rects = LifeGrid.Children;

			foreach (UIElement uie in rects)
				((Rectangle)uie).Style = (Style)(LifeGrid.Resources["RectStyle"]);
		}
        
		/// <summary>
		/// PopulateGrid()
		/// 
		/// Does the work of setting up the rectangles in the cells of the life grid. Creates
		/// the rectangles and assigns them to the grid, adds them to the child collection,
		/// sets up rectangle data contexts for the rect->cell link, sets the rectangle style,
		/// and wires up the rectangle mouse events
		/// </summary>
		private void PopulateGrid()
		{
			for (int row = 0; row < _ls.Model.Rows; row++)
			{
				for (int col = 0; col < _ls.Model.Columns; col++)
				{
					Rectangle rect = new Rectangle();
					Grid.SetRow(rect, row);
					Grid.SetColumn(rect, col);
					LifeGrid.Children.Add(rect);
					rect.DataContext = _ls.Model.CellGrid[row, col];
					rect.MouseDown += new MouseButtonEventHandler(Rect_OnMouseDown);
					rect.MouseMove += new MouseEventHandler(Rect_OnMouseEnter);
				}
			}
		}

		/// <summary>
		/// GetFileType(string)
		/// 
		/// Called from ExecSave to determine the type of file that the user selected.
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		private ALFileType GetFileType( string fileName )
		{
			string ext = System.IO.Path.GetExtension(fileName);
			ALFileType ft = ALFileType.None;

			if (string.Compare(".avl", ext, true) == 0)
				ft = ALFileType.AVL;
			else if (string.Compare(".cells", ext, true) == 0)
				ft = ALFileType.Cells;

			return ft;
		}
        
		/// <summary>
		/// SetFileType( string )
		/// 
		/// Called from the ExecSave and ExecLoad functions. Retrieves and stores
		/// the base filename and sets the file format type.
		/// </summary>
		/// <param name="fileName"></param>
		private ALFileType SetFileType( string fileName )
		{
			_currentModelFileBase = System.IO.Path.GetFileNameWithoutExtension(fileName);
			string ext = System.IO.Path.GetExtension(fileName);

			if ( string.Compare(".avl", ext, true) == 0 )
				_currentModelFileType = ALFileType.AVL;
			else if ( string.Compare(".cells", ext, true) == 0 )
				_currentModelFileType = ALFileType.Cells;
			else 
				_currentModelFileType = ALFileType.None;

			return _currentModelFileType;
		}

		#endregion
        
		/// <summary>
		/// Holds the instance of the simulation controller
		/// </summary>
		private LifeSim _ls = null;

		/// <summary>
		/// True if the game has been modified since last save
		/// </summary>
		private bool _gameIsDirty = false;

		/// <summary>
		/// Tracks the last cell that the mouse was in
		/// </summary>
		private LifeCell _lastMouseCell = null;

		/// <summary>
		/// Container for the ReticleAdorner
		/// </summary>
		private AdornerLayer _gridAdornerLayer = null;

		/// <summary>
		/// True if we are dragging across cells in edit mode
		/// </summary>
		private bool _startEdit = false;

		/// <summary>
		/// If not null contains the last file name used to load or
		/// save the current model.
		/// </summary>
		private string _currentModelFileBase = null;

		private ALFileType _currentModelFileType = ALFileType.AVL;

		/// <summary>
		/// Self-explanatory
		/// </summary>
		private string _winTitleBase = "AvalonLife 1.0";
    }
}

AvalonLife Listing 3 – ALMainWin.xaml

<Window x:Class="AvalonLife.ALMainWin"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="AvalonLife 1.0" Height="600" Width="600" Name="ALMainWindow" Background="#FFFFFFFF"
	Loaded="ALMainWin_OnLoaded" >
	<Window.Resources>
		<Style x:Key="RunSpeedSliderStyle" TargetType="{x:Type Slider}">
			<Setter Property="Width" Value="120" />
			<Setter Property="Value" Value="{Binding Path=TimerInterval, Mode=TwoWay}" />
			<Setter Property="Orientation" Value="Horizontal" />
			<Setter Property="HorizontalAlignment" Value="Left" />
			<Setter Property="IsSnapToTickEnabled" Value="True" />
			<Setter Property="Minimum" Value="100" />
			<Setter Property="Maximum" Value="1000" />
			<Setter Property="TickPlacement" Value="BottomRight" />
			<Setter Property="TickFrequency" Value="100" />
			<Setter Property="IsDirectionReversed" Value="True" />
			<Setter Property="IsMoveToPointEnabled" Value="True" />
		</Style>
		<Style x:Key="GenCountStyle" TargetType="{x:Type TextBlock}">
			<Setter Property="Foreground" Value="Blue" />
			<Setter Property="Text" Value="{Binding Path=Generation}" />
		</Style>
		<Style x:Key="BirthCountStyle" TargetType="{x:Type TextBlock}">
			<Setter Property="Foreground" Value="Blue" />
			<Setter Property="Text" Value="{Binding Path=CellBirths}" />
		</Style>
		<Style x:Key="DeathCountStyle" TargetType="{x:Type TextBlock}">
			<Setter Property="Foreground" Value="Blue" />
			<Setter Property="Text" Value="{Binding Path=CellDeaths}" />
		</Style>
		<Style x:Key="PopulationCountStyle" TargetType="{x:Type TextBlock}">
			<Setter Property="Foreground" Value="Blue" />
			<Setter Property="Text" Value="{Binding Path=Population}" />
		</Style>
		<Style x:Key="PeakPopulationCountStyle" TargetType="{x:Type TextBlock}">
			<Setter Property="Foreground" Value="Blue" />
			<Setter Property="Text" Value="{Binding Path=PeakPopulation}" />
		</Style>
	</Window.Resources>
	<Grid>
		<DockPanel>
			<DockPanel DockPanel.Dock="Top" LastChildFill="False"  
				Background="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}">
				<Menu Margin="0,5,5,2" DockPanel.Dock="Left" Width="Auto" HorizontalAlignment="Left" 
					Background="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}" >
						<MenuItem Header="Game">
						<MenuItem Name="MenuGameNew" Header="New" Click="Menu_OnGameNew" />
						<MenuItem Name="MenuGameReset" Header="Reset" Click="Menu_OnGameReset" />
						<MenuItem Name="MenuGameSave" Header="Save..." Click="Menu_OnGameSave" />
						<MenuItem Name="MenuGameSaveAs" Header="Save as..." Click="Menu_OnGameSaveAs" />
						<MenuItem Name="MenuGameLoad" Header="Load..." Click="Menu_OnGameLoad" />
						<Separator />
						<MenuItem Header="Exit" Click="Menu_OnGameExit" />
					</MenuItem>
					<MenuItem Header="Settings">
						<MenuItem Name="MenuSettingsGridLines" Header="Show Grid" Click="Menu_OnSettingsGridLines" />
						<MenuItem Name="MenuSettingsReticle" Header="Show Reticle" Click="Menu_OnSettingsReticle" />
						<MenuItem Name="MenuSettingsHaltStable" Header="Halt Stable Model" Click="Menu_OnSettingsHaltStable" />
						<Separator />
						<MenuItem Name="MenuSettingsGridBkgColor" Header="Grid Background..." Click="Menu_OnSettingsGridBackground" />
						<MenuItem Name="MenuSettingsReticleColor" Header="Reticle Color..." Click="Menu_OnSettingsReticleColor" />
						<MenuItem Name="MenuSettingsCellBrush" Header="Cell Color..." Click="Menu_OnSettingsCellBrush" />
						<Separator />
						<MenuItem Header="Grid Type">
							<MenuItem Name="MenuSettingsGridTorus" Header="Torus" Click="Menu_OnSettingsGridType" />
							<MenuItem Name="MenuSettingsGridXCyl" Header="X Cylinder" Click="Menu_OnSettingsGridType" />
							<MenuItem Name="MenuSettingsGridYCyl" Header="Y Cylinder" Click="Menu_OnSettingsGridType" />
							<MenuItem Name="MenuSettingsGridFinite" Header="Finite" Click="Menu_OnSettingsGridType" />
						</MenuItem>
						<MenuItem Header="Grid Size">
							<MenuItem Name="MenuSettingsGrid40x40" Header="40 x 40" Click="Menu_OnSettingsGridSize" />
							<MenuItem Name="MenuSettingsGrid50x50" Header="50 x 50" Click="Menu_OnSettingsGridSize" />
							<MenuItem Name="MenuSettingsGrid60x60" Header="60 x 60" Click="Menu_OnSettingsGridSize" />
							<MenuItem Name="MenuSettingsGrid70x70" Header="70 x 70" Click="Menu_OnSettingsGridSize" />
							<Separator />
							<MenuItem Name="MenuSettingsGridCustom" Header="Custom..." Click="Menu_OnSettingsGridSizeCustom" />
							<Separator />
							<MenuItem Name="MenuSettingsGridShrink" Header="Shrink to Model" Click="Menu_OnSettingsGridSizeShrink" />
						</MenuItem>
						<MenuItem Name="MenuSettingsGridSettings" Header="Grid Settings..." Click="Menu_OnSettingsGridSettings" />
						<Separator />
						<MenuItem Name="MenuSettingsModelName" Header="Model Name..." Click="Menu_OnSettingsModelName" />
					</MenuItem>
					<MenuItem Header="Help">
						<MenuItem Name="MenuHelpHowTo" Header="How to Play..." Click="Menu_OnHelpHowTo" />
						<MenuItem Name="MenuHelpAbout" Header="About AvalonLife..." Click="Menu_OnHelpAbout" />
						<MenuItem Name="MenuHelpAboutLife" Header="About the Game of Life..." Click="Menu_OnHelpAboutLife" />
					</MenuItem>
				</Menu>
				<Button Margin="10,5,5,2" DockPanel.Dock="Right" Name="RunButton" VerticalAlignment="Center" HorizontalAlignment="Right" 
					Click="RunButton_OnClick" Height="20" Width="60" Content="Run" />
				<TextBlock Margin="0,5,5,2" VerticalAlignment="Center" Width="60" Foreground="Blue" Name="MenuGridSizeText" DockPanel.Dock="Right" />
				<TextBlock Margin="0,5,5,2" VerticalAlignment="Center" Width="50" Text="Grid Size:" DockPanel.Dock="Right" />
			</DockPanel>
			<Canvas Name="TopWrapIndicator" Height="5" Width="Auto" DockPanel.Dock="Top" HorizontalAlignment="Stretch" Background="Gray" />
			<StatusBar Background="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}" 
				Height="30" DockPanel.Dock="Bottom" Padding="4,0,4,0">
				<StatusBarItem>
					<TextBlock Width="Auto" Text="Time:" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Name="StatusGenCount" Width="30" Style="{StaticResource GenCountStyle}" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Width="Auto" Text="Census:" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Name="PopulationCount" Width="30" Style="{StaticResource PopulationCountStyle}" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Width="Auto" Text="Peak:" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Name="PeakPopulationCount" Width="30" Style="{StaticResource PeakPopulationCountStyle}" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Width="Auto" Text="Born:" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Name="CellBirthCount" Width="40" Style="{StaticResource BirthCountStyle}" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Width="Auto" Text="Died:" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Name="CellDeathCount" Width="40" Style="{StaticResource DeathCountStyle}" />
				</StatusBarItem>
				<StatusBarItem>
					<TextBlock Width="Auto" Padding="10,0,0,0">Speed:</TextBlock>
				</StatusBarItem>
				<StatusBarItem>
					<Slider Name="RunSpeedSlider" Style="{StaticResource RunSpeedSliderStyle}" />
				</StatusBarItem>
			</StatusBar>
			<Canvas Name="BottomWrapIndicator" Height="5" Width="Auto" DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" Background="Gray" />
			<Canvas Name="LeftWrapIndicator" Height="Auto" Width="4" DockPanel.Dock="Left" VerticalAlignment="Stretch" Background="Gray" />
			<Canvas Name="RightWrapIndicator" Height="Auto" Width="4" DockPanel.Dock="Right" VerticalAlignment="Stretch" Background="Gray" />
			<Grid Name="LifeGrid" Background="White" ForceCursor="True" AllowDrop="True" >
				<Grid.Resources>
					<Style TargetType="{x:Type Rectangle}">
						<Setter Property="Opacity" Value="{Binding Path=IsAlive}" />
					</Style>
					<Style BasedOn="{StaticResource {x:Type Rectangle}}" TargetType="{x:Type Rectangle}" x:Key="RectStyle" >
						<Setter Property="Fill" Value="Red" />
					</Style>
				</Grid.Resources>
			</Grid>
		</DockPanel>
	</Grid>
</Window>

AvalonLife Listing 2 – LifeSim.cs

using System;
using System.IO;
using System.ComponentModel;
using System.Windows;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace AvalonLife
{
	[Serializable]
	class LifeSim : INotifyPropertyChanged, ISerializable
	{
		#region LifeSim public interface
		/// <summary>
		/// LifeSim()
		/// 
		/// Constructs an instance of the sim controller and creates an empty model
		/// for it to run. LifeSim is in the paused state after construction, but the
		/// timer is running.
		/// </summary>
		/// <param name="lm"></param>
		public LifeSim()
		{
			_lm = new LifeModel();
			_timerInterval = ALSettings.Default.TimerInterval;
			_timer = new System.Windows.Forms.Timer();
			_timer.Interval = _timerInterval;
			_timer.Tick += new EventHandler(TimerEvent);
			_timer.Tag = this;
			_timer.Start();
		}

		/// <summary>
		/// LifeSim(int, int)
		/// 
		/// Constructs an instance of the sim controller and creates an empty model
		/// for it to run, using the specified dimensions. LifeSim is in the paused
		/// state after construction, but the timer is running.
		/// </summary>
		/// <param name="rows">Height of the requested model grid</param>
		/// <param name="columns">Width of the requested model grid</param>
		public LifeSim(int rows, int columns)
		{
			_lm = new LifeModel(rows, columns);
			_timerInterval = ALSettings.Default.TimerInterval;
			_timer = new System.Windows.Forms.Timer();
			_timer.Interval = _timerInterval;
			_timer.Tick += new EventHandler(TimerEvent);
			_timer.Tag = this;
			_timer.Start();
		}

        #region ISerializable methods
        
		/// <summary>
		/// LifeSim(SerializationInfo, StreamingContext)
		/// 
		/// Called by the BinaryFormatter to construct an instance of LifeSim from a
		/// stream. Deserializes the private members, then the LifeModel deserialization
		/// is called, and finally the timer is created and started. The sim is constructed
		/// in a paused state. The UI is responsible for wiring up events.
		/// </summary>
		/// <param name="info"></param>
		/// <param name="ctxt"></param>
		public LifeSim( SerializationInfo info, StreamingContext ctxt )
		{
			_generation = (int)info.GetValue( "_generation", typeof(int) );
			_timerInterval = (int)info.GetValue( "_timerInterval", typeof(int) );

			_lm = new LifeModel( info, ctxt );

			_isPaused = true;

			_timer = new System.Windows.Forms.Timer();
			_timer.Interval = _timerInterval;
			_timer.Tick += new EventHandler(TimerEvent);
			_timer.Tag = this;
			_timer.Start();
		}
        
		/// <summary>
		/// GetObjectData(SerializationInfo, StreamingContext)
		/// 
		/// Called by the BinaryFormatter to serialize an instance of LifeSim. Serializes
		/// the private members and then calls the LifeModel serialization method directly.
		/// </summary>
		/// <param name="info"></param>
		/// <param name="ctxt"></param>
		public void GetObjectData( SerializationInfo info, StreamingContext ctxt )
		{
			info.AddValue( "_generation", _generation );
			info.AddValue( "_timerInterval", _timerInterval );

			_lm.GetObjectData( info, ctxt );
		}

		#endregion
        
		/// <summary>
		/// ~LifeSim()
		/// 
		/// I haven't verified it, but this is almost certainly unnecessary. The timer
		/// class dispose() should kill the timer and clean up. Anyway no harm done.
		/// </summary>
		~LifeSim()
		{
			if ( _timer != null )
				_timer.Stop();
		}
        
		/// <summary>
		/// TimerEvent(Object, EventArgs)
		/// 
		/// This function handles the timer tick. If the game is in the unpaused state it calls
		/// the LifeModel.Evaluate() method to iterate the model. It then increments the
		/// generation count. It's a static method so we pass in the 'this' pointer for the
		/// LifeSim instance that owns the timer in its Tag property. Useful little things, Tags.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private static void TimerEvent( Object sender, EventArgs e )
		{
			LifeSim ls = ((System.Windows.Forms.Timer)sender).Tag as LifeSim;
			if ( ls != null && !ls.IsPaused )
			{
				if ( !ls._lm.EvolutionHalted || ALSettings.Default.HaltOnStability == false )
				{
					ls._lm.Evaluate();
					ls.Generation++;
				}
				else 
				{
					ls.IsPaused = true;
					if (ls._uiCallback != null)
					{    
						ls._uiCallback();
					}
				} 
			}
			else if ( ls == null )
				throw( new System.InvalidOperationException("TimerEvent") );
		}
        
		/// <summary>
		/// ResetSim()
		/// 
		/// Called from the UI to reset the simulation to its starting condition. On
		/// exit the simulation is paused.
		/// </summary>
		public void ResetSim()
		{
			if ( !_isPaused )
				IsPaused = true;

			Generation = 0;

			_lm.ResetModel();
		}

		/// <summary>
		/// NewModel()
		/// 
		/// Called from the UI during handling of a click on Game | New.
		/// </summary>
		public void NewModel()
		{
			if ( !_isPaused )
				_isPaused = true;

			_lm = new LifeModel();
			Generation = 0;
		}

		/// <summary>
		/// NewModel(int, int)
		/// 
		/// Called from the UI during handling of a click on Game | New. Creates a
		/// new model using the passed in dimensions.
		/// </summary>
		public void NewModel(int rows, int columns)
		{
			if (!_isPaused)
				_isPaused = true;

			_lm = new LifeModel(rows, columns);
			Generation = 0;
		}

		/// <summary>
		/// NewModel(Stream)
		/// 
		/// Called to decode a stream of .cells format data into a LifeModel. The bulk
		/// of the work is done in the LifeModel.LifeModel(Stream) constructor. If
		/// construction fails we will already have warned the user in the constructor,
		/// so all we do here is restore the paused state we had on entry.
		/// </summary>
		/// <param name="str">Stream containing the .cells formated data</param>
		/// <returns></returns>
		public bool NewModel( Stream str )
		{
			bool result = false;
			bool paused = _isPaused;

			_isPaused = true;

			try
			{
				LifeModel lm = new LifeModel( str );
				_lm = lm;
				Generation = 0;
				result = true;
			}
			catch( System.Exception )
			{
				_isPaused = paused;
			}    
			return result;
		}

		#endregion

		#region INotifyPropertyChanged members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion

		#region LifeSim public properties
		/// <summary>
		/// Counts the generations that the current model has run
		/// </summary>
		private int _generation = 0;
		public int Generation
		{
			get
			{
				return _generation;
			}
			protected set
			{
				_generation = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new PropertyChangedEventArgs("Generation"));
			}
		}
        
		/// <summary>
		/// Holds a reference to the simulation model
		/// </summary>
		private LifeModel _lm = null;
		public LifeModel Model
		{
			get
			{
				return _lm;
			}
		}

		/// <summary>
		/// Holds the timer interval, set to default on start
		/// </summary>
		private int _timerInterval = 0;
		public int TimerInterval
		{
			get
			{
				return _timerInterval;
			}
			set
			{
				_timerInterval = value;
				_timer.Interval = _timerInterval;
				if (PropertyChanged != null)
					PropertyChanged(this, new PropertyChangedEventArgs("TimerInterval"));
			}
		}
        
		/// <summary>
		/// Holds the simulation run state: false if running, true if paused. 
		/// </summary>
		private bool _isPaused = true;
		public bool IsPaused
		{
			get
			{
				return _isPaused;
			}
			set
			{
				_isPaused = value;
			}
		}

		/// <summary>
		/// The timer handler may detect that the model has ceased evolving,
		/// i.e. entered a stable state. In that case it will make a call to
		/// the function in this delegate to inform the UI. The UI is responsible
		/// for setting a function-typed value to this delegate member if it
		/// wants to receive this callback.
		/// </summary>
		public delegate void UISimStatusCallback();

		private UISimStatusCallback _uiCallback = null;
		public UISimStatusCallback UICallback
		{
			get
			{
				return _uiCallback;
			}
			set
			{
				_uiCallback = value;
			}
		}

		#endregion
        
		#region LifeSim private data

		private System.Windows.Forms.Timer _timer = null;

		#endregion
	}
}

AvalonLife Listing 1 – LifeModel.cs

using System;
using System.IO;
using System.Windows.Media;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Runtime.Serialization;

namespace AvalonLife
{
	/// <summary>
	/// GridType
	/// 
	/// This enumeration is used to specify the type of grid bounding that
	/// the model will use:
	/// 
	/// Torus - the cells on all edges of the grid wrap to the opposite edge.
	/// XCylinder - the cells on the x axis edges wrap, the y-axis is finite.
	/// YCylinder - the cells on the y axis edges wrap, the x-axis is finite.
	/// Finite - all four edges of the grid are finite.
	/// 
	/// </summary>
	enum GridType
	{
		Torus,
		XCylinder,
		YCylinder,
		Finite
	};
    
	/// <summary>
	/// I just wanted a simple rectangle class with four numbers in it, to use in
	/// resizing the model grid (I use it as return type from a function that
	/// calculates the extent of the current model).
	/// </summary>
	struct ALRectangle
	{
		public int Left;
		public int Top;
		public int Right;
		public int Bottom;
	}
    
	/// <summary>
	/// The LifeCell class represents a single cell and its live/dead state
	/// </summary>
	class LifeCell : INotifyPropertyChanged
	{
		#region LifeCell public properties


		private bool _isAlive = false;
		public bool IsAlive
		{
			get
			{
				return _isAlive;
			}
			set
			{
				_isAlive = value;
				if ( PropertyChanged != null )
					PropertyChanged( this, new PropertyChangedEventArgs("IsAlive") );
			}
		}
		#endregion
        
		#region INotifyPropertyChanged members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion 
	}
    
	/// <summary>
	/// The LifeModel class represents a grid of life cells
	/// </summary>
	[Serializable]
	class LifeModel : ISerializable, INotifyPropertyChanged
	{
		#region LifeModel public interface

		/// <summary>
		/// LifeModel()
		/// 
		/// Initializes the LifeCell array with the default bounds
		/// </summary>
		public LifeModel()
		{
			InitArrays( ALSettings.Default.GridHeight, ALSettings.Default.GridWidth );
		}

		/// <summary>
		/// LifeModel(int, int)
		/// 
		/// Initializes the LifeCell array with the passed in bounds
		/// </summary>
		/// <param name="columns">int, width of the life grid</param>
		/// <param name="rows">int, height of the life grid</param>
		public LifeModel( int rows, int columns )
		{
			InitArrays( rows, columns ); 
		}

		/// <summary>
		/// LifeModel(Stream)
		/// 
		/// Constructs a LifeModel from a stream of .cells data compatible with the
		/// Life Lexicon website.
		/// </summary>
		/// <param name="str"></param>
		public LifeModel( Stream str )
		{
			int copyRow = 0;
			int copyCol = 0;

			StreamReader reader = new StreamReader(str);
			string input = reader.ReadToEnd();
			char[] sep = { '\n' };
			string[] inputs = input.Split(sep);
			if ( inputs.Length < 2 )
				throw( new System.Exception("Bad cell data format") );

			_modelName = inputs[0].Substring(6, inputs[0].Length - 6);

			int newRows = inputs.Length - 3;
			int newCols = inputs[2].Length;
			int defRows = ALSettings.Default.GridHeight;
			int defCols = ALSettings.Default.GridWidth;

			if ( !ALSettings.Default.ShrinkGridToModel )
			{
				if (newRows < defRows)
					copyRow = (defRows - newRows) / 2;
				else
					defRows = newRows;

				if (newCols < defCols)
					copyCol = (defCols - newCols) / 2;
				else
					defCols = newCols;
			}
			else
			{
				defRows = newRows;
				defCols = newCols;
			}

		_evaluated = false;
		PeakPopulation = 0;
		Population = 0;
		_gridType = ALSettings.Default.DefaultGridType;

		InitArrays(defRows, defCols);

		BuildWorkGrid();

		for ( int row = 0; row < newRows; row++ )
		{
			for ( int col = 0; col < newCols; col++ )
			{
				bool set = false;
				if ( inputs[row + 2][col] == '\x4f' )
				{    
					set = true;
				}
				else if ( inputs[row + 2][col] != '\x2e' )
					throw( new System.Exception("Invalid character in cell data: " + inputs[row + 2][col].ToString()) );

				_cellGrid[row + copyRow, col + copyCol].IsAlive = 
				_lastGrid[row + copyRow, col + copyCol] = 
				_startingGrid[row + copyRow, col + copyCol] = set;
				}
			}
		}

		#region ISerializable methods

		/// <summary>
		/// LifeModel(SerializationInfo, StreamingContext)
		/// 
		/// Called by the BinaryFormatter to construct a LifeModel instance from a stream. Expects
		/// _lastGrid to contain the most recent grid array, and copies it into a newly constructed
		/// array of LifeCells.
		/// </summary>
		/// <param name="info"></param>
		/// <param name="ctxt"></param>
		public LifeModel( SerializationInfo info, StreamingContext ctxt )
		{
			_rows = (int)info.GetValue( "_rows", typeof(int) );
			_columns = (int)info.GetValue( "_columns", typeof(int) );
			_startingGrid = (bool[,])info.GetValue( "_startingGrid", typeof(bool[,]) );
			_lastGrid = (bool[,])info.GetValue("_lastGrid", typeof(bool[,]) );
			_evaluated = (bool)info.GetValue( "_evaluated", typeof(bool) );
			_peakPopulation = (int)info.GetValue( "_peakPopulation", typeof(int) );
			_gridType = (GridType)info.GetValue( "_gridType", typeof(GridType) );
			_modelName = (string)info.GetValue( "_modelName", typeof(string) );
			_cellGrid = new LifeCell[_rows, _columns];

			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
				{    
					_cellGrid[row, col] = new LifeCell();
					_cellGrid[row, col].IsAlive = _lastGrid[row, col];
				}
			}
			BuildWorkGrid();
		}

		/// <summary>
		/// GetObjectData(SerializationInfo, StreamingContext)
		/// 
		/// Called by the BinaryFormatter to serialize the data for a LifeModel
		/// into a stream. If the model has never been evaluated (i.e. edits have been
		/// made to the grid but the sim hasn't been run, the contents of the grid array
		/// are copied into _lastGrid first, and then serialization occurs.
		/// </summary>
		/// <param name="info"></param>
		/// <param name="ctxt"></param>
		public void GetObjectData( SerializationInfo info, StreamingContext ctxt )
		{
			if ( !_evaluated )
			{
				for ( int row = 0; row < _rows; row++ )
				{
					for ( int col = 0; col < _columns; col++ )
					_lastGrid[row, col] = _cellGrid[row, col].IsAlive;
				}
			}
			info.AddValue( "_rows", _rows );
			info.AddValue( "_columns", _columns );
			info.AddValue( "_startingGrid", _startingGrid );
			info.AddValue( "_lastGrid", _lastGrid );
			info.AddValue( "_evaluated", _evaluated );
			info.AddValue( "_gridType", _gridType );
			info.AddValue( "_modelName", _modelName );
			info.AddValue( "_peakPopulation", _peakPopulation );
		}

		/// <summary>
		/// StreamCells(Stream)
		/// 
		/// Writes out the current model grid to the passed in Stream in .cells
		/// format compatible with the format on the Life Lexicon website. Note
		/// that this format doesn't store any of the additional information, such
		/// as grid type and starting state, that the .avl format saves.
		/// </summary>
		/// <param name="str"></param>
		public void StreamCells( Stream str )
		{
			str.WriteByte( 0x21 );

			byte[] bytes;
			if ( _modelName != null && _modelName.Length > 0 )
				bytes = Encoding.UTF8.GetBytes( "Name: " + _modelName );
			else
				bytes = Encoding.UTF8.GetBytes( "Name: untitled" );

			str.Write( bytes, 0, bytes.Length );
			str.WriteByte( 0x0a );

			str.WriteByte( 0x21 );
			str.WriteByte( 0x0a );

			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
				{
					if ( _cellGrid[row, col].IsAlive )
						str.WriteByte( 0x4f );
					else
						str.WriteByte( 0x2e );
				}
				str.WriteByte( 0x0a );
			}
		}

		/// <summary>
		/// ResizeModel(int, int)
		/// 
		/// This function performs a resizing of the grid according to the specified
		/// dimensions. It does not resize the model, i.e. the current set of live cells
		/// but will center the model in the new grid if it is larger. If this function
		/// is passed 0 for the rows and columns it will shrink the grid to the size
		/// of the current model. The function will return false if the specific requested
		/// grid size is too small to contain the current set of live cells.
		/// </summary>
		/// <param name="newRows"></param>
		/// <param name="newCols"></param>
		/// <returns></returns>
		public bool ResizeModel( int newRows, int newCols )
		{
			int copyRow = 0;
			int copyCol = 0;
			int currRows = 0;
			int currCols = 0;

			// if the dimensions haven't changed bail
			if ( newRows == _rows && newCols == _columns )
				return true;

			// if the requested dimensions are larger we can just expand and center
			// the model on the grid
			if ( newRows > _rows && newCols > _columns )
			{
				copyRow = (newRows - _rows) / 2;
				copyCol = (newCols - _columns) / 2;

				bool[,] tempLifeGrid = new bool[_rows, _columns];
				bool[,] tempStartGrid = _lastGrid;

				for ( int row = 0; row < _rows; row++ )
				{
					for ( int col = 0; col < _columns; col++ )
					{    
						tempLifeGrid[row, col] = _cellGrid[row, col].IsAlive;
						tempStartGrid[row, col] = _startingGrid[row, col];
					}
				}
				currRows = _rows;
				currCols = _columns;

				InitArrays(newRows, newCols);
				BuildWorkGrid();

				for ( int row = 0; row < currRows; row++ )
				{
					for ( int col = 0; col < currCols; col++ )
					{    
						_cellGrid[row + copyRow, col + copyCol].IsAlive = tempLifeGrid[row, col];
						_startingGrid[row + copyRow, col + copyCol] = tempStartGrid[row, col];
					}
				}
				return true;
			}

			// if we get here either the requested grid is smaller than the current
			// one, or the user has asked for the grid to be shrunk to the model. In
			// either case we first need to know the extent of the current set of live
			// cells.
			ALRectangle rect = GetModelExtent();
			currRows = (rect.Bottom - rect.Top) + 1;
			currCols = (rect.Right - rect.Left) + 1;

			if ( newRows == 0 ) newRows = currRows;
			if ( newCols == 0 ) newCols = currCols;

			if ( newRows >= currRows && newCols >= currCols )
			{
				copyRow = (newRows - currRows) / 2;
				copyCol = (newCols - currCols) / 2;

				bool[,] tempLifeGrid = new bool[_rows, _columns];
				bool[,] tempStartGrid = _lastGrid;

				for (int row = 0; row < _rows; row++)
				{
					for (int col = 0; col < _columns; col++)
					{
						tempLifeGrid[row, col] = _cellGrid[row, col].IsAlive;
						tempStartGrid[row, col] = _startingGrid[row, col];
					}
				}
				InitArrays(newRows, newCols);
				BuildWorkGrid();
				for (int row = 0; row < currRows; row++)
				{
					for (int col = 0; col < currCols; col++)
					{
						_cellGrid[row + copyRow, col + copyCol].IsAlive = tempLifeGrid[row + rect.Top, col + rect.Left];
						_startingGrid[row + copyRow, col + copyCol] = tempStartGrid[row + rect.Top, col + rect.Left];
					}
				}
				return true;
			}
			return false;
		}

		#endregion

		/// <summary>
		/// IsEmpty()
		/// 
		/// Returns false if at least one cell in the grid is alive
		/// </summary>
		/// <returns></returns>
		public bool IsEmpty()
		{
			bool empty = true;
			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
				{
					if ( _cellGrid[row, col].IsAlive )
					{
						empty = false;
						break;
					}
				}
			} 
			return empty;
		}

		/// <summary>
		/// Called by the LifeSim class to iterate through the array and apply the game
		/// rules once.
		/// </summary>
		public void Evaluate()
		{
			bool[,] temp = new bool[_rows, _columns];

			int population = 0;

			if ( !_evaluated )
			{
				_evaluated = true;
				for ( int row = 0; row < _rows; row++ )
				{
					for ( int col = 0; col < _columns; col++ )
					{    
						_startingGrid[row, col] = _lastGrid[row, col] = _cellGrid[row, col].IsAlive;
						if ( _startingGrid[row, col] )
							_peakPopulation++;
					}
				}
				PeakPopulation = _peakPopulation;
			} 

			for( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
				{
					_lastGrid[row, col] = _cellGrid[row, col].IsAlive;
					int adj = CountAdjacent( row, col );
					if ( _cellGrid[row, col].IsAlive )
					{
						if ( adj == 2 || adj == 3 )
							temp[row, col] = true;
						else
							CellDeaths++;
					}
					else
					{
						if ( adj == 3 )
						{
							temp[row, col] = true;
							CellBirths++;
						}
					}
				}
			}
			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
				{
					if ( temp[row, col] == true )
					{    
						_cellGrid[row, col].IsAlive = true;
						population++;
					}
					else
						_cellGrid[row, col].IsAlive = false;   
				}
			}
			Population = population;
			if ( _peakPopulation < population )
				PeakPopulation = population;
			if ( AreEqualGrids(_cellGrid, _lastGrid) )
				_evoHalted = true;
		}

		/// <summary>
		/// ResetModel()
		/// 
		/// Resets the life model to the starting state by restoring the values in
		/// _startingGrid to the cell array.
		/// </summary>
		public void ResetModel()
		{
			_evaluated = false;
			CellBirths = 0;
			CellDeaths = 0;
			_evoHalted = false;
			Population = 0;
			PeakPopulation = 0;

			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
					_cellGrid[row, col].IsAlive = _startingGrid[row, col];
			}
		}
		#endregion

		#region LifeModel private methods

		/// <summary>
		/// GetModelExtent()
		/// 
		/// Returns an ALRectangle structure with the extent of the current
		/// set of life cells.
		/// </summary>
		/// <returns></returns>
		private ALRectangle GetModelExtent()
		{
			ALRectangle rect;
			rect.Left = _columns / 2;
			rect.Top = _rows / 2;
			rect.Right = rect.Left + 1;
			rect.Bottom = rect.Top + 1;

			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
				{
					if ( _cellGrid[row, col].IsAlive )
					{
						if (row < rect.Top ) rect.Top = row;
						if (row > rect.Bottom ) rect.Bottom = row;
						if (col < rect.Left) rect.Left = col;
						if (col > rect.Right) rect.Right = col;
					}
				}
			}
			return rect;
		}

		/// <summary>
		/// AreEqualGrids(LifeCell[,], bool[,])
		/// 
		/// Called from Evaluate() to determine if evolution has ceased or the sim
		/// has fallen into an  oscillating pattern. Compares an array of LifeCells
		/// to a backup array of booleans and returns true if they are the same
		/// </summary>
		/// <param name="lc"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		private bool AreEqualGrids( LifeCell[,] lc, bool[,] b )
		{
			bool result = true;
			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
				{
					if ( lc[row, col].IsAlive != b[row, col] )
					{
						result = false;
						break;
					}
				}
			}
			return result;
		}

		/// <summary>
		/// CountAdjacent(int, int)
		/// 
		/// This function counts neighbors using the work grid, which returns
		/// the correct values for edge cells depending on the grid type. The work
		/// grid is two cells larger in both dimensions, with the outer cells used
		/// for correct wrapping of neighbor checks, as set up in BuildWorkGrid().
		/// The incoming coordinates are in the _cellGrid space, so this function
		/// shifts them down and right 1 cell to get to _workGrid space.
		/// </summary>
		/// <param name="row"></param>
		/// <param name="column"></param>
		/// <returns></returns>
		private int CountAdjacent(int row, int column)
		{
			int count = 0;

			row++;
			column++;

			// upper left
			if ( _workGrid[row - 1, column - 1].IsAlive )
				count++;

			if ( _workGrid[row - 1, column].IsAlive )
				count++;

			// upper right
			if ( _workGrid[row - 1, column + 1].IsAlive )
				count++;

			// left
			if ( _workGrid[row, column - 1].IsAlive )
				count++;

			// right
			if ( _workGrid[row, column + 1].IsAlive )
				count++;

			// lower left
			if ( _workGrid[row + 1, column - 1].IsAlive )
				count++;

			// lower middle
			if ( _workGrid[row + 1, column].IsAlive )
				count++;

			// lower right
			if ( _workGrid[row + 1, column + 1].IsAlive )
				count++;

			return count;
		}

		/// <summary>
		/// BuildWorkGrid()
		/// 
		/// The work grid is two cells larger in each dimension, with the edge cells being
		/// used for controlling how the model wraps. This method builds the work grid off
		/// of the _cellGrid taking the GridType into account.
		/// </summary>
		private void BuildWorkGrid()
		{
			_workGrid = new LifeCell[_rows + 2, _columns + 2];
			for ( int row = 0; row < _rows + 2; row++ )
			{
				for ( int col = 0; col < _columns + 2; col++ )
				{
					// Handle the corner conditions. A corner cell can only be
					// alive in the case of a torus. In all other grid types it
					// will be dead by one of the other edges. In the case of a
					// torus it wraps to the opposite corner.
					if ( row == 0 && col == 0 )
					{
						switch (_gridType)
						{
							case GridType.Torus:
								_workGrid[row, col] = _cellGrid[_rows - 1, _columns - 1];
								break;
							default:    
								_workGrid[row, col] = new LifeCell();
								break;
						}
					}
					else if ( row == 0 && col == _columns + 1 )
					{
						switch (_gridType)
						{
							case GridType.Torus:
								_workGrid[row, col] = _cellGrid[_rows - 1, 0];
								break;
							default:
								_workGrid[row, col] = new LifeCell();
								break;
						}
					}
					else if (row == _rows + 1 && col == _columns + 1)
					{
						switch (_gridType)
						{
							case GridType.Torus:
								_workGrid[row, col] = _cellGrid[0, 0];
								break;
							default:
								_workGrid[row, col] = new LifeCell();
								break;
						}
					}
					else if (row == _rows + 1 && col == 0)
					{
						switch (_gridType)
						{
							case GridType.Torus:
								_workGrid[row, col] = _cellGrid[0, _columns - 1];
								break;
							default:
								_workGrid[row, col] = new LifeCell();
								break;
						}
					}
					// Handle the non-corner edges. They are dead in the
					// finite case, or the case where they lie along the top/bottom
					// in an x cylinder grid, or the left/right in a y cylinder grid.
					// Otherwise they wrap to the cell on the opposite side.
					else if (row == 0)
					{
						switch( _gridType )
						{
							case GridType.Finite:
							case GridType.XCylinder:
								_workGrid[row, col] = new LifeCell();
								break;
							case GridType.Torus:
							case GridType.YCylinder:
								_workGrid[row, col] = _cellGrid[_rows - 1, col - 1];
								break;
						}
					}
					else if ( row == _rows + 1 )
					{
						switch( _gridType )
						{
							case GridType.Finite:
							case GridType.XCylinder:
								_workGrid[row, col] = new LifeCell();
								break;
							case GridType.Torus:
							case GridType.YCylinder:
								_workGrid[row, col] = _cellGrid[0, col - 1];
								break;
						}
					}
					else if ( col == 0 )
					{
						switch (_gridType)
						{
							case GridType.Finite:
							case GridType.YCylinder:
								_workGrid[row, col] = new LifeCell();
								break;
							case GridType.Torus:
							case GridType.XCylinder:
								_workGrid[row, col] = _cellGrid[row - 1, _columns - 1];
								break;
						}
					}
					else if ( col == _columns + 1 )
					{
						switch (_gridType)
						{
							case GridType.Finite:
							case GridType.YCylinder:
								_workGrid[row, col] = new LifeCell();
								break;
							case GridType.Torus:
							case GridType.XCylinder:
								_workGrid[row, col] = _cellGrid[row - 1, 0];
								break;
						}
					}
					else
						_workGrid[row, col] = _cellGrid[row - 1, col - 1];
				}
			}          
		}

		/// <summary>
		/// InitArrays(int, int)
		/// 
		/// Called from the LifeModel constructors to create the LifeCell array and assign
		/// values to related private members
		/// </summary>
		/// <param name="columns">int, the width (columns) of the grid to be created</param>
		/// <param name="rows">int, the height (rows) of the grid to be created</param>
		private void InitArrays(int rows, int columns )
		{
			if ( columns <= 0 || rows <= 0 )
				throw( new System.ArgumentOutOfRangeException("InitArrays") );

			_columns = columns;
			_rows = rows;

			_cellGrid = new LifeCell[_rows, _columns];

			for ( int row = 0; row < _rows; row++ )
			{
				for ( int col = 0; col < _columns; col++ )
					_cellGrid[row, col] = new LifeCell();
			}

			BuildWorkGrid();

			_startingGrid = new bool[_rows, _columns];
			_lastGrid = new bool[_rows, _columns];
		}
		#endregion

		#region LifeModel Properties

		/// <summary>
		/// The number of columns across the life grid
		/// </summary>
		private int _columns = 0;
		public int Columns
		{
			get
			{
				return _columns;
			}
		}

		/// <summary>
		/// The number of rows down the life grid
		/// </summary>
		private int _rows = 0;
		public int Rows
		{
			get
			{
				return _rows;
			}
		}

		/// <summary>
		/// LifeCell
		/// 
		/// This property gives access to the array of LifeCell objects so that they can be used
		/// as data context in binding to the UI. Not sure I like this design.
		/// </summary>
		private LifeCell[,] _cellGrid = null;
		public LifeCell[,] CellGrid
		{
			get
			{
				if ( _cellGrid != null )
					return _cellGrid;    
				else
					throw( new System.InvalidOperationException("LifeCell_get") );
			}
		}

		/// <summary>
		/// Evaluated
		/// 
		/// True if the model has been evaluated at least once, meaning that the data
		/// in _startingGrid is valid
		/// </summary>
		bool _evaluated = false;
		public bool Evaluated
		{
			get
			{
				return _evaluated;
			}
		}

		/// <summary>
		/// CellBirths
		/// 
		/// Counts the number of cell births. Fires change notification.
		/// </summary>
		private int _cellBirths = 0;
		public int CellBirths
		{
			get
			{
				return _cellBirths;
			}
			protected set
			{
				_cellBirths = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new PropertyChangedEventArgs("CellBirths"));
			}
		}

		/// <summary>
		/// CellDeaths
		/// 
		/// Counts the number of cell deaths. Fires change notification.
		/// </summary>
		private int _cellDeaths = 0;
		public int CellDeaths
		{
			get
			{
				return _cellDeaths;
			}
			protected set
			{
				_cellDeaths = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new PropertyChangedEventArgs("CellDeaths"));
			}
		}

		/// <summary>
		/// EvolutionHalted
		/// 
		/// True if the model evolution has halted
		/// </summary>
		private bool _evoHalted = false;
		public bool EvolutionHalted
		{
			get
			{
				return _evoHalted;
			}
		}

		/// <summary>
		/// PeakPopulation
		/// 
		/// Tracks the maximum population of cells on the grid. Fires
		/// change notification.
		/// </summary>
		private int _peakPopulation = 0;
		public int PeakPopulation
		{
			get
			{
				return _peakPopulation;
			}
			protected set
			{
				_peakPopulation = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new PropertyChangedEventArgs("PeakPopulation"));
			}
		}

		/// <summary>
		/// Population
		/// 
		/// Tracks the current population of the grid at the close of each tick.
		/// Fires change notification.
		/// </summary>
		private int _population = 0;
		public int Population
		{
			get
			{
				return _population;
			}
			protected set
			{
				_population = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new PropertyChangedEventArgs("Population"));
			}
		}

		/// <summary>
		/// GridType
		/// 
		/// Used to set the bounding behavior of the cell grid according to the
		/// GridType enumeration. 
		/// </summary>
		private GridType _gridType = ALSettings.Default.DefaultGridType;
		public GridType LifeGridType
		{
			get
			{
				return _gridType;
			}
			set
			{
				_gridType = value;
				BuildWorkGrid();
			}
		}

		/// <summary>
		/// Contains the name (title) of the current model
		/// </summary>
		private string _modelName = null;
		public string ModelName
		{
			get
			{
				return _modelName;
			}
			set
			{
				_modelName = value;
			}
		}

		#endregion

		#region INotifyPropertyChanged members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion

		#region LifeModel private data

		// holds the starting grid state so we can revert on demand
		private bool[,] _startingGrid = null;

		// holds the state of the last calculate grid, used to 
		// detect a halt
		private bool[,] _lastGrid = null;

		// holds the working grid, which is two cells larger than
		// the cell grid on each dimension, with the edge cells
		// being set up to wrap correctly according to the 
		// grid type. See BuildWorkGrid()
		private LifeCell[,] _workGrid = null;

		#endregion
	}
}

WordPressed

It’s not in my nature to throw away work I have done. I still have archives of project folders that were set up in 1990 on my hard disk. You never know when you’re going to need a quick answer to a Borland C++ 4.0 question. My physical workshop looks a lot like my virtual one: dusty shelves; boxes piled high with old components. You never know when you’re going to need a modem, or a PS/2 to USB converter.

I mention these things to illustrate that today represents a significant departure for me. For a few years I maintained a blog at modalogica.com, and a personal page at markbetz.net. The framework for these pages was derived from PopForums, a forum platform for ASP.Net 1.1. I made a lot of modifications to it over time, including the blog pages, file libraries, and some other things. It worked very well, but as my kids grew along with my responsibilities I realized that I wanted to program and write, not spend my scarce programming time implementing features to support writing.

Another change for me has been my increasing use of Linux over the last year. I was never a Windows zealot, and I am not now an anti-Microsoft crusader of some kind. It’s just that I have had to use it for work, and it has turned out to be easier for some things that I have done here at home, too. The situation has evolved to the point where I have a sort of geeky crush on Debian, but I wouldn’t prescribe it to my mother for her daily use. When it works, it works really well. When it fails, it fails in spectacularly arcane ways that take a specialist spelunker of old forum posts to figure out.

These trends all lead to to today’s re-launch of my blog site running in WordPress on Linux. I’m pretty impressed with the platform so far, but will have much more to say on that later. Getting the content that I wanted out of the old blog has been painful, enough so that I have not brought everything, nor, for reasons of focus, will I. I intend this blog to be much more narrowly focused on programming, with a side dish of history, and occasional seasoning of games and literature.

I’ve spent about a week so far getting everything running the way I want it to, or pretty close. The theme is Paalam with a custom header image and some other small tweaks. In addition I am using NextGEN Gallery, and a few other widgets and plugins. Over the next couple of weeks I plan to add a lot of pictures and some of the files that I used to have linked on the old site. In the meantime, I hope you enjoy what you find here. Drop me a comment if you have any suggestions.