PythonでTETRISアルゴリズムを考える 2

PythonでTETRISアルゴリズムを考える 2

前回の続き。

    # テトリミノの移動や回転をする
    def move(self, to: str=' ') -> bool:

        pt = self.pt
        # left
        if   to == 'h':
            q_li = [pt[0]  , pt[1]-1, self.rot]
        # right
        elif to == 'l':
            q_li = [pt[0]  , pt[1]+1, self.rot]
        # rotate right
        elif to == 'f':
            q_li = [pt[0]  , pt[1]  ,(self.rot+1)%4]
        # rotate left
        elif to == 'a':
            q_li = [pt[0]  , pt[1]  ,(self.rot-1)%4]
        # down
        else:
            q_li = [pt[0]+1, pt[1]  , self.rot]

        for ix in self.t4mino_li[self.cur][q_li[-1]]:
            if ix[0]+q_li[0] >= self.board_size[0]          \
            or ix[1]+q_li[1] < 0                            \
            or ix[1]+q_li[1] >= self.board_size[1]          \
            or self.board[ix[0]+q_li[0], ix[1]+q_li[1]] != 0:
                return False

        pt[0], pt[1], self.rot = q_li
        return True

移動や回転ができる場合はTrue、そうでなければFalseを返すようにしています。

このように書き換えてからプログラムを再度実行してみましょう!

 0|      5   |
 1|    555   |
 2|          |
...
19|          |
--+----------+
h
...
h
 0|  5       |
 1|555       |
 2|          |
...
19|          |
--+----------+
h
 0|  5       |
 1|555       |
 2|          |
...
19|          |
--+----------+

ちゃんとのめり込みをしないようになりました!

6. テトリミノを保存する

積み上がったテトリミノを保存するために、save_board()を宣言します。ついでに、テトリミノが横で揃ったときに消す操作も追加します。

class Tetris(object):...

    # ボードに保存する
    def save_board(self):

        pt = self.pt
        for ix in self.t4mino_li[self.cur][self.rot]:
            self.board[ix[0]+pt[0], ix[1]+pt[1]] = self.cur

        # flush
        for i in range(self.board_size[0]):
            if (self.board[i] != 0).all():
                for i_ in reversed(range(i)):
                    self.board[i_+1] = self.board[i_]

これでボードに保存するための関数が完成しました。

あとはこれをgame()に書き込んでゆきます。

class Tetris(object):...

    # ゲームのメイン部分
    def game(self):

        while True:

            self.display()
            _pt = self.pt.copy()
            key = input()
            self.move(key)
            self.move()     # テトリミノを1個下げる
            # テトリミノが動いていない場合、新たにテトリミノを生成する
            if _pt == self.pt:
                self.save_board()
                self.gen_t4mino()

        print('END')

7. 終了条件を書く

ついにラストです!

テトリスは、0行目の中央4マスに動かせないテトリミノがある場合、ゲームオーバーとなります。

そのために関数yet()を宣言してゆきます。ついでに、game()も書き換えます。

class Tetris(object):...

    # ゲームのメイン部分
    def game(self):

        while self.yet():

            self.display()
            _pt = self.pt.copy()
            key = input()
            self.move(key)
            self.move()     # テトリミノを1個下げる
            if _pt == self.pt:
                self.save_board()
                self.gen_t4mino()

        print('END')


    # ゲームの終了条件
    def yet(self) -> bool:

        mid = self.board_size[1] // 2
        if (self.board[0, mid-2:mid+2] == 0).all():
            return True
        return False

これでテトリスをPythonで実現できました!

さいごに

テトリスは最初に言った通り様々な言語で実現されています。

でもほとんど最初から自力で作るのは至難でした。ここまでで8時間近くかかってしまいました……。

他にもいろいろ作ってみたいものが見つかったら、また書いてゆきます。

ソースコード

「分解されてて読みにくい!」という人のために、ソースコードを公開します。ほとんどアルゴリズム自体なので、著作権フリーみたいな感じです。

pyqtとかpygameとかに組み込んでみて、動作を確認してみるのもありだと思います。

import numpy as np


class Tetris(object):

    def __init__(self):

        self.board_size = [20, 10]
        self.init_t4mino()
        self.init_board()
        self.gen_t4mino()


    # ボードを初期化する
    def init_board(self):

        self.board = np.zeros(self.board_size, dtype=np.int)


    # テトリミノを初期化する
    def init_t4mino(self):
        
        # including rotation table
        self.t4mino_li = [
            # i
            [[[1, i] for i in range(4)],
             [[i, 1] for i in range(4)]] * 2,
            # o
            [[[0, 0], [0, 1], [1, 0], [1, 1]]] * 4,
            # t
            [[[0, 1]] + [[1, i] for i in range(3)],
             [[1, 2]] + [[i, 1] for i in range(3)],
             [[2, 1]] + [[1, i] for i in range(3)],
             [[1, 0]] + [[i, 1] for i in range(3)]],
            # j
            [[[0, 0]] + [[1, i] for i in range(3)],
             [[0, 2]] + [[i, 1] for i in range(3)],
             [[2, 2]] + [[1, i] for i in range(3)],
             [[2, 0]] + [[i, 1] for i in range(3)]],
            # l
            [[[0, 2]] + [[1, i] for i in range(3)],
             [[2, 2]] + [[i, 1] for i in range(3)],
             [[2, 0]] + [[1, i] for i in range(3)],
             [[0, 0]] + [[i, 1] for i in range(3)]],
            # s
            [[[0, 1], [0, 2], [1, 0], [1, 1]],
             [[0, 0], [1, 0], [1, 1], [2, 1]]] * 2,
            # t
            [[[0, 0], [0, 1], [1, 1], [1, 2]],
             [[0, 1], [1, 0], [1, 1], [2, 0]]] * 2
        ]


    # ゲームのメイン部分
    def game(self):

        while self.yet():

            self.display()
            _pt = self.pt.copy()
            key = input()
            self.move(key)
            self.move()     # テトリミノを1個下げる
            if _pt == self.pt:
                self.save_board()
                self.gen_t4mino()

        print('END')


    # ゲームの終了条件
    def yet(self) -> bool:

        mid = self.board_size[1] // 2
        if (self.board[0, mid-2:mid+2] == 0).all():
            return True
        return False


    # ボードを保存する
    def save_board(self):
        pt = self.pt
        for ix in self.t4mino_li[self.cur][self.rot]:
            self.board[ix[0]+pt[0], ix[1]+pt[1]] = self.cur + 1

        # flush
        for i in range(self.board_size[0]):
            if (self.board[i] != 0).all():
                for i_ in reversed(range(i)):
                    self.board[i_+1] = self.board[i_]


    # ボードに表示する要素
    def element(self, i: int, j: int) -> str:

        ix_li = self.t4mino_li[self.cur][self.rot]
        pt = self.pt

        for ix in ix_li:
            if ix[0]+pt[0] == i and ix[1]+pt[1] == j:
                return self.cur + 1

        tmp = self.board[i, j]

        return ' ' if tmp == 0 else tmp


    # ボードの表示
    def display(self):

        ix_li = self.t4mino_li[self.cur][self.rot]
        pt = self.pt
        for i in range(self.board_size[0]):
            print('{:>2}|'.format(i), end='')
            for j in range(self.board_size[1]):
                print(self.element(i, j), end='')

            print('|')

        print('--+' + '-' * self.board_size[1] + '+')


    # テトリミノの移動や回転をする
    def move(self, to: str=' ') -> bool:

        pt = self.pt
        # left
        if   to == 'h':
            q_li = [pt[0]  , pt[1]-1, self.rot]
        # right
        elif to == 'l':
            q_li = [pt[0]  , pt[1]+1, self.rot]
        # rotate right
        elif to == 'f':
            q_li = [pt[0]  , pt[1]  ,(self.rot+1)%4]
        # rotate left
        elif to == 'a':
            q_li = [pt[0]  , pt[1]  ,(self.rot-1)%4]
        # down
        else:
            q_li = [pt[0]+1, pt[1]  , self.rot]

        for ix in self.t4mino_li[self.cur][q_li[-1]]:
            if ix[0]+q_li[0] >= self.board_size[0]          \
            or ix[1]+q_li[1] < 0                            \
            or ix[1]+q_li[1] >= self.board_size[1]          \
            or self.board[ix[0]+q_li[0], ix[1]+q_li[1]] != 0:
                return False

        pt[0], pt[1], self.rot = q_li
        return True


    # テトリミノを生成する
    def gen_t4mino(self):

        self.cur = np.random.randint(7)
        # i, otjlst
        self.pt = [0, self.board_size[1] // 2 - (2 if self.cur == 0 else 1)]
        self.pt = [0, self.board_size[1] // 2 - 1]
        self.rot = 0



if __name__ == '__main__':
    
    t = Tetris()
    t.game()