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.