""" Dzień 2 — GŁÓWNE ZADANIE: własna hierarchia klas. Ten plik DZIAŁA od razu: jest gracz, 10 losowych monet, licznik punktów i wykrywanie kolizji. Uruchom go najpierw bez zmian: python dzien2/01_monety_start.py Sterowanie: strzałki. Wyjście: Esc. Restart: R. =========================================================================== ZADANIE =========================================================================== 1. Napisz klasę bazową `Enemy` dziedziczącą po `arcade.SpriteCircle`. Wróg ma: * konstruktor `__init__`, który przyjmuje promień, kolor i pozycję startową, woła `super().__init__(...)` i ustawia prędkość (`self.change_x`, `self.change_y`), * nadpisaną metodę `update(self, delta_time=1/60, *args, **kwargs)`, w której najpierw wołasz `super().update(delta_time)` (to przesuwa sprite'a zgodnie z prędkością), a potem sprawdzasz krawędzie okna i ODBIJASZ wroga: przy zderzeniu z lewą/prawą krawędzią zmieniasz znak `change_x`, przy górnej/dolnej — znak `change_y`. 2. Napisz DWIE klasy pochodne po `Enemy`, o RÓŻNYM zachowaniu. Każda musi nadpisywać `update()` i wołać w nim `super().update(delta_time)` (żeby odbijanie z klasy bazowej dalej działało), a potem dokładać coś swojego. Przykładowe pomysły — wybierz dwa albo wymyśl własne: * `Chaser` — powoli przyspiesza w stronę gracza (dostaje gracza w konstruktorze i zapamiętuje go w atrybucie), * `ZigZag` — co pewną liczbę klatek zmienia `change_y` na przeciwny, * `Grower` — pulsuje: rośnie i maleje (odczyt: `self.scale_x`, zapis: `self.scale = 1.2`; uwaga — `self.scale` przy odczycie zwraca PARĘ wartości (x, y), nie jedną liczbę), * `Sniper` — stoi w miejscu i co jakiś czas skacze w losowe miejsce. 3. Dodaj do `setup()` listę `self.enemies = arcade.SpriteList()`, wstaw do niej po kilka wrogów obu klas, rysuj ją w `on_draw()` i aktualizuj w `on_update()` przez `self.enemies.update(delta_time)`. 4. Obsłuż kolizję gracza z wrogiem (`arcade.check_for_collision_with_list`): odejmij punkt / zakończ grę / cofnij gracza na środek — Twój wybór. Napis "KONIEC GRY" i restart klawiszem R już są przygotowane (patrz atrybut `self.koniec_gry`). DLACZEGO TAK: `self.enemies.update()` woła `update()` na każdym wrogu, ale KAŻDY wróg wykona SWOJĄ wersję tej metody — mimo że lista jest jedna i nie ma w niej żadnego `if`. To jest POLIMORFIZM: ten sam kod, różne zachowania, bo obiekty są różnych klas. Bez OOP musiałbyś trzymać typ wroga w polu `rodzaj` i pisać `if rodzaj == "chaser": ... elif rodzaj == "zigzag": ...` w każdym miejscu, które dotyka wrogów. """ import random import arcade SZEROKOSC = 800 WYSOKOSC = 600 TYTUL = "Dzień 2 — monety i wrogowie" PROMIEN_GRACZA = 20 PREDKOSC_GRACZA = 5 PROMIEN_MONETY = 10 LICZBA_MONET = 10 # =========================================================================== # PODPOWIEDZIANY ZARYS KLASY Enemy — odkomentuj i uzupełnij. # # Zwróć uwagę na sygnaturę update() w arcade 3.x: MUSI przyjmować # delta_time oraz *args/**kwargs, bo SpriteList woła update(delta_time) # na każdym elemencie. Stare tutoriale (arcade 2.x) pokazują `def update(self)` # bez argumentów — u nas to wysypie program. # =========================================================================== # class Enemy(arcade.SpriteCircle): # """Klasa BAZOWA wroga: porusza się i odbija od krawędzi okna.""" # # def __init__(self, promien, kolor, center_x, center_y, change_x, change_y): # # super() woła konstruktor SpriteCircle — bez tego sprite nie ma # # ani tekstury, ani rozmiaru. # super().__init__(promien, kolor) # self.center_x = center_x # self.center_y = center_y # self.change_x = change_x # self.change_y = change_y # # def update(self, delta_time=1 / 60, *args, **kwargs): # # Najpierw zachowanie odziedziczone: przesunięcie o change_x/change_y. # super().update(delta_time, *args, **kwargs) # # # TODO: odbijanie od krawędzi. # # if self.left < 0 or self.right > SZEROKOSC: # # self.change_x = -self.change_x # # ... analogicznie dla bottom / top i change_y # # # class Chaser(Enemy): # """Klasa POCHODNA nr 1 — dopisuje własne zachowanie do bazowego.""" # # def __init__(self, center_x, center_y, player): # super().__init__(15, arcade.color.RED, center_x, center_y, 2, 2) # self.player = player # zapamiętujemy CUDZY obiekt — to kompozycja # # def update(self, delta_time=1 / 60, *args, **kwargs): # super().update(delta_time, *args, **kwargs) # ruch + odbijanie # # TODO: delikatnie skręcaj w stronę gracza, np. # # if self.player.center_x > self.center_x: # # self.change_x += 0.05 # # else: # # self.change_x -= 0.05 # # # class ZigZag(Enemy): # """Klasa POCHODNA nr 2 — inne zachowanie, ta sama klasa bazowa.""" # # def __init__(self, center_x, center_y): # super().__init__(12, arcade.color.PURPLE, center_x, center_y, 3, 0) # self.licznik = 0 # # def update(self, delta_time=1 / 60, *args, **kwargs): # super().update(delta_time, *args, **kwargs) # # TODO: co 30 klatek zmień kierunek w pionie # # self.licznik += 1 # # if self.licznik % 30 == 0: # # self.change_y = -self.change_y if self.change_y else 3 class MyGame(arcade.Window): """Okno gry.""" def __init__(self): super().__init__(SZEROKOSC, WYSOKOSC, TYTUL) self.background_color = arcade.color.DARK_BLUE self.player_list = arcade.SpriteList() self.coin_list = arcade.SpriteList() # TODO (punkt 3 zadania): self.enemies = arcade.SpriteList() self.player = None self.punkty = 0 self.koniec_gry = False self.setup() def setup(self): """Ustawia grę w stan początkowy. Wołane też przy restarcie (klawisz R).""" self.punkty = 0 self.koniec_gry = False # Gracz self.player_list = arcade.SpriteList() self.player = arcade.SpriteCircle(PROMIEN_GRACZA, arcade.color.AMAZON) self.player.center_x = SZEROKOSC / 2 self.player.center_y = WYSOKOSC / 2 self.player_list.append(self.player) # Monety w losowych miejscach self.coin_list = arcade.SpriteList() for _ in range(LICZBA_MONET): moneta = arcade.SpriteCircle(PROMIEN_MONETY, arcade.color.GOLD) moneta.center_x = random.randint(PROMIEN_MONETY, SZEROKOSC - PROMIEN_MONETY) moneta.center_y = random.randint(PROMIEN_MONETY, WYSOKOSC - PROMIEN_MONETY) self.coin_list.append(moneta) # TODO (punkt 3 zadania): utwórz self.enemies i dodaj do niej wrogów, # np. kilku Chaserów i kilka ZigZagów w losowych miejscach. # Chaser potrzebuje referencji do gracza: Chaser(x, y, self.player) def on_draw(self): self.clear() self.coin_list.draw() self.player_list.draw() # TODO (punkt 3 zadania): self.enemies.draw() arcade.draw_text( f"Punkty: {self.punkty}", x=10, y=WYSOKOSC - 30, color=arcade.color.WHITE, font_size=18, ) if self.koniec_gry: arcade.draw_text( "KONIEC GRY — naciśnij R", x=SZEROKOSC / 2, y=WYSOKOSC / 2, color=arcade.color.RED, font_size=32, anchor_x="center", ) def on_update(self, delta_time: float): if self.koniec_gry: return # arcade 3.x: lista sprite'ów odświeża się metodą update(delta_time). # SpriteList nie ma metody on_update — to częsty błąd ze starych kursów. self.player_list.update(delta_time) # Trzymamy gracza w granicach okna if self.player.left < 0: self.player.left = 0 if self.player.right > SZEROKOSC: self.player.right = SZEROKOSC if self.player.bottom < 0: self.player.bottom = 0 if self.player.top > WYSOKOSC: self.player.top = WYSOKOSC # Kolizje z monetami: funkcja zwraca LISTĘ trafionych sprite'ów. zebrane = arcade.check_for_collision_with_list(self.player, self.coin_list) for moneta in zebrane: # remove_from_sprite_lists() usuwa sprite'a ze WSZYSTKICH list, # do których należy — nie trzeba pamiętać, gdzie go włożyliśmy. moneta.remove_from_sprite_lists() self.punkty += 1 # TODO (punkty 3 i 4 zadania): # 1) self.enemies.update(delta_time) # — każdy wróg wykona SWOJĄ wersję update(), bo są różnych klas # 2) trafieni = arcade.check_for_collision_with_list(self.player, self.enemies) # if trafieni: # self.koniec_gry = True # albo: odejmij punkt, cofnij gracza... def on_key_press(self, symbol: int, modifiers: int): if symbol == arcade.key.LEFT: self.player.change_x = -PREDKOSC_GRACZA elif symbol == arcade.key.RIGHT: self.player.change_x = PREDKOSC_GRACZA elif symbol == arcade.key.UP: self.player.change_y = PREDKOSC_GRACZA elif symbol == arcade.key.DOWN: self.player.change_y = -PREDKOSC_GRACZA elif symbol == arcade.key.R: self.setup() elif symbol == arcade.key.ESCAPE: self.close() def on_key_release(self, symbol: int, modifiers: int): if symbol in (arcade.key.LEFT, arcade.key.RIGHT): self.player.change_x = 0 elif symbol in (arcade.key.UP, arcade.key.DOWN): self.player.change_y = 0 def main(): MyGame() arcade.run() if __name__ == "__main__": main()