4.5. Życie Conwaya (str)
Gra w życie zrealizowana z użyciem biblioteki PyGame. Wersja strukturalna. Biblioteka PyGame ułatwia tworzenie aplikacji multimedialnych, w tym gier.

4.5.1. Zmienne i plansza gry
Tworzymy plik life.py
w terminalu lub w wybranym edytorze i zaczynamy od zdefiniowania zmiennych określających właściwości obiektów w naszej grze.
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4import pygame
5import sys
6import random
7from pygame.locals import * # udostępnienie nazw metod z locals
8
9# inicjacja modułu pygame
10pygame.init()
11
12# szerokość i wysokość okna gry
13OKNOGRY_SZER = 800
14OKNOGRY_WYS = 400
15
16# przygotowanie powierzchni do rysowania, czyli inicjacja okna gry
17OKNOGRY = pygame.display.set_mode((OKNOGRY_SZER, OKNOGRY_WYS), 0, 32)
18# tytuł okna gry
19pygame.display.set_caption('Gra o życie')
20
21# rozmiar komórki
22ROZ_KOM = 10
23# ilość komórek w poziomie i pionie
24KOM_POZIOM = int(OKNOGRY_SZER / ROZ_KOM)
25KOM_PION = int(OKNOGRY_WYS / ROZ_KOM)
26
27# wartości oznaczające komórki "martwe" i "żywe"
28KOM_MARTWA = 0
29KOM_ZYWA = 1
30
31# lista opisująca stan pola gry, 0 - komórki martwe, 1 - komórki żywe
32# na początku tworzymy listę zawierającą KOM_POZIOM zer
33POLE_GRY = [KOM_MARTWA] * KOM_POZIOM
34# rozszerzamy listę o listy zagnieżdżone, otrzymujemy więc listę dwuwymiarową
35for i in range(KOM_POZIOM):
36 POLE_GRY[i] = [KOM_MARTWA] * KOM_PION
W instrukcji pygame.display.set_mode()
inicjalizujemy okno gry o rozmiarach 800x400 pikseli i 32-bitowej głębi kolorów. Tworzymy w ten sposób powierzchnię główną do rysowania zapisaną w zmiennej OKNOGRY
. Ilość możliwych do narysowania komórek, reprezentowanych przez kwadraty o boku 10 pikseli, wyliczamy w zmiennych KOM_POZIOM
i KOM_PION
. Najważniejszą strukturą w naszej grze jest POLE_GRY
, dwuwymiarowa lista elementów reprezentujących „żywe” i „martwe” komórki, czyli populację. Tworzymy ją w dwóch krokach, na początku inicjujemy zerami jednowymiarową listę o rozmiarze odpowiadającym ilości komórek w poziomie (POLE_GRY = [KOM_MARTWA] * KOM_POZIOM
). Następnie do każdego elementu listy przypisujemy listę zawierającą tyle zer, ile jest komórek w pionie.
4.5.2. Populacja komórek
Kolejnym krokiem będzie zdefiniowanie funkcji przygotowującej i rysującej populację komórek.
39# przygotowanie następnej generacji komórek, czyli zaktualizowanego POLA_GRY
40def przygotuj_populacje(polegry):
41 # na początku tworzymy 2-wymiarową listę wypełnioną zerami
42 nast_gen = [KOM_MARTWA] * KOM_POZIOM
43 for i in range(KOM_POZIOM):
44 nast_gen[i] = [KOM_MARTWA] * KOM_PION
45
46 # iterujemy po wszystkich komórkach
47 for y in range(KOM_PION):
48 for x in range(KOM_POZIOM):
49
50 # zlicz populację (żywych komórek) wokół komórki
51 populacja = 0
52 # wiersz 1
53 try:
54 if polegry[x - 1][y - 1] == KOM_ZYWA:
55 populacja += 1
56 except IndexError:
57 pass
58 try:
59 if polegry[x][y - 1] == KOM_ZYWA:
60 populacja += 1
61 except IndexError:
62 pass
63 try:
64 if polegry[x + 1][y - 1] == KOM_ZYWA:
65 populacja += 1
66 except IndexError:
67 pass
68
69 # wiersz 2
70 try:
71 if polegry[x - 1][y] == KOM_ZYWA:
72 populacja += 1
73 except IndexError:
74 pass
75 try:
76 if polegry[x + 1][y] == KOM_ZYWA:
77 populacja += 1
78 except IndexError:
79 pass
80
81 # wiersz 3
82 try:
83 if polegry[x - 1][y + 1] == KOM_ZYWA:
84 populacja += 1
85 except IndexError:
86 pass
87 try:
88 if polegry[x][y + 1] == KOM_ZYWA:
89 populacja += 1
90 except IndexError:
91 pass
92 try:
93 if polegry[x + 1][y + 1] == KOM_ZYWA:
94 populacja += 1
95 except IndexError:
96 pass
97
98 # "niedoludnienie" lub przeludnienie = śmierć komórki
99 if polegry[x][y] == KOM_ZYWA and (populacja < 2 or populacja > 3):
100 nast_gen[x][y] = KOM_MARTWA
101 # życie trwa
102 elif polegry[x][y] == KOM_ZYWA \
103 and (populacja == 3 or populacja == 2):
104 nast_gen[x][y] = KOM_ZYWA
105 # nowe życie
106 elif polegry[x][y] == KOM_MARTWA and populacja == 3:
107 nast_gen[x][y] = KOM_ZYWA
108
109 # zwróć nowe polegry z następną generacją komórek
110 return nast_gen
111
112
113def rysuj_populacje():
114 """Rysowanie komórek (kwadratów) żywych"""
115 for y in range(KOM_PION):
116 for x in range(KOM_POZIOM):
117 if POLE_GRY[x][y] == KOM_ZYWA:
118 pygame.draw.rect(OKNOGRY, (255, 255, 255), Rect(
119 (x * ROZ_KOM, y * ROZ_KOM), (ROZ_KOM, ROZ_KOM)), 1)
Najważniejszym fragmentem kodu, implementującym logikę naszej gry, jest funkcja przygotuj_populacje(), która jako parametr przyjmuje omówioną wcześniej strukturę POLE_GRY
(pod nazwą polegry
). Funkcja sprawdza, jak rozwija się populacja komórek, według następujących zasad:
Jeżeli żywa komórka ma mniej niż 2 żywych sąsiadów, umiera z powodu samotności.
Jeżeli żywa komórka ma więcej niż 3 żywych sąsiadów, umiera z powodu przeludnienia.
Żywa komórka z 2 lub 3 sąsiadami żyje dalej.
Martwa komórka z 3 żywymi sąsiadami ożywa.
Funkcja iteruje po każdym elemencie POLA_GRY
i sprawdza stan sąsiadów każdej komórki, w wierszu 1 powyżej komórki, w wierszu 2 na tym samym poziomie i w wierszu 3 poniżej. Konstrukcja try...except
pozwala obsłużyć sytuacje wyjątkowe (błędy), a więc komórki skrajne, które nie mają sąsiadów u góry czy u dołu, z lewej bądź z prawej strony: w takim przypadku wywoływana jest instrukcja pass
, czyli nie rób nic :-). Końcowa złożona instrukcja warunkowa if
ożywia lub uśmierca sprawdzaną komórkę w zależności od stanu sąsiednich komórek (czyli zmiennej populacja
).
Zadaniem funkcji rysuj_populacje()
jest narysowanie kwadratów (obiekty Rect) o białych bokach w rozmiarze 10 pikseli dla pól (elementów), które w liście POLE_GRY
są żywe (mają wartość 1).
4.5.3. Główna pętla programu
Programy interaktywne, w tym gry, reagujące na działania użytkownika, takie jak ruchy czy kliknięcia myszą, działają w pętli, której zadaniem jest:
przechwycenie i obsługa działań użytkownika, czyli tzw. zdarzeń (ruchy, kliknięcia myszą, naciśnięcie klawiszy),
aktualizacja stanu gry (przesunięcia elementów, aktualizacja planszy),
aktualizacja wyświetlanego okna (narysowanie nowego stanu gry).
Dopisujemy więc do kodu główną pętlę wraz z obsługą zdarzeń:
122# zmienne sterujące wykorzystywane w pętli głównej
123zycie_trwa = False
124przycisk_wdol = False
125
126# pętla główna programu
127while True:
128 # obsługa zdarzeń generowanych przez gracza
129 for event in pygame.event.get():
130 # przechwyć zamknięcie okna
131 if event.type == QUIT:
132 pygame.quit()
133 sys.exit()
134
135 if event.type == KEYDOWN and event.key == K_RETURN:
136 zycie_trwa = True
137
138 if zycie_trwa is False:
139 if event.type == MOUSEBUTTONDOWN:
140 przycisk_wdol = True
141 przycisk_typ = event.button
142
143 if event.type == MOUSEBUTTONUP:
144 przycisk_wdol = False
145
146 if przycisk_wdol:
147 mouse_x, mouse_y = pygame.mouse.get_pos()
148 mouse_x = int(mouse_x / ROZ_KOM)
149 mouse_y = int(mouse_y / ROZ_KOM)
150 # lewy przycisk myszy ożywia
151 if przycisk_typ == 1:
152 POLE_GRY[mouse_x][mouse_y] = KOM_ZYWA
153 # prawy przycisk myszy uśmierca
154 if przycisk_typ == 3:
155 POLE_GRY[mouse_x][mouse_y] = KOM_MARTWA
156
157 if zycie_trwa is True:
158 POLE_GRY = przygotuj_populacje(POLE_GRY)
159
160 OKNOGRY.fill((0, 0, 0)) # ustaw kolor okna gry
161 rysuj_populacje()
162 pygame.display.update()
163 pygame.time.delay(100)
W obrębie głównej pętli programu pętla for
odczytuje kolejne zdarzenia zwracane przez
metodę pygame.event.get()
. Jak widać, w pierwszej kolejności obsługujemy wydarzenie typu
(właściwość .type
) QUIT, czyli zakończenie aplikacji.
Jednak na początku gry gracz klika lewym lub prawym klawiszem myszy i ożywia lub uśmierca
kliknięte komórki w obrębie okna gry. Dzieje się tak dopóty, dopóki zmienna zycie_trwa
ma wartość False
, a więc dopóki gracz nie naciśnie klawisza ENTER (if event.type == KEYDOWN and event.key == K_RETURN:
). Każde kliknięcie myszą zostaje przechwycone (if event.type == MOUSEBUTTONDOWN:
) i zapamiętane w zmiennej przycisk_wdol
. Jeżeli zmienna ta ma wartość True
, pobieramy współrzędne kursora myszy (mouse_x, mouse_y = pygame.mouse.get_pos()
) i obliczamy indeksy elementu listy POLE_GRY
odpowiadającego klikniętej komórce. Następnie sprawdzamy, który przycisk myszy został naciśnięty; informację tę zapisaliśmy wcześniej za pomocą funkcji event.button
w zmiennej przycisk_typ
, która przyjmuje wartość 1 (lewy) lub 3 (prawy przycisk myszy), w zależności od klikniętego przycisku ożywiamy lub uśmiercamy komórkę, zapisując odpowiedni stan w liście POLE_GRY
.
Naciśnięcie klawisza ENTER uruchamia symulację rozwoju populacji. Zmienna zycie_trwa
ustawiona zostaje na wartość True
, co przerywa obsługę kliknięć myszą, i wywoływana jest funkcja przygotuj_populacje()
, która przygotowuje kolejny stan populacji. Końcowe polecenia wypełniają okno gry kolorem (.fill()
), wywołują funkcję rysującą planszę (rysuj_populacje()
). Funkcja pygame.display.update()
, która musi być wykonywana na końcu rysowania, aktualizuje obraz gry na ekranie. Ostatnie polecenie pygame.time.delay(100)
dodaje 100-milisekundowe opóźnienie kolejnej aktualizacji stanu populacji. Dzięki temu możemy obserwować jej rozwój na planszy.
Grę możemy uruchomić poleceniem wpisanym w terminalu:
~$ python life_str.py
4.5.4. Zadania dodatkowe
Spróbuj inaczej zaimplementować funkcję
przygotuj_populacje
. Spróbuj zmodyfikować kod tak, aby plansza gry była biała, a komórki rysowane były jako kolorowe kwadraty o różniącym się od wypełnienia obramowaniu.
4.5.5. Materiały
Źródła:
Materiały Python 101
udostępniane przez
Centrum Edukacji Obywatelskiej na licencji
Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.
- Utworzony:
2025-04-12 o 10:21 w Sphinx 7.3.7
- Autorzy: