4.3. Kółko i krzyżyk (str)
Klasyczna gra w kółko i krzyżyk zrealizowana przy pomocy PyGame.

4.3.1. Zmienne i plansza gry
Tworzymy plik tictactoe.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 python3
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# przygotowanie powierzchni do rysowania, czyli inicjacja okna gry
13OKNOGRY = pygame.display.set_mode((150, 150), 0, 32)
14# tytuł okna gry
15pygame.display.set_caption('Kółko i krzyżyk')
16
17# lista opisująca stan pola gry, 0 - pole puste, 1 - gracz, 2 - komputer
18POLE_GRY = [0, 0, 0,
19 0, 0, 0,
20 0, 0, 0]
21
22RUCH = 1 # do kogo należy ruch: 1 – gracz, 2 – komputer
23WYGRANY = 0 # wynik gry: 0 - nikt, 1 - gracz, 2 - komputer, 3 - remis
24WYGRANA = False
W instrukcji pygame.display.set_mode()
inicjalizujemy okno gry o rozmiarach 150x150 pikseli i 32 bitowej głębi kolorów. Tworzymy w ten sposób powierzchnię główną do rysowania zapisaną w zmiennej OKNOGRY
. POLE_GRY
to lista elementów reprezentujących pola planszy, które mogą być puste (wartość 0), zawierać kółka gracza (wartość 1) lub komputera (wartość 2). Pozostałe zmienne określają, do kogo należy następny ruch, kto wygrał i czy nastąpił koniec gry.
4.3.2. Rysuj planszę gry
Planszę można narysować na wiele sposobów, np. tak:
24WYGRANA = False
25
26# rysowanie planszy gry, czyli linii oddzielających pola
27
28
29def rysuj_plansze():
30 for i in range(0, 3): # x
31 for j in range(0, 3): # y
32 # argumenty: powierzchnia, kolor, x,y, w,h, grubość linii
33 pygame.draw.rect(OKNOGRY, (255, 255, 255),
34 Rect((j * 50, i * 50), (50, 50)), 1)
35
36# narysuj kółka
37
38
39def rysuj_pole_gry():
40 for i in range(0, 3):
41 for j in range(0, 3):
42 pole = i * 3 + j # zmienna pole przyjmuje wartości od 0-8
43 # x i y określają środki kolejnych pól,
44 # a więc wartości: 25,25, 25,75 25,125 75,25 itd.
45 x = j * 50 + 25
46 y = i * 50 + 25
47
48 if POLE_GRY[pole] == 1:
49 # rysuj kółko gracza
50 pygame.draw.circle(OKNOGRY, (0, 0, 255), (x, y), 10)
51 elif POLE_GRY[pole] == 2:
52 # rysuj kółko komputera
53 pygame.draw.circle(OKNOGRY, (255, 0, 0), (x, y), 10)
Pierwsza funkcja, rysuj_plansze()
, wykorzystując zagnieżdżone pętle, rysuje nam 9 kwadratów o białym obramowaniu i szerokości 50 pikseli (formalnie są to obiekty Rect zwracane przez metodę pygame.draw.rect()
). Zadaniem funkcji rysuj_pole_gry()
jest narysowanie w zależności od stanu planszy gry zapisanego w liście POLE_GRY
kółek o niebieskim (gracz) lub czerwonym (komputer) kolorze za pomocą metody pygame.draw.circle()
.
4.3.3. Sztuczna inteligencja
Decydującą rolę w grze odgrywa komputer, od którego inteligencji zależy, czy rozgrywka przyniesie jakąś satysfakcję. Dopisujemy więc funkcje obsługujące sztuczną inteligencję:
46 y = i * 50 + 25
47
48 if POLE_GRY[pole] == 1:
49 # rysuj kółko gracza
50 pygame.draw.circle(OKNOGRY, (0, 0, 255), (x, y), 10)
51 elif POLE_GRY[pole] == 2:
52 # rysuj kółko komputera
53 pygame.draw.circle(OKNOGRY, (255, 0, 0), (x, y), 10)
54
55# postaw kółko lub krzyżyk (w tej wersji też kółko, ale w innym kolorze :-))
56
57
58def postaw_znak(pole, RUCH):
59 if POLE_GRY[pole] == 0:
60 if RUCH == 1: # ruch gracza
61 POLE_GRY[pole] = 1
62 return 2
63 elif RUCH == 2: # ruch komputera
64 POLE_GRY[pole] = 2
65 return 1
66
67 return RUCH
68
69# funkcja pomocnicza sprawdzająca, czy komputer może wygrać, czy powinien
70# blokować gracza, czy może wygrał komputer lub gracz
71
72
73def sprawdz_pola(uklad, wygrany=None):
74 wartosc = None
75 # lista wielowymiarowa, której elementami są inne listy zagnieżdżone
76 POLA_INDEKSY = [ # trójki pól planszy do sprawdzania
77 [0, 1, 2], [3, 4, 5], [6, 7, 8], # indeksy pól w poziomie (wiersze)
78 [0, 3, 6], [1, 4, 7], [2, 5, 8], # indeksy pól w pionie (kolumny)
79 [0, 4, 8], [2, 4, 6] # indeksy pól na skos (przekątne)
80 ]
81
82 for lista in POLA_INDEKSY:
83 kol = [] # lista pomocnicza
84 for ind in lista:
85 kol.append(POLE_GRY[ind]) # zapisz wartość odczytaną z POLE_GRY
86 if (kol in uklad): # jeżeli znalazłeś układ wygrywający lub blokujący
87 # zwróć wygranego (1,2) lub indeks pola do zaznaczenia
88 wartosc = wygrany if wygrany else lista[kol.index(0)]
89
90 return wartosc
91
92# ruchy komputera
93
94
95def ai_ruch(RUCH):
96 pole = None # które pole powinien zaznaczyć komputer
97
98 # listy wielowymiarowe, których elementami są inne listy zagnieżdżone
99 uklady_wygrywam = [[2, 2, 0], [2, 0, 2], [0, 2, 2]]
100 uklady_blokuje = [[1, 1, 0], [1, 0, 1], [0, 1, 1]]
101
102 # sprawdź, czy komputer może wygrać
103 pole = sprawdz_pola(uklady_wygrywam)
104 if pole is not None:
105 return postaw_znak(pole, RUCH)
106
107 # jeżeli komputer nie może wygrać, blokuj gracza
108 pole = sprawdz_pola(uklady_blokuje)
109 if pole is not None:
110 return postaw_znak(pole, RUCH)
111
112 # jeżeli nie można wygrać i gracza nie trzeba blokować, wylosuj pole
113 while pole is None:
114 pos = random.randrange(0, 9) # wylosuj wartość od 0 do 8
115 if POLE_GRY[pos] == 0:
116 pole = pos
117
118 return postaw_znak(pole, RUCH)
Za sposób gry komputera odpowiada funkcja ai_ruch()
(ai – ang. artificial intelligence, sztuczna inteligencja). Na początku zawiera ona definicje dwóch list (uklady_wygrywam, uklady_blokuje
), zawierających układy wartości, dla których komputer wygrywa oraz które powinien zablokować, aby nie wygrał gracz. O tym, które pole należy zaznaczyć, decyduje funkcja sprawdz_pola()
przyjmująca jako argument najpierw układy wygrywające, później blokujące.
Podstawą działania funkcji sprawdz_pola()
jest lista POLA_INDEKSY
zawierająca jako elementy listy indeksów pól tworzących wiersze, kolumny i przekątne POLA_GRY
(czyli planszy). Pętla for lista in POLA_INDEKSY:
pobiera kolejne listy, tworzy w liście pomocniczej kol trójkę wartości odczytanych z POLA_GRY
i próbuje ją dopasować do przekazanego jako argument układu wygrywającego lub blokującego. Jeżeli znajdzie dopasowanie zwraca liczbę oznaczającą gracza lub komputer, o ile opcjonalny argument WYGRANY
ma wartość inną niż None
, w przeciwnym razie zwracany jest indeks POLA_GRY
, na którym komputer powinien postawić swój znak.
Jeżeli indeks zwrócony przez funkcję sprawdz_pola()
jest inny niż None
, przekazywany jest do funkcji postaw_znak()
, której zadaniem jest zapisanie w POLU_GRY
pod otrzymanym indeksem wartości symbolizującej znak komputera (czyli 2) oraz nadanie i zwrócenie zmiennej RUCH wskazującej na gracza (wartość 1).
O ile na planszy nie ma układu wygrywającego lub nie ma konieczności blokowania gracza, komputer w pętli losuje przypadkowe pole (random.randrange(0,9)
), dopóki nie znajdzie pustego, i przekazuje jego indeks do funkcji postaw_znak()
.
4.3.4. 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ń oraz dwie funkcje pomocnicze w niej wywoływane:
105 return postaw_znak(pole, RUCH)
106
107 # jeżeli komputer nie może wygrać, blokuj gracza
108 pole = sprawdz_pola(uklady_blokuje)
109 if pole is not None:
110 return postaw_znak(pole, RUCH)
111
112 # jeżeli nie można wygrać i gracza nie trzeba blokować, wylosuj pole
113 while pole is None:
114 pos = random.randrange(0, 9) # wylosuj wartość od 0 do 8
115 if POLE_GRY[pos] == 0:
116 pole = pos
117
118 return postaw_znak(pole, RUCH)
119
120# sprawdź, kto wygrał, a może jest remis?
121
122
123def kto_wygral():
124 # układy wygrywające dla gracza i komputera
125 uklad_gracz = [[1, 1, 1]]
126 uklad_komp = [[2, 2, 2]]
127
128 WYGRANY = sprawdz_pola(uklad_gracz, 1) # czy wygrał gracz?
129 if not WYGRANY: # jeżeli gracz nie wygrywa
130 WYGRANY = sprawdz_pola(uklad_komp, 2) # czy wygrał komputer?
131
132 # sprawdź remis
133 if 0 not in POLE_GRY and WYGRANY not in [1, 2]:
134 WYGRANY = 3
135
136 return WYGRANY
137
138# funkcja wyświetlająca komunikat końcowy
139# tworzy nowy obrazek z tekstem, pobiera jego prostokątny obszar
140# pozycjonuje go i rysuje w oknie gry
141
142
143def drukuj_wynik(WYGRANY):
144 fontObj = pygame.font.Font('freesansbold.ttf', 16)
145 if WYGRANY == 1:
146 tekst = u'Wygrał gracz!'
147 elif WYGRANY == 2:
148 tekst = u'Wygrał komputer!'
149 elif WYGRANY == 3:
150 tekst = 'Remis!'
151 tekst_obr = fontObj.render(tekst, True, (20, 255, 20))
152 tekst_prost = tekst_obr.get_rect()
153 tekst_prost.center = (75, 75)
154 OKNOGRY.blit(tekst_obr, tekst_prost)
155
156
157# pętla główna programu
158while True:
159 # obsługa zdarzeń generowanych przez gracza
160 for event in pygame.event.get():
161 # przechwyć zamknięcie okna
162 if event.type == QUIT:
163 pygame.quit()
164 sys.exit()
165
166 if WYGRANA is False:
167 if RUCH == 1:
168 if event.type == MOUSEBUTTONDOWN:
169 if event.button == 1: # jeżeli naciśnięto 1. przycisk
170 mouseX, mouseY = event.pos # rozpakowanie tupli
171 # wylicz indeks klikniętego pola
172 pole = (int(mouseY / 50) * 3) + int(mouseX / 50)
173 RUCH = postaw_znak(pole, RUCH)
174 elif RUCH == 2:
175 RUCH = ai_ruch(RUCH)
176
177 WYGRANY = kto_wygral()
178 if WYGRANY is not None:
179 WYGRANA = True
180
181 OKNOGRY.fill((0, 0, 0)) # definicja koloru powierzchni w RGB
182 rysuj_plansze()
183 rysuj_pole_gry()
184 if WYGRANA:
185 drukuj_wynik(WYGRANY)
186 pygame.display.update()
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. Później, o ile nikt nie wygrał (zmienna WYGRANA
ma wartość False
), a kolej na ruch gracza (zmienna RUCH
ma wartość 1), przechwytujemy wydarzenie MOUSEBUTTONDOWN
, tj. kliknięcie myszą. Sprawdzamy, czy naciśnięto pierwszy przycisk, pobieramy współrzędne kursora (.pos
) i wyliczamy indeks klikniętego pola. Na koniec wywołujemy omówioną wcześniej funkcję postaw_znak()
. Jeżeli kolej na komputer, uruchamiamy sztuczną inteligencję (ai_ruch()
).
Po wykonaniu ruchu przez komputer lub gracza trzeba sprawdzić, czy któryś z przeciwników nie wygrał. Korzystamy z funkcji kto_wygral()
, która definiuje dwa układy wygrywające (uklad_gracz
i uklad_komputer
) i za pomocą omówionej wcześniej funkcji sprawdz_pola()
sprawdza, czy można je odnaleźć w POLU_GRY
. Na końcu sprawdza możliwość remisu i zwraca wartość symbolizującą wygranego (1, 2, 3) lub None
, o ile możliwe są kolejne ruchy. Wartość ta wpływa w pętli głównej na zmienną WYGRANA
kontrolującą obsługę ruchów gracza i komputera.
Funkcja drukuj_wynik()
ma za zadanie przygotowanie końcowego napisu. W tym celu tworzy obiekt czcionki z podanego pliku (pygame.font.Font()
), następnie renderuje nowy obrazek z odpowiednim tekstem (.render()
), pobiera jego powierzchnię prostokątną (.get_rect()
), pozycjonują ją (.center()
) i rysują na głównej powierzchni gry (.blit()
).
Ostatnie linie kodu wypełniają okno gry kolorem (.fill()
), wywołują funkcję rysujące planszę (rysuj_plansze()
), stan gry (rysuj_pole_gry()
, czyli znaki gracza i komputera), a także ewentualny komunikat końcowy (drukuj_wynik()
). Funkcja pygame.display.update()
, która musi być wykonywana na końcu rysowania, aktualizuje obraz gry na ekranie.
Informacja
Plik wykorzystywany do wyświetlania tekstu (freesansbold.ttf
) musi znaleźć się w katalogu ze skryptem.
Grę możemy uruchomić poleceniem wpisanym w terminalu:
~$ python tictactoe.py
4.3.5. Zadania dodatkowe
Zmień grę tak, aby zaczynał ją komputer. Dodaj do gry możliwość rozgrywki wielokrotnej bez konieczności ponownego uruchamiania skryptu. Zmodyfikuj funkcję rysującą pole gry tak, aby komputer rysował krzyżyki, a nie kółka.
4.3.6. 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: