Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
428 lines (274 sloc) 12.8 KB

Pong (obj)

Klasyczna gra w odbijanie piłeczki zrealizowana z użyciem biblioteki PyGame. Wersja obiektowa. Biblioteka PyGame ułatwia tworzenie aplikacji multimedialnych, w tym gier.

pong_0.png

Przygotowanie

Do rozpoczęcia pracy z przykładem pobieramy szczątkowy kod źródłowy:

~/python101$ git checkout -f pong/z1

Okienko gry

Na wstępie w pliku ~/python101/games/pong.py otrzymujemy kod który przygotuje okienko naszej gry:

Kod nr
.. literalinclude:: pong_z1.py
    :linenos:

W powyższym kodzie zdefiniowaliśmy klasę Board z dwiema metodami:

  1. konstruktorem __init__, oraz
  2. metodą draw posługującą się biblioteką PyGame do rysowania w oknie.

Na końcu utworzyliśmy instancję klasy Board i wywołaliśmy jej metodę draw na razie bez żadnych elementów wymagających narysowania.

Note

Każdy plik skryptu Python jest uruchamiany w momencie importu — plik/moduł główny jest importowany jako pierwszy.

Deklaracje klas są faktycznie instrukcjami sterującymi mówiącymi, by w aktualnym module utworzyć typy zawierające wskazane definicje.

Możemy mieszać deklaracje klas ze zwykłymi instrukcjami sterującymi takimi jak print, czy przypisaniem wartości zmiennej board = Board(800, 400) i następnie wywołaniem metody na obiekcie board.draw().

Nasz program możemy uruchomić komendą:

~/python101$ python games/pong.py

Mrugnęło? Program się wykonał i zakończył działanie :). Żeby zobaczyć efekt na dłużej, możemy na końcu chwilkę uśpić nasz program:

Kod nr

Jednak zamiast tego, dla lepszej kontroli powinniśmy zadeklarować klasę kontrolera gry, usuńmy kod od linii 37 do końca i dodajmy klasę kontrolera:

Kod nr
.. literalinclude:: pong_z2.py
    :linenos:
    :lineno-start: 38
    :lines: 38-

Note

Prócz dodania kontrolera zmieniliśmy także sposób, w jaki gra jest uruchamiana — nie mylić z uruchomieniem programu.

Na końcu dodaliśmy instrukcję warunkową if __name__ == "__main__":, w niej sprawdzamy, czy nasz moduł jest modułem głównym programu, jeśli nim jest, gra zostanie uruchomiona.

Dzięki temu, jeśli nasz moduł zostałby zaimportowany gdzieś indziej instrukcją import pong, deklaracje klas wykonałyby się, ale sama gra nie zostanie uruchomiona.

Gotowy kod możemy pobrać komendą:

~/python101$ git checkout -f pong/z2

Piłeczka

Czas dodać piłkę do gry. Piłeczką będzie kolorowe kółko, które z każdym przejściem naszej pętli przesuniemy o kilka punktów w osi X i Y, zgodnie wektorem prędkości.

Wcześniej jednak zdefiniujemy wspólną klasę bazową dla obiektów, które będziemy rysować w oknie naszej gry:

Kod nr
.. literalinclude:: pong_z3.py
    :linenos:
    :lineno-start: 71
    :lines: 75-88

Następnie dodajmy klasę samej piłeczki dziedzicząc z Drawable:

Kod nr
.. literalinclude:: pong_z3.py
    :linenos:
    :lineno-start: 87
    :lines: 91-127

W przykładzie powyżej wykonaliśmy :term:`dziedziczenie` oraz :term:`przesłanianie` konstruktora, ponieważ rozszerzamy Drawable i chcemy zachować efekt działania konstruktora na początku konstruktora Ball wywołujemy konstruktor klasy bazowej:

super(Ball, self).__init__(width, height, x, y, color)

Teraz musimy naszą piłeczkę zintegrować z resztą gry:

Kod nr
.. literalinclude:: pong_z3.py
    :linenos:
    :lines: 38-61
    :emphasize-lines: 12, 20-23
    :lineno-start: 38

Note

Metoda Board.draw oczekuje wielu opcjonalnych argumentów, choć na razie przekazujemy tylko jeden. By zwiększyć czytelność potencjalnie dużej listy argumentów — kto wie co jeszcze dodamy :) — podajemy każdy argument w osobnej linii zakończonej przecinkiem ,.

Python nie traktuje takich osieroconych przecinków jako błąd, jest to ukłon w stronę programistów, którzy często zmieniają kod, kopiują i wklejają kawałki.

Dzięki temu możemy wstawiać nowe i zmieniać kolejność bez zwracania uwagi, czy na końcu jest przecinek, czy go brakuje, czy go należy usunąć. Zgodnie z konwencją powinien być tam zawsze.

Gotowy kod możemy pobrać komendą:

~/python101$ git checkout -f pong/z3

Odbijanie piłeczki

Uruchommy naszą "grę" ;)

~/python101$ python games/pong.py
pong_3.png

Efekt nie jest powalający, ale mamy już jakiś ruch na planszy. Szkoda, że piłka spada z planszy. Może mogła by się odbijać od krawędzi okienka? Możemy wykorzystać wcześniej przygotowane metody do zmiany kierunku wektora prędkości, musimy tylko wykryć moment w którym piłeczka będzie dotykać krawędzi.

W tym celu piłeczka musi być świadoma istnienia planszy i pozycji krawędzi, dlatego zmodyfikujemy metodę Ball.move tak by przyjmowała board jako argument i na jego podstawie sprawdzimy, czy piłeczka powinna się odbijać:

Kod nr

Jeszcze zmodyfikujmy wywołanie metody move w naszej pętli głównej:

Kod nr

Warning

Powyższe przykłady mają o jedno wcięcie za mało. Poprawnie wcięte przykłady straciłyby kolorowanie w tej formie materiałów. Ze względu na czytelność kodu zdecydowaliśmy się na taki drobny błąd. Kod po ewentualnym wklejeniu należy poprawić dodając jedno wcięcie (4 spacje).

Sprawdzamy, czy piłka się odbija, uruchamiamy nasz program:

~/python101$ python games/pong.py

Gotowy kod możemy pobrać komendą:

~/python101$ git checkout -f pong/z4

Odbijamy piłeczkę rakietką

Dodajmy "rakietkę", przy pomocy której będziemy mogli odbijać piłeczkę. Będzie to zwykły prostokąt, który będziemy przesuwać za pomocą myszki.

Kod nr
.. literalinclude:: pong_z5.py
    :linenos:
    :lines: 147-164
    :lineno-start: 136

Note

W tym przykładzie zastosowaliśmy operator warunkowy, który ogranicza prędkość poruszania się rakietki:

delta = self.max_speed if delta > 0 else -self.max_speed

Zmienna delta otrzyma wartość max_speed ze znakiem + lub - w zależności od znaku jaki ma aktualnie.

Następnie "pokażemy" rakietkę piłeczce, tak by mogła się od niej odbijać. Wiemy że rakietek będzie więcej, dlatego od razu tak zmodyfikujemy metodę Ball.move, by przyjmowała kolekcję rakietek:

Kod nr

Tak jak w przypadku dodawania piłeczki, rakietkę też trzeba dodać do "gry", dodatkowo musimy ją pokazać piłeczce:

Kod nr
.. literalinclude:: pong_z5.py
    :linenos:
    :lines: 38-79
    :emphasize-lines: 13, 21, 24, 39-42
    :lineno-start: 38

Gotowy kod możemy pobrać komendą:

~/python101$ git checkout -f pong/z5

Note

W tym miejscu można się pobawić naszą grą. Zmodyfikuj ją według uznania i podziel się rezultatem z innymi. Jeśli kod przestanie działać, można szybko cofnąć zmiany poniższą komendą.

~/python101$ git reset --hard

Gramy przeciwko komputerowi

Dodajemy przeciwnika, nasz przeciwnik będzie mistrzem, będzie dokładnie śledził piłeczkę i zawsze starał się utrzymać rakietkę gotową do odbicia piłeczki.

Kod nr
.. literalinclude:: pong_z6.py
    :linenos:
    :lines: 170-181
    :lineno-start: 167

Tak jak w przypadku piłeczki i rakietki dodajemy nasze Ai do gry, a wraz nią drugą rakietkę. Rakietki ustawiamy na przeciwległych brzegach planszy.

Trzeba pamiętać, by pokazać drugą rakietkę piłeczce, tak by mogła się od niej odbijać.

Kod nr
.. literalinclude:: pong_z6.py
    :linenos:
    :lines: 38-67
    :emphasize-lines: 13-15, 23, 27, 29
    :lineno-start: 38

Pokazujemy punkty

Dodajmy klasę sędziego, który patrząc na poszczególne elementy gry będzie decydował, czy graczom należą się punkty i będzie ustawiał piłkę w początkowym położeniu.

Kod nr
.. literalinclude:: pong_z7.py
    :linenos:
    :lines: 184-231
    :lineno-start: 184

Jak zwykle dodajemy instancję nowej klasy do gry:

Kod nr
.. literalinclude:: pong_z7.py
    :linenos:
    :lines: 38-70
    :emphasize-lines: 16, 29
    :lineno-start: 38

Zadania dodatkowe

  1. Piłeczka "odbija się" po zewnętrznej prawej i dolnej krawędzi. Można to poprawić.
  2. Metoda Ball.move otrzymuje w argumentach planszę i rakietki. Te elementy można piłeczce przekazać tylko raz w konstruktorze.
  3. Komputer nie odbija piłeczkę rogiem rakietki.
  4. Rakietka gracza rusza się tylko, gdy gracz rusza myszką, ruch w stronę myszki powinien być kontynuowany także, gdy myszka jest bezczynna.
  5. Gdy piłeczka odbija się od boków rakietki, powinna odbijać się w osi X.
  6. Gra dwuosobowa z użyciem komunikacji po sieci.