おたすけサイトへようこそ! このサイトには、本を読み進めるときに助けになる、色々な情報が書かれています。 本の内容と現実の世界に、どこがズレがあることに気がついたら、このサイトにかかれてあることをよく読んでみてください。 きっと問題を解決する助けがみつかるはずです。
なんと、本が印刷されて一週間しかたっていないのに、 Pythonの本家サイトのデザインが、 すごくカッコいいものに変わってしまいました。 なので、本に書いてあるインストーラーを入手するためのリンク等が、なくなってしまっています。 せっかく一所懸命に画面写真を撮って本に載せたのに………。
ここでは、WindowsとMac OS Xを使っている人向けに、 Pythonのインストーラーの入手方法を説明しておくことにしましょう。
なんと、こんなに変わってしまいました!
ページにある、メニューの「Downloads」の部分にマウスカーソルを乗せると、
のようになります。この画面で表示されている [Dowload]ボタンは、 Pythonのソースコード (Pythonそのもののプログラムコード)なので、 ダウンロードしても、そのままインストールできません。 Windowsを使っている人は、[Windows]の部分をクリックします。 Mac OS X を使っている人は [Mac OS X] の部分をクリックしましょう。
Windowsの部分をクリックすると、 どのバージョンのPythonを使うかを選択する画面になります。
Pythonのバージョン3を使うので、[Latest Python 3.x.x Release - ...]と書かれているリンクをクリックしましょう。
選択したバージョンのダウンロードページが表示されるので、画面の下の方までスクロールして、 [Download]と書かれているところを探します。
ダウンロードするべきファイルは二つのうちのどちらかです。 使っているWindowsが32bitのときは、32bit版を、 64bitのときは64bit版のインストーラのリンクをクリックして、 ダウンロードしましょう。
Mac OS Xの部分をクリックすると、 どのバージョンのPythonを使うかを選択する画面になります。
Pythonのバージョン3を使うので、[Latest Python 3.x.x Release - ...]と書かれているリンクをクリックしましょう。
選択したバージョンのダウンロードページが表示されるので、画面の下の方までスクロールして、 [Download]と書かれているところを探します。
たぶん、あなたが使っているMac OS Xは64bit版でしょう。 上の例でマークのあるインストール用のファイルをダウンロードします。
好きなものや食べ物をそれぞれ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
わかりやすい変数名を使いましょう。名前を文字列に埋め込むために %s %s
のように、
プレースホルダーを2つ使います。
>>> first_name = 'Brando' >>> last_name = 'Ickett' >>> print('Hi there, %s %s!' % (first_name, last_name)) Hi there, Brando Ickett!
四角形を書くには、正方形の時と同じようにすれば良いのです。 ただし、向かい合う辺の長さを同じにします。
>>> 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)
正三角形以外の三角形を書くためには、角度の計算のために、三角関数という数学を使います。 これは高校生になると習います。もし、あなたが、もう三角関数を勉強しているのなら、 いろいろチャレンジしてみてください。
角が無い四角形を良く観察すると、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)
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の間にある」
という条件を書くには下のようにします。
and
とor
をうまく組み合わせれば、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!
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
関数の引数は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))
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
レジナルドを踊らせるための関数を追加する前に、Animals
, Mammals
, Giraffes
クラスについてもう少し見てみましょう。
まずは、Animals
クラスからです。本文ではAnimate
クラスの子クラスとしていましたが、
ここでは簡略にしてあります。
class Animals: def breathe(self): print('breathing') def move(self): print('moving') def eat_food(self): print('eating food')
Mammals
はAnimals
の子クラスです。
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
Pen
はturtle
モジュールで提供されているクラスなので、
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)
他に、もっとうまいやり方がありそうですよね。工夫してみましょう。
abs
関数は、数字の絶対値を計算するものでした。
つまり、負の数(0より小さい数)が、正の数(0より大きい数)に変換されるとも言えます。
次のプログラムでは、最初のものは計算結果が20に、2つ目のものは0になります。
>>> a = abs(10) + abs(-10) >>> print(a) 20 >>> b = abs(-10) + -10 >>> print(b) 0
まずは、文字列を変数に割り当てます。
その後に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])
for
とrange
を組み合わせて、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')
問題は、2つのprint
がそれぞれどんな表示をするかを尋ねています。
まず、最初のものを見てみましょう。
>>> car1 = Car()>>> car1.wheels = 4 >>> car2 = car1 >>> car2.wheels = 3 >>> print(car1.wheels) 3
なぜ print
で表示されるのは 3 なのでしょう。
car1
のwheels
には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
関数で行います。
八角形には辺が八つあるので、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()
filled
がTrue
かどうかを確認します。
True
のときは、begin_fill
関数を使って、色を塗る準備をします(filled
がTrue
かどうかを確認し、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度を星の角の数で割って求めます(下のプログラムの
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))
size
で指定されたピクセルの距離だけカメさんを前進させます(
>>> draw_star(80, 70)
画面を三角形でいっぱいにするための第一歩として、まずキャンバスを準備します。 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')
プレイヤーがキャンバスをクリックすると同時にゲームがはじまるようにするには、プログラムをちょっといじらなければいけません。
はじめに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
関数は、呼び出されるとオブジェクトの変数started
にTrue
をセットします。
続いて、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
関数を結びつけています。
最後に、プログラムの一番下にあるメインループの修正を行います。
オブジェクトの変数started
がTrue
だったら、ボールとラケットを描きはじめるようにします。
チェックする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"というテキストを作るために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__
関数の引数はcanvas
とcolor
の二つです。
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
関数を定義します。引数はありません。
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)
背景をチェッカーボードにするため、__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の場合は背景を描き(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
関数を使います。
いくつかの違った背景を描くために、
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_shelf
とbg_lamp
の変数にセットします。
count
変数を作成します。 前の問題では、if
文を使って、変数draw_background
の値を元に背景画像を切り替えていました。
ここでも同じ方法を使います。ただし、交互に背景画像を表示するのではなく、変数count
の値を1ずつ増加させます(count = count + 1
)count
の値を元に、どの画像を描くのかを決めます。
変数count
の値が5のときはcount
の値が9のときは
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秒間スリープします'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
をセットしますsprite
のopendoor
関数を呼びます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 = Falseif 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
の場合は、sprite
はDoorSprite
のオブジェクトです。
sprite
のend
関数を呼んでいます。
スティックマンが右に移動していて、彼の右側がスプライトと衝突している場合も同様に変更します
床(プラットフォーム)を動かすためのクラスは、スティックマンのクラスと似ています。
固定座標を持つのではなく、床の座標を計算する必要があります。
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
変数を作成しますlast_time
変数に時間をセットしますlast_time
変数は、床の動きを遅くするために利用されます)。
最後は、widthとheightをセットするためのwidth
とheight
です
次は、作成したクラスに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にセットします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()