Last active
April 5, 2023 21:23
-
-
Save ap-Codkelden/100b659dbe41de7e60118988ac421110 to your computer and use it in GitHub Desktop.
A simple John Conway Life game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
# More about Conway's Game of Life you can read here: | |
# < https://conwaylife.com/wiki/Conway%27s_Game_of_Life> | |
import random | |
import time | |
import sys | |
import os | |
from typing import Final, List, Tuple | |
# Ширина, висота сітки | |
width: int = 70 | |
height: int = 35 | |
# Кольори у вигляді керуючих послідовностей ANSI | |
GREEN: Final = '\033[32m' | |
BLACK: Final = '\033[30m' | |
DARKGREY: Final = '\033[90m' | |
LIGHTGREY: Final = '\033[97m' | |
def make_grid(neighbors_data: List[List[Tuple[int]]]) -> List[List[int]]: | |
""" | |
Будує сітку, виходячи з даних про клітину та її сусідів, за правилами | |
Дж. Конвея | |
Аргументи: | |
neighbors_data: кортеж із даними про клітину та кількість її сусідів | |
Повертає: | |
Список списків з клітинами | |
""" | |
new_grid: List[List[int]] = [] | |
# Перебираємо всі рядки, по кожному рядку народжуємо нову клітину, або ж | |
# знищуємо стару. | |
# Клітина повертається функцією `raise_cell_or_die()` | |
for row in neighbors_data: | |
new_row = [raise_cell_or_die(*c) for c in row] | |
new_grid.append(new_row) | |
return new_grid | |
def raise_cell_or_die(cell_state: int, neighbors_count: int) -> int: | |
""" | |
Повертає стан клітини (0 або 1) у відповідності до даних про кількість її | |
сусідів за правилами Дж. Конвея | |
Аргументи: | |
cell_state: клітина (0 або 1) | |
neighbors_count: загальна кількість сусідів у сміжних клітинах з нею | |
Повертає: | |
клітину живу (1) або ні (0) | |
""" | |
if cell_state == 0: | |
return 1 if neighbors_count == 3 else 0 | |
if cell_state == 1: | |
if neighbors_count <= 1 or neighbors_count >= 4: | |
return 0 | |
return 1 | |
def display_grid(grid: List[List[int]], gen: int = None) -> None: | |
""" | |
Виводить на екран колонію клітин. | |
Аргументи: | |
grid: сітка з клітинами | |
gen: номер покоління. Необовʼязковий параметр. Якщо не вказаний, | |
виводиться 'Undefined' | |
""" | |
if gen is None: | |
gen = 'Undefined' | |
k = ["".join([GREEN+' @' if x else DARKGREY+' .' for x in row]) for row in grid] | |
at_grid = "\n".join(k) | |
print(BLACK + f"Generation: {gen}\n" + at_grid) | |
return | |
def get_neighbor_cell_positions(x: int, y: int) -> List[Tuple[int, int]]: | |
""" | |
Отримання координат навколишніх клітин у сітці для клітини з координатами | |
x та y. | |
Аргументи: | |
x: x-координата | |
y: y-координата | |
Повертає: | |
Список кортежів вигляду [(x1, y1), ... (xn, yn)] для координат кожної з | |
оточуючих клітин, які лежать в межах сітки. | |
""" | |
pos_box: List[Tuple[int, int]] = [] # порожній список для повернення | |
k: List[int] = [-1, 0, 1] # відносний зсув по координатах у всі боки | |
# Розрахунок зсуву для поточних координат | |
# Координати додаються у список для повернення | |
for i in k: | |
for j in k: | |
pos_x: int = x + i | |
pos_y: int = y + j | |
# за винятком координат самої клітини, координат, що містять | |
# X або Y, які менші за 0 або ж більші за розмір сітки (а значить | |
# лежать за межами сітки) | |
if (i == j == 0) or (-1 in (pos_x, pos_y)) or pos_y >= width \ | |
or pos_x >= height: | |
# наступний крок | |
continue | |
# додавання у список | |
pos_box.append((pos_x, pos_y),) | |
return pos_box | |
def calculate_neigbors(grid: List[List[int]]) -> List[List[Tuple[int]]]: | |
""" | |
Підрахунок кількості живих клітинок навколо кожної з клітин колонії. | |
Аргументи: | |
grid: сітка | |
Повертає: | |
Список списків по кількості рядків. Кожен список рядка містить | |
відповідну клітинам кількість кортежів. Кожен кортеж в свою чергу | |
містить саму клітину (0 чи 1), та кількість живих сусідів навколо неї. | |
""" | |
new_grid: List = [] # порожній список для списків рядків | |
for i in range(height): | |
new_row: List = [] # порожній рядок | |
for j in range(width): | |
# отримуємо координати x та y | |
neigbor_positions = get_neighbor_cell_positions(i, j) | |
try: | |
# для кожної пари координат отримуємо клітину з цієї пари | |
# координат, живу чи мертву (0 або 1) | |
# В результаті отримуємо список з нулів та одиниць, | |
# в подальшому сума цього списку дасть нам кількість | |
try: | |
cells: List[int] = [grid[x][y] for (x, y) in neigbor_positions] | |
except Exception as _: | |
raise | |
except IndexError: | |
# В разі помилки буде виведено повідомлення, і робота | |
# завершиться | |
print(f"Out of boundaries for cell at {i}, {j}!\n") | |
sys.exit() | |
# Додоаємо саму клітину та кількість живих сусідів у рядок, | |
new_row.append((grid[i][j], sum(cells))) | |
# а рядок -- у сітку | |
new_grid.append(new_row) | |
return new_grid | |
def clear() -> None: | |
""" | |
Процедура очистки терміналу шляхом надіслання відповідної команди | |
у термінал | |
""" | |
platform = sys.platform | |
if platform.startswith('darwin'): | |
os.system("clear") | |
elif platform.startswith('win'): | |
os.system("cls") | |
return | |
def resize_terminal() -> None: | |
""" | |
Змінює розмір терміналу відповідно до кожного типу операційної системи | |
""" | |
if sys.platform.startswith('darwin'): | |
command = "\x1b[8;{rows};{cols}t".format(rows=height + 4, cols=width + width) | |
sys.stdout.write(command) | |
elif sys.platform.startswith('win'): | |
command = "mode con: cols={0} lines={1}".format(width + width, height + 5) | |
os.system(command) | |
elif sys.platform.startswith("linux"): | |
command = '\x1b[8;{rows};{cols}t'.format(rows=height + 4, cols=width + width) | |
os.system(command) | |
else: | |
print("Your operating system is not supported.\n\r") | |
sys.exit() | |
return | |
def run(): | |
""" | |
Рушій | |
""" | |
c = 0 # лічильник поколінь | |
resize_terminal() | |
# початкова сітка з віпадковою генерацією живих клітин | |
grid = [[0 if random.randint(0, 1) == 0 else 1 for _ in range(width)] for _ in range(height)] | |
# показуємо початкову сітку | |
display_grid(grid) | |
try: | |
while True: | |
# очищаємо екран від сітки, показуємо сітку, потім | |
time.sleep(.85) # тримаємо сітку на екрані 0,85 с | |
# розраховуємо сусідів клітин | |
# і на основі цих даних створюємо нову сітку | |
neighbors = calculate_neigbors(grid) | |
grid = make_grid(neighbors) | |
# очищаємо екран і показуємо нову сітку | |
clear() | |
display_grid(grid, gen=c) | |
# збільшуємо лічильник поколінь | |
c += 1 | |
# Якщо натиснута комбінація Ctrl+C, очищаємо екран і пишемо інформацію про | |
# кількість поколінь | |
except KeyboardInterrupt: | |
clear() | |
print(BLACK + f"End of Life Game. {c} generations.") | |
if __name__ == "__main__": | |
run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment