2018年12月2日 | ブログ記事

Pythonでタートルグラフィックス【アドベントカレンダー2018 39日目】

shirodoni

はじめまして18のshirodoniです。
今回は亀で遊んでみようと思います。

タートルグラフィックスとは

1960年代末に登場した教育用プログラミング言語「LOGO」に搭載されていた機能で、
ペンを持たせた亀に命令を出して絵を描くことができるというものです。

ペンを上げたり色を変えたりもできるので、
様々な図形を描くことができます。

環境

Pythonにはデフォルトでタートルグラフィックス機能が付属しています。
(環境によってはグラフィックスライブラリをインストールする必要があるかもしれません)
今回使用するのはPython3です。

動作デモ

動作デモを見てみましょう。

python3 -m turtle

----------2018-11-02-15.24.44

python3 -m turtledemo

こちらでは複数のデモを見ることができます。

亀を操る

亀の用意

まずはturtleモジュールをimportします。

from turtle import *

次にタートルオブジェクトを作成します。

kame = Turtle()

これにて準備完了です。

詳しくは公式ドキュメントを参照すると幸せになれます。

実践

肩慣らし

144度回転して前進という操作を5回繰り返してみます。

from turtle import *

kame = Turtle()

for i in range(5):
    kame.right(144)
    kame.forward(200)

# クリックで終了
screen = Screen()
screen.exitonclick()

----------2018-12-02-20.46.18

お星様ができました。

コッホ曲線を描く

再帰関数を使うと楽に描けます!

from turtle import *

kame = Turtle()
kame.speed(0)

def draw(dist, step):
    if step > 0:
        draw(dist / 3, step - 1)
        kame.left(60)
        draw(dist / 3, step - 1)
        kame.right(120)
        draw(dist / 3, step - 1)
        kame.left(60)
        draw(dist / 3, step - 1)
    else:
        kame.forward(dist)

draw(100, 1)
draw(100, 2)
draw(100, 3)
draw(100, 4)

screen = Screen()
screen.exitonclick()

----------2018-12-02-20.25.53

3つ組み合わせるとコッホ雪片です。

for i in range(3):
    draw(300, 4)
    kame.right(120)

----------2018-12-02-20.42.57

2重振り子の軌跡を描く

タートルグラフィックスの本来の用途から少しずれているような気もしますが…
pd
適当な初期条件で亀に上図のような2重振り子の2番目のおもりの位置を歩かせてみることにします。

2重振り子は振れ幅が大きいときに初期値を少しでも変えると軌跡が大きく変化することで有名です。これを実際に試せるようにしてみましょう。
最初に青色の軌跡を描かせて、クリックすると初期条件のθ2\theta_20.10.1度だけずらした赤色の軌跡を描くようにしました。
----------2018-12-02-20.53.57
完成したプログラムはこちらです。

長いので折りたたみ
from turtle import *
from math import sin, cos, pi
import random
import time

# 質量[kg]
M1 = 1.0
M2 = 1.0
# 棒の長さ[m]
L1 = 0.5
L2 = 0.5
# 重力加速度[m/s^2]
G = 9.8

# 初期角度[rad]
R1 = random.random() * pi * 2
R2 = random.random() * pi * 2
# 初期角速度[rad/s]
V1 = 0
V2 = 0

# 1[m]に相当する長さ
pxm = 256

def bibun(r1, r2, v1, v2):
    sin1 = sin(r1)
    cos1 = cos(r1)
    sin2 = sin(r2)
    sin21 = sin(r1 - r2)
    cos21 = cos(r1 - r2)
    a1 = (-M2 * L1 * sin21 * cos21 * v1 * v1 - M2 * L2 * sin21 * v2 * v2 + M2 * G * sin2 * cos21 - (M1 + M2) * G * sin1) / L1 / (M1 + M2 * sin21 * sin21)
    a2 = ((M1 + M2) * L1 * sin21 * v1 * v1 + M2 * L2 * sin21 * cos21 * v2 * v2 + (M1 + M2) * G * sin21 * cos1) / L2 / (M1 + M2 * sin21 * sin21)
    return [v1, v2, a1, a2]

screen = Screen()

kame0 = Turtle()    # 固定点
kame1 = Turtle()    # おもり1
kame2 = Turtle()    # おもり2
kame1.speed(0)
kame2.speed(0)
kame0.shape("square")
kame1.shape("circle")
kame2.shape("turtle")
kame1.penup()

# 軌跡の描画
def draw(r1, r2):
    x1 = L1 * sin(r1) * pxm
    y1 = -L1 * cos(r1) * pxm
    x2 = x1 + L2 * sin(r2) * pxm
    y2 = y1 - L2 * cos(r2) * pxm
    kame1.goto(x1, y1)
    kame2.goto(x2, y2)    

# 振り子の初期化、軌跡の色変更
def init(R1, R2, V1, V2, color):
    global r1, r2, v1, v2
    r1, r2, v1, v2 = R1, R2, V1, V2
    kame2.color(color)
    kame2.penup()
    draw(r1, r2)
    kame2.pendown()

init(R1, R2, V1, V2, "blue")
state = 0
clicked = False

# 描画の時間間隔[s]
Wait = 1 / 30
# シミュレーションのタイムステップ[s]
Loop = 100
Step = Wait / Loop

def loop():
    global r1, r2, v1, v2, clicked, state

    start = time.time()

    # ルンゲ・クッタ法による数値積分
    for i in range(Loop):
        k1v1, k1v2, k1a1, k1a2 = bibun(r1, r2, v1, v2)
        k2v1, k2v2, k2a1, k2a2 = bibun(r1 + k1v1 * Step / 2, r2 + k1v2 * Step / 2, v1 + k1a1 * Step / 2, v2 + k1a2 * Step / 2)
        k3v1, k3v2, k3a1, k3a2 = bibun(r1 + k2v1 * Step / 2, r2 + k2v2 * Step / 2, v1 + k2a1 * Step / 2, v2 + k2a2 * Step / 2)
        k4v1, k4v2, k4a1, k4a2 = bibun(r1 + k2v1 * Step, r2 + k2v2 * Step, v1 + k2a1 * Step, v2 + k2a2 * Step)
        r1 += (k1v1 + k2v1 * 2 + k3v1 * 2 + k4v1) * Step / 6
        r2 += (k1v2 + k2v2 * 2 + k3v2 * 2 + k4v2) * Step / 6
        v1 += (k1a1 + k2a1 * 2 + k3a1 * 2 + k4a1) * Step / 6
        v2 += (k1a2 + k2a2 * 2 + k3a2 * 2 + k4a2) * Step / 6

    # タートルの移動
    draw(r1, r2)

    if clicked:
        clicked = False
        # 色を変え初期条件をわずかにずらしてもう一度
        if state == 0:
            init(R1, R2 + 0.1 * pi / 180, V1, V2, "red")
            state = 1
        # 終了
        elif state == 1:
            screen.bye()

    # 計算にかかった時間を計測して待機する時間を調整する
    diff = time.time() - start
    screen.ontimer(loop, max(round((Wait - diff) * 1000), 0))

def click(x, y):
    global clicked
    clicked = True
screen.onclick(click)

screen.ontimer(loop, 0)
screen.mainloop()

おわりに

タートルグラフィックスの強みといえば、やはりその手軽さでしょうか?
それがPythonにデフォルトで入っているということで、手軽さが極まったなあと感じます。たぶんデフォルトの機能じゃなければ私は一生これに触ることはなかったと思うので。

それでいてシンプルなコードで綺麗な図形を描けるので奥が深いです。
私にはそういったセンスがなくてつい2重振り子を作ってしまったのですが…

明日はopferさんとninjaさんの記事です。お楽しみに!

この記事を書いた人
shirodoni

難解プログラミング言語に手をだしたい。

この記事をシェア

このエントリーをはてなブックマークに追加

関連する記事

2018年12月19日
ゾーマからの脱出〜後編〜
ran
2018年12月18日
ゾーマからの脱出~前編~
Meffi
2018年12月17日
ワンモデ参加記
nao
2018年12月16日
2018年のクッキー職人が知っておくべきこと
spa
2018年12月16日
ICPCアジア地区横浜大会参加記【アドベントカレンダー2018 52日目】
eiya
2018年12月15日
合宿免許【アドベントカレンダー2018 52日目】
osushi

活動の紹介

カテゴリ

タグ