|
|
|
Conway's Game of Life
2
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() }
}




There are currently no comments for this snippet.