サンプルプログラム集 (Golang)
このページでは、「亀場」を使ったサンプルコードをいくつか紹介します。 プログラムの実行には亀場サーバーが必要です。
また、turtle.go をダウンロードの上、turtleパッケージをimportできるよう設定しておいてください。
turtle
というサブディレクトリを作成の上、turtle.go
をその中に入れ、
環境に応じて、冒頭のimport文の"tfield/turtle"
のところを適宜調整("使用ディレクトリ名/turtle"
や"./turtle"
に変更)する必要があります。
フラクタル曲線
ヒルベルト曲線(Hilbert curve)を描くサンプルプログラム。描画が終わると、ロボット亀が登場するはず。
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() }
「ツリー」の描画
再帰を使って、樹形の様なパターンを描くプログラム。亀が行きつ戻りつしている様子が観察できます。
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モードを使っている。
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) }
ひまわり
右回りと左まわりの螺旋が「見える」だろうか。
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モードを使った例として。
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() }
雲のようなパターン
自己アフィンフラクタル(的)なパターンを生成する例。 少しリストが長くなるので、以下にリンクを設けました。 これと同様の手法が、コンピュータグラフィックスで地形や雲を生成する際に使われています。
ペンローズ・タイリング
二種類のタイルで非(準)周期的に空間を充填するペンローズ・タイリングの例。
掃除ロボット
亀場にコインを100枚ばらまいてから、カメがそれを回収します。 プログラムを複数走らせることによって、複数のカメが共同でお掃除してくれます。
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匹の亀が並列的にコイン集めをします。
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ルーチンの使い方の練習用のデモになります。
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が必要。
トランプによる「ハノイの塔」
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) }