おたすけサイトへようこそ! このサイトには、本を読み進めるときに助けになる、色々な情報が書かれています。 本の内容と現実の世界に、どこがズレがあることに気がついたら、このサイトにかかれてあることをよく読んでみてください。 きっと問題を解決する助けがみつかるはずです。
なんと、本が印刷されて一週間しかたっていないのに、 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 = 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の場合は、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()