Python 3.0 Hacks

第3回turtleモジュールで図と戯れる

Python 2.5以前からある「turtle」モジュールが、Python 2.6/3.0からパワーアップしています。新しいturtleモジュールを利用すると、オブジェクト指向アプローチでさまざまなデータの可視化ができます。turtleモジュールはtkinterを利用しただけの単一モジュールになっており、140Kバイトもあります。すべての機能を紹介することはこのスペースでは無理ですので、主要な機能だけをダイジェストで紹介します。

プロッティング

このモジュールにおいて最も重要な「Turtleオブジェクト」はペンプロッタのような役割を持っています。

以下のコードで、Turtleオブジェクトを作成できます。

from turtle import *
t = Turtle()

ペンプロッタのような役割として、以下のメソッドがあります。

表1
メソッド 動作
t.penup() ペン先をキャンバスから離す
t.pendown() ペン先をキャンバスに置く
t.setpos(x, y) 任意の座標まで移動する
t.pencolor('red') 線の色を変更する
t.pensize(3) 線の太さを変更する
t.write('hello!') 現在位置のすぐ上に文字列を描画

ペンプロッタではペンの現在位置を保持して動作しますが、Turtleオブジェクトではさらに「向き」を保持しています。そして、Turtleオブジェクトが自ら動く以下のような指令も持っています。

表2
メソッド 動作
t.forward(10) 現在の向きに10距離前進する
t.left(10) 10度左に向きを変える
t.right(10) 10度右に向きを変える
t.circle(50,240) 回転半径50で270度の円弧を描くように進む

これらの指令は、turtleモジュールに定義されているTurtleクラスのインスタンスに対して使うのが普通ですが、turtleモジュールそのものに対して使う方法もあります。この場合、Turtleインスタンスを作らなくても自動的に作成してくれるようです。

サンプル1

リスト1のコードで陰陽太極図のようなマーク図1が描けます。

リスト1
from turtle import *
t = Turtle()      # タートルオブジェクト作成(初めて作るときはウインドウも作成されます)
t.circle(50,180)  # 反時計回り半径50で180度まで
t.circle(-50,180) # 時計回り半径50で180度まで
t.circle(-100)    # 時計回り半径100で360度まで
exitonclick()     # ウインドウクリックするまで待機
図1 リスト1の実行結果
図1 リスト1の実行結果

図形の塗りつぶしを行いたい場合は、リスト2のように「fillcolor」指定ののち一連の図形描画命令を「begin_fill()」⁠end_fill()」の2命令で囲んでください。

サンプル2 - 塗りつぶし

リスト2
from turtle import *
t = Turtle()
t.fillcolor('blue')
t.begin_fill()
t.setpos(-50,100)
t.setpos(50,100)
t.setpos(0,0)
t.end_fill()
exitonclick()
図2 リスト2の実行結果
図2 リスト2の実行結果

閉じた図形ではない場合は、始点と終点が接続されたように塗りつぶします。

イベントハンドリング

turtleモジュールはtkinterを利用して作られていますので、tkinterのイベントハンドリングがそのまま利用できますが、一般によく使うイベントに関しては、単純化して利用しやすくしてあります。

イベントハンドリングは以下の2グループに分かれています。

  • Turtleオブジェクトのためのイベントハンドリング
  • Screenオブジェクトのためのイベントハンドリング

Turtleオブジェクトのイベントハンドラでは、マーカー(亀マークや三角マークなど)をマウスカーソルで操作したときの挙動をカスタマイズします。

def イベントハンドラ(x,y):
  ... #任意の記述

というような関数定義をしておいて、

turtle.onclick(イベントハンドラ)

というような操作をしておくと、turtleオブジェクトをクリックしたときに任意の挙動を実行します。

Turtleオブジェクト用のイベントには以下の3つがあります。

表3
イベント 動作
onclick クリックしたときに実行する関数を登録
ondrag ドラッグしたときに実行する関数を登録
onrelease クリックを解除したときに実行する関数を登録

Screenオブジェクトとは、描画が行われるキャンバスのことです。Screenオブジェクト用のイベントには以下の3つがあります。

表4
イベント 動作
onkey キーボードのキーを押したときに実行する関数を登録
onscreenclick スクリーンをクリックしたときに実行する関数を登録
ontimer 指定時間経過時に実行する関数を登録

「onkey」「ontimer」のイベントハンドラ関数は引数なしです。その他のマウスカーソルに関わるイベントハンドラ関数では引数に「x」「y」が渡されますが、それらは実際のマウスカーソルの位置です。

サンプル3 - スクリーンクリックイベント

クリックしたところにタートルが移動します図3⁠。

リスト3
from turtle import *
t = Turtle()         # タートルオブジェクトの作成
def click(x,y):      # イベントハンドラの定義
  t.setpos(x,y)      # クリック座標に移動
onscreenclick(click) # イベントハンドラの登録
mainloop()           # イベントループ
図3 リスト3の実行結果
図3 リスト3の実行結果

サンプル4 - キーイベントハンドリング

エスケープキーを押すと終了します。

リスト4
from turtle import *
def escape():
  bye()
onkey(escape, 'Escape')
listen()
mainloop()

onkeyの第2引数は必須でキーシンボルが必要です。また、⁠listen()」という記述はウインドウフォーカスを獲得する関数です。⁠bye()」はウインドウを閉じてメインループを終了する関数です。

サンプル5 - タイマーイベントハンドリング

1秒ごとに時計回りを6度ずつ移動させる(時計の秒針のような動き⁠⁠。

リスト5
from turtle import *
t = Turtle()
def tick():
  t.circle(-100, 6)
  ontimer(tick, 1000)
ontimer(tick, 1000)
mainloop()
図4 リスト5の実行結果
図4 リスト5の実行結果

まとめ

プロッティングとイベントハンドリングを駆使することで、チャート描画に用いたり、グラフ理論等の可視化に用いることもできます。対話的に操作できるというのがこのturtleモジュールの特徴でもあります。私の場合、ロボットの経路アルゴリズム動作の可視化などに使っています。

今回は主要機能だけを紹介しました。以下のリファレンスページをご覧いただければ、もう少し凝ったことができます。

おまけ

最後にちょっとお遊びなサンプルを紹介します。ロボット関連開発では実機テストが自由にできる期間が限られるので、動作アルゴリズムの検証にこういったビジュアライザがあると非常に助かります。ここでは、ラジコンカーのシミュレータを作成してみます。ジョイスティックを模した赤い丸をドラッグすることで「車を模した亀」を操縦できます。

リスト6 ラジコンカーシミュレーション
from turtle import *

class Joystick(Turtle):
  def __init__(self, pos=(100,100), radius=50):
    self.input = Vec2D(0,0)
    self.center = Vec2D(*pos)
    self.radius = radius
    super().__init__(shape='circle')
    tracer(0)
    self.penup()
    self.setpos(self.center-(0,self.radius))
    self.pendown()
    self.pen(pensize=3, fillcolor='gray')
    self.circle(self.radius)
    self.penup()
    self.fillcolor('red')
    self.setpos(self.center)
    tracer(1)
    self.target = None
    self.ondrag(self.Drag)
    self.onrelease(self.Release)

  def Release(self, x, y):
    self.setpos(self.center)
    self.input = Vec2D(0.,0.)

  def Drag(self, x, y):
    vec = Vec2D(x,y) - self.center
    if abs(vec)>self.radius:
      vec *= self.radius/abs(vec)
      x, y = self.center + vec
    self.input = (Vec2D(x,y) - self.center)*self.radius**-1
    self.setpos(x,y)

class Cart(Turtle):
  def __init__(self):
    super().__init__(shape='turtle')
    self.speed(0)
    self.velocity = 0.0
    self.handle = 0.0

  def Tick(self, dt):
    distance = self.velocity*dt
    self.forward(50.0*distance)
    self.setheading(self.heading() + 200.0*self.handle*distance)

stick = Joystick()
cart = Cart()

def tick():
  dt = 0.1
  cart.handle = -stick.input[0]
  cart.velocity = stick.input[1]
  cart.Tick(dt)
  ontimer(tick, int(dt*1000))

ontimer(tick)
mainloop()
図5 リスト6の実行結果
図5 リスト6の実行結果

おすすめ記事

記事・ニュース一覧