やっつけな日常(11) スマホでGo!再帰で飛び込む頭山。懐かしのタートルグラフィクス

Joseph Halfmoon

Go言語をスマホ上で勉強し始めて約2週間、いまだfmt.Printに頼っております。前回、画像ファイルに直線を引けるようになったので、今回はその上に懐かしい、最近あまり聞かない、タートルグラフィクス命令を「被せて」みます。タートルグラフィクスといえば再帰的(あるいは頭山的)なやつですな。描いてみました。

上でタートルグラフィクスを最近あまり聞かないなどと書いてしまった直ぐ後で、このごろもご活躍の件、思い出してしまいました。ビジュアル言語 Scratchでは、タートルならぬ猫さんが活躍されていたのでした。Scratch系のビジュアル言語には当方もmicro:bit関係の投稿などでお世話になっております。

Go上に実装のタートルグラフィクス

今回Go言語上に実装したタートルグラフィクスは以下のようなものです。

    • 座標系は浮動小数指定、上がY軸+、右がX軸+の「普通な」2D平面
    • 角度は「度」単位。反時計方向が+、時計方向が-。
    • 原点は画像の中央、初期状態はX軸+向いている。
    • 今のところ命令は4つ。Mov、Rot、Color、PenDown。Movで進み、Rotで方向を回転。適宜Colorを変えれば色変可能。またPenDownで描きながら進み、PenDownをfalse(PenUp)すれば描かずに進む。

現状描画の下回りは前回作成の ppm モジュール(今回バグ発見してまた改版しましたが)ですが、Line描画関数さえ、適合するシグネチャ型?のものであれば呼び出して使えるので切り替えも簡単。

Androidスマホ上のTerumux環境でnano使って編集しているところが以下に。

turtleGoSRC

なお、配置場所は以下のパスとし、go.modを適宜配置してあります。

go/src/local/turtle

実際のソースコード全文が以下に。

package turtle

import "math"

type LineFunc func(x1 int, y1 int, x2 int, y2 int, r int, g int, b int) bool

type TurtleGraphics struct {
  xpos float64
  ypos float64
  direction float64
  ijSCALE float64
  iORG int
  jORG int
  ipos int
  jpos int
  iold int
  jold int
  colR int
  colG int
  colB int
  penDOWN bool
  line LineFunc
  verbose bool
}

func (t *TurtleGraphics) InitTurtleGraphics(x int, y int, sca float64, lf LineFunc) bool {
  t.xpos = 0.0
  t.ypos = 0.0
  t.direction = 0.0
  t.colR = 128
  t.colG = 128
  t.colB = 128
  t.penDOWN = true
  t.verbose = false
  t.line = lf
  t.ijSCALE = sca
  t.iORG = x
  t.jORG = y
  t.ipos = x
  t.jpos = y
  return true
}

func(t *TurtleGraphics) Mov(dist float64) bool {
  t.xpos += math.Cos(t.direction)*dist
  t.ypos += math.Sin(t.direction)*dist
  t.iold = t.ipos
  t.jold = t.jpos
  t.ipos = int(math.Round(t.xpos * t.ijSCALE)) + t.iORG
  t.jpos = t.jORG - int(math.Round(t.ypos * t.ijSCALE))
  if t.penDOWN {
    t.line(t.iold, t.jold, t.ipos, t.jpos, t.colR, t.colG, t.colB)
  }
  return true
}

func(t *TurtleGraphics) Rot(theta float64) bool {
  rad := theta * math.Pi / 180
  t.direction += rad
  if t.direction < (-math.Pi) {
    t.direction = 2.0*math.Pi + t.direction
  } else if t.direction > math.Pi {
    t.direction = t.direction - 2.0*math.Pi
  }
  return true
}

func(t* TurtleGraphics) Color(r int, g int, b int) {
  t.colR = r
  t.colG = g
  t.colB = b
}

func(t* TurtleGraphics) PenDown(x bool) {
  t.penDOWN = x
}
turtleモジュールを呼び出すmain側のソース

main側の実体は、再帰的なグラフィクスを描くための rotBox関数1個です。こいつで4角形を重ねて書いて、巻貝的(アンモナイト的?)な絵を描くと。main関数自体は、自作の2つのモジュール turtle と ppm を初期化し、rotBoxに最初のトリガをかけて、戻ってきたらばppmに出力するのみ。

なお、ppm モジュール内にバグがあったので訂正版が末尾にあります。

package main

import (
  "local/turtle"
  "local/ppm"
)

func rotBox(t *turtle.TurtleGraphics, siz float64, smax float64, th float64) {
  if siz > smax {
    return
  } else {
    t.Mov(siz)
    t.Rot(90)
    t.Mov(siz)
    t.Rot(90)
    t.Mov(siz)
    t.Rot(90)
    t.Mov(siz)
    t.Rot(90)
    t.Rot(th)
    rotBox(t, siz+0.25, smax, th)
  }
}

func main() {
  var img ppm.PpmImage
  XMAX:= 256
  YMAX:= 256
  img.InitPpmImage(XMAX, YMAX, 256)
  var ttl turtle.TurtleGraphics
  ttl.InitTurtleGraphics(128, 128, 10.0, img.Line)
  ttl.Color(255, 0, 128)
  rotBox(&ttl, 1.0, 8.0, 5.0)
  img.WritePPM()
}
実行結果

上記のメイン関数(turtleTST3.go)は、以下のようなパスにおき、かつ、そこのgo.modファイル内に ppm と turtle モジュールを発見できるように記載をしてあります。

go/proj/ttl

以下のようにして実行

$ go run turtleTST3.go > turtleSample.ppm
$ convert turtleSample.ppm turtleSample.png

なお ppm形式からpng形式への変換使っております convertコマンドは、ImageMagickの要インストールであります。

描いた「タートルグラフィクス」を表のAndroidアプリで眺めたところが以下に。

turtleSample_SC

懐かしのタートルグラフィクス。遥かな昔、40年近く前ですな、Sony(Z80)やAtari(68K)のマシンの上で、今は亡きデジタル・リサーチ社製のDR. LOGOというLOGO処理系で遊びましたです。こんどLOGOも久しぶりにやってみますかねえ。First、ButFirst(LispのCAR、CDRみたいなもの)

やっつけな日常(10) スマホでGo! Line描画機能をppmパッケージに追加 へ進む

やっつけな日常(12) スマホでGo!1.26186…次元から染み出すコッホ曲線 へ進む

BUG FIX版:  grad2d.go
package ppm

import (
  "fmt"
  "os"
)

func (this *PpmImage) Box(x1 int, y1 int, x2 int, y2 int, r int, g int, b int) bool {
  if x1 > x2 {
    x1, x2 = x2, x1
  }
  if y1 > y2 {
    y1, y2 = y2, y1
  }
  for x := x1; x<=x2; x++ {
    this.DotXY(x, y1, r, g, b)
    this.DotXY(x, y2, r, g, b)
  }
  for y := y1; y<=y2; y++ {
    this.DotXY(x1, y, r, g, b)
    this.DotXY(x2, y, r, g, b)
  }
  return true
}

func (this *PpmImage) Line(x1 int, y1 int, x2 int, y2 int, r int, g int, b int) bool {
  var dx int
  var dy int
  var x, y int
  var revx bool = false
  var revy bool = false
  if x1 > x2 {
    dx = x1 - x2
    revx = true
  } else {
    dx = x2 - x1
  }
  if y1 > y2 {
    dy = y1 - y2
    revy = true
  } else {
    dy = y2 - y1
  }
  if dx > dy {
    fmt.Fprintln(os.Stderr, "X mode ", x1, y2, dx, dy)
    if revx {
      fmt.Fprintln(os.Stderr, "REVX")
      x1, x2 = x2, x1
      y1, y2 = y2, y1
    }
    dy = y2 - y1
    for x= 0; x<=dx; x++ {
      if dx != 0 {
        y= (dy * x) / dx
      } else {
        y= 0
      }
      this.DotXY(x+x1, y+y1, r, g, b)
    }
  } else {
    fmt.Fprintln(os.Stderr, "Y mode", x1, y2, dx, dy)
    if revy {
      fmt.Fprintln(os.Stderr, "REVY")
      x1, x2 = x2, x1
      y1, y2 = y2, y1
    }
    dx = x2 - x1
    for y= 0; y<=dy; y++ {
      if dy != 0 {
        x= (dx * y) / dy
      } else {
        x= 0
      }
      this.DotXY(x+x1, y+y1, r, g, b)
    }
  }
  return true
}