タートルグラフィックスで学ぶ Python のキホン 第 8 章
教材コード
第 8 章

たくさんのタートル

かめを増やして,名前をつけて,別々に動かします.

第 2 章からここまで,画面にいるかめはずっと 1 匹でした. fd(100) と書けばそのかめが進み,lt(90) と書けばそのかめが回る. 命令する相手が 1 匹しかいないので,相手を指定する必要がなかったのです.

今回はかめを何匹でも増やせるようにします. そうなると「どのかめに命令するのか」を書かなければなりません. そのために,かめを変数に入れて名前をつけ,その名前に向かって命令を出す という書き方を覚えます.これは Python だけの話ではなく, 現在のプログラミングで最もよく使われている考え方 (オブジェクト指向)の入口でもあります.

この章でできるようになること
  • t = Turtle() でかめを何匹でも作れる
  • t.fd(100) のように,相手を指定して命令が出せる
  • これまでの fd(100) が誰への命令だったのかを説明できる
  • かめごとに色・太さ・形・大きさを変えられる
  • 複数のかめを同時に動いているように見せられる
  • distance・towards でかめ同士の関係を調べられる
今回のいちばんの山場 今回のプログラムは「かめを作る」「かめに命令する」の 2 つでできています. 長さも行数も今までと変わりませんが,書き方の形が変わります. 最初の 8.1 節をていねいに読んでください.ここさえ越えれば,あとは今までと同じです.

8.1 かめを増やす

8.1.1 なぜ 1 匹では足りないのか

前の章(第 7 章)までで,if と for, そして繰り返しの入れ子まで覚えました. 1 匹のかめで描ける絵なら,もうたいていのものは描けます. しかし,次のようなことはまだできません.

  • 2 つの絵が同時に育っていくアニメーション(左の円と右の円が一緒に描かれていく)
  • 競走.3 匹が横に並んで,それぞれ違う速さで進む
  • 追いかけっこ.片方がもう片方を追いかける
  • 画面のあちこちにばらばらの状態(位置・向き・色)を同時に持たせること

どれも「状態を持った登場人物が複数いる」という点で共通しています. かめが 1 匹しかいないと,位置も向きも色も 1 組しか持てません. goto で行ったり来たりさせれば真似はできますが,すぐに破綻します. 登場人物が 2 人いるなら,かめも 2 匹用意するのが素直です.

8.1.2 これまでの fd(100) は誰への命令だったのか

from turtle import * と書いて fd(100) と命令したとき, 実は舞台裏で名前のないかめが 1 匹用意されていました. かめの名前を付けずに書いた fd(100) は,その「名前のないかめ」への命令だったのです. この教材ではこれを既定(きてい)のかめと呼ぶことにします.

既定のかめにも,あとから名前をつけることができます. getturtle() と書くと,既定のかめを受け取れます.

default_turtle.py
from turtle import *

pensize(3)
fd(100)              # 名前のないかめ(既定のかめ)への命令

kame = getturtle()   # そのかめに「kame」という名前をつける

kame.lt(90)          # 今度は名前を指定して命令する
kame.fd(100)

done()
図 8.1 前半と後半で書き方が違うが,動いているのは同じ 1 匹.

前半の fd(100) と後半の kame.fd(100) は, まったく同じかめを動かしています.違うのは書き方だけです. 前半は「(あのかめに)進め」,後半は「kame よ,進め」と言っています.

ヒント getturtle() を使う場面は実はあまりありません. ここでは「今まで命令していた相手は誰だったのか」をはっきりさせるために出しました. 次の節からは,自分で新しいかめを作ります.

8.1.3 t = Turtle() でかめを作る

新しいかめを作るには Turtle() と書きます. そして,できたかめを変数に入れて名前をつけます.

かめを 1 匹作って t という名前をつける
t = Turtle()

= の右でかめが 1 匹生まれ, = の左の変数 t がそのかめを指す名札になります. Turtle は大文字の T で始まることに注意してください. かっこも必要です.

命令を出すときは,名前のうしろに .(ドット)を打って命令を書きます.

t という名前のかめへの命令
t.fd(100)
t.lt(90)
t.pencolor("red")

ドットは「の」と読むとわかりやすいでしょう. t.fd(100) は「t の fd 命令を,100 で実行する」, もっとくだけて言えば「t よ,100 進め」です.

これまで(第 1〜7 回)今回から
fd(100)t.fd(100)
lt(90)t.lt(90)
pencolor("red")t.pencolor("red")
pu()t.pu()
相手は 1 匹だけ.書かなくても決まる相手を名前で指定する.何匹いてもよい

8.1.4 2 匹のかめを動かす

では実際に 2 匹作ってみましょう.名前は何でもかまいませんが, ここでは亀太郎と亀次郎にします.

two_turtles.py
from turtle import *

# かめを 2 匹作る
kame_taro = Turtle()
kame_jiro = Turtle()

# 亀太郎への命令
kame_taro.pensize(4)
kame_taro.pencolor("crimson")
kame_taro.fd(120)

# 亀次郎への命令
kame_jiro.pensize(4)
kame_jiro.pencolor("royalblue")
kame_jiro.lt(90)
kame_jiro.fd(120)

done()
図 8.2 2 匹のかめ.どちらも中央から出発し,別々の方向へ進んだ.

kame_taro への命令と kame_jiro への命令が まったく干渉していないことを確かめてください. 太郎を赤にしても次郎は青のままですし,次郎を左に向けても太郎は右を向いたままです. かめはそれぞれ自分の位置・向き・色・太さを持っています.

生まれたてのかめの状態 新しく作ったかめは,必ず中央 (0, 0)・右向き・黒・ペンを下ろした状態 で生まれます.第 2 章で習った「かめの初期位置」と同じです. 何匹作っても全員そこから始まるので,ばらけさせたいときは pu() → goto() → pd() で配置します.

8.1.5 よくある間違い

かめを作るときの 6 つのつまずき
書き方何が起きるか
t = turtle()NameError.大文字の T で Turtle()
t = Turtleかっこが無い.かめが作られず,t.fd(100) でおかしなエラーになる
Turtle() だけ書くかめは生まれるが名札が無いので,二度と命令できない
t fd(100)ドットが無い.SyntaxError
t.fd 100かっこが無い.SyntaxError
かめを作らずに t.fd(100)NameError: name 't' is not defined.先に作ってから命令する
虫食い虫食い 8-A 3 匹で Y 字を描く

3 匹のかめを中央から 3 方向へ進ませて,Y 字(三つ又)を描きます. a は右上(30 度),b は左上(150 度),c は真下へ進みます. ____ を埋めてください. 3 方向に均等に開くので,角度は 360 ÷ 3 = 120 度ずつです.

fill7_Y.py
from turtle import *

a = Turtle()
b = Turtle()
c = ____()

a.pensize(5)
b.pensize(5)
c.pensize(5)

a.pencolor("crimson")
b.pencolor("seagreen")
c.pencolor("royalblue")

a.lt(30)
a.fd(120)

b.lt(150)
____.fd(120)

c.lt(____)
c.fd(120)

done()

8.2 かめごとに見た目を変える

かめが複数いると,どれがどれだか分からなくなります. 見た目を変えて区別できるようにするのが,複数のかめを扱うコツです.

8.2.1 色と太さ

pencolor(線の色)と pensize(線の太さ)は, 第 2 章で習ったとおりです.前にドットで名前を付けるだけで, そのかめだけに効くようになります.

three_look.py
from turtle import *

a = Turtle()
b = Turtle()
c = Turtle()

a.pencolor("crimson")
a.pensize(1)

b.pencolor("seagreen")
b.pensize(4)
b.lt(120)

c.pencolor("darkorange")
c.pensize(8)
c.lt(240)

# 3 匹とも「正六角形を描く」という同じ動きをする
for i in range(6):
    a.fd(60)
    a.rt(60)
    b.fd(60)
    b.rt(60)
    c.fd(60)
    c.rt(60)

done()
図 8.3 同じ命令でも,向きと見た目が違うので別の絵になる.
pencolor と color の違い color("red") と書くと,線の色と塗りの色の両方が変わります. かめの姿(次の節で出てくる shape)の色も塗りの色で決まるので, かめ自身を色分けしたいときは pencolor ではなく color を使うのが早道です.

8.2.2 かめの形を変える — shape

shape で,かめの姿そのものを変えられます. 使える形は次の 7 種類です.

指定見た目
"classic"細い矢じり(最初の姿)
"arrow"三角の矢印
"square"四角
"triangle"大きめの三角
"circle"丸.ボールに使える
"turtle"かめの形.いちばんかわいい
"blank"姿を出さない(ht() と似ている)

大きさは shapesize(縦, 横) で変えます. shapesize(3, 3) なら縦横とも 3 倍です.

shapes.py
from turtle import *

a = Turtle()
b = Turtle()
c = Turtle()
d = Turtle()
e = Turtle()

a.shape("classic")
b.shape("arrow")
c.shape("square")
d.shape("triangle")
e.shape("turtle")

a.color("crimson")
b.color("darkorange")
c.color("goldenrod")
d.color("seagreen")
e.color("royalblue")

a.pu()
b.pu()
c.pu()
d.pu()
e.pu()

a.goto(-200, 20)
b.goto(-100, 20)
c.goto(0, 20)
d.goto(100, 20)
e.goto(200, 20)

# 全員 2 倍の大きさにする
a.shapesize(2, 2)
b.shapesize(2, 2)
c.shapesize(2, 2)
d.shapesize(2, 2)
e.shapesize(2, 2)

# 文字を書くためだけのかめ
pen = Turtle()
pen.ht()
pen.pu()

pen.goto(-225, -50)
pen.write("classic", font=("Arial", 13, "normal"))
pen.goto(-125, -50)
pen.write("arrow", font=("Arial", 13, "normal"))
pen.goto(-25, -50)
pen.write("square", font=("Arial", 13, "normal"))
pen.goto(75, -50)
pen.write("triangle", font=("Arial", 13, "normal"))
pen.goto(175, -50)
pen.write("turtle", font=("Arial", 13, "normal"))

done()
classicarrowsquaretriangleturtle
図 8.4 5 種類の姿.どれも右(東)を向いている.
役割ごとにかめを用意する 最後に出てきた pen は,文字を書くためだけのかめです. 絵を描くかめと文字を書くかめを分けておくと, 色や位置を行ったり来たりさせずに済みます. 複数のかめが使えるようになると,こういう「役割分担」ができるようになります.

8.2.3 姿を隠す・出す — ht と st

ht()(hide turtle)でかめの姿が消え, st()(show turtle)でまた現れます. 線はそのまま残るので,絵の仕上げによく使います.

姿の出し入れ
a.ht()      # a の姿を隠す(a が描いた線は残る)
a.st()      # a の姿をまた出す

ht()        # 既定のかめを隠す

既定のかめは,fd() や ht() のように 名前を付けない命令を初めて書いたときに生まれます. ですから,Turtle() で作ったかめだけを使うなら,既定のかめは現れません. この章の例で冒頭に ht() と書いてあるのは, うっかり名前なしの命令を書いて既定のかめが生まれても,画面に姿が出ないようにするためです. 書かなくても困ることはありません.

虫食い虫食い 8-B 3 匹を見た目で区別する

3 匹のかめを横に並べ,形・色・大きさをそれぞれ変えます. ____ を埋めてください. 文字列(形や色の名前)は " で囲むのを忘れないように.

fill7_looks.py
from turtle import *

ht()          # 既定のかめを消しておく

a = Turtle()
b = Turtle()
c = Turtle()

a.shape("turtle")
b.shape(____)          # 丸にする
c.shape("square")

a.color("seagreen")
b.color("crimson")
c.color(____)          # 好きな色にする

a.shapesize(2, 2)
b.shapesize(____, ____)   # b だけ 3 倍
c.shapesize(1, 1)

a.pu()
b.pu()
c.pu()

a.goto(-140, 0)
b.goto(0, 0)
c.goto(140, 0)

done()

8.3 同時に動いているように見せる

ここが今回の一番おもしろいところです. 同じ絵ができあがるのに,動き方がまったく違う 2 つのプログラムを見比べます.

8.3.1 順番に動かす

まず,素直に書いた版です.亀太郎が円を描き終わってから,亀次郎が描き始めます. 実行して,動きをよく見てください.

four_sequential.py
from turtle import *

ht()

a = Turtle()
b = Turtle()
c = Turtle()
d = Turtle()

a.pencolor("crimson")
b.pencolor("royalblue")
c.pencolor("seagreen")
d.pencolor("darkorange")

b.lt(90)
c.lt(180)
d.lt(270)

# a が 1 周ぜんぶ描いてから,b が描き始める
for i in range(120):
    a.fd(2)
    a.rt(3)

for i in range(120):
    b.fd(2)
    b.rt(3)

for i in range(120):
    c.fd(2)
    c.rt(3)

for i in range(120):
    d.fd(2)
    d.rt(3)

done()
図 8.5 4 匹が順番に円を描いた結果.

8.3.2 交互に命令を出す

次は,繰り返しの中身を変えた版です. 4 匹ぶんの命令を 1 つの for の中に並べました. できあがる絵はさっきとまったく同じです.実行してください.

four_simultaneous.py
from turtle import *

ht()

a = Turtle()
b = Turtle()
c = Turtle()
d = Turtle()

a.pencolor("crimson")
b.pencolor("royalblue")
c.pencolor("seagreen")
d.pencolor("darkorange")

b.lt(90)
c.lt(180)
d.lt(270)

# 4 匹に少しずつ交互に命令を出す
for i in range(120):
    a.fd(2)
    a.rt(3)
    b.fd(2)
    b.rt(3)
    c.fd(2)
    c.rt(3)
    d.fd(2)
    d.rt(3)

done()
図 8.6 4 匹が同時に円を描いた結果.図 8.5 と同じ絵になる.

4 つの円が一緒に育っていくように見えたはずです. 実際には,コンピュータは「a を少し,b を少し,c を少し,d を少し」と 1 匹ずつ順番に動かしています. ただ,切り替えが細かいので,人間の目には同時に見えるのです.

コラム:コンピュータは同時に 2 つの仕事をできない コンピュータは,本当は2 つの仕事を同時にはできません. みなさんのパソコンやスマホは,動画を見ながらメッセージを打ち, 裏で更新をダウンロードすることができます. しかしその中身は,このタートルの例とまったく同じで, 細かく切り替えながら,いろいろな仕事を少しずつ進めているだけなのです.
  • 1 つの仕事を最後までやってしまうと,他の仕事が待たされる(8.3.1).
  • 少しずつ切り替えると,全部が同時に進んでいるように見える(8.3.2).
  • 切り替えを細かくしすぎると,切り替えそのものに時間を取られて遅くなる.

この考え方をマルチタスクといいます. 第 10 章のアニメーションも,第 12 章のゲームも,同じ原理でできています.

8.3.3 交互に出すときの書き方のコツ

  1. 1 回ぶんの動きを小さくする.fd(100) のように大きく動かすと,交互にしても同時に見えない.fd(2) くらいに刻む.
  2. 繰り返しは 1 つにまとめる.かめの数だけ for を書いたら,それは 8.3.1 の「順番」になる.
  3. 動く距離が違ってもよい.a.fd(2) と b.fd(5) にすれば,b のほうが速く見える.競走はこれで作る.
  4. 途中で抜けるかめがいてもよい.if を使えば,あるかめだけ止めることもできる.
虫食い虫食い 8-C 2 匹が向かい合って進む

左端の赤いかめと右端の青いかめが,同時に弧を描きながら進みます. ____ を埋めてください. 青いかめは最初に左(西)を向かせる必要があります.

fill7_face.py
from turtle import *

ht()

a = Turtle()
b = Turtle()

a.pensize(3)
a.pencolor("crimson")
a.pu()
a.goto(-220, -60)
a.pd()

b.pensize(3)
b.pencolor("royalblue")
b.pu()
b.goto(220, -60)
b.pd()
b.lt(____)          # 左(西)を向かせる

for i in range(60):
    a.fd(5)
    a.lt(3)
    ____.fd(5)
    ____.rt(____)   # b は a と反対まわり

done()

8.4 4〜5 匹をまとめて使う

8.4.1 まんじ(卍)を描く

4 匹を中央から東・南・西・北に向けて,同じ命令を同時に出します. 「まっすぐ進む → 左に 90 度曲がる → またまっすぐ進む」だけで卍になります.

swastika.py
from turtle import *

ht()          # 既定のかめを消す

a = Turtle()
b = Turtle()
c = Turtle()
d = Turtle()

a.pensize(4)
b.pensize(4)
c.pensize(4)
d.pensize(4)

a.pencolor("crimson")
b.pencolor("royalblue")
c.pencolor("seagreen")
d.pencolor("darkorange")

# 90 度ずつずらして,四方を向かせる
b.rt(90)
c.rt(180)
d.rt(270)

# まっすぐ進む
for i in range(50):
    a.fd(2)
    b.fd(2)
    c.fd(2)
    d.fd(2)

# その場で左に 90 度まわる
for i in range(90):
    a.lt(1)
    b.lt(1)
    c.lt(1)
    d.lt(1)

# またまっすぐ進む
for i in range(50):
    a.fd(2)
    b.fd(2)
    c.fd(2)
    d.fd(2)

done()
図 8.7 4 匹のかめで描いたまんじ.
なぜ 1 度ずつ 90 回なのか 真ん中の for は,向きを変えるだけなので線を引きません. それでも 1 度ずつ 90 回に分けているのは, lt(90) と一気に書くと 4 匹が「カクッ」と同時に回ってしまい, アニメーションとして面白くないからです. 動きを細かく刻むという発想は,第 10 章でそのまま使います.

8.4.2 タートルリレー

4 匹を四角形の頂点に配置し,1 匹ずつ順番に走らせます. 走り終わったかめは ht() で消えて,次のかめにバトンを渡します. こちらは「同時」ではなく「順番」を意図的に使う例です.

turtle_relay.py
from turtle import *

ht()

a = Turtle()
b = Turtle()
c = Turtle()
d = Turtle()

a.shape("turtle")
b.shape("turtle")
c.shape("turtle")
d.shape("turtle")

a.color("crimson")
b.color("royalblue")
c.color("seagreen")
d.color("darkorange")

a.pensize(4)
b.pensize(4)
c.pensize(4)
d.pensize(4)

# 4 すみに配置して,走る向きを決める
a.pu()
a.goto(-150, 90)
a.seth(0)
a.pd()

b.pu()
b.goto(150, 90)
b.seth(270)
b.pd()

c.pu()
c.goto(150, -90)
c.seth(180)
c.pd()

d.pu()
d.goto(-150, -90)
d.seth(90)
d.pd()

# 第 1 走者
for i in range(60):
    a.fd(5)
a.ht()

# 第 2 走者
for i in range(36):
    b.fd(5)
b.ht()

# 第 3 走者
for i in range(60):
    c.fd(5)
c.ht()

# 第 4 走者(ゴール)
for i in range(36):
    d.fd(5)

done()
図 8.8 4 匹でつないだリレー.最後の 1 匹だけが残る.
seth — 向きを直接決める seth(角度) は「その場でその方角を向く」命令です. lt・rt が「今の向きから何度回るか」なのに対し, seth は今の向きに関係なく向きが決まります. 0 が東,90 が北,180 が西,270 が南です. 複数のかめをそろえて配置するときに便利です.

8.4.3 変数を 4 つ並べるということ

ここまでのプログラムでは,a・b・c・d と変数を 4 つ書き,同じ命令を 4 回ずつ書いてきました. かめが 4 匹なら我慢できますが,10 匹だったらどうでしょうか.100 匹だったら.

かめが増えると手に負えなくなる
a.fd(2)
b.fd(2)
c.fd(2)
d.fd(2)
# …あと 96 行
100 匹のかめはどうするのか 第 16 章で習うリストを使うと, 「かめ 100 匹」をひとまとめにして for で回せるようになります. 今は面倒に感じておくことが大事です. 第 2 章で同じ図形を 3 回書いたのと同じ状況にいます. 今回は多くても 5 匹までにしておきましょう.

8.5 かめ同士の関係を調べる

かめが複数いると,「あのかめはどこにいるのか」を 知りたくなります.そのための命令が 2 つあります.

8.5.1 距離を測る — distance

a.distance(b) と書くと, a から b までの距離が数として返ってきます. かめに命令するのではなく,かめにたずねて答えをもらう使い方です. 第 3 章の input() や int() と同じ仲間で, 返ってきた数を変数に入れたり,計算に使ったりできます. こういう「答えを返す命令」を自分で作る方法は,第 14 章で習います.

distance.py
from turtle import *

ht()

a = Turtle()
b = Turtle()

a.shape("turtle")
a.color("seagreen")
b.shape("circle")
b.color("crimson")

a.pu()
a.goto(-120, -60)
b.pu()
b.goto(90, 70)

# 距離を測って表示する
print("a から b までの距離は", round(a.distance(b)))

# 2 匹を線で結んでみる
a.pencolor("gray")
a.pensize(2)
a.pd()
a.goto(b.xcor(), b.ycor())
a.pu()

done()
図 8.9 2 匹を結んだ線.長さがコンソールに出る.

b.xcor()・b.ycor() は, b の x 座標・y 座標を返します. 相手の座標を取ってきて,そこへ goto したわけです. 座標を数で書かずに「相手のいるところ」と書けるのが, かめを変数で持っていることの利点です.

ヒント a.distance(b) は,a.distance(100, 50) のように 座標を直接渡すこともできます. どちらの書き方でも,返ってくるのは「そこまでの距離」です.

8.5.2 相手の方を向く — towards

a.towards(b) は,a から見た b の方角を返します. これを seth に渡せば,a は b の方を向きます.

追いかける動きの 2 行
a.seth(a.towards(b))    # a が b の方を向く
a.fd(5)                 # b に向かって 5 進む

この 2 行を繰り返すだけで,追いかけっこになります.

chase.py
from turtle import *

ht()

nezumi = Turtle()      # 逃げるほう
kame = Turtle()        # 追うほう

nezumi.shape("circle")
nezumi.color("crimson")
nezumi.pensize(2)
nezumi.pu()
nezumi.goto(160, 100)
nezumi.pd()

kame.shape("turtle")
kame.color("seagreen")
kame.shapesize(1.5, 1.5)
kame.pensize(2)
kame.pu()
kame.goto(-200, -120)
kame.pd()

for i in range(80):
    # ねずみは円を描いて逃げる
    nezumi.lt(4)
    nezumi.fd(6)

    # かめはねずみの方を向いて追いかける
    kame.seth(kame.towards(nezumi))
    kame.fd(7)

print("最後の距離は", round(kame.distance(nezumi)))

done()
図 8.10 かめ(緑)がねずみ(赤)を追いかけた軌跡.

かめのほうが 1 歩だけ大きい(7 と 6)ので,じわじわ追いつきます. kame.fd(7) を kame.fd(5) に変えると, 今度は追いつけません.数を変えて確かめてください.

当たり判定の原型 towards が返すのは角度, distance が返すのは長さです. どちらも答えを返してくる命令なので, print で表示したり,if の条件に使ったりできます. if kame.distance(nezumi) < 20: と書けば, 「つかまえた」の判定になります.第 12 章のゲームで使います.
虫食い虫食い 8-D 鬼ごっこ

青い子どもが逃げ,赤い鬼が追いかけます. ____ を埋めてください. 「相手の方を向く」ための命令と,「距離を測る」ための命令を思い出しましょう. 鬼が 1 回に進む長さは,子ども(8)より 1 だけ大きくします.

fill7_oni.py
from turtle import *

ht()

ko = Turtle()      # 逃げる子ども
oni = Turtle()     # 追う鬼

ko.shape("circle")
ko.color("royalblue")
ko.pensize(2)
ko.pu()
ko.goto(180, 120)
ko.pd()

oni.shape("turtle")
oni.color("crimson")
oni.pensize(2)

for i in range(60):
    ko.lt(5)
    ko.fd(8)
    oni.seth(oni.____(ko))    # ko の方を向く
    oni.fd(____)              # ko(8)より 1 だけ大きい数

print("最後の距離は", round(oni.____(ko)))

done()

8.6 足跡を残す — stamp

t.stamp() と書くと,そのときのかめの姿が その場にハンコのように押されて残ります. 線ではなく姿そのものが残るのがポイントです.

stamp.py
from turtle import *

ht()

t = Turtle()
t.shape("turtle")
t.color("seagreen")
t.shapesize(1.6, 1.6)
t.pu()                 # 線は引かない
t.goto(-220, -80)

for i in range(9):
    t.stamp()          # 今の姿をその場に残す
    t.fd(52)
    t.lt(7)

done()
図 8.11 9 個の足跡.線は 1 本も引いていない.

pu() してあるので線は引かれません. それでも進んだ跡がわかるのは,stamp() が姿を残しているからです.

命令意味
t.stamp()今の姿をその場に残す
t.clearstamps()そのかめが残した足跡をすべて消す
ヒント stamp は第 10 章・第 12 章でとても役に立ちます. たとえば「弾を撃つ」「星をばらまく」「通った道に印をつける」といった処理を, かめを何匹も作らずに書けます.

8.7 この章で出てきた命令

命令意味
t = Turtle()かめを 1 匹作り,t という名前をつける
t.fd(長さ)t に「進め」と命令する.bk lt rt も同じ形
t.pu() / t.pd()t のペンを上げる・下ろす
t.pencolor(色)t の線の色
t.color(色)t の線の色と姿の色の両方
t.pensize(太さ)t の線の太さ
t.shape(名前)t の姿.classic arrow square triangle circle turtle blank
t.shapesize(縦, 横)t の姿の大きさ(倍率)
t.ht() / t.st()t の姿を隠す・出す
t.goto(x, y)t をその座標へ動かす
t.seth(角度)t をその方角に向ける.0 が東,90 が北
t.xcor() / t.ycor()t の x 座標・y 座標を返す
t.heading()t が今向いている角度を返す
t.distance(u)t から u までの距離を返す
t.towards(u)t から見た u の方角を返す
t.stamp()t の姿をその場に残す
t.clearstamps()t が残した足跡を消す
getturtle()既定のかめ(名前のないかめ)を受け取る

演習

以下の演習には,出発点となるプログラムが入っています. 「▶ この演習を開く」で右側に読み込み,それを発展させて作品にしてください. できあがったら「⤓ 保存」でファイルに保存して,手元に残しておきましょう.

ヒント かめに命令するときは,どの行も名前とドットから始まっているかを確かめてください. 名前を付け忘れた fd() は,既定のかめへの命令になります. 作ったかめが何匹いるのか,自分でも分からなくなったら, かめごとに色を変えてみてください.
演習演習 8-1 まんじをアレンジする

4 匹のかめを同時に動かしてまんじ(卍)を描くプログラムを与えます. これをもとに,6 匹で描く渦巻きのまんじに作り変えてください.

  • かめを 6 匹にする.全員原点から出発し,向きを 360 ÷ 6 = 60 度ずつずらす(1 匹目は東向きのまま,2 匹目から rt(60)・rt(120)…).
  • 色は 6 匹とも変える("crimson"・"darkorange"・"goldenrod"・"seagreen"・"royalblue"・"purple").
  • 「進む → 左へ 90 度曲がる」を外側の for k in range(4): で 4 回繰り返す.進む長さは 1 回目から 20・40・60・80 と長くする(出発点と同じく fd(2) を刻んで進む).
  • 全員への命令を同じ for の中に交互に書き,6 匹が同時に動いて見えるようにする.
  • これもやりましょう:外側の k を使って,進むたびに 6 匹全員の線の太さを 1・2・3・4 と太くする(pensize(k + 1)).
出発点のまんじ
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. かめを 6 匹にし,色を変え,向きを 60 度ずつずらしている
  2. 「進む → 曲がる」を外側の for で 4 回繰り返し,進む長さを外側のループ変数で 20・40・60・80 と変えて渦巻きにしている
  3. 全員への命令を同じ for の中に交互に書き,全員が同時に動いて見える
  4. これもやりましょう:k を使い,進むたびに全員の線が 1・2・3・4 と太くなっている

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 繰り返しの回数(4)を変数 n にし,長さ・太さを n と k の式で決める(n を 6 に変えるだけで 6 段の渦になる)
  • k % 2 などで,進むたびに曲がる向き(lt/rt)を切り替える

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 8-2 タートルリレーをアレンジする

4 匹のかめが四角形のコースをリレーするプログラムを与えます. これをもとに,正三角形のコースを 3 匹でリレーするプログラムに作り変えてください.

  • コースは 1 辺 280 の正三角形.頂点は (-140, -80)・(140, -80)・(0, 162).
  • 走者は 3 匹("crimson"・"royalblue"・"seagreen",形は "turtle").1 匹が 1 辺ずつ走る.1 走目は (-140, -80) から seth(0),2 走目は (140, -80) から seth(120),3 走目は (0, 162) から seth(240).
  • 走り終わった走者は ht() で消さずにそのまま残し,バトンを渡す地点(走り終わった場所)で stamp() して印を残す.
  • これもやりましょう:走る速さを走者ごとに変える.1 回に進む長さを,1 走目 5・2 走目 7・3 走目 4 にし,どの走者もちょうど 280 進むように繰り返しの回数を計算して決める.
出発点のリレー
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. 3 匹が指定の頂点から seth で 0・120・240 度を向いて走り出している
  2. 各走者が 1 辺ずつ走り,正三角形のコースがすき間なくつながっている
  3. バトンを渡す地点で,走り終わった走者が stamp() で印を残し,消えずに残っている
  4. これもやりましょう:1 回に進む長さが 5・7・4 で,回数を 280 ÷(進む長さ)にしてどの走者もちょうど 280 ずつ走っている

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 1 辺の長さを変数 side にし,頂点の座標と回数をすべて side と進む長さから計算する(side を変えても閉じた正三角形になる.割り切れない長さのときの扱いも考える)
  • 最後に towards と distance を使って,全員をコースの中央に集合させる

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 8-3 タートルレースをアレンジする

3 匹のかめが左から右へ競走するプログラムを与えます. 進む距離を乱数で決めるので,実行するたびに結果が変わります. これをもとに,ゴールに着いたところで止まり,優勝者を発表するレースに作り変えてください.

  • ゴール線を引く係のかめ(姿は ht() で隠す)を別に作り,(250, 150) から (250, -150) まで,太さ 4・"gold" の線を引く.
  • スタート位置・色は出発点のまま(赤 (-270, 90)・青 (-270, 0)・緑 (-270, -90)).1 回に進む長さはどの走者も randint(0, 6).
  • for i in range(100): の代わりに while を使い,3 匹とも x 座標が 250 より小さい間,3 匹を交互に進める.
  • 繰り返しが終わったら xcor() を比べ,いちばん x 座標が大きいかめを「優勝は 赤 でした」のように表示する(同じ x 座標のときは赤・青・緑の順に優先).
  • これもやりましょう:緑のかめが途中で転ぶようにする.繰り返しの回数を count で数え,21 回目から 30 回目までの 10 回は緑だけ進ませない(if).そのほかの回は緑もふつうに進む.
ヒント from random import randint と書いておくと, randint(0, 6) で 0 以上 6 以下のでたらめな整数 が手に入ります.毎回違う数になるので,レースらしくなります. 「誰かがゴールに着くまで」繰り返すには,第 6 章の while と xcor() を組み合わせます.
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. ゴールの線を引く係のかめを別に作り,x = 250 にゴール線を引いている
  2. while と xcor() を使い,誰かが x = 250 に着くまで交互に進ませている
  3. xcor() を比べて,優勝者を「優勝は 〇 でした」と print で発表している
  4. これもやりましょう:count で回数を数え,21〜30 回目だけ緑が進まない

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 同じ回にゴールした走者が 2 匹以上いたら「同着」と表示する(境目の扱い)
  • ゴールまでにかかった回数と,2 位との差(x 座標の差)も表示する
  • 転ぶ回の範囲を変数 start・stop にし,条件をその変数で書く

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 8-4 たくさんのタートルで 1 枚の絵を描く

今まで描いた絵を,何匹かのかめで分担して描いてみます. 第 2 章で描いた「家」を,屋根係・かべ係・ドア係の 3 匹で描くプログラムを与えます. これをもとに,係を増やし,何匹かが同時に描いて家が一度に組み上がるように作り変えてください.

  • 屋根係・かべ係の 2 匹を,1 つの for i in range(4): の中で交互に動かし,同時に描く.屋根は 3 辺しかないので,i が 3 より小さいときだけ屋根係を動かす(if).
  • ドア係も同じ for の中で同時に描く.ドアは (-20, -120) から北向き(lt(90))に,「進む → rt(90)」を 3 回.進む長さは 50・40・50 なので,i が 3 より小さいときだけ動かし,長さは i % 2 で 50 と 40 を切り替える(if).
  • どのかめも pu() → goto() → pd() で描き始めの位置を決める(屋根とかべは出発点と同じ (-60, 0),ドアは (-20, -120)).
  • これもやりましょう:太陽係(taiyo,色 "orange")を足し,(150, 160) から東向きに,fd(6) と rt(10) を 36 回繰り返す円を描く.太陽もかべ係と同時に描く.かべの 1 辺を描くあいだに太陽は 9 歩ずつ進むよう,かべの for の中に太陽の for を入れる(4 × 9 = 36).
出発点の家(3 匹で分担して描いた)
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. 屋根・かべの 2 匹を1 つの for の中で交互に動かし,if で屋根を 3 辺だけにしている
  2. ドア係も同じ for の中で動かし,i < 3 と i % 2 の if で50・40・50 の 3 辺を描いている
  3. かめごとに pu() → goto() → pd() で描き始めの位置を決めている
  4. これもやりましょう:太陽係が (150, 160) から 36 歩の円を,かべ係と同時に(入れ子で 9 歩ずつ)描いている

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 家の左上の角を変数 x0・y0 にし,すべての係の描き始めをx0・y0 からの式で決める(2 つの値を変えるだけで家全体が動く)
  • 家の幅を変数にし,屋根・かべ・ドアの長さをその式で決める

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 8-5 星と五角形を同時に描く

2 匹のかめが,同時にそれぞれ星と五角形を描くプログラムを与えます. どちらも「進む」と「曲がる」を細かく刻み,交互に命令を出しています. 星は 1 か所で 144 度,五角形は 72 度曲がるので, 同じ回数だけ刻んで,1 回に曲がる角度を変えています.

これをもとに,3 匹目を足して 3 つの図形を同時に描き, 描き終わったあとにかめ同士の距離と向きを使うプログラムに作り変えてください.

  • 描き始めの位置を,星 a は (-240, -60),五角形 b は (-30, -80) に変える(大きさ・色は出発点のまま).
  • 3 匹目 c(色 "crimson",太さ 3)を足し,(140, -40) から逆まわり(lt)の小さい星を描く.1 辺は 60(fd(1.5) を 40 回),角では左へ合計 144 度曲がる.
  • 3 匹とも,出発点と同じ外側 5 回の for の中で交互に命令を出し,同時に描き終わる.
  • 描き終わったら,distance で a と b の距離を求め,round で整数にして「a と b の距離は 211」と表示する(星も五角形も閉じた図形なので,描き終わると描き始めの点に戻っている).
  • これもやりましょう:c のペンを上げ,towards で a の方を向かせてから,a との距離より 30 だけ短く進ませる(a の手前 30 で止まる).最後に「c と a の距離は 30」と表示する.
出発点:星(左)と五角形(右)
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. 3 匹目 c が (140, -40) から,同じ外側 5 回の繰り返しの中で逆まわりの小さい星を描いている
  2. 3 匹とも同じ for の中で交互に命令を出し,同時に描き終わる
  3. 描き終わったあと,distance を使って「a と b の距離は 211」と表示している
  4. これもやりましょう:c が towards で a の方を向き,a の手前 30 で止まって「c と a の距離は 30」と表示している

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 描き終わったあと,3 匹を中央 (0, 0) に集合させる
  • 外側の回数をそろえる工夫をして,正六角形も同時に描く

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

自由演習 8-6 かめたちの物語

ここまでの命令だけを使って,複数のかめが登場する作品を作ってください. 絵でも,競走でも,追いかけっこでも,かめたちのダンスでもかまいません.

  • かめを 3 匹以上使うこと
  • かめごとに色または形を変えて,区別できるようにすること
  • 8.3 節の「交互に命令を出す」(1 つの for の中で複数のかめに順に命令を出す)をどこかで必ず使うこと
  • 先頭にコメントで,題名・氏名・日付を書くこと
  • これもやりましょう:この章で習った distance・towards・stamp のうちどれか 1 つ以上を使うこと
ヒント ネタに困ったら,次のどれかから始めてみてください. 「花火(中央から何匹も外へ飛び出す)」 「太陽と地球と月(追いかけながら回る)」 「合唱(横一列のかめが順番に上下する)」 「迷路(何匹かが別々の道を進む)」.
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. Turtle() でかめを 3 匹以上作り,色か形で区別している
  2. 1 つの for の中で複数のかめに交互に命令を出す場面がある
  3. 先頭のコメントに題名・氏名・日付がある
  4. これもやりましょう:distance・towards・stamp のどれかを 1 か所以上使っている

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • if と distance で「つかまえた」などの判定をする
  • 役割ごとにかめを分け(文字係など),名前で役割が分かるようにする

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

追加実習:仕組みを確かめてから設計する

同じかめへ別の名前を付けた場合

Turtle()を2回呼ぶと,2匹のかめが作られます.

一方,a = Turtle()の後にb = aと書いた場合は,新しいかめを作っていません.aが指しているかめへ,bという名前からも命令できるようになっただけです.

変数の数と,かめの数は同じとは限りません.複数の対象を使う際に,まずこの関係を確認します.

同じかめへ2つの名前から命令すると,位置や向きは共有されます.

a.fd(30)の後にb.fd(20)を呼べば,1匹が合計50進みます.独立した2匹なら,各自が30と20進むので,結果は違います.

画面に線が重なっていて区別しにくい場合には,xcorやycorで位置を表示して確かめましょう.

名前を代入し直した場合にも注意が必要です.

b = Turtle()と後から書けば,bは新しいかめを指しますが,aが指す元のかめが消えるわけではありません.画面に何匹いるか分からなくなった場合は,Turtle()が実行された回数を数えます.

繰り返しの中で毎回生成していないかも確認してください.

以下の例は,共有する名前を意図的に作って観察します.

色を変えたとき,どちらの命令に対してどのかめが変わるかを予測してください.次の演習では独立した2匹へ直します.

同じ色や同じ位置から始めても,生成が別なら状態を独立して変更できることを確かめます.

lec05_2_extra_1.py
from turtle import *
a = Turtle()
b = a
a.color("blue")
a.fd(30)
b.fd(20)
print(a.xcor(),b.xcor())
done()
演習設計して確かめる

aとbを独立した2匹にし,aは上の列で30,bは下の列で20進めてください.色を青と赤に分け,それぞれのx座標を表示して結果を確認します.

評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. Turtle() を 2 回呼び,a と b を独立した 2 匹にしている(b = a を使っていない)
  2. a を上の列(y = 40)で 30,b を下の列(y = -40)で 20 進めている
  3. a を青,b を赤にし,xcor() で 2 匹の x 座標を表示している

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • b = a の場合と比べ,表示される x 座標の違いをコメントに書く

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

交互の更新と,まとめて動かす違い

2匹を同時に動かしたいとき,aを最後まで動かしてからbを最後まで動かすと,見た目には順番に動いてしまいます.

aを少し,bを少しという処理を1つのループに入れると,交互に進む様子を作れます.コンピュータがどの命令をどの順で実行するかと,画面でどう見えるかを分けて考えます.

最終位置だけを比べると,まとめて動かす方法と交互に動かす方法は同じ結果になることがあります.

しかし,途中の見え方や,毎回衝突を確認したい場合の意味は違います.作品の仕様が最終的な絵だけなのか,動く過程も含むのかを確認してください.

アニメーションの章では,時間を空けて更新する方法へ発展します.

交互に動かす場合でも,1回の移動量が違えば速度の比を表せます.

同じ回数だけ更新し,aは4,bは2進むなら,進む距離の比は2対1です.更新回数と1回の変化量を区別すると,どちらの値を変えれば目的の動きになるかを説明できます.

途中で片方だけを回転させれば,進む方向も独立して変わります.

以下では,毎回の位置をコンソールへ表示します.

画面の見た目だけではなく,数値の列でも交互の進行を追えます.次の演習では5回目以降にbの向きを変えます.

条件分岐と複数の対象への命令を組み合わせ,変更されるのがbだけであることを確認してください.

lec05_2_extra_2.py
from turtle import *
a = Turtle()
b = Turtle()
a.pu()
b.pu()
a.goto(-100,40)
b.goto(-100,-40)
a.pd()
b.pd()
for step in range(10):
    a.fd(4)
    b.fd(2)
    print(step,a.xcor(),b.xcor())
done()
演習設計して確かめる

10回の更新で,aは毎回4進み,bは毎回2進む構成を保ちます.stepが5のときだけbを左へ90度回し,後半は上へ進むようにしてください.aの向きは変えません.

評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. 10 回の for の中で,a と b に交互に命令を出している
  2. step が 5 のときだけ,if で b を左へ 90 度回している
  3. a の向きは変えず,最後に 2 匹の位置を表示して確かめている

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • a と b の 1 回の移動量を変え,速さの比を 3 対 1 にする

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

描く係を分けると,消す範囲を選べる

複数のかめは,同じ絵を並行して描くためだけでなく,役割ごとに線を管理するためにも使えます.

背景を描くかめ,動く対象を描くかめ,文字を書くかめを分けると,一部だけを消して描き直せます.1匹ですべてを描いている場合,clearすると残したい背景も一緒に消えるため,描画を分ける意味が出てきます.

clearは,そのかめが描いたものを消す操作です(clearとdotは第4章の早見表にあります).

位置や向きは,描き終わったときのまま残ります.一方,resetは状態も初期化する操作なので,用途が違います.

画面が消えたことだけを見て同じ命令と考えないでください.消した後にどこから描き始めるか,色や向きがどうなっているかも確認します.

役割ごとに分ける際は,意味が分かる名前を付けます.

a,bは小さな実験には使いやすいですが,作品ではbackgroundやmarkerなどの名前があると,どの線を消すか読み取りやすくなります.名前を付けることと,対象を分けることが,あとでコードを修正する際の手がかりになります.

以下は,横線を背景用のかめ,点を目印用のかめで描く例です.

目印だけを消して位置を変えても,背景は残ります.次の演習では目印を2回移動し,最後の点だけを残してください.

clearの後でも現在位置が変わらないことを利用するのか,毎回gotoで位置を指定するのかを,自分で説明できる形にします.

lec05_2_extra_3.py
from turtle import *
background = Turtle()
marker = Turtle()
background.ht()
marker.ht()
background.pu()
background.goto(-100,0)
background.pd()
background.fd(200)
marker.pu()
marker.goto(-50,0)
marker.dot(20,"blue")
marker.clear()
marker.goto(50,0)
marker.dot(20,"red")
done()
演習設計して確かめる

背景の横線を保ち,目印を-60,0,60の順へ移し,最後の緑の点だけを残してください.消す対象がmarkerであることを,命令の左側の名前で確認します.

評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. 背景の横線を描くかめと,目印を打つかめを分けている
  2. 目印を -60,0,60 の順に移し,移るたびに目印のかめだけ clear() している
  3. 最後に緑の点だけが残り,背景の横線は消えていない

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 文字を書く係のかめを足し,目印の位置の座標を書く

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

同じかめを共有する場合と,独立したかめを作る場合を,命令の結果から区別できるか確認してください.

作品を確かめる観点

複数のかめを使う作品では,各かめの担当,初期位置,初期の向き,線を描くかどうかを表にして確認します.

すべての初期値を設定しなくても動く場合がありますが,他の例からコードを移したとき,残った状態へ依存していると結果が変わります.生成直後に必要な値をそろえると,意図した状態から開始できます.

同じ図形を2匹へ描かせる場合も,最終位置だけでなく,命令の順序と途中の線が仕様どおりかを見ます.消去を使う作品では,消したい線をどのかめが描いたかまで説明してください.

見た目が重なっていると,同じ線を複数のかめが描いている可能性もあります.片方をclearしても線が残る場合は,命令が失敗したと決め付けず,もう片方の線が残っていないかを調べます.

この回で経験した,個体ごとに状態と操作を分ける考え方は,第23章で自分のクラスを作る際の土台になります.

まとめ

  • t = Turtle() でかめを作り,t.fd(100) のようにドットで相手を指定して命令する.
  • これまでの fd(100) は,名前のないかめ(既定のかめ)への命令だった.
  • かめはそれぞれ自分の位置・向き・色・太さ・姿を持っている.shape と color で区別をつける.
  • 順番に動かすと順番に見え,交互に少しずつ動かすと同時に見える.コンピュータは本当は同時に 2 つの仕事をしていない.
  • t.distance(u) で距離,t.towards(u) で方角がわかる.seth(towards(…)) で追いかけっこになる.
  • t.stamp() で姿をその場に残せる.
  • かめが増えると変数も増える.まとめて扱う方法は第 16 章(リスト)で習う.

次の回では,いったん絵から離れます. タートルを使わず,入力・計算・表示だけで作る CUI プログラムを書いてみます. 絵がない代わりに,仕様を読んでそのとおりに作る力がためされます.

そのあとの第 10 章で,このかめたちを本当に動かします. ontimer という命令を使って, 「少し動かして,少し待って,また少し動かす」を繰り返させると, 絵がアニメーションになります.

復習:虫食いで確かめる

コードの ____ を埋めてください.実行前に結果を予測し,実行後に問題文の条件と照合しましょう.assert は条件が成り立つかを調べ,成り立たないとエラーになります.

虫食い虫食い・復習 1 二匹の位置を分ける

赤いかめを (-80, 0),青いかめを (80, 0) に置きます.操作対象の名前を補ってください.

fill_review_lec05_2_1.py
from turtle import *
a = Turtle()
b = Turtle()
a.color("red")
b.color("blue")
a.penup()
b.penup()
____.goto(-80, 0)
____.goto(80, 0)
done()
虫食い虫食い・復習 2 一匹だけ向きを変える

a は右へ,b は上へ50進ませます.b だけの向きを変える名前と角度を補ってください.

fill_review_lec05_2_2.py
from turtle import *
a = Turtle()
b = Turtle()
____.setheading(____)
a.forward(50)
b.forward(50)
done()