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 f;\n default value is 1\n' + '-f 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 or ') 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() } }