サンプルプログラム集 (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)
}