たのしいプログラミング Pythonではじめよう:おたすけサイト

おたすけサイトへようこそ! このサイトには、本を読み進めるときに助けになる、色々な情報が書かれています。 本の内容と現実の世界に、どこがズレがあることに気がついたら、このサイトにかかれてあることをよく読んでみてください。 きっと問題を解決する助けがみつかるはずです。

Pythonのインストールについて

なんと、本が印刷されて一週間しかたっていないのに、 Pythonの本家サイトのデザインが、 すごくカッコいいものに変わってしまいました。 なので、本に書いてあるインストーラーを入手するためのリンク等が、なくなってしまっています。 せっかく一所懸命に画面写真を撮って本に載せたのに………。

ここでは、WindowsとMac OS Xを使っている人向けに、 Pythonのインストーラーの入手方法を説明しておくことにしましょう。

Pythonサイトの現在の姿

なんと、こんなに変わってしまいました!

ページにある、メニューの「Downloads」の部分にマウスカーソルを乗せると、

のようになります。この画面で表示されている [Dowload]ボタンは、 Pythonのソースコード (Pythonそのもののプログラムコード)なので、 ダウンロードしても、そのままインストールできません。 Windowsを使っている人は、[Windows]の部分をクリックします。 Mac OS X を使っている人は [Mac OS X] の部分をクリックしましょう。

Windows用のインストーラの入手方法

Windowsの部分をクリックすると、 どのバージョンのPythonを使うかを選択する画面になります。

Pythonのバージョン3を使うので、[Latest Python 3.x.x Release - ...]と書かれているリンクをクリックしましょう。

選択したバージョンのダウンロードページが表示されるので、画面の下の方までスクロールして、 [Download]と書かれているところを探します。

ダウンロードするべきファイルは二つのうちのどちらかです。 使っているWindowsが32bitのときは、32bit版を、 64bitのときは64bit版のインストーラのリンクをクリックして、 ダウンロードしましょう。

Mac OS X用のインストーラの入手方法

Mac OS Xの部分をクリックすると、 どのバージョンのPythonを使うかを選択する画面になります。

Pythonのバージョン3を使うので、[Latest Python 3.x.x Release - ...]と書かれているリンクをクリックしましょう。

選択したバージョンのダウンロードページが表示されるので、画面の下の方までスクロールして、 [Download]と書かれているところを探します。

たぶん、あなたが使っているMac OS Xは64bit版でしょう。 上の例でマークのあるインストール用のファイルをダウンロードします。

「じぶんでやってみよう」の解説

3章

好きなもの

好きなものや食べ物をそれぞれ3つずつなら、下のようにしてみればよいでしょう。

>>> hobbies = ['Pokemon', 'LEGO Mindstorms', 'Mountain Biking']
>>> foods = ['Pancakes', 'Chocolate', 'Apples']
>>> favorites = hobbies + foods
>>> print(favorites)
['Pokemon', 'LEGO Mindstorms', 'Mountain Biking', 'Pancakes', 'Chocolate', 'Apples']

戦士は何人?

答えを計算する方法には、いくらか違ったやりかたがあります。 3つの建物の屋根に25人の忍者が隠れていて、2つのトンネルにのそれぞれに40人のサムライが隠れているので、忍者とサムライの人数をそれぞれ計算して、計算した結果を足し算すればいいわけです。

>>> 3*25
75
>>> 2*40
80
>>> 75+80
155

もっと短く書くのなら、カッコをつかって1つの計算にまとめてしまいましょう。 この場合、掛け算は足し算よりも先に計算されるので、カッコをかなならず書かなければならないということはないのですが、 カッコが付いているほうが計算式の意味がはっきりして読みやすいのです。

>>> (3*25)+(2*40)

いま、何を計算しているのかをはっきりさせたいなら、下のようにすると、よりわかりやすくなりますね。

>>> roofs = 3
>>> ninjas_per_roof = 25
>>> tunnels = 2
>>> samurai_per_tunnel = 40
>>> print((roofs * ninjas_per_roof) + (tunnels * samurai_per_tunnel))
155

Greetings!

わかりやすい変数名を使いましょう。名前を文字列に埋め込むために %s %s のように、 プレースホルダーを2つ使います。

>>> first_name = 'Brando'
>>> last_name = 'Ickett'
>>> print('Hi there, %s %s!' % (first_name, last_name))
Hi there, Brando Ickett!

4章

四角形を描く

四角形を書くには、正方形の時と同じようにすれば良いのです。 ただし、向かい合う辺の長さを同じにします。

>>> import turtle
>>> t = turtle.Pen()
>>> t.forward(100)
>>> t.left(90)
>>> t.forward(50)
>>> t.left(90)
>>> t.forward(100)
>>> t.left(90)
>>> t.forward(50)

三角形を描く

問題では、どのような三角形を描けばいいのかを書きませんでした。 ここでは一番簡単な、正三角形の書き方を説明しましょう。 正三角形を書くときには、全部の辺を同じ長さにして、カメを回転させる角度も、全部120度にします。

>>> import turtle
>>> t = turtle.Pen()
>>> t.forward(100)
>>> t.left(120)
>>> t.forward(100)
>>> t.left(120)
>>> t.forward(100)

で、カメを100ピクセル進めます。の部分で、カメを120度廻転させています。 正三角形の内側の角度は60度なので、180度から60度を引いた、120度を指定するのです。 で同じように線を引いていき、の部分で、三角形の線を閉じます。

正三角形以外の三角形を書くためには、角度の計算のために、三角関数という数学を使います。 これは高校生になると習います。もし、あなたが、もう三角関数を勉強しているのなら、 いろいろチャレンジしてみてください。

角なしの四角形を描く

角が無い四角形を良く観察すると、4つの辺がない八角形だということに気が付きます。 八角形を書くときは、四角形を書くときとほとんど同じですが、回る角度は45度です。 なので、まず、前進して線を描き、45度回転して、ペンを持ち上げます。その後、すこし前進して、再びペンを下ろし、45度回転してから、前進するのです。これで辺一本分です。

t.forward(50)
t.left(45)
t.up()
t.forward(50)
t.down()
t.left(45)

上の例を必要なだけ繰り返せば、角がない四角形がかけます。 完成品のプログラムは下の通りです。入力して、動かしてみましょう。

import turtle
t = turtle.Pen()
t.forward(50)
t.left(45)
t.up()
t.forward(50)
t.down()
t.left(45)
t.forward(50)
t.left(45)
t.up()
t.forward(50)
t.down()
t.left(45)
t.forward(50)
t.left(45)
t.up()
t.forward(50)
t.down()
t.left(45)
t.forward(50)
t.left(45)
t.up()
t.forward(50)
t.down()
t.left(45)

5章

カスタードケーキ!

twinkies という変数に割り当てられた値が、100よりも多くて、500よりも少ない場合をテストするには、 下のようにifの条件を書きます。

>>> twinkies = 600
>>> if twinkies < 100 or twinkies > 500:
        print('Too few or too many')

Too few or too many

ちょうどいい数字

ifを使って、変数moneyの値が「100から500の間にある、または、1000から5000の間にある」 という条件を書くには下のようにします。 andorをうまく組み合わせれば、1行で書けますよ。

if (money >= 100 and money <= 500) or (money >= 1000 and money <= 5000):
    print('money is between 100 & 500 or between 1000 & 5000')

カッコを使って、どの部分を比較を先にするのかをきちんと指定しましょう。 変数moneyに割り当てる値を、いろいろ変えて実験してみましょう。

>>> money = 800
>>> if (money >= 100 and money <= 500) or (money >= 1000 and money <= 5000):
        print('money is between 100 & 500 or between 1000 & 5000')

>>> money = 400
>>> if (money >= 100 and money <= 500) or (money >= 1000 and money <= 5000):
        print('money is between 100 & 500 or between 1000 & 5000')

money is between 100 & 500 or between 1000 & 5000
>>> money = 3000
>>> if (money >= 100 and money <= 500) or (money >= 1000 and money <= 5000):
        print('money is between 100 & 500 or between 1000 & 5000')

money is between 100 & 500 or between 1000 & 5000

ニンジャ軍団と戦えますか?

ちょっと意地悪問題ですね。 ifを使って、問題文の説明の順番通りに、下のようなプログラムを書いてしまったあなた。 そのプログラムはちゃんと動きません。

>>> ninjas = 5
>>> if ninjas < 50:
        print("That's too many")
    elif ninjas < 30:
        print("It'll be a struggle, but I can take 'em")
    elif ninjas < 10:
        print("I can fight those ninjas!")

That's too many

変数ninjasの値は5で、 本当ならI can fight those ninjas!と表示されなければならないのに、 そうなっていません。 何故でしょう。 ninjasの値は5ですから、 最初のifの条件がTrueになってしまって、 Thas' too manyが表示されたあと、続く elifの部分が実行されないためなのです。 この間違いを直すには、下のように、数字が小さい順番にifで条件をチェックすればいいのです。

>>> ninjas = 5
>>> if ninjas < 10:
        print("I can fight those ninjas!")
    elif ninjas < 30:
        print("It'll be a struggle, but I can take 'em")
    elif ninjas < 50:
        print("That's too many")

I can fight those ninjas!

6章

Helloループ

forループの中にあるprintは1回しか実行されません。 なぜなら、forのブロックの中にifがあって、 変数xに割り当てられている値が9より小さい時にはbreakで、 forによる繰り返しから抜け出すようになっているからです。 繰り返しがスタートするとき、変数xの値は0です。 なので、ifの条件はTureになり、すぐに繰り返しが終了してしまうのです。

>>> for x in range(0, 20):
        print('hello %s' % x)
        if x < 9:
            break

hello 0

偶数だけを取り出す

偶数は2で割ったときの余りが0の数字です。余りが0かどうかをifで調べて、偶数なら表示するようにします。 割り算の余りを計算するときには %演算子を使います。 もしあなたが15歳なら下のようになるでしょう。

  >>> for x in range(1, 16):
          if (x % 2) == 0:
              print(x)
  2
  4
  6
  8
  10
  12
  14 

お気に入りのサンドイッチ

リストの要素を数字付きで表示するには、いくつかの方法がありますが、 ここでは1つの例を示すことにしましょう。

>>> ingredients = ['snails', 'leeches', 'gorilla belly-button lint',
'caterpillar eyebrows', 'centipede toes']
>>> x = 1
>>> for i in ingredients:
        print('%s %s' % (x, i))
        x = x + 1

1 snails
2 leeches
3 gorilla belly-button lint
4 caterpillar eyebrows
5 centipede toes

の部分で、変数xを準備しています。この変数の値を表示するのです。 forによる繰り返しが始まります。変数iには、変数ingredientsに割り当てられているリストの要素が一つずつ入ります。 で、数字とリストの要素を表示します。 の部分が大切で、変数xの値を1だけ増やしています。つまり、x + 1を計算して、 その結果を再び変数xへ割り当て直しているのです。 こうすることで、forによる繰り返して、リストの要素が一つ取り出させれるたびに、 変数xの値も1だけ増えることになるので、数字付きでリストの要素が表示できるという寸法です。

月に行ったら体重は?

最初に、今の自分の体重をweightに割り当てます。

>>> weight = 30

毎年1kgずつ体重が増え、月での重さは地球の16.5%、つまり0.165を掛け算した値になるので、 下のようにプログラムを書けばお望みの結果が表示されます。

>>> weight = 30
>>> for year in range(1, 16):
        weight = weight + 1
        moon_weight = weight * 0.165
        print('Year %s is %s' % (year, moon_weight))

Year 1 is 5.115
Year 2 is 5.28
Year 3 is 5.445
Year 4 is 5.61
Year 5 is 5.775
Year 6 is 5.94
Year 7 is 6.105
Year 8 is 6.2700000000000005
Year 9 is 6.4350000000000005
Year 10 is 6.6000000000000005
Year 11 is 6.765000000000001
Year 12 is 6.930000000000001
Year 13 is 7.095000000000001
Year 14 is 7.260000000000001
Year 15 is 7.425000000000001

7章

月面体重計算、再び

関数の引数は2つ必要です。一つ目はweightで、体重をセットします。2つ目はincreaseで、1年辺りどれだけ体重が増えるかをセットします。関数本体のプログラムは、6章の4番目の問題とほとんど同じです。

>>> def moon_weight(weight, increase):
        for year in range(1, 16):
                weight = weight + increase
                moon_weight = weight * 0.165
                print('Year %s is %s' % (year, moon_weight))
>>> moon_weight(40, 0.5)
Year 1 is 6.6825
Year 2 is 6.765
Year 3 is 6.8475
Year 4 is 6.93
Year 5 is 7.0125
Year 6 is 7.095
Year 7 is 7.1775
Year 8 is 7.26
Year 9 is 7.3425
Year 10 is 7.425
Year 11 is 7.5075
Year 12 is 7.59
Year 13 is 7.6725
Year 14 is 7.755
Year 15 is 7.8375

年数を指定できるように

関数への追加は、ほんの少しです。years引数を追加して、年数を指定できるようにします。

>>> def moon_weight(weight, increase, years):
        years = years + 1
        for year in range(1, years):
                weight = weight + increase
                moon_weight = weight * 0.165
                print('Year %s is %s' % (year, moon_weight))

>>> moon_weight(35, 0.3, 5)
Year 1 is 5.8245
Year 2 is 5.874
Year 3 is 5.9235
Year 4 is 5.973
Year 5 is 6.0225

関数本体の最初の行で、変数yearsの値を1だけ増やしている部分に注意してください。 これをしておくことで、forによる繰り返しが正しく動くようになるのです。

月面体重計算プログラム

sysモジュールにある、stdinオブジェクトをつかって、 キーボードから数値を入力してもらいます。(入力はreadline関数をつかって受け取ります)

import sys
def moon_weight():
    print('Please enter your current Earth weight')
    weight = float(sys.stdin.readline())
    print('Please enter the amount your weight might increase each year')
    increase = float(sys.stdin.readline())
    print('Please enter the number of years')
    years = int(sys.stdin.readline())
    years = years + 1
    for year in range(1, years):
        weight = weight + increase
        moon_weight = weight * 0.165
        print('Year %s is %s' % (year, moon_weight))

で体重を、で、1年あたり体重が増える量を、で、計算する年数を入力させます。 sys.stdin.readlineは文字列を返すので、intをつかって整数値に変換しておきます。 これであとは計算して結果を表示するだけです。 この関数を呼び出してみると、下のように動くでしょう。

>>> moon_weight()
Please enter your current Earth weight
45
Please enter the amount your weight might increase each year
0.4
Please enter the number of years
12
Year 1 is 7.491
Year 2 is 7.557
Year 3 is 7.623
Year 4 is 7.689
Year 5 is 7.755
Year 6 is 7.821
Year 7 is 7.887
Year 8 is 7.953
Year 9 is 8.019
Year 10 is 8.085
Year 11 is 8.151
Year 12 is 8.217

8章

踊るキリン

レジナルドを踊らせるための関数を追加する前に、Animals, Mammals, Giraffesクラスについてもう少し見てみましょう。 まずは、Animalsクラスからです。本文ではAnimateクラスの子クラスとしていましたが、 ここでは簡略にしてあります。

class Animals:
    def breathe(self):
        print('breathing')
    def move(self):
        print('moving')
    def eat_food(self):
        print('eating food')

MammalsAnimalsの子クラスです。

class Mammals(Animals):
    def feed_young_with_milk(self):
        print('feeding young')

そして、GiraffeクラスはMammalsクラスの子クラスなわけです。

class Giraffes(Mammals):
    def eat_leaves_from_trees(self):
        print('eating leaves')

それぞれの足を動かす関数は、簡単に追加できますね。

class Giraffes(Mammals):
    def eat_leaves_from_trees(self):
        print('eating leaves')
    def left_foot_forward(self):
        print('left foot forward')
    def right_foot_forward(self):
        print('right foot forward')
    def left_foot_backward(self):
        print('left foot back')
    def right_foot_backward(self):
        print('right foot back')

そして、dance関数は、足を動かす関数を呼びだせばいいので、こんな感じでしょうか。

def dance(self):
    self.left_foot_forward()
    self.left_foot_backward()
    self.right_foot_forward()
    self.right_foot_backward()
    self.left_foot_backward()
    self.right_foot_backward()
    self.right_foot_forward()
    self.left_foot_forward()

さぁ、レジナルドにダンスをさせましょう。 Giraffesクラスのオブジェクトを作って、danceを呼び出します。

>>> reginald = Giraffes()
>>> reginald.dance()

left foot forward
left foot back
right foot forward
right foot back
left foot back
right foot back
right foot forward
left foot forward

干し草熊手

Penturtleモジュールで提供されているクラスなので、 Penクラスのオブジェクトをたくさん作り出すことができます。 ここでは4つのPenクラスのオブジェクトを作っています。 それぞれが一匹のカメに相当します。 オブジェクトは違う変数に割り当てます。そうすれば、変数を参照することで、 それぞれのカメに別々の指示を出すことができます。 この例では、あまり工夫をせずに、すごく単純なやりかたをしていますが、ねらいとしては、 複数のオブジェクトを別々に取り扱うことができるということを、確認することなので、 これでいいのです。

import turtle
t1 = turtle.Pen()
t2 = turtle.Pen()
t3 = turtle.Pen()
t4 = turtle.Pen()
t1.forward(100)
t1.left(90)
t1.forward(50)
t1.right(90)
t1.forward(50)
t2.forward(110)
t2.left(90)
t2.forward(25)
t2.right(90)
t2.forward(25)
t3.forward(110)
t3.right(90)
t3.forward(25)
t3.left(90)
t3.forward(25)
t4.forward(100)
t4.right(90)
t4.forward(50)
t4.left(90)
t4.forward(50)

他に、もっとうまいやり方がありそうですよね。工夫してみましょう。

9章

不思議な計算結果

abs関数は、数字の絶対値を計算するものでした。 つまり、負の数(0より小さい数)が、正の数(0より大きい数)に変換されるとも言えます。 次のプログラムでは、最初のものは計算結果が20に、2つ目のものは0になります。

>>> a = abs(10) + abs(-10)
>>> print(a)
20

>>> b = abs(-10) + -10
>>> print(b)
0

では、計算は10+10ということで、では10 + (-10)ということになるわけです。

秘密のメッセージ

まずは、文字列を変数に割り当てます。 その後にdir関数を使って、文字列に対して使える機能を調べてみましょう。

>>> s = 'this if is you not are a reading very this good then way you to have hide done a it message wrong'
>>> print(dir(s))
['__add__', '__class__', '__contains__', '__delattr__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
'__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__',
'__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__',
'__rmul__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '_formatter_field_name_split',
'_formatter_parser', 'capitalize', 'center', 'count',
'encode', 'endswith', 'expandtabs', 'find', 'format', 'index',
'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier',
'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 
'join', 'ljust', 'lower', 'lstrip', 'maketrans', 
'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition',
'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip',
'swapcase', 'title', 'translate', 'upper', 'zfill']

機能の一覧を見てみるとsplitという、それっぽい名前があります。 help関数をつかって、splitがどんな動きをするのかを確認します。

>>> help(s.split)
Help on built-in function split:

split(...)
    S.split([sep[, maxsplit]]) -> list of strings

    Return a list of the words in S, using sep as the
    delimiter string. If maxsplit is given, at most maxsplit
    splits are done. If sep is not specified or is None, any
    whitespace string is a separator and empty strings are
    removed from the result.

説明を見ると、splitは、文字列を語に分割してリストとして返すようです。 分割するために使う文字は、引数sepで指定するようになっていて、 sepが指定されなかったときは、空白文字を使うようです。 どうやら、これが使えそうですね。 では、さっそくこれを使って文字列を分解して、2語ごとに表示してみましょう。

>>> message = 'this if is you not are a reading very this good then way you to have hide done a it message wrong'
>>> words = message.split()
>>> for x in range(0, len(words), 2):
        print(words[x])

の部分で、文字列を変数に割り当てて、それを空白文字で分解した結果のリストも変数に割り当てます。 その後、forrangeを組み合わせて、0番目から、リストの長さまでの数字を 1つ飛びにして、繰り返しを作ります。 変数xの値は0, 2, 4, ... のように増えていきます。 この値をつかって、リストの位置を指定すれば、1つ飛びで単語が表示されていくという仕組みです。

ファイルのコピー

ファイルをコピーするには、ファイルの内容を変数に読み込んでから、別のファイルに書きだせばOKです。 つまり、コピー元のファイルを読み込みモードで開く、内容を全部読み込む、ファイルを閉じる、 コピー先のファイルを書き込みモードで開く、内容を全部書きだす、ファイルを閉じるという手順です。

f = open('test.txt')
s = f.read()
f.close()
f = open('output.txt', 'w')
f.write(s)
f.close()

この例でもちゃんと動作しますが、もっといい方法があります。 Pythonにはshutilというモジュールがあるので、それをつかいます。

import shutil
shutil.copy('test.txt', 'output.txt')

10章

コピーされた車

問題は、2つのprintがそれぞれどんな表示をするかを尋ねています。

まず、最初のものを見てみましょう。

>>> car1 = Car()
>>> car1.wheels = 4
>>> car2 = car1
>>> car2.wheels = 3
>>> print(car1.wheels)

3

なぜ printで表示されるのは 3 なのでしょう。 car1wheelsには4をセットしたはずなのに。 プログラムを良く見ると、car2 = car1と書かれています。 つまり、car2に割り当てられるオブジェクトは、 car1に割り当てられているオブジェクトそのものなのです。

では二番目のprintはどうでしょう。

>>> car3 = copy.copy(car1)
>>> car3.wheels = 6
>>> print(car1.wheels)

3

この場合、car3に割り当てられるオブジェクトは、 コピーされた新しいオブジェクトです。 car1に割り当てられている元のオブジェクトとは異なるものなので、 car3と通して行った操作は、 car1に割り当てられているオブジェクトとは無関係というわけですね。

お気に入りリストを保存する

pickleモジュールを使って、変数の値をファイルに保存出来ます。

>>> import pickle
>>> favorites = ['PlayStation', 'Fudge', 'Movies', 'Python for Kids']
>>> f = open('favorites.dat', 'wb')
>>> pickle.dump(favorites, f)
>>> f.close()

で、pickleモジュールをインポートしています。 次にで、お気に入りリストを変数に割り当てて保存の準備をしておきます。 で、データを保存するファイルを書き込みモードで開きます。 で、お気に入りリストをファイルに保存しています。 最後にファイルを閉じればおしまいです。

データを読み込むときにもpickleモジュールを使います。

>>> import pickle
>>> f = open('favorites.dat', 'rb')
>>> favorites = pickle.load(f)
>>> print(favorites)

['PlayStation', 'Fudge', 'Movies', 'Python for Kids']

保存の時と似ていますが、ファイルを開くときに読み込みモードを指定しているところに注意してください。 実際のでデータの読み出しは、pickleモジュールのload関数で行います。

11章

八角形に挑戦

八角形には辺が八つあるので、forを使って「進んで曲がって」を八回繰り返せば、八角形が描けますね。 次に、カメの向きについて考えてみます。 八角形を描こうとすると、描きはじめてから描き終えるまでに一周します。 これは360度回転することを意味しているので、360を八角形の辺の数で割れば、カメが一回で曲がらなければいけない角度がわかります(ヒントで述べたように、それは45度です)。

>>> import turtle
>>> t = turtle.Pen()
>>> def octagon(size):
        for x in range(1,9):
            t.forward(size)
            t.right(45)

一辺の長さを100として、この関数を呼び出してみましょう。

>>> octagon(100)

八角形をカラフルに

単純に色つきの八角形を描く関数にしてしまうと、外枠を追加することが難しくなってしまいます。 より良いアプローチは、引数を使って、八角形に色を塗るかどうかを選べるような関数にすることです。

>>> import turtle
>>> t = turtle.Pen()
>>> def octagon(size, filled):
        if filled == True:
            t.begin_fill()
        for x in range(1,9):
            t.forward(size)
            t.right(45)
        if filled == True:
            t.end_fill()

で、引数filledTrueかどうかを確認します。 Trueのときは、begin_fill関数を使って、色を塗る準備をします()。 それから、続く3行で八角形を描きます。 で再び引数filledTrueかどうかを確認し、Trueであればend_fill関数を呼び出して実際に色を塗ります()。

さっそく、この関数を試してみましょう。 一つめの星は、ペンの色を黄色にしてからfilled引数にTrueを渡して呼び出します。 それからペンの色を黒に戻し、今度はfilled引数にFalse渡して関数を呼び出すことで、外枠を描きます。

>>> t.color(1, 0.85, 0)
>>> octagon(40, True)
>>> t.color(0, 0, 0)
>>> octagon(40, False)

角の数も指定できるかな?

関数の中で星の内角を求めることがポイントです。 星の内角は、360度を星の角の数で割って求めます(下のプログラムの行を見てください)。 180度から内角を引けば、カメさんの曲がる角度(外角)がわかります。

import turtle
t = turtle.Pen()
def draw_star(size, points):
    angle = 360 / points
    for x in range(0, points):
        t.forward(size)
        t.left(180 - angle)
        t.forward(size)
        t.right(180-(angle * 2))

で、0から星の角の数まで繰り返しを行います。 繰り返しの中で、引数sizeで指定されたピクセルの距離だけカメさんを前進させます()。 で、計算によって求めた外角の角度でカメさんをターンさせ、で再び前進させます。 これで星のとげとげの、最初の「とげ」ができました。 とげとげを描きながら一周するためには、再び曲がらなければいけませんね。 内角の角度に2を掛けて、180からその値を引くとカメさんの次に曲がる角度がわかります()。 ためしに、一辺の長さが80ピクセルで星の角が70個ある星を描いてみましょう。

>>> draw_star(80, 70)

12章

キャンバスが三角形だらけ

画面を三角形でいっぱいにするための第一歩として、まずキャンバスを準備します。 400ピクセルの幅と高さのキャンバスを作りましょう。

>>> from tkinter import *
>>> import random
>>> w = 400
>>> h = 400
>>> tk = Tk()
>>> canvas = Canvas(tk, width=w, height=h)
>>> canvas.pack()

三角形は角が三つありますね。それはxとyの座標が三つずつあることを意味します。 第12章で、randomモジュールのrandrange関数を使ってたくさんの四角形を描いたように、三つの角の座標(合計六つの数字)の値もランダムに生成しましょう。 ランダムな形の三角形を描くためにrandom_triangle関数を作ります。

>>> def random_triangle():
        p1 = random.randrange(w)
        p2 = random.randrange(h)
        p3 = random.randrange(w)
        p4 = random.randrange(h)
        p5 = random.randrange(w)
        p6 = random.randrange(h)
        canvas.create_polygon(p1, p2, p3, p4, p5, p6, fill="", outline="black")

最後に、三角形をたくさん描くために繰り返しを使ってこの関数を呼び出します。

>>> for x in range(0, 100):
        random_triangle()

次に、画面をいろいろな色の三角形でいっぱいにするために色のリストを作ります。 それをプログラムのはじめの方に追加してください。

>>> from tkinter import *
>>> import random
>>> w = 400
>>> h = 400
>>> tk = Tk()
>>> canvas = Canvas(tk, width=w, height=h)
>>> canvas.pack()
>>> colors = ['red','green','blue','yellow','orange','white','purple']

色のリストからランダムに色を取得するために、randomモジュールのchoice関数を使います。 そして、取得した色をcreate_polygonに渡します。

def random_triangle():
    p1 = random.randrange(w)
    p2 = random.randrange(h)
    p3 = random.randrange(w)
    p4 = random.randrange(h)
    p5 = random.randrange(w)
    p6 = random.randrange(h)
    color = random.choice(colors)
    canvas.create_polygon(p1, p2, p3, p4, p5, p6, fill=color, outline="")

それでは、再び100回ループを回してみましょう。

>>> for x in range(0, 100):
        random_triangle()

三角形を自由に動かそう

三角形を動かすために、まずキャンバスを準備します。 それから、create_polygon関数を使って三角形を描きます。

import time
from tkinter import *
tk = Tk()
canvas = Canvas(tk, width=200, height=400)
canvas.pack()
canvas.create_polygon(10, 10, 10, 60, 50, 35)

三角形を右に移動させるには、xの値は正数、yの値は0でなければなりません。 では、move関数を繰り返し呼び出すforループを作りましょう。 一番めの引数に三角形の識別番号、引数xに10、引数yに0を渡します。

for x in range(0, 35):
    canvas.move(1, 10, 0)
    tk.update()
    time.sleep(0.05)

三角形を下に移動させるには、引数xの値を0、引数yの値を正数にするだけです。右に動かしたときと、ほとんど変わりませんね。

for x in range(0, 14):
    canvas.move(1, 0, 10)
    tk.update()
    time.sleep(0.05)

三角形を左に移動させるためには、引数xは負数でなければなりません。引数yの値は0です。 上に移動させるためには引数yを負数にし、引数xを0にします。

for x in range(0, 35):
    canvas.move(1, -10, 0)
    tk.update()
    time.sleep(0.05)

for x in range(0, 14):
    canvas.move(1, 0, -10)
    tk.update()
    time.sleep(0.05)

写真も動かせる?

あなたの写真がface.gifというファイル名で、C:ドライブに保存してあるとします。 その画像を表示してから、これまでと同じように動かしてみましょう。

import time
from tkinter import *
tk = Tk()
canvas = Canvas(tk, width=400, height=400)
canvas.pack()
myimage = PhotoImage(file='c:\\face.gif')
canvas.create_image(0, 0, anchor=NW, image=myimage)
for x in range(0, 35):
    canvas.move(1, 10, 0)
    tk.update()
    time.sleep(0.05)

このプログラムは、画像を画面の右に動かしています。 もしUbuntuやMac OS Xを使っているなら、画像のファイルの場所は違ったものになるでしょう。 Ubuntuを使っていて、画像ファイルをホームディレクトリ上に置いたなら、下のようにして画像を読み込んでください。 下の例の「malcolm」の部分は、あなたがUbuntuにログインしたときに使ったユーザ名に置き換えてください。

myimage = PhotoImage(file='/home/malcolm/face.gif')

Macを使っているなら、下のように画像を読み込んでしてください。 下の例の「samantha」の部分は、あなたがMacにログインしたときに使ったユーザ名に置き換えてください。

myimage = PhotoImage(file='/Users/samantha/face.gif')

14章

ゲームのスタートを遅らせる

プレイヤーがキャンバスをクリックすると同時にゲームがはじまるようにするには、プログラムをちょっといじらなければいけません。 はじめにPaddleクラスに新しい関数を追加します。

    def turn_left(self, evt):
        self.x = -2

    def turn_right(self, evt):
        self.x = 2

    def start_game(self, evt):
        self.started = True

start_game関数は、呼び出されるとオブジェクトの変数startedTrueをセットします。 続いて、Paddle__init__関数に、オブジェクトの変数startedを追加する必要がありますね。ここではFalseをセットしています。 さらに、マウスがクリックされたときにstart_game関数が呼ばれるようにイベントバインディングも追加します。

    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)
        self.canvas.move(self.id, 200, 300)
        self.x = 0
        self.canvas_width = self.canvas.winfo_width()
        self.started = False
        self.canvas.bind_all('<KeyPress-Left>', self.turn_left)
        self.canvas.bind_all('<KeyPress-Right>', self.turn_right)
        self.canvas.bind_all('<Button-1>', self.start_game)

でオブジェクトの変数startedを新たに追加し、で、マウスのクリックとstart_game関数を結びつけています。 最後に、プログラムの一番下にあるメインループの修正を行います。 オブジェクトの変数startedTrueだったら、ボールとラケットを描きはじめるようにします。 チェックするif文は次のようになります。

while True:
    if ball.hit_bottom == False and paddle.started == True:
        ball.draw()
        paddle.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

"Game Over"!!

"Game Over"というテキストを作るためにcreate_text関数を使いましょう。 BallクラスのオブジェクトとPaddleクラスのオブジェクトを作った後に、以下のコードを追加してください。

paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, paddle, 'red')
game_over_text = canvas.create_text(250, 200, text='GAME OVER', state='hidden')

create_text関数のstateというキーワード引数に'hidden'という文字列をセットしていますね。 こうすると、書いたテキストを見えなくすることができるのです。 プログラムのメインループに新しくif文を追加して、ゲームオーバーになったときにこのテキストが見えるようにしましょう。

while True:
    if ball.hit_bottom == False and paddle.started == True:
        ball.draw()
        paddle.draw()
    if ball.hit_bottom == True:
        time.sleep(1)
        canvas.itemconfig(game_over_text, state='normal')
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

で、オブジェクトの変数hit_bottomの値がTrueかどうかをチェックします。 もしTrueなら、テキストを表示する前に一秒間お休みします()。 で、キャンバスのitemconfig関数を使って、テキストのstate引数を'hidden'から'normal'に変更しています。 itemconfig関数には、テキストの識別番号(変数game_over_textに格納済み)と、キーワード引数stateの二つの引数を渡しています。

スピードアップ

プログラムの変更自体は簡単なものですが、変更するところを見つけることが難しいかもしれません。 ラケットでボールを打ったときに、ボールとラケットが同じ方向に移動しているならボールを加速させて、逆に反対方向に動いているときは減速させたいとします。 そのためには、ボールが横方向に動く速度に、ラケットが左右(横)に動かす速度を加えればよいのです。

Ballクラスのhit_paddle関数の中をちょっと変更しましょう。

    def hit_paddle(self, pos):
        paddle_pos = self.canvas.coords(self.paddle.id)
        if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
            if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
                self.x += self.paddle.x
                return True
        return False

で、ボールがラケットに当たったと判断されると、ボールオブジェクトのx変数に、ラケットオブジェクトのx変数の値を足します()。 たとえばラケットが右に動いていて(x変数の値が2)、ボールが3の速さで右に移動している(x変数の値が3)とすると、ボールは5の速さで跳ね返ります。 ボールがラケットに当たったときに、両方のxの値を足すことでボールのスピードを変化させているのです。

いま、何点?

Bounce!ゲームに得点を持たせるために、Scoreという新しいクラスを作りましょう。

class Score:
    def __init__(self, canvas, color):
        self.score = 0
        self.canvas = canvas
        self.id = canvas.create_text(450, 10, text=self.score, fill=color)

Scoreクラスの__init__関数の引数はcanvascolorの二つです。 で、オブジェクトの変数scoreに0をセットし、で、オブジェクトの変数canvasに、引数canvasをセットします。 で、キャンバスのcreate_text関数を使って、(450, 10)の位置に得点を表示するためのテキストを作ります。 表示するテキストはオブジェクト変数scoreの値です。 また、fill引数にcolor引数をセットすることで文字の色も指定できます。 続いて、得点を追加・再表示させるための関数を、Scoreクラスに作りましょう。

class Score:
    def __init__(self, canvas, color):
        self.score = 0
        self.canvas = canvas
        self.id = canvas.create_text(450, 10, text=self.score, fill=color)

    def hit(self):
        self.score += 1
        self.canvas.itemconfig(self.id, text=self.score)

で、hit関数を定義します。引数はありません。 で、得点に1を追加しています。 で、キャンバスのitemconfig関数を使って、表示されているテキストを新しい得点に書きかえています。 PaddleクラスのオブジェクトとBallクラスのオブジェクトを作る直前に、Scoreクラスのオブジェクトを作りましょう。

score = Score(canvas, 'green')
paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, paddle, score, 'red')
game_over_text = canvas.create_text(250, 200, text='GAME OVER', state='hidden')

次に、Ballクラスを変更します。 Scoreクラスのオブジェクトを保存して、ボールのhit_paddle関数の中で、今作ったhit関数を呼び出してください。 まず、Ball__init__関数で、引数scoreをオブジェクトの変数scoreにセットします。

    def __init__(self, canvas, paddle, score, color):
        self.canvas = canvas
        self.paddle = paddle
        self.score = score

hit_paddle関数は次のようになります。

    def hit_paddle(self, pos):
        paddle_pos = self.canvas.coords(self.paddle.id)
        if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
            if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
                self.x += self.paddle.x
                self.score.hit()
                return True
        return False

「自分でやってみよう」の四つのチャレンジ問題はできましたか? 全部できたなら、ゲームのプログラムはこのようになっているはずです。

from tkinter import *
import random
import time

class Ball:
    def __init__(self, canvas, paddle, score, color):
        self.canvas = canvas
        self.paddle = paddle
        self.score = score
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)
        starts = [-3, -2, -1, 1, 2, 3]
        random.shuffle(starts)
        self.x = starts[0]
        self.y = -3
        self.canvas_height = self.canvas.winfo_height()
        self.canvas_width = self.canvas.winfo_width()
        self.hit_bottom = False

    def hit_paddle(self, pos):
        paddle_pos = self.canvas.coords(self.paddle.id)
        if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
            if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
                self.x += self.paddle.x
                self.score.hit()
                return True
        return False

    def draw(self):
        self.canvas.move(self.id, self.x, self.y)
        pos = self.canvas.coords(self.id)
        if pos[1] <= 0:
            self.y = 3
        if pos[3] >= self.canvas_height:
            self.hit_bottom = True
        if self.hit_paddle(pos) == True:
            self.y = -3
        if pos[0] <= 0:
            self.x = 3
        if pos[2] >= self.canvas_width:
            self.x = -3

class Paddle:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)
        self.canvas.move(self.id, 200, 300)
        self.x = 0
        self.canvas_width = self.canvas.winfo_width()
        self.started = False
        self.canvas.bind_all('<KeyPress-Left>', self.turn_left)
        self.canvas.bind_all('<KeyPress-Right>', self.turn_right)
        self.canvas.bind_all('<Button-1>', self.start_game)

    def draw(self):
        self.canvas.move(self.id, self.x, 0)
        pos = self.canvas.coords(self.id)
        if pos[0] <= 0:
            self.x = 0
        elif pos[2] >= self.canvas_width:
            self.x = 0

    def turn_left(self, evt):
        self.x = -2

    def turn_right(self, evt):
        self.x = 2

    def start_game(self, evt):
        self.started = True

class Score:
    def __init__(self, canvas, color):
        self.score = 0
        self.canvas = canvas
        self.id = canvas.create_text(450, 10, text=self.score, fill=color)

    def hit(self):
        self.score += 1
        self.canvas.itemconfig(self.id, text=self.score)

tk = Tk()
tk.title("Game")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
tk.update()

score = Score(canvas, 'green')
paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, paddle, score, 'red')
game_over_text = canvas.create_text(250, 200, text='GAME OVER', state='hidden')

while True:
    if ball.hit_bottom == False and paddle.started == True:
        ball.draw()
        paddle.draw()
    if ball.hit_bottom == True:
        time.sleep(1)
        canvas.itemconfig(game_over_text, state='normal')
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

16章

チェッカーボード

背景をチェッカーボードにするため、__init__関数を変更する必要があります。

        self.bg = PhotoImage(file="background.gif")
        w = self.bg.width()
        h = self.bg.height()
        draw_background = 0
        for x in range(0, 5):
            for y in range(0, 5):
                if draw_background == 1:
                    self.canvas.create_image(x * w, y * h, \
                            image=self.bg, anchor='nw')
                    draw_background = 0
                else:
                    draw_background = 1

draw_background変数を作り、0を設定します。 は、draw_background変数が1かどうかをチェックします。 変数が1の場合は背景を描き()、変数に0を設定します()。 変数が1ではない場合は(else)、変数に1を設定します()。 このプログラムの変更により何が起こるのでしょうか? はじめてif文を実行するときは、背景を描かず、draw_background変数に1を設定します。 次にif文を実行するときは、背景画像を描き、draw_background変数の値を0に戻します。 このループ処理では、実行の度に変数の値を1と0の交互に書き換えます。 背景画像を描いたら、次のループでは背景画像を描きません。

二つの画像を使ったチェッカーボード

チェッカーボードの作り方が理解出来たら、2つの画像を使ったチェッカーボードはとても簡単です。オリジナルの背景画像と同様に、2つ目の新しい背景画像を読み込みます。次の例では、新しい背景画像background2.gif (画像編集ソフトを使って、あらかじめ画像を用意しておきます)を読み込み、オブジェクト変数bg2へセットします。

        self.bg = PhotoImage(file="background.gif")
        self.bg2 = PhotoImage(file="background2.gif")
        w = self.bg.width()
        h = self.bg.height()
        draw_background = 0
        for x in range(0, 5):
            for y in range(0, 5):
                if draw_background == 1:
                    self.canvas.create_image(x * w, y * h, \
                            image=self.bg, anchor='nw')
                    draw_background = 0
                else:
                    self.canvas.create_image(x * w, y * h, \
                            image=self.bg2, anchor='nw')
                    draw_background = 1

チェッカーボード で作成した elseの部分があります。ここで、新しい背景画像をキャンバスに表示させるためにcreate_image関数を使います。

本棚とランプ

いくつかの違った背景を描くために、 二つの画像を使ったチェッカーボード のプログラムを変更していきます。いつくかの新しい画像を読み込んだり、読み込んだ新しい画像をキャンバスに配置するプログラムを追加していきます。 この例では、画像background2.gifをコピーし、コピーした画像に本棚を描き込み、新しい画像ファイルshelf.gifとして保存します。 また、画像background2.gifのコピーをもう一つ作成し、今度はランプを描き込み、lamp.gifとして保存します。

        self.bg = PhotoImage(file="background.gif")
        self.bg2 = PhotoImage(file="background2.gif")
        self.bg_shelf = PhotoImage(file="shelf.gif")
        self.bg_lamp = PhotoImage(file="lamp.gif")
        w = self.bg.width()
        h = self.bg.height()
        count = 0
        draw_background = 0
        for x in range(0, 5):
            for y in range(0, 5):
                if draw_background == 1:
                    self.canvas.create_image(x * w, y * h, \
                            image=self.bg, anchor='nw')
                    draw_background = 0
                else:
                    count = count + 1
                    if count == 5:
                        self.canvas.create_image(x * w, y * h, \
                                image=self.bg_shelf, anchor='nw')
                    elif count == 9:
                        self.canvas.create_image(x * w, y * h, \
                                image=self.bg_lamp, anchor='nw')
                    else:
                        self.canvas.create_image(x * w, y * h, \
                                image=self.bg2, anchor='nw')
                    draw_background = 1

は、新しい画像を読み込み、それぞれbg_shelfbg_lampの変数にセットします。 では、新しくcount変数を作成します。 前の問題では、if文を使って、変数draw_backgroundの値を元に背景画像を切り替えていました。 ここでも同じ方法を使います。ただし、交互に背景画像を表示するのではなく、変数countの値を1ずつ増加させます(count = count + 1。 変数countの値を元に、どの画像を描くのかを決めます。 変数countの値が5のときは、本棚の画像を描きます。 変数countの値が9のときは、ランプの画像を描きます。 その他のプログラムについては、前の問題と同様に背景画像を交互に描くだけです。

18章

"You Win!"

Gameクラスの__init__関数の中で、Gameクラスの変数として、``You Win!''文字列を追加します。

        for x in range(0, 5):
            for y in range(0, 5):
                self.canvas.create_image(x * w, y * h, \
                        image=self.bg, anchor='nw')
        self.sprites = []
        self.running = True
        self.game_over_text = self.canvas.create_text(250, 250, \
                text='YOU WIN!', state='hidden')

ゲームが終わったときに``You Win!''文字列を表示するために、mainloop関数へelseを追加します。

    def mainloop(self):
        while 1:
            if self.running == True:
                for sprite in self.sprites:
                    sprite.move()
            else:
                time.sleep(1)
                self.canvas.itemconfig(self.game_over_text, \
                        state='normal')
            self.tk.update_idletasks()
            self.tk.update()
            time.sleep(0.01)

変更する部分は、です。if文にelseを追加しますrunning変数がTrueでは無くなったときに、else以下のブロックが実行されます。 ``You Win!''文字列がゲーム終了直後に表示されないように、1秒間スリープします。 キャンバスに``You Win!''を表示させるため、文字列の状態を'normal'に変更します

ドアをアニメーションさせよう

スティックマンがドアにたどり着いたとき、ドアをアニメーション(開いて閉じる)させるため、DoorSpriteクラスを変更します。 画像を引数として受け取るのではなく、今度は__init__関数の中で、2つのドア画像を読み込みます。

class DoorSprite(Sprite):
    def __init__(self, game, x, y, width, height):
    Sprite.__init__(self, game)
        self.closed_door = PhotoImage(file="door1.gif")
        self.open_door = PhotoImage(file="door2.gif")
        self.image = game.canvas.create_image(x, y, \
                image=self.closed_door, anchor='nw')
        self.coordinates = Coords(x, y, x + (width / 2), y + height)
        self.endgame = True

2つの画像をオブジェクト変数にセットしますDoorSpriteクラスのオブジェクト作成に、画像の引数が要らなくなったので、 doorオブジェクトを作成している部分を変更する必要があります。 変更箇所は、ゲームプログラムの最後の方にあります。

door = DoorSprite(g, 45, 30, 40, 35)

DoorSpriteクラスには、新しい2つの関数が必要になります。 1つは開いたドアを表示する関数で、もう1つは閉じたドアを表示する関数です。

    def opendoor(self):
        self.game.canvas.itemconfig(self.image, image=self.open_door)
        self.game.tk.update_idletasks()

    def closedoor(self):
        self.game.canvas.itemconfig(self.image,
                image=self.closed_door)
        self.game.tk.update_idletasks()

キャンバスのitemconfig関数を使って、表示画像をopen_doorオブジェクト変数にセットされている画像に変更します。 変更した画像を強制的に表示させるため、tkオブジェクトのupdate_idletasks関数を呼びます。 (update_idletasks関数を呼ばないと、直ちには画像が変わりません。) closedoor関数はopen_doorとほぼ同じです。 ただし、open_doorではなく、closed_doorオブジェクト変数にセットされている画像を利用します。 次の新しい関数は、StickFigureSpriteクラスに追加します。

    def end(self, sprite):
        self.game.running = False
        sprite.opendoor()
        time.sleep(1)
        self.game.canvas.itemconfig(self.image, state='hidden')
        sprite.closedoor()

gameのrunningオブジェクト変数にFalseをセットしますspriteopendoor関数を呼びますspriteは、実際にはDoorSpriteのオブジェクトです。これについては、後ほど説明します。 1秒間スリープし、スティックマンが隠れclosedoor関数を呼びます。 これは、スティックマンがドアを通り抜け、その後、ドアが閉まったように見せる演出です。

最後の変更は、StickFigureSpriteクラスの関数moveです。 変更前のプログラムは、スティックマンがドアがに衝突したとき、running変数をFalseにセットしていました。 しかし、running変数をFalseにセットする部分が、end関数に移動したので、 running変数をFalseにセットしていた部分を、end関数を呼び出すように変更します。

            if left and self.x < 0 and collided_left(co, sprite_co):
                self.x = 0
                left = False
                if sprite.endgame:
                    self.end(sprite)
            if right and self.x > 0 and collided_right(co, sprite_co):
                self.x = 0
                right = False
                if sprite.endgame:
                    self.end(sprite)

このプログラムは、スティックマンが左に移動しているかどうか、彼の左側がスプライトと衝突していないかどうか、 endgame変数がTrueかどうかをチェックしていますendgame変数がTrueの場合は、spriteDoorSpriteのオブジェクトです。 は、spriteend関数を呼んでいます。 スティックマンが右に移動していて、彼の右側がスプライトと衝突している場合も同様に変更します

プラットフォームを動かそう

床(プラットフォーム)を動かすためのクラスは、スティックマンのクラスと似ています。 固定座標を持つのではなく、床の座標を計算する必要があります。 PlatformSpriteクラスの子クラスを作成します。__init__関数は次のようになります。

class MovingPlatformSprite(PlatformSprite):
    def __init__(self, game, photo_image, x, y, width, height):
        PlatformSprite.__init__(self, game, photo_image, x, y, \
                width, height)
            self.x = 2
            self.counter = 0
            self.last_time = time.time()
            self.width = width
            self.height = height

PlatformSpriteクラスと同じ引数を渡しますと同じ引数を渡して、親クラスの__init__関数を呼びます。 これは、どんなMovingPlatformSpriteクラスのオブジェクトも、PlatformSpriteクラスのオブジェクトと同じように初期化されることを意味します。 x変数を作成します。あらかじめ2をセットしておきます(床は右へ移動することになります)。 続いてcounter変数を作成します。 このcounterは、床の移動方向を変えるタイミングをチェックするために利用します。 床が高速で左右に動いて欲しくは無いからです。 last_time変数に時間をセットします (このlast_time変数は、床の動きを遅くするために利用されます)。 最後は、widthとheightをセットするためのwidthheightです

次は、作成したクラスにcoords関数を追加します。

        self.last_time = time.time()
        self.width = width
        self.height = height

    def coords(self):
        xy = self.game.canvas.coords(self.image)
        self.coordinates.x1 = xy[0]
        self.coordinates.y1 = xy[1]
        self.coordinates.x2 = xy[0] + self.width
        self.coordinates.y2 = xy[1] + self.height
        return self.coordinates

coords関数は、StickFigureSpriteクラスのcoords関数とほぼ同じです。 widthとheightに固定値を使うのではなく、__init__関数の中でセットしたwidthとheightを利用するところが異なります。 (異なっている箇所) これは動くスプライトなので、moveも追加します。

        self.coordinates.x2 = xy[0] + self.width
        self.coordinates.y2 = xy[1] + self.height
        return self.coordinates

    def move(self):
        if time.time() - self.last_time > 0.03:
            self.last_time = time.time()
            self.game.canvas.move(self.image, self.x, 0)
            self.counter = self.counter + 1
        if self.counter > 20:
            self.x = self.x * -1
            self.counter = 0

move関数は、現在時刻がlast_timeにセットされた時間より0.03秒より大きいかどうかをチェックしています。 もし、大きい場合は、last_time変数に現在時刻をセットします。 床の画像を移動させますcounter変数に1を足しますcounterが20より大きい場合は(if)、x変数に-1を掛けて、移動方向を反対にします (正の場合は負に、負の場合は正になります)counter変数を0にセットします。 床は一つの方向に20カウント移動し、その後、移動してきた方向と反対方向へ20カウント移動します。 動く床を試すには、いくつかの床オブジェクトをPlatformSpriteからMovingPlatformSpriteへ変更します。

platform5 = MovingPlatformSprite(g, PhotoImage(file="platform2.gif"), \
        175, 350, 66, 10)
platform6 = PlatformSprite(g, PhotoImage(file="platform2.gif"), \
        50, 300, 66, 10)
platform7 = PlatformSprite(g, PhotoImage(file="platform2.gif"), \
        170, 120, 66, 10)
platform8 = PlatformSprite(g, PhotoImage(file="platform2.gif"), \
        45, 60, 66, 10)
platform9 = MovingPlatformSprite(g, PhotoImage(file="platform3.gif"), \
        170, 250, 32, 10)
platform10 = PlatformSprite(g, PhotoImage(file="platform3.gif"), \
        230, 200, 32, 10)

以下は、 プラットフォームを動かそう の変更プログラム全てです。

from tkinter import *
import random
import time

class Game:
    def __init__(self):
        self.tk = Tk()
        self.tk.title("Mr Stick Man Races for the Exit")
        self.tk.resizable(0, 0)
        self.tk.wm_attributes("-topmost", 1)
        self.canvas = Canvas(self.tk, width=500, height=500, \
                highlightthickness=0)
        self.canvas.pack()
        self.tk.update()
        self.canvas_height = 500
        self.canvas_width = 500
        self.bg = PhotoImage(file="background.gif")
        w = self.bg.width()
        h = self.bg.height()
        for x in range(0, 5):
            for y in range(0, 5):
                self.canvas.create_image(x * w, y * h, \
                        image=self.bg, anchor='nw')
        self.sprites = []
        self.running = True
        self.game_over_text = self.canvas.create_text(250, 250, \
                text='YOU WIN!', state='hidden')

    def mainloop(self):
        while 1:
            if self.running:
                for sprite in self.sprites:
                    sprite.move()
            else:
                time.sleep(1)
                self.canvas.itemconfig(self.game_over_text, \
                        state='normal')
            self.tk.update_idletasks()
            self.tk.update()
            time.sleep(0.01)

class Coords:
    def __init__(self, x1=0, y1=0, x2=0, y2=0):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

def within_x(co1, co2):
    if (co1.x1 > co2.x1 and co1.x1 < co2.x2) \
            or (co1.x2 > co2.x1 and co1.x2 < co2.x2) \
            or (co2.x1 > co1.x1 and co2.x1 < co1.x2) \
            or (co2.x2 > co1.x1 and co2.x2 < co1.x1):
        return True
    else:
        return False

def within_y(co1, co2):
    if (co1.y1 > co2.y1 and co1.y1 < co2.y2) \
            or (co1.y2 > co2.y1 and co1.y2 < co2.y2) \
            or (co2.y1 > co1.y1 and co2.y1 < co1.y2) \
            or (co2.y2 > co1.y1 and co2.y2 < co1.y1):
        return True
    else:
        return False

def collided_left(co1, co2):
    if within_y(co1, co2):
        if co1.x1 <= co2.x2 and co1.x1 >= co2.x1:
            return True
        return False

def collided_right(co1, co2):
    if within_y(co1, co2):
        if co1.x2 >= co2.x1 and co1.x2 <= co2.x2:
            return True
        return False

def collided_top(co1, co2):
    if within_x(co1, co2):
        if co1.y1 <= co2.y2 and co1.y1 >= co2.y1:
            return True
        return False

def collided_bottom(y, co1, co2):
    if within_x(co1, co2):
        y_calc = co1.y2 + y
        if y_calc >= co2.y1 and y_calc <= co2.y2:
            return True
        return False

class Sprite:
    def __init__(self, game):
        self.game = game
        self.endgame = False
        self.coordinates = None
    def move(self):
        pass
    def coords(self):
        return self.coordinates

class PlatformSprite(Sprite):
    def __init__(self, game, photo_image, x, y, width, height):
        Sprite.__init__(self, game)
        self.photo_image = photo_image
        self.image = game.canvas.create_image(x, y, \
                image=self.photo_image, anchor='nw')
        self.coordinates = Coords(x, y, x + width, y + height)

class MovingPlatformSprite(PlatformSprite):
    def __init__(self, game, photo_image, x, y, width, height):
        PlatformSprite.__init__(self, game, photo_image, x, y, \
                width, height)
        self.x = 2
        self.counter = 0
        self.last_time = time.time()
        self.width = width
        self.height = height

    def coords(self):
        xy = self.game.canvas.coords(self.image)
        self.coordinates.x1 = xy[0]
        self.coordinates.y1 = xy[1]
        self.coordinates.x2 = xy[0] + self.width
        self.coordinates.y2 = xy[1] + self.height
        return self.coordinates

    def move(self):
        if time.time() - self.last_time > 0.03:
            self.last_time = time.time()
            self.game.canvas.move(self.image, self.x, 0)
            self.counter += 1
            if self.counter > 20:
                self.x *= -1
                self.counter = 0

class DoorSprite(Sprite):
    def __init__(self, game, x, y, width, height):
        Sprite.__init__(self, game)
        self.closed_door = PhotoImage(file="door1.gif")
        self.open_door = PhotoImage(file="door2.gif")
        self.image = game.canvas.create_image(x, y, \
                image=self.closed_door, anchor='nw')
        self.coordinates = Coords(x, y, x + (width / 2), y + height)
        self.endgame = True

    def opendoor(self):
        self.game.canvas.itemconfig(self.image, image=self.open_door)
        self.game.tk.update_idletasks()

    def closedoor(self):
        self.game.canvas.itemconfig(self.image, \
                image=self.closed_door)
        self.game.tk.update_idletasks()

class StickFigureSprite(Sprite):
    def __init__(self, game):
        Sprite.__init__(self, game)
        self.images_left = [
            PhotoImage(file="figure-L1.gif"),
            PhotoImage(file="figure-L2.gif"),
            PhotoImage(file="figure-L3.gif")
        ]
        self.images_right = [
            PhotoImage(file="figure-R1.gif"),
            PhotoImage(file="figure-R2.gif"),
            PhotoImage(file="figure-R3.gif")
        ]
        self.image = game.canvas.create_image(200, 470, \
                image=self.images_left[0], anchor='nw')
        self.x = -2
        self.y = 0
        self.current_image = 0
        self.current_image_add = 1
        self.jump_count = 0
        self.last_time = time.time()
        self.coordinates = Coords()
        game.canvas.bind_all('<KeyPress-Left>', self.turn_left)
        game.canvas.bind_all('<KeyPress-Right>', self.turn_right)
        game.canvas.bind_all('<space>', self.jump)

    def turn_left(self, evt):
        if self.y == 0:
            self.x = -2

    def turn_right(self, evt):
        if self.y == 0:
            self.x = 2

    def jump(self, evt):
        if self.y == 0:
            self.y = -4
            self.jump_count = 0

    def animate(self):
        if self.x != 0 and self.y == 0:
            if time.time() - self.last_time > 0.1:
                self.last_time = time.time()
                self.current_image += self.current_image_add
                if self.current_image >= 2:
                    self.current_image_add = -1
                if self.current_image <= 0:
                    self.current_image_add = 1
        if self.x < 0:
            if self.y != 0:
                self.game.canvas.itemconfig(self.image, \
                        image=self.images_left[2])
            else:
                self.game.canvas.itemconfig(self.image, \
                        image=self.images_left[self.current_image])
        elif self.x > 0:
            if self.y != 0:
                self.game.canvas.itemconfig(self.image, \
                        image=self.images_right[2])
            else:
                self.game.canvas.itemconfig(self.image, \
                        image=self.images_right[self.current_image])

    def coords(self):
        xy = self.game.canvas.coords(self.image)
        self.coordinates.x1 = xy[0]
        self.coordinates.y1 = xy[1]
        self.coordinates.x2 = xy[0] + 27
        self.coordinates.y2 = xy[1] + 30
        return self.coordinates

    def move(self):
        self.animate()
        if self.y < 0:
            self.jump_count += 1
            if self.jump_count > 20:
                self.y = 4
        if self.y > 0:
            self.jump_count -= 1
        co = self.coords()
        left = True
        right = True
        top = True
        bottom = True
        falling = True
        if self.y > 0 and co.y2 >= self.game.canvas_height:
            self.y = 0
            bottom = False
        elif self.y < 0 and co.y1 <= 0:
            self.y = 0
            top = False
        if self.x > 0 and co.x2 >= self.game.canvas_width:
            self.x = 0
            right = False
        elif self.x < 0 and co.x1 <= 0:
            self.x = 0
            left = False
        for sprite in self.game.sprites:
            if sprite == self:
                continue
            sprite_co = sprite.coords()
            if top and self.y < 0 and collided_top(co, sprite_co):
                self.y = -self.y
                top = False
            if bottom and self.y > 0 and collided_bottom(self.y, \
                    co, sprite_co):
                self.y = sprite_co.y1 - co.y2
                if self.y < 0:
                    self.y = 0
                bottom = False
                top = False
            if bottom and falling and self.y == 0 \
                    and co.y2 < self.game.canvas_height \
                    and collided_bottom(1, co, sprite_co):
                falling = False
            if left and self.x < 0 and collided_left(co, sprite_co):
                self.x = 0
                left = False
                if sprite.endgame:
                    self.end(sprite)
            if right and self.x > 0 \
                and collided_right(co, sprite_co):
                self.x = 0
                right = False
                if sprite.endgame:
                    self.end(sprite)
        if falling and bottom and self.y == 0 \
                and co.y2 < self.game.canvas_height:
            self.y = 4
        self.game.canvas.move(self.image, self.x, self.y)

    def end(self, sprite):
        self.game.running = False
        sprite.opendoor()
        time.sleep(1)
        self.game.canvas.itemconfig(self.image, state='hidden')
        sprite.closedoor()

g = Game()
platform1 = PlatformSprite(g, PhotoImage(file="platform1.gif"), \
        0, 480, 100, 10)
platform2 = PlatformSprite(g, PhotoImage(file="platform1.gif"), \
        150, 440, 100, 10)
platform3 = PlatformSprite(g, PhotoImage(file="platform1.gif"), \
        300, 400, 100, 10)
platform4 = PlatformSprite(g, PhotoImage(file="platform1.gif"), \
        300, 160, 100, 10)
platform5 = MovingPlatformSprite(g, PhotoImage(file="platform2.gif"),\
        175, 350, 66, 10)
platform6 = PlatformSprite(g, PhotoImage(file="platform2.gif"), \
        50, 300, 66, 10)
platform7 = PlatformSprite(g, PhotoImage(file="platform2.gif"), \
        170, 120, 66, 10)
platform8 = PlatformSprite(g, PhotoImage(file="platform2.gif"), \
        45, 60, 66, 10)
platform9 = MovingPlatformSprite(g, PhotoImage(file="platform3.gif"),\
        170, 250, 32, 10)
platform10 = PlatformSprite(g, PhotoImage(file="platform3.gif"), \
        230, 200, 32, 10)
g.sprites.append(platform1)
g.sprites.append(platform2)
g.sprites.append(platform3)
g.sprites.append(platform4)
g.sprites.append(platform5)
g.sprites.append(platform6)
g.sprites.append(platform7)
g.sprites.append(platform8)
g.sprites.append(platform9)
g.sprites.append(platform10)
door = DoorSprite(g, 45, 30, 40, 35)
g.sprites.append(door)
sf = StickFigureSprite(g)
g.sprites.append(sf)
g.mainloop()