tkinterで手動で更新するライフゲームをつくる

PaizaのPython講座を受けて、さて、何を作ろうとgoogleで探していたら、マイナビニュースの【ゼロからはじめるPython】にライフゲームの作り方（https://news.mynavi.jp/article/zeropython-9/) があったのでそれを参考に、手動で更新するライフゲームを作ってみた。

実行すると、下記の黒い画面が出て、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661642/81a85b0f-8c00-c332-21cd-8f9ef6aaa951.png)

クリックするとセルを配置、取り消しができる。
下記は【ペンタデカスロン】という配置
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661642/55eb2d64-862c-60cb-e790-3714a3008562.png)

[Enter]キーを押すごとに更新する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661642/03e4ad94-73dc-48d4-741e-02c74e8d868a.png)

【ペンタデカスロン】という配置は一定周期で繰り返し、滅びることはないが、
途中で１つのセルを加えることで、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661642/126bf387-06f4-6abe-cd59-06bb31ed4087.png)
バランスが崩れ、新しい形ができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661642/fd9cc7a4-1be8-fe12-9c3f-24e9a27af393.png)

```py
import tkinter as tk

WIDTH, HEIGHT = 600, 400  # Canvasの大きさ
cell = {'size': 20, 'color': 'green'}  # セルの情報
COLS, ROWS = WIDTH//cell['size'], HEIGHT//cell['size']  # Canvasの列数、行数
data = [[False for x in range(COLS)] for y in range(ROWS)]  # セルの存在

win = tk.Tk()
win.title('LifeGame')

# 世代の更新回数
text = tk.StringVar()  # 文字列を保持するWidget変数
update = 0  # 更新回数の初期値
text.set(update)  # 更新回数を設定
label = tk.Label(win, textvariable=text,  font=('IPAexゴシック', '24'))
label.pack()

# Canvasの設定
cv = tk.Canvas(win, width=WIDTH, height=HEIGHT, bg='black')
cv.pack()


# セルを配置しやすいように格子状に線を描画する
def draw_grid():
    for x in range(COLS):
        x1 = x * cell['size']
        cv.create_line(x1, 0, x1, HEIGHT, fill='white')
    for y in range(ROWS):
        y1 = y * cell['size']
        cv.create_line(0, y1, WIDTH, y1, fill='white')


# マウスのクリックでセルを配置したり、取り消したりする
def place_cells(e):
    draw_grid()
    # クリックした座標から、セルを描画するindexを計算する
    x, y = e.x // cell['size'], e.y // cell['size']
    # 既に描画しているセルを削除する。
    if data[y][x]:
        cv.delete('current')
        data[y][x] = False
    # セルを描画する。
    else:
        x1, y1 = x * cell['size'], y * cell['size']
        cv.create_oval(x1, y1, x1+cell['size'], y1+cell['size'], fill=cell['color'])
        data[y][x] = True


cv.bind('<Button>', place_cells)  # Canvasがクリックされたら


# 周りのセルがいくつ生きているかによって次世代の自分の運命が決まる
def check_cells(x, y):
    # 周りのセルの相対座標
    tbl = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)]
    cnt = 0  # 周りのセルがいくつ生きているかの個数
    # 周りのセルを順々にチェックする
    for t in tbl:
        xx, yy = x + t[0], y + t[1]  # 周りのセルの絶対値を計算する。
        if not 0 <= xx < COLS or not 0 <= yy < ROWS:
            continue
        if data[yy][xx]:
            cnt += 1  # 周りのセルが生きていればカウントする。

    if cnt == 3:
        return True  # 誕生
    if data[y][x]:  # 自分が生きているなら
        if 2 <= cnt <= 3:
            return True  # 生存
        return False  # 過密、過疎
    return data[y][x]  # 現状維持


# セルの次世代を運命を調べる。
def next_turn(e):
    global data
    global update
    # 更新したデータを1時保存する
    data2 = [[check_cells(x, y) for x in range(COLS)] for y in range(ROWS)]
    data = data2  # データを更新
    update += 1  # 更新回数をカウントする
    text.set(update)  # 世代の回数を更新
    draw()


win.bind('<Return>', next_turn)  # Enterが押されたら


# セルを描画する。
def draw():
    cv.delete('all')
    for y in range(ROWS):
        for x in range(COLS):
            if data[y][x]:
                x1, y1 = x * cell['size'], y * cell['size']
                cv.create_oval(x1, y1, x1+cell['size'], y1+cell['size'], fill=cell['color'])


win.mainloop()
```

ライフゲームは色々なパターンがあるらしい。https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661642/6defa5ab-0c54-e769-1c0a-57046628595c.png)