Conway's Game of Life





2
Date Submitted Fri. Apr. 13th, 2007 11:50 AM
Revision 1 of 1
Helper BerndSchiffer
Tags conway | groovy
Comments 0 comments
Conway's Game of Life - a solution for GroovyQuiz.com. You can download all of the sources and an executable jar.


class ArgumentsInterpreterTest extends GroovyTestCase {

        def interpreter = new ArgumentsInterpreter()

        void testInterpretsArgumentWithoutValue() {
                interpreter << new ExpectedArgument(shortcut:'-h', argument:'printHelp')
                def arguments = interpreter.interpret(['-h'])
                assertNotNull(arguments['printHelp'])
        }
       
        void testInterpretsNoArguments() {
                def arguments = interpreter.interpret([])
                assertTrue(arguments.isEmpty())
        }
       
        void testInterpretsArgumentWithValue() {
                interpreter << new ExpectedArgument(shortcut:'-t', argument:'tickInterval', defaultValue:1)
                def arguments = interpreter.interpret(['-t', '2'])
                assertEquals('2', arguments.tickInterval)
        }
       
        void testAcceptsArgumentWithoutDefaultValue() {
                interpreter << new ExpectedArgument(shortcut:'-h', argument:'printHelp')
                def arguments = interpreter.interpret([])
                assertNull(arguments['printHelp'])
        }
       
        void testAcceptsArgumentWithDefaultValue() {
                interpreter << new ExpectedArgument(shortcut:'-t', argument:'tickInterval', defaultValue:1)
                def arguments = interpreter.interpret([])
                assertEquals('1', arguments.tickInterval)
        }
       
        void testInterpretsTwoArgumentsWithDefaultValues() {
                interpreter <<
                        new ExpectedArgument(shortcut:'-a', argument:'b', defaultValue:1) <<
                        new ExpectedArgument(shortcut:'-c', argument:'d', defaultValue:2)
                def arguments = interpreter.interpret(['-a', '3', '-c', '4'])
                assertEquals('3', arguments.b)
                assertEquals('4', arguments.d)
        }
       
        void testInterpretsTwoArgumentsOneWithAndOneWithoutDefaultValue() {
                interpreter <<
                        new ExpectedArgument(shortcut:'-a', argument:'b') <<
                        new ExpectedArgument(shortcut:'-c', argument:'d', defaultValue:1)
                def arguments = interpreter.interpret(['-a', '-c', '2'])
                assertNotNull(arguments.b)
                assertEquals('2', arguments.d)
        }
       
        void testPrintsSummary() {
                interpreter <<
                        new ExpectedArgument(shortcut:'-d', argument:'e', defaultValue:1, description:'f') <<
                        new ExpectedArgument(shortcut:'-a', argument:'b', description:'c') <<
                        new ExpectedArgument(shortcut:'-f', argument:'long', defaultValue:2, description:'g')
                assertEquals('-a        c\n' +
                                     '-d <e>    f;\n          default value is 1\n' +
                                     '-f <long> g;\n          default value is 2\n', interpreter.summary)
               
        }
}

class ConwaysGameOfLifeTest extends GroovyTestCase {

        static def BLINKER_HORIZONTAL = "['   ', 'XXX', '   ']"
        static def BLINKER_VERTICAL = "[' X ', ' X ', ' X ']"
       
        void testBlinker() {
                def universe = new ConwaysGameOfLife().createUniverse(new GroovyShell().evaluate(BLINKER_HORIZONTAL))
                assertEquals(BLINKER_HORIZONTAL, universe.toString())
                assertEquals(BLINKER_VERTICAL, universe.next().toString())
                assertEquals(BLINKER_HORIZONTAL, universe.next().toString())
        }
       
        static def TOAD_FIRST_TICK = "['      ', '  XXX ', ' XXX  ', '      ']"
        static def TOAD_SECOND_TICK = "['   X  ', ' X  X ', ' X  X ', '  X   ']"
       
        void testToad() {
                def universe = new ConwaysGameOfLife().createUniverse(new GroovyShell().evaluate(TOAD_FIRST_TICK))
                assertEquals(TOAD_FIRST_TICK, universe.toString())
                assertEquals(TOAD_SECOND_TICK, universe.next().toString())
                assertEquals(TOAD_FIRST_TICK, universe.next().toString())
        }
}

class RuleBuilderTest extends GroovyTestCase {

        void testBuildsRules() {
                def expectedFrom = 'a'
                def expectedWithLivingNeighbours = 'b'
                def expectedTo = 'c'
                def ruler = new RuleBuilder().rules {
                        rule(from:expectedFrom, withLivingNeighbours:expectedWithLivingNeighbours, to:expectedTo)
                }
                assertEquals(1, ruler.size())
                def rule = ruler[0]
                assertEquals(expectedFrom, rule.from)
                assertEquals(expectedWithLivingNeighbours, rule.withLivingNeighbours)
                assertEquals(expectedTo, rule.to)
        }
}

import org.easymock.EasyMock

class RulerTest extends GroovyTestCase {

        def ruler = new Ruler()
       
        void testAppliesRuleOnCell() {
                def ruleMock = EasyMock.createMock(IRule.class)
                def cellDummy = new Expando()
               
                def universeExpando = new Expando()
                universeExpando.eachCellWithXY = { closure -> closure.call(cellDummy, 0, 0) }
                universeExpando.switchToNextTick = { /*dummy behaviour */ }
               
                def expectedNumberRulesAppliedTo = 2
                ruleMock.applyTo(cellDummy)
                EasyMock.expectLastCall().times(expectedNumberRulesAppliedTo)
                EasyMock.replay(ruleMock)
               
                expectedNumberRulesAppliedTo.times { ruler << ruleMock }
                ruler.applyRulesTo(universeExpando)
                EasyMock.verify(ruleMock)
        }
       
        void testSwitchesUniverseToNextTickAfterApplyingRules() {
                def universeMock = EasyMock.createMock(IUniverse.class)
                universeMock.switchToNextTick()
                EasyMock.replay(universeMock)
               
                ruler.applyRulesTo(universeMock)
                EasyMock.verify(universeMock)     
        }
}

class RuleTest extends GroovyTestCase {
       
        static def LIVE_CELL = new Cell(Cell.LIVE)

        void testDoesntApplyItselfToCellWithDifferentStartStateThenRulesFromState() {
                def rule = new Rule(from:Cell.LIVE)
                def cell = new Cell(Cell.DEAD)
                rule.applyTo(cell)
                assertNull cell.nextState
        }
       
        void testAppliesItselfToCellWithSameStartStateThenRulesFromState() {
                def rule = new Rule(from:Cell.LIVE, to:Cell.DEAD)
                def cell = new Cell(Cell.LIVE)
                rule.applyTo(cell)
                assertEquals(Cell.DEAD, cell.nextState)
        }
       
        void testDoesntApplyItselfToCellWithoutEnoughNeighbours() {
                def rule = new Rule(from:Cell.LIVE, withLivingNeighbours:1..2, to:Cell.DEAD)
                def cell = new Cell(Cell.LIVE)
                rule.applyTo(cell)
                assertNull cell.nextState
                cell.neighbours = [north:LIVE_CELL, west:LIVE_CELL, south:LIVE_CELL]
                rule.applyTo(cell)
                assertNull cell.nextState
        }
       
        void testAppliesItselfToCellWithEnoughNeighbours() {
                def rule = new Rule(from:Cell.LIVE, withLivingNeighbours:1..2, to:Cell.DEAD)       
                def cellWithOneNeighbour = new Cell(Cell.LIVE)
                cellWithOneNeighbour.neighbours.north = LIVE_CELL
                rule.applyTo(cellWithOneNeighbour)
                assertEquals(Cell.DEAD, cellWithOneNeighbour.nextState)
               
                def cellWithTwoNeighbours = new Cell(Cell.LIVE)
                cellWithTwoNeighbours.neighbours.south = LIVE_CELL
                cellWithTwoNeighbours.neighbours.north = LIVE_CELL
                rule.applyTo(cellWithTwoNeighbours)
                assertEquals(Cell.DEAD, cellWithTwoNeighbours.nextState)
        }
       
        void testDoesntApplyAnyRuleOnAlreadyChangedState() {
                def rule = new Rule(from:Cell.LIVE, to:Cell.DEAD)             
                def changedCell = new Cell(Cell.LIVE)
                changedCell.nextState = Cell.DEAD
        }
}

class UniverseTest extends GroovyTestCase {

        void testCreatesItselfByWidthAndHeightWithAllDeadCells() {
                def universe = new Universe(3, 3, Cell.DEAD)
                assertSame Cell.DEAD, universe[0][0].state
                assertSame Cell.DEAD, universe[2][2].state
                assertNull universe[3][2]
                assertNull universe[2][3]
                assertEquals 9, universe.size()
        }
       
        void testCreatesItselfByPatternWithAllDeadCells() {
                def universe = new Universe(['   ', '   '])
                assertEquals(new Universe(3, 2, Cell.DEAD), universe)
                assertEquals(3, universe.width)
                assertEquals(2, universe.height)
        }
       
        void testCreatesItselfByPatternWithLiveCellsInTheCornersAndInTheMiddle() {
                def universe = new Universe(3, 3, Cell.DEAD)
                universe[0][0].alive()
                universe[2][0].alive()
                universe[1][1].alive()
                universe[0][2].alive()
                universe[2][2].alive()
                assertEquals(universe, new Universe(['X X',
                                                     ' X ',
                                                     'X X']))
        }
       
        void testCountsNeighboursByState() {
                def universe = new Universe(3, 3, Cell.LIVE)
                assertEquals 3, universe[0][0].countLiveNeighbours()
                assertEquals 5, universe[1][0].countLiveNeighbours()
                assertEquals 8, universe[1][1].countLiveNeighbours()
        }
       
        void testIteratesOverEachCellWithxAndYCoords() {
                def universe = new Universe(2, 1, Cell.LIVE)
                def counter = 0;
                universe.eachCellWithXY{ cell, x, y -> cell.state = Cell.DEAD; counter++ }
                assertEquals(2, counter)
                assertEquals(Cell.DEAD, universe[0][0].state)
                assertEquals(Cell.DEAD, universe[0][1].state)
        }
       
        void testSwitchesToNextTick() {
                def universe = new Universe(2, 1, Cell.LIVE)
                universe[0][0].nextState = Cell.DEAD
                universe.switchToNextTick()
                assertEquals(Cell.DEAD, universe[0][0].state)
                assertEquals(Cell.LIVE, universe[0][1].state)
                assertNull(universe[0][0].nextState)
                assertNull(universe[0][1].nextState)
        }
}

class ArgumentsInterpreter {

        def expectedArguments = [:]
       
        def interpret(args) {
                def arguments = createDefaultArguments()
                args.eachWithIndex{ arg, index ->
                        if(arg in expectedArguments.keySet()) {
                                def expectedArgument = expectedArguments[arg]
                                if(!expectedArgument.defaultValue) arguments[expectedArgument.argument] = true
                                        else arguments[expectedArgument.argument] = args[index + 1]
                        }
                }
                return arguments
        }
       
        def leftShift(expectedArgument) {
                expectedArguments[expectedArgument.shortcut] = expectedArgument
                this
        }
       
        private createDefaultArguments() {
                def arguments = [:]
               expectedArguments.values().each{ expectedArgument ->
                      if(expectedArgument.defaultValue)
                             arguments[expectedArgument.argument] = expectedArgument.defaultValue as String
               }
                arguments
        }
       
        def getSummary() {
                def arguments = expectedArguments.values()
                arguments = arguments.sort{ it.shortcut }
                def longestArgument = arguments.findAll{ it.defaultValue }.argument.sort{ it.size() }[-1].size()
                def summary = ''
                arguments.each{ argument ->
                        summary <<= argument.shortcut
                        def argumentPart = ' '
                        if(argument.defaultValue) {
                                argumentPart = " <${argument.argument}> "
                        }
                        summary <<= argumentPart.padRight(longestArgument + 4, ' ')
                        summary <<= argument.description
                        if(argument.defaultValue) {
                                summary <<= ';\n'
                                summary <<= ' ' * (longestArgument + 6)
                                summary <<= "default value is ${argument.defaultValue}"
                        }
                        summary <<= '\n'
                }
                summary.toString()
        }
}

class Cell {
       
        static def DEAD = CellState.DEAD
        static def LIVE = CellState.LIVE
       
        def state = DEAD
        def nextState
       
        def neighbours = [:]
       
        Cell(CellState state) {
                this.state = state
        }
       
        Cell(char statePattern) {
                state = CellState.get(statePattern)
        }
       
        def countLiveNeighbours() {
                neighbours.findAll{ entry -> LIVE == entry.value?.state}.size()
        }
       
        def alive() { state = LIVE }
       
        String toString() {
                state.toString()
        }
}

class CellState {

        static def LIVE = new CellState(state:'X')
        static def DEAD = new CellState(state:' ')
       
        private state
       
        static def get(statePattern) {
                if(statePattern == LIVE.state)
                        return LIVE
                DEAD
        }
       
        String toString() { state }
}

class ConwaysGameOfLife {

        private static def RULER = new RuleBuilder().rules {
                        rule(from:Cell.LIVE, withLivingNeighbours:0..1, to:Cell.DEAD)
                        rule(from:Cell.LIVE, withLivingNeighbours:3..9, to:Cell.DEAD)
                        rule(from:Cell.LIVE, withLivingNeighbours:2..3, to:Cell.LIVE)
                        rule(from:Cell.DEAD, withLivingNeighbours:3, to:Cell.LIVE)
        }
                       
        def universe
       
        def createUniverse(pattern) {
                universe = new Universe(pattern)
                this
        }
       
        def next() {
                RULER.applyRulesTo(universe)
                this
        }
       
        def eachCellWithXY(def closure) { universe.eachCellWithXY(closure) }
        def getHeight() { universe.height }
        def getWidth() { universe.width }
       
        String toString() { universe.toString() }
}

class ExpectedArgument {
        def shortcut
        def argument
        def defaultValue
        def description
       
        String toString() { shortcut }
}

interface IRule {

        void applyTo(cell)

}

interface IUniverse {
        void switchToNextTick()
}

import groovy.swing.SwingBuilder
import javax.swing.JComponent
import java.awt.Color
import javax.swing.WindowConstants

class Main {

        static def TOAD = "['      ','  XXX ',' XXX  ','      ']"
        static def BLINKER = "['   ','XXX','   ']"
        static def GUN = "['                        X           '," +
                                         "'                      X X           '," +
                                         "'            XX      XX            XX'," +
                                         "'           X   X    XX            XX'," +
                                         "'XX        X     X   XX              '," +
                                         "'XX        X   X XX    X X           '," +
                                         "'          X     X       X           '," +
                                         "'           X   X                    '," +
                                         "'            XX                      '," +
                                        ("'                                    '," * 18) +
                                         "'                                    ']"
       
        static void main(args) {
                def interpreter = new ArgumentsInterpreter() <<
                                new ExpectedArgument(shortcut:'-h', argument:'printHelp',
                                                description:'prints this help text') <<
                                new ExpectedArgument(shortcut:'-t', argument:'tickInterval', defaultValue:200,
                                                description:'sets the tick interval in milliseconds') <<
                                new ExpectedArgument(shortcut:'-p', argument:'pattern', defaultValue:TOAD,
                                                description:'sets the inital pattern (try TOAD, BLINKER or GUN)') <<
                                new ExpectedArgument(shortcut:'-b', argument:'blockSize', defaultValue:10,
                                                description:'sets the length of a block in pixel') <<
                                new ExpectedArgument(shortcut:'-v', argument:'view', defaultValue:'swing',
                                                description:'sets the view to <swing> or <console>')
                def arguments = interpreter.interpret(args)
                if(arguments.printHelp) {
                        printHelp(interpreter)
                        return
                }
                def pattern = calculatePattern(arguments)
                def universe = new ConwaysGameOfLife().createUniverse(pattern)
                def view = new GroovyShell(new Binding(Main:Main.class)).evaluate("Main.&${arguments.view}")
                view(universe, arguments)
        }
       
        static calculatePattern(arguments) {
                if(arguments.pattern[0] != '[') arguments.pattern = Main."${arguments.pattern}"
                return new GroovyShell(new Binding(Main:Main.class)).evaluate(arguments.pattern)
        }

       
        static void printHelp(interpreter) {
                println 'Usage: java -jar conwaysgame.jar [arguments]\n' + interpreter.summary
        }
       
        static void console(universe, arguments) {
                while(true) {
                        println universe.toString()[2..-3].split('\', \'').join('\n')
                        universe = universe.next()
                        Thread.sleep(arguments.tickInterval.toInteger())
                }
        }
       
        static void swing(universe, arguments) {
                def swing = new SwingBuilder()
                def blockSize = arguments.blockSize.toInteger()
                def size = [universe.width * blockSize, universe.height * blockSize]
                def painterPanel = new PainterPanel(blockSize:blockSize, universe:universe,
                                tickInterval:arguments.tickInterval)
                def frame = swing.frame (title:'Conway\'s Game of Life in Groovy',
                                size: size, defaultCloseOperation:WindowConstants.EXIT_ON_CLOSE) {
                    widget(painterPanel, minimumSize: size, preferredSize: size, maximumSize: size)
                }
                frame.pack()
                frame.show()
                painterPanel.run()
        }
}

class PainterPanel extends JComponent {

        def blockSize
        def tickInterval
        def universe
       
        void paintComponent(java.awt.Graphics graphics) {
                universe.eachCellWithXY{ cell, x, y ->
                        if(Cell.DEAD == cell.state) graphics.color = Color.WHITE
                                else graphics.color = Color.BLACK
                        graphics.fillRect(y * blockSize, x * blockSize, blockSize, blockSize)
                        graphics.color = Color.GRAY
                        graphics.drawRect(y * blockSize, x * blockSize, blockSize, blockSize)
                }
        }
       
        void run() {
                while(true) {
                        Thread.sleep(tickInterval.toInteger())
                        universe = universe.next()
                        repaint()
                }
        }
}

class Rule implements IRule {

        def from
        def withLivingNeighbours = 0..Integer.MAX_VALUE
        def to
       
        void applyTo(cell) {
                def fire = from == cell.state && cell.countLiveNeighbours() in withLivingNeighbours
                if(fire) cell.nextState = to
        }
       
        String toString() { "Rule changes cells from <${from}> to <${to}> " +
                                "if number of neighbours is within ${withLivingNeighbours}." }
}

class RuleBuilder extends BuilderSupport {
       
        static def DEBUG = false
        def ruler

    void setParent(Object parent, Object child){}
   
    Object createNode(Object name){
        ruler = new Ruler()
        debug 'createNode name'
        debug "\t${name}"
        ruler
    }
   
    Object createNode(Object name, Object value){
        debug 'createNode name value'
        debug "\t${name} ${value}"
        this
    }
   
    Object createNode(Object name, Map attributes){
        debug 'createNode name attributes'
        debug "\t${name} ${attributes}"
        ruler << new Rule(attributes)
        this
    }
   
    Object createNode(Object name, Map attributes, Object value){
        debug 'createNode name attributes value'
        debug "\t${name} ${attributes} ${value}"
        this
    }
   
    private void debug(message) {
        if(DEBUG) println message
    }
}

class Ruler {
       
        def rules = []
       
        def leftShift(def rule) {
                rules << rule
                this
        }
       
        def getAt(def index) {
                rules[index]
        }
       
        void applyRulesTo(def universe) {
                rules.each{ rule ->
                        universe.eachCellWithXY{ cell, x, y ->
                                rule.applyTo(cell)
                        }
                }
                universe.switchToNextTick()
        }
       
        def size() { rules.size() }
}

class Universe implements IUniverse {
       
        private universe = [:] as TreeMap
       
        Universe(width, height, state) {
                init(width, height, state)
                linkNeighbours()
        }
       
        Universe(pattern) {
        init(pattern[0].size(), pattern.size(), Cell.DEAD)
                pattern.eachWithIndex{ row, x -> row.eachWithIndex{ statePattern, y ->
                  universe[x][y] = new Cell(statePattern as char)
                } }
                linkNeighbours()
        }
       
        private init(width, height, state) {
                createCells(width, height, state)
        }
       
        private createCells(width, height, state) {
                height.times{ x ->
                  width.times { y ->
                        if(!universe[x]) universe[x] = [:] as TreeMap
                        universe[x][y] = new Cell(state)
                } }
        }
       
        private linkNeighbours() {
                eachCellWithXY{ cell, x, y ->
                def neighbours = cell.neighbours
                        neighbours.east = this[x + 1][y]
                        neighbours.southeast = this[x + 1][y + 1]
                        neighbours.south = this[x][y + 1]
                        neighbours.west = this[x - 1][y]
                        neighbours.southwest = this[x - 1][y + 1]
                        neighbours.north = this[x][y - 1]
                        neighbours.northeast = this[x - 1][y - 1]
                        neighbours.northwest = this[x + 1][y - 1]
                }
        }

        def getAt(def key) {
                universe[key] ? universe[key] : [:]
        }
       
        def size() {
                universe.size() * universe[0].size()
        }
       
        String toString() {
                def rows = universe.collect{ entry -> entry.value.values().join('') }
                rows.collect{ '\'' + it + '\''}.toString()
        }
       
        boolean equals(def object) {
                toString() == object.toString()
        }
       
        void eachCellWithXY(def closure) {
                universe.each{ x, row ->  row.each{ y, cell ->
                        closure.call(cell, x, y)               
                }}
        }
       
        void switchToNextTick() {
                eachCellWithXY{ cell, x, y ->
                        if(cell.nextState) cell.state = cell.nextState
                        cell.nextState = null
                }
        }
       
        def getHeight() { universe.size() }
       
        def getWidth() { universe[0].size() }
}
 

Comments

There are currently no comments for this snippet.

Voting

Votes Down


Newbie kievescorts