2011-01-28

Conway's Game of Life Code Kata #2

After yesterday's code retreat, I've refined, refactored, and added some IEnumerable plumbing to the solution I implemented in preparation for the retreat.

It would be a really fun exercise to visualize the generations with WPF, Silverlight or something else. I'd also like to finish the attempts made at the retreat to implement this solution with JavaScript. As one of my pairing partners pointed out, visualization could be done by manipulating the DOM of an ordinary web page.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using SharpTestsEx;

namespace ConwaysGameOfLifeKatas.Generations.UnitTests {
    [Description("en.wikipedia.org/wiki/Conway's_Game_of_Life")]
    public class GenerationsUnitTests {
        [Test]
        public void new_generation_should_kill_alive_cell_with_fewer_than_two_live_neighbours() {
            var generations = new Generations(new Cell(1, 1), new Cell(0, 1));

            generations.First().Contains(new Cell(1, 1)).Should().Be.False();
        }

        [Test]
        public void new_generation_should_keep_alive_cell_with_two_live_neighbours_alive() {
            var generations = new Generations(new Cell(1, 1), new Cell(0, 1), new Cell(2, 1));

            generations.First().Contains(new Cell(1, 1)).Should().Be.True();
        }

        [Test]
        public void new_generation_should_keep_alive_cell_with_three_alive_neighbours_alive() {
            var generations = new Generations(new Cell(1, 1), new Cell(0, 1), new Cell(2, 1), new Cell(0, 0));

            generations.First().Contains(new Cell(1, 1)).Should().Be.True();
        }

        [Test]
        public void new_generation_should_kill_alive_cell_with_more_than_three_alive_neighbours() {
            var generations = new Generations(new Cell(0, 0), new Cell(1, 0), new Cell(2, 0),
                                              new Cell(0, 1), new Cell(1, 1));

            generations.First().Contains(new Cell(1, 1)).Should().Be.False();
        }

        [Test]
        public void new_generation_should_revive_dead_cell_with_three_alive_neighbours() {
            var generation = new Generations(new Cell(0, 0), new Cell(1, 0), new Cell(2, 0));

            generation.First().Contains(new Cell(1, 1)).Should().Be.True();
        }

        [TestCase(0,0,0,false)]
        [TestCase(1,0,0,true)]
        [TestCase(2,0,0,false)]
        [TestCase(0,1,0,false)]
        [TestCase(1,1,0,true)]
        [TestCase(2,1,0,false)]
        [TestCase(0,2,0,false)]
        [TestCase(1,2,0,true)]
        [TestCase(2,2,0,false)]
        [TestCase(0,0,1,false)]
        [TestCase(1,0,1,false)]
        [TestCase(2,0,1,false)]
        [TestCase(0,1,1,true)]
        [TestCase(1,1,1,true)]
        [TestCase(2,1,1,true)]
        [TestCase(0,2,1,false)]
        [TestCase(1,2,1,false)]
        [TestCase(2,2,1,false)]
        public void blinker_oscillator_should_oscillate_according_to_wikipedia(int x, int y, int generationIndex, bool isAlive) {
            var generations = new Generations(new Cell(0, 1), new Cell(1, 1), new Cell(2, 1));

            generations[generationIndex].Contains(new Cell(x, y)).Should().Be.EqualTo(isAlive);
        }
    }

    public class Generations : IEnumerable<Generation> {
        private readonly Generation _seedGeneration;

        public Generations(params Cell[] seed) {
            _seedGeneration= new Generation(seed);
        }

        public IEnumerator<Generation> GetEnumerator() {
            return new GenerationEnumerator(_seedGeneration);
        }

        public Generation this[int index] { 
            get {
                return this.ElementAt(index);
            }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        public class GenerationEnumerator : IEnumerator<Generation> {
            public GenerationEnumerator(Generation generation) {
                Current = generation;
            }

            public void Dispose() {}

            public bool MoveNext() {
                Current = Current.Tick();

                return true;
            }

            public void Reset() {}

            public Generation Current { get; private set; }

            object IEnumerator.Current {
                get { return Current; }
            }
        }
    }

    public class Generation {
        private readonly ISet<Cell> _aliveCells;

        public Generation(params Cell[] aliveCellsSeed) {
            _aliveCells = new HashSet<Cell>(aliveCellsSeed);
        }

        private IEnumerable<Cell> KeepAlives {
            get {
                return _aliveCells
                    .Where(c => GetNumberOfAliveNeighboursOf(c) == 2
                                || GetNumberOfAliveNeighboursOf(c) == 3);
            }
        }

        private IEnumerable<Cell> Revives {
            get {
                return _aliveCells
                    .SelectMany(GetDeadNeighboursOf)
                    .Where(c => GetNumberOfAliveNeighboursOf(c) == 3);
            }
        }

        public Generation Tick() {
            return new Generation(KeepAlives.Union(Revives).ToArray());
        }

        private IEnumerable<Cell> GetDeadNeighboursOf(Cell cell) {
            return GetNeighboursOf(cell).Where(c => !Contains(c));
        }

        private static IEnumerable<Cell> GetNeighboursOf(Cell cell) {
            return Enumerable.Range(-1, 3)
                .SelectMany(x => Enumerable.Range(-1, 3)
                                     .Select(y => new Cell(cell.X + x, cell.Y + y)))
                .Except(cell);
        }

        private int GetNumberOfAliveNeighboursOf(Cell cell) {
            return GetNeighboursOf(cell).Count(Contains);
        }

        public bool Contains(Cell cell) {
            return _aliveCells.Contains(cell);
        }
    }

    public struct Cell : IEquatable<Cell> {
        private readonly int _x;
        private readonly int _y;

        public Cell(int x, int y) {
            _x = x;
            _y = y;
        }

        public int Y {
            get { return _y; }
        }

        public int X {
            get { return _x; }
        }

        public bool Equals(Cell other) {
            return other._x == _x && other._y == _y;
        }

        public override bool Equals(object obj) {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof (Cell)) return false;
            return Equals((Cell) obj);
        }

        public override int GetHashCode() {
            unchecked {
                return (_x*397) ^ _y;
            }
        }
    }

    public static class Extensions {
        public static IEnumerable<T> Except<T>(this IEnumerable<T> @this, T element) {
            return @this.Except(new[] {element});
        }
    }
}

No comments:

Post a Comment