LoginSignup
11
9

More than 3 years have passed since last update.

Python Tkinterでテトリスを作ってみる

Last updated at Posted at 2021-02-01

0. はじめに

 progateを終えたら何をしようか。「実際に何か作ってみたいが何を作ればいいかわからない」、「一人で0から作ることができるか不安」などの意見があるだろう。そこで、私がPythonの基礎を詰め込んだ後に実際に作成したテトリスについてまとめてみようと思う。作成当時にYouTubeにソースコードを開示した。こちらも参照してほしいが、今回は作成当初より短くコードをまとめている。
 Pythonの基本文法が既に理解していることを前提にする。また、Tikinter文法については一緒にまとめる。

1. 使用モジュール

Tkinterrandmライブラリをインポートする。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)からなる四角形を描画することができる。

draw_foundation()

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から座標を取り出す。

create_mino()
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なら描画する。それ以外は描画しない。これによってミノのブロック部分のみを描画することができる。

draw_mino()
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()はプログラムが終了するまでループさせる。

main()
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移動、方向を引数として受け取り定義する。

Move_mino
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

次に動作後の基盤状況を調べるインスタンスメソッドを作成する。現在のミノの座標から実行後にミノの位置する基盤の状態を抽出する。

Move_mino.reference
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")でキャンバスの描画をすべて消すことができる。これを行わないと上書きされ続け処理が重くなってしまう。

Move_mino.move_mino()
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を渡す必要がある。

Move_mino.drop_mino()
def drop_mino(self):
    global speed
    self.move_mino(Event)
    if speed > 200:
        speed -= 1
    win.after(speed, self.drop_mino)

ミノがy軸方向に進行不可能な場合の処理を追加する。ミノを基盤情報に追加し、新しいミノを作成する。

Move_mino.move_mino()
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()を定義する。

Move_mino.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()は消しておく。

main()
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()関数を定義する。

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="メッセージ")とすることでメッセージボックスを用いてメッセージを表示できる。

game_over()
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()をミノが停止した際の処理に追加する。

Move_mino.move_mino()
    foundation[y_data[i]][x_data[i]] = form     #記載済み
delete()
game_over()
mode = 0     #記載済み

以上で完成。

8. まとめ

 コーディングお疲れさまでした。これでテトリスの作成は終了です。長い記事になってしまいましたが最後まで読んでいただきありがとうございました。
 今後も作成したものを記事にまとめていきますのでフォローよろしくお願いします♪また、YouTubeTwitterも更新してますのでご覧ください。
 それではまた、次の記事でお会いしましょう。

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9