# Dzień 5 — własna gra od zera Projekt zaliczeniowy. Piszesz **własną** grę — nie przepisujesz tutoriala. Pomysł jest Twój; poniżej są tylko wymagania techniczne, które kod musi spełnić. ## Zakres Gra ma być mała i skończona. Lepiej prosta gra, która działa i ma ekran końca, niż ambitna, która się nie uruchamia. Przykłady o dobrym rozmiarze: - uciekaj przed ścigającymi Cię wrogami i zbieraj punkty, - prosty shooter (statek strzela, przeciwnicy lecą z góry), - wąż / snake z przeszkodami, - gra zręcznościowa: unikaj spadających obiektów, - mini-platformówka z jednym poziomem i bossem. ## Wymagania obowiązkowe ### 1. Minimum 3 własne klasy Nie licząc klas widoków. Np. `Player`, `Enemy`, `Bullet`, `PowerUp`. ### 2. Jedna hierarchia dziedziczenia: baza + co najmniej 2 klasy pochodne ``` Enemy <- klasa bazowa: wspólne dane i wspólne zachowanie / \ Chaser Shooter <- klasy pochodne: każda nadpisuje update() inaczej ``` Warunki: - klasa bazowa musi mieć **realną treść** — wspólny `__init__` i wspólną implementację `update()`, a nie samo `pass`, - każda klasa pochodna nadpisuje przynajmniej jedną metodę i woła w niej `super().(...)`, - wszystkie obiekty tej hierarchii trzymasz w **jednej** `SpriteList` i aktualizujesz jednym `lista.update(delta_time)` — bez `if`-a po typie. To jest sens polimorfizmu: dodanie trzeciego rodzaju wroga nie wymaga zmiany ani jednej linii w pętli gry. Pamiętaj o sygnaturze z arcade 3.x: ```python def update(self, delta_time=1 / 60, *args, **kwargs): super().update(delta_time, *args, **kwargs) ... ``` ### 3. Minimum jedno `property` z walidacją Np. `health` (0–100), `ammo` (nie mniej niż 0), `poziom_trudnosci` (1–5), `speed` (z górnym limitem). ```python @property def ammo(self): return self._ammo @ammo.setter def ammo(self, wartosc): if wartosc < 0: raise ValueError("Amunicja nie może być ujemna") self._ammo = min(wartosc, self.max_ammo) ``` Walidacja musi **coś robić** — przycinać zakres, rzucać wyjątek albo wywoływać efekt uboczny (np. `health` spadające do 0 przełącza grę na ekran końca). Samo `return self._x` bez settera nie liczy się jako walidacja. ### 4. Stany gry przez `arcade.View` Minimum trzy widoki, każdy jako osobna klasa: - `MenuView` — ekran startowy („naciśnij ENTER, żeby zacząć”), - `GameView` — właściwa rozgrywka, - `GameOverView` — wynik + możliwość powtórki. Przełączanie: `self.window.show_view(GameView())`. Nie rób stanów przez `if self.stan == "menu"` w jednym wielkim `on_draw()` — o to właśnie chodzi w tym wymaganiu. ### 5. Kod w wielu plikach w `src/` Struktura, do której dążymy: ``` dzien5_projekt/ ├── WYMAGANIA.md ├── main.py # tylko uruchomienie: tworzy okno i pokazuje MenuView └── src/ ├── __init__.py ├── settings.py # stałe: rozmiar okna, prędkości, kolory ├── entities.py # Player, Enemy + klasy pochodne, Bullet... └── views.py # MenuView, GameView, GameOverView ``` Import między plikami: ```python from src.settings import SZEROKOSC, WYSOKOSC from src.entities import Player, Chaser ``` Uruchamiasz z katalogu `dzien5_projekt/`: ```bash python main.py ``` Podział na pliki jest sensowny wtedy, gdy `entities.py` **nie musi** nic wiedzieć o `views.py`. Jeśli oba pliki importują się nawzajem — to znak, że podział jest zły; przenieś wspólne rzeczy do `settings.py`. ## Sposób pracy 1. Najpierw narysuj na kartce, jakie masz klasy i co która trzyma. 2. Zrób `settings.py` i `main.py`, które otwierają puste okno. **Commit.** 3. Dodaj `MenuView` i `GameView` z pustą planszą. **Commit.** 4. Dodaj gracza, ruch. **Commit.** 5. Dodaj hierarchię wrogów. **Commit.** 6. Dodaj `property`, kolizje, punkty, `GameOverView`. **Commit.** 7. Dopiero na końcu — dźwięki, grafika z , efekty. ## Checklista przed oddaniem - [ ] `python main.py` uruchamia się bez błędów (`PerformanceWarning: draw_text is an extremely slow function` to nie błąd — arcade sugeruje obiekty `arcade.Text` zamiast `arcade.draw_text`; przy naszej liczbie napisów można to zignorować) - [ ] 3+ własne klasy (bez widoków) - [ ] hierarchia: baza + 2 pochodne, każda z nadpisanym `update()` i `super()` - [ ] wrogowie w jednej `SpriteList`, aktualizowani bez `if`-a po typie - [ ] `property` z działającą walidacją (pokaż, że odrzuca złą wartość) - [ ] 3 widoki `arcade.View`, przełączanie działa w obie strony - [ ] kod w `src/`, `main.py` ma mniej niż 20 linii - [ ] historia gita: kilka commitów, każdy z działającym stanem gry - [ ] w kodzie nie ma zmiennych globalnych trzymających stan gry (stałe z `settings.py` są OK — one się nie zmieniają)