#0. はじめに#
progateを終えたら何をしようか。「実際に何か作ってみたいが何を作ればいいかわからない」、「一人で0から作ることができるか不安」などの意見があるだろう。そこで、私がPythonの基礎を詰め込んだ後に実際に作成したテトリスについてまとめてみようと思う。作成当時にYouTubeにソースコードを開示した。こちらも参照してほしいが、今回は作成当初より短くコードをまとめている。
Pythonの基本文法が既に理解していることを前提にする。また、Tikinter文法については一緒にまとめる。
#1. 使用モジュール
Tkinter
とrandm
ライブラリをインポートする。messageboxはmsgbox
、randintはrnd
として呼び出せるようにする。
from tkinter import *
from tkinter import messagebox as msgbox
from random import randint as rnd
#2. グローバル変数の定義
グローバル変数を定義する。関数を作成中に追加することもあるが、あらかじめ利用するとわかっている変数は定義しておく。なお色は16進数のカラーコードで定義する。
vertical = 20 #基盤の縦のマス数
side = 10 #基盤の横のマス数
size = 30 #1マスの大きさ
mino_size = 4 #ミノの(縦横最大の)ブロック数
form = 0 #ミノの種類
mode = 0 #ミノの向き
y = -1 #ミノのy座標
x = 4 #ミノのx座標
speed = 500 #落下速度
#色の定義
colors = ["#00ffff", #I:0
"#0000ff", #J:1
"#ffa500", #L:2
"#ffff00", #O:3
"#008000", #S:4
"#800080", #T:5
"#ff0000", #Z:6
"#404040"] #foundation:7
#3. 基盤の作成
基盤を作成する。何もないマスを7、壁を8として2次元のリストで基盤を表す。YouTubeで図解しているから参照してほしい。
foundation = [[7 for i in range(side + 2)] for j in range(vertical + 2)]
for i in range(vertical + 2):
foundation[i][0], foundation[i][side + 1] = 8, 8
foundation[vertical + 1] = [8 for i in range(side + 2)]
次に、draw_foundation()
関数を定義し基盤を描画する。create_rectangle(x1, y1, x2, y2, fill="色")
で(x1, y1), (x2, y2)からなる四角形を描画することができる。
def draw_foundation():
for v in range(vertical):
v1 = v * size
v2 = v1 + size
for s in range(side):
s1 = s * size
s2 = s1 + size
for c in range(len(colors)):
if foundation[v + 1][s + 1] == c:
color = colors[c]
cv.create_rectangle(s1, v1, s2, v2, fill=color) #四角形を描画
#4. ミノの作成
ミノを作成する。4×4のマス内にミノのブロックがあるところのみ描画する。mino_data
にミノの1マス1マスの座標を入力。mino
には4×4のマスを用意し、mino_dataの座標のみ数値を変える。こちらもYouTubeで図解している。
mino_data = [[[[2, 0], [2, 1], [2, 2], [2, 3]], #I:0
[[0, 1], [1, 1], [2, 1], [3, 1]],
[[1, 0], [1, 1], [1, 2], [1, 3]],
[[0, 2], [1, 2], [2, 2], [3, 2]]],
[[[1, 0], [2, 0], [2, 1], [2, 2]], #J:1
[[1, 1], [1, 2], [2, 1], [3, 1]],
[[2, 0], [2, 1], [2, 2], [3, 2]],
[[1, 1], [2, 1], [3, 0], [3, 1]]],
[[[1, 2], [2, 0], [2, 1], [2, 2]], #L:2
[[1, 1], [2, 1], [3, 1], [3, 2]],
[[2, 0], [2, 1], [2, 2], [3, 0]],
[[1, 0], [1, 1], [2, 1], [3, 1]]],
[[[1, 1], [1, 2], [2, 1], [2, 2]], #O:3
[[1, 1], [1, 2], [2, 1], [2, 2]],
[[1, 1], [1, 2], [2, 1], [2, 2]],
[[1, 1], [1, 2], [2, 1], [2, 2]]],
[[[1, 1], [1, 2], [2, 0], [2, 1]], #S:4
[[1, 1], [2, 1], [2, 2], [3, 2]],
[[2, 1], [2, 2], [3, 0], [3, 1]],
[[1, 0], [2, 0], [2, 1], [3, 1]]],
[[[1, 1], [2, 0], [2, 1], [2, 2]], #T:5
[[1, 1], [2, 1], [2, 2], [3, 1]],
[[2, 0], [2, 1], [2, 2], [3, 1]],
[[1, 1], [2, 0], [2, 1], [3, 1]]],
[[[1, 0], [1, 1], [2, 1], [2, 2]], #Z:6
[[1, 2], [2, 1], [2, 2], [3, 1]],
[[2, 0], [2, 1], [3, 1], [3, 2]],
[[1, 1], [2, 0], [2, 1], [3, 0]]]]
mino = [[7 for i in range(mino_size)] for j in range(mino_size)]
次にcreate_mino()
関数を定義しランダムにミノを生成する。randint
で0~6までランダムに数字を選び、ミノの種類を指定、mino_dataから座標を取り出す。
def create_mino():
global form, mino
form = rnd(0, 6)
for i in range(len(mino_data[form][mode % 4])):
y = mino_data[form][mode % 4][i][0]
x = mino_data[form][mode % 4][i][1]
mino[y][x] = form
ミノを描画する。mino
のマスがform
なら描画する。それ以外は描画しない。これによってミノのブロック部分のみを描画することができる。
def draw_mino():
for v in range(mino_size):
v1 = (v + y - 1) * size
v2 = v1 + size
for s in range(mino_size):
s1 = (s + x - 1) * size
s2 = s1 + size
if mino[v][s] == form:
cv.create_rectangle(s1, v1, s2, v2, fill = colors[form])
#5. メイン関数を定義
メイン関数を定義して実際にデバッグしてみる。Tk()
でウィンドウを作成、Cancas("ウィンドウ", width="幅", height="高さ")
でキャンバスを作成することができる。また、pack()
でオブジェクトを配置するオプションを指定する。mainloop()
はプログラムが終了するまでループさせる。
def main():
global win, cv
win = Tk() #ウィンドウの作成
cv = Canvas(win, width=side*size, height=vertical * size) #キャンバスの作成
cv.pack() #オブジェクト配置のオプション
create_mino()
draw_foundation()
draw_mino()
win.mainloop()
定義したmain()
関数を呼び出す。
if __name__ == "__main__":
main()
デバッグしてみよう。基盤が描画され、その上部中央にミノが描画されていたらここまでは完璧だ。ミノは上部が画面外にはみ出すように描画している。
#6. ミノの移動と回転
ミノの移動と回転はクラスで定義する。ミノを1マス移動後、あるいは回転後の基盤の状態を調べ、何もなければ実行、壁や他のミノなどがあれば実行しないようにする。
まずはミノの座標と基盤の状態を保存しておくglobal変数を追加定義する。
y_data = [0, 0, 0, 0]
x_data = [0, 0, 0, 0]
foundation_data = [7, 7, 7, 7]
次にクラスを作成する。インスタンス変数にはx移動、y移動、方向を引数として受け取り定義する。
class Move_mino:
def __init__(self, next_y, next_x, next_mode):
self.next_y = next_y
self.next_x = next_x
self.next_mode = next_mode
次に動作後の基盤状況を調べるインスタンスメソッドを作成する。現在のミノの座標から実行後にミノの位置する基盤の状態を抽出する。
def reference(self):
global x_data, y_data, foundation_data
for i in range(len(mino_data[form][mode % 4])):
y_data[i] = mino_data[form][(mode + self.next_mode) % 4][i][0] + y
x_data[i] = mino_data[form][(mode + self.next_mode) % 4][i][1] + x
foundation_data[i] = foundation[y_data[i] + self.next_y][x_data[i] + self.next_x]
抽出した基盤データを元に次のマスにブロックがなければ移動する関数move_mino()
を定義する。キーボード処理を行う関数には引数としてe(Event)
を渡す必要がある。また、delete("all")
でキャンバスの描画をすべて消すことができる。これを行わないと上書きされ続け処理が重くなってしまう。
def move_mino(self, e):
global x, y
self.reference()
if foundation_data == [7, 7, 7, 7]:
y += 1 * self.next_y
x += 1 * self.next_x
cv.delete("all") #キャンバス描画の消去
draw_foundation()
draw_mino()
上記の関数を用いてミノが落下する関数drop_ mino()
を定義する。落下スピードが200より大きい場合は落下速度を早くする。after("ミリ秒", "関数")
は指定ミリ秒後に関数を呼び出すことができる。また、キーボードで操作する関数を呼び出しているので、引数にEvent
を渡す必要がある。
def drop_mino(self):
global speed
self.move_mino(Event)
if speed > 200:
speed -= 1
win.after(speed, self.drop_mino)
ミノがy軸方向に進行不可能な場合の処理を追加する。ミノを基盤情報に追加し、新しいミノを作成する。
global foundation, mino, mode #グローバル変数を追加
x += 1 * self.next_x #記載済み
if self.next_y == 1 and foundation_data != [7, 7, 7, 7]:
for i in range(len(y_data)):
foundation[y_data[i]][x_data[i]] = form
mode = 0
y = -1
x = 4
mino = [[7 for i in range(mino_size)] for j in range(mino_size)]
create_mino()
draw_foundation() #記載済み
回転後の基盤に何もなければ回転する関数spin_mino()
を定義する。
def spin_mino(self, e):
self.reference()
global mode, mino
if foundation_data == [7, 7, 7, 7]:
mode += 1 * self.next_mode
mino = [[7 for i in range(mino_size)] for j in range(mino_size)]
for i in range(len(mino_data[form][mode % 4])):
y = mino_data[form][mode % 4][i][0]
x = mino_data[form][mode % 4][i][1]
mino[y][x] = form
cv.delete("all")
draw_foundation()
draw_mino()
ここまで出来たら一度動くか確かめてみよう。main()
関数に以下を追加しデバッグしてみる。なおmain()
関数内に前記したdraw_foundation()
とdraw_mino()
は消しておく。
draw_foundation() #削除
draw_mino() #削除
left = Move_mino(0, -1, 0)
right = Move_mino(0, 1, 0)
under = Move_mino(1, 0, 0)
left_spin = Move_mino(0, 0, -1)
right_spin = Move_mino(0, 0, 1)
win.bind("<Left>", left.move_mino) #左キーが押されたら左に移動
win.bind("<Right>", right.move_mino) #右気ーが押されたら右に移動
win.bind("<Return>", under.move_mino) #エンターキーが押されたら下に移動
win.bind("<Up>", right_spin.spin_mino) #上キーが押されたら右回転
win.bind("<Down>", left_spin.spin_mino) #下キーが押されたら左回転
under.drop_mino()
win.mainloop() #記載済み
動いていたら成功だ。上記のbind(""<Key>"", "関数")
を利用することにより指定したKey
が押された際に関数を呼び出すことができる。
#7. 列消去とゲームオーバー判定
minoが停止した際に列がそろったら消すdelete()
関数を定義する。
def delete():
for v in range(len(foundation)):
if (7 in foundation[v]) == False and foundation[v] != [8 for i in range(side + 2)]:
del foundation[v]
add_foundation = [7 for i in range(side + 2)]
add_foundation[0], add_foundation[side + 1] = 8, 8
foundation.insert(0, add_foundation)
次にミノが最上列を超えたらゲームを終了するgame_over()
関数を定義する。msgboc.showinfo(message="メッセージ")
とすることでメッセージボックスを用いてメッセージを表示できる。
def game_over():
top_foundation = [7 for i in range(side + 2)]
top_foundation[0], top_foundation[side + 1] = 8, 8
if foundation[1] != top_foundation:
msgbox.showinfo(message = "Game Over") #メッセージの表示
quit() #プログラムの終了
作成したdelete()
とgame_over()
をミノが停止した際の処理に追加する。
foundation[y_data[i]][x_data[i]] = form #記載済み
delete()
game_over()
mode = 0 #記載済み
以上で完成。
#8. まとめ
コーディングお疲れさまでした。これでテトリスの作成は終了です。長い記事になってしまいましたが最後まで読んでいただきありがとうございました。
今後も作成したものを記事にまとめていきますのでフォローよろしくお願いします♪また、YouTubeとTwitterも更新してますのでご覧ください。
それではまた、次の記事でお会いしましょう。