PApplet

[ActionScript 3.0] Fisher-Yatesアルゴリズムの可視化│miscellaneous を Groovy で実装したい。

依存関係がなく Groovy スクリプトから利用し易そうな Processing を使ってみる。

Processing を Grape から利用するための設定

公開されている Maven リポジトリに存在しないのでローカルリポジトリに core.jar をインストールする。

mvn install:install-file -Dfile=core.jar -DgroupId=processing -DartifactId=core -Dversion=1.2.1 -Dpackaging=jar -DgeneratePom=true


Groovy は Ivy でライブラリを管理している。Ivy からローカルの Maven リポジトリを参照するためには .groovy/grapeConfig.xml に設定を追加する。
参考: Re: Read from Maven local repository

Processing の利用方法
@Grab('processing:core:1.2.1')
import processing.core.*

class XXX extends PApplet {
  @Override void setup() {}
  @Override void draw() {}
}

PApplet.main(["XXX"] as String[])
  • PApplet クラスを継承して使用する
  • setup に初期化処理を記述する
  • draw にアニメーション処理を記述する *2
  • アプリケーションで実行するには PApplet.main にクラス名を渡す


APIチュートリアルやリファレンスを参考にした。
その他 Groovy からの使用例: Groovy and Processing.org! :: Thom Nichols
Groovy スクリプトなので内部クラスを使用したが、別クラスにする場合は @Delegate を使用するパターンが参考になるだろう。

fisher-yates.groovy *3
@Grab('processing:core:1.2.1')
import processing.core.*

class FisherYates extends PApplet {
  static final int RADIUS   = 4
  static final int DIAMETER = RADIUS * 2
  static final int Y        = 100
  static final int INTERVAL = 10

  def balls = []
  def processing
  def i, j, t

  @Override void setup() {
    size(400, 200)
    for (i in 0..<40) {
      int  c = new java.awt.Color(i*5, i*5, 0).RGB
      balls << new Ball(c, i * INTERVAL + RADIUS, Y)
    }
    smooth()
    noStroke()
    frameRate(30)
  }

  @Override void draw() {
    background(255)
    balls.eachWithIndex { ball, index ->
      int x = ball.x
      int y = ball.y
      if (processing && (index == i || index == j)) {
        def target = index == i ? balls[j] : balls[i]
        def h      = index == i ? -40 : 40
        x = map(t, 0, frameRate, ball.x, target.x)
        y = bezierPoint(y, y+h, y+h, y, t / frameRate as float)
      }
      fill(ball.color)
      ellipse(x, y, DIAMETER, DIAMETER)
    }
    if (processing) {
      t++
      if (t >= frameRate) {
        swap()
        next()
      }
    }
  }

  @Override void mousePressed() {
    if (processing) return
    processing = true
    i = balls.size()
    next()
  }

  void next() {
    if (i > 1) {
      j = Math.random() * i as int
      i--
      t = 0
    } else {
      processing = false
    }
  }

  void swap() {
    if (i == j) return
    int temp   = balls[i].x
    balls[i].x = balls[j].x
    balls[j].x = temp
    Collections.swap(balls, i, j)
  }

  class Ball {
    int color, x, y
    Ball(color, x, y) {
      this.color  = color
      this.x      = x
      this.y      = y
    }
  }
}

PApplet.main(["FisherYates"] as String[])

Processing の値は主に float 型だが Groovy では float で演算した結果は double になるので注意する。

*1:griffon 関連はもう少し調べてからということで削除する

*2:静止した状態でよければ setup だけでよい

*3:Groovy スクリプトは小文字から始めるようにして、クラス名と衝突しないようにしている