Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».
Conway's Game of Life - Quiz-Lösung in Groovy
Ich habe bei Matt Secoskes GroovyQuiz mitgemacht und eine Lösung für Conways Spiel des Lebens in Groovy erstellt. Dabei bin ich testgetrieben vorgegangen und hab' viel Spaß mit Groovy gehabt.
Zuerst habe ich eine Version erstellt, die ein Universum Tick für Tick auf der Konsole ausgibt:
XXX
XXX
X
X X
X X
X
Danach habe ich eine Oberfläche mit dem SwingBuilder erstellt:
Ich habe ein Jar per Gant erzeugt, mit welchem das Progrämmchen ausgeführt werden kann. Da ich selbst immer wieder an den Einstellschrauben des Programms rumgespielt habe, dachte ich mir, dass das vielleicht auch für den Benutzer nett sein könnte. Wenn man also nun das Groovy-Programm per
java -jar conwaysgameoflife-64.jar -h
aufruft, dann gibt's das hier:
Usage: java -jar conwaysgame.jar [arguments]
-b <blockSize> sets the length of a block in
pixel;
default value is 10
-h prints this help text
-p <pattern> sets the inital pattern (try
TOAD, BLINKER or GUN);
default value is [' ',' XXX ',' XXX ',' ']
-t <tickInterval> sets the tick interval in
milliseconds;
default value is 200
-v <view> sets the view to <swing>
or <console>;
default value is swing
Die Blockgröße, der Tickintervall und die Ausgabeart sollten klar sein. Das Muster, mit dem das Programm startet, ist dagegen schon interessanter: Ich habe drei typische Muster (Toad, Blinker und Gun) quasi vorinstalliert, und man kann deren Namen angeben.
java -jar conwaysgameoflife-64.jar
-p BLINKER
Zusätzlich dazu kann man aber auch alternativ ein eigenes Muster angeben, etwa
java -jar conwaysgameoflife-64.jar
-p "['XX ','XX ',' XX',' XX']"
Die Syntax dazu sollte allen bekannt vorkommen, die schonmal mit Groovy rumgespielt haben :-)
Lessons learned habe ich auch eine ganze Menge:
- Gant ist nett. Ich habe ein Buildskript für dieses Progrämmchen geschrieben und es in Eclipse per
External Tools...
eingebunden. Liest sich IMHO viel besser als antiges XML. - Closures isoliert zu testen ist nicht trivial. Man schaue sich hierzu an, welche Verrenkungen ich machen mußte, um den
Ruler
zu testen (sieheRulerTest.groovy
). Ich bin da erstmal glücklich gewesen, überhaupt das machen zu können, was ich wollte - aber da sind noch nicht die letzten Zeilen Code und Blogwörter drüber geschrieben worden! - Der SwingBuilder ist hakelig, zumindest wenn man auf
Graphics
arbeiten möchte. Vielleicht mag da Danno Ferrin bald einiges schöner groovyfizieren. Im Nachhinein habe ich mich ja geärgert: Warum nicht gleich mit Grails machen...?! - Matt schlug vor, eine DSL für die Rules zu machen. Dafür war der von mir verwendete Builder fast schon überflüssig, denn die Rules sehen in Standard-Groovy-Notation schon fast genug DSLig aus:
new Rule(from:Cell.LIVE,
withLivingNeighbours:0..1,
to:Cell.DEAD
new Rule(from:Cell.LIVE,
withLivingNeighbours:3..9,
to:Cell.DEAD)
new Rule(from:Cell.LIVE,
withLivingNeighbours:2..3,
to:Cell.LIVE)
new Rule(from:Cell.DEAD,
withLivingNeighbours:3,
to:Cell.LIVE)
Mit eigenem Builder ist's nur minimal besser lesbar: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)
} - Ausführbare Jars mit integrierten Libs in Java zu erzeugen ist ein Tritt in den Hintern für jeden, der's versucht. One Jar ist da eine mögliche Antwort drauf. Ich habe es mir einfacher gemacht und das komplette
groovy-all-1.0.jar
perzipfileset
in Gant in mein eigenes Jar gedrückt. Fein ist was anderes :-( - Lustig war dann wieder das Programmieren mit Methodenzeigern (ich haßte sie in C++!):
def view = new GroovyShell(
new Binding(Main:Main.class))
.evaluate("Main.&${arguments.view}")
view(universe, arguments)
Es gibt zwei Methoden in der Main-Klasse, die die Ausgabeart darstellen:swing
undconsole
, und diese werden mit den Übergabeparametern-v swing
oder-v console
so wie oben direkt angesprochen. - Ähnlich einfach kann ich per
GroovyShell
das Muster der Startuniversen parsen, wenn ich das Programm aufrufe mit-p ['XX','XX']
:static calculatePattern(arguments) {
if(arguments.pattern[0] != '[')
arguments.pattern =
Main."${arguments.pattern}"
return new GroovyShell(
new Binding(Main:Main.class))
.evaluate(arguments.pattern)
}
Wenn ich im Pattern-Argument kein sich öffnende eckige Klammer an erster Stelle entdecke, dann verweise ich direkt auf die entsprechenden Konstanten, also z.B.BLINKER
, die so aussehen:static def BLINKER = "[' ','XXX',' ']"
Und solch einen String kann man dann mit derGroovyShell
auswerten lassen. Das ist Groovy! :-)
Update 13.04.2007 21:30 h:GroovyShell
ist groovy und einfach, aber noch grovier und noch einfacher geht's trotzdem noch. DieGroovyShell
benutzt man eher aus Javaklassen heraus, um Groovy-Code auszuführen. Es gibt eine Klassegroovy.util.Eval
, die dieGroovyShell
innerhalb von Groovyklassen bequemer zugreifbar macht. Stattreturn new GroovyShell(
schreibe ich da einfach
new Binding(Main:Main.class))
.evaluate(arguments.pattern)return Eval.me(arguments.pattern)
und fertig ist die Einfachheit. Okay, ist ein wenig geflunkert, denn das Binding habe ich nicht übernommen, denn es war hier tatsächlich überflüssig gewesen. Nicht so beidef view =
. Um hier das Binding aufrecht zu erhalten, schreibe ich nun
new GroovyShell(
new Binding(Main:Main.class))
.evaluate("Main.&${arguments.view}")def view =
. Und es geht noch einfacher:
Eval.me('Main',
Main.class,
"Main.&${arguments.view}")Eval
bietet drei Methoden,x()
,xy()
undxyz()
, die zwei bis vier Parameter erwarten, von denen das letzte der zu evaluierende Ausdruck sein muss und die übrigen direkt ins Binding gegeben werden und dann als x, y oder z verfügbar sind. Die view-Methode bekomme ich dann also mitdef view =
. Ich liebe solch kleine Optimierungsschmankerl!
Eval.x(Main.class,
"x.&${arguments.view}")
Danke an Matt Secoske für diese kurzweilige Programmieraufgabe, an Glen Pepicelli und Peter Ledbrook für Ihre Hilfen beim Problem mit ausführbaren Jars, an Danno Ferrin und Guillaume Laforge für Ihre Hilfen bei meinen Malversuchen im SwingBuilder und an Stefan Roock fürs Zuhören bei meinem Rumgeheule übers Closure-Testen.