テトリス風落ち物パズルを作る part02 ミノ配置


[前回](https://qiita.com/comet725/items/5ecc49da19bfac3d25db)ではフィールドの表示を行った、
今回はテトリミノを配置してみる。

# Tiled で編集

前回のようにTiledでデータを作っていく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398956/d57bed24-dc3f-eea4-4b50-3a7c895a8d7d.png)


出来上がったファイル

<details><summary>mino_block.tmx</summary>

```xml:mino_block.tmx
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="28" height="16" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
 <tileset firstgid="1" source="mino_block.tsx"/>
 <layer id="1" name="mino_block" width="28" height="16">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,2,2,0,0,0,3,3,0,4,0,0,0,0,0,5,0,0,6,0,0,7,7,0,8,8,8,8,
0,0,2,2,0,3,3,0,0,4,4,4,0,5,5,5,0,6,6,6,0,7,7,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,
0,0,0,2,0,0,3,0,0,0,4,4,0,0,5,0,0,0,6,0,0,7,7,0,0,0,8,0,
0,0,2,2,0,0,3,3,0,0,4,0,0,0,5,0,0,0,6,6,0,7,7,0,0,0,8,0,
0,0,2,0,0,0,0,3,0,0,4,0,0,0,5,5,0,0,6,0,0,0,0,0,0,0,8,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,
0,2,2,0,0,0,3,3,0,4,4,4,0,5,5,5,0,6,6,6,0,7,7,0,8,8,8,8,
0,0,2,2,0,3,3,0,0,0,0,4,0,5,0,0,0,0,6,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,
0,0,2,0,0,3,0,0,0,0,4,0,0,5,5,0,0,0,6,0,0,7,7,0,0,8,0,0,
0,2,2,0,0,3,3,0,0,0,4,0,0,0,5,0,0,6,6,0,0,7,7,0,0,8,0,0,
0,2,0,0,0,0,3,0,0,4,4,0,0,0,5,0,0,0,6,0,0,0,0,0,0,8,0,0
</data>
 </layer>
</map>
```

</details>


# 読み込んで加工

作成したtmxファイルを読み込んでみる

```python:mino.py
from enum import Enum, auto

import pytmx

# tmx ファイルからミノを構成するブロックの配置を定義する
def get_mino_data(t, size=4):
    tmx_data = pytmx.TiledMap('mino_block.tmx')
    
    shapes = [[] for _ in range(4)]
    tile = 0
    for r in range(4):
        for y in range(size):
            for x in range(size):
                gid = tmx_data.get_tile_gid(size*t+x, size*(r%4)+y, 0)
                if gid != 0:
                    tile = gid
                    shapes[r].append((x,y))        
    return tile, shapes

# ミノの形を定義する列挙型
class minotype(Enum):
    Z = 'Z', auto(), *get_mino_data(0)
    S = 'S', auto(), *get_mino_data(1)
    J = 'J', auto(), *get_mino_data(2)
    L = 'L', auto(), *get_mino_data(3)
    T = 'T', auto(), *get_mino_data(4)
    O = 'O', auto(), *get_mino_data(5)
    I = 'I', auto(), *get_mino_data(6)
    def __init__(self, name, value, tile, shape):
        self._name = name
        self._value_ = value
        self.tile = tile
        self.shape = tuple(tuple(s) for s in shape)
    
    @classmethod
    def values(cls):
        # Order dictionary -> list
        return list(cls.__members__.values())

def main():
    for mino in minotype.values():
        print(mino.name, mino.value, mino.tile, mino.shape)
        
if __name__ == '__main__':
    main()


```

とりあえず実行してみた結果はこんな感じ
```実行結果
Z 1 1 (((1, 1), (2, 1), (2, 2), (3, 2)), ((3, 1), (2, 2), (3, 2), (2, 3)), ((1, 2), (2, 2), (2, 3), (3, 3)), ((2, 1), (1, 2), (2, 2), (1, 3)))
S 2 2 (((2, 1), (3, 1), (1, 2), (2, 2)), ((2, 1), (2, 2), (3, 2), (3, 3)), ((2, 2), (3, 2), (1, 3), (2, 3)), ((1, 1), (1, 2), (2, 2), (2, 3)))
J 3 3 (((1, 1), (1, 2), (2, 2), (3, 2)), ((2, 1), (3, 1), (2, 2), (2, 3)), ((1, 2), (2, 2), (3, 2), (3, 3)), ((2, 1), (2, 2), (1, 3), (2, 3)))
L 4 4 (((3, 1), (1, 2), (2, 2), (3, 2)), ((2, 1), (2, 2), (2, 3), (3, 3)), ((1, 2), (2, 2), (3, 2), (1, 3)), ((1, 1), (2, 1), (2, 2), (2, 3)))
T 5 5 (((1, 1), (2, 1), (3, 1), (2, 2)), ((2, 1), (1, 2), (2, 2), (2, 3)), ((2, 1), (1, 2), (2, 2), (3, 2)), ((2, 1), (2, 2), (3, 2), (2, 3)))
O 6 6 (((1, 1), (2, 1), (1, 2), (2, 2)), ((1, 1), (2, 1), (1, 2), (2, 2)), ((1, 1), (2, 1), (1, 2), (2, 2)), ((1, 1), (2, 1), (1, 2), (2, 2)))
I 7 7 (((0, 1), (1, 1), (2, 1), (3, 1)), ((2, 0), (2, 1), (2, 2), (2, 3)), ((0, 2), (1, 2), (2, 2), (3, 2)), ((1, 0), (1, 1), (1, 2), (1, 3)))
```

問題なく読み込んでいるようだ

# フィールドに配置

前回作ったプログラムに追記してミノを表示してみる。

```python:main.py
import pygame
from pytmx import util_pygame as tmx_util

from mino import minotype

TILE_SIZE = 32

MATRIX_SIZE = (20,10)
MATRIX_POS  = (11,1)


class Game:
    
    # 番兵用に連想配列でラクする
    matrix = {
        i:{
            j:0 for j in range(-1,11)
        } for i in range(-20,21)
    }

    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((1024, 768))

    def draw_tile(self, tmx, x, y, t):
        tile_image = tmx.get_tile_image_by_gid(t)
        if tile_image:
            self.screen.blit(tile_image, (x * TILE_SIZE, y * TILE_SIZE))

    def run(self):
        clock = pygame.time.Clock()
        running = True
        
        self.tmx_data = tmx_util.load_pygame('field.tmx')
        self.mino_block = tmx_util.load_pygame('mino_block.tmx')
        
        # 番兵を設置
        for i in range(-20,21):
            self.matrix[i][-1] = 1
            self.matrix[i][10] = 1
        for i in range(-1,11):
            self.matrix[20][i] = 1

        # test code: 各種ミノを設置
        for i, m in enumerate(minotype.values()):
            for x,y in m.shape[0]:
                self.matrix[i*3+y-1][2+x] = m.tile
        
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

            self.screen.fill((0, 0, 0))

            for layer in self.tmx_data.layers:
                if layer.name == "mainfield":
                    for x, y, gid in layer.iter_data():
                        self.draw_tile(self.tmx_data, x, y, gid)
            
            x, y = MATRIX_POS
            for i in range(-1, MATRIX_SIZE[0]):
                for j in range(MATRIX_SIZE[1]):
                    self.draw_tile(self.mino_block, x+j, y+i, self.matrix[i][j])

            pygame.display.flip()
            clock.tick(60)

        pygame.quit()

if __name__ == "__main__":
    game = Game()
    game.run()


```

実行してみた結果はこんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398956/9ce353bb-9a36-cec5-2f63-b9cbc8c9f275.png)

無事にテトリミノを配置できているようだ。

# まとめ
- ブロックを配置したマップから、各種テトリミノを定義した
- 定義したテトリミノをフィールドに配置してみた

次回:[テトリス風落ち物パズルを作る part03 キー入力・落下処理 #Python - Qiita](https://qiita.com/comet725/items/5004cbea97247d5bf33b)