プログラミング上達講座2：テトリスのデータ構造を考える

プログラミング上達講座の2回目です。

テトリスを題材にしてプログラムを考えてみましょう。

解説動画はこちら

[![表示されなかったらすみません](https://img.youtube.com/vi/2Yn4L2pAbdc/0.jpg)](https://youtu.be/2Yn4L2pAbdc)


## テトリスのデータ構造を考える

`テトリス`は4つのブロックで構成された`テトリミノ`を
フィールドに並べていくパズルゲームです。

このようなパズルゲームは
プログラミングの題材には打って付け!!

早速考えてみましょう。

### 1.テトリスゲームの素材を描画してみよう

#### 1.1フィールドのデータ化

盤面(10 x 20マス)のフィールドと
上方以外を囲むブロック下記のような
フィールドをデータ化してみよう。

■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■□□□□□□□□□□■
■■■■■■■■■■■■

ヒント：

白黒のマスを1つずつ表示させるのは面倒臭いので
繰り返しを用いてみよう。

全体では12マスかける21行となる。



#### 1.1回答

単純に上記になるようにデータを作成して出力するだけ。
データをひとまとめにしておくと再利用しやすくなります。

縦横の揃ったデータはリスト型を用いると
表現しやすく管理しやすくなります。

リストの掛け算は要素の繰り返し足し算は要素の連結となります。

```python
data = [['■'] + ['□']*10 + ['■']]*20 + [['■']*12]

for y in data:
    print(''.join(y))
```


この場合は文字としてデータを出力しますが

パズルゲームの場合文字列でデータを格納すると
あとあと面倒い事もあるので数値で考えるケースが多いです。

```python

data = [[1] + [0]*10 + [1]]*20 + [[1]*12]

for y in data:
    for x in y:
        print('■' if x==1 else '□',end='')
    print()
```

■なら1
□なら0としてデータを格納して出力時に変換します。

これで結果は一緒になります。


#### 1.2テトリミノのデータ化

テトリミノ(7つ)
□■□□　□■□□　□■□□　□□■□　□■□□　□□■□　□□□□
□■□□　□■■□　□■□□　□□■□　□■■□　□■■□　□■■□
□■□□　□■□□　□■■□　□■■□　□□■□　□■□□　□■■□
□■□□　□□□□　□□□□　□□□□　□□□□　□□□□　□□□□

上記のような７種類のテトリミノをデータ化してみよう


#### 1.2回答

こちらもゲームで用いる際には数値や文字を値とするリスト型のような
縦横の個数が決まったデータ型を用いると表現しやすくなります。

7つのテトリミノは独立して考え回転などを考慮すると
縦横4x4マスのリスト型で定義するのが再利用しやすい形でしょう。

```python

line = [[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]]
tblo = [[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]]
lr   = [[0,1,0,0],[0,1,0,0],[0,1,1,0],[0,0,0,0]]
ll   = [[0,0,1,0],[0,0,1,0],[0,1,1,0],[0,0,0,0]]
hr   = [[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,0,0]]
hl   = [[0,0,1,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]]
sq   = [[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]]

t_minos = [line,tblo,lr,ll,hr,hl,sq]

for mino in t_minos:
    for y in mino:
        for x in y:
            print('■' if x==1 else '□',end='')
        print()
    print()
```
□■□□
□■□□
□■□□
□■□□

□■□□
□■■□
□■□□
□□□□

□■□□
□■□□
□■■□
□□□□

□□■□
□□■□
□■■□
□□□□

□■□□
□■■□
□□■□
□□□□

□□■□
□■■□
□■□□
□□□□

□□□□
□■■□
□■■□
□□□□



### 2.テトリミノを回転させる関数を作ってみよう

先ほど作ったテトリミノのデータを使って
90度ずつ回転を行う関数`rotate`を作成しよう

```python
def rotate(data , 角度):
    処理
    return data
```

□■□□
□■■□
□■□□
□□□□

これを90度回転させると

□□□□
□■■■
□□■□
□□□□


### 2.回答

まずテトリミノのデータを用意します。

```python
data = [[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]]
```

毎回描画コードを書くのは面倒なので関数化します。

```python
def print_block(block):
    for y in block:
        for x in y:
            print('■' if x==1 else '□',end='')
        print()
print_block(data)
```
□■□□
□■■□
□■□□
□□□□




#### 2.回答 [::-1] とzip関数を用いる方法

リストに対して`[::-1]`を用いると元のリストの浅いコピーを逆順で作成します。
`zip`関数は各引数の先頭から1つの要素を繰り返し抽出し`Tuple`を作成します。

合わせると要素が90度回転したことになります。

```python
data = [[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]]
print_block(data[::-1])
```
□□□□
□■□□
□■■□
□■□□

```
print_block(zip(*data[::-1]))
```
□□□□
□■■■
□□■□
□□□□

関数にまとめると

```python
def rotate(data,num):
    tmp = data.copy()
    if num==0:
        return tmp
    rotated = list(map(list, zip(*tmp[::-1])))
    for i in range(num//90-1):
        rotated = list(map(list, zip(*rotated[::-1])))
    return rotated
​
data = [[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]]
block = rotate(data,270)
print_block(block)
```
□□□□
□■□□
■■■□
□□□□



#### 2.回答 numpyライブラリを用いる方法

numpyライブラリにはこうした行列のようなデータを
操作する機能を備えたものがあります。

`numpy.rot90(データ , 回転数)`

数値の配列を反時計回りに90度回転させる
回転数は0~を指定 , 1で90度回転
回転数を-の値にすると時計回り

```python
import numpy as np

def rotate2(data,num):
    return np.rot90(data.copy(),-num//90)

data = [[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]]
block = rotate2(data,90)
print_block(block)
```

□□□□
□■■■
□□■□
□□□□

こちらは2行で出来てしまいますね。


## まとめ

データ構造の考え方とデータの操作方法は
パズルゲームがおすすめです。

今後もやっていきますので色々解いてみて下さい。

それでは。

## 作者の情報

乙pyのHP：
http://www.otupy.net/

Youtube：
https://www.youtube.com/channel/UCaT7xpeq8n1G_HcJKKSOXMw

Twitter：
https://twitter.com/otupython