fsd

ssdg

from tkinter import *       # импортируем всё из графической библиотеки tkinter
import types                # для динамического добавления метода show к каждой кнопке

cols = rows = 12            # столбцы и строки (определяют размер игрового поля)
playground = [False] * cols * rows  # список состояний клеток:
                                    # False = мертва (чёрная), True = жива (зелёная)
colors = ['black', 'green'] # Индекс для colors берём из playground
                            # и таким образом определяем цвет кнопки
power = False               # флаг, запущена игра или включён режим редактирования

def start(event):
    ''' обработчик нажатия на клавишу пробел — запуск/останов игры '''
    global power
    power = not power       # переключаем состояние (игра или режим редактирования)
    if power:               # если игра запущена, вызываем функцию life()
        life()

def roomie(n):
    """Подсчитывает количество живых соседей для клетки с индексом n."""
    s = 0                   # счётчик соседей
    for i in (-1, 0, 1):    # перебираем номера 8-ми кнопок, соседей кнопки n
        for j in (-cols, 0, cols):
            # исключаем из соседей клетку с номером n и соседей за пределами поля
            if i+j != 0 and n+i+j >= 0 and n+i+j < cols*rows:
                s += playground[n+i+j]
    return s

def life():                 # Conway Game Life
    """
    Выполняет один шаг игры «Жизнь» и планирует следующий шаг через 500 мс.
    Клетка становится живой, если у неё 3 соседа (рождение)
    или она уже жива и у неё есть 2 соседа (выживание)
    """
    global playground
    pg = [False] * (cols * rows)    # временное поле для нового поколения
    for i in range(cols * rows):
        s = roomie(i)               # количество живых соседей
        pg[i] = s == 3 or (playground[i] and s == 2)    # правила Конвея
    playground = pg
    if power:                       # если игра запущена,
        tk.after(500, life)         # планируем следующий шаг

def play(n):                        # обработчик нажатия на кнопку-клетку
    playground[n] = not playground[n]   # инвертируем состояние клетки

def show(self):
    """
    Метод, добавляемый каждой кнопке. Обновляет цвет кнопки
    в соответствии с состоянием клетки (состояние клетки в playground).
    """
    self.config(bg=colors[playground[self.n]],
                activebackground=colors[playground[self.n]])
    self.after(400, self.show)      # повторяем обновление каждые 400 мс

tk = Tk()                           # создаём главное окно программы
tk.geometry('400x400')              # устанавливаем размер окна
tk.title("Conway Game")             # заголовок окна программы
tk.bind('<space>', start)           # привязываем пробел к функции start

# Создаём игровое поле из кнопок, расположенных в rows рядов по cols штук в каждом
for n in range(cols * rows):        # цикл для перебора всех клеток
    if n % cols == 0:               # если n кратно количеству колонок, начинаем новый ряд
        f = Frame()                 # создаём фрейм (контейнер для нового ряда)
        f.pack(expand=YES, fill=BOTH)   # размещаем фрейм в окне, создавая новый ряд

    btn = Button(f, font=('mono', 1))   # создаём маленькую кнопку
    btn.pack(expand=YES, fill=BOTH, side=LEFT)  # отрисовываем кнопку
    btn.config(command=lambda n=n: play(n))     # привязываем функцию к кнопке
    btn.n = n                       # сохраняем номер клетки в атрибуте кнопки
    btn.show = types.MethodType(show, btn) # добавляем метод show() к каждой кнопке
    btn.show()                      # запускаем бесконечное обновление цвета кнопки

mainloop()

Лист. 1

Рис. 1

Пояснения к программе:

  • Игровое поле playground хранится как одномерный список длиной cols*rows. Индекс клетки в списке playground вычисляется как row*cols + col.

  • Функция roomie использует вложенные циклы по смещениям строк и столбцов, но из-за того, что поле хранится линейно, смещение по строке — это ±1, а по столбцу — ±cols. Такой подход элегантно обрабатывает границы без отдельных проверок для краёв.

  • Функция life создаёт новое поколение на основе правил Конвея и перезаписывает поле playground. Затем, если игра активна, планирует свой следующий вызов через after(500, life). Это простейший способ организовать бесконечный цикл без блокировки интерфейса.

  • Кнопки выступают в роли клеток. Каждая кнопка хранит свой индекс в атрибуте n. Для обновления цвета используется динамически добавленный метод show(), который меняет фон кнопки в соответствии с playground[n] и снова планирует себя через 400 мс.

  • Недостаток производительности: Каждая из 144 кнопок запускает собственный цикл after, что создаёт огромное количество событий в очереди tkinter (~144 * 2.5 = 360 событий в секунду). Это может приводить к замедлению работы программы на слабых процессорах.

 ыпывпы

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 600                            # ширина и высота окна
SIZE = 50                                       # размер игрового поля
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'black'                      # цвет поля
LIFE_COLOR = 'green'                            # цвет жизни
GRID_COLOR = 'dimgray'                          # цвет сетки
playground = [False] * SIZE ** 2                # список состояний клеток:
                                                # False = мёртвая, True = живая
power = False                                   # флаг, запущена игра или режим редактирования


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def n_to_xy(n):
    """
    Конвертирует номер клетки в пару координат x, y 
    """
    x = n % SIZE * CELL_SIZE
    y = n // SIZE* CELL_SIZE
    return (x, y)


def draw_life():
    """
    Закрашивает клетки в цвет,
    соответствующий игровой ситуации
    """
    for n in range(SIZE ** 2):
        if playground[n]:
            screen.draw.filled_rect(Rect(*n_to_xy(n), CELL_SIZE, CELL_SIZE), LIFE_COLOR)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def play(n):
    """
    Редактор игрового поля
    """
    playground[n] = not playground[n]           # инвертируем состояние клетки


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_life()                                 # нарисовать живых
    draw_grid()                                 # нарисовать клетки
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    play(xy_to_n(*pos))                         # сделать ход


def roomie(n):
    """Подсчитывает количество живых соседей для клетки с индексом n."""
    s = 0                   # счётчик соседей
    for i in (-1, 0, 1):    # перебираем номера 8-ми кнопок, соседей кнопки n
        for j in (-SIZE, 0, SIZE):
            # исключаем из соседей клетку с номером n и соседей за пределами поля
            if i+j != 0 and n+i+j >= 0 and n+i+j < SIZE ** 2:
                s += playground[n+i+j]
    return s


def life():                                     # Conway Game Life
    """
    Выполняет один шаг игры «Жизнь» и планирует следующий шаг через 500 мс.
    Клетка становится живой, если у неё 3 соседа (рождение)
    или она уже жива и у неё есть 2 соседа (выживание)
    """
    global playground
    pg = [False] * (SIZE ** 2)                  # временное поле для нового поколения
    for i in range(SIZE ** 2):
        s = roomie(i)                           # количество живых соседей
        pg[i] = s == 3 or (playground[i] and s == 2)    # правила Конвея
    playground = pg


def start():
    ''' обработчик нажатия на клавишу пробел — запуск/останов игры '''
    global power
    power = not power                           # переключатель игра или режим редактирования
    if power:                                   # если игра запущена,
        clock.schedule_interval(life, 0.2)      # планируем вызов функции life()
    else:
        clock.unschedule(life)                  # отключаем вызов функции life()


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        start()


pgzrun.go()                                     # главный игровой цикл

Лист. 2.

Рис. 2.