サンプルプログラム集 (Golang)

このページでは、「亀場」を使ったサンプルコードをいくつか紹介します。 プログラムの実行には亀場サーバーが必要です。

また、turtle.go をダウンロードの上、turtleパッケージをimportできるよう設定しておいてください。 turtleというサブディレクトリを作成の上、turtle.goをその中に入れ、 環境に応じて、冒頭のimport文の"tfield/turtle"のところを適宜調整("使用ディレクトリ名/turtle""./turtle"に変更)する必要があります。

フラクタル曲線

ヒルベルト曲線(Hilbert curve)を描くサンプルプログラム。描画が終わると、ロボット亀が登場するはず。

tfield-examples-hilbert-small
package main

import (
	"tfield/turtle"
)

const (
	STEP = 13
)

func hilbert(t *turtle.Turtle, level int, angle float64) {
	if level<=0 {
		return
	}
	t.Rt(angle)
	hilbert(t,level-1,-angle)
	t.Fd(STEP)
	t.Lt(angle)
	hilbert(t,level-1,angle)
	t.Fd(STEP)
	hilbert(t,level-1,angle)
	t.Lt(angle)
	t.Fd(STEP)
	hilbert(t,level-1,-angle)
	t.Rt(angle)
}

func main() {
	ttl := new(turtle.Turtle)
	ttl.Connect("localhost")
	ttl.Clr()
	ttl.Rst()
	ttl.Lw(2)
	ttl.Col(1.0,1.0,0.0)
	ttl.Bk(200)
	ttl.Lt(90)
	ttl.Fd(200)
	ttl.Rt(90)
	ttl.Pd()
	hilbert(ttl,5,90.0)
	ttl.Fire()
}

「ツリー」の描画

再帰を使って、樹形の様なパターンを描くプログラム。亀が行きつ戻りつしている様子が観察できます。

tfield-examples-tree-small
package main

import (
	"tfield/turtle"
)

func branch(ttl *turtle.Turtle, step float64) {
	if step<4.0 {
		return
	} else {
		ttl.Lt(20)
		ttl.Pd() ; ttl.Fd(step) ; ttl.Pu()
		branch(ttl,step*0.6) 
		ttl.Bk(step)
		ttl.Rt(20)

		ttl.Rt(10)
		ttl.Pd() ; ttl.Fd(step) ; ttl.Pu() ;
		branch(ttl,step*0.7)
		ttl.Bk(step)
		ttl.Lt(10)
	}
}

func main() {
	ttl := new(turtle.Turtle)
	ttl.Connect("localhost")
	ttl.Clr()
	ttl.Rst()
	ttl.Col(0.1,0.7,0.2)
	ttl.Jump(0,-220)
	ttl.North()
	branch(ttl,150)
}

シェルピンスキーのガスケット

これも代表的なフラクタル図形。FILLモードを使っている。

tfield-examples-sierpinski-small
package main

import(
	"tfield/turtle"
)

func triangle(ttl *turtle.Turtle, size float64) {
	ttl.Fill(); ttl.Pd(); ttl.Fd(size)
	ttl.Lt(120); ttl.Fd(size); ttl.Lt(120)
	ttl.Fd(size); ttl.Pu(); ttl.Lt(120)
}

func sierpinski(ttl *turtle.Turtle, size float64) {
	if size<20.0 {
		triangle(ttl,size)
		return
	} else {
		sierpinski(ttl,size/2.0)
		ttl.Fd(size/2.0)
		sierpinski(ttl,size/2.0)
		ttl.Lt(120)
		ttl.Fd(size/2.0)
		ttl.Rt(120)
		sierpinski(ttl,size/2.0)
		ttl.Rt(120)
		ttl.Fd(size/2.0)
		ttl.Lt(120)
	}
}

func main() {
	ttl := new(turtle.Turtle)
	ttl.Connect("localhost")
	ttl.Clr()
	ttl.Jump(-250,-200)
	ttl.East()
	sierpinski(ttl,500)
}

ひまわり

右回りと左まわりの螺旋が「見える」だろうか。

tfield-examples-sunflower
package main

import (
	"tfield/turtle"
	"math"
)

func seed(ttl *turtle.Turtle, x float64, y float64, size float64) {
	step := size*math.Sqrt(2.0)
	ttl.Jump(x,y)
	ttl.East() ;
	ttl.Lt(math.Atan2(y,x)*180.0/math.Pi)
	ttl.Fd(size)
	ttl.Lt(135)
	ttl.Pd()
	ttl.Fd(step); ttl.Lt(90); ttl.Fd(step)
	ttl.Lt(90); ttl.Fd(step); ttl.Lt(90); ttl.Fd(step)
	ttl.Pu()
}

func main() {
	ttl := new(turtle.Turtle)
	ttl.Connect("localhost")

	GR := (1.0+math.Sqrt(5.0))*0.5 

	ttl.Clr()
	ttl.Fill()
	ttl.Col(1,1,0)
	for n:=1; n<250; n++ {
		x := float64(n)*math.Cos(2.0*math.Pi/(GR*GR)*float64(n))
		y := float64(n)*math.Sin(2.0*math.Pi/(GR*GR)*float64(n))
		seed(ttl,x,y,math.Sqrt(float64(n)))
	}
}

書道

「亀」と書くプログラム。Goのプログラムとしては全く見所はありませんが、BRUSHモードを使った例として。

tfield-examples-brush-kame-small
package main

import(
	"tfield/turtle"
)

func main() {
	t := new(turtle.Turtle)
	t.Connect("localhost")
	t.Rst()
	t.Bgc(0.8,0.8,0.7)
	t.Clr()
	t.Col(0,0,0) ;
	t.Brush()
	t.Lw(30)
	t.Jump(10,205)
	t.West(); t.Lt(40) ; t.Pd() ; t.Fd(100) ; t.Pu()
	t.Jump(0,190) ; t.East() ; t.Rt(30) ; t.Pd() ; t.Fd(40) ; t.Rt(120) ; t.Fd(100) ; t.Pu()
	t.Jump(-90,120) ; t.East() ; t.Lt(5) ; 
	t.Pd() ; t.Fd(170) ; t.Rt(100) ; t.Fd(80) ; t.Fd(0) ; t.Pu()
	t.Jump(-90, 100) ; t.South() ; t.Lt(8) ; t.Pd() ; t.Fd(60) ; t.Fd(0) ; t.Pu() 
	t.Jump(-70, 80) ; t.East() ; t.Lt(5) ; t.Pd() ; t.Fd(130) ; t.Fd(0) ; t.Pu()
	t.Jump(-70, 40) ; t.East() ; t.Lt(5) ; t.Pd() ; t.Fd(130) ; t.Fd(0) ; t.Pu()
	t.Jump(-130,0) ; t.East() ; t.Lt(5) ; t.Pd() ; t.Fd(250) ; t.Rt(100) ; t.Fd(90) ; t.Fd(0) ; t.Pu()
	t.Jump(-120, -5) ; t.South() ; t.Lt(8) ; t.Pd() ; t.Fd(80) ; t.Fd(0) ; t.Pu()
	t.Jump(-100, -40) ; t.East() ; t.Lt(5) ; t.Pd() ; t.Fd(200) ; t.Fd(0) ; t.Pu()
	t.Jump(-110, -80) ; t.East() ; t.Lt(5) ; t.Pd() ; t.Fd(220) ; t.Fd(0) ; t.Pu()
	t.Lw(35)
	t.Jump(0,110) ; t.South(); t.Rt(5)
	t.Pd() ; t.Fd(210)
	for cnt:=0; cnt<10; cnt++ {
		t.Lt(10) ; t.Fd(8)
	}
	t.Fd(120) ; t.Lt(85) ; t.Fd(50) ; t.Pu()
}

雲のようなパターン

自己アフィンフラクタル(的)なパターンを生成する例。 少しリストが長くなるので、以下にリンクを設けました。 これと同様の手法が、コンピュータグラフィックスで地形や雲を生成する際に使われています。

Golangコード (cloud4.go)

tfield-examples-cloud4

ペンローズ・タイリング

二種類のタイルで非(準)周期的に空間を充填するペンローズ・タイリングの例。

Golangコード (penrose-tile.go)

images/penrose-tile

掃除ロボット

亀場にコインを100枚ばらまいてから、カメがそれを回収します。 プログラムを複数走らせることによって、複数のカメが共同でお掃除してくれます。

tfield-examples-cleaning-small
package main

import (
	"tfield/turtle"
)

func run_into_wall(t turtle.Turtle, timecode int) {
	for cnt:=0; cnt<60; cnt++ {
		t.Lt(3)
	}
}

func run_into_turtle(t turtle.Turtle, timecode int) {
	for cnt:=0; cnt<40; cnt++ {
		t.Lt(3)
	}
}

func found_coin(t turtle.Turtle, timecode int) {
	t.PickCoin()
}

func main() {
	ttl := new(turtle.Turtle)

	ttl.Connect("localhost")		
	
	ttl.SetRunIntoWallCallback(run_into_wall)
	ttl.SetRunIntoTurtleCallback(run_into_turtle)
	ttl.SetFoundCoinCallback(found_coin)
	
	ttl.Bmode()
	ttl.Nm("KAME")

	if ttl.Q_Nt()==1 {
		for cnt:=0; cnt<100; cnt++ {
			ttl.Coin()
		}
	}
	
	for {
		ttl.Fd(2)
		ttl.Rt(0.2)
	}
}

コイン集め

上の例と同様に、コインを100枚散らかしてから、「コインが見つかったときに、もしコインを持っていたらその場に落とす、 持っていなければ拾う」を繰り返す例です。 ここで、「コインが見つかる」とは、「周囲にコインが無い状態からある状態に変化した」という事象のことです。 プログラムを走らせているうちに、(効率は悪いですが)だんだんとコインが集まっていく様子が観察できるはずです。

この例では、goルーチンを使って、10匹の亀が並列的にコイン集めをします。

tfield-examples-coin-move-small

10匹の亀が同時にコイン集めをしている例

package main

import (
	"tfield/turtle"
)

func run_into_wall(ttl turtle.Turtle, tm int) {
	for cnt := 0; cnt < 50; cnt++ {
		ttl.Lt(3)
	}
}

func run_into_turtle(ttl turtle.Turtle, tm int) {
	for cnt := 0; cnt < 50; cnt++ {
		ttl.Rt(3)
	}
}

func found_coin(ttl turtle.Turtle, tm int) {
	nc := ttl.Q_Mycoin()
	if nc > 0 {
		ttl.DropCoin()
	} else {
		ttl.PickCoin()
	}
}

func walk_about(ttl *turtle.Turtle, c chan bool) {
	for {
		ttl.Fd(2)
		ttl.Rt(0.2)
	}
	c <- true
}

const (
	n = 10
)

func main() {
	var ttls [n]*turtle.Turtle
	for i := 0; i < n; i++ {
		ttl := new(turtle.Turtle)
		ttl.Connect("localhost")
		ttl.Bmode()
		ttl.Nm("kame")

		ttl.SetRunIntoWallCallback(run_into_wall)
		ttl.SetRunIntoTurtleCallback(run_into_turtle)
		ttl.SetFoundCoinCallback(found_coin)

		if ttl.Q_Nt() == 1 {
			for cnt := 0; cnt < 100; cnt++ {
				ttl.Coin()
			}
		}
		ttls[i] = ttl
	}

	done := make(chan bool)
	for i := 0; i < n; i++ {
		go walk_about(ttls[i], done)
	}
	<-done
}

亀の戦闘

10個のドーナツをばらまいた後に、10匹の亀が現れ、移動しながら、ドーナツと他の亀と衝突すると、砲弾を発射します。 ドーナツに命中するとポイントが加算され、被弾すると減点されながら、ポイントが0になると亀場から退場となります。

goルーチンの使い方の練習用のデモになります。

tfield-examples-battle-10

10匹の亀が戦闘している様子

package main

import (
	"tfield/turtle"
	"strconv"
	"sync"
	"fmt"
)

func run_into_wall(ttl turtle.Turtle, tm int) {
	for cnt := 0; cnt < 50; cnt++ {
		ttl.Lt(3)
	}
}

func run_into_turtle(ttl turtle.Turtle, tm int) {
	ttl.Fire()	
	for cnt := 0; cnt < 50; cnt++ {
		ttl.Rt(3)
	}
}

func run_into_donut(ttl turtle.Turtle, tm int) {
	ttl.Fire()
	for cnt := 0; cnt < 50; cnt++ {
		ttl.Rt(3)
	}
}

func walk_about(ttl *turtle.Turtle) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("killed")
		}
	}()
	for {
		ttl.Fd(2)
		ttl.Rt(0.2)
	}
}

const (
	n = 10
)

func main() {
	var ttls [n]*turtle.Turtle
	for i := 0; i < n; i++ {
		ttl := new(turtle.Turtle)
		ttl.Connect("localhost")
		ttl.Bmode()
		ttl.Nm("kame"+strconv.Itoa(i))

		ttl.SetRunIntoWallCallback(run_into_wall)
		ttl.SetRunIntoTurtleCallback(run_into_turtle)
		ttl.SetRunIntoDonutCallback(run_into_donut)

		if ttl.Q_Nt() == 1 {
			for cnt := 0; cnt < 30; cnt++ {
				ttl.Donut()
			}
		}
		ttls[i] = ttl
	}

	var wg sync.WaitGroup
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func(i int) {
			walk_about(ttls[i])
			wg.Done()
		}(i)
	}
	wg.Wait()
}

ハノイのカード

よく知られた「ハノイの塔」をトランプのカードを使って表示するデモ。 動作には、2017年6月版以降のバージョンの亀場サーバーとturtle.goが必要。

tfield-examples-card-hanoi

トランプによる「ハノイの塔」

package main

import (
	"tfield/turtle"
)

func redraw(x int) {
	ttl.Clr()
	for i:=0;i<3;i++ {
		for j:=0;j<ncard[i];j++ {
			k := card[i][j]
			ttl.Card(k, float64(-200 + i*150.0 - j*2), float64(-220+j*30.0))
		}
	}
	ttl.Jump(float64(-160 + x*150.0), float64(-140+(ncard[x]-1)*30.0))
}

func move_card(x int, y int) {
	if ncard[x]>0 {
		ncard[x]-=1
		card[y][ncard[y]] = card[x][ncard[x]]
		ncard[y]+=1
	}
	redraw(y)
}

func move(n int, x int, y int, z int) {
	if n==2 {
		move_card(x,z)
		move_card(x,y)
		move_card(z,y)
	} else {
		move(n-1,x,z,y)
		move_card(x,y)
		move(n-1,z,y,x)
	}
}

var ncard = []int{0,0,0}
var card = make([][]int,3)
var ttl *turtle.Turtle

func main() {
	ttl = new(turtle.Turtle)
	
	ttl.Connect("localhost")
	for i:=0; i<3; i++ {
		card[i] = make([]int, 53)
		for j:=0; j<53; j++ {
			card[i][j] = j
		}
	}

	var n=13
	ttl.Clr()
	ttl.ClrCard()
	ttl.Bgc(0.1, 0.3, 0.1)
	ttl.North()
	ncard[0]=n
	for k:=0;k<n;k++ {
		card[0][k] = 39+n-k
	}
	
	redraw(0)
	move(n,0,2,1)
}