GroovyにProcessingを! G* Advent Calender 2012
どうも、はじめまして。@tetsurokitahara です。
ノリでG* Advent Calenderに参戦してみたのの、そもそもブログ自体やったことないのにどうしたものかなあ、と思っていて、でもそう思っていたのはホンノ一瞬で、エントリーしたことをすっかり忘れてしまい、今、思い出して、とりあえず、はてなダイアリーを開設してみたところです。
Processingとは
で、自分が気持ちよくプログラムを書けるかというのも大事なんですが、職業柄、どういう言語なら初学者が挫折せずに楽しみながらプログラミングを習得できるか、ということに興味があります。Rubyなんかお約束の記述が少なくて初学者がプログラミングを体験するのに適していると思いますが、初学者向けのRubyのプログラミングの解説書とかって、やっぱりgetsとputsから入って文字列をナンヤカンヤして・・・ってな感じで、CUIが中心なんですよね。今の若い人って、CUI時代のソフトウェアを見たことがないので、CUIなプログラムが書けても何もうれしくありません。極端な話、CUIプログラミングで学んだ条件分岐や繰り返し、配列などの概念がGUIプログラミングでもやっぱり必須なんだということがそもそも想像できない人もいるわけです。
楽しみながらプログラミングを覚えるといえば、やっぱりゲーム作りですよね。で、ゲームを作るにはグラフィックス表示が必須です。そういうグラフィックスを用いたゲームを初学者がちろっと作ってみるのに、もっとも適したのは、Processing (http://www.processing.org/) だと思います。たとえば、
float t = 0; float dt = 0.2; // 一番最初に呼ばれる関数 void setup() { size(200, 200); // ウィンドウサイズを設定 } // 1/60秒ごとに自動実行 void draw() { background(0); // 画面を黒色で初期化 float x = 100 + 100 * cos(t); float y = 100 + 100 * sin(t); ellipse(x, y, 10, 10); // 円を描画 t += dt; } // マウスボタンがクリックされたら呼ばれる関数 void mouseClicked() { if (dt > 0) dt = 0; else dt = 0.2; }
と書くだけで、白いボールがウィンドウをぐるぐる回ります。
Processingでは、setup()関数が最初に呼ばれ、その後、draw()関数が1/60秒ごと(変更可能)に自動的に呼ばれます。line、rect、ellipseなどの、グラフィックス描画用の組み込み関数が豊富にあり、これらを使うことで簡単にグラフィックスを描画できます。イベントハンドリングは、keyPressed(), mouseClicked(), mousePressed(), mouseReleased(), mouseDragged()などのコールバック関数を定義することで簡単に実現できます。
これはこれでよくできているのですが、Processingは裏でJavaプログラムに変換されコンパイル、実行されます。なので、ちょっと凝ったことをしようと思ったら、例外ハンドリングを覚えないといけないし、ArrayListやHashMapといったコレクションフレームワークも覚えないといけません。Javaマスターのみなさんからしたら、それぐらいおぼえたらええやん(なぜか似非関西弁)と思われるかもしれませんが、こういうことを覚える心理的負荷は結構大きいものです。だいたい、初学者からしたら、なぜ配列の他にVectorだのArrayListだの似たようなものがいろいろあるのかよく分からないし。
ProcessingでGroovy的な書き方をできるようにする
そこでようやく本題なのですが、ProcessingをGroovyに書けたらいいんじゃないか、と。そうすれば、例外ハンドリングもしなくてもとりあえず怒られないし、ArrayListだのHashMapだのいろいろなクラスを覚える必要もなくなるだろう、と。巷にはGrocessingというまさに全く同じコンセプトのものがあるらしいのですが、いろいろ調べても実態がいまいち分かりませんでした。
Groovyが(ほぼ)Javaの上位互換になっているように、GroovyなProcessingも通常のProcessingの上位互換になってほしいです。
やり方は、単純です。Processingでは、上のプログラムはPAppletというクラスのサブクラスとして扱われます。つまり、Processingの実行系は、上のプログラムを
public class mysketch extends PApplet {
と
}
で囲んでmain関数を補っていてコンパイルします。なので、同じことをGroovyでやって、Groovy上で実行すれば、Groovyのシンタックスシュガーなどがそのまま使えるはず、という寸法です。
で、実際作ってみたのがコレです。
def s1 = ["import processing.core.*", "class MyApp extends PApplet {"] def s2 = ["}", "(new MyApp()).runSketch()"] if (args.length < 1) { System.err.println("Usage: groovy processing.groovy <script-file>") exit(1) } def script = (new File(args[0])).readLines() def head = script.findAll { it.trim().startsWith("package") || it.trim().startsWith("import") } def whole_code = (head + s1 + (script - head) + s2).join("\n") (new GroovyShell()).parse(whole_code).run()
このファイルをprocessing.groovyとし、実行したいProcessingのプログラムをmysketch.pdeとすると、
$ groovy processing.groovy mysketch.pde
とすれば実行できます。
(Processingに付属するcore.jarが必要です。)
やってることは単純で、指定されたファイルを読み込んで、それを「class MyApp extends PApplet {」と「}」で囲んでるだけです。
ただし、import文はclass文より前にないといけないので、そこだけ
抜き出して先頭にくっつけてます。Processingにpackage文を使うのかどうかはよく分かりませんが、一応それも先頭にくっつけています。
あとは、GroovyShellにパースさせて実行させているだけです。
一応、これでGroovyっぽくProcessingプログラムを書けるようになります。たとえば、こんな感じ。
def balls = [] void setup() { size(200, 200) } void draw() { background(0) if (random(1) < 0.01) { balls.add([x: random(200), y: 0]) } balls.each { it.y++ ellipse(it.x, it.y, 10, 10) } }
上の例では、リストとマップをGroovyに書いてます。あと、一応申し訳程度にクロージャも使っています。うーん、どうでしょう。個人的には結構おもしろいと思うんですけどね。さらに発展させて、DSLだの演算子オーバーロードだのいろいろやってみるとか。みなさん、あんまり興味ないですかね、こういうの。
ただ、これには結構致命的な欠点があって、たとえばellipseを間違えてelipseとか書いちゃうと、こうなります。
Exception in thread "Animation Thread" groovy.lang.MissingMethodException: No signature of method: MyApp.elipse() is applicable for argument types: (java.lang.Float, java.lang.Integer, java.lang.Integer, java.lang.Integer) values: [81.713745, 1, 10, 10] Possible solutions: list(), ellipse(float, float, float, float), enable(), hide(), size(), delete() at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:55) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:78) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:153) at MyApp$_draw_closure1.doCall(Script1.groovy:16) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:877) at groovy.lang.Closure.call(Closure.java:412) at groovy.lang.Closure.call(Closure.java:425) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1376) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1348) at org.codehaus.groovy.runtime.dgm$162.invoke(Unknown Source) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116) at MyApp.draw(Script1.groovy:14) at processing.core.PApplet.handleDraw(Unknown Source) at processing.core.PApplet.run(Unknown Source) at java.lang.Thread.run(Thread.java:722)
MyApp$_draw_closure1.doCall(Script1.groovy:16)
とか書いてあるから16行めが原因かと思いきや、これはprocessing.groovyがコードを追加した後のスクリプトでの16行目ですから、実際は14行目です。ちょっと注意が必要ですね。
おわりに
G*界隈ではグラフィクスといえばGroovyFX!という感じですが、とにかく単純明快!お手軽!という意味では、Processingはかなりいいと思います。最近のプログラミング環境はいろいろ便利になってるのは間違いないんですが、その便利さを実感できるようになるには、始めにいろいろ勉強しないといけない場合が多いように思います。その点、Processingは、学んだ量とそれによってできる事柄とが、かなり線形に近い印象があって、初学者でも苦にならずに学べると思います。GroovyとProcessingがタッグを組めば、初学者向けのプログラミング環境としてはかなりいいものができるんじゃないか、と日頃から思っているわけです。
今回、スクリプトを文字列レベルでいじるという、かなりイケてない手法を取りましたが、もうちょっとなんとかしたいですね。ユーザが書いた(Groovyな)Processingスクリプトをmysketch.groovyというファイルに保存したとすると、Groovyではmysketchというクラスとして扱われるわけですが、これを動的にPAppletのサブクラスに変更できないのでしょうか。調べてみたのですが、クラスの継承関係を動的に変えるというのは、よく分かりませんでした。Groovyならそういうことできてもいいのにな、と思うのですが。その方が動的言語っぽさを活かしてるっぽくておもしろいような気がするんですが。
みなさんも、ぜひ「GroovyなProcessing」で遊んでみていただけると幸いです。
編集後記:こんな内容で良かったのかなぁ。