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
}
}