Pythonでライフゲームを作りました

f:id:collatz:20210205171514g:plain

セルの初期状態をランダムに作成します。
結果はカレントディレクトリにGIFアニメーションで出力されます。
設定値
盤の大きさ:lifegame.pyのBOARD_SIZE
世代数:lifegame.pyのGENERATIONS
初期状態の生きているセルの割合:lifegame.pyのLIFE_RATE
GIFで表示するセルの大きさ:my_gif.pyのSQUARE_SIZE
GIFで表示する1世代の表示時間:DURATION(ミリ秒)

lifegame.py

import copy
import my_gif
import random

#その座標がboardの範囲内にあるか?
def is_on_board(row, column):
    return (0 <= row < BOARD_SIZE) and (0 <= column < BOARD_SIZE)

#その座標に隣接するcellのリストを返す
def adjacent_cells(row, column):
    cells = []

    if is_on_board(row - 1, column - 1):
        cells.append((row - 1, column - 1))
    if is_on_board(row - 1, column):
        cells.append((row - 1, column))
    if is_on_board(row - 1, column + 1):
        cells.append((row - 1, column + 1))

    if is_on_board(row, column - 1):
        cells.append((row, column - 1))
    if is_on_board(row, column + 1):
        cells.append((row, column + 1))

    if is_on_board(row + 1, column - 1):
        cells.append((row + 1, column - 1))
    if is_on_board(row + 1, column):
        cells.append((row + 1, column))
    if is_on_board(row + 1, column + 1):
        cells.append((row + 1, column + 1))

    return cells

#その座標の隣接するcellの中で生きているcellの数を返す
def get_number_of_living_cells(board, row, column):
    n = 0
    for r, c in adjacent_cells(row, column):
        if board[r][c] == LIFE:
            n += 1
    return n

#その座標の次の世代の状態を返す
def next_state(board, row, column):
    number_of_living_cells = get_number_of_living_cells(board, row, column)
    if number_of_living_cells == 3:
        return LIFE
    elif number_of_living_cells == 2:
        return board[row][column]
    else:
        return DEATH

#そのboardの次の世代のboardを返す
def make_next_board(board):
    next_board = [[DEATH for i in range(BOARD_SIZE)] for j in range(BOARD_SIZE)]
    for row in range(BOARD_SIZE):
        for column in range(BOARD_SIZE):
            next_board[row][column] = next_state(board, row, column)
    return next_board

#各世代のboardのリストを返す
def make_boards_list(board):
    boards_list = []
    boards_list.append(board)
    for i in range(GENERATIONS):
        next_board = make_next_board(board)
        boards_list.append(next_board)
        board = copy.deepcopy(next_board)
    return boards_list

#生死の状態をランダムに作成したboardを返す
def initialize_board(rate):
    board = [[DEATH for i in range(BOARD_SIZE)] for j in range(BOARD_SIZE)]
    for row in range(BOARD_SIZE):
        for column in range(BOARD_SIZE):
            if random.random() < rate:
                board[row][column] = LIFE
            else:
                board[row][column] = DEATH
    return board

#main
DEATH = 0
LIFE = 1
BOARD_SIZE = 10
GENERATIONS = 20
LIFE_RATE = 0.2

my_gif.make_gif(make_boards_list(initialize_board(LIFE_RATE)))

GIFアニメーションを作成するモジュール(my_gif.py)

from PIL import Image, ImageDraw

SQUARE_SIZE = 20
DURATION = 500

def square_positions(row, column):
    x1 = column * SQUARE_SIZE
    y1 = row * SQUARE_SIZE
    x2 = column * SQUARE_SIZE + SQUARE_SIZE
    y2 = row * SQUARE_SIZE + SQUARE_SIZE
    return (x1, y1, x2, y2)

def make_image(data):
    data_size_width = len(data[0])
    data_size_height = len(data)

    image_size_width = SQUARE_SIZE * data_size_width
    image_size_height = SQUARE_SIZE * data_size_height

    im = Image.new("RGB", (image_size_width, image_size_height), 'white')
    draw = ImageDraw.Draw(im)
    for row in range(data_size_height):
        for column in range(data_size_width):
            if data[row][column] == 0:
                draw.rectangle(square_positions(row, column), fill='white')
            else:
                draw.rectangle(square_positions(row, column), fill='black')
    return im

def make_gif(data_list):
    images = []
    for data in data_list:
        images.append(make_image(data))
    images[0].save('lifegame.gif', save_all=True, append_images=images[1:], duration=DURATION)