Conways Game of life на Python

Это мой первый пост, где я хочу рассказать про самый известный клеточный автомат "Игра жизнь", а также напишем её на Python с использованием графики Pygame.

Conways Game of life ( по русски 'Игра жизнь' ) - клеточный автомат, придуманный Джоном Конвеем в далеком 1970 году.

Правила очень просты, вся игра происходит в 2D пространстве (плоскости) на которой могут быть 2 типа клеток "Живые" - 0 и "Пустые" -1. Основные правила жизни клетки таковы Birth3 Survive23, что значит клетка рождается при трёх соседях и выживает при двух или трёх, иначе умирает.

Определения кол-ва соседей происходит по соседству Мура.

Небольшая предыстория создания с Википедии.

Джон Конвей заинтересовался проблемой, предложенной в 1940-х годах известным математиком Джоном фон Нейманом, который пытался создать гипотетическую машину, которая может воспроизводить сама себя. Джону фон Нейману удалось создать математическую модель такой машины с очень сложными правилами. Конвей попытался упростить идеи, предложенные Нейманом, и в конце концов ему удалось создать правила, которые стали правилами игры «Жизнь».

Впервые описание этой игры было опубликовано в октябрьском (1970 год) выпуске журнала Scientific American, в рубрике «Математические игры» Мартина Гарднера (Martin Gardner)

Уверен, вам надоела вся эта теория, приступаем к написанию симуляции на Python/Pygame

Надеюсь у вас уже установлен Python, если же нет то вперёд устанавливать.

Для графики скачайте библиотеку pygame с помощью команды в терминале "pip install pygame" или же "pip3 install pygame" (если у вас высвечивается ошибка "pip не является внешней или внутренней командой" используя каждую команду ,скорее всего вы не поставили PATH при установке Python)

Для тестирования графики попробуем данный код, создающий сетку во всё окно

# Импорты
import pygame as p
from pygame.locals import *

# Константы цветов RGB
BLACK = (0 , 0 , 0)
WHITE = (255 , 255 , 255)
# Создаем окно
root = p.display.set_mode((1000 , 500))
# Основной цикл
while 1:
    # Заполняем экран белым цветом
    root.fill(WHITE)
    
    # Рисуем сетку
    for i in range(0 , root.get_height() // 20):
        p.draw.line(root , BLACK , (0 , i * 20) , (root.get_width() , i * 20))
    for j in range(0 , root.get_width() // 20):
        p.draw.line(root , BLACK , (j * 20 , 0) , (j * 20 , root.get_height()))
    # Нужно чтобы виндовс не думал что программа "не отвечает"
    for i in p.event.get():
        if i.type==	QUIT:
          quit()
    p.display.update()

А теперь сделаем список клеток и функцию определения кол-ва соседей

Как работает определение кол-ва соседей
  1. Создаем систему соседства system

  2. Создаем переменную счетчик count

  3. Проходимся по каждому элементу системы

  4. Если сосед клетки по системе "живой", увеличиваем counter.

  5. Возвращаем count

# 2х мерный список с помощью генераторных выражений
cells=[ [0 for j in range(root.get_width()//20)] for i in range(root.get_height()//20)]
cells2=cells
# Функция определения кол-ва соседей
def near(pos: list , system=[[-1 , -1] , [-1 , 0] , [-1 , 1] , [0 , -1] , [0 , 1] , [1 , -1] , [1 , 0] , [1 , 1]]):
    count = 0
    for i in system:
        if cells[(pos[0] + i[0]) % len(cells)][(pos[1] + i[1]) % len(cells[0])]:
            count += 1
    return count

И так, теперь сделаем основную логику.

    # Проходимся по всем клеткам
    for i in range(len(cells)):
        for j in range(len(cells[0])):
            # Если клетка жива
            if cells[i][j]:
                # Если у соседей не 2 или 3 соседа
                if near([i , j]) not in (2 , 3):
                    cells2[i][j] = 0
                    continue
                # В ином случае
                cells2[i][j] = 1
                continue
            # Если клетка мертва и у неё 3 соседа    
            if near([i , j]) == 3:
                cells2[i][j] = 1
                continue
            # В противном случае    
            cells2[i][j] = 0
    cells = cells2
Полный код
# Импорты
import time

import pygame as p
import random
from pygame.locals import *

# Константы цветов RGB
BLACK = (0 , 0 , 0)
WHITE = (255 , 255 , 255)
# Создаем окно
root = p.display.set_mode((1000 , 500))
# 2х мерный список с помощью генераторных выражений
cells = [[random.choice([0 , 1]) for j in range(root.get_width() // 20)] for i in range(root.get_height() // 20)]


# Функция определения кол-ва соседей
def near(pos: list , system=[[-1 , -1] , [-1 , 0] , [-1 , 1] , [0 , -1] , [0 , 1] , [1 , -1] , [1 , 0] , [1 , 1]]):
    count = 0
    for i in system:
        if cells[(pos[0] + i[0]) % len(cells)][(pos[1] + i[1]) % len(cells[0])]:
            count += 1
    return count


# Основной цикл
while 1:
    # Заполняем экран белым цветом
    root.fill(WHITE)

    # Рисуем сетку
    for i in range(0 , root.get_height() // 20):
        p.draw.line(root , BLACK , (0 , i * 20) , (root.get_width() , i * 20))
    for j in range(0 , root.get_width() // 20):
        p.draw.line(root , BLACK , (j * 20 , 0) , (j * 20 , root.get_height()))
   # Нужно чтобы виндовс не думал что программа "не отвечает"
    for i in p.event.get():
        if i.type == QUIT:
            quit()
    # Проходимся по всем клеткам

    for i in range(0 , len(cells)):
        for j in range(0 , len(cells[i])):
            print(cells[i][j],i,j)
            p.draw.rect(root , (255 * cells[i][j] % 256 , 0 , 0) , [i * 20 , j * 20 , 20 , 20])
    # Обновляем экран
    p.display.update()
    cells2 = [[0 for j in range(len(cells[0]))] for i in range(len(cells))]
    for i in range(len(cells)):
        for j in range(len(cells[0])):
            if cells[i][j]:
                if near([i , j]) not in (2 , 3):
                    cells2[i][j] = 0
                    continue
                cells2[i][j] = 1
                continue
            if near([i , j]) == 3:
                cells2[i][j] = 1
                continue
            cells2[i][j] = 0
    cells = cells2

Проверка кода
Проверка кода

Все получилось, скорость тоже не расстраивает.

В следующих статьях попробуем реализовать модификации игры "Жизнь".

@LedinecMing
13.12.2020 00:50 UTC
Первоисточник

Комментарии

@tmnhy
12.12.2020 20:55 UTC
0
Почему двумерный список, когда достаточно и просто списка?
Что будет с клеткой на краю игрового поля?
@LedinecMing
16.12.2020 06:11 UTC
0

Клетка на краю будет брать соседей по поверхности Тора, тоесть с других краёв

@ABy
13.12.2020 06:15 UTC
0

Кстати, вспомнил что когда-то давно, когда только узнал про эту игру и различные фигуры-долгожители, хотел гонять симуляции рандомных начальных состояний, чтобы искать такие фигуры. Так вот, может кто-то на хабре таким занимался? Было бы интересно почитать.

@Xaliuss
13.12.2020 08:04 UTC
+1
Помню как я лет 20 назад делал всё на Паскале процедурами. На экран выводилось поле 250-250 попиксельно и меню (640 на 480 ограничение того паскаля), всё перерисовывалось каждый ход, была возможность остановить прорисовку ускорив обновление в десятки раз, можно было добавить или убрать вручную клетку на поле по координатам или же целиком глайдер, корабль и несколько других объектов, рандомизировать поле. Всего 1000+ строк с кодом со всеми фичами.

Надо бы как-то обновить, чтобы запускалась сейчас, а то USES CRT,GRAPH и INITGRAPH(DET,MODUL,'C:\BP\BGI') хорошо работало в 98 винде, а сейчас такой доступ к экрану не сработает.
@mskotyn
13.12.2020 13:50 UTC
+1
Лабораторная по информатике? Или как оно там сейчас в школе называется? Код выглядит как порт с Паскаля, да еще и сделанный влоб… Я не говорю про Лас-Вегас, азартные игры и женщин легкого поведения — ну там дополнительные состояния, третье и больше измерение, затейливая геометрия пространства. Я не говорю про то, что в Python массивы таки есть (и встроенный array и продвинутый в numpy), что можно использовать словарь с ключами-таплами или объекты. Но! Не нарезать код даже на функции… При этом оставить комментарии, по которым в первом приближении можно выделить блоки кода в отдельные функции…
@LedinecMing
16.12.2020 06:13 UTC
0

Постараюсь учесть ваши пожелания.
А также, когда я использовал numpy массивы, код становился в разы медленнее.

@
13.12.2020 16:39 UTC
0
НЛО прилетело и опубликовало эту надпись здесь
@Airee
16.12.2020 08:34 UTC
0

А чем enumerate() заместо range(len()) не мил? Да и continue вместо else усложняет чтение.

@LedinecMing
16.12.2020 21:53 UTC
0

Я предпочитаю меньше else, больше continue

17.12.2020 09:08 UTC
0
Тогда не показывайте свой код никому.