Click here to Skip to main content
15,886,840 members
Articles / Programming Languages / C#

LINQ to Life

Rate me:
Please Sign up or sign in to vote.
4.94/5 (69 votes)
6 Mar 2008CPOL7 min read 118.6K   442   138  
Using LINQ to improve everyday code
//
// PetriDish.cs
//
// -------------------------------------------------------------------
// History:
//  2008-03-02	Kwak		Original File 
// -------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;

namespace GameOfLife.Core
{
    /// <summary>
    /// A container for cells
    /// </summary>
    public class PetriDish
    {
        #region Constants
        private const int DefaultAgeLimit = 40;
        private const int DefaultInitialChanceOfLife = 3; // 1 in 3 
        #endregion

        #region Fields
        private List<Cell> _cells; // used for queries
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets or sets the age limit.
        /// </summary>
        /// <value>The age limit.</value>
        /// <remarks>No cell is allowed to be over this age</remarks>
        public int AgeLimit
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the height.
        /// </summary>
        /// <value>The height.</value>
        public int Height
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the number of living cells.
        /// </summary>
        /// <value>The number of living cells.</value>
        public int NumberOfLivingCells
        {
            get
            {
                var numberOfLivingCells =
                    from c in _cells
                    where c.Age > 0
                    select c;

                return numberOfLivingCells.Count();
            }
        }

        /// <summary>
        /// Gets the width.
        /// </summary>
        /// <value>The width.</value>
        public int Width
        {
            get;
            private set;
        }

        #endregion

        #region Indexers


        public Cell this[int row, int col]
        {
            get
            {
                Cell result = null;
                if (row >= 0 && col >= 0)
                {
                    if (row < Height && col < Width)
                    {
                        int position = row * Width + col;
                        result = _cells.ElementAtOrDefault(position);
                    }
                }
                return result;
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Initializes a new instance of the <see cref="Grid"/> class.
        /// </summary>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        public PetriDish(int width, int height)
        {
            Width = width;
            Height = height;
            AgeLimit = DefaultAgeLimit;

            _cells = new List<Cell>(Height * Width);

            Enumerable.Range(0, Height * Width).Each(InitializeCells);
            _cells.Each(PopulateNeighbors);
        }


        /// <summary>
        /// Initializes the cells.
        /// </summary>
        /// <param name="index">The index.</param>
        private void InitializeCells(int index)
        {
            Cell c = new Cell(index);
            _cells.Add(c);
        }

        private void PopulateNeighbors(Cell c)
        {
            int row = c.Position / Width;
            int col = c.Position % Width;
            List<Cell> neighbors = new List<Cell> 
            {
                this[row - 1, col - 1],
                this[row - 1, col],
                this[row - 1, col + 1],
                this[row, col -1],
                // this[row, col], -- hey that's the Cell c!
                this[row, col + 1],
                this[row + 1, col - 1],
                this[row + 1, col],
                this[row+1, col + 1]
            };

            c.Neighbors = neighbors.FindAll(e => e != null);
        }

        #endregion // initialization

        #region Public Methods

        /// <summary>
        /// Calculates the next generation on the grid
        /// </summary>
        public void NextGeneration()
        {
            // calculate what the next generation will look like
            ApplyRulesToLivingCells();
            ApplyRulesToNonLivingCells();

            // make it so
            UpdateCells();

        }

        /// <summary>
        /// Resets the current cell grid using the default random seed.
        /// </summary>
        public void Reset()
        {
            Reset(DefaultInitialChanceOfLife);
        }

        /// <summary>
        /// Resets the current cell grid using the specified random seed.
        /// </summary>
        /// <param name="chance">The chance a cell will be alive in the first generation</param>
        public void Reset(int chance)
        {
            // Kill them all
            _cells.Each(c => c.TakeLife());

            // Randomly pick a new bunch
            Random rnd = new Random();
            var pregnantCells =
                from c in _cells
                where rnd.Next(0, chance) == 0
                select c;
            pregnantCells.Each(c => c.GiveLife());
        }

        #endregion // Public Methods

        #region The Rules of Life

        /// <summary>
        /// Applies the rules to living cells.
        /// </summary>
        private void ApplyRulesToLivingCells()
        {
            // a living cell can either die or surive
            // a non-living cell can only be born
            // spawn rule: a cell dying of old age spawns.
            var livingCells =
                from c in _cells
                where c.Age > 0
                select c;

            var cellsToKill =
                from c in livingCells
                where
                    c.NumberOfLivingNeighbors <= 1 || // loneliness  
                    c.NumberOfLivingNeighbors >= 4 // over population
                select c;

            var cellsToKeepAlive =
                from c in livingCells
                where
                    c.NumberOfLivingNeighbors == 2 ||
                    c.NumberOfLivingNeighbors == 3
                select c;

            var dyingOfOldAge =
                from c in cellsToKeepAlive
                where
                    c.Age >= AgeLimit
                select c;

            // remove the old-age cells
            cellsToKeepAlive = cellsToKeepAlive.Except(dyingOfOldAge);

            // old age cells spawn by making
            // any non-living neighbors alive
            var spawnedCells =
                from old in dyingOfOldAge
                from n in old.Neighbors
                where
                    n.Age == 0
                select n;

            cellsToKill.Each(c => c.LivesInNextGeneration = false);
            dyingOfOldAge.Each(c => c.LivesInNextGeneration = false);
            cellsToKeepAlive.Each(c => c.LivesInNextGeneration = true);
            spawnedCells.Each(c => c.LivesInNextGeneration = true);

        }

        /// <summary>
        /// Applies the rules to non living cells.
        /// </summary>
        private void ApplyRulesToNonLivingCells()
        {
            // a non-living cell can only be born
            // a living cell can either die or surive
            var notLivingCells =
                from c in _cells
                where c.Age == 0
                select c;

            var pregnantCells =
                from c in notLivingCells
                where c.NumberOfLivingNeighbors == 3
                select c;

            pregnantCells.Each(c => c.LivesInNextGeneration = true);
        }

        /// <summary>
        /// Update cells effects the age of the cells in the dish
        /// </summary>
        private void UpdateCells()
        {
            // giveth life
            var livingCells =
                from c in _cells
                where c.LivesInNextGeneration
                select c;

            livingCells.Each(c => c.GiveLife());

            // taketh it away
            var dyingCells =
                from c in _cells
                where !c.LivesInNextGeneration
                select c;

            dyingCells.Each(c => c.TakeLife());
        }

        #endregion // The Rules of Life

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Jeff Kwak (a.k.a., FutureTurnip) is an open minded software developer/architect on top of the .net stack. He's coded in everything from 8-bit assembly language to C#. Currently, he's into big architecture, organizing software teams/process, emerging web technologies (like Silverlight and MS MVC), languages, ... all things software and related to software.

Comments and Discussions