🔩 Nowy Rekrut
0 / 19 misji

ASTROPYTHON

Zbuduj Grę z Asteroidami

Kurs Pygame dla Pasjonatów Gier

ROK 2189
Galaktyka została zaatakowana przez sztuczną inteligencję VOID. Większość floty została zniszczona.
Ostatnią nadzieją jest statek SPACE SURVIVAL. Ale statek jest uszkodzony: silniki nie działają, radar jest offline, system bojowy został zniszczony, komputer pokładowy ledwo żyje.

Tylko jedna osoba może uratować statek.
ASTRO. Czyli TY.
Technologie
Python 3 Pygame Bez OOP Język polski
Czego się nauczysz
zmienne i typy pętle for i while funkcje i return listy i filtrowanie if / elif / else losowość animacja kolizje timery debugowanie
0 Etap 0 — Wstęp i przygotowanie środowiska
E00

Zanim zaczniemy — środowisko i struktura kursu

Przed pierwszą linią kodu — przygotuj swoje środowisko tak, jak robi to prawdziwy developer.
Dla kogo jest ten kurs?
Ten kurs zakłada, że znasz już podstawy Pythona — piszesz proste programy, rozumiesz zmienne, pętle, funkcje i listy.

Jeśli dopiero zaczynasz przygodę z Pythonem — zacznij tutaj:

🐍 Kurs Python Podstawy dla Pasjonatów Gier →

Jeśli Python to nie Twoja nowość — jesteś we właściwym miejscu. Zaczynamy budować grę!
Struktura kursu — jak tu będziemy pracować
Każdy temat podzielony jest na dwie części:

T — Teoria Python — nowe pojęcie Pythona + ćwiczenia w pliku .py
G — Gra — bezpośredni kod który buduje grę Asteroidy

Przez cały kurs budujesz jedną grę — dodając mechanikę po mechanice. Na końcu masz działającą, samodzielną aplikację którą możesz uruchomić na swoim komputerze i pokazać znajomym.

Pracujemy tak, jak pracują prawdziwi developerzy: edytor kodu na własnym komputerze, pliki .py na dysku, uruchamianie z terminala. Żadnych platform w przeglądarce.
Instalacja środowiska
Żadnych przeglądarek. Kod piszemy i uruchamiamy lokalnie — tak jak prawdziwi developerzy.

Będziesz potrzebował trzech rzeczy:
  • Edytora kodu (IDE) — proponujemy Visual Studio Code
  • Pythona 3.12 — wersja wymagana przez bibliotekę Pygame
  • Biblioteki Pygame — silnik do tworzenia gier 2D

Wybierz swój system operacyjny klikając w zakładki poniżej i zobacz jak zainstalować.
🪟 Windows
🍎 macOS
🐧 Linux
1
Zainstaluj Visual Studio Code
⬇️ Pobierz VS Code
2
Zainstaluj Python 3.12
⬇️ Pobierz Python 3.12
Podczas instalacji zaznacz "Add python.exe to PATH" na pierwszym ekranie — bez tego Python nie będzie działał!
3
Zainstaluj Pygame — otwórz cmd lub PowerShell i wpisz:
pip install pygame
4
Sprawdź instalację:
python --version pip show pygame
1
Zainstaluj Visual Studio Code
⬇️ Pobierz VS Code Wypakuj .zip i przeciągnij do Applications.
Dodaj do PATH: Cmd+Shift+PShell Command: Install 'code' in PATH
2
Zainstaluj Python 3.12
⬇️ Pobierz Python 3.12 (macOS)
3
Zainstaluj Pygame w Terminalu:
pip3 install pygame
Na macOS używaj python3 i pip3.
Instalacja wymaga uprawnień sudo — Twoje konto musi być w grupie sudoers (na większości desktopowych dystrybucji jest to domyślne).
Zalecamy instalację przez terminal. Uruchom terminal (szukaj „Terminal" w aplikacjach), a następnie kopiuj komendy poniżej i wklejaj je w terminalu klikając prawym przyciskiem myszy → Wklej.
1
Zainstaluj Visual Studio Code
Wklej w terminalu:
sudo apt update && sudo apt install code
Jeśli nie zadziała — pobierz plik .deb ze strony:
⬇️ Pobierz VS Code (.deb)
2
Zainstaluj Python 3.12 (Ubuntu/Debian):
sudo add-apt-repository ppa:deadsnakes/ppa sudo apt update sudo apt install python3.12 python3.12-pip
3
Zainstaluj Pygame — poprosi o hasło:
sudo apt install python3-pygame
Na Linuxie używamy python3 zamiast python.
VS Code po polsku — zmiana języka interfejsu
VS Code uruchamia się domyślnie po angielsku. Aby zmienić na polski:
1
Otwórz VS Code
2
Naciśnij Ctrl+Shift+P
3
Wpisz i wybierz:
Configure Display Language
4
Jeśli języka polskiego nie ma na liście — wybierz "Install Additional Languages"
5
Wyszukaj: Polish Language Pack for Visual Studio Code → kliknij Install
6
VS Code uruchomi się ponownie automatycznie
7
Znowu Ctrl+Shift+PConfigure Display Language
8
Wybierz Polski — interfejs przełączy się na język polski
Test — sprawdź czy Pygame działa
Stwórz nowy plik test.py w swoim folderze, wklej poniższy kod i kliknij ▶ Run.
import pygame

pygame.init()
ekran = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Test Devostro — Pygame dziala!")

ekran.fill((0, 0, 30))
font = pygame.font.SysFont("Arial", 36)
napis = font.render("Pygame dziala! Mozemy zaczac.", True, (0, 200, 255))
ekran.blit(napis, (150, 270))
pygame.display.flip()

pygame.time.wait(3000)
pygame.quit()
print("Test OK! Srodowisko gotowe.")
Granatowe okno 800×600 z napisem — po 3 sekundach zamknie się samo. W terminalu: Test OK!
Struktura folderów — porządek jak u developera
Zanim napiszesz pierwszą linię gry — zaplanuj gdzie będą Twoje pliki.
dev/ └── asteroidy/ ├── test.py ├── gra_01.py ├── gra_02.py └── ...
Jak to zrobić w VS Code:
  1. Plik → Otwórz Folder → wybierz miejsce na dysku
  2. Prawy klik w panelu plików po lewej → Nowy Folder → wpisz dev
  3. W folderze dev → Nowy Folder → asteroidy
  4. W asteroidy → Nowy Plik → test.py
Zasady nazewnictwa — ważne!
✅ małe litery: asteroidy, gra_01.py
✅ podkreślnik lub myślnik zamiast spacji: moja_gra, moja-gra
❌ spacje: moja gra — powodują błędy w terminalu
❌ polskie znaki: ćwiczenia — mogą powodować problemy
❌ wielkie litery na początku: Asteroidy — niekonwencjonalne
Podstawy VS Code — jak będziemy pracować
📂 Otwieranie projektu
Plik → Otwórz Folder → wybierz folder asteroidy
📄 Nowy plik
Prawy klik w panelu → Nowy Plik → nazwij gra_01.py
▶️ Uruchamianie
Kliknij ▶ w prawym górnym rogu lub terminal: python gra_01.py
💻 Terminal
Terminal → Nowy Terminal — tu widzisz output i błędy gry
🔍 Błędy
Czerwone podkreślenia w kodzie + panel PROBLEMY na dole
⌨️ Skróty
Ctrl+S zapisz   Ctrl+Z cofnij
Środowisko gotowe! Możesz zacząć budować grę Asteroidy.
1 Etap 1 — Fundamenty Pygame

🎯 Cel ETAPU 1 — co tu zbudujesz?

  • Tworzyć funkcje — z argumentami i bez, zwracające wartości
  • Pracować z krotkami (np. kolory RGB, pozycje na ekranie)
  • Używać pętli while i for
  • Rozumieć typy danych: int, float, bool, str
  • Debugować — czytać błędy i je naprawiać
🚀 W ETAPIE 2 użyjemy tego wszystkiego żeby uruchomić pierwsze okno gry Pygame!
T01

Python: Podstawy funkcji — def, argumenty, return

Misja: opanuj definicję funkcji, przekazywanie argumentów i zwracanie wartości. To fundament każdego programu w Pygame!
🔵 MODUŁ PYTHON — Fundament Pygame
Dlaczego zaczynamy od funkcji?
W Pygame absolutnie wszystko jest funkcją:
pygame.init() — funkcja bez argumentów
ekran.fill((0,0,30)) — funkcja z argumentem
pygame.display.flip() — funkcja bez argumentów
pygame.draw.rect(ekran, kolor, (x,y,w,h)) — funkcja z wieloma argumentami

Jeśli rozumiesz funkcje w Pythonie, rozumiesz 80% Pygame. Dlatego zaczynamy od fundamentów.
⌨️ Krok 0 — Przygotuj środowisko (2 minuty)
Zanim napiszesz pierwszy kod — upewnij się że folder i plik są gotowe:
1Otwórz VS Code → Plik → Otwórz Folder → przejdź do folderu dev/asteroidy/lekcja1 (który stworzyłeś w Etapie 0). Jeśli nie masz — stwórz go teraz.
2Utwórz pierwszy plik: prawy klik w panelu plików → Nowy Plik → wpisz cwiczenie_2_1.py → Enter
3Wpisz kod (nie kopiuj — wpisz ręcznie!) i zapisz: Ctrl+S
4Otwórz terminal: VS Code → Terminal → Nowy Terminal (lub kliknij przycisk ▶ w prawym górnym rogu)
5Uruchom plik wpisując w terminalu:
python cwiczenie_2_1.py
Jak wygląda poprawne uruchomienie (przykład dla 2.1):
> python cwiczenie_2_1.py
Cześć, jestem funkcją bez argumentów!
Dla każdej sekcji utwórz osobny plik: cwiczenie_2_1.py, cwiczenie_2_2.py...
Wpisuj kod ręcznie — nie kopiuj. Potem zmień coś i sprawdź co się stanie.
Ćwiczenie i rozumienie to klucz — czytanie kodu nie wystarczy!
🔑 Zanim zaczniesz — jak działa funkcja?
Wyobraź sobie przepis kulinarny. Napisanie przepisu nie gotuje potrawy — to tylko instrukcja na kartce. Dopiero gdy zaczniesz gotować według przepisu, coś się dzieje w kuchni. Funkcja w Pythonie działa dokładnie tak samo.
Praca z funkcją składa się zawsze z dwóch osobnych kroków:

KROK 1 — Definicja (def)
Piszesz słowo def, dajesz nazwę i dwukropek. Python zapamiętuje kod wewnątrz — ale jeszcze nic nie wykonuje. To tylko zapisanie przepisu.

KROK 2 — Wywołanie (nazwa())
Piszesz nazwę funkcji z nawiasami. Dopiero teraz Python wykonuje zapamiętany kod. Możesz to zrobić dowolną liczbę razy.

# KROK 1 — definicja: Python ZAPAMIĘTUJE, ale NIE wykonuje
def przywitaj():
    print("To się nie wykona od razu!")

# Tu jeszcze nic nie ma w terminalu.

# KROK 2 — wywołanie: dopiero teraz Python to WYKONUJE
przywitaj()   # ← nawiasy () są obowiązkowe!
przywitaj()   # ← można wywołać wielokrotnie
przywitaj()
> python test.py
To się nie wykona od razu!
To się nie wykona od razu!
To się nie wykona od razu!
Częsty błąd: przywitaj bez nawiasów — Python nie wykona funkcji, tylko pokaże że ona istnieje. Zawsze przywitaj() z nawiasami, żeby ją wywołać.
2.1 Funkcja bez argumentów — pierwszy przykład
def przywitaj():
    print("Cześć, jestem funkcją bez argumentów!")

# Definicja powyżej nic nie wypisała.
# Dopiero wywołanie uruchamia kod:
przywitaj()
> python cwiczenie_2_1.py
Cześć, jestem funkcją bez argumentów!
Linia def przywitaj(): — Python zapamiętuje funkcję.
Linia przywitaj() — Python ją uruchamia.
Gdybyś napisał tylko def przywitaj(): bez wywołania — terminal byłby pusty!
Zadanie: Dopisz funkcję pozegnaj(), która wypisze pożegnanie. Pamiętaj o wywołaniu: pozegnaj().
✔️ Po tej sekcji powinieneś umieć:
  • Zdefiniować funkcję bez argumentów (def nazwa():)
  • Wywołać ją po nazwie z nawiasami: nazwa()
  • Rozumieć że def = zapamiętaj, wywołanie = wykonaj
2.2 Funkcja z jednym argumentem
def powiedz(imie):
    print("Hej", imie)

powiedz("Ala")
powiedz("Bartek")
> python cwiczenie_2_2.py
Hej Ala
Hej Bartek
imie to parametr — dziura w przepisie do uzupełnienia przy wywołaniu.
powiedz("Ala") — wywołujesz z argumentem "Ala". Python podstawia go w miejsce imie.
Dzięki temu ta sama funkcja może obsłużyć dowolne imię.
Zadanie: Napisz funkcję powiedz_wiek(wiek), która wypisze "Mam X lat". Wywołaj ją z kilkoma różnymi wartościami.
✔️ Po tej sekcji powinieneś umieć:
  • Przekazać wartość do funkcji przez parametr
  • Używać parametru wewnątrz funkcji
  • Wywołać funkcję z różnymi argumentami
2.3 Funkcja z wieloma argumentami
def przedstaw(imie, wiek):
    print("Nazywam się", imie, "i mam", wiek, "lat.")

przedstaw("Bartek", 15)
przedstaw("Anna", 22)
> python cwiczenie_2_3.py
Nazywam się Bartek i mam 15 lat.
Nazywam się Anna i mam 22 lat.
Zadanie: Napisz funkcję pozycja(x, y) wypisującą pozycję gracza.
✔️ Po tej sekcji powinieneś umieć:
  • Stworzyć funkcję z wieloma parametrami
  • Przekazać kilka argumentów w jednym wywołaniu
2.4 Funkcja zwracająca wartość (return)
def dodaj(a, b):
    return a + b

wynik = dodaj(3, 5)
print("Wynik:", wynik)
> python cwiczenie_2_4.py
Wynik: 8
Zadanie: Napisz funkcję pole_kwadratu(bok) zwracającą pole.
✔️ Po tej sekcji powinieneś umieć:
  • Użyć słowa return żeby zwrócić wartość
  • Zapisać wynik funkcji do zmiennej
  • Rozróżnić print() od return
2.5 Funkcja zwracająca tekst
def powiedz_hello(imie):
    return "Cześć " + imie + "!"

# return daje wartość z powrotem — żeby ją ZOBACZYĆ, musisz użyć print()!
wynik = powiedz_hello("Bartek")
print(wynik)

# albo krócej — w jednej linii:
print(powiedz_hello("Ala"))
⚠️ return ≠ print!
return oddaje wartość temu kto wywołał funkcję — ale jej nie wypisuje.
Żeby zobaczyć wynik w terminalu, musisz go przekazać do print().

Wyobraź sobie: return to kelner który przynosi danie na stół. print() to Ty, który je zjada (widzi). Bez Ciebie danie leży niewidoczne.
> python cwiczenie_2_5.py
Cześć Bartek!
Cześć Ala!
Zadanie: Napisz funkcję opis_statku(x, y) zwracającą tekst "Statek na pozycji X, Y". Wypisz wynik przez print(opis_statku(100, 200)).
✔️ Po tej sekcji powinieneś umieć:
  • Zwracać string z funkcji przez return
  • Wypisać zwróconą wartość przez print(funkcja(...))
  • Rozumieć że return i print() to różne operacje
🎯 Dobra robota! Opanowałeś funkcje — fundament każdego programu. Teraz poznasz krotki, pętle i debugowanie, po czym uruchomisz pierwszą grę!
T01b

Python: Krotki, pętla while i debugowanie

Misja: poznaj krotki (pary wartości), pętlę while i jak czytać błędy — bez tego nie uruchomisz pierwszego okna gry!
Wyobraź sobie etykietę na pudełku z grą: „Pozycja: (120, 350)". Nie możesz zmienić etykiety — jest naklejona na stałe. Krotka w Pythonie działa tak samo: kilka wartości razem, które razem tworzą jedną informację i nie zmieniają się osobno.
Teoria — Krotka (tuple)
Krotka to kilka wartości zapisanych razem w nawiasach, oddzielonych przecinkami. Używamy jej gdy kilka liczb „należy razem":

pozycja = (100, 200)    # x=100, y=200
wymiary = (800, 600)    # szerokość, wysokość okna
punkt   = (0, 0)        # lewy górny róg ekranu

Kolor RGB to krotka trzech liczb — każda od 0 do 255:
(R, G, B) — ile czerwonego, zielonego, niebieskiego

(0, 0, 30)       # ciemny niebieski — tło kosmosu
(255, 0, 0)      # czerwony
(0, 200, 255)    # jasny błękit — nasz statek
(160, 160, 160)  # szary — asteroida
(255, 255, 255)  # biały
(0, 0, 0)        # czarny
2.6 Funkcja zwracająca krotkę
def kolor_niebieski():
    return (0, 0, 255)

# Pamiętaj: żeby zobaczyć wynik — użyj print()!
kolor = kolor_niebieski()
print("Kolor:", kolor)
> python cwiczenie_2_6.py
Kolor: (0, 0, 255)
True i False to wartości logiczne. Można je wypisać przez print(), zapisać do zmiennej, albo użyć bezpośrednio w if — bez porównywania!
Zadanie: Napisz funkcję czy_wiekszy(a, b) zwracającą True jeśli a > b. Wypisz wynik dla kilku par liczb.
2.8 Funkcja zwracająca liczbę całkowitą (int)
def oblicz_punkty(trafienia, mnozmik):
    return trafienia * mnozmik

wynik = oblicz_punkty(5, 10)
print("Punkty:", wynik)
> python cwiczenie_2_8.py
Punkty: 50
Zadanie: Napisz funkcję premia_za_fale(numer_fali), która zwraca numer_fali * 50. Wypisz premię dla fali 1, 2 i 3.
2.9 Pętla while True — fundament gier
licznik = 0

while True:
    print("Klatka:", licznik)
    licznik += 1

    if licznik > 5:
        break
> python cwiczenie_2_9.py
Klatka: 0
Klatka: 1
Klatka: 2
Klatka: 3
Klatka: 4
Klatka: 5
Zadanie: Zmień warunek kończący pętlę — np. licznik > 10. Sprawdź ile razy pętla się wykona.
2.10 Zmienne — stan gry
szerokosc = 800
wysokosc = 600
kolor = (0, 0, 30)
print("Rozmiar okna:", szerokosc, "x", wysokosc)
Zadanie: Dodaj zmienną tytul = "Moja gra" i wypisz ją.
🐛 Jak czytać błędy i debugować
Błędy będą się zdarzać — to normalne. Ważne jest żeby je czytać, nie ignorować.

Komunikat błędu ma zawsze ten sam schemat:

File "cwiczenie.py", line 5, in 
    print(wynik)
NameError: name 'wynik' is not defined

Czytaj od dołu do góry:
  1. Typ błędu (NameError) — co poszło nie tak
  2. Opis (name 'wynik' is not defined) — szczegóły
  3. Numer linii (line 5) — gdzie szukać w kodzie

Trzy najczęstsze błędy:

# NameError — literówka lub zapomniana definicja
print(wynik)         # błąd: 'wynik' nie istnieje
wynik = 42
print(wynik)         # OK — najpierw definiujemy

# TypeError — mieszanie typów
punkty = 10
napis = "Zdobyłeś " + punkty   # błąd: int + str
napis = "Zdobyłeś " + str(punkty)   # OK

# IndexError — za duży indeks
lista = [1, 2, 3]
print(lista[5])      # błąd: lista ma tylko 3 elementy (0,1,2)
print(lista[2])      # OK — ostatni element

print() jako debugger — wypisz wartość zmiennej żeby sprawdzić co się dzieje:

statek_x = 350
print("DEBUG statek_x:", statek_x)   # sprawdzamy wartość

for i in range(5):
    print("DEBUG i =", i)            # śledzimy pętlę

Gdy znajdziesz błąd — usuń linie print("DEBUG...").
2.11 cwiczenie_debug_1.py — znajdź i napraw 3 błędy
# Ten kod ma 3 błędy — znajdź je i napraw!

def podwoj(liczba)
    return liczba * 2

wynik = podwoj(5)
print("Podwojona wartość:", Wynik)

imie = "Bartek"
powitanie = "Cześć " + imie + "! Masz " + wiek + " lat."
print(powitanie)
Wskazówka: uruchom kod, przeczytaj błąd, znajdź linię, popraw, uruchom ponownie. Jeden błąd na raz.
Zadanie: po naprawieniu — dodaj print("DEBUG wynik:", wynik) przed ostatnią linią. Usuń gdy wszystko działa.
✔️ Po sekcjach 2.6–2.11 powinieneś umieć:
  • Zwracać krotki, liczby logiczne i całkowite z funkcji
  • Korzystać z pętli while True i zmiennej pętli
  • Definiować zmienne różnych typów
  • Czytać komunikat błędu i znaleźć jego przyczynę
🚀 Masz już fundament! W kolejnej lekcji użyjemy tych wszystkich funkcji żeby uruchomić pierwsze okno gry Pygame. Twoja pierwsza gra jest w ETAPIE 2!
G01

Gra: Pierwsze Okno Pygame

Centrum dowodzenia jest offline! Cel: otworzyć okno Pygame 800×600, ustawić ciemne tło kosmosu i uruchomić pętlę 60 FPS — Twój pierwszy program który działa jak gra, nie jak terminal.
🟢 Przejście do Pygame — „to wszystko już znacie"
Pygame to po prostu Python + funkcje + pętla + zmienne. Nic nowego — tylko używamy gotowych funkcji do grafiki. I teraz przechodzimy do lekcji finałowej.
🚀 PYGAME — Pierwsze Okno Gry
Cel lekcji
Co: Otworzyć okno gry 800×600 z ciemnym tłem, tytułem i płynną pętlą 60 FPS.
Jak: pygame.init() uruchamia silnik, set_mode() tworzy okno, pętla while True obsługuje zdarzenia i odświeża ekran przez flip().
Dlaczego: Bez pętli okno zamknęłoby się natychmiast. Bez obsługi zdarzeń system uznałby program za zawieszony.
📂 Krok 0 — Utwórz plik i przejrzyj cały program
Otwórz edytor i stwórz nowy plik gra_01.py w folderze kursu.
Najpierw — oceń cały program. Poniżej jest kompletny kod gra_01.py z komentarzami przy każdej linii.
Nie musisz teraz rozumieć każdego szczegółu — przez kilka kolejnych lekcji wyjaśnimy każdy element dokładnie. Ważne żeby wpisać kod, uruchomić i zobaczyć działające okno!
import pygame              # wczytujemy bibliotekę do gier

pygame.init()              # uruchamiamy wszystkie moduły Pygame (grafika, dźwięk, itp.)

SZEROKOSC = 800            # szerokość okna w pikselach (duże litery = stała)
WYSOKOSC  = 600            # wysokość okna w pikselach

# set_mode tworzy okno gry — argument to krotka (szerokosc, wysokosc)
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))

pygame.display.set_caption("Asteroidy — Lekcja 1!")  # tytuł na pasku okna

zegar = pygame.time.Clock()  # zegar do kontroli FPS (klatek na sekundę)

while True:                          # główna pętla gry — działa bez końca
    for zdarzenie in pygame.event.get():    # sprawdź czy coś się stało (klik, klawisz...)
        if zdarzenie.type == pygame.QUIT:   # jeśli kliknięto X w rogu okna...
            pygame.quit()                   # ...zamknij Pygame
            exit()                          # ...i zakończ program

    ekran.fill((0, 0, 30))       # pomaluj tło na ciemny niebieski (R=0, G=0, B=30)
    pygame.display.flip()        # wyświetl gotową klatkę na ekranie
    zegar.tick(60)               # czekaj — nie przekraczaj 60 klatek/sekundę
Wpisz ten kod do gra_01.py i uruchom: python gra_01.py
Powinieneś zobaczyć ciemne okno 800×600 z tytułem. Zamknij je klikając X.
📊 Jak działa pętla gry Pygame?
Teraz kiedy widziałeś cały kod — spójrz na schemat który pokazuje przepływ programu. Każdy krok 3.1–3.10 poniżej odpowiada jednemu elementowi na tym schemacie.
Schemat blokowy — program Pygame od startu do zamknięcia
graph TD A([START]) --> B["import pygame"] B --> C["pygame.init()"] C --> D["ekran = set_mode(800, 600)"] D --> E["set_caption(tytul)"] E --> F["zegar = Clock()"] F --> G(["Pętla gry — while True"]) G --> H["event.get() — czytaj zdarzenia"] H --> I{"QUIT ?"} I -->|"TAK"| J["pygame.quit() + exit()"] I -->|"NIE"| K["ekran.fill(kolor tla)"] K --> L["rysuj obiekty"] L --> M["display.flip() — gotowa klatka\nna ekran!"] M --> N["zegar.tick(60) — poczekaj\nmax 60 FPS"] N --> G
Kroki 3.1–3.5 (poza pętlą) wykonują się raz przy starcie.
Kroki 3.6–3.10 (wewnątrz pętli) wykonują się 60 razy na sekundę — to serce gry.

💡 Kluczowa para: fill() i flip()
fill() — malujesz nową klatkę w pamięci komputera (niewidoczna).
flip() — zamieniasz pamięć na widoczny ekran. Dopiero teraz gracz coś widzi.
Dzieje się to 60 razy na sekundę — jak film: 60 obrazków/s tworzy złudzenie ruchu.
Zacznijmy zatem tworzyć tę główną pętlę!
Wpisuj po kolei instrukcje poniżej — każdy krok to jeden element schematu który widziałeś powyżej.
3.1 Import i start silnika
import pygame

pygame.init()
import pygame — mówisz Pythonowi: „będziemy używać biblioteki do gier".
pygame.init() — funkcja bez argumentów, uruchamia moduły Pygame (grafikę, dźwięk itd.).
To jest dokładnie taka sama funkcja jak przywitaj() — tylko robi dużo rzeczy „pod maską".
3.2 Stałe — rozmiar okna
SZEROKOSC = 800
WYSOKOSC = 600
Duże litery → stałe (umowa programistów). To tylko liczby — jak w ćwiczeniu z szerokosc, wysokosc.
3.3 Tworzenie okna gry
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
Funkcja z argumentem — krotką (szerokość, wysokość). Zwraca obiekt „ekran" — powierzchnię, na której rysujemy. To jest jak kartka, na której będziemy malować kosmos, statek, asteroidy.
3.4 Tytuł okna
pygame.display.set_caption("Asteroidy — Lekcja 1: Pierwsze Okno!")
Funkcja z argumentem tekstowym. Ustawia napis na pasku okna.
3.5 Zegar — kontrola FPS
zegar = pygame.time.Clock()
Tworzymy obiekt zegara. Będziemy go używać żeby gra nie działała „za szybko".
3.6 Główna pętla gry
while True:
To ta sama pętla, którą robiliście wcześniej — tylko teraz to pętla gry. W każdej „klatce" robimy: obsługę zdarzeń, rysowanie, odświeżenie ekranu, ograniczenie FPS.
3.7 Obsługa zdarzeń
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()
pygame.event.get() — funkcja zwracająca listę zdarzeń (np. kliknięcie X).
Jeśli typ zdarzenia to pygame.QUIT → zamykamy grę.

Pętlę for będziemy dokładnie omawiać i ćwiczyć w następnej lekcji — na razie wpisz ten blok tak jak jest.
3.8 Wypełnianie ekranu kolorem
    ekran.fill((0, 0, 30))
fill(...) — funkcja z argumentem: krotką (R, G, B). Każda wartość od 0 do 255.
(0, 0, 30) — prawie czarny, ciemny niebieski (kosmos).
(255, 0, 0) — czerwony.   (255, 255, 255) — biały.   (0, 0, 0) — czarny.

Dlaczego fill() na początku każdej klatki?
Bez fill() obiekty z poprzedniej klatki zostają na ekranie — statek zostawia ślad jak pędzel. Fill() "ściera" poprzednią klatkę i zaczyna czystą kartką.
3.9 Wyświetlenie klatki na ekranie
    pygame.display.flip()
Jak działa flip() — podwójne buforowanie:

Pygame utrzymuje dwie "kartki":
 • Bufor tylny (niewidoczny) — tu rysujesz: fill(), draw.rect(), itp.
 • Ekran (widoczny) — to co widzi gracz.

flip() zamienia je miejscami — gotowa klatka pojawia się na ekranie, a stary ekran staje się nowym buforem do rysowania.

Dlaczego nie rysujemy bezpośrednio na ekranie?
Bo gracz zobaczyłby niedokończony obraz (migotanie). Z flip() zawsze widzi gotową, kompletną klatkę.

Analogia: artysta maluje obraz za kurtyną, a flip() to podniesienie kurtyny przed publicznością.
3.10 Ograniczenie FPS
    zegar.tick(60)
FPS = Frames Per Second = klatki na sekundę.
Film kinowy = 24 FPS. Dobra gra = 60 FPS. Im więcej, tym płynniej.

zegar.tick(60) mówi: „odczekaj tyle czasu, żeby pętla nie wykonała się więcej niż 60 razy na sekundę".

Dlaczego to ważne?
Bez tego na mocnym komputerze pętla działałaby 500 lub 1000 razy na sekundę — statek poruszałby się z różną prędkością na różnych komputerach, a CPU pracowałby na 100%. tick(60) sprawia że gra działa tak samo wszędzie.
📚 Dokumentacja Pygame — gdzie szukać?
Wszystkie funkcje Pygame które widzisz w kursie mają oficjalną dokumentację na stronie:

🔗 pygame.org/docs

Jak szukać? Na przykład żeby znaleźć opis pygame.display.flip():
 • Otwórz docs → znajdź sekcję pygame.display → kliknij flip
 • Albo użyj Ctrl+F na stronie i wyszukaj "flip"

Na początku nie musisz tam zaglądać — kurs tłumaczy wszystko czego potrzebujesz. Ale jeśli jesteś ciekaw jak działa jakaś funkcja dokładnie — docs to pierwsze miejsce do sprawdzenia.

Eksperymenty — zmień i sprawdź!
  1. Zmień ekran.fill((0, 0, 30)) na (255, 0, 0) — czerwone tło. Potem (0, 255, 0) — zielone. Jak zmieszać kolory?
  2. Usuń linię pygame.display.flip() i uruchom — co się dzieje? Przywróć ją.
  3. Napisz ekran.fill() dwa razy z różnymi kolorami — który z nich "wygrywa"?
Zadania dodatkowe
  • Stwórz okno 400×400 z różowym tłem
  • Znajdź wartości RGB dla koloru czarnego i białego i ustaw je jako tło
  • Zmień FPS na 10 i opisz co się dzieje
  • Zmień tytuł okna tak, aby zawierał Twoje imię i numer lekcji
✔️ Sprawdź działającą grę:
  • Okno gry otwiera się
  • Tło jest ciemne (kosmos)
  • Tytuł widoczny na pasku
  • Kliknięcie X zamyka program
T02

Python: Pętla for i range()

Misja: naucz się pętli for i range() — zaraz użyjemy jej żeby poruszać wieloma obiektami na ekranie jednocześnie!
🔵 MODUŁ PYTHON — Pętla for
Teoria — pętla for i range()
Pętla for przechodzi przez zakres wartości jeden po jednym:

for i in range(5):
    print("Krok:", i)
# Wypisze: 0, 1, 2, 3, 4

range() może przyjmować 1, 2 lub 3 argumenty:

range(5)            # 0, 1, 2, 3, 4
range(2, 6)         # 2, 3, 4, 5
range(0, 800, 100)  # 0, 100, 200, 300, 400, 500, 600, 700  ← pozycje X na ekranie!
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_for_1.py i cwiczenie_for_2.py — wpisuj kod ręcznie i uruchamiaj:  python cwiczenie_for_1.py
2.1 cwiczenie_for_1.py — podstawowy range
for i in range(5):
    print("Krok:", i)
Zadanie: Zmień na range(10). Potem wypisz każdą wartość × 2: print("Krok:", i * 2).
✔️ Po tej sekcji powinieneś umieć:
  • Używać range(n) i wypisywać każdą wartość
  • Odczytać ile razy pętla się wykona
  • Modyfikować parametry range()
2.2 cwiczenie_for_2.py — range z krokiem
for x in range(0, 800, 100):
    print("Pozycja X:", x)
> python cwiczenie_for_2.py
Pozycja X: 0
Pozycja X: 100
Pozycja X: 200
Pozycja X: 300
Pozycja X: 400
Pozycja X: 500
Pozycja X: 600
Pozycja X: 700
Zadanie: Zmień krok na 50. Potem napisz to samo dla osi Y: range(0, 600, 100).
✔️ Po tej sekcji powinieneś umieć:
  • Używać range(a, b, krok) do generowania pozycji na ekranie
  • Rozumieć parametr kroku i jego wpływ na wynik
  • Stosować pętlę do wyliczania współrzędnych
🚀 Pętla for to klucz do gier — w następnej lekcji użyjemy jej żeby narysować statek i pracować z pozycjami na ekranie!
G02

Gra: Pierwszy Statek

Radar wykrył pole asteroid! Zmontuj statek kosmiczny i wyświetl go na ekranie — czas nauczyć się rysować!
💡 Pamiętasz z L01 ten kod? Upewnij się że go masz — będzie Ci potrzebny za chwilę.

Teraz już wiesz jak działa for — znajdź go w kodzie poniżej i sprawdź gdzie w Lekcji 2 będziemy coś doklejać:
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600

ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 1: Pierwsze Okno!")

zegar = pygame.time.Clock()

# <-- TUTAJ DODAMY ZMIENNE STATKU (Lekcja 2, krok 4.4)

while True:
    for zdarzenie in pygame.event.get():   # <-- ta petla for dziala jak range()!
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    ekran.fill((0, 0, 30))

    # <-- TUTAJ NARYSUJEMY STATEK (Lekcja 2, krok 4.5)

    pygame.display.flip()
    zegar.tick(60)
🟢 Przejście do Pygame — rysujemy pierwszy obiekt
Skoro wiesz już co to krotka (L01) i jak działa for — czas narysować statek.
🚀 PYGAME — Pierwszy Statek
📂 Skopiuj gra_01.py jako gra_02.py
Weź swój plik gra_01.py i zapisz kopię jako gra_02.py.
Zmień tytuł okna na "Asteroidy — Lekcja 2: Pierwszy Statek!"
Będziesz dodawać nowe linie w odpowiednich miejscach — zgodnie z krokami 4.4 i 4.5.
Cel lekcji
Co: Narysować niebieski prostokąt (statek gracza) na dole ekranu.
Jak: Cztery zmienne pozycji i rozmiaru (statek_x, statek_y, statek_szerokosc, statek_wysokosc) przekazujemy do pygame.draw.rect().
Dlaczego: Zmienne zamiast liczb dosłownych — gdy chcemy przesunąć statek, zmieniamy jedną zmienną, a nie szukamy liczb w całym kodzie.
4.1 Układ współrzędnych
Ekran gry ma 800 × 600 pikseli. Punkt (0, 0) to lewy górny róg. X rośnie w prawo, Y rośnie w dół:
(0,0) ──────────────────────► X rośnie (0 → 799)
  │
  │
  ▼
Y rośnie (0 → 599)
Przykłady punktów:
(0, 0) — lewy górny róg
(800, 0) — prawy górny róg
(0, 600) — lewy dolny róg
(350, 500) — prawie na dole, lekko z lewej → tam stanie nasz statek
4.2 Czym jest rect (prostokąt)
W Pygame prostokąt opisujemy 4 liczbami jako krotkę — pamiętasz krotki z L01?

(x, y, szerokość, wysokość)

Przykład dla naszego statku:
(350, 500, 40, 50) → x=350, y=500, szerokość=40, wysokość=50
4.3 pygame.draw.rect — rysowanie prostokąta
pygame.draw.rect(
    ekran,                                                     # gdzie rysujemy
    (0, 200, 255),                                             # kolor RGB (niebieski)
    (statek_x, statek_y, statek_szerokosc, statek_wysokosc)    # rect: x,y,w,h
)
1. powierzchnia — zawsze ekran (nasza „kartka do rysowania")
2. kolor — krotka RGB — pamiętasz z L01? (0, 200, 255) = jasny niebieski
3. rect — krotka (x, y, szerokość, wysokość) opisująca prostokąt
4.4 Zmienne statku — dodaj nad pętlą
W pliku gra_02.py znajdź linię zegar = pygame.time.Clock() i wklej zaraz pod nią:
# --- ZMIENNE STATKU ---
statek_x = 350          # pozycja pozioma (od lewej)
statek_y = 500          # pozycja pionowa (od góry)
statek_szerokosc = 40   # szerokość statku w pikselach
statek_wysokosc = 50    # wysokość statku w pikselach
4.5 Rysowanie statku — dodaj w pętli
W pętli while True, zaraz po ekran.fill((0, 0, 30)), wklej:
    # --- RYSOWANIE STATKU ---
    pygame.draw.rect(
        ekran,
        (0, 200, 255),
        (statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    )
Uruchom: python gra_02.py — niebieski prostokąt na dole ekranu. To Twój pierwszy obiekt w grze!
🗂️ Pełny kod gra_02.py — kliknij aby porównać ze swoim plikiem
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 2: Pierwszy Statek!")
zegar = pygame.time.Clock()

# --- ZMIENNE STATKU ---
statek_x = 350
statek_y = 500
statek_szerokosc = 40
statek_wysokosc = 50

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    ekran.fill((0, 0, 30))

    # --- RYSOWANIE STATKU ---
    pygame.draw.rect(
        ekran,
        (0, 200, 255),
        (statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    )

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień kolor statku na (255, 255, 0) — żółty. Potem (255, 0, 255) — fioletowy.
  2. Ustaw statek_x = -20 — czy statek może wyjść poza ekran?
  3. Usuń ekran.fill((0, 0, 30)) — co się dzieje z kolejnymi klatkami?
Zadania dodatkowe
  • Ustaw statek dokładnie na środku ekranu — użyj SZEROKOSC // 2 - statek_szerokosc // 2
  • Narysuj dwa skrzydła: dwa wąskie prostokąty po bokach statku
  • Stwórz „flagę" z trzech prostokątów jeden pod drugim w różnych kolorach
  • Nowe kształty: narysuj koło obok statku: pygame.draw.circle(ekran, (255, 255, 0), (400, 300), 40)
  • Narysuj elipsę jako „silnik" pod statkiem: pygame.draw.ellipse(ekran, (0, 100, 255), (statek_x + 10, statek_y + 45, 20, 10))
  • Narysuj linię przekątną ekranu: pygame.draw.line(ekran, (255, 0, 0), (0, 600), (800, 0), 3)
✔️ Sprawdź działającą grę:
  • Statek pojawia się na ekranie
  • Strzałki poruszają go w 4 kierunkach
  • Statek nie wychodzi poza krawędzie ekranu
T03

Python: // i str.format()

Misja: naucz się dzielenia całkowitego i formatowania tekstu — zaraz użyjemy tego do wyśrodkowania statku i wyświetlenia danych na ekranie!
🔵 MODUŁ PYTHON — Operator // i formatowanie tekstu
Teoria — operatory arytmetyczne, szczególnie //
Znasz już +, -, *. Oto reszta:

print(800 / 2)    # 400.0  — wynik z ułamkiem (float)
print(800 // 2)   # 400    — dzielenie całkowite (int), bez ułamka
print(7 // 2)     # 3      — zaokrągla w dół
print(7 % 2)      # 1      — reszta z dzielenia
print(2 ** 8)     # 256    — potęgowanie

Dlaczego // jest ważne? Pozycja piksela musi być liczbą całkowitą — ułamków pikseli nie ma. Dlatego do centrowania używamy //, nie /.
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_op_1.py, cwiczenie_op_2.py, cwiczenie_format_1.py, cwiczenie_format_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_op_1.py — porównanie operatorów
print("Dzielenie:", 800 / 2)
print("Dzielenie calkowite:", 800 // 2)
print("Reszta:", 7 % 2)
print("Potega:", 2 ** 8)
> python cwiczenie_op_1.py
Dzielenie: 400.0
Dzielenie calkowite: 400
Reszta: 1
Potega: 256
Zadanie: oblicz środek ekranu 800×600 używając //. Ile to pikseli dla X? Ile dla Y?
✔️ Po tej sekcji powinieneś umieć:
  • Rozróżnić / (wynik float) od // (wynik int)
  • Używać operatora modulo % do reszty z dzielenia
  • Używać ** do potęgowania
2.2 cwiczenie_op_2.py — wzór na środek ekranu
szerokosc = 800
statek_szer = 40

srodek_x = szerokosc // 2 - statek_szer // 2
print("Statek startuje na X:", srodek_x)
> python cwiczenie_op_2.py
Statek startuje na X: 380
Zadanie: zmień szerokosc = 600 — jaki jest nowy środek? Czy statek nadal jest wycentrowany?
✔️ Po tej sekcji powinieneś umieć:
  • Obliczać środek ekranu wzorem szerokosc // 2 - szer // 2
  • Stosować operator // do pozycjonowania obiektów
  • Rozumieć dlaczego odejmujemy połowę szerokości statku
Teoria — str.format()
.format() wstawia wartości zmiennych w miejsce {} — po kolei:

imie = "Bartek"
poziom = 3
print("Gracz: {} | Poziom: {}".format(imie, poziom))
# Wypisze: Gracz: Bartek | Poziom: 3

Ile {} — tyle argumentów w .format().
2.3 cwiczenie_format_1.py — podstawowe formatowanie
imie = "Bartek"
poziom = 3
print("Gracz: {} | Poziom: {}".format(imie, poziom))
> python cwiczenie_format_1.py
Gracz: Bartek | Poziom: 3
Zadanie: dodaj zmienną punkty = 150 i dopisz ją do wypisywanego tekstu: "Gracz: {} | Poziom: {} | Punkty: {}".
✔️ Po tej sekcji powinieneś umieć:
  • Używać {}.format() do wstawiania wartości w tekst
  • Wstawiać wiele wartości naraz w jeden string
  • Budować czytelne komunikaty z danych
2.4 cwiczenie_format_2.py — HUD gry
x = 380
y = 520
zycia = 3
print("Pozycja: X={} Y={}".format(x, y))
print("Zycia: {}".format(zycia))
> python cwiczenie_format_2.py
Pozycja: X=380 Y=520
Zycia: 3
Zadanie: dodaj zmienną fala = 1 i wyświetl ją w osobnej linii. Potem zmień wartości x, y i sprawdź wynik.
✔️ Po tej sekcji powinieneś umieć:
  • Budować tekst HUD z danych gry
  • Wyświetlać wiele linii z format()
  • Rozumieć jak dane gracza trafiają na ekran
Teoria — input() i konwersja typów
input() pyta użytkownika o dane — ale zawsze zwraca tekst (string), nawet gdy wpiszesz liczbę:

wiek = input("Ile masz lat? ")
print(type(wiek))   #  — to tekst, nie liczba!

# Aby użyć jako liczby — konwertuj:
wiek_int = int(wiek)
print("Za rok będziesz mieć:", wiek_int + 1)

Trzy konwersje typów:

int("42")       # "42"    → 42      (tekst → liczba całkowita)
float("3.14")   # "3.14"  → 3.14   (tekst → liczba z ułamkiem)
str(100)        # 100     → "100"  (liczba → tekst)

# Klasyczny błąd:
punkty = 10
print("Masz " + punkty + " punktów")       # BŁĄD — str + int!
print("Masz " + str(punkty) + " punktów")  # OK
print("Masz {} punktów".format(punkty))    # też OK — format robi to automatycznie
2.5 cwiczenie_input_1.py — prosty kalkulator
a = int(input("Podaj pierwszą liczbę: "))
b = int(input("Podaj drugą liczbę: "))

print("Suma:", a + b)
print("Różnica:", a - b)
print("Iloczyn:", a * b)
print("Wynik jako tekst:", str(a + b))
> python cwiczenie_input_1.py
Podaj pierwsza liczbe: 5
Podaj druga liczbe: 3
Suma: 8
Roznica: 2
Iloczyn: 15
Wynik jako tekst: 8
Zadanie: dodaj obliczanie średniej: (a + b) / 2. Potem spróbuj wpisać tekst zamiast liczby — przeczytaj komunikat błędu.
✔️ Po tej sekcji powinieneś umieć:
  • Używać input() do pobierania danych od użytkownika
  • Konwertować tekst na liczbę przez int()
  • Budować prosty kalkulator
🚀 Formatowanie tekstu i operator // to narzędzia których użyjesz przy każdym HUD w grze — zaraz wycentrujemy statek na ekranie!
G03

Gra: Środek Ekranu i Tekst

Komputer pokładowy wymaga kalibracji! Ustaw statek dokładnie na środku ekranu i wyświetl dane nawigacyjne.
🟢 Przejście — teraz wiesz jak wycentrować i wyświetlić dane!
Masz już wszystkie narzędzia. Sprawdź kod L02 poniżej — zaznaczone miejsca to dokładnie te, które zmodyfikujemy.
🚀 PYGAME — Środek ekranu i tekst
📂 Skopiuj gra_02.py jako gra_03.py
Weź swój plik gra_02.py i zapisz kopię jako gra_03.py.
Zmień tytuł okna na "Asteroidy — Lekcja 3: Środek Ekranu"
Poniżej masz adnotowany kod L02 — strzałki pokazują gdzie doklejamy zmiany:
Cel lekcji
Co: Statek automatycznie na środku dołu ekranu + dwa wiersze tekstu z danymi nawigacyjnymi w lewym górnym rogu.
Jak: Centrowanie przez SZEROKOSC // 2 - statek_szerokosc // 2. Tekst przez font.render() + blit() z .format().
Dlaczego: Formuła z // działa dla każdego rozmiaru okna — zmień SZEROKOSC i statek nadal jest na środku bez żadnych poprawek.
zegar = pygame.time.Clock()

# --- ZMIENNE STATKU ---
statek_x = 350          # <-- ZAMIENIMY NA WZOR Z // (krok 4.1)
statek_y = 500          # <-- ZAMIENIMY NA WYSOKOSC - 80 (krok 4.1)
statek_szerokosc = 40
statek_wysokosc = 50

# <-- TUTAJ DODAMY FONT (krok 4.2)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    ekran.fill((0, 0, 30))
    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    # <-- TUTAJ WYSWIETLIMY TEKST (kroki 4.3–4.6)

    pygame.display.flip()
    zegar.tick(60)
4.1 Wyśrodkowanie statku — zamień stałe na wzory
W gra_03.py zamień dwie linie zmiennych statku:
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80   # blisko dołu ekranu
SZEROKOSC // 2 → środek ekranu w poziomie
- statek_szerokosc // 2 → cofamy o połowę szerokości statku, żeby jego środek był na środku ekranu
Teraz zmiana SZEROKOSC automatycznie przesuwa statek — nie trzeba nic poprawiać ręcznie.
4.2 Font — pygame.font.SysFont()
Pod zmiennymi statku (przed pętlą) dodaj:
font = pygame.font.SysFont("Arial", 24)
Tworzymy obiekt czcionki raz — przed pętlą. Nie w pętli (nie robimy tego 60 razy na sekundę).
Argumenty: nazwa czcionki systemowej, rozmiar w punktach.
4.3 Renderowanie tekstu — font.render()
napis = font.render("Tekst", True, (255, 255, 255))
font.render() zwraca Surface — obrazek z tekstem.
Argumenty: tekst do wyświetlenia, antyaliasing (True = gładkie krawędzie), kolor RGB.
4.4 Naklejanie na ekran — ekran.blit()
ekran.blit(napis, (10, 10))
blit() naklejia Surface na ekran w pozycji (x, y).
Wywołujemy po ekran.fill() i przed pygame.display.flip().
4.5 Pozycja statku w HUD
W pętli, po rysowaniu statku, dodaj:
tekst = "Pozycja statku: X={} Y={}".format(statek_x, statek_y)
napis = font.render(tekst, True, (255, 255, 255))
ekran.blit(napis, (10, 10))
Połączenie .format() z ćwiczenia 2.4 + render() + blit() — wszystko razem.
4.6 Środek ekranu w HUD
Dodaj drugi wiersz tekstu, zaraz pod poprzednim:
tekst2 = "Srodek: X={} Y={}".format(SZEROKOSC // 2, WYSOKOSC // 2)
napis2 = font.render(tekst2, True, (100, 200, 100))
ekran.blit(napis2, (10, 40))
Uruchom: python gra_03.py — statek na środku, dwa napisy w lewym górnym rogu!
🗂️ Pełny kod gra_03.py — kliknij aby porównać ze swoim plikiem
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 3: Srodek Ekranu")
zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50

statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80

font = pygame.font.SysFont("Arial", 24)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    tekst = "Pozycja statku: X={} Y={}".format(statek_x, statek_y)
    napis = font.render(tekst, True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    tekst2 = "Srodek: X={} Y={}".format(SZEROKOSC // 2, WYSOKOSC // 2)
    napis2 = font.render(tekst2, True, (100, 200, 100))
    ekran.blit(napis2, (10, 40))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień SZEROKOSC = 800 na 600 — czy statek nadal jest na środku? Dlaczego?
  2. Zmień rozmiar czcionki z 24 na 6, potem na 72.
  3. Zmień kolor tekstu na (255, 255, 0) — żółty. Potem (255, 100, 100).
  4. Przesuń napis na dół: ekran.blit(napis, (10, WYSOKOSC - 40))
Zadania dodatkowe
  • Wyśrodkuj statek też pionowo: statek_y = WYSOKOSC // 2 - statek_wysokosc // 2
  • Stwórz zmienną punkty = 0 i wyświetl ją jako trzeci wiersz HUD: "Punkty: {}"
  • Wyświetl napis "ASTEROIDY" na środku ekranu — utwórz drugi font rozmiaru 52 i wycentruj go poziomo
  • Wypisz swoje imię w prawym dolnym rogu: ekran.blit(napis_imie, (SZEROKOSC - 150, WYSOKOSC - 30))
✔️ Sprawdź działającą grę:
  • Statek wyświetlony dokładnie na środku dołu ekranu
  • Tekst HUD widoczny w lewym górnym rogu
  • Dane nawigacyjne (pozycja statku) aktualizują się gdy poruszasz statkiem
T04

Python: Operatory porównania i if/else

Misja: opanuj operatory porównania, if/else i += — zaraz użyjemy ich żeby sterować statkiem i pilnować granic ekranu!
🔵 MODUŁ PYTHON — Operatory porównania, if/else, += i -=
Teoria — operatory porównania
Znasz już == z warunków. Oto pełna rodzina:

x = 350
print(x > 0)     # True  — większe od 0?
print(x < 0)     # False — mniejsze od 0?
print(x == 350)  # True  — równe 350? (== nie = !)
print(x != 350)  # False — różne od 350?
print(x >= 350)  # True  — większe LUB równe?
print(x <= 100)  # False — mniejsze LUB równe?

Uwaga: = to przypisanie (daj wartość), == to porównanie (czy równe?). To częsty błąd!
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_if_1.py, cwiczenie_if_2.py, cwiczenie_ruch_1.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_if_1.py — if/else z pozycją
x = 350

if x > 400:
    print("statek za daleko w prawo")
else:
    print("statek jest OK")
> python cwiczenie_if_1.py
statek jest OK
Zadanie: zmień x = 900 — co się wypisze? Potem x = -10. Potem dodaj trzeci warunek: wypisz komunikat gdy x == 0.
✔️ Po tej sekcji powinieneś umieć:
  • Używać operatorów >, <, == w warunkach
  • Pisać blok if/else
  • Rozumieć wynik True/False porównania
Teoria — += i -= (skrót aktualizacji pozycji)
x = 400
x += 5     # to samo co: x = x + 5   →  x staje się 405
x -= 3     # to samo co: x = x - 3   →  x staje się 402
x *= 2     # to samo co: x = x * 2   →  x staje się 804

W grze używamy += i -= w każdej klatce — to serce animacji.
2.2 cwiczenie_ruch_0.py — kolejne kroki ruchu
pozycja_x = 0
predkosc = 50

pozycja_x += predkosc
print("Krok 1 (w prawo):", pozycja_x)   # 50

pozycja_x += predkosc
print("Krok 2 (w prawo):", pozycja_x)   # 100

pozycja_x += predkosc
print("Krok 3 (w prawo):", pozycja_x)   # 150

pozycja_x -= predkosc
print("Krok 4 (w lewo):", pozycja_x)    # 100
> python cwiczenie_ruch_0.py
Krok 1 (w prawo): 50
Krok 2 (w prawo): 100
Krok 3 (w prawo): 150
Krok 4 (w lewo): 100
Zadanie: zmień predkosc = 100. Ile kroków w prawo żeby dojść do pozycji 800? Ile kroków z powrotem do 0?
✔️ Po tej sekcji powinieneś umieć:
  • Używać += i -= do aktualizacji pozycji
  • Rozumieć że każdy krok to zmiana wartości zmiennej
  • Śledzić wartość zmiennej krok po kroku
2.3 cwiczenie_ruch_1.py — ruch + granica prawa
x = 700          # startujemy blisko prawej krawędzi
predkosc = 5
szerokosc = 800
szer_statku = 40

# Chcemy wykonać 20 kroków ruchu — jak 20 klatek animacji
for klatka in range(20):
    x += predkosc   # przesuń o predkosc pikseli w prawo

    # Granica prawa:
    # szerokosc - szer_statku = 800 - 40 = 760
    # Gdy x > 760, prawa krawędź statku (x + 40) przekracza 800 — wychodzi za ekran!
    if x > szerokosc - szer_statku:
        x = szerokosc - szer_statku   # siłą cofamy do ostatniej dozwolonej pozycji

    print("Krok", klatka, "| x =", x, "| prawa krawedz =", x + szer_statku)
range(20) — chcemy wykonać dokładnie 20 kroków (jak 20 klatek animacji). Zmienna klatka liczy od 0 do 19.

if x > szerokosc - szer_statku — rozbijmy to na liczby:
  szerokosc - szer_statku = 800 - 40 = 760
  Gdy x = 760, prawa krawędź statku = 760 + 40 = 800 — dokładnie na granicy ekranu.
  Gdy x = 761, prawa krawędź = 801 — już jeden piksel za ekranem!
  Więc pytamy: czy x przekroczył tę ostatnią dozwoloną pozycję (760)?

x = szerokosc - szer_statku — gdy warunek prawdziwy, siłą ustawiamy x na 760. Statek „przykleja się" do prawej ściany — nie może iść dalej.

print(...) — wypisujemy oba: x (lewa krawędź) i x + szer_statku (prawa krawędź). Obserwuj jak prawa krawędź zatrzymuje się dokładnie na 800.
Zadanie: Napisz funkcję kolor_szary(jasnosc) która zwraca (jasnosc, jasnosc, jasnosc). Wypisz wynik dla jasności 100 i 200.
2.7 Funkcja zwracająca wartość logiczną
def czy_parzysta(n):
    return n % 2 == 0

wynik = czy_parzysta(4)
print("Czy 4 jest parzyste?", wynik)    # True

if czy_parzysta(7):
    print("7 jest parzyste")
else:
    print("7 jest nieparzyste")
> python cwiczenie_2_7.py
Czy 4 jest parzyste? True
7 jest nieparzyste
Romb = pytanie TAK/NIE. Dwie ścieżki wychodzą z rombu — jedna dla TAK, jedna dla NIE. Obie prowadzą do tego samego końca.
Ten sam schemat w Pythonie
pada_deszcz = True   # lub False

if pada_deszcz:
    print("Biorę parasol")
else:
    print("Nie biorę parasola")

print("Idę na spacer")
Romb → if. Lewa gałąź (TAK) → blok if. Prawa (NIE) → else. Kod pod blokiem = wspólny koniec.
Schemat blokowy granicy ekranu (prawa ściana)
Algorytm krok 4.5 — granica prawa
graph TD A([Klatka gry]) --> B["statek_x = statek_x + predkosc"] B --> C{"statek_x > 760 ?"} C -->|TAK| D["statek_x = 760"] C -->|NIE| E([Rysuj statek]) D --> E
To dokładnie algorytm który piszemy w kroku 4.5! Schemat pokazuje logikę zanim zobaczysz kod — łatwiej zrozumieć skąd pochodzi warunek 760 = SZEROKOSC - statek_szerokosc.
🚀 Operatory i if/else to mózg każdej gry — w następnej lekcji wpiszemy te warunki do pętli gry i statek zatrzyma się przy ścianie!
G04

Gra: Ruch Statku

Silniki startowe gotowe! Podłącz klawiaturę — czas polecieć!
🟢 Przejście — masz już wszystkie narzędzia!
Porównania → sprawdzanie granic. +=/-= → aktualizacja pozycji. if → reakcja na klawisze.
Sprawdź kod L03 poniżej — strzałki pokazują gdzie doklejamy zmiany:
🚀 PYGAME — Ruch Statku
📂 Skopiuj gra_03.py jako gra_04.py
Weź gra_03.py, zapisz kopię jako gra_04.py.
Zmień tytuł na "Asteroidy — Lekcja 4: Ruch Statku!"
Adnotowany kod L03 pokazuje gdzie lądują zmiany:
predkosc = 5    # <-- DODAJEMY (krok 4.1)

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT: ...

    # <-- TUTAJ: pygame.key.get_pressed() + ruch (kroki 4.2–4.3)
    # <-- TUTAJ: granice ekranu (krok 4.5)

    ekran.fill((0, 0, 30))
    pygame.draw.rect(...)
    ekran.blit(napis, (10, 10))
    pygame.display.flip()
    zegar.tick(60)
Cel lekcji
Co: Statek sterowany strzałkami (i WASD), zatrzymujący się przy krawędziach ekranu.
Jak: pygame.key.get_pressed() sprawdza stan klawiszy co klatkę; +=/-= zmienia pozycję. Cztery bloki if po ruchu korygują pozycję gdy przekracza granicę.
Dlaczego: Najpierw uruchamiamy BEZ granic żeby zobaczyć problem — statek wylata poza ekran. Dopiero potem dodajemy granice, rozumiejąc czemu są potrzebne.
4.1 Zmienna predkosc
Przed pętlą, pod zmiennymi statku, dodaj:
predkosc = 5   # pikseli na klatkę
Co oznacza predkosc = 5?
W każdej klatce gry (60 klatek na sekundę) statek przesuwa się o 5 pikseli.
To znaczy: 5 × 60 = 300 pikseli na sekundę. Ekran ma 800px — w ~2.7 sekundy statek przeleci przez cały ekran.

predkosc = 1 → bardzo wolno (60px/s). predkosc = 20 → bardzo szybko (1200px/s).

Pamiętaj też: statek ma rozmiar (statek_szerokosc = 40). Rysujemy go od punktu x, ale jego prawa krawędź sięga do x + 40. Przy szybkim ruchu w prawo może „wyskoczyć" za ekran nawet gdy x wygląda na OK — to wrócimy w kroku 4.5.

Dlaczego zmienna, nie liczba wpisana na stałe? Bo predkosc = 5 jest w jednym miejscu — zmiana jednej liczby zmienia szybkość w całej grze.
4.2 pygame.key.get_pressed() — skąd Pygame wie które klawisze są wciśnięte?
klawisze = pygame.key.get_pressed()
Co zwraca get_pressed()?
Wyobraź sobie klawiaturę — każdy klawisz ma swój numer (od 0 do ~511). Funkcja get_pressed() zwraca listę ~512 wartości True/False — jedną dla każdego klawisza:

# Co "widzi" Python po wywołaniu get_pressed():
# klawisze = [False, False, ..., True, ..., False, ...]
#                               ^
#                          ten jeden True = strzałka lewa jest wciśnięta
Każdy slot to: True = klawisz wciśnięty, False = klawisz zwolniony.

klawisze[pygame.K_LEFT] — to zwykłe indeksowanie listy!
pygame.K_LEFT to stała o wartości 276 — po prostu numer klawisza strzałki lewej.
Więc klawisze[pygame.K_LEFT] = klawisze[276] — sprawdzasz slot 276 na liście True/False.
Jeśli = True → klawisz jest teraz wciśnięty. Jeśli = False → nie jest.

To jest dokładnie tak samo jak indeksowanie zwykłej listy:
lista = [False, False, True, False]
print(lista[2])   # True — jak sprawdzasz element o indeksie 2

# get_pressed działa tak samo:
klawisze = pygame.key.get_pressed()
print(klawisze[pygame.K_LEFT])   # True jeśli lewa strzałka wciśnięta
Skąd biorą się pygame.K_LEFT, K_RIGHT... i jak znaleźć klawisz litery?
Pygame definiuje nazwy dla każdego klawisza — to po prostu liczby całkowite ze zrozumiałymi nazwami:

print(pygame.K_LEFT)    # 276 — strzałka lewa
print(pygame.K_RIGHT)   # 275 — strzałka prawa
print(pygame.K_UP)      # 273 — strzałka górna
print(pygame.K_DOWN)    # 274 — strzałka dolna
print(pygame.K_SPACE)   # 32  — spacja

Zamiast klawisze[276] piszemy klawisze[pygame.K_LEFT] — czytelnie i bezpiecznie.

Litery — konwencja: pygame.K_ + mała litera. Zawsze małe!

pygame.K_a    # klawisz A
pygame.K_d    # klawisz D
pygame.K_w    # klawisz W
pygame.K_s    # klawisz S

Nie ma pygame.K_A (z wielką literą) — litery są zawsze małe, od K_a do K_z.
Pełna lista wszystkich stałych: pygame.org/docs/ref/key.html
4.3 Ruch w 4 kierunki — dodaj w pętli (po obsłudze zdarzeń)
klawisze = pygame.key.get_pressed()

if klawisze[pygame.K_LEFT]:
    statek_x -= predkosc
if klawisze[pygame.K_RIGHT]:
    statek_x += predkosc
if klawisze[pygame.K_UP]:
    statek_y -= predkosc
if klawisze[pygame.K_DOWN]:
    statek_y += predkosc
Jak to działa krok po kroku w każdej klatce:

1. Pygame sprawdza które klawisze są wciśnięte.
2. Jeśli wciśnięto lewostatek_x -= predkosc → zmieniamy współrzędną X o 5 pikseli w lewo.
3. Jeśli wciśnięto prawostatek_x += predkosc → zmieniamy X o 5 pikseli w prawo.
4. Jeśli wciśnięto góra lub dół → zmieniamy statek_y (nie X!).

Potem następuje ekran.fill() — ekran zostaje wyczyszczony, i pygame.draw.rect() rysuje statek od nowa — ale już w nowych współrzędnych. Dlatego statek wydaje się poruszać.

Reguła: lewo/prawo zmieniają statek_x  |  góra/dół zmieniają statek_y.
🚀 Uruchom teraz — BEZ granic!
Zapisz plik i uruchom: python gra_04.py

Lataj statkiem we wszystkich kierunkach. Przekrocz każdą krawędź ekranu.

Co się dzieje? Zapisz co obserwujesz — to ważne dla następnego kroku.
4.4 Problem z granicami — co zobaczyłeś?
Bez granic wartości pozycji wychodzą poza ekran:

# Lecimy w lewo przez chwilę:
statek_x = -60    # statek całkowicie poza ekranem, niewidoczny
statek_x = -800   # daleko "po lewej stronie" — Pygame rysuje poza oknem

# Lecimy w prawo:
statek_x = 1200   # gdzieś daleko w prawo, też niewidoczny

Potrzebujemy strażników — bloków if które sprawdzają i korygują pozycję po każdym ruchu.
4.5 Granice ekranu — pełne wyjaśnienie
Krawędź lewa — prosta:
Gdy statek_x spadnie poniżej 0 → wróć na 0.
if statek_x < 0:
    statek_x = 0
Krawędź prawa — dlaczego SZEROKOSC - statek_szerokosc?

Pamiętaj: statek_x to współrzędna lewej krawędzi prostokąta (tak działa draw.rect).
Prawa krawędź statku = statek_x + statek_szerokosc
Chcemy żeby prawa krawędź ≤ 800:
statek_x + statek_szerokosc ≤ SZEROKOSC
Czyli: statek_x ≤ SZEROKOSC - statek_szerokosc → (800 - 40 = 760)
# Diagram: ekran 800px, statek 40px
# |←── statek_x ──→|←─ 40 ─→|
# 0                 760       800
#
# statek_x może być maksymalnie 760 — wtedy prawa krawędź jest na 800

if statek_x > SZEROKOSC - statek_szerokosc:
    statek_x = SZEROKOSC - statek_szerokosc
Identyczna logika dla góry i dołu. Wszystkie 4 granice razem — dodaj po bloku ruchu:
if statek_x < 0:
    statek_x = 0
if statek_x > SZEROKOSC - statek_szerokosc:
    statek_x = SZEROKOSC - statek_szerokosc
if statek_y < 0:
    statek_y = 0
if statek_y > WYSOKOSC - statek_wysokosc:
    statek_y = WYSOKOSC - statek_wysokosc
Uruchom ponownie — statek zatrzymuje się dokładnie na krawędzi. Wypróbuj wszystkie cztery ściany.
🗂️ Pełny kod gra_04.py — kliknij aby porównać ze swoim plikiem
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 4: Ruch Statku!")
zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()

    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0:
        statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc:
        statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0:
        statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc:
        statek_y = WYSOKOSC - statek_wysokosc

    ekran.fill((0, 0, 30))
    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    napis = font.render("Steruj strzalkami | X={} Y={}".format(statek_x, statek_y),
                        True, (200, 200, 200))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień predkosc = 5 na 25 — prawie niemożliwe do kontrolowania. Na 1 — ultra precyzja.
  2. Wciśnij lewo i prawo jednocześnie — co się dzieje? Czy statek stoi?
  3. Zmień prawą granicę na SZEROKOSC // 2 — statek nie może wejść do prawej połowy ekranu.
  4. Zmień lewą granicę z 0 na 100 — statek nie podchodzi do lewej ściany.
Zadania dodatkowe
  • Dodaj sterowanie WASD: K_a (lewo), K_d (prawo), K_w (góra), K_s (dół)
  • Dodaj osobną zmienną predkosc_y = 3 — wolniejszy ruch pionowy
  • Wyświetl prędkość w HUD: "X={} Y={} | V={}".format(statek_x, statek_y, predkosc)
  • Spraw żeby statek „odbijał się" od lewej ściany: zamiast statek_x = 0 wpisz predkosc = -predkosc
✔️ Sprawdź działającą grę:
  • Na ekranie widoczny HUD (tekst)
  • Wyświetlają się dane statku
  • Dane aktualizują się każdą klatkę
2 Etap 2 — Logika Gry

🎯 Cel ETAPU 2 — co tu zbudujesz?

  • Importować moduły Pythona i generować liczby losowe (random)
  • Zrozumieć animację — zmiana pozycji co klatkę = ruch na ekranie
  • Używać flag (True/False) do wykrywania kolizji i nietykalności
  • Asteroida spada z góry, resetuje się po wyjściu z ekranu i zderza ze statkiem
🚀 Po tym etapie Twój statek koliduje z asteroidami — zaraz zaczniesz strzelać!
T05

Python: Moduły i random

Misja: naucz się importowania modułów i losowania liczb — zaraz użyjemy tego żeby każda rozgrywka wyglądała inaczej!
🔵 MODUŁ PYTHON — Moduły i losowość
Teoria — co to jest moduł i jak go importować
Python ma wbudowane funkcje (print, range...), ale do wyspecjalizowanych zadań używamy modułów — gotowych zestawów funkcji napisanych przez innych programistów:

import random   # moduł do losowania liczb
import math     # moduł do obliczeń matematycznych
# import pygame  — to też jest moduł!

import musi być na początku pliku — zanim użyjemy czegokolwiek z modułu.
Po imporcie dostęp przez kropkę: random.nazwa_funkcji().
Teoria — random.randint(a, b)
import random

print(random.randint(1, 10))    # losowa liczba od 1 do 10 włącznie
print(random.randint(0, 799))   # losowa pozycja X na ekranie 800px
print(random.randint(20, 55))   # losowy rozmiar asteroidy

Ważne: randint zawiera oba końce zakresurandint(1, 10) może dać 1 i może dać 10.
Każde wywołanie = nowa losowa liczba. Każde uruchomienie programu = inne wyniki.
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_random_1.py, cwiczenie_random_2.py, cwiczenie_random_3.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_random_1.py — podstawowe losowanie
import random

liczba = random.randint(1, 10)
print("Wylosowana liczba:", liczba)
> python cwiczenie_random_1.py
Wylosowana liczba: 7
Zadanie: uruchom 5 razy — czy zawsze inna liczba? Zmień zakres na (1, 100), potem na (500, 800).
✔️ Po tej sekcji powinieneś umieć:
  • Importować moduł random
  • Używać random.randint(a, b) do losowania liczb
  • Rozumieć że wynik zmienia się przy każdym uruchomieniu
2.2 cwiczenie_random_2.py — symulacja losowania asteroidy
import random

szerokosc = 800
wysokosc = 600

x = random.randint(0, szerokosc - 50)
y = random.randint(0, wysokosc // 2)
rozmiar = random.randint(20, 55)

print("Asteroida:")
print("  x      =", x)
print("  y      =", y)
print("  rozmiar=", rozmiar)
print("  prawa krawedz =", x + rozmiar)
> python cwiczenie_random_2.py
Asteroida:
x = 234
y = 87
rozmiar= 42
prawa krawedz = 276
Zadanie: zmień zakres y żeby asteroida pojawiała się tylko w górnej ćwiartce (0–150). Zmień max rozmiar na 150 — czy prawa krawędź mieści się w ekranie?
✔️ Po tej sekcji powinieneś umieć:
  • Generować kilka zmiennych opisujących jeden obiekt gry
  • Obliczać prawą krawędź obiektu przez x + rozmiar
  • Sprawdzać czy obiekt mieści się na ekranie
2.3 cwiczenie_random_3.py — for + random (5 asteroid!)
import random

print("Losujemy 5 asteroid:")
for i in range(5):
    x = random.randint(0, 760)
    y = random.randint(0, 300)
    rozmiar = random.randint(20, 55)
    print("Asteroida", i, "→ x={} y={} rozmiar={}".format(x, y, rozmiar))
Połączenie for z L02 i random z tej lekcji. Za kilka lekcji użyjemy tego samego wzorca do tworzenia wielu asteroid w grze!
Zadanie: zmień na x -= predkosc i startuj od x = 50. Dodaj granicę lewą: if x < 0: x = 0. Teraz prawa krawędź to po prostu x + szer_statku.
✔️ Po tej sekcji powinieneś umieć:
  • Implementować granicę ekranu wzorem SZEROKOSC - szer_statku
  • Łączyć ruch (+=) z warunkiem granicy (if)
  • Rozumieć różnicę między lewą a prawą krawędzią obiektu
📊 Schematy blokowe — algorytm bez kodu
Schemat blokowy to obrazkowy zapis algorytmu — zanim napiszesz kod, możesz narysować co program ma robić. Każdy symbol ma swoje znaczenie:

Elipsa = Start / Stop Prostokąt = Akcja (przypisanie, wywołanie) Romb = Decyzja (if / else) Strzałka = Przepływ wykonania
Zacznijmy od czegoś z codziennego życia — potem to samo zrobimy dla gry.
Przykład z życia — "Czy zabrać parasol?"
Algorytm decyzji
graph TD A([START]) --> B{"Pada deszcz?"} B -->|TAK| C["Biore parasol"] B -->|NIE| D["Nie biore parasola"] C --> E([Ide na spacer]) D --> E
> python cwiczenie_flaga_2.py
Klatka 0 → normalny
...
Klatka 5 → NIETYKALNY
Klatka 6 → NIETYKALNY
...
Klatka 12 → normalny
Klatka 13 → normalny
Na samej górze pliku, zaraz pod import pygame, dodaj:
import random
Dlaczego na górze? Konwencja Pythona — wszystkie importy na początku, przed kodem.
Bez tego linia random.randint(...) wywoła błąd: NameError: name 'random' is not defined.
4.2 Zmienne asteroidy — 3 zmienne = 1 obiekt gry
Pod zmiennymi statku (przed pętlą) dodaj:
# --- ASTEROIDA (losujemy raz przy uruchomieniu) ---
asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = random.randint(50, 250)
asteroida_rozmiar = random.randint(20, 55)
Dlaczego przed pętlą? Bo chcemy losować raz przy uruchomieniu — nie 60 razy na sekundę.
Trzy zmienne opisują jeden obiekt: gdzie jest (x, y) i jak duży (rozmiar).
Dlaczego SZEROKOSC - 50? Żeby prawa krawędź asteroidy (x + ~50) nie wychodziła poza ekran.
❌ Klasyczny błąd — losowanie WEWNĄTRZ pętli:

while True:
    # ŹLE — te linie SĄ W PĘTLI:
    asteroida_x = random.randint(0, SZEROKOSC - 50)   # losuje 60x na sekundę!
    asteroida_y = random.randint(50, 250)
    ...
    pygame.draw.rect(ekran, (160,160,160), (asteroida_x, asteroida_y, ...))
Efekt: Asteroida teleportuje się w losowe miejsce co klatkę — 60 razy na sekundę. Zamiast jednej nieruchomej asteroidy widzisz chaotyczny migający prostokąt wszędzie na ekranie.

✅ Poprawnie — losowanie PRZED pętlą: jeden raz przy starcie programu, potem asteroida stoi w miejscu.
4.3 Rysowanie asteroidy — draw.rect znasz z L02!
W pętli, po rysowaniu statku, dodaj:
pygame.draw.rect(ekran, (160, 160, 160),
                 (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))
Kolor (160, 160, 160) — szary (wszystkie trzy wartości RGB równe = szarość).
Kwadratowa asteroida: szerokość = wysokość = asteroida_rozmiar.
4.4 Dane asteroidy w HUD
Po rysowaniu asteroidy dodaj informacje na ekranie:
napis = font.render("Asteroida X={} Y={} rozmiar={}".format(
    asteroida_x, asteroida_y, asteroida_rozmiar), True, (200, 200, 200))
ekran.blit(napis, (10, 10))
🚀 Uruchom — zamknij — uruchom znowu!
Uruchom python gra_05.py. Obserwuj pozycję i rozmiar asteroidy w HUD.
Zamknij okno i uruchom ponownie — inna asteroida!

To jest siła modułu random — każda rozgrywka wygląda inaczej.
🗂️ Pełny kod gra_05.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 5: Pierwsza Asteroida!")
zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = random.randint(50, 250)
asteroida_rozmiar = random.randint(20, 55)

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]: statek_x += predkosc
    if klawisze[pygame.K_UP]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:  statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    pygame.draw.rect(ekran, (160, 160, 160),
                     (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    napis = font.render("Asteroida X={} Y={} rozmiar={}".format(
        asteroida_x, asteroida_y, asteroida_rozmiar), True, (200, 200, 200))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Uruchom program 5 razy z rzędu — za każdym razem inna asteroida!
  2. Zmień zakres X na (0, 100) — asteroida zawsze blisko lewej. Potem (700, 760) — blisko prawej.
  3. Zmień zakres rozmiaru na (5, 200) — jakie monstrum może wylosować?
  4. Ustaw random.randint(400, 400) — co zwraca gdy a == b?
Zadania dodatkowe
  • Dodaj drugą asteroidę: asteroida2_x, asteroida2_y, asteroida2_rozmiar i narysuj ją
  • Pokoloruj asteroidę losowo: jasnosc = random.randint(100, 255) → kolor (jasnosc, jasnosc, jasnosc)
  • Wyświetl w HUD dane obu asteroid (dwa wiersze tekstu)
  • Zmień zakres y tak, żeby asteroida nigdy nie pojawiała się w dolnej połowie ekranu (gdzie jest statek)
✔️ Sprawdź działającą grę:
  • Asteroida pojawia się przy każdym uruchomieniu w innym miejscu
  • HUD pokazuje pozycję i rozmiar asteroidy
T06

Python: Animacja przez pętlę

Misja: zrozum jak while True działa jak silnik animacji — każda iteracja to jedna klatka, zmiana pozycji tworzy ruch!
🔵 MODUŁ PYTHON — Animacja przez pętlę
Teoria — każda iteracja while = jedna klatka
Pętla while True działa 60 razy na sekundę. Każde przejście przez pętlę to jedna klatka animacji.

Żeby coś się poruszyło, wystarczy zmienić jego pozycję o stałą wartość w każdej klatce:

y = 0          # pozycja startowa
predkosc = 3   # pikseli na klatkę

# Wyobraź sobie 30 klatek animacji:
for klatka in range(30):
    y += predkosc   # przesuwamy o 3 piksele
    print("Klatka", klatka, "→ y =", y)

Po 30 klatkach: y = 90. W grze przy 60 FPS: 60 × 3 = 180 pikseli na sekundę.
Teoria — reset gdy obiekt wyjdzie za ekran
y = 0
predkosc = 3
wysokosc_ekranu = 600

for klatka in range(300):
    y += predkosc
    if y > wysokosc_ekranu:   # wyleciał poza dół
        y = -50               # teleportuj nad ekran
    print("Klatka", klatka, "→ y =", y)

Dlaczego y = -50? Bo chcemy żeby obiekt wjechał z góry ekranu — zaczyna ponad krawędzią.
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_animacja_1.py i cwiczenie_animacja_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_animacja_1.py — symulacja 30 klatek
y = 0
predkosc = 3

for klatka in range(30):
    y += predkosc
    if y > 600:
        y = -50   # reset nad ekran
    print("Klatka {:2d} → y = {}".format(klatka, y))
> python cwiczenie_animacja_1.py
Start: y=0
Klatka 0: y=3
Klatka 1: y=6
Klatka 2: y=9
...
Klatka 29: y=87
Zadanie: uruchom i obserwuj — kiedy y się resetuje? Zmień predkosc na 50 — po ilu klatkach pierwszy reset? Zmień na 200 — co się dzieje?
✔️ Po tej sekcji powinieneś umieć:
  • Symulować animację jako y += predkosc w każdej klatce
  • Resetować obiekt gdy wyleci poza ekran
  • Rozumieć że animacja = wielokrotna zmiana danych
2.2 cwiczenie_animacja_2.py — dwa obiekty, różne prędkości
y1 = 0
y2 = 0
predkosc1 = 3
predkosc2 = 7

for klatka in range(20):
    y1 += predkosc1
    y2 += predkosc2
    if y1 > 600: y1 = -50
    if y2 > 600: y2 = -50
    print("Klatka {:2d} → y1={:4d}  y2={:4d}".format(klatka, y1, y2))
> python cwiczenie_animacja_2.py
Klatka 0 | y1= 3 y2= 7
Klatka 1 | y1= 6 y2= 14
Klatka 2 | y1= 9 y2= 21
...
Klatka 19 | y1= 60 y2= 140
Zadanie: który obiekt szybciej dociera do 600? Zmień predkosc2 = 7 na predkosc2 = predkosc1 * 2 — czy wynik się zmienia?
✔️ Po tej sekcji powinieneś umieć:
  • Animować dwa niezależne obiekty z różnymi prędkościami
  • Używać osobnych zmiennych dla każdego obiektu
  • Obserwować efekt różnych prędkości w tej samej pętli
🚀 Animacja to zmiana danych w każdej klatce — w następnej lekcji nasza asteroida spadnie z góry ekranu i odrodzi się!
G06

Gra: Ruch Asteroidy

Asteroida weszła w pole grawitacyjne! Aktywuj silnik spadania — niech spada w dół i resetuje się po wyjściu z ekranu.
🟢 Przejście — ożywiamy asteroidę!
Wiesz już jak symulować ruch w pętli. Teraz dodamy zmienną prędkości i logikę ruchu do gry.
Sprawdź adnotowany kod L05 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Ruch Asteroidy
📂 Skopiuj gra_05.py jako gra_06.py
Weź gra_05.py, zapisz kopię jako gra_06.py.
Zmień tytuł na "Asteroidy — Lekcja 6: Spadajaca Asteroida!"
Cel lekcji
Co: Asteroida spada z góry ekranu, znika po wyjściu za dół i natychmiast pojawia się na nowej losowej pozycji u góry.
Jak: W każdej klatce: asteroida_y += asteroida_predkosc. Gdy y > WYSOKOSC: reset y do wartości ujemnej i nowe losowe x.
Dlaczego: Reset na wartość ujemną (ponad ekranem) daje efekt "wylatuje z góry" zamiast teleportacji. To podstawowy wzorzec animacji — pozycja zmienia się każdą klatkę.
Schemat blokowy — ruch i reset asteroidy (każda klatka)
graph TD A([Klatka gry]) --> B["asteroida_y = asteroida_y + predkosc"] B --> C{"asteroida_y > 600 ?"} C -->|"TAK - wyleciala"| D["asteroida_y = -rozmiar"] D --> E["asteroida_x = nowa pozycja"] C -->|"NIE - spada"| F([Rysuj asteroide]) E --> F
import pygame
import random

...

predkosc = 5

# Asteroida — ZMIENIAMY: startuje ponad ekranem
asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50              # <-- ZMIENIAMY (krok 4.1)
asteroida_rozmiar = random.randint(25, 55)
asteroida_predkosc = 3         # <-- DODAJEMY (krok 4.1)

font = pygame.font.SysFont("Arial", 22)

while True:
    ...

    # <-- DODAJEMY ruch asteroidy (krok 4.2)
    # <-- DODAJEMY reset gdy wyleci z ekranu (krok 4.3)

    ekran.fill((0, 0, 30))
    pygame.draw.rect(...)  # statek
    pygame.draw.rect(...)  # asteroida

    pygame.display.flip()
    zegar.tick(60)
4.1 Zmienna prędkości — asteroida_predkosc
Zmień deklarację asteroidy — ustaw y = -50 (ponad ekranem) i dodaj zmienną prędkości:
asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50                     # startuje ponad ekranem
asteroida_rozmiar = random.randint(25, 55)
asteroida_predkosc = 3                # 3 piksele na klatkę
Dlaczego -50? Chcemy żeby asteroida wpłynęła z góry — zanim pojawi się na ekranie, lecąc od góry.
Prędkość osobno (asteroida_predkosc = 3) zamiast wpisanego 3 w pętli — łatwo zmienić jedną liczbą.
4.2 Ruch asteroidy w pętli
W pętli gry, po sprawdzaniu klawiszy, dodaj:
# --- NOWE: poruszamy asteroidą w dół ---
asteroida_y += asteroida_predkosc   # += to skrót: asteroida_y = asteroida_y + asteroida_predkosc
To jedna linia — i wystarczy! Każda klatka: y rośnie o 3. Przy 60 FPS: 180 pikseli na sekundę.
4.3 Reset po wyjściu z ekranu
Zaraz po linii asteroida_y += ... dodaj blok resetu:
# --- NOWE: reset asteroidy gdy wyleci za dolną krawędź ---
if asteroida_y > WYSOKOSC:
    asteroida_y = -asteroida_rozmiar                          # teleportuj nad ekran
    asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)  # nowa pozycja X
    asteroida_rozmiar = random.randint(25, 55)                # nowy losowy rozmiar
Dlaczego -asteroida_rozmiar? Żeby asteroida zaczęła wchodzić od góry, a nie pojawiła się nagle w połowie ekranu.
Reset losuje też nową pozycję X i nowy rozmiar — każde przejście to inna asteroida!
🗂️ Pełny kod gra_06.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 6: Ruch Asteroidy ===
# Cel: asteroida spada z góry i resetuje się na nowej pozycji!

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 6: Spadajaca Asteroida!")

zegar = pygame.time.Clock()

# Statek
statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

# Asteroida — zaczyna poza ekranem (nad górą)
asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(25, 55)
asteroida_predkosc = 3

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    # --- NOWE: poruszamy asteroidą w dół ---
    asteroida_y += asteroida_predkosc

    # --- NOWE: reset asteroidy gdy wyleci za dolną krawędź ---
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        asteroida_rozmiar = random.randint(25, 55)

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    napis = font.render("Unikaj asteroidy! Predkosc={}".format(asteroida_predkosc), True, (255, 255, 100))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień asteroida_predkosc = 3 na 10 — o ile szybciej spada? Oblicz piksele na sekundę.
  2. Zmień na 1 — jak długo trwa jedno przejście przez ekran?
  3. Ustaw asteroida_predkosc = 0 — co się dzieje? Dlaczego asteroid nie widać?
  4. Zmień kolor asteroidy na czerwony (200, 50, 50) — jak wygląda czerwona skała?
Zadania dodatkowe
  • Dodaj drugą asteroidę z osobnymi zmiennymi asteroida2_x, asteroida2_y, asteroida2_predkosc i własnym resetem
  • Spraw żeby asteroida po resecie losowała też nową prędkość z zakresu random.randint(2, 7)
  • Wyświetl aktualną prędkość asteroidy w HUD — sprawdź jak zmienia się przy każdym resecie
  • Zwiększaj prędkość asteroidy o 0.5 przy każdym resecie — jak szybko staje się niepokonana?
✔️ Sprawdź działającą grę:
  • Asteroida płynnie porusza się w dół
  • Odradza się automatycznie po opuszczeniu ekranu
  • Prędkość jest stała
T07

Python: Flagi logiczne (True/False)

Misja: naucz się flag logicznych i operatorów and/or/not — zaraz użyjemy ich żeby wykrywać kolizje i tworzyć efekt nietykalności!
🔵 MODUŁ PYTHON — Flagi logiczne (True/False)
Wyobraź sobie oświetlony przełącznik: albo świeci (włączony = True), albo nie (wyłączony = False). Trzeciej opcji nie ma. Flaga w programie działa tak samo — to lampka informująca o stanie: „statek jest trafiony" albo „statek nie jest trafiony".
Teoria — True i False jako stan programu
Flaga to zmienna, która ma tylko dwa stany: True (tak/włączone) lub False (nie/wyłączone).

trafiony = False    # statek nie jest trafiony

# Gdy nastąpi trafienie:
trafiony = True

# Sprawdzamy stan:
if trafiony:
    print("Statek trafiony!")
else:
    print("Statek cały.")

Operatory logiczne: and (oba muszą być True), not (odwraca wartość):
zycia = 3
trafiony = False

if zycia > 0 and not trafiony:
    print("Gra trwa!")
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_flaga_1.py i cwiczenie_flaga_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_flaga_1.py — symulacja trafienia
trafiony = False
zycia = 3

print("Stan startowy:")
print("  trafiony =", trafiony)
print("  zycia =", zycia)

# Symulujemy trafienie
trafiony = True
zycia -= 1

print("\nPo trafieniu:")
print("  trafiony =", trafiony)
print("  zycia =", zycia)

# Sprawdzamy warunek
if zycia > 0 and not trafiony:
    print("\nMozna strzelac!")
else:
    print("\nNie mozna — trafiony lub brak zyc!")
> python cwiczenie_flaga_1.py
Stan startowy:
trafiony = False
zycia = 3

Po trafieniu:
trafiony = True
zycia = 2

Nie mozna — trafiony lub brak zyc!
Zadanie: zmień trafiony = False i zycia = 1 — jaki komunikat? Zmień na trafiony = True i zycia = 5 — co wtedy?
✔️ Po tej sekcji powinieneś umieć:
  • Tworzyć zmienne logiczne True/False
  • Zmieniać wartość flagi i sprawdzać jej stan w if
  • Łączyć warunki przez and not
2.2 cwiczenie_flaga_2.py — nietykalność z licznikiem
nietykalny = False
licznik_nietykalnosci = 0

for klatka in range(20):
    if klatka == 5:        # w klatce 5 dostajemy cios
        nietykalny = True
        licznik_nietykalnosci = 0

    if nietykalny:
        licznik_nietykalnosci += 1
        if licznik_nietykalnosci >= 8:   # 8 klatek nietykalności
            nietykalny = False

    stan = "NIETYKALNY" if nietykalny else "normalny"
    print("Klatka {:2d} → {}".format(klatka, stan))
Dokładnie ten sam wzorzec zastosujemy w grze! Zamiast licznika klatek użyjemy milisekund z pygame.time.get_ticks().
Zadanie: zmień range(5) na range(10). Dopisz wypisywanie prawej krawędzi (x + rozmiar) i sprawdź czy żadna nie wychodzi poza 800.
✔️ Po tej sekcji powinieneś umieć:
  • Łączyć pętlę for z losowaniem przez random
  • Generować wiele obiektów gry w jednej pętli
  • Rozumieć że każde uruchomienie daje inne wyniki
Teoria — random.choice() i random.uniform()
randint(a, b) losuje liczbę całkowitą. Są też inne przydatne funkcje z modułu random:

import random

# choice() — losuje jeden element z listy
kolory = ["czerwony", "zielony", "niebieski", "żółty"]
wybrany = random.choice(kolory)
print("Losowy kolor:", wybrany)

# choice() z listą liczb
rozmiary = [20, 30, 40, 50, 60]
rozmiar = random.choice(rozmiary)
print("Losowy rozmiar asteroidy:", rozmiar)

# uniform(a, b) — losuje liczbę z ułamkiem między a i b
predkosc = random.uniform(1.5, 4.0)
print("Losowa prędkość:", predkosc)   # np. 2.847...

W grze: random.choice() przyda się do losowania koloru wroga, typu asteroidy, kierunku ruchu.
🚀 Losowość to serce gier — teraz nasza asteroida pojawi się w innym miejscu przy każdym uruchomieniu!
G05

Gra: Pierwsza Asteroida

Czujniki wykryły pole asteroid! Moduł losowania jest uszkodzony — napraw go i wygeneruj pierwszą przeszkodę. Każde uruchomienie gry będzie inne!
🟢 Przejście — dodajemy asteroidę do gry!
Wiesz już jak losować liczby. Teraz użyjemy tego żeby umieścić asteroidę w losowym miejscu na planszy.
Sprawdź adnotowany kod L04 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Pierwsza Asteroida
📂 Skopiuj gra_04.py jako gra_05.py
Weź gra_04.py, zapisz kopię jako gra_05.py.
Zmień tytuł na "Asteroidy — Lekcja 5: Pierwsza Asteroida!"
import pygame
import random   # <-- DODAJEMY (krok 4.1)

...

predkosc = 5

# <-- TUTAJ DODAMY ZMIENNE ASTEROIDY (krok 4.2)

font = pygame.font.SysFont("Arial", 22)

while True:
    ...
    ekran.fill((0, 0, 30))
    pygame.draw.rect(...)  # statek

    # <-- TUTAJ NARYSUJEMY ASTEROIDĘ (krok 4.3)
    # <-- TUTAJ DODAMY JEJ DANE DO HUD (krok 4.4)

    pygame.display.flip()
    zegar.tick(60)
Cel lekcji
Co: Nieruchoma szara asteroida — każde uruchomienie gry daje inną pozycję i inny rozmiar.
Jak: Trzy zmienne (asteroida_x, asteroida_y, asteroida_rozmiar) losujemy przez random.randint() przed pętlą gry.
Dlaczego: Losowanie przed pętlą = raz przy starcie. Losowanie w pętli = 60 nowych pozycji na sekundę (asteroida teleportowałaby się co klatkę!).
4.1 import random — na górze pliku
Na samej górze gra_05.py, zaraz pod import pygame, dodaj:
import random
Dlaczego na górze? Konwencja Pythona — wszystkie importy na początku pliku, przed jakimkolwiek kodem.
Bez tego linia random.randint(...) wywoła błąd: NameError: name 'random' is not defined.
4.2 Zmienne asteroidy — losujemy raz przed pętlą
Pod zmiennymi statku (przed font = ...), dodaj trzy zmienne asteroidy:
# --- NOWE: losowa asteroida ---
asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = random.randint(50, 250)
asteroida_rozmiar = random.randint(20, 55)
Dlaczego trzy zmienne? Jeden obiekt opisany przez trzy liczby — gdzie jest (x, y) i jak duży (rozmiar).
Dlaczego SZEROKOSC - 50? Żeby prawa krawędź asteroidy (x + ~50) nie wychodziła poza ekran.
❌ Klasyczny błąd — losowanie WEWNĄTRZ pętli:

while True:
    # ŹLE — te linie SĄ W PĘTLI:
    asteroida_x = random.randint(0, SZEROKOSC - 50)   # losuje 60x na sekundę!
    asteroida_y = random.randint(50, 250)
    pygame.draw.rect(ekran, (160,160,160), (asteroida_x, asteroida_y, ...))
Efekt: Asteroida teleportuje się w losowe miejsce 60 razy na sekundę — widzisz migający chaos zamiast jednej nieruchomej asteroidy.

✅ Poprawnie — losowanie PRZED pętlą: raz przy starcie programu, potem asteroida stoi w miejscu.
4.3 Rysuj asteroidę — draw.rect jak statek
W pętli gry, w sekcji rysowania, po rysowaniu statku dodaj:
# --- NOWE: rysujemy asteroidę (szary prostokąt) ---
pygame.draw.rect(ekran, (160, 160, 160),
                 (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))
Kolor (160, 160, 160) — szary (wszystkie trzy wartości RGB równe = szarość).
Kwadratowa asteroida: szerokość = wysokość = asteroida_rozmiar. Dokładnie to samo draw.rect co statek z G02!
4.4 Dane asteroidy w HUD
Po rysowaniu asteroidy, dodaj napis z jej pozycją i rozmiarem:
napis = font.render("Asteroida X={} Y={} rozmiar={}".format(
    asteroida_x, asteroida_y, asteroida_rozmiar), True, (200, 200, 200))
ekran.blit(napis, (10, 10))
Obserwuj HUD: każde uruchomienie programu daje inne X, Y i rozmiar.
Zamknij okno i uruchom ponownie — inna asteroida! To jest siła modułu random.
🗂️ Pełny kod gra_05.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 5: Losowość i Pierwsza Asteroida ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 5: Pierwsza Asteroida!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = random.randint(50, 250)
asteroida_rozmiar = random.randint(20, 55)

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]: statek_x += predkosc
    if klawisze[pygame.K_UP]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:  statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    pygame.draw.rect(ekran, (160, 160, 160),
                     (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    napis = font.render("Asteroida X={} Y={} rozmiar={}".format(
        asteroida_x, asteroida_y, asteroida_rozmiar), True, (200, 200, 200))
    ekran.blit(napis, (10, 10))

    napis2 = font.render("Zamknij i uruchom ponownie — inna asteroida!", True, (255, 255, 100))
    ekran.blit(napis2, (10, 40))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Uruchom program 5 razy z rzędu — czy zawsze inna asteroida?
  2. Zmień zakres X: random.randint(0, 100) — asteroida zawsze blisko lewej. Potem (700, 760) — blisko prawej.
  3. Zmień zakres rozmiaru na (5, 200) — jakie monstrum może wylosować?
  4. Ustaw random.randint(400, 400) — co zwraca gdy a == b?
Zadania dodatkowe
  • Dodaj drugą asteroidę: asteroida2_x, asteroida2_y, asteroida2_rozmiar i narysuj ją w innym kolorze
  • Losuj kolor asteroidy: jasnosc = random.randint(100, 255) → kolor (jasnosc, jasnosc, jasnosc)
  • Wyświetl w HUD dane obu asteroid (dwa wiersze tekstu)
  • Zmień zakres y tak, żeby asteroida nigdy nie pojawiała się w dolnej połowie ekranu (gdzie jest statek)
✔️ Sprawdź działającą grę:
  • Szara asteroida pojawia się przy każdym uruchomieniu w innym miejscu
  • HUD pokazuje pozycję X, Y i rozmiar asteroidy
  • Statek można nadal sterować strzałkami
3 Etap 3 — System Walki

🎯 Cel ETAPU 3 — co tu zbudujesz?

  • Strzelać laserem — cooldown między strzałami, lista laserów w pętli
  • Filtrować listę — usuwać lasery które opuściły ekran (wzorzec budowania nowej listy)
  • Naliczać punkty za trafienia asteroid i wrogów
  • Używać if/elif/else żeby rozróżnić trafienie wroga (20 pkt) od asteroidy (10 pkt)
🚀 Po tym etapie możesz strzelać i zdobywać punkty — czas na pierwszego wroga!
T08

Python: Złożone warunki (and, not)

Misja: opanuj złożone warunki — zaraz użyjemy ich żeby kontrolować strzał: można strzelić tylko gdy poprzedni laser już nie leci!
🔵 MODUŁ PYTHON — Złożone warunki (and, not)
Teoria — and i not w warunkach
Operatory logiczne łączą kilka warunków w jeden:

zycia = 3
laser_aktywny = False

# and — oba muszą być True
if zycia > 0 and laser_aktywny:
    print("Laser leci i grasz")

# not — odwraca wartość
if not laser_aktywny:
    print("Laser jest gotowy do strzału")

# and not — połączenie
if zycia > 0 and not laser_aktywny:
    print("Możesz strzelać!")

Tabela wartości and:
True and True → True  |  True and False → False  |  False and True → False
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_and_1.py i cwiczenie_and_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_and_1.py — ćwiczenie and i not
zycia = 3
tarczа = True
laser_aktywny = False

print("zycia > 0:", zycia > 0)
print("not laser_aktywny:", not laser_aktywny)
print("zycia > 0 and not laser_aktywny:", zycia > 0 and not laser_aktywny)
print("tarcza and not laser_aktywny:", tarcza and not laser_aktywny)

# Zmień wartości i sprawdź co się zmienia:
laser_aktywny = True
print("\nPo zmianie laser_aktywny = True:")
print("zycia > 0 and not laser_aktywny:", zycia > 0 and not laser_aktywny)
> python cwiczenie_and_1.py
zycia > 0: True
not laser_aktywny: True
zycia > 0 and not laser_aktywny: True
tarcza and not laser_aktywny: True

Po zmianie laser_aktywny = True:
zycia > 0 and not laser_aktywny: False
Zadanie: zmień zycia = 0 — co zwraca pierwszy warunek? Zmień tarcza = False — jak zmienia się wynik złożonych warunków?
✔️ Po tej sekcji powinieneś umieć:
  • Tworzyć tabele prawdy dla and, or, not
  • Łączyć kilka warunków w jeden złożony wyrażenie
  • Rozumieć kiedy złożony warunek daje True
2.2 cwiczenie_and_2.py — stan lasera z warunkiem złożonym
laser_aktywny = False
zycia = 3
spacja_wcisnięta = True   # symulujemy wciśnięcie SPACJI

for klatka in range(10):
    # Strzelamy tylko gdy SPACJA i laser NIE jest aktywny
    if spacja_wcisnięta and not laser_aktywny:
        laser_aktywny = True
        print("Klatka {}: STRZAL! laser leci.".format(klatka))

    if laser_aktywny:
        print("Klatka {}: laser w powietrzu".format(klatka))
        if klatka >= 5:            # po klatce 5 laser dociera do góry
            laser_aktywny = False
            print("Klatka {}: laser zniknal".format(klatka))
To dokładny schemat działania lasera w grze — and not laser_aktywny blokuje drugi strzał dopóki pierwszy nie zniknie.
Zadanie: zmień próg na 4 — krótka nietykalność. Zmień na 15 — długa. Dodaj drugie trafienie w klatce 12.
✔️ Po tej sekcji powinieneś umieć:
  • Symulować klatki nietykalności (i-frames) z licznikiem
  • Rozumieć mechanizm timingu przez zliczanie klatek
  • Stosować wzorzec flaga + licznik w grze
Teoria — operator or (lub)
and wymaga żeby oba warunki były prawdziwe. or wymaga żeby przynajmniej jeden był prawdziwy:

stan = "game_over"

# or — prawda gdy CHOĆ JEDEN warunek jest spełniony
if stan == "start" or stan == "game_over":
    print("Jesteśmy w menu — czekamy na ENTER")

# and — prawda gdy OBA warunki są spełnione
hp = 0
trafiony = True
if hp == 0 and trafiony:
    print("Gracz pokonany")

# not — odwraca warunek
laser_aktywny = False
if not laser_aktywny:
    print("Można strzelić")

Szybkie podsumowanie:
  True and TrueTrue
  True and FalseFalse
  True or FalseTrue
  False or FalseFalse
  not TrueFalse
🚀 Flagi logiczne to strażnicy stanu gry — zaraz użyjemy ich żeby statek nie tracił wielu żyć w jednej klatce!
G07

Gra: Kolizje!

Alarmy ostrzegawcze! Komputer musi wiedzieć kiedy asteroida uderza w statek. Aktywuj system kolizji — BUM!
🟢 Przejście — system kolizji w grze!
Wiesz już jak flaga działa. Teraz pygame.Rect + colliderect() zastąpią ręczne sprawdzanie pozycji.
Sprawdź adnotowany kod L06 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Kolizje!
📂 Skopiuj gra_06.py jako gra_07.py
Weź gra_06.py, zapisz kopię jako gra_07.py.
Zmień tytuł na "Asteroidy — Lekcja 7: Kolizje!"
Cel lekcji
Co: Kolizja statku z asteroidą → ekran miga czerwono, wyświetla "BUM!", gracz traci życie. Po 3 życiach koniec gry.
Jak: pygame.Rect + colliderect() wykrywa zachodzenie prostokątów. Flaga trafiony = True aktywuje efekt i blokuje kolejne trafienia przez 600 ms.
Dlaczego: Bez flagi Pygame wykrywałby kolizję 60 razy na sekundę — gracz straciłby wszystkie życia w ułamku sekundy. To klasyczny mechanizm "i-frames" (klatki nietykalności).
...
asteroida_predkosc = 3

# <-- DODAJEMY system żyć i flagę (krok 4.1)
# zycia = 3, trafiony = False, czas_trafienia = 0

while True:
    ...
    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC: ...

    # <-- TUTAJ: pygame.Rect i colliderect (krok 4.2)
    # <-- TUTAJ: obsługa trafienia i flagi (krok 4.3)

    # <-- ZMIENIAMY: kolor tła i kolor statku (krok 4.4)
    ekran.fill(...)
    pygame.draw.rect(ekran, kolor_statku, ...)
    ...
4.1 Zmienne systemu żyć i flag — przed pętlą
Pod zmiennymi asteroidy (przed font = ...) dodaj zmienne systemu żyć:
zycia = 3
trafiony = False     # czy właśnie dostaliśmy cios?
czas_trafienia = 0   # kiedy dostaliśmy cios (ms)

font = pygame.font.SysFont("Arial", 26)
font_duzy = pygame.font.SysFont("Arial", 64)
trafiony = False — flaga logiczna. Gdy True: statek jest w trybie nietykalności.
czas_trafienia — zapamiętamy tu moment trafienia w milisekundach.
Dwa fonty: font do HUD, font_duzy do napisu "BUM!" na środku ekranu.
4.2 pygame.Rect — prostokąt do wykrywania kolizji
W pętli gry, po bloku ruchu asteroidy, utwórz prostokąty dla obu obiektów:
# --- NOWE: tworzymy prostokąty do sprawdzania kolizji ---
statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)
pygame.Rect(x, y, szerokosc, wysokosc) — tworzy niewidoczny prostokąt w podanym miejscu.
Pygame sprawdza kolizje na prostokątach, nie na pikselach — to szybkie i wystarczające dla gier 2D.
Tworzymy nowe Recty każdą klatkę, bo statek i asteroida się poruszają.
4.3 colliderect() — wykrywanie zderzenia i flaga nietykalności
Zaraz po utworzeniu Rectów sprawdź kolizję:
# --- NOWE: czy prostokąty się nakrywają? ---
if statek_rect.colliderect(asteroida_rect) and not trafiony:
    zycia -= 1
    trafiony = True
    czas_trafienia = pygame.time.get_ticks()   # zapisz czas trafienia w ms
    # Resetuj asteroidę — nowa pozycja
    asteroida_y = -asteroida_rozmiar
    asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

# Efekt trafienia kończy się po 600 ms
if trafiony and pygame.time.get_ticks() - czas_trafienia > 600:
    trafiony = False
colliderect() zwraca True gdy dwa prostokąty się nakrywają.
and not trafiony — blokuje kolejne trafienia przez 600 ms. Bez tego gracz traciłby 3 życia w ułamku sekundy (kolizja wykrywana 60× na sekundę przez kilka klatek z rzędu).
❌ Kolizja bez flagi — tracisz wszystkie życia natychmiast:

# ŹLE — brak "and not trafiony":
if statek_rect.colliderect(asteroida_rect):
    zycia -= 1   # odpala 60x na sekundę przez kilka klatek!
Efekt: Klatka 1: -1, klatka 2: -1, klatka 3: -1. Gra kończy się zanim gracz zdąży zareagować.
✅ Flaga trafiony daje 600 ms nietykalności po każdym ciosie — klasyczny wzorzec i-frames.
4.4 Efekty wizualne — czerwone tło, BUM!, kolor statku
Zastąp stałe ekran.fill i rysowanie statku wersją reagującą na flagę:
# Tło — czerwone po trafieniu, normalne podczas gry
if trafiony:
    ekran.fill((80, 0, 0))
else:
    ekran.fill((0, 0, 30))

# Kolor statku zmienia się po trafieniu
kolor_statku = (255, 80, 80) if trafiony else (0, 200, 255)
pygame.draw.rect(ekran, kolor_statku, (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

# HUD z życiami i napisem BUM!
zycia_tekst = "Zycia: " + ("* " * zycia)
ekran.blit(font.render(zycia_tekst, True, (255, 80, 80)), (10, 10))

if trafiony:
    bum = font_duzy.render("BUM!", True, (255, 255, 0))
    ekran.blit(bum, (SZEROKOSC // 2 - 70, WYSOKOSC // 2 - 50))
(255, 80, 80) if trafiony else (0, 200, 255) — skrócony if/else w jednej linii (wyrażenie warunkowe).
Trzy sygnały trafienia naraz: czerwone tło + czerwony statek + żółty napis BUM! — gracz nie przegapi.
4.5 Koniec gry — ekran KONIEC GRY gdy zycia <= 0
Po sekcji rysowania, przed pygame.display.flip(), dodaj sprawdzenie:
if zycia <= 0:
    ekran.fill((0, 0, 0))
    koniec = font_duzy.render("KONIEC GRY!", True, (255, 50, 50))
    ekran.blit(koniec, (SZEROKOSC // 2 - 180, WYSOKOSC // 2 - 50))
    pygame.display.flip()
    pygame.time.wait(3000)   # czekamy 3 sekundy
    pygame.quit()
    exit()
pygame.time.wait(3000) — zatrzymuje program na 3000 ms = 3 sekundy, żeby gracz zdążył przeczytać wynik.
Potem pygame.quit() i exit() zamykają grę.
🗂️ Pełny kod gra_07.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 7: Kolizje! ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 7: Kolizje!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

zycia = 3
trafiony = False
czas_trafienia = 0

font = pygame.font.SysFont("Arial", 26)
font_duzy = pygame.font.SysFont("Arial", 64)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]: statek_x += predkosc
    if klawisze[pygame.K_UP]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:  statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        asteroida_rozmiar = random.randint(30, 55)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)

    if statek_rect.colliderect(asteroida_rect) and not trafiony:
        zycia -= 1
        trafiony = True
        czas_trafienia = pygame.time.get_ticks()
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    if trafiony and pygame.time.get_ticks() - czas_trafienia > 600:
        trafiony = False

    if trafiony:
        ekran.fill((80, 0, 0))
    else:
        ekran.fill((0, 0, 30))

    kolor_statku = (255, 80, 80) if trafiony else (0, 200, 255)
    pygame.draw.rect(ekran, kolor_statku, (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    zycia_tekst = "Zycia: " + ("* " * zycia)
    ekran.blit(font.render(zycia_tekst, True, (255, 80, 80)), (10, 10))

    if trafiony:
        bum = font_duzy.render("BUM!", True, (255, 255, 0))
        ekran.blit(bum, (SZEROKOSC // 2 - 70, WYSOKOSC // 2 - 50))

    if zycia <= 0:
        ekran.fill((0, 0, 0))
        koniec = font_duzy.render("KONIEC GRY!", True, (255, 50, 50))
        ekran.blit(koniec, (SZEROKOSC // 2 - 180, WYSOKOSC // 2 - 50))
        pygame.display.flip()
        pygame.time.wait(3000)
        pygame.quit()
        exit()

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień zycia = 3 na 5 — więcej szans. Ile asteroid możesz ominąć?
  2. Zmień 600 na 100 — krótka nietykalność. Trudniej czy łatwiej?
  3. Zmień 600 na 2000 — 2 sekundy nietykalności. Czy asteroida może trafić gdy jesteś nietykalny?
  4. Zmień kolor BUM! z żółtego (255, 255, 0) na biały — jaki wygląda lepiej?
Zadania dodatkowe
  • Po każdym trafieniu asteroida przyspiesza: dodaj asteroida_predkosc += 0.5 wewnątrz bloku kolizji
  • Wyświetl w HUD ile ms pozostało do końca nietykalności: max(0, 600 - (pygame.time.get_ticks() - czas_trafienia))
  • Zmień ekran końca gry — zamiast czekać 3 sekundy wyświetl "Wciśnij R aby zagrać ponownie"
  • Zamiast serduszek w HUD wyświetl liczbę żyć jako cyfrę: "HP: {}"
✔️ Sprawdź działającą grę:
  • Zderzenie statku z asteroidą → ekran miga czerwono i pojawia się "BUM!"
  • HUD pokazuje liczbę żyć, która maleje po każdym trafieniu
  • Po 3 trafieniach pojawia się ekran "KONIEC GRY!" i gra się zamyka
  • Możesz oberwać tylko raz na 600 ms — nie tracisz wszystkich żyć naraz
T09

Python: Listy — pełny moduł

Misja: opanuj listy — najważniejszą strukturę danych w grach! Lista to klucz do zarządzania wieloma laserami, asteroidami i wrogami jednocześnie.
🔵 MODUŁ PYTHON — Listy (pełny moduł)
1. Co to jest lista?
Lista to pudełko z wieloma wartościami w jednej zmiennej. Możesz pobierać, zmieniać, dodawać i usuwać elementy.

owoce = ["jabłko", "banan", "gruszka"]

print(owoce[0])   # jabłko  — indeksy zaczynają się od 0!
print(owoce[1])   # banan
print(owoce[2])   # gruszka
print(len(owoce)) # 3 — liczba elementów

Indeksy zawsze od 0 — pierwszy element to [0], nie [1]!
2. Dodawanie elementów — append()
owoce = []                   # pusta lista
owoce.append("jabłko")       # ["jabłko"]
owoce.append("banan")        # ["jabłko", "banan"]
owoce.append("gruszka")      # ["jabłko", "banan", "gruszka"]

print(owoce)
print("Mam", len(owoce), "owoce")
3. Pętla for po liście
owoce = ["jabłko", "banan", "gruszka"]

for owoc in owoce:
    print("Lubię:", owoc)

Lista liczb — można operować matematycznie:

liczby = [10, 20, 30, 40]

for n in liczby:
    print(n * 2)   # 20, 40, 60, 80
4. Tworzenie listy w pętli
kwadraty = []

for i in range(1, 6):
    kwadraty.append(i * i)

print(kwadraty)   # [1, 4, 9, 16, 25]

Wzorzec pusta lista → pętla → append to fundament generowania obiektów w grze (asteroid, laserów, wrogów).
5. Listy zagnieżdżone — lista list
Lista może zawierać inne listy — to fundament do map 2D, plansz gier i list obiektów:

punkty = [
    [10, 20],
    [30, 40],
    [50, 60]
]

print(punkty[0])      # [10, 20]     — cały pierwszy element
print(punkty[0][0])   # 10           — x pierwszego punktu
print(punkty[0][1])   # 20           — y pierwszego punktu
print(punkty[1][0])   # 30           — x drugiego punktu

Pętla po liście zagnieżdżonej:

for p in punkty:
    print("X:", p[0], "Y:", p[1])
6. Pętla for w for — zagnieżdżone pętle
Aby przejść przez każdy element każdego wiersza, używamy dwóch pętli — jednej w drugiej:

plansza = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

for wiersz in plansza:
    for element in wiersz:
        print(element, end=" ")   # end=" " nie robi nowej linii
    print()   # nowa linia po każdym wierszu
Wynik:
1 2 3
4 5 6
7 8 9
7. Praktyczne przykłady do gier
Lista asteroid — każda ma x, y, rozmiar:

asteroidy = [
    [100, 50, 40],
    [300, 80, 30],
    [500, 120, 50]
]

for ast in asteroidy:
    print("Asteroida X:{} Y:{} rozmiar:{}".format(ast[0], ast[1], ast[2]))

Lista laserów — każdy ma x, y:

lasery = [[400, 500], [410, 480], [420, 460]]

for laser in lasery:
    laser[1] -= 9   # każdy leci w górę o 9 pikseli

Lista wrogów — kolor, x, y:

wrogowie = [
    ["zielony", 200, 50],
    ["czerwony", 300, 80],
    ["fioletowy", 450, 120]
]

for wrog in wrogowie:
    print("Wróg:", wrog[0], "pozycja:", wrog[1], wrog[2])
8. Generowanie tablicy — for w for
Tak generuje się dwuwymiarową tablicę (np. planszę gry):

tablica = []

for y in range(4):
    wiersz = []
    for x in range(4):
        wiersz.append(x + y * 4)   # każde pole ma unikalny numer
    tablica.append(wiersz)

# Wyświetlamy
for wiersz in tablica:
    print(wiersz)

Mapa gry — 0 = puste, 1 = ściana:

mapa = [
    [0, 0, 1, 0],
    [1, 0, 1, 0],
    [0, 0, 0, 1],
    [1, 1, 0, 0]
]

for y in range(len(mapa)):
    for x in range(len(mapa[y])):
        if mapa[y][x] == 1:
            print("Ściana na: x={} y={}".format(x, y))
9. Plansza 5×5 — układ współrzędnych jak w Pygame
plansza = []

for y in range(5):
    wiersz = []
    for x in range(5):
        wiersz.append((x, y))   # krotka (x, y)
    plansza.append(wiersz)

for wiersz in plansza:
    print(wiersz)
Wynik:
[(0,0), (1,0), (2,0), (3,0), (4,0)]
[(0,1), (1,1), (2,1), (3,1), (4,1)]
...

To dokładnie jak działa układ współrzędnych w Pygame — (0,0) w lewym górnym rogu, X rośnie w prawo, Y rośnie w dół.
10. Listy + funkcje
Funkcja może zwracać listę — to wzorzec używany w grze od L14:

def generuj_asteroidy(n):
    lista = []
    for i in range(n):
        lista.append([i * 100, 50, 40])   # x, y, rozmiar
    return lista

asteroidy = generuj_asteroidy(5)
for ast in asteroidy:
    print("Asteroida:", ast)
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_lista_1.py do cwiczenie_lista_5.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_lista_1.py — podstawy: append, len, for
wyniki = []

wyniki.append(10)
wyniki.append(25)
wyniki.append(5)

print("Lista:", wyniki)
print("Liczba wyników:", len(wyniki))
print("Pierwszy:", wyniki[0])
print("Ostatni:", wyniki[len(wyniki) - 1])

suma = 0
for w in wyniki:
    suma += w
print("Suma:", suma)
> python cwiczenie_lista_1.py
Lista: [10, 25, 5]
Liczba wyników: 3
Pierwszy: 10
Ostatni: 5
Suma: 40
Zadanie: dodaj czwarty wynik 40 przez append(). Potem stwórz listę kwadraty zawierającą kwadraty liczb 1–5 używając pętli for i in range(1, 6).
✔️ Po tej sekcji powinieneś umieć:
  • Tworzyć listę i dodawać elementy przez append()
  • Używać len() i indeksowania [0]
  • Liczyć sumę elementów pętlą for
2.2 cwiczenie_lista_2.py — listy zagnieżdżone
asteroidy = [
    [100, 50, 40],
    [300, 80, 30],
    [500, 120, 50]
]

print("Ile asteroid:", len(asteroidy))
print("Pierwsza asteroida:", asteroidy[0])
print("X pierwszej:", asteroidy[0][0])
print("Y pierwszej:", asteroidy[0][1])

for ast in asteroidy:
    print("Asteroida X:{} Y:{} rozmiar:{}".format(ast[0], ast[1], ast[2]))
> python cwiczenie_lista_2.py
Ile asteroid: 3
Pierwsza asteroida: [100, 50, 40]
X pierwszej: 100
Y pierwszej: 50
Asteroida X:100 Y:50 rozmiar:40
Asteroida X:300 Y:80 rozmiar:30
Asteroida X:500 Y:120 rozmiar:50
Zadanie: dodaj czwartą asteroidę przez append([700, 200, 60]). Potem zmień y każdej asteroidy o +10 w pętli: ast[1] += 10.
✔️ Po tej sekcji powinieneś umieć:
  • Tworzyć listy zagnieżdżone (lista list)
  • Odwoływać się do elementów przez [i][j]
  • Iterować po liście obiektów pętlą for
2.3 cwiczenie_lista_3.py — for w for: symulacja laserów
lasery = []
lasery.append([400, 500])
lasery.append([100, 480])
lasery.append([700, 490])

print("Lasery przed ruchem:")
for laser in lasery:
    print("  x={} y={}".format(laser[0], laser[1]))

# Symulacja 5 klatek
for klatka in range(5):
    for laser in lasery:
        laser[1] -= 9   # każdy laser leci w górę

print("Lasery po 5 klatkach:")
for laser in lasery:
    print("  x={} y={}".format(laser[0], laser[1]))
Zewnętrzna pętla = klatki animacji. Wewnętrzna = każdy laser. To dokładny schemat z gry!
Zadanie: zmień próg resetu na klatka >= 2 — szybszy laser. Zmień spacja_wcisnięta = False — czy strzał następuje?
✔️ Po tej sekcji powinieneś umieć:
  • Używać and not jako warunku strzału lasera
  • Rozumieć maszynę stanów lasera (wolny → lecący → zniknął)
  • Blokować drugie strzały dopóki pierwszy nie doleci
🚀 Złożone warunki pozwolą ci kontrolować kiedy gracz może strzelać — w następnej lekcji jeden laser poleci przez cały ekran!
G08

Gra: Pierwszy Laser

Uzbrojenie pokładowe gotowe! Podłącz działo laserowe — naciśnij SPACJĘ żeby strzelać!
🟢 Przejście — laser w grze!
Warunek and not laser_aktywny bezpośrednio trafi do kodu gry.
Sprawdź adnotowany kod L07 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Pierwszy Laser
📂 Skopiuj gra_07.py jako gra_08.py
Weź gra_07.py, zapisz kopię jako gra_08.py.
Zmień tytuł na "Asteroidy — Lekcja 8: Pierwszy Laser!"
Cel lekcji
Co: Laser wystrzelony SPACJĘ leci w górę i znika po wyjściu za ekran — można strzelić następny dopiero wtedy.
Jak: Flaga laser_aktywny + dwie zmienne pozycji. Warunek and not laser_aktywny blokuje nowy strzał gdy poprzedni jeszcze leci.
Dlaczego: Jeden laser naraz to celowe ograniczenie — upraszcza kod i daje graczowi wyzwanie celowania. W L09 to rozszerzymy do wielu laserów.
...
zycia = 3

# <-- DODAJEMY zmienne lasera (krok 4.1)

font = pygame.font.SysFont("Arial", 24)

while True:
    ...
    klawisze = pygame.key.get_pressed()
    ...

    # <-- DODAJEMY strzał na SPACJĘ (krok 4.2)
    # <-- DODAJEMY ruch lasera (krok 4.3)
    # <-- DODAJEMY dezaktywację (krok 4.4)

    ...
    ekran.fill((0, 0, 30))
    pygame.draw.rect(...)  # statek
    pygame.draw.rect(...)  # asteroida
    # <-- DODAJEMY rysowanie lasera (krok 4.4)
    ...
4.1 Zmienne lasera — przed pętlą
Pod zmiennymi systemu żyć (przed font = ...) dodaj cztery zmienne lasera:
# --- NOWE: laser ---
laser_x = 0
laser_y = 0
laser_aktywny = False    # czy laser jest w powietrzu?
laser_predkosc = 9       # laser leci szybciej niż statek
laser_aktywny = False — flaga: na starcie nie ma żadnego lasera w powietrzu.
laser_predkosc = 9 — trzy razy szybciej niż statek (predkosc = 5). Szybki laser = łatwiej trafić.
laser_x, laser_y — pozycja lasera (aktualizowane przy strzale).
4.2 Strzał na SPACJĘ — and not laser_aktywny
W pętli gry, po sprawdzaniu granic statku, dodaj blok strzału:
# --- NOWE: strzał laserem ---
# Strzelamy tylko gdy SPACJA wciśnięta I laser NIE jest aktywny
if klawisze[pygame.K_SPACE] and not laser_aktywny:
    laser_aktywny = True
    laser_x = statek_x + statek_szerokosc // 2 - 3   # środek statku
    laser_y = statek_y                                 # na górze statku
and not laser_aktywny — blokuje nowy strzał gdy poprzedni laser jeszcze leci.
statek_x + statek_szerokosc // 2 - 3 — środek statku (42 px) minus połowa lasera (3 px). Laser wychodzi dokładnie ze środka.
❌ Bez flagi — laser resetuje się 60× na sekundę:

# ŹLE — brak "and not laser_aktywny":
if klawisze[pygame.K_SPACE]:
    laser_aktywny = True
    laser_x = statek_x + ...
    laser_y = statek_y   # resetuje pozycję co klatkę!
Efekt: Trzymasz SPACJĘ — laser co klatkę teleportuje się z powrotem na statek i nigdy nie doleci do celu.
✅ Flaga laser_aktywny sprawia że nowy strzał jest możliwy dopiero gdy poprzedni zniknie.
4.3 Ruch lasera w górę i dezaktywacja poza ekranem
Po bloku strzału dodaj ruch lasera i warunek dezaktywacji:
# --- NOWE: poruszamy laserem w górę ---
if laser_aktywny:
    laser_y -= laser_predkosc
    # Gdy laser wylatuje poza górę ekranu — deaktywujemy
    if laser_y < -20:
        laser_aktywny = False
laser_y -= 9 — laser porusza się w górę (y maleje). Minus bo y=0 jest na górze ekranu.
laser_y < -20 — gdy laser w całości wyleciał poza górę: flaga z powrotem na False. Można strzelić następny!
4.4 Rysowanie lasera i stan w HUD
W sekcji rysowania, po rysowaniu statku i asteroidy, dodaj laser i zaktualizuj HUD:
# --- NOWE: rysujemy laser (czerwony prostokąt) ---
if laser_aktywny:
    pygame.draw.rect(ekran, (255, 50, 50), (laser_x, laser_y, 6, 22))

stan_lasera = "AKTYWNY" if laser_aktywny else "gotowy"
napis = font.render("Zycia: {}   SPACJA=strzal   Laser: {}".format(
    zycia, stan_lasera), True, (255, 255, 255))
ekran.blit(napis, (10, 10))
Laser: szerokość 6 px, wysokość 22 px — wąski i długi jak prawdziwy laser. Kolor czerwony (255, 50, 50).
HUD pokazuje stan: "gotowy" gdy można strzelić, "AKTYWNY" gdy laser leci.
🗂️ Pełny kod gra_08.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 8: Pierwszy Laser ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 8: Pierwszy Laser!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

zycia = 3

laser_x = 0
laser_y = 0
laser_aktywny = False
laser_predkosc = 9

font = pygame.font.SysFont("Arial", 24)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]: statek_x += predkosc
    if klawisze[pygame.K_UP]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:  statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    if klawisze[pygame.K_SPACE] and not laser_aktywny:
        laser_aktywny = True
        laser_x = statek_x + statek_szerokosc // 2 - 3
        laser_y = statek_y

    if laser_aktywny:
        laser_y -= laser_predkosc
        if laser_y < -20:
            laser_aktywny = False

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)
    if statek_rect.colliderect(asteroida_rect):
        zycia -= 1
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    if laser_aktywny:
        pygame.draw.rect(ekran, (255, 50, 50), (laser_x, laser_y, 6, 22))

    stan_lasera = "AKTYWNY" if laser_aktywny else "gotowy"
    napis = font.render("Zycia: {}   SPACJA=strzal   Laser: {}".format(
        zycia, stan_lasera), True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień laser_predkosc = 9 na 2 — wolny laser. Czy trudniej trafić asteroidę?
  2. Zmień kolor lasera na żółty (255, 255, 0) — jak wygląda żółty laser?
  3. Zmień rozmiar lasera z (6, 22) na (20, 5) — szeroki i płaski pocisk.
  4. Zmień warunek dezaktywacji z laser_y < -20 na laser_y < WYSOKOSC // 2 — laser znika w połowie ekranu.
Zadania dodatkowe
  • Zmień laser tak żeby startował z lewej strony statku zamiast środka: laser_x = statek_x
  • Dodaj zmienną liczba_strzalow = 0 i zwiększaj ją przy każdym strzale. Wyświetl w HUD.
  • Spraw żeby laser strzelał w dół zamiast w górę: zmień laser_y -= laser_predkosc na += i dostosuj warunek dezaktywacji
  • Dodaj drugi laser strzelający w dół klawiszem Z (pygame.K_z) z osobnymi zmiennymi laser2_*
✔️ Sprawdź działającą grę:
  • SPACJA wystrzeliwuje laser ze środka statku
  • Laser leci w górę i znika po wyjściu za górę ekranu
  • Można strzelić kolejny dopiero gdy poprzedni zniknie — HUD pokazuje "gotowy"/"AKTYWNY"
T10

Python: Operacje na listach (PRO)

Misja: opanuj wyszukiwanie, usuwanie i filtrowanie list — zaraz użyjemy tego żeby usuwać lasery poza ekranem i naliczać punkty za trafienia!
🔵 MODUŁ PYTHON — Operacje na listach (wersja PRO)
1. Wyszukiwanie elementów w liście
Sprawdź czy element jest w liście — operator in:

owoce = ["jabłko", "banan", "gruszka"]

if "banan" in owoce:
    print("Banan jest na liście!")

if "mango" not in owoce:
    print("Nie mamy mango")

Znajdź indeks elementu — .index():

indeks = owoce.index("gruszka")
print("Gruszka jest na pozycji:", indeks)   # 2

W grze:

wrogowie = ["zielony", "czerwony", "fioletowy"]

if "czerwony" in wrogowie:
    print("Uwaga — czerwony wróg!")
2. Usuwanie elementów
Trzy sposoby usuwania:

owoce = ["jabłko", "banan", "gruszka", "mango"]

# 1. Usuń po wartości (remove)
owoce.remove("banan")          # szuka i usuwa pierwsze wystąpienie
print(owoce)   # ["jabłko", "gruszka", "mango"]

# 2. Usuń po indeksie (del)
del owoce[1]                   # usuwa element o indeksie 1
print(owoce)   # ["jabłko", "mango"]

# 3. Usuń i zwróć element (pop)
ostatni = owoce.pop()          # domyślnie usuwa ostatni
print("Usunięto:", ostatni)    # mango
print(owoce)   # ["jabłko"]

pierwszy = owoce.pop(0)        # usuwa element o indeksie 0
print("Usunięto:", pierwszy)   # jabłko

Uwaga: usuwanie elementu podczas iteracji pętlą for to błąd! Zamiast tego — filtrowanie (sekcja 4).
3. Zliczanie elementów — count()
liczby = [1, 2, 3, 2, 2, 5, 2]
print(liczby.count(2))   # 4 — liczba 2 pojawia się 4 razy

akcje = ["strzal", "ruch", "strzal", "strzal", "ruch"]
print(akcje.count("strzal"))   # 3

Zliczanie z warunkiem — ręcznie przez pętlę:

hp_wrogow = [100, 0, 80, 0, 50, 0]

martwi = 0
for hp in hp_wrogow:
    if hp == 0:
        martwi += 1

print("Martwych wrogów:", martwi)   # 3
4. Filtrowanie — budowanie nowej listy
Nigdy nie usuwaj elementów podczas iteracji pętlą! Zamiast tego buduj nową listę z elementami, które chcesz zachować:

liczby = [5, -3, 8, -1, 2, -7, 4]

pozostale = []
for liczba in liczby:
    if liczba > 0:          # warunek zachowania
        pozostale.append(liczba)

liczby = pozostale
print(liczby)               # [5, 8, 2, 4]

W grze — usuwanie laserów poza ekranem:

aktywne = []
for laser in lasery:
    if laser[1] > -30:       # laser wciąż na ekranie
        aktywne.append(laser)
lasery = aktywne
5. Pętle zagnieżdżone na listach obiektów
Plansza gry — wyświetlanie i zliczanie:

plansza = [
    [1, 0, 1, 0],
    [0, 1, 0, 1],
    [1, 1, 0, 0]
]

# Wyświetlanie
for wiersz in plansza:
    for pole in wiersz:
        print(pole, end=" ")
    print()

# Zliczanie jedynek
licznik = 0
for wiersz in plansza:
    for pole in wiersz:
        if pole == 1:
            licznik += 1
print("Jedynek:", licznik)
6. Operacje na listach obiektów gry
Lista asteroid — typowe operacje:

asteroidy = [
    [100, 50, 40],   # x, y, rozmiar
    [300, 80, 30],
    [500, 120, 50]
]

# Przesuń wszystkie w dół
for ast in asteroidy:
    ast[1] += 10

# Usuń asteroidę o rozmiarze 30 (filtrowanie)
asteroidy = [ast for ast in asteroidy if ast[2] != 30]

# Policz duże asteroidy (rozmiar > 40)
duze = 0
for ast in asteroidy:
    if ast[2] > 40:
        duze += 1
print("Duże asteroidy:", duze)
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_szukanie_1.py, cwiczenie_usuwanie_1.py, cwiczenie_zliczanie_1.py, cwiczenie_filtr_1.py, cwiczenie_filtr_2.py, cwiczenie_gra_1.py, cwiczenie_fala_1.py.
2.1 cwiczenie_szukanie_1.py — in i index
kolory = ["czerwony", "zielony", "niebieski", "żółty"]

# Sprawdź czy jest "zielony"
if "zielony" in kolory:
    print("Zielony jest na liście!")

# Znajdź indeks
indeks = kolory.index("niebieski")
print("Niebieski jest na pozycji:", indeks)

# Sprawdź czego nie ma
if "fioletowy" not in kolory:
    print("Fioletowego brak")
> python cwiczenie_szukanie_1.py
Zielony jest na liście!
Niebieski jest na pozycji: 2
Fioletowego brak
Zadanie: sprawdź czy "czerwony" jest w liście i wypisz jego indeks. Napisz funkcję czy_jest(lista, element) zwracającą True/False.
✔️ Po tej sekcji powinieneś umieć:
  • Używać operatora in do sprawdzania obecności elementu
  • Znajdować indeks przez .index()
  • Używać not in do sprawdzania braku elementu
2.2 cwiczenie_usuwanie_1.py — remove, del, pop
punkty = [10, 30, 50, 20, 40]

print("Przed:", punkty)

# Usuń wartość 30
punkty.remove(30)
print("Po remove(30):", punkty)

# Usuń element o indeksie 0
del punkty[0]
print("Po del[0]:", punkty)

# Usuń i zwróć ostatni element
ostatni = punkty.pop()
print("Usunięto pop():", ostatni)
print("Zostało:", punkty)
> python cwiczenie_usuwanie_1.py
Przed: [10, 30, 50, 20, 40]
Po remove(30): [10, 50, 20, 40]
Po del[0]: [50, 20, 40]
Usunięto pop(): 40
Zostało: [50, 20]
Zadanie: usuń z listy [5, 10, 15, 20, 25] wszystkie liczby większe niż 10 używając filtrowania (pętla + nowa lista, nie remove).
✔️ Po tej sekcji powinieneś umieć:
  • Usuwać element przez wartość (.remove())
  • Usuwać przez indeks (del lista[i])
  • Zdejmować ostatni element przez .pop()
2.3 cwiczenie_zliczanie_1.py — count i ręczne zliczanie
strzaly = ["trafil", "pudlo", "trafil", "trafil", "pudlo"]

trafienia = strzaly.count("trafil")
pudla = strzaly.count("pudlo")

print("Trafień:", trafienia)
print("Pudel:", pudla)

hp_wrogow = [100, 0, 80, 0, 50, 0, 30]
zywi = 0
for hp in hp_wrogow:
    if hp > 0:
        zywi += 1
print("Żywych wrogów:", zywi)
> python cwiczenie_zliczanie_1.py
Trafień: 3
Pudel: 2
Żywych wrogów: 4
Zadanie: policz ile liczb parzystych jest w liście [1,2,3,4,5,6,7,8,9,10] używając pętli i warunku n % 2 == 0.
✔️ Po tej sekcji powinieneś umieć:
  • Używać .count() do liczenia wystąpień
  • Zliczać elementy spełniające warunek ręczną pętlą
  • Rozumieć różnicę między .count() a pętlą z if
2.4 cwiczenie_filtr_1.py — filtrowanie liczb
wyniki = [10, -5, 20, -3, 0, 15, -8, 7]

print("Przed:", wyniki, "— elementów:", len(wyniki))

pozostale = []
for w in wyniki:
    if w > 0:
        pozostale.append(w)
wyniki = pozostale

print("Po (tylko > 0):", wyniki, "— elementów:", len(wyniki))
> python cwiczenie_filtr_1.py
Przed: [10, -5, 20, -3, 0, 15, -8, 7] — elementów: 8
Po (tylko > 0): [10, 20, 15, 7] — elementów: 4
Zadanie: zmień warunek na w >= 10. Potem zmień na w != 0. Ile elementów zostaje w każdym przypadku?
✔️ Po tej sekcji powinieneś umieć:
  • Filtrować listę przez budowanie nowej listy z warunkiem
  • Rozumieć dlaczego nie usuwamy elementów podczas iteracji
  • Wzorzec: pozostale = []; for x in lista: if warunek: pozostale.append(x)
2.5 cwiczenie_filtr_2.py — filtrowanie laserów poza ekranem
lasery = [[400, 500], [200, -40], [100, 300], [600, -100], [350, 200]]

print("Przed filtrowaniem:")
for laser in lasery:
    print("  x={} y={}".format(laser[0], laser[1]))

aktywne = []
for laser in lasery:
    if laser[1] > -30:       # laser wciąż widoczny
        aktywne.append(laser)
lasery = aktywne

print("\nPo filtrowaniu (y > -30):")
for laser in lasery:
    print("  x={} y={}".format(laser[0], laser[1]))
Ten dokładny fragment kodu trafia do gry!
Zadanie: zmień na generuj_wrogow(6). Potem przesuń wszystkich wrogów o 5 w prawo: wrog[0] += 5 w pętli.
✔️ Po tej sekcji powinieneś umieć:
  • Pisać funkcję zwracającą listę obiektów gry
  • Używać range do obliczania równomiernego rozmieszczenia
  • Rozumieć wzorzec generuj_fale()
🚀 Listy to fundament zarządzania wieloma obiektami gry — w następnej lekcji statek strzeli wieloma laserami naraz!
G09

Gra: Wiele Laserów

Ulepszenie działa: szybkostrzelność odblokowana! Zastępujemy pojedynczy laser listą — trzymaj SPACJĘ!
🟢 Przejście — wiele laserów w grze!
Zamieniamy pojedynczą zmienną laser_aktywny na listę lasery = []. Nowy laser = nowy element listy.
Sprawdź adnotowany kod L08 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Wiele Laserów
📂 Skopiuj gra_08.py jako gra_09.py
Weź gra_08.py, zapisz kopię jako gra_09.py.
Zmień tytuł na "Asteroidy — Lekcja 9: Wiele Laserow!"
Cel lekcji
Co: Trzymając SPACJĘ gracz strzela serią laserów z cooldownem. Licznik w HUD pokazuje ile laserów jest w powietrzu.
Jak: Lista lasery = [] zastępuje pojedyncze zmienne. append([x, y]) dodaje laser, pętla for porusza wszystkimi naraz.
Dlaczego: Lista skaluje się na dowolną liczbę laserów bez zmiany kodu. W L10 nauczymy się je usuwać gdy wylecą poza ekran.
...
zycia = 3

# <-- ZAMIENIAMY laser_aktywny/laser_x/laser_y na listę (krok 4.1)

font = pygame.font.SysFont("Arial", 22)

while True:
    ...
    teraz = pygame.time.get_ticks()

    # <-- ZAMIENIAMY strzał: append nowego lasera (krok 4.2)
    # <-- ZAMIENIAMY ruch: for laser in lasery (krok 4.3)

    ...

    # <-- ZAMIENIAMY rysowanie: for laser in lasery (krok 4.4)
    # <-- DODAJEMY len(lasery) do HUD (krok 4.5)
    ...
4.1 lasery = [] — zastępujemy trzy zmienne listą
Usuń stare zmienne lasera (laser_x, laser_y, laser_aktywny) i zastąp je listą z cooldownem:
# --- NOWE: lista laserów ---
lasery = []              # pusta lista — każdy laser to [x, y]
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 180   # ms między strzałami
lasery = [] — jedna lista zastępuje trzy osobne zmienne. Każdy laser to mała lista [x, y].
opoznienie_strzalu = 180 — bez cooldownu trzymając SPACJĘ wstawialibyśmy 60 laserów na sekundę!
4.2 Strzał — append nowego lasera do listy
W pętli gry pobierz czas i zastąp stary blok strzału nowym:
teraz = pygame.time.get_ticks()

# --- NOWE: dodajemy nowy laser do listy ---
if klawisze[pygame.K_SPACE] and teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
    nowy_laser = [statek_x + statek_szerokosc // 2 - 3, statek_y]
    lasery.append(nowy_laser)   # append = "dodaj na koniec listy"
    czas_ostatniego_strzalu = teraz
lasery.append(nowy_laser) — dodaje element na koniec listy. Każde naciśnięcie SPACJI (z cooldownem) = jeden nowy laser w liście.
teraz - czas_ostatniego_strzalu > 180 — minęło co najmniej 180 ms od ostatniego strzału.
4.3 Ruch wszystkich laserów — for laser in lasery
Zastąp stary ruch jednego lasera pętlą po liście:
# --- NOWE: poruszamy WSZYSTKIMI laserami ---
for laser in lasery:
    laser[1] -= laser_predkosc   # laser[1] to pozycja Y (drugi element)
Jedna pętla porusza wszystkimi laserami naraz — obojętnie czy jest 1 czy 50.
laser[0] = X, laser[1] = Y. Indeksy zamiast osobnych zmiennych.
4.4 Rysowanie wszystkich laserów
Zastąp stare rysowanie jednego lasera pętlą:
# --- NOWE: rysujemy WSZYSTKIE lasery z listy ---
for laser in lasery:
    pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))
Dokładnie ta sama struktura co pętla ruchu — for laser in lasery działa tak samo dla rysowania jak dla poruszania.
4.5 Licznik laserów w HUD — obserwuj problem!
Zaktualizuj HUD żeby pokazywał liczbę laserów w powietrzu:
napis = font.render("Zycia: {}   Laserow w powietrzu: {}   SPACJA=strzal".format(
    zycia, len(lasery)), True, (255, 255, 255))
ekran.blit(napis, (10, 10))
len(lasery) — liczba laserów aktualnie na liście.
Obserwuj HUD: licznik rośnie bez końca gdy trzymasz SPACJĘ i lasery wylecą poza górę ekranu.
To celowe! W G10 nauczymy się je usuwać — wzorzec filtrowania listy.
❌ Lista bez czyszczenia — pamięć rośnie bez końca:

# ŹLE — lasery nigdy nie są usuwane z listy:
while True:
    if K_SPACE: lasery.append([x, y])   # dodajemy co klatkę
    for laser in lasery: laser[1] -= 9  # poruszamy wszystkimi
    # Laser wylatuje za górę? Zostaje w liście na zawsze!
    # Po 1 minucie: 300+ obiektów, gra zwalnia
Efekt: Po kilku minutach gry lista ma setki "martwych" laserów (niewidocznych, ale istniejących w pamięci). Gra zwalnia, potem zawiesza się.

✅ Rozwiązanie w G10: wzorzec filtrowania listy — nowe_lasery = [l for l in lasery if l[1] > -30]
🗂️ Pełny kod gra_09.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 9: Wiele Laserów — LISTY! ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 9: Wiele Laserow!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

zycia = 3

lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 180

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]: statek_x += predkosc
    if klawisze[pygame.K_UP]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:  statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    teraz = pygame.time.get_ticks()

    if klawisze[pygame.K_SPACE] and teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        nowy_laser = [statek_x + statek_szerokosc // 2 - 3, statek_y]
        lasery.append(nowy_laser)
        czas_ostatniego_strzalu = teraz

    for laser in lasery:
        laser[1] -= laser_predkosc

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        asteroida_rozmiar = random.randint(30, 55)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)
    if statek_rect.colliderect(asteroida_rect):
        zycia -= 1
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    for laser in lasery:
        pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))

    napis = font.render("Zycia: {}   Laserow w powietrzu: {}   SPACJA=strzal".format(
        zycia, len(lasery)), True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień opoznienie_strzalu = 180 na 50 — szybkie strzelanie! Obserwuj licznik.
  2. Zmień na 600 — wolne strzelanie. Ile laserów maks. jest na ekranie jednocześnie?
  3. Trzymaj SPACJĘ przez 10 sekund z predkosc = 1 — ile laserów zbierze się na liście?
  4. Zmień laser_predkosc = 9 na 1 — wolne lasery. Ile masz naraz na ekranie?
Zadania dodatkowe
  • Ogranicz listę do max 5 laserów: przed append sprawdź if len(lasery) < 5
  • Strzelaj dwoma laserami naraz: jeden normalny i jeden przesunięty o +20 px w prawo
  • Dodaj do każdego lasera losowy kolor: laser to [x, y, r] gdzie r = random.randint(100, 255)
  • Wyświetl rekord — max liczbę laserów w powietrzu jednocześnie (if len(lasery) > rekord: rekord = len(lasery))
✔️ Sprawdź działającą grę:
  • Trzymając SPACJĘ strzelasz wieloma laserami z cooldownem 180 ms
  • HUD pokazuje "Laserow w powietrzu: X" — licznik rośnie
  • Wszystkie lasery lecą w górę z tą samą prędkością
T10b

Python: List Comprehension — skrócony zapis filtrowania

Misja: poznaj skrócony zapis pętli tworzącej listę. To "syntactic sugar" Pythona — ten sam efekt co pętla + append, ale w jednej linii.
🔵 MODUŁ PYTHON — List Comprehension
Wyobraź sobie taśmę produkcyjną: wkładasz jabłka (lista wejściowa), każde przechodzi przez maszynę (wyrażenie), a tylko te które spełniają warunek (filtr) trafiają do skrzynki wyjściowej. List comprehension to ten sam proces — zapisany jednym zdaniem.
Teoria — skrócony zapis pętli
Porównaj dwa zapisy — robią dokładnie to samo:

# Stary zapis — pętla + append
dodatnie = []
for x in liczby:
    if x > 0:
        dodatnie.append(x)

# List comprehension — to samo w jednej linii!
dodatnie = [x for x in liczby if x > 0]

Składnia: [co wrzucić for element in lista if warunek]

Trzy formy:

liczby = [1, 2, 3, 4, 5, 6, 7, 8]

# 1. Transformacja (bez warunku)
kwadraty = [x**2 for x in liczby]
# [1, 4, 9, 16, 25, 36, 49, 64]

# 2. Filtrowanie (z warunkiem)
parzyste = [x for x in liczby if x % 2 == 0]
# [2, 4, 6, 8]

# 3. Transformacja + filtr
kwadraty_parzystych = [x**2 for x in liczby if x % 2 == 0]
# [4, 16, 36, 64]
W grze — to właśnie tej linii użyjesz w G10!
Usuwanie laserów które wyleciały poza ekran:

# Stary zapis (poznałeś w T10):
aktywne = []
for laser in lasery:
    if laser[1] > -30:
        aktywne.append(laser)
lasery = aktywne

# List comprehension — identyczny efekt, jedna linia:
lasery = [l for l in lasery if l[1] > -30]

# A nawet krócej z przypisaniem w miejscu:
lasery[:] = [l for l in lasery if l[1] > -30]

lasery[:] to specjalny zapis — modyfikuje listę w miejscu zamiast tworzyć nową referencję. W praktyce dla prostych przypadków różnica jest niewidoczna, ale to konwencja stosowana w kodzie Pygame.
⌨️ Do dzieła — otwórz edytor!
Utwórz plik cwiczenie_comprehension_1.py — wpisuj i uruchamiaj.
2.1 cwiczenie_comprehension_1.py — porównanie zapisów
liczby = [5, -3, 8, -1, 2, -7, 4]

# Stary zapis
pozostale_stary = []
for x in liczby:
    if x > 0:
        pozostale_stary.append(x)

# List comprehension
pozostale_nowy = [x for x in liczby if x > 0]

print("Stary:", pozostale_stary)
print("Nowy: ", pozostale_nowy)
print("Takie same?", pozostale_stary == pozostale_nowy)
> python cwiczenie_comprehension_1.py
Stary: [5, 8, 2, 4]
Nowy: [5, 8, 2, 4]
Takie same? True
✔️ Po tej sekcji powinieneś umieć:
  • Pisać list comprehension z filtrowaniem: [x for x in lista if warunek]
  • Zastępować pętle filtrujące jedną linią list comprehension
  • Rozpoznać lasery[:] = [l for l in lasery if l[1] > -30] w kodzie gry
🚀 List comprehension to jeden z najbardziej rozpoznawalnych wzorców Pythona — teraz rozumiesz kod który widzisz w niemal każdym projekcie Python!
4 Etap 4 — Przeciwnicy

🎯 Cel ETAPU 4 — co tu zbudujesz?

  • Zaprogramować AI wroga — ruch lewo-prawo, automatyczne odbijanie od ścian (wzorzec ±1)
  • Mierzyć czas przez get_ticks() — timer odrodzenia wroga po zestrzeleniu
  • Zarządzać listą wrogów i generować fale — każda następna liczniejsza i szybsza
  • Pisać funkcje obsługujące złożoną logikę: generuj_fale(numer)
🚀 Po tym etapie gra ma fale wrogów — jeden krok do wersji finalnej!
T11

Python: elif — łańcuch warunków

Misja: naucz się if/elif/else — zaraz użyjemy tego żeby laser rozróżniał między trafieniem wroga a asteroidą (różne punkty!).
🔵 MODUŁ PYTHON — elif (łańcuch warunków)
Teoria — if / elif / else
elif to skrót od "else if" — sprawdza kolejny warunek tylko gdy poprzedni był False:

punkty = 75

# Wiele ifów — sprawdza KAŻDY niezależnie
if punkty >= 90: print("bdb")
if punkty >= 70: print("dobry")    # też się wyświetli!
if punkty >= 50: print("dostateczny")  # i to też!

# elif — sprawdza kolejny tylko gdy poprzedni False
if punkty >= 90:
    print("bdb")
elif punkty >= 70:
    print("dobry")          # tylko to się wyświetli
elif punkty >= 50:
    print("dostateczny")
else:
    print("niedostateczny")

Reguła: w łańcuchu if/elif/else wykonuje się dokładnie jeden blok.
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_elif_1.py i cwiczenie_elif_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_elif_1.py — ocena szkolna
punkty = 65    # zmień tę wartość i sprawdzaj wynik

if punkty >= 90:
    ocena = "bdb (5)"
elif punkty >= 75:
    ocena = "dobry (4)"
elif punkty >= 60:
    ocena = "dostateczny (3)"
elif punkty >= 40:
    ocena = "dopuszczający (2)"
else:
    ocena = "niedostateczny (1)"

print("Punkty:", punkty)
print("Ocena:", ocena)
Zadanie: przetestuj z wartościami: 95, 80, 60, 40, 15. Zmień progi tak żeby dobry zaczynał od 70. Co się stanie gdy ustawisz punkty = 90 — bdb czy dobry?
✔️ Po tej sekcji powinieneś umieć:
  • Pisać łańcuch if/elif/else
  • Rozumieć że wykonuje się dokładnie jeden blok
  • Mapować zakresy wartości na kategorie
2.2 cwiczenie_elif_2.py — strefa gry
y = 350    # pozycja Y obiektu na ekranie (0-600)

if y < 150:
    strefa = "NIEBEZPIECZNA (wrogowie)"
elif y < 350:
    strefa = "środkowa (asteroidy)"
elif y < 500:
    strefa = "bezpieczna"
else:
    strefa = "strefa statku gracza"

print("Y =", y, "→ strefa:", strefa)
Dokładnie tak w pełnej grze dzielimy ekran na strefy — wrogowie u góry, gracz na dole.
Zadanie: Napisz list comprehension które: (a) zwraca kwadraty liczb [1,2,3,4,5], (b) filtruje słowa dłuższe niż 4 litery z ["kot","astronauta","dom","Pygame","X"].
✔️ Po tej sekcji powinieneś umieć:
  • Rozumieć składnię: [wyrażenie for x in lista if warunek]
  • Zamieniać pętle filtrujące na list comprehension
  • Rozpoznać lasery[:] = [l for l in lasery if l[1] > -30] w kodzie gry
🚀 List comprehension to jeden z najbardziej rozpoznawalnych wzorców Pythona — teraz rozumiesz kod który widzisz w niemal każdym projekcie Python!
G10

Gra: Usuwanie Laserów i Trafienie Asteroidy

System zarządzania pamięcią wymaga naprawy! Usuń lasery poza ekranem i zaprogramuj system trafień — 10 punktów za każdą asteroidę!
🟢 Przejście — filtrowanie w grze!
Wzorzec "nowa lista bez złych elementów" zastosujesz dwukrotnie: usuwanie laserów poza ekranem i usuwanie laserów które trafiły.
Schemat blokowy tego algorytmu — zapamiętaj go, jest wszędzie w grach:

Schemat — filtrowanie listy laserów
graph TD A([lasery - lista]) --> B["aktywne = lista pusta"] B --> C{"wiecej laserów ?"} C -->|NIE| H([lasery = aktywne]) C -->|TAK| D["wez kolejny laser"] D --> E{"laser.y > -30 ?"} E -->|"TAK - na ekranie"| F["aktywne.append"] E -->|"NIE - poza ekranem"| G["pomin"] F --> C G --> C
Sprawdź adnotowany kod L09 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Usuwanie Laserów i Trafienie Asteroidy
📂 Skopiuj gra_09.py jako gra_10.py
Weź gra_09.py, zapisz kopię jako gra_10.py.
Zmień tytuł na "Asteroidy — Lekcja 10: Trafienie!"
Cel lekcji
Co: Lasery trafione w asteroidę dają +10 pkt i znikają. Lasery poza ekranem też są usuwane — lista nie rośnie w nieskończoność.
Jak: Wzorzec filtrowania: budujemy nową listę tylko z laserów które zachować. Kolizja laser-asteroida przez colliderect().
Dlaczego: Usuwanie elementów podczas iteracji w Pythonie jest niebezpieczne. Nowa lista jest zawsze bezpieczna i czytelna — to technika używana przez cały kurs.
...
zycia = 3
punkty = 0  # <-- DODAJEMY (krok 4.4)

while True:
    ...
    for laser in lasery:
        laser[1] -= laser_predkosc

    # <-- DODAJEMY usuwanie laserów poza ekranem (krok 4.1)

    ...
    asteroida_rect = pygame.Rect(...)

    # <-- DODAJEMY kolizja laser-asteroida (krok 4.2 + 4.3)

    ...
4.1 Filtrowanie laserów poza ekranem
Po pętli ruchu laserów, przed ruchem asteroidy, dodaj blok filtrowania:
# --- NOWE: usuwamy lasery które wyleciały poza ekran ---
nowe_lasery = []
for laser in lasery:
    if laser[1] > -30:   # laser wciąż na ekranie
        nowe_lasery.append(laser)
lasery = nowe_lasery    # zastępujemy starą listę nową
Dlaczego -30? Laser ma wysokość 22 px — przy y = -30 jest całkowicie poza ekranem.
Dlaczego nowa lista zamiast usuwania? Usuwanie elementów podczas iteracji pętlą jest niebezpieczne — pętla może pominąć elementy. Nowa lista zawsze bezpieczna.
4.2 Kolizja laser-asteroida — wzorzec filtrowania
Dodaj punkty = 0 obok zycia = 3. Po ruchu asteroidy dodaj jej Rect i pętlę kolizji:
asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)

# --- NOWE: kolizja laser-asteroida ---
lasery_po = []
for laser in lasery:
    laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
    if laser_rect.colliderect(asteroida_rect):
        punkty += 10
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        asteroida_rozmiar = random.randint(30, 55)
        asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)
    else:
        lasery_po.append(laser)   # laser NIE trafił — zostaje
lasery = lasery_po
Ten sam wzorzec co filtrowanie — laser który trafił nie trafia do nowej listy i znika.
asteroida_rect aktualizujemy po resecie, żeby kolejny laser w pętli sprawdzał nową pozycję.
4.3 HUD z punktami i licznikiem laserów
Zamień HUD na wersję z punktami:
napis = font.render("Zycia: {}   Punkty: {}   Laserow: {}".format(
    zycia, punkty, len(lasery)), True, (255, 255, 255))
ekran.blit(napis, (10, 10))
Teraz len(lasery) nie rośnie bez końca — lista jest czyszczona co klatkę. Obserwuj jak licznik trzyma się w ryzach!
🗂️ Pełny kod gra_10.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 10: Usuwanie Laserów i Trafienie Asteroidy ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Gra 10: Trafienie!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

wrog_x = 120
wrog_y = 80
wrog_szerokosc = 45
wrog_wysokosc = 35

zycia = 3
punkty = 0

lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 180

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    teraz = pygame.time.get_ticks()
    if klawisze[pygame.K_SPACE] and teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz

    for laser in lasery:
        laser[1] -= laser_predkosc

    nowe_lasery = []
    for laser in lasery:
        if laser[1] > -30:
            nowe_lasery.append(laser)
    lasery = nowe_lasery

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)
    wrog_rect = pygame.Rect(wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc)

    if statek_rect.colliderect(asteroida_rect):
        zycia -= 1
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    pozostale = []
    for laser in lasery:
        laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
        if laser_rect.colliderect(wrog_rect):
            punkty += 30   # wróg: 30 pkt (3x więcej niż asteroida)
        elif laser_rect.colliderect(asteroida_rect):
            punkty += 10
            asteroida_y = -asteroida_rozmiar
            asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        else:
            pozostale.append(laser)
    lasery = pozostale

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))
    pygame.draw.rect(ekran, (255, 100, 0), (wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc))

    for laser in lasery:
        pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))

    napis = font.render("Zycia: {}   Punkty: {}   Laserow: {}".format(
        zycia, punkty, len(lasery)), True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Obserwuj Laserow: X w HUD — teraz nie rośnie bez końca! Porównaj z G09.
  2. Zmień punkty += 10 na 25 — więcej punktów za strzał. Ile zdobędziesz w 30 sekund?
  3. Zwiększ asteroida_predkosc = 5 — szybsza asteroida, trudniejszy cel.
  4. Zmień próg filtrowania z -30 na 0 — laser znika szybciej?
Zadania dodatkowe
  • Wyświetl rekord — max punkty zdobyte w jednej grze: if punkty > rekord: rekord = punkty
  • Po każdym trafieniu asteroida przyspiesza o 0.2: asteroida_predkosc += 0.2
  • Dodaj licznik trafień: zmienną trafienia = 0 i zwiększaj przy każdym punkty += 10
  • Pokaż skuteczność: "Celnosc: {}%".format(trafienia * 100 // max(strzaly, 1))
✔️ Sprawdź działającą grę:
  • Lasery znikają po wyjściu poza górę ekranu — licznik w HUD nie rośnie bez końca
  • Trafienie asteroidy laserem daje +10 punktów i resetuje asteroidę
  • HUD pokazuje Zycia, Punkty i Laserow na bieżąco
T12

Python: Kierunek jako mnożnik (±1)

Misja: naucz się wzorca kierunek=±1 — zaraz użyjemy go żeby wróg automatycznie chodził lewo-prawo, odbijając się od ścian!
🔵 MODUŁ PYTHON — Kierunek jako mnożnik (+1 / -1)
Wyobraź sobie wentylator ze wskaźnikiem kierunku: +1 to obrót w prawo, -1 to obrót w lewo. Zamiast dwóch oddzielnych silników (jeden do lewej, drugi do prawej), masz jeden silnik i jeden przełącznik znaku. Mnożysz prędkość przez +1 albo -1 — i silnik sam wie w którą stronę iść.
Teoria — wzorzec kierunek = 1 lub -1
Zamiast pisać dwa osobne warunki dla ruchu w lewo i prawo, używamy jednej zmiennej kierunek:

x = 400
predkosc = 3
kierunek = 1   # 1 = prawo, -1 = lewo

for klatka in range(10):
    x += predkosc * kierunek   # jeden wzór dla obu kierunków!
    print("Klatka {}: x = {}".format(klatka, x))

Zmiana kierunku = zmiana znaku mnożnika:
if x > 800:           # doszedł do prawej krawędzi
    kierunek = -1      # zawróć — teraz odejmuje
if x < 0:             # doszedł do lewej krawędzi
    kierunek = 1       # zawróć — teraz dodaje
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_kierunek_1.py i cwiczenie_kierunek_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_kierunek_1.py — odbijający się punkt
x = 0
predkosc = 50
kierunek = 1
szerokosc = 800

for klatka in range(25):
    x += predkosc * kierunek

    if x >= szerokosc:
        kierunek = -1
        print("Klatka {}: ODBICIE od prawej! x = {}".format(klatka, x))
    elif x <= 0:
        kierunek = 1
        print("Klatka {}: ODBICIE od lewej! x = {}".format(klatka, x))
    else:
        print("Klatka {:2d}: x = {}".format(klatka, x))
> python cwiczenie_kierunek_1.py
Klatka 0: x = 50
Klatka 1: x = 100
...
Klatka 15: ODBICIE od prawej! x = 800
Klatka 16: x = 750
...
Zadanie: zmień predkosc = 50 na 200 — ile razy odbija się w 25 klatkach? Zmień x = 700 — gdzie będzie pierwsze odbicie?
✔️ Po tej sekcji powinieneś umieć:
  • Używać zmiennej kierunku ±1 do ruchu obiektu
  • Odbijać obiekt od ściany przez zmianę znaku kierunku
  • Rozumieć wzorzec x += predkosc * kierunek
2.2 cwiczenie_kierunek_2.py — dwa obiekty, różne startowe kierunki
x1 = 100
x2 = 700
kierunek1 = 1    # startuje w prawo
kierunek2 = -1   # startuje w lewo
predkosc = 60

for klatka in range(15):
    x1 += predkosc * kierunek1
    x2 += predkosc * kierunek2

    if x1 >= 800: kierunek1 = -1
    if x1 <= 0:   kierunek1 = 1
    if x2 >= 800: kierunek2 = -1
    if x2 <= 0:   kierunek2 = 1

    print("Klatka {:2d}: x1={:4d}  x2={:4d}".format(klatka, x1, x2))
Dokładnie ten wzorzec użyjemy dla wrogów w lekcji 14 — każdy wróg ma własny kierunek w liście [x, y, kierunek].
Zadanie: przetestuj z y = 50, 200, 400, 550. Dodaj piątą strefę dla y < 50 — "poza ekranem górnym".
✔️ Po tej sekcji powinieneś umieć:
  • Używać wielu elif do mapowania pozycji Y na strefy
  • Rozumieć że strefa zależy od pierwszego pasującego warunku
  • Stosować elif do kategoryzowania obiektów gry
🚀 elif pozwala laserowi rozróżnić trafienie wroga od asteroidy — zaraz pomarańczowy najeźdźca pojawi się na ekranie!
G11

Gra: Pierwszy Wróg

Flota wroga weszła w przestrzeń! Dodaj pierwszy wrogi statek — pomarańczowy najeźdźca czeka na zestrzelenie.
🟢 Przejście — wróg w grze!

Dodamy pierwszego wroga — statyczny pomarańczowy prostokąt. Ale skoro już jest, warto przemyśleć system punktów:

  Asteroida = 10 punktów — spada sama, nie atakuje, łatwy cel
  Wróg = 30 punktów — za chwilę zacznie się ruszać i atakować, trudniejszy cel

Chcemy żeby gracz bardziej opłacało się polować na wrogów — dlatego 30, a nie 10 jak za asteroidę.

elif pojawi się w sprawdzaniu kolizji — laser najpierw sprawdza wroga, potem asteroidę. Nie może trafić obu naraz.
Sprawdź adnotowany kod L10 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Pierwszy Wróg
📂 Skopiuj gra_10.py jako gra_11.py
Weź gra_10.py, zapisz kopię jako gra_11.py.
Zmień tytuł na "Asteroidy — Lekcja 11: Pierwszy Wrog!"
Cel lekcji
Co: Statyczny pomarańczowy wróg na górze ekranu — zestrzel go za 30 pkt (3× więcej niż asteroida).
Jak: Nowe zmienne (wrog_x, y, szerokosc, wysokosc) + nowy pygame.Rect w kolizji. elif gwarantuje że laser trafia albo wroga, albo asteroidę — nie obu.
Dlaczego: Hierarchia punktów (asteroida=10, wróg=30) nagradza gracza za trudniejsze cele. To projekt gry — trudniejszy wróg = wyższa nagroda.
...
zycia = 3
punkty = 0

# <-- DODAJEMY zmienne wroga (krok 4.1)

lasery = []
...

while True:
    ...
    asteroida_rect = pygame.Rect(...)
    # <-- DODAJEMY wrog_rect (krok 4.1)

    # <-- ZMIENIAMY kolizję laserów: elif dla wroga (krok 4.2 + 4.3)

    ...
    ekran.fill((0, 0, 30))
    pygame.draw.rect(...)  # statek
    pygame.draw.rect(...)  # asteroida
    # <-- DODAJEMY rysowanie wroga (krok 4.2)
    ...
4.1 Zmienne wroga — przed pętlą
Pod zmiennymi punkty = 0 (przed pętlą) dodaj cztery zmienne wroga:
# --- NOWE: wróg ---
wrog_x = 120
wrog_y = 80
wrog_szerokosc = 45
wrog_wysokosc = 35
Wróg opisany tak samo jak statek i asteroida — pozycja (x, y) i rozmiar.
Statyczny na razie — w G12 dostanie AI i zacznie sam chodzić.
4.2 Rysowanie wroga — pomarańczowy prostokąt
W sekcji rysowania, po rysowaniu asteroidy, dodaj:
# --- NOWE: wróg — pomarańczowy prostokąt ---
pygame.draw.rect(ekran, (255, 100, 0), (wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc))
Kolor (255, 100, 0) — pomarańczowy. Wyróżnia się od szarej asteroidy i niebieskiego statku.
Na razie wróg stoi w miejscu — to celowe. Trafienie go laserem daje punkty!
4.3 Kolizja laser-wróg z elif — 20 punktów
W pętli kolizji laserów dodaj wrog_rect i zmień logikę na elif:
wrog_rect = pygame.Rect(wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc)

pozostale = []
for laser in lasery:
    laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
    if laser_rect.colliderect(wrog_rect):
        punkty += 20   # wróg wart 20 pkt (2x więcej niż asteroida)
    elif laser_rect.colliderect(asteroida_rect):
        punkty += 10
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
    else:
        pozostale.append(laser)
lasery = pozostale
elif — laser może trafić tylko jeden obiekt na klatkę. Sprawdzamy najpierw wroga (wyższe punkty), potem asteroidę.
Hierarchia punktów: asteroida=10, wróg=20. Trudniejszy cel = wyższa nagroda.
🗂️ Pełny kod gra_11.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 11: Pierwszy Wróg! ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 11: Pierwszy Wrog!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

wrog_x = 120
wrog_y = 80
wrog_szerokosc = 45
wrog_wysokosc = 35
wrog_predkosc = 2
wrog_kierunek = 1

zycia = 3
punkty = 0

lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 180

font = pygame.font.SysFont("Arial", 22)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    teraz = pygame.time.get_ticks()
    if klawisze[pygame.K_SPACE] and teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz

    for laser in lasery:
        laser[1] -= laser_predkosc

    nowe_lasery = []
    for laser in lasery:
        if laser[1] > -30:
            nowe_lasery.append(laser)
    lasery = nowe_lasery

    # --- NOWE: AI wroga ---
    wrog_x += wrog_predkosc * wrog_kierunek

    if wrog_x > SZEROKOSC - wrog_szerokosc:
        wrog_kierunek = -1
    if wrog_x < 0:
        wrog_kierunek = 1

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)
    wrog_rect = pygame.Rect(wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc)

    if statek_rect.colliderect(asteroida_rect):
        zycia -= 1
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    pozostale = []
    for laser in lasery:
        laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
        if laser_rect.colliderect(wrog_rect):
            punkty += 30   # wróg: 30 pkt (3x więcej niż asteroida)
        elif laser_rect.colliderect(asteroida_rect):
            punkty += 10
            asteroida_y = -asteroida_rozmiar
            asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        else:
            pozostale.append(laser)
    lasery = pozostale

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))
    pygame.draw.rect(ekran, (255, 100, 0), (wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc))

    for laser in lasery:
        pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))

    kierunek_str = ">>>" if wrog_kierunek == 1 else "<<<"
    napis = font.render("Zycia: {}   Punkty: {}   Wrog: {}".format(zycia, punkty, kierunek_str), True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień wrog_x = 120 i wrog_y = 80 na inne wartości — przesuń wroga.
  2. Zmień kolor wroga na fioletowy (200, 0, 200) — wyróżnia się lepiej?
  3. Zmień rozmiar: wrog_szerokosc = 80 — łatwiejszy cel. wrog_szerokosc = 15 — bardzo trudny!
  4. Zmień punkty za wroga z 20 na 50 — ile zdobędziesz w 30 sekund?
Zadania dodatkowe
  • Dodaj drugiego wroga: wrog2_x = 600, wrog2_y = 120 i jego kolizję w kolejnym elif
  • Narysuj napis "WROG" nad wrogiem: ekran.blit(font.render("WROG", True, (255,255,255)), (wrog_x, wrog_y-20))
  • Spraw żeby trafiony wróg przez 0.5s zmienił kolor na biały (wzorzec flagi jak w G07)
  • Ustaw wrog_x = SZEROKOSC // 2 - wrog_szerokosc // 2 — wróg zawsze na środku
✔️ Sprawdź działającą grę:
  • Pomarańczowy wróg pojawia się na ekranie w stałym miejscu
  • Trafienie wroga laserem daje +20 punktów
  • Laser niszczy tylko wroga LUB asteroidę — nie obu naraz (elif)
T13

Python: Timer (pygame.time.get_ticks)

Misja: naucz się mierzenia czasu przez get_ticks() — zaraz użyjemy tego żeby wróg odrodził się po 2 sekundach od zestrzelenia!
🔵 MODUŁ PYTHON — Timer (pygame.time.get_ticks)
Teoria — mierzenie czasu przez porównanie chwil
pygame.time.get_ticks() zwraca czas od uruchomienia programu w milisekundach.

Wzorzec timera — zapisz czas zdarzenia, potem porównuj z bieżącym:

import time

# Symulacja (bez pygame — czas w sekundach * 1000 = ms)
teraz = 0

# Zdarzenie nastąpiło w "chwili 500 ms"
czas_zdarzenia = 500
czas_trwania = 2000   # 2 sekundy = 2000 ms

# Co klatkę sprawdzamy:
for klatka in range(30):
    teraz += 100   # symulujemy upływ 100ms na klatkę

    if teraz > czas_zdarzenia + czas_trwania:
        print("Klatka {}: timer dobiegł końca!".format(klatka))
        break
    elif teraz > czas_zdarzenia:
        print("Klatka {}: timer w toku ({} ms minęło)".format(klatka, teraz - czas_zdarzenia))
    else:
        print("Klatka {}: przed zdarzeniem".format(klatka))
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_timer_1.py i cwiczenie_timer_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_timer_1.py — symulacja odliczania do odrodzenia
zyje = True
czas_odrodzenia = 0
teraz = 0
czas_trwania_smierci = 2000   # 2000 ms = 2 sekundy

for klatka in range(40):
    teraz += 100   # każda klatka = 100ms

    # W klatce 10 — wróg ginie
    if klatka == 10 and zyje:
        zyje = False
        czas_odrodzenia = teraz + czas_trwania_smierci
        print("Klatka {}: wróg POKONANY! Odrodzi się za 2s.".format(klatka))

    # Sprawdzamy odrodzenie
    if not zyje and teraz > czas_odrodzenia:
        zyje = True
        print("Klatka {}: wróg ODRODZONY!".format(klatka))

    stan = "ŻYJE" if zyje else "martwy"
    print("  t={}ms stan={}".format(teraz, stan))
> python cwiczenie_timer_1.py
t=100ms stan=ŻYJE
...
Klatka 10: wróg POKONANY! Odrodzi się za 2s.
t=1100ms stan=martwy
...
Klatka 30: wróg ODRODZONY!
t=3100ms stan=ŻYJE
Zadanie: zmień czas_trwania_smierci na 1000 — szybkie odrodzenie. Na 5000 — powolne. W której klatce nastąpi odrodzenie w każdym przypadku?
✔️ Po tej sekcji powinieneś umieć:
  • Symulować odliczanie przez dodawanie czasu w każdej klatce
  • Zapisywać czas zdarzenia i sprawdzać kiedy minął czas
  • Rozumieć wzorzec: czas_odrodzenia = teraz + opóźnienie
2.2 cwiczenie_timer_2.py — porównanie zapisanego czasu z bieżącym
teraz = 0
czas_zapisany = None   # jeszcze nie zapisaliśmy

for klatka in range(20):
    teraz += 150   # każda klatka = 150ms

    # W klatce 5 zapisujemy czas
    if klatka == 5:
        czas_zapisany = teraz
        print("Klatka {}: ZAPISANO czas = {}ms".format(klatka, czas_zapisany))

    # Sprawdzamy czy minęło 1000ms od zapisu
    if czas_zapisany is not None and teraz - czas_zapisany > 1000:
        print("Klatka {}: minęło >1000ms od zapisu!".format(klatka))
        czas_zapisany = None   # resetuj żeby nie wypisywać co klatkę
Wzorzec teraz - czas_zapisany > 1000 to dokładnie to co używamy w grze dla trafiony (L07) i odrodzenia wroga!
Zadanie: w której klatce x1 i x2 mijają się? Sprawdź czy x1 == x2 w którymś momencie (dodaj if x1 == x2: print("ZDERZENIE!")).
✔️ Po tej sekcji powinieneś umieć:
  • Animować dwa obiekty z przeciwnymi kierunkami
  • Zarządzać niezależnymi zmiennymi kierunku dla każdego obiektu
  • Wykrywać moment spotkania dwóch obiektów
🚀 Wzorzec ±1 to najprostsze AI w grach — za chwilę wróg będzie chodził sam między ścianami!
G12

Gra: AI Przeciwnika

Wróg aktywował silniki! Zaprogramuj sztuczną inteligencję — niech chodzi w lewo i prawo jak w Space Invaders.
🟢 Przejście — AI w grze!
wrog_x += wrog_predkosc * wrog_kierunek trafi bezpośrednio do pętli gry.
Sprawdź adnotowany kod L11 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — AI Przeciwnika
📂 Skopiuj gra_11.py jako gra_12.py
Weź gra_11.py, zapisz kopię jako gra_12.py.
Zmień tytuł na "Asteroidy — Lekcja 12: AI Przeciwnika!"
Cel lekcji
Co: Wróg automatycznie porusza się lewo-prawo, odbijając się od ścian — prosta AI bez żadnych klawiszy.
Jak: wrog_x += wrog_predkosc * wrog_kierunek gdzie wrog_kierunek to 1 (prawo) lub -1 (lewo). Przy ścianie: zmiana znaku na przeciwny.
Dlaczego: Mnożenie przez ±1 to elegancki wzorzec zmiany kierunku — jeden if zamiast dwóch osobnych logik ruchu. Wróg staje się wyzwaniem bo jego pozycja jest nieprzewidywalna.
...
wrog_szerokosc = 45
wrog_wysokosc = 35
# <-- DODAJEMY predkosc i kierunek wroga (krok 4.1)

...

while True:
    ...
    # <-- DODAJEMY ruch AI wroga (krok 4.2)
    # <-- DODAJEMY odbicie od ścian (krok 4.3)
    ...
4.1 Prędkość i kierunek wroga — przed pętlą
Pod zmiennymi wroga (wrog_wysokosc = 35) dodaj prędkość i kierunek:
wrog_predkosc = 2
wrog_kierunek = 1    # 1 = prawo, -1 = lewo
wrog_kierunek = 1 — wróg startuje ruchem w prawo.
Mnożenie przez kierunek (±1) to elegancki wzorzec — jeden wzór dla obu kierunków. Zmiana AI = zmiana znaku.
4.2 Ruch AI — wróg chodzi sam
W pętli gry, po sprawdzaniu klawiszy statku, dodaj automatyczny ruch wroga:
# --- NOWE: AI wroga — automatyczny ruch ---
wrog_x += wrog_predkosc * wrog_kierunek
Jedna linia — i wróg chodzi sam, bez żadnych klawiszy.
kierunek = 1: x += 2 (prawo). kierunek = -1: x -= 2 (lewo).
4.3 Odbicie od ścian — zmiana znaku kierunku
Zaraz po linii ruchu dodaj sprawdzenie krawędzi:
# Odbicie od ścian — zmień kierunek gdy dojdzie do krawędzi
if wrog_x > SZEROKOSC - wrog_szerokosc:
    wrog_kierunek = -1   # zawróć w lewo
if wrog_x < 0:
    wrog_kierunek = 1    # zawróć w prawo
Dlaczego SZEROKOSC - wrog_szerokosc? Bo wrog_x to LEWA krawędź wroga. Prawa krawędź = wrog_x + wrog_szerokosc. Gdy prawa przekroczy 800 — zawracamy.
HUD: zaktualizuj napis żeby pokazywał kierunek: "Wrog: {}" gdzie ">>>" gdy wrog_kierunek == 1.
4.5 Rysowanie: wróg lub BOOM!
Zastąp stałe rysowanie wroga warunkowym:
# --- NOWE: rysujemy wroga lub wybuch ---
if wrog_zyje:
    pygame.draw.rect(ekran, (255, 100, 0), (wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc))
else:
    czas_do_odrodzenia = (wrog_odrodzenie - teraz) // 1000
    wybuch = font_duzy.render("BOOM! +{}s".format(czas_do_odrodzenia + 1), True, (255, 200, 0))
    ekran.blit(wybuch, (wrog_x - 30, wrog_y - 30))
(wrog_odrodzenie - teraz) // 1000 — ile całych sekund pozostało do odrodzenia. Wyświetlamy odliczanie!
🗂️ Pełny kod gra_12.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 12: AI Przeciwnika ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 12: AI Przeciwnika!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

wrog_x = 120
wrog_y = 80
wrog_szerokosc = 45
wrog_wysokosc = 35
wrog_predkosc = 2
wrog_kierunek = 1
wrog_zyje = True
wrog_odrodzenie = 0

zycia = 3
punkty = 0

lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 180

font = pygame.font.SysFont("Arial", 22)
font_duzy = pygame.font.SysFont("Arial", 52)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    teraz = pygame.time.get_ticks()

    if klawisze[pygame.K_SPACE] and teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz

    for laser in lasery:
        laser[1] -= laser_predkosc

    nowe_lasery = []
    for laser in lasery:
        if laser[1] > -30:
            nowe_lasery.append(laser)
    lasery = nowe_lasery

    if wrog_zyje:
        wrog_x += wrog_predkosc * wrog_kierunek
        if wrog_x > SZEROKOSC - wrog_szerokosc:
            wrog_kierunek = -1
        if wrog_x < 0:
            wrog_kierunek = 1
    else:
        if teraz > wrog_odrodzenie:
            wrog_zyje = True
            wrog_x = random.randint(50, SZEROKOSC - wrog_szerokosc - 50)
            wrog_y = random.randint(50, 180)

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)

    if statek_rect.colliderect(asteroida_rect):
        zycia -= 1
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    pozostale_lasery = []
    for laser in lasery:
        laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
        trafil = False

        if wrog_zyje:
            wrog_rect = pygame.Rect(wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc)
            if laser_rect.colliderect(wrog_rect):
                punkty += 20
                wrog_zyje = False
                wrog_odrodzenie = teraz + 2000
                trafil = True

        if laser_rect.colliderect(asteroida_rect) and not trafil:
            punkty += 10
            asteroida_y = -asteroida_rozmiar
            asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
            trafil = True

        if not trafil:
            pozostale_lasery.append(laser)
    lasery = pozostale_lasery

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    if wrog_zyje:
        pygame.draw.rect(ekran, (255, 100, 0), (wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc))
    else:
        czas_do_odrodzenia = (wrog_odrodzenie - teraz) // 1000
        wybuch = font_duzy.render("BOOM! +{}s".format(czas_do_odrodzenia + 1), True, (255, 200, 0))
        ekran.blit(wybuch, (wrog_x - 30, wrog_y - 30))

    for laser in lasery:
        pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))

    napis = font.render("Zycia: {}   Punkty: {}   SPACJA=strzal".format(zycia, punkty), True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień wrog_predkosc = 2 na 6 — szybki wróg! Czy trudniej go zestrzelić?
  2. Zmień wrog_y = 80 na 300 — wróg bliżej gracza. Jak zmienia to trudność?
  3. Zmień wrog_kierunek = 1 na -1 — wróg startuje w lewo.
  4. Sprawdź kierunek w HUD — widać >>> i <<< zmieniające się przy odbiciach.
Zadania dodatkowe
  • Dodaj ruch góra-dół: wrog_kierunek_y = 1 i wrog_y += wrog_predkosc * wrog_kierunek_y z odbiciami od Y=50 i Y=200
  • Wróg przyspiesza o 0.5 co 5 zestrzelonych: dodaj licznik i zwiększaj po każdym trafieniu
  • Ustaw wrog_predkosc = 0 na start — po pierwszym trafieniu ustaw na 3
  • Pokaż prędkość wroga w HUD: "Predkosc: {}".format(wrog_predkosc)
✔️ Sprawdź działającą grę:
  • Wróg porusza się automatycznie lewo-prawo bez klawiszy
  • Przy krawędziach ekranu zmienia kierunek
  • Ruch jest płynny i ciągły
T14

Python: Funkcje (def)

Misja: naucz się definiowania funkcji i wzorca generowania list — zaraz stworzymy system fal wrogów!
🔵 MODUŁ PYTHON — Funkcje (def)
Teoria — def, parametry, return
Funkcja to nazwany blok kodu który możemy wywołać wielokrotnie:

def przywitaj(imie):           # def = definicja, imie = parametr
    napis = "Cześć, " + imie + "!"
    return napis               # return = zwróć wynik

wynik = przywitaj("Ania")      # wywołanie funkcji
print(wynik)                   # "Cześć, Ania!"
print(przywitaj("Kacper"))     # "Cześć, Kacper!"

Funkcja która zwraca listę — przydatna do generowania fal wrogów:
def generuj_pozycje(ile):
    pozycje = []
    for i in range(ile):
        pozycje.append(i * 100)
    return pozycje

fala1 = generuj_pozycje(3)   # [0, 100, 200]
fala2 = generuj_pozycje(5)   # [0, 100, 200, 300, 400]
print(fala1)
print(fala2)
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_def_1.py i cwiczenie_def_2.py — wpisuj i uruchamiaj każdy z osobna.
2.1 cwiczenie_def_1.py — prosta funkcja z return
def oblicz_wynik(trafienia, bonus):
    return trafienia * 10 + bonus

wynik1 = oblicz_wynik(5, 50)
wynik2 = oblicz_wynik(3, 0)
wynik3 = oblicz_wynik(10, 100)

print("5 trafień + bonus 50 =", wynik1)
print("3 trafienia + bonus 0 =", wynik2)
print("10 trafień + bonus 100 =", wynik3)

# Możemy wywoływać wielokrotnie z różnymi argumentami!
for trafienia in range(1, 6):
    print("  {} trafień → {}".format(trafienia, oblicz_wynik(trafienia, 25)))
> python cwiczenie_def_1.py
5 trafień + bonus 50 = 100
3 trafienia + bonus 0 = 30
10 trafień + bonus 100 = 200
1 trafień → 35
2 trafień → 45
3 trafień → 55
4 trafień → 65
5 trafień → 75
Zadanie: dodaj trzeci parametr mnoznik i zmień wzór na trafienia * mnoznik + bonus. Wywołaj z mnoznik=20 i mnoznik=5.
✔️ Po tej sekcji powinieneś umieć:
  • Definiować funkcję z parametrami i return
  • Wywoływać funkcję wielokrotnie z różnymi argumentami
  • Rozumieć różnicę między definicją a wywołaniem
2.2 cwiczenie_def_2.py — generuj_fale() zwracająca listę
SZEROKOSC = 800
wrog_szerokosc = 44

def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer   # fala 1 = 4 wrogów, fala 2 = 5 itd.
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        x = odst * (i + 1) - wrog_szerokosc // 2
        y = 40 + (i % 2) * 35   # naprzemienne wysokości
        nowi.append([x, y, 1])  # [x, y, kierunek]
    return nowi

fala1 = generuj_fale(1)
fala2 = generuj_fale(2)
fala3 = generuj_fale(3)

print("Fala 1 ({} wrogów):".format(len(fala1)))
for w in fala1:
    print("  x={} y={} kier={}".format(w[0], w[1], w[2]))

print("\nFala 2 ({} wrogów):".format(len(fala2)))
print("Fala 3 ({} wrogów):".format(len(fala3)))
To jest dokładna funkcja z lekcji 14! Wywołujemy ją raz na początek i potem po każdym pokonaniu całej fali.
Zadanie: zmień próg z 1000 na 600. W której klatce teraz pojawia się komunikat? Zmień krok czasu na 100 ms — ile klatek potrzeba na 600ms?
✔️ Po tej sekcji powinieneś umieć:
  • Zapisywać moment zdarzenia i porównywać z bieżącym czasem
  • Implementować cooldown przez teraz - czas_zapisany > próg
  • Stosować wzorzec timera do odrodzenia, nietykalności, cooldown strzału
🚀 Timery przez get_ticks() to podstawa opóźnień w grach — za chwilę wróg odrodzi się dokładnie 2 sekundy po zestrzeleniu!
G13

Gra: Zestrzelenie Wroga

Wróg ma pancerz — ale jeden celny strzał go pokonuje! Zaprogramuj system zniszczenia i odrodzenia wroga.
🟢 Przejście — timer odrodzenia w grze!
wrog_odrodzenie = teraz + 2000 zapisuje "kiedy odrodzić", if teraz > wrog_odrodzenie sprawdza czy czas nadszedł.
Sprawdź adnotowany kod L12 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Zestrzelenie Wroga
📂 Skopiuj gra_12.py jako gra_13.py
Weź gra_12.py, zapisz kopię jako gra_13.py.
Zmień tytuł na "Asteroidy — Lekcja 13: Zestrzel Wroga!"
Cel lekcji
Co: Trafiony wróg pokazuje "BOOM!", znika i po 2 sekundach odradza się w losowym miejscu.
Jak: Flaga wrog_zyje kontroluje stan. Timer: zapisujemy pygame.time.get_ticks() przy trafieniu i sprawdzamy czy minęły 2000 ms.
Dlaczego: Timer przez get_ticks() to podstawa wszelkich opóźnionych akcji w grach — nietykalność, animacje wybuchu, odliczanie. Jeden wzorzec, tysiące zastosowań.
...
wrog_kierunek = 1
# <-- DODAJEMY wrog_zyje i wrog_odrodzenie (krok 4.1)

...
font_duzy = pygame.font.SysFont("Arial", 52)  # <-- DODAJEMY

while True:
    ...
    teraz = pygame.time.get_ticks()

    # <-- ZMIENIAMY ruch wroga: tylko gdy zyje (krok 4.2)
    # <-- ZMIENIAMY kolizję lasera: ustaw wrog_zyje = False (krok 4.3)
    # <-- DODAJEMY sprawdzenie odrodzenia (krok 4.4)
    # <-- ZMIENIAMY rysowanie wroga: if wrog_zyje else BOOM (krok 4.5)
    ...
4.1 Flaga wrog_zyje i timer odrodzenia — przed pętlą
Pod zmiennymi wrog_kierunek = 1 dodaj flagę i timer, a przed pętlą dodaj drugi font:
wrog_zyje = True       # czy wróg żyje?
wrog_odrodzenie = 0    # kiedy się odrodzi (w ms)

font_duzy = pygame.font.SysFont("Arial", 52)
wrog_zyje = True — flaga logiczna: na starcie wróg żyje.
wrog_odrodzenie = 0 — zapamiętamy tu chwilę "2 sekundy od teraz". Przy odrodzeniu: teraz > wrog_odrodzenie → True.
font_duzy — do wyświetlania napisu "BOOM!" po trafieniu.
4.2 Ruch wroga tylko gdy żyje + sprawdzenie odrodzenia
Owiń istniejący blok ruchu AI w if wrog_zyje i dodaj gałąź else z odrodzeniem:
# --- ZMIENIAMY: ruch wroga tylko gdy żyje ---
if wrog_zyje:
    wrog_x += wrog_predkosc * wrog_kierunek
    if wrog_x > SZEROKOSC - wrog_szerokosc:
        wrog_kierunek = -1
    if wrog_x < 0:
        wrog_kierunek = 1
else:
    # Sprawdzamy czy czas odrodzenia minął
    if teraz > wrog_odrodzenie:
        wrog_zyje = True
        wrog_x = random.randint(50, SZEROKOSC - wrog_szerokosc - 50)
        wrog_y = random.randint(50, 180)
Gdy wróg nie żyje: sprawdzamy czy minęły 2 sekundy. Jeśli tak — odradzamy go w losowym miejscu.
teraz > wrog_odrodzenie — właśnie tego wzorca użyliśmy dla trafiony w G07!
4.3 Kolizja laser-wróg z flagą wrog_zyje
W pętli kolizji laserów dodaj warunek and wrog_zyje i ustaw flagę przy trafieniu:
pozostale = []
for laser in lasery:
    laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
    trafil_wroga = False

    if wrog_zyje:
        wrog_rect = pygame.Rect(wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc)
        if laser_rect.colliderect(wrog_rect):
            punkty += 20
            wrog_zyje = False
            wrog_odrodzenie = teraz + 2000    # odrodzi się za 2 sekundy
            trafil_wroga = True

    if laser_rect.colliderect(asteroida_rect) and not trafil_wroga:
        punkty += 10
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        trafil_wroga = True

    if not trafil_wroga:
        pozostale.append(laser)
lasery = pozostale
wrog_odrodzenie = teraz + 2000 — zapisujemy "2 sekundy od teraz" w ms. Potem porównujemy z teraz co klatkę.
Flaga trafil_wroga — laser może trafić tylko jeden cel na klatkę.
4.4 Rysowanie warunkowe — wróg lub BOOM!
Zastąp stałe rysowanie wroga blokiem warunkowym:
# --- ZMIENIAMY: rysujemy wroga lub wybuch ---
if wrog_zyje:
    pygame.draw.rect(ekran, (255, 100, 0), (wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc))
else:
    czas_do_odrodzenia = (wrog_odrodzenie - teraz) // 1000
    wybuch = font_duzy.render("BOOM! +{}s".format(czas_do_odrodzenia + 1), True, (255, 200, 0))
    ekran.blit(wybuch, (wrog_x - 30, wrog_y - 30))
(wrog_odrodzenie - teraz) // 1000 — ile całych sekund zostało do odrodzenia. Wyświetlamy odliczanie: BOOM! +2s → BOOM! +1s → wróg wraca!
🗂️ Pełny kod gra_13.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 13: Zestrzelenie Wroga! ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 13: Zestrzel Wroga!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

wrog_x = 120
wrog_y = 80
wrog_szerokosc = 45
wrog_wysokosc = 35
wrog_predkosc = 2
wrog_kierunek = 1
wrog_zyje = True
wrog_odrodzenie = 0

zycia = 3
punkty = 0

lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 180

font = pygame.font.SysFont("Arial", 22)
font_duzy = pygame.font.SysFont("Arial", 52)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]: statek_x += predkosc
    if klawisze[pygame.K_UP]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:  statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    teraz = pygame.time.get_ticks()

    if klawisze[pygame.K_SPACE] and teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz

    for laser in lasery:
        laser[1] -= laser_predkosc

    nowe_lasery = []
    for laser in lasery:
        if laser[1] > -30:
            nowe_lasery.append(laser)
    lasery = nowe_lasery

    if wrog_zyje:
        wrog_x += wrog_predkosc * wrog_kierunek
        if wrog_x > SZEROKOSC - wrog_szerokosc:
            wrog_kierunek = -1
        if wrog_x < 0:
            wrog_kierunek = 1
    else:
        if teraz > wrog_odrodzenie:
            wrog_zyje = True
            wrog_x = random.randint(50, SZEROKOSC - wrog_szerokosc - 50)
            wrog_y = random.randint(50, 180)

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)

    if statek_rect.colliderect(asteroida_rect):
        zycia -= 1
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    pozostale = []
    for laser in lasery:
        laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
        trafil_wroga = False

        if wrog_zyje:
            wrog_rect = pygame.Rect(wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc)
            if laser_rect.colliderect(wrog_rect):
                punkty += 20
                wrog_zyje = False
                wrog_odrodzenie = teraz + 2000
                trafil_wroga = True

        if laser_rect.colliderect(asteroida_rect) and not trafil_wroga:
            punkty += 10
            asteroida_y = -asteroida_rozmiar
            asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
            trafil_wroga = True

        if not trafil_wroga:
            pozostale.append(laser)
    lasery = pozostale

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160), (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    if wrog_zyje:
        pygame.draw.rect(ekran, (255, 100, 0), (wrog_x, wrog_y, wrog_szerokosc, wrog_wysokosc))
    else:
        czas_do_odrodzenia = (wrog_odrodzenie - teraz) // 1000
        wybuch = font_duzy.render("BOOM! +{}s".format(czas_do_odrodzenia + 1), True, (255, 200, 0))
        ekran.blit(wybuch, (wrog_x - 30, wrog_y - 30))

    for laser in lasery:
        pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))

    napis = font.render("Zycia: {}   Punkty: {}   SPACJA=strzal".format(zycia, punkty), True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień + 2000 na + 500 — krótkie odrodzenie. Trudniejsza gra?
  2. Zmień + 2000 na + 5000 — długie 5 sekund. Obserwuj odliczanie BOOM! +4s → +3s → +2s → +1s.
  3. Daj więcej punktów za wroga: punkty += 50. Jak zmienia to strategię?
  4. Zmień zakres odrodzenia na stały środek: wrog_x = SZEROKOSC // 2 zamiast losowego.
Zadania dodatkowe
  • Wróg przyspiesza po każdym odrodzeniu: wrog_predkosc += 0.5 wewnątrz bloku odrodzenia
  • Dodaj licznik zestrzelonych wrogów: zmienną zestrzeleni = 0 i zestrzeleni += 1 przy każdym trafieniu
  • Flash tła po trafieniu wroga — użyj wzorca flagi i timera z G07 (czerwone tło przez 200ms)
  • Wyświetl czas do odrodzenia już w HUD, nie tylko jako BOOM!: "Wrog: {}s"
✔️ Sprawdź działającą grę:
  • Trafiony wróg znika i pojawia się "BOOM! +2s" z odliczaniem
  • Po 2 sekundach wróg odradza się w nowym losowym miejscu
  • Punkty rosną: +20 za wroga, +10 za asteroidę
  • Wróg porusza się tylko gdy żyje — gdy martwy, stoi w miejscu BOOM!
G14

Gra: Wielu Wrogów — Fale!

Flota wroga nadchodzi falami! Użyj listy wrogów i pętli zagnieżdżonych — oto prawdziwa gra kosmiczna!
🟢 Przejście — wrogowie jako lista + funkcja generująca fale!
Zamieniamy jednego wroga na listę wrogowie = [] i funkcję def generuj_fale(numer).
Sprawdź adnotowany kod L13 — strzałki pokazują gdzie lądują zmiany:
🚀 PYGAME — Wielu Wrogów — Fale!
📂 Skopiuj gra_13.py jako gra_14.py
Weź gra_13.py, zapisz kopię jako gra_14.py.
Zmień tytuł na "Asteroidy — Lekcja 14: Fala Wrogow!"
Cel lekcji
Co: Lista wrogów zamiast jednego — pokonaj wszystkich i przychodzi nowa fala, liczniejsza i szybsza.
Jak: wrogowie = [[x, y, kierunek], ...] + funkcja generuj_fale(numer) tworzy formację. Pętle zagnieżdżone sprawdzają każdy laser vs każdy wróg.
Dlaczego: Lista wrogów skaluje się łatwo — dodaj element, gra widzi nowego wroga. Funkcja generuj_fale() kapsułkuje logikę tworzenia formacji — to wzorzec używany w finałowej grze.
...
# <-- ZAMIENIAMY asteroida na lista asteroidy[] (krok 4.1)
# <-- ZAMIENIAMY wrog_x/y/itd na wrogowie=[] (krok 4.1)

# <-- DODAJEMY def generuj_fale(numer) (krok 4.2)

wrogowie = generuj_fale(numer_fali)
...

while True:
    ...
    # <-- DODAJEMY ruch wszystkich wrogów (krok 4.3)
    # <-- DODAJEMY ruch asteroid z listy (krok 4.3)

    # <-- ZMIENIAMY kolizję: pętla zagnieżdżona laser-wrogowie (krok 4.4)

    # <-- DODAJEMY sprawdzenie końca fali (krok 4.5)
    ...
🔄 Refaktoryzacja — skąd przychodzimy i dokąd idziemy
W G13 zarządzałeś jednym wrogiem przez trzy osobne zmienne:

# G13 — jeden wróg = trzy zmienne
wrog_x = SZEROKOSC // 2
wrog_y = 60
wrog_kierunek = 1

# Ruch
wrog_x += wrog_predkosc * wrog_kierunek
if wrog_x > SZEROKOSC - wrog_szerokosc: wrog_kierunek = -1
if wrog_x < 0: wrog_kierunek = 1

Problem: Chcemy 8, 16 wrogów — trzy zmienne × 16 = 48 zmiennych. Niemożliwe do zarządzania!

Rozwiązanie — lista zamiast zmiennych:

# G14 — wielu wrogów = lista list [x, y, kierunek]
wrogowie = [[100, 60, 1], [200, 60, 1], [300, 60, -1]]

# Ruch WSZYSTKICH — jedna pętla zastępuje 3 zmienne × N wrogów
for wrog in wrogowie:
    wrog[0] += wrog_predkosc * wrog[2]
    if wrog[0] > SZEROKOSC - wrog_szerokosc: wrog[2] = -1
    if wrog[0] < 0: wrog[2] = 1

I funkcja generuj_fale(numer): automatycznie rozkłada wrogów równomiernie, każda fala liczniejsza:

wrogowie = generuj_fale(1)   # fala 1: 4 wrogów
wrogowie = generuj_fale(2)   # fala 2: 5 wrogów, trudniej
wrogowie = generuj_fale(5)   # fala 5: 8 wrogów!
Teraz krok po kroku — zamieniamy G13 na G14:
4.1 wrogowie=[] i asteroidy=[] — listy zamiast pojedynczych zmiennych
Zastąp pojedyncze zmienne wróg/asteroida listami:
# Asteroidy jako lista — każda: [x, y, rozmiar]
asteroidy = []
for i in range(2):
    asteroidy.append([
        random.randint(0, SZEROKOSC - 50),
        random.randint(-400, -30),
        random.randint(28, 55)
    ])

# Wrogowie jako lista — każdy: [x, y, kierunek]
wrog_szerokosc = 44
wrog_wysokosc = 32
wrog_predkosc = 2
numer_fali = 1
Każdy wróg to [x, y, kierunek]wrog[0]=x, wrog[1]=y, wrog[2]=kierunek.
4.2 def generuj_fale(numer) — funkcja zwracająca listę wrogów
Przed pętlą gry, przed wrogowie = ..., zdefiniuj funkcję:
def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer   # każda fala ma więcej wrogów
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        x = odst * (i + 1) - wrog_szerokosc // 2
        y = 40 + (i % 2) * 35   # co drugi wróg trochę niżej
        nowi.append([x, y, 1])  # [x, y, kierunek=prawo]
    return nowi

wrogowie = generuj_fale(numer_fali)
def musi być przed pierwszym wywołaniem! Równomierne rozmieszczenie: SZEROKOSC // (liczba+1) dzieli ekran na równe odcinki.
4.3 Ruch wszystkich wrogów i asteroid — pętla for
Zastąp stary ruch jednego wroga nową pętlą dla wszystkich:
# --- NOWE: ruch wszystkich wrogów ---
for wrog in wrogowie:
    wrog[0] += wrog_predkosc * wrog[2]
    if wrog[0] > SZEROKOSC - wrog_szerokosc:
        wrog[2] = -1
    if wrog[0] < 0:
        wrog[2] = 1

# --- NOWE: ruch wszystkich asteroid ---
for ast in asteroidy:
    ast[1] += 3
    if ast[1] > WYSOKOSC:
        ast[1] = random.randint(-300, -30)
        ast[0] = random.randint(0, SZEROKOSC - ast[2])
4.4 Kolizja laser-wrogowie — pętle zagnieżdżone
Zastąp stary blok kolizji nowym z pętlą zagnieżdżoną:
# --- NOWE: lasery vs wrogowie (pętle zagnieżdżone) ---
pozostale_lasery = []
trafieni_idx = []

for laser in lasery:
    laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
    trafil = False

    for j in range(len(wrogowie)):
        if j not in trafieni_idx:
            wrog_rect = pygame.Rect(wrogowie[j][0], wrogowie[j][1], wrog_szerokosc, wrog_wysokosc)
            if laser_rect.colliderect(wrog_rect):
                trafieni_idx.append(j)
                punkty += 20
                trafil = True
                break

    if not trafil:
        for ast in asteroidy:
            ast_rect = pygame.Rect(ast[0], ast[1], ast[2], ast[2])
            if laser_rect.colliderect(ast_rect) and not trafil:
                punkty += 10
                ast[1] = random.randint(-300, -30)
                trafil = True

    if not trafil:
        pozostale_lasery.append(laser)
lasery = pozostale_lasery

# Usuwamy trafionych wrogów (od końca — indeksy się nie przesuną)
for j in sorted(trafieni_idx, reverse=True):
    wrogowie.pop(j)
Pętla zagnieżdżona: dla każdego lasera sprawdzamy każdego wroga. trafieni_idx zapobiega trafieniu tego samego wroga dwoma laserami w jednej klatce.
4.5 Nowa fala gdy wszyscy pokonani
Po bloku kolizji dodaj sprawdzenie końca fali:
# --- NOWE: gdy wszyscy wrogowie pokonani — nowa fala! ---
if len(wrogowie) == 0:
    numer_fali += 1
    punkty += 50   # bonus za ukończenie fali
    wrogowie = generuj_fale(numer_fali)
    wrog_predkosc = min(2 + numer_fali, 7)   # z każdą falą szybciej (max 7)
min(2 + numer_fali, 7) — prędkość rośnie z falami, ale nigdy nie przekroczy 7. Fala 1: 3, Fala 5: 7, Fala 10: 7.
🗂️ Pełny kod gra_14.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 14: Wielu Wrogów — Fale! ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 14: Fala Wrogow!")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroidy = []
for i in range(2):
    asteroidy.append([
        random.randint(0, SZEROKOSC - 50),
        random.randint(-400, -30),
        random.randint(28, 55)
    ])

wrog_szerokosc = 44
wrog_wysokosc = 32
wrog_predkosc = 2

numer_fali = 1

def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        x = odst * (i + 1) - wrog_szerokosc // 2
        y = 40 + (i % 2) * 35
        nowi.append([x, y, 1])
    return nowi

wrogowie = generuj_fale(numer_fali)

zycia = 3
punkty = 0

lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 160

font = pygame.font.SysFont("Arial", 22)
font_duzy = pygame.font.SysFont("Arial", 52)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc

    teraz = pygame.time.get_ticks()
    if klawisze[pygame.K_SPACE] and teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz

    for laser in lasery:
        laser[1] -= laser_predkosc

    nowe_lasery = []
    for laser in lasery:
        if laser[1] > -30:
            nowe_lasery.append(laser)
    lasery = nowe_lasery

    for wrog in wrogowie:
        wrog[0] += wrog_predkosc * wrog[2]
        if wrog[0] > SZEROKOSC - wrog_szerokosc:
            wrog[2] = -1
        if wrog[0] < 0:
            wrog[2] = 1

    for ast in asteroidy:
        ast[1] += 3
        if ast[1] > WYSOKOSC:
            ast[1] = random.randint(-300, -30)
            ast[0] = random.randint(0, SZEROKOSC - ast[2])

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)

    for ast in asteroidy:
        ast_rect = pygame.Rect(ast[0], ast[1], ast[2], ast[2])
        if statek_rect.colliderect(ast_rect):
            zycia -= 1
            ast[1] = random.randint(-300, -30)

    pozostale_lasery = []
    trafieni_idx = []

    for laser in lasery:
        laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
        trafil = False

        for j in range(len(wrogowie)):
            if j not in trafieni_idx:
                wrog_rect = pygame.Rect(wrogowie[j][0], wrogowie[j][1], wrog_szerokosc, wrog_wysokosc)
                if laser_rect.colliderect(wrog_rect):
                    trafieni_idx.append(j)
                    punkty += 20
                    trafil = True
                    break

        if not trafil:
            for ast in asteroidy:
                ast_rect = pygame.Rect(ast[0], ast[1], ast[2], ast[2])
                if laser_rect.colliderect(ast_rect) and not trafil:
                    punkty += 10
                    ast[1] = random.randint(-300, -30)
                    ast[0] = random.randint(0, SZEROKOSC - ast[2])
                    trafil = True

        if not trafil:
            pozostale_lasery.append(laser)
    lasery = pozostale_lasery

    for j in sorted(trafieni_idx, reverse=True):
        wrogowie.pop(j)

    if len(wrogowie) == 0:
        numer_fali += 1
        punkty += 50
        wrogowie = generuj_fale(numer_fali)
        wrog_predkosc = min(2 + numer_fali, 7)

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    for ast in asteroidy:
        pygame.draw.rect(ekran, (160, 160, 160), (ast[0], ast[1], ast[2], ast[2]))

    for wrog in wrogowie:
        pygame.draw.rect(ekran, (255, 100, 0), (wrog[0], wrog[1], wrog_szerokosc, wrog_wysokosc))

    for laser in lasery:
        pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))

    napis = font.render("Zycia: {}   Punkty: {}   Fala: {}   Wrogow: {}".format(
        zycia, punkty, numer_fali, len(wrogowie)), True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    if zycia <= 0:
        ekran.fill((0, 0, 0))
        ekran.blit(font_duzy.render("KONIEC GRY!", True, (255, 50, 50)), (240, 230))
        ekran.blit(font.render("Wynik koncowy: {}  Fala: {}".format(punkty, numer_fali), True, (255, 255, 255)), (260, 310))
        pygame.display.flip()
        pygame.time.wait(4000)
        pygame.quit()
        exit()

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień 3 + numer na 2 + numer — mniej wrogów w każdej fali. Ile fal możesz przeżyć?
  2. Zmień bonus za falę z 50 na 100 — wyższy wynik za ukończenie fali.
  3. Zmień min(2 + numer_fali, 7) na min(1 + numer_fali, 10) — inne tempo przyspieszania.
  4. Dodaj trzecią asteroidę do listy asteroidy dopisując kolejny append przed pętlą.
Zadania dodatkowe
  • Zmodyfikuj generuj_fale żeby wrogowie w parzystych falach startowali z kierunkiem -1 (w lewo)
  • Dodaj wyświetlanie "FALA POKONANA! +50 PKT" przez 1 sekundę po ukończeniu fali — użyj timerów z lekcji 13
  • Dodaj wrogów w drugiej linii: zmodyfikuj funkcję żeby tworzyła dwa rzędy wrogów dla fal > 2
  • Stwórz własną funkcję def sprawdz_rekord(punkty, rekord) która zwraca nowy rekord i wyświetl go w KONIEC GRY
✔️ Sprawdź działającą grę:
  • Formacja wrogów na ekranie
  • Wszyscy poruszają się razem
  • Pokonanie wszystkich wrogów wywołuje nową falę
  • Każda fala ma więcej wrogów
5 Etap 5 — Droga do Finału

🎯 Cel ETAPU 5 — co tu zbudujesz?

  • Używać global w funkcjach które muszą zmieniać stan gry
  • Refaktoryzować — rozbić 80-liniową pętlę na czytelne, jednozadaniowe funkcje
  • Zbudować maszynę stanów: ekran startowy → gra → game over → restart
  • Dodać efekty wizualne — wielokątny statek, kamieniste asteroidy, gwiazdki w tle, rekord
🚀 Po tym etapie uruchomisz pełną grę — ETAP 6 to gotowa wersja finalna!
T15

Python: Słowo kluczowe global

Misja: opanuj słowo global — zaraz użyjemy go żeby funkcje mogły zmieniać zmienne gry!
🔵 MODUŁ PYTHON — Słowo kluczowe global
Teoria — dlaczego funkcja nie widzi zmiennych zewnętrznych?
Funkcja ma własny obszar nazw. Gdy piszesz x = 5 wewnątrz funkcji, tworzysz lokalną zmienną — nie zmienisz tej zewnętrznej.

x = 100

def zmien():
    x = 999      # tworzy LOKALNĄ zmienną x, zewnętrzna bez zmian!
    print("w funkcji:", x)

zmien()
print("poza funkcja:", x)   # nadal 100!

Słowo global mówi funkcji: „ta zmienna pochodzi spoza mnie":
x = 100

def zmien():
    global x
    x = 999      # teraz modyfikujemy ZEWNĘTRZNĄ zmienną
    print("w funkcji:", x)

zmien()
print("poza funkcja:", x)   # 999!
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_global_1.py i cwiczenie_global_2.py.
2.1 cwiczenie_global_1.py — bez i z global
zycia = 3

def strac_zycie():
    global zycia
    zycia -= 1
    print("Pozostalo zyc:", zycia)

strac_zycie()
strac_zycie()
print("Koniec:", zycia)
> python cwiczenie_global_1.py
Pozostalo zyc: 2
Pozostalo zyc: 1
Koniec: 1
Zadanie: usuń linię global zycia i uruchom — co się stanie? Przywróć ją.
✔️ Po tej sekcji powinieneś umieć:
  • Używać słowa global aby modyfikować zmienną zewnętrzną
  • Rozumieć różnicę między zmienną lokalną a globalną
  • Wiedzieć kiedy funkcja potrzebuje global
2.2 cwiczenie_global_2.py — kilka zmiennych global
punkty = 0
numer_fali = 1

def nowa_fala():
    global punkty, numer_fali
    punkty += 50
    numer_fali += 1
    print("Fala {} | Punkty: {}".format(numer_fali, punkty))

nowa_fala()
nowa_fala()
nowa_fala()
> python cwiczenie_global_2.py
Fala 2 | Punkty: 50
Fala 3 | Punkty: 100
Fala 4 | Punkty: 150
Zadanie: dodaj zmienną wrog_predkosc = 2 i zwiększ ją w nowa_fala() (max 7).
✔️ Po tej sekcji powinieneś umieć:
  • Deklarować kilka zmiennych globalnych w jednej linii
  • Pisać funkcję nowa_fala() aktualizującą stan gry
  • Rozumieć że funkcje z global mogą zastąpić bezpośrednią modyfikację
🚀 global to most między funkcją a stanem gry — zaraz główna pętla skróci się do kilku czytelnych wywołań!
G15

Gra: Funkcje i global — porządkujemy kod

Kod z L14 działa, ale główna pętla ma 80+ linii. Czas rozbić go na funkcje — ta sama gra, czystszy kod!
🟢 Przejście — refaktoryzacja
Weź gra_14.py, zapisz jako gra_15.py i wytnij logikę do funkcji.
Poniżej: gdzie każda funkcja ląduje w kodzie.
🚀 PYGAME — Organizacja kodu w funkcje
📂 Skopiuj gra_14.py jako gra_15.py
Zmień tytuł na "Asteroidy — Lekcja 15: Funkcje i global!"
Będziesz wycinać bloki kodu i owijać je w funkcje.
Cel lekcji
Co: Ta sama gra co L14 — identyczna mechanika, ale kod podzielony na funkcje. Główna pętla skrócona do 5 linii.
Jak: Każdy obszar gry dostaje własną funkcję: ruch_statku(), strzal(), sprawdz_kolizje(). Słowo global pozwala funkcjom modyfikować zmienne zewnętrzne.
Dlaczego: Krótka pętla = łatwe debugowanie. Funkcja o jednej odpowiedzialności = łatwa zmiana bez ryzyka zepsucia reszty. To fundament dobrego kodu.
4.1 ruch_statku(klawisze) — wytnij ruch z pętli
def ruch_statku(klawisze):
    global statek_x, statek_y
    if klawisze[pygame.K_LEFT] or klawisze[pygame.K_a]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT] or klawisze[pygame.K_d]: statek_x += predkosc
    if klawisze[pygame.K_UP] or klawisze[pygame.K_w]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN] or klawisze[pygame.K_s]:  statek_y += predkosc
    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc
global statek_x, statek_y — funkcja musi mieć dostęp do tych zmiennych żeby je zmieniać.
4.2 strzal(teraz), aktualizuj_lasery/asteroidy/wrogow()
def strzal(teraz):
    global czas_ostatniego_strzalu
    if teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz

def aktualizuj_lasery():
    for laser in lasery:
        laser[1] -= laser_predkosc
    lasery[:] = [l for l in lasery if l[1] > -30]
Funkcje aktualizujące nie potrzebują global dla list — listy są mutowalne, ich zawartość można zmieniać bez global. Tylko proste zmienne (int, str) wymagają global przy przypisaniu.
4.3 sprawdz_kolizje() — największa funkcja
def sprawdz_kolizje():
    global zycia, punkty, trafiony_czas, numer_fali, wrog_predkosc
    # ... cała logika kolizji z L14 ...
Ta funkcja modyfikuje 5 zmiennych — wszystkie deklarujemy w jednej linii global.
4.4 Główna pętla — teraz czytelna
while True:
    teraz = pygame.time.get_ticks()
    # zdarzenia...

    klawisze = pygame.key.get_pressed()
    ruch_statku(klawisze)
    if klawisze[pygame.K_SPACE]:
        strzal(teraz)

    aktualizuj_lasery()
    aktualizuj_asteroidy()
    aktualizuj_wrogow()
    sprawdz_kolizje()

    # rysowanie...
Pętla stała się opisem co robić — szczegóły są ukryte w funkcjach. Łatwiej czytać i naprawiać błędy.
🗂️ Pełny kod gra_15.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

pygame.init()
SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Lekcja 15: Funkcje i global!")
zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroidy = []
for _ in range(2):
    asteroidy.append([random.randint(0, SZEROKOSC-50), random.randint(-400,-30), random.randint(28,55)])

wrog_szerokosc = 44
wrog_wysokosc = 32
wrog_predkosc = 2
numer_fali = 1

def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        nowi.append([odst*(i+1) - wrog_szerokosc//2, 40 + (i%2)*35, 1])
    return nowi

wrogowie = generuj_fale(numer_fali)
zycia = 3
punkty = 0
lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 160
trafiony_czas = 0
CZAS_TRAFIENIA = 500
font = pygame.font.SysFont("Arial", 22)
font_duzy = pygame.font.SysFont("Arial", 52)

def ruch_statku(klawisze):
    global statek_x, statek_y
    if klawisze[pygame.K_LEFT] or klawisze[pygame.K_a]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT] or klawisze[pygame.K_d]: statek_x += predkosc
    if klawisze[pygame.K_UP] or klawisze[pygame.K_w]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN] or klawisze[pygame.K_s]:  statek_y += predkosc
    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC-statek_szerokosc: statek_x = SZEROKOSC-statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC-statek_wysokosc: statek_y = WYSOKOSC-statek_wysokosc

def strzal(teraz):
    global czas_ostatniego_strzalu
    if teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x+statek_szerokosc//2-3, statek_y])
        czas_ostatniego_strzalu = teraz

def aktualizuj_lasery():
    for l in lasery: l[1] -= laser_predkosc
    lasery[:] = [l for l in lasery if l[1] > -30]

def aktualizuj_asteroidy():
    for ast in asteroidy:
        ast[1] += 3
        if ast[1] > WYSOKOSC:
            ast[1] = random.randint(-300,-30)
            ast[0] = random.randint(0,SZEROKOSC-ast[2])

def aktualizuj_wrogow():
    for w in wrogowie:
        w[0] += wrog_predkosc * w[2]
        if w[0] > SZEROKOSC-wrog_szerokosc: w[2] = -1
        if w[0] < 0: w[2] = 1

def sprawdz_kolizje():
    global zycia, punkty, trafiony_czas, numer_fali, wrog_predkosc
    teraz = pygame.time.get_ticks()
    sr = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    if teraz - trafiony_czas > CZAS_TRAFIENIA:
        for ast in asteroidy:
            if sr.colliderect(pygame.Rect(ast[0],ast[1],ast[2],ast[2])):
                zycia -= 1; trafiony_czas = teraz; ast[1] = random.randint(-300,-30)
    pozostale = []; trafieni = []
    for laser in lasery:
        lr = pygame.Rect(laser[0],laser[1],6,22); trafil = False
        for j in range(len(wrogowie)):
            if j not in trafieni and lr.colliderect(pygame.Rect(wrogowie[j][0],wrogowie[j][1],wrog_szerokosc,wrog_wysokosc)):
                trafieni.append(j); punkty += 20; trafil = True; break
        if not trafil:
            for ast in asteroidy:
                if lr.colliderect(pygame.Rect(ast[0],ast[1],ast[2],ast[2])):
                    punkty += 10; ast[1]=random.randint(-300,-30); ast[0]=random.randint(0,SZEROKOSC-ast[2]); trafil=True; break
        if not trafil: pozostale.append(laser)
    lasery[:] = pozostale
    for j in sorted(trafieni,reverse=True): wrogowie.pop(j)
    if len(wrogowie)==0:
        numer_fali+=1; punkty+=50; wrog_predkosc=min(2+numer_fali,7); wrogowie.extend(generuj_fale(numer_fali))

while True:
    teraz = pygame.time.get_ticks()
    for z in pygame.event.get():
        if z.type == pygame.QUIT: pygame.quit(); exit()
    klawisze = pygame.key.get_pressed()
    ruch_statku(klawisze)
    if klawisze[pygame.K_SPACE]: strzal(teraz)
    aktualizuj_lasery(); aktualizuj_asteroidy(); aktualizuj_wrogow(); sprawdz_kolizje()
    if zycia <= 0:
        ekran.fill((0,0,0)); ekran.blit(font_duzy.render("KONIEC GRY!",True,(255,50,50)),(240,230))
        ekran.blit(font.render("Wynik: {}  Fala: {}".format(punkty,numer_fali),True,(255,255,255)),(270,310))
        pygame.display.flip(); pygame.time.wait(3000); pygame.quit(); exit()
    trafiony = teraz - trafiony_czas < CZAS_TRAFIENIA
    kolor_s = (255,80,80) if trafiony else (0,200,255)
    ekran.fill((0,0,30))
    pygame.draw.rect(ekran,kolor_s,(statek_x,statek_y,statek_szerokosc,statek_wysokosc))
    for ast in asteroidy: pygame.draw.rect(ekran,(160,160,160),(ast[0],ast[1],ast[2],ast[2]))
    for w in wrogowie: pygame.draw.rect(ekran,(255,100,0),(w[0],w[1],wrog_szerokosc,wrog_wysokosc))
    for l in lasery: pygame.draw.rect(ekran,(255,50,50),(l[0],l[1],6,22))
    ekran.blit(font.render("Zycia:{}  Pkt:{}  Fala:{}".format(zycia,punkty,numer_fali),True,(255,255,255)),(10,10))
    pygame.display.flip(); zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Usuń global statek_x, statek_y z ruch_statku() — co się stanie?
  2. Dodaj print("strzal!") do funkcji strzal() — sprawdź czy się wywołuje
  3. Zmień predkosc = 5 na 10 — czy działa tak samo jak w L14?
  4. Dodaj funkcję wyswietl_hud() która rysuje tekst z życiami i punktami
Zadania dodatkowe
  • Wyodrębnij rysowanie do funkcji rysuj_scene(trafiony)
  • Dodaj funkcję dodaj_asteroidy(ile) przyjmującą ile asteroid dodać
  • Sprawdź: czy możesz wywołać sprawdz_kolizje() przed aktualizuj_wrogow()? Co się zmienia?
  • Dodaj do generuj_fale() parametr predkosc_bazowa z domyślną wartością 2
✔️ Sprawdź działającą grę:
  • Nowa mechanika działa poprawnie
  • Gra nie crashuje
  • HUD aktualizuje się
T16

Python: Maszyna stanów i continue

Misja: naucz się maszyny stanów i instrukcji continue — zaraz dodamy ekran startowy i game over!
🔵 MODUŁ PYTHON — Maszyna stanów i continue
Teoria — maszyna stanów (string jako stan)
Gra może być w różnych trybach. Zamiast wielu flag logicznych, używamy jednej zmiennej tekstowej:

stan = "menu"   # "menu", "gra", "koniec"

if stan == "menu":
    print("Pokazuj ekran startowy")
elif stan == "gra":
    print("Uruchamiaj logike gry")
elif stan == "koniec":
    print("Pokazuj game over")

Słowo continue w pętli — przeskakuje resztę bieżącej iteracji i zaczyna następną:
for i in range(5):
    if i == 2:
        continue   # przeskocz gdy i==2
    print(i)       # wypisze 0, 1, 3, 4
W grze: continue po obsłudze ekranu menu sprawia że kod gry NIE wykonuje się gdy jesteśmy w menu.
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_stan_1.py i cwiczenie_stan_2.py.
2.1 cwiczenie_stan_1.py — maszyna stanów
stan = "menu"

while True:
    if stan == "menu":
        print("=== MENU === (wpisz 's' aby zaczac)")
        inp = input()
        if inp == "s":
            stan = "gra"

    elif stan == "gra":
        print("=== GRA === (wpisz 'k' aby skonczyc)")
        inp = input()
        if inp == "k":
            stan = "koniec"

    elif stan == "koniec":
        print("=== KONIEC === gra sie skonczyla")
        break
> python cwiczenie_stan_1.py
=== MENU === (wpisz 's' aby zaczac)
s
=== GRA === (wpisz 'k' aby skonczyc)
k
=== KONIEC === gra sie skonczyla
Zadanie: dodaj stan "pauza" — gdy gracz wpisze 'p' przechodzi do pauzy, 'p' znowu — wraca do gry.
✔️ Po tej sekcji powinieneś umieć:
  • Używać stringa jako stanu maszyny stanów
  • Przechodzić między stanami przez przypisanie
  • Rozumieć wzorzec start → gra → koniec
2.2 cwiczenie_stan_2.py — continue w pętli
stan = "menu"
klatka = 0

while klatka < 10:
    klatka += 1

    if stan == "menu":
        print("Klatka", klatka, "— menu, brak logiki gry")
        if klatka == 3:
            stan = "gra"
        continue   # przeskocz resztę — nie wykonuj logiki gry

    # To wykonuje się tylko gdy stan == "gra"
    print("Klatka", klatka, "— gra dziala!")
> python cwiczenie_stan_2.py
Klatka 1 — menu, brak logiki gry
Klatka 2 — menu, brak logiki gry
Klatka 3 — menu, brak logiki gry
Klatka 4 — gra dziala!
Klatka 5 — gra dziala!
...
Zadanie: zmień warunek zmiany stanu na klatka == 5. Sprawdź kiedy pojawia się "gra dziala!".
✔️ Po tej sekcji powinieneś umieć:
  • Używać continue do pomijania logiki gry w stanie menu
  • Rozumieć że kod po continue nie wykonuje się w danej iteracji
  • Stosować maszyny stanów z continue w pętli gry
🚀 Maszyna stanów to szkielet każdej gry — zaraz dodamy ekran startowy i możliwość restartu bez zamykania okna!
G16

Gra: Stany Gry — start / gra / koniec

Gra kończy się i trzeba ją restartować od nowa. Czas to naprawić — dodaj ekran startowy, game_over i możliwość restartu bez zamykania okna!
🟢 Przejście — dodajemy stany do gry!
Weź gra_15.py → zapisz jako gra_16.py. Dodamy stan_gry, nowa_gra() i dwa ekrany.
🚀 PYGAME — Ekran startowy i game over
📂 Skopiuj gra_15.py jako gra_16.py
Zmień tytuł na "Asteroidy — Lekcja 16: Stany Gry!"
Cel lekcji
Co: Gra ma trzy tryby: start → gra → game_over. Po śmierci widać wynik, ENTER restartuje bez zamykania okna.
Jak: Zmienna stan_gry = "start"/"gra"/"game_over" + continue przeskakuje logikę gry gdy jesteśmy w menu. nowa_gra() resetuje wszystko.
Dlaczego: Maszyna stanów to wzorzec używany w każdej grze. continue jest czystszy niż zagnieżdżone if/else — gdy stan to menu, reszta pętli w ogóle się nie wykonuje.
4.1 stan_gry i najlepszy_wynik — dodaj przed pętlą
stan_gry = "start"   # "start", "gra", "game_over"
najlepszy_wynik = 0
4.2 nowa_gra() — reset wszystkiego
def nowa_gra():
    global zycia, punkty, numer_fali, wrog_predkosc
    global lasery, asteroidy, wrogowie, trafiony_czas, statek_x, statek_y
    zycia = 3;  punkty = 0;  numer_fali = 1;  wrog_predkosc = 2
    lasery = [];  trafiony_czas = 0
    statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
    statek_y = WYSOKOSC - 80
    asteroidy.clear()
    for _ in range(2):
        asteroidy.append([random.randint(0,SZEROKOSC-50), random.randint(-400,-30), random.randint(28,55)])
    wrogowie.clear()
    wrogowie.extend(generuj_fale(numer_fali))
4.3 Obsługa KEYDOWN (ENTER) i continue w pętli
for zdarzenie in pygame.event.get():
    if zdarzenie.type == pygame.QUIT: ...
    if zdarzenie.type == pygame.KEYDOWN:
        if zdarzenie.key == pygame.K_RETURN:
            if stan_gry in ("start", "game_over"):
                nowa_gra()
                stan_gry = "gra"

if stan_gry == "start":
    ekran_startowy()
    pygame.display.flip()
    zegar.tick(60)
    continue   # przeskocz logikę gry!

if stan_gry == "game_over":
    ekran_game_over()
    pygame.display.flip()
    zegar.tick(60)
    continue
pygame.KEYDOWN odpala się raz gdy klawisz jest naciśnięty (nie 60× gdy trzymasz).
get_pressed() daje stan w każdej klatce — dobre do ruchu. KEYDOWN — do pojedynczych akcji (start, pauza).
4.4 Zmień koniec gry — ustaw stan zamiast exit()
if zycia <= 0:
    najlepszy_wynik = max(najlepszy_wynik, punkty)
    stan_gry = "game_over"   # nie exit() — tylko zmień stan!
🗂️ Pełny kod gra_16.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 16: Stany Gry — start / gra / koniec ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Gra 16: Stany Gry!")
zegar = pygame.time.Clock()

stan_gry = "start"   # mozliwe wartosci: "start", "gra", "game_over"

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

wrog_szerokosc = 44
wrog_wysokosc = 32
wrog_predkosc = 2
numer_fali = 1

zycia = 3
punkty = 0
najlepszy_wynik = 0

lasery = []
laser_predkosc = 9
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 160

asteroidy = []
wrogowie = []

trafiony_czas = 0
CZAS_TRAFIENIA = 500

font = pygame.font.SysFont("Arial", 22)
font_sredni = pygame.font.SysFont("Arial", 32)
font_duzy = pygame.font.SysFont("Arial", 52)


def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        x = odst * (i + 1) - wrog_szerokosc // 2
        y = 40 + (i % 2) * 35
        nowi.append([x, y, 1])
    return nowi


def nowa_gra():
    global zycia, punkty, numer_fali, wrog_predkosc
    global lasery, asteroidy, wrogowie, trafiony_czas, statek_x, statek_y
    zycia = 3
    punkty = 0
    numer_fali = 1
    wrog_predkosc = 2
    lasery = []
    trafiony_czas = 0
    statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
    statek_y = WYSOKOSC - 80
    asteroidy.clear()
    for _ in range(2):
        asteroidy.append([random.randint(0, SZEROKOSC - 50),
                          random.randint(-400, -30),
                          random.randint(28, 55)])
    wrogowie.clear()
    wrogowie.extend(generuj_fale(numer_fali))


def ruch_statku(klawisze):
    global statek_x, statek_y
    if klawisze[pygame.K_LEFT] or klawisze[pygame.K_a]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT] or klawisze[pygame.K_d]: statek_x += predkosc
    if klawisze[pygame.K_UP] or klawisze[pygame.K_w]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN] or klawisze[pygame.K_s]:  statek_y += predkosc
    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc


def strzal(teraz):
    global czas_ostatniego_strzalu
    if teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz


def aktualizuj_lasery():
    for laser in lasery:
        laser[1] -= laser_predkosc
    lasery[:] = [l for l in lasery if l[1] > -30]


def aktualizuj_asteroidy():
    for ast in asteroidy:
        ast[1] += 3
        if ast[1] > WYSOKOSC:
            ast[1] = random.randint(-300, -30)
            ast[0] = random.randint(0, SZEROKOSC - ast[2])


def aktualizuj_wrogow():
    for wrog in wrogowie:
        wrog[0] += wrog_predkosc * wrog[2]
        if wrog[0] > SZEROKOSC - wrog_szerokosc: wrog[2] = -1
        if wrog[0] < 0: wrog[2] = 1


def sprawdz_kolizje():
    global zycia, punkty, trafiony_czas, numer_fali, wrog_predkosc
    teraz = pygame.time.get_ticks()
    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    if teraz - trafiony_czas > CZAS_TRAFIENIA:
        for ast in asteroidy:
            if statek_rect.colliderect(pygame.Rect(ast[0], ast[1], ast[2], ast[2])):
                zycia -= 1
                trafiony_czas = teraz
                ast[1] = random.randint(-300, -30)
    pozostale = []
    trafieni = []
    for laser in lasery:
        lr = pygame.Rect(laser[0], laser[1], 6, 22)
        trafil = False
        for j in range(len(wrogowie)):
            if j not in trafieni:
                if lr.colliderect(pygame.Rect(wrogowie[j][0], wrogowie[j][1],
                                              wrog_szerokosc, wrog_wysokosc)):
                    trafieni.append(j)
                    punkty += 20
                    trafil = True
                    break
        if not trafil:
            for ast in asteroidy:
                if lr.colliderect(pygame.Rect(ast[0], ast[1], ast[2], ast[2])):
                    punkty += 10
                    ast[1] = random.randint(-300, -30)
                    ast[0] = random.randint(0, SZEROKOSC - ast[2])
                    trafil = True
                    break
        if not trafil:
            pozostale.append(laser)
    lasery[:] = pozostale
    for j in sorted(trafieni, reverse=True):
        wrogowie.pop(j)
    if len(wrogowie) == 0:
        numer_fali += 1
        punkty += 50
        wrog_predkosc = min(2 + numer_fali, 7)
        wrogowie.extend(generuj_fale(numer_fali))


def ekran_startowy():
    ekran.fill((0, 0, 25))
    tytul = font_duzy.render("ASTEROIDY", True, (255, 220, 50))
    ekran.blit(tytul, (SZEROKOSC // 2 - tytul.get_width() // 2, 200))
    info = font_sredni.render("Nacisnij ENTER aby zaczac!", True, (50, 220, 50))
    ekran.blit(info, (SZEROKOSC // 2 - info.get_width() // 2, 320))
    sterowanie = font.render("Strzalki/WSAD — ruch   |   SPACJA — strzal", True, (200, 200, 200))
    ekran.blit(sterowanie, (SZEROKOSC // 2 - sterowanie.get_width() // 2, 400))


def ekran_game_over():
    ekran.fill((0, 0, 0))
    koniec = font_duzy.render("KONIEC GRY", True, (255, 60, 60))
    ekran.blit(koniec, (SZEROKOSC // 2 - koniec.get_width() // 2, 180))
    wynik = font_sredni.render("Twoj wynik: {}".format(punkty), True, (255, 220, 50))
    ekran.blit(wynik, (SZEROKOSC // 2 - wynik.get_width() // 2, 270))
    rekord = font.render("Rekord sesji: {}".format(najlepszy_wynik), True, (200, 200, 200))
    ekran.blit(rekord, (SZEROKOSC // 2 - rekord.get_width() // 2, 330))
    fala = font.render("Dotarles do fali: {}".format(numer_fali), True, (200, 200, 200))
    ekran.blit(fala, (SZEROKOSC // 2 - fala.get_width() // 2, 370))
    restart = font_sredni.render("ENTER = zagraj ponownie", True, (50, 220, 50))
    ekran.blit(restart, (SZEROKOSC // 2 - restart.get_width() // 2, 450))


while True:
    teraz = pygame.time.get_ticks()

    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()
        if zdarzenie.type == pygame.KEYDOWN:
            if zdarzenie.key == pygame.K_RETURN:
                if stan_gry in ("start", "game_over"):
                    nowa_gra()
                    stan_gry = "gra"

    if stan_gry == "start":
        ekran_startowy()
        pygame.display.flip()
        zegar.tick(60)
        continue

    if stan_gry == "game_over":
        ekran_game_over()
        pygame.display.flip()
        zegar.tick(60)
        continue

    klawisze = pygame.key.get_pressed()
    ruch_statku(klawisze)
    if klawisze[pygame.K_SPACE]:
        strzal(teraz)

    aktualizuj_lasery()
    aktualizuj_asteroidy()
    aktualizuj_wrogow()
    sprawdz_kolizje()

    if zycia <= 0:
        najlepszy_wynik = max(najlepszy_wynik, punkty)
        stan_gry = "game_over"

    trafiony = teraz - trafiony_czas < CZAS_TRAFIENIA
    kolor_statku = (255, 80, 80) if trafiony else (0, 200, 255)

    ekran.fill((0, 0, 30))
    pygame.draw.rect(ekran, kolor_statku,
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    for ast in asteroidy:
        pygame.draw.rect(ekran, (160, 160, 160), (ast[0], ast[1], ast[2], ast[2]))
    for wrog in wrogowie:
        pygame.draw.rect(ekran, (255, 100, 0),
                         (wrog[0], wrog[1], wrog_szerokosc, wrog_wysokosc))
    for laser in lasery:
        pygame.draw.rect(ekran, (255, 50, 50), (laser[0], laser[1], 6, 22))

    ekran.blit(font.render("Zycia: {}  Pkt: {}  Fala: {}".format(zycia, punkty, numer_fali),
                           True, (255, 255, 255)), (10, 10))
    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Uruchom — czy ekran startowy się pojawia? Wciśnij ENTER — czy gra startuje?
  2. Zgub wszystkie życia — czy game_over się pojawia i można wcisnąć ENTER?
  3. Zmień warunek startowania z K_RETURN na K_SPACE
  4. Dodaj do ekranu startowego aktualny najlepszy_wynik
Zadania dodatkowe
  • Dodaj stan "pauza" — klawisz P zatrzymuje grę, P znowu ją wznawia
  • Wyświetl na ekranie game_over numer ostatniej fali
  • Dodaj na ekranie startowym instrukcję sterowania (WSAD / strzałki)
  • Zmień ekran startowy żeby miał 3-sekundowe odliczanie zamiast ENTER
✔️ Sprawdź działającą grę:
  • Efekt wizualny lub mechanika z tej lekcji widoczna
  • Poprzednie funkcje nadal działają
T17

Python: Stałe i list comprehension

Misja: naucz się stałych KOLOR_* i list comprehension — zaraz zastąpimy prostokąty wielokątami i dodamy gwiazdki!
🔵 MODUŁ PYTHON — Stałe i list comprehension
Teoria — stałe (WIELKIE_LITERY) i list comprehension
Stałe — zmienne które nigdy nie powinny się zmieniać. Konwencja: WIELKIE_LITERY:

KOLOR_TLO    = (0, 0, 25)
KOLOR_STATEK = (0, 200, 255)
SZEROKOSC    = 800

List comprehension — zwięzły sposób budowania przefiltrowanej listy:

# Stary sposób (z L10):
nowe = []
for laser in lasery:
    if laser[1] > -30:
        nowe.append(laser)
lasery = nowe

# List comprehension — to samo w jednej linii:
lasery[:] = [l for l in lasery if l[1] > -30]
Czytaj jako: „zostaw te l z lasery dla których l[1] > -30".
⌨️ Do dzieła — otwórz edytor!
Utwórz pliki cwiczenie_listcomp_1.py i cwiczenie_listcomp_2.py.
2.1 cwiczenie_listcomp_1.py — filtrowanie list
liczby = [5, -3, 8, -1, 0, 12, -7, 4]

# Zostaw tylko dodatnie
dodatnie = [x for x in liczby if x > 0]
print("Dodatnie:", dodatnie)

# Tylko parzyste
parzyste = [x for x in liczby if x % 2 == 0]
print("Parzyste:", parzyste)
> python cwiczenie_listcomp_1.py
Dodatnie: [5, 8, 12, 4]
Parzyste: [8, 0, 12, -6]
Zadanie: z listy [100, -50, 200, 850, 300, -10, 760] zostaw tylko liczby z zakresu 0–800 (symulacja pozycji X na ekranie).
✔️ Po tej sekcji powinieneś umieć:
  • Pisać list comprehension z warunkiem if
  • Rozumieć składnię [x for x in lista if warunek]
  • Porównać list comprehension z pętlą for + append
2.2 cwiczenie_listcomp_2.py — filtrowanie laserów
lasery = [[100, 500], [200, -10], [300, 200], [150, -50], [400, 100]]

# Zostaw lasery wciąż na ekranie (y > -30)
lasery[:] = [l for l in lasery if l[1] > -30]
print("Lasery na ekranie:", lasery)
> python cwiczenie_listcomp_2.py
Lasery na ekranie: [[100, 500], [300, 200], [400, 100]]
Zadanie: dodaj kilka laserów do listy z y=600 (poniżej ekranu) i przefiltruj je: l[1] < 600.
✔️ Po tej sekcji powinieneś umieć:
  • Stosować list comprehension do filtrowania obiektów gry
  • Używać lasery[:] = [...] zamiast lasery = [...] (modyfikacja w miejscu)
  • Rozumieć różnicę między przypisaniem a modyfikacją listy
🚀 Stałe i list comprehension to narzędzia profesjonalnego kodu — zaraz statek zmieni się w wielokąt a tło wypełnią gwiazdki!
G17

Gra: Wielokąty, gwiazdki i stałe kolorów

Gra działa świetnie — ale wygląda jak placeholders. Czas nadać jej prawdziwy wygląd: wielokątny statek, kamieniste asteroidy, gwiazdki w tle!
🟢 Przejście — dodajemy graficzne szlify!
Weź gra_16.py → zapisz jako gra_17.py. Nowe funkcje rysujące zastąpią draw.rect.
🚀 PYGAME — Wielokąty, gwiazdki, HUD
Cel lekcji
Co: Statek jako trójkąt ze skrzydłami, asteroida jako sześciokąt, 80 gwiazdek w tle, pasek HUD z życiami i punktami.
Jak: draw.polygon() rysuje kształt z listy punktów (x, y). Gwiazdki to lista 80 losowych [x, y, rozmiar] + pętla draw.circle(). Stałe KOLOR_* zastępują dosłowne krotki RGB.
Dlaczego: Funkcje rysujące (rysuj_statek(), rysuj_asteroide()) kapsułkują wygląd — zmiana grafiki nie dotyka logiki gry. Stałe kolorów = jeden zmieniamy w jednym miejscu.
4.1 KOLOR_* stałe i gwiazdki
KOLOR_TLO = (0, 0, 25)
KOLOR_STATEK = (0, 200, 255)
KOLOR_LASER = (255, 60, 60)
KOLOR_WROG = (255, 100, 0)

gwiazdki = []
for _ in range(80):
    gwiazdki.append([random.randint(0, SZEROKOSC),
                     random.randint(0, WYSOKOSC),
                     random.randint(1, 3)])
4.2 rysuj_statek(trafiony) — draw.polygon
def rysuj_statek(trafiony):
    kolor = (255, 80, 80) if trafiony else KOLOR_STATEK
    # Dziob
    pygame.draw.polygon(ekran, kolor, [
        (statek_x + 21, statek_y),
        (statek_x, statek_y + 22),
        (statek_x + 42, statek_y + 22)
    ])
    # Kadlub
    pygame.draw.rect(ekran, kolor, (statek_x + 12, statek_y + 16, 18, 30))
    # Skrzydla
    pygame.draw.polygon(ekran, (0, 140, 200), [
        (statek_x, statek_y + 22),
        (statek_x - 8, statek_y + 52),
        (statek_x + 16, statek_y + 40)
    ])
draw.polygon(ekran, kolor, lista_punktow) rysuje wielokąt przez podaną listę punktów (x, y). Krawędzie są łączone automatycznie.
4.3 rysuj_asteroide(x, y, rozmiar) i wyswietl_hud()
def rysuj_asteroide(x, y, rozmiar):
    pts = [
        (x + rozmiar*0.5, y),
        (x + rozmiar,     y + rozmiar*0.3),
        (x + rozmiar*0.9, y + rozmiar),
        (x + rozmiar*0.3, y + rozmiar*0.95),
        (x,               y + rozmiar*0.6),
        (x + rozmiar*0.1, y + rozmiar*0.2),
    ]
    pygame.draw.polygon(ekran, (155, 155, 155), pts)
    pygame.draw.polygon(ekran, (120, 120, 120), pts, 2)  # kontur

def wyswietl_hud():
    pygame.draw.rect(ekran, (0, 0, 60), (0, 0, SZEROKOSC, 36))
    ekran.blit(font_maly.render("ZYCIA: " + ("* "*max(zycia,0)), True, (255,60,60)), (10, 9))
    ekran.blit(font_maly.render("PUNKTY: {}".format(punkty), True, (255,220,50)), (220, 9))
    ekran.blit(font_maly.render("FALA: {}".format(numer_fali), True, (50,220,50)), (420, 9))
🗂️ Pełny kod gra_17.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 17: Wielokąty, gwiazdki i stałe kolorów ===

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy — Gra 17: Wielokaty i Gwiazdy!")
zegar = pygame.time.Clock()

KOLOR_TLO     = (0, 0, 25)
KOLOR_STATEK  = (0, 200, 255)
KOLOR_LASER   = (255, 60, 60)
KOLOR_WROG    = (255, 100, 0)
KOLOR_BIALY   = (255, 255, 255)
KOLOR_ZOLTY   = (255, 220, 50)
KOLOR_ZIELONY = (50, 220, 50)
KOLOR_CZERWONY= (255, 60, 60)

stan_gry = "start"

statek_szerokosc = 42
statek_wysokosc = 52
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 90
predkosc = 5

wrog_szerokosc = 44
wrog_wysokosc = 32
wrog_predkosc = 2
numer_fali = 1

zycia = 3
punkty = 0
najlepszy_wynik = 0

lasery = []
laser_predkosc = 10
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 160

asteroidy = []
wrogowie = []

trafiony_czas = 0
CZAS_TRAFIENIA = 500

font_maly  = pygame.font.SysFont("Arial", 20)
font_sredni= pygame.font.SysFont("Arial", 32)
font_duzy  = pygame.font.SysFont("Arial", 52)
font_wielki= pygame.font.SysFont("Arial", 72)

gwiazdki = []
for _ in range(80):
    gwiazdki.append([random.randint(0, SZEROKOSC),
                     random.randint(0, WYSOKOSC),
                     random.randint(1, 3)])


def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        x = odst * (i + 1) - wrog_szerokosc // 2
        y = 40 + (i % 2) * 38
        nowi.append([x, y, 1])
    return nowi


def nowa_gra():
    global zycia, punkty, numer_fali, wrog_predkosc
    global lasery, asteroidy, wrogowie, trafiony_czas, statek_x, statek_y
    zycia = 3; punkty = 0; numer_fali = 1; wrog_predkosc = 2
    lasery = []; trafiony_czas = 0
    statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
    statek_y = WYSOKOSC - 90
    asteroidy.clear()
    dodaj_asteroidy(3)
    wrogowie.clear()
    wrogowie.extend(generuj_fale(numer_fali))


def dodaj_asteroidy(ile):
    for _ in range(ile):
        rozmiar = random.randint(28, 55)
        asteroidy.append([random.randint(0, SZEROKOSC - rozmiar),
                          random.randint(-350, -rozmiar),
                          rozmiar,
                          random.uniform(2.0, 3.5)])


def ruch_statku(klawisze):
    global statek_x, statek_y
    if klawisze[pygame.K_LEFT] or klawisze[pygame.K_a]:  statek_x -= predkosc
    if klawisze[pygame.K_RIGHT] or klawisze[pygame.K_d]: statek_x += predkosc
    if klawisze[pygame.K_UP] or klawisze[pygame.K_w]:    statek_y -= predkosc
    if klawisze[pygame.K_DOWN] or klawisze[pygame.K_s]:  statek_y += predkosc
    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc


def strzal(teraz):
    global czas_ostatniego_strzalu
    if teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz


def aktualizuj_lasery():
    for laser in lasery:
        laser[1] -= laser_predkosc
    lasery[:] = [l for l in lasery if l[1] > -30]


def aktualizuj_asteroidy():
    for ast in asteroidy:
        ast[1] += ast[3]
        if ast[1] > WYSOKOSC:
            ast[1] = random.randint(-300, -ast[2])
            ast[0] = random.randint(0, SZEROKOSC - ast[2])
            ast[2] = random.randint(28, 55)
            ast[3] = random.uniform(2.0, 3.5)


def aktualizuj_wrogow():
    for wrog in wrogowie:
        wrog[0] += wrog_predkosc * wrog[2]
        if wrog[0] > SZEROKOSC - wrog_szerokosc: wrog[2] = -1
        if wrog[0] < 0: wrog[2] = 1


def sprawdz_kolizje():
    global zycia, punkty, trafiony_czas, numer_fali, wrog_predkosc
    teraz = pygame.time.get_ticks()
    sr = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    if teraz - trafiony_czas > CZAS_TRAFIENIA:
        for ast in asteroidy:
            if sr.colliderect(pygame.Rect(ast[0], ast[1], ast[2], ast[2])):
                zycia -= 1; trafiony_czas = teraz
                ast[1] = random.randint(-300, -ast[2])
    pozostale = []; trafieni = []
    for laser in lasery:
        lr = pygame.Rect(laser[0], laser[1], 6, 22)
        trafil = False
        for j in range(len(wrogowie)):
            if j not in trafieni:
                if lr.colliderect(pygame.Rect(wrogowie[j][0], wrogowie[j][1],
                                              wrog_szerokosc, wrog_wysokosc)):
                    trafieni.append(j); punkty += 20; trafil = True; break
        if not trafil:
            for ast in asteroidy:
                if lr.colliderect(pygame.Rect(ast[0], ast[1], ast[2], ast[2])):
                    punkty += 10
                    ast[1] = random.randint(-300, -ast[2])
                    ast[0] = random.randint(0, SZEROKOSC - ast[2])
                    trafil = True; break
        if not trafil:
            pozostale.append(laser)
    lasery[:] = pozostale
    for j in sorted(trafieni, reverse=True):
        wrogowie.pop(j)
    if len(wrogowie) == 0:
        numer_fali += 1; punkty += 50
        wrog_predkosc = min(2 + numer_fali, 8)
        wrogowie.extend(generuj_fale(numer_fali))


def rysuj_gwiazdki():
    for gw in gwiazdki:
        pygame.draw.circle(ekran, (180, 180, 180), (gw[0], gw[1]), 1)


def rysuj_statek(trafiony):
    kolor = (255, 80, 80) if trafiony else KOLOR_STATEK
    pygame.draw.rect(ekran, kolor, (statek_x + 12, statek_y + 16, 18, 30))
    pygame.draw.polygon(ekran, kolor, [
        (statek_x + 21, statek_y),
        (statek_x, statek_y + 22),
        (statek_x + 42, statek_y + 22)
    ])
    pygame.draw.polygon(ekran, (0, 140, 200), [
        (statek_x, statek_y + 22),
        (statek_x - 8, statek_y + 52),
        (statek_x + 16, statek_y + 40)
    ])
    pygame.draw.polygon(ekran, (0, 140, 200), [
        (statek_x + 42, statek_y + 22),
        (statek_x + 50, statek_y + 52),
        (statek_x + 26, statek_y + 40)
    ])


def rysuj_asteroide(x, y, rozmiar):
    pts = [
        (x + rozmiar * 0.5, y),
        (x + rozmiar, y + rozmiar * 0.3),
        (x + rozmiar * 0.9, y + rozmiar),
        (x + rozmiar * 0.3, y + rozmiar * 0.95),
        (x, y + rozmiar * 0.6),
        (x + rozmiar * 0.1, y + rozmiar * 0.2),
    ]
    pygame.draw.polygon(ekran, (155, 155, 155), pts)
    pygame.draw.polygon(ekran, (120, 120, 120), pts, 2)


def rysuj_wroga(x, y):
    pygame.draw.rect(ekran, KOLOR_WROG, (x + 8, y, wrog_szerokosc - 16, wrog_wysokosc))
    pygame.draw.polygon(ekran, (220, 80, 0), [
        (x + wrog_szerokosc // 2, y + wrog_wysokosc),
        (x, y + 8),
        (x + wrog_szerokosc, y + 8)
    ])
    pygame.draw.circle(ekran, (255, 200, 50), (x + wrog_szerokosc // 2, y + 10), 5)


def wyswietl_hud():
    pygame.draw.rect(ekran, (0, 0, 60), (0, 0, SZEROKOSC, 36))
    ekran.blit(font_maly.render("ZYCIA: " + ("* " * max(zycia, 0)), True, KOLOR_CZERWONY), (10, 9))
    ekran.blit(font_maly.render("PUNKTY: {}".format(punkty), True, KOLOR_ZOLTY), (220, 9))
    ekran.blit(font_maly.render("FALA: {}".format(numer_fali), True, KOLOR_ZIELONY), (420, 9))
    ekran.blit(font_maly.render("WROGOW: {}".format(len(wrogowie)), True, KOLOR_BIALY), (580, 9))


def ekran_startowy():
    ekran.fill(KOLOR_TLO)
    rysuj_gwiazdki()
    tytul = font_wielki.render("ASTEROIDY", True, KOLOR_ZOLTY)
    ekran.blit(tytul, (SZEROKOSC // 2 - tytul.get_width() // 2, 180))
    info = font_sredni.render("Nacisnij ENTER aby zaczac!", True, KOLOR_ZIELONY)
    ekran.blit(info, (SZEROKOSC // 2 - info.get_width() // 2, 330))
    ster = font_maly.render("Strzalki/WSAD — ruch   |   SPACJA — strzal", True, KOLOR_BIALY)
    ekran.blit(ster, (SZEROKOSC // 2 - ster.get_width() // 2, 400))


def ekran_game_over():
    ekran.fill((0, 0, 0))
    rysuj_gwiazdki()
    koniec = font_wielki.render("KONIEC GRY", True, KOLOR_CZERWONY)
    ekran.blit(koniec, (SZEROKOSC // 2 - koniec.get_width() // 2, 150))
    wynik = font_duzy.render("Twoj wynik: {}".format(punkty), True, KOLOR_ZOLTY)
    ekran.blit(wynik, (SZEROKOSC // 2 - wynik.get_width() // 2, 270))
    rekord = font_sredni.render("Rekord: {}".format(najlepszy_wynik), True, KOLOR_BIALY)
    ekran.blit(rekord, (SZEROKOSC // 2 - rekord.get_width() // 2, 350))
    restart = font_sredni.render("ENTER = zagraj ponownie", True, KOLOR_ZIELONY)
    ekran.blit(restart, (SZEROKOSC // 2 - restart.get_width() // 2, 450))


while True:
    teraz = pygame.time.get_ticks()

    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()
        if zdarzenie.type == pygame.KEYDOWN:
            if zdarzenie.key == pygame.K_RETURN:
                if stan_gry in ("start", "game_over"):
                    nowa_gra()
                    stan_gry = "gra"

    if stan_gry == "start":
        ekran_startowy()
        pygame.display.flip()
        zegar.tick(60)
        continue

    if stan_gry == "game_over":
        ekran_game_over()
        pygame.display.flip()
        zegar.tick(60)
        continue

    klawisze = pygame.key.get_pressed()
    ruch_statku(klawisze)
    if klawisze[pygame.K_SPACE]:
        strzal(teraz)

    aktualizuj_lasery()
    aktualizuj_asteroidy()
    aktualizuj_wrogow()
    sprawdz_kolizje()

    if zycia <= 0:
        najlepszy_wynik = max(najlepszy_wynik, punkty)
        stan_gry = "game_over"

    trafiony = teraz - trafiony_czas < CZAS_TRAFIENIA

    ekran.fill(KOLOR_TLO)
    rysuj_gwiazdki()

    for ast in asteroidy:
        rysuj_asteroide(ast[0], ast[1], ast[2])
    for wrog in wrogowie:
        rysuj_wroga(wrog[0], wrog[1])
    for laser in lasery:
        pygame.draw.rect(ekran, KOLOR_LASER, (laser[0], laser[1], 6, 22))

    rysuj_statek(trafiony)
    wyswietl_hud()

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zmień KOLOR_STATEK — jaki kolor lubisz?
  2. Zmień 80 gwiazdek na 200 — jak wygląda gęste tło?
  3. Dodaj drugi kontur asteroidy z innym kolorem (trzecia linia draw.polygon)
  4. Zmień HUD żeby pokazywał serduszka ♥ zamiast gwiazdek *
Zadania dodatkowe
  • Dodaj funkcję rysuj_wroga(x, y) z draw.polygon i draw.circle
  • Asteroida ma 4. wartość: [x, y, rozmiar, predkosc] — każda asteroida spada z własną prędkością
  • Dodaj do HUD licznik wrogów: "WROGOW: {}"
  • Zamień list comprehension na stary sposób i z powrotem — sprawdź czy efekt jest taki sam
✔️ Sprawdź działającą grę:
  • Kolejna mechanika zintegrowana bez regresji
  • Gra jest grywalna
T18

Python: max() i KEYDOWN vs get_pressed

Misja: naucz się max() i różnicy KEYDOWN/get_pressed — ostatnie dwa pojęcia przed finalną grą!
🔵 MODUŁ PYTHON — max() i KEYDOWN vs get_pressed
Teoria — max() i różnica KEYDOWN vs get_pressed
max(a, b) zwraca większą z dwóch wartości — idealne do aktualizacji rekordu:

najlepszy = 0
wynik = 150

najlepszy = max(najlepszy, wynik)
print(najlepszy)   # 150

wynik = 80
najlepszy = max(najlepszy, wynik)
print(najlepszy)   # nadal 150 — rekord nie spada!

Dwa sposoby odczytu klawiszy:

# get_pressed() — stan w każdej klatce (do ruchu, strzału)
klawisze = pygame.key.get_pressed()
if klawisze[pygame.K_LEFT]: x -= 5   # odpala 60x/s gdy trzymasz

# KEYDOWN — zdarzenie gdy klawisz jest naciśnięty (1 raz)
if zdarzenie.type == pygame.KEYDOWN:
    if zdarzenie.key == pygame.K_RETURN:
        stan = "gra"   # odpala się raz przy naciśnięciu
⌨️ Do dzieła — otwórz edytor!
Utwórz plik cwiczenie_rekord_1.py.
2.1 cwiczenie_rekord_1.py — śledzenie rekordu
import random

najlepszy = 0

for rozgrywka in range(5):
    wynik = random.randint(10, 200)
    print("Rozgrywka {}: wynik = {}".format(rozgrywka + 1, wynik))

    if wynik > najlepszy:
        najlepszy = wynik
        print("  NOWY REKORD!")

print("Rekord sesji:", najlepszy)
> python cwiczenie_rekord_1.py
Rozgrywka 1: wynik = 87
NOWY REKORD!
Rozgrywka 2: wynik = 34
Rozgrywka 3: wynik = 120
NOWY REKORD!
Rozgrywka 4: wynik = 45
Rozgrywka 5: wynik = 98
Rekord sesji: 120
Zadanie: przepisz używając max() zamiast if wynik > najlepszy. Wyświetl "NOWY REKORD!" tylko gdy wynik == najlepszy i najlepszy > 0.
✔️ Po tej sekcji powinieneś umieć:
  • Używać max(a, b) do aktualizowania rekordu
  • Śledzić najlepszy wynik w pętli rozgrywek
  • Wyświetlać komunikat "NOWY REKORD!" tylko gdy warunek spełniony
🚀 To ostatnia teoria przed finalną grą! Po tej lekcji Twoja gra_18.py będzie identyczna z gra_asteroidy.py — masz własną grę! 🎮
G18

Gra: Rekord, ekran tytułowy — FINAŁ!

Ostatni krok! Dodaj rekord sesji, polished ekran startowy z instrukcją i komunikat "NOWY REKORD!" — to jest gra_asteroidy.py!
🟢 Przejście — ostatnie szlify!
Weź gra_17.py → zapisz jako gra_18.py. Trzy małe zmiany = pełna gra.
🚀 PYGAME — Rekord i polished ekrany
Cel lekcji
Co: Gra śledzi rekord sesji, wyświetla "NOWY REKORD!" gdy gracz go pobije, i ma pełny ekran startowy z instrukcją.
Jak: max(najlepszy_wynik, punkty) aktualizuje rekord bez if. Warunek punkty >= najlepszy and punkty > 0 odróżnia rekord od zerowego wyniku przy pierwszej grze.
Dlaczego: Rekord sesji to motywacja do powtarzania gry. Po tej lekcji Twój plik gra_18.py jest identyczny z gra_asteroidy.py — ukończyłeś kurs!
4.1 Rekord — max() przy game_over
if zycia <= 0:
    najlepszy_wynik = max(najlepszy_wynik, punkty)   # NOWE: max()
    stan_gry = "game_over"
4.2 ekran_startowy() — z tytułem i instrukcją
def ekran_startowy():
    ekran.fill(KOLOR_TLO)
    rysuj_gwiazdki()
    tytul = font_wielki.render("ASTEROIDY", True, KOLOR_ZOLTY)
    ekran.blit(tytul, (SZEROKOSC//2 - tytul.get_width()//2, 130))
    linie = [
        "Strzalki / WSAD  —  ruch statku",
        "SPACJA           —  strzal laserem",
        "Wrog = 20 pkt  |  Skala = 10 pkt  |  Fala = +50 pkt",
    ]
    for i, l in enumerate(linie):
        n = font_maly.render(l, True, KOLOR_BIALY)
        ekran.blit(n, (SZEROKOSC//2 - n.get_width()//2, 310 + i*32))
    start = font_duzy.render("Nacisnij ENTER aby zaczac!", True, KOLOR_ZIELONY)
    ekran.blit(start, (SZEROKOSC//2 - start.get_width()//2, 480))
4.3 ekran_game_over() — z rekordem i "NOWY REKORD!"
def ekran_game_over():
    ekran.fill((0, 0, 0))
    rysuj_gwiazdki()
    koniec = font_wielki.render("KONIEC GRY", True, KOLOR_CZERWONY)
    ekran.blit(koniec, (SZEROKOSC//2 - koniec.get_width()//2, 150))
    wynik = font_duzy.render("Twoj wynik: {}".format(punkty), True, KOLOR_ZOLTY)
    ekran.blit(wynik, (SZEROKOSC//2 - wynik.get_width()//2, 250))
    rekord = font_sredni.render("Rekord: {}".format(najlepszy_wynik), True, KOLOR_BIALY)
    ekran.blit(rekord, (SZEROKOSC//2 - rekord.get_width()//2, 320))
    if punkty >= najlepszy_wynik and punkty > 0:   # NOWE
        nr = font_sredni.render("NOWY REKORD!", True, KOLOR_ZIELONY)
        ekran.blit(nr, (SZEROKOSC//2 - nr.get_width()//2, 360))
punkty >= najlepszy_wynik and punkty > 0 — sprawdzamy że wynik jest najlepszym i że jest >0 (nie chcemy "NOWY REKORD!" dla 0 punktów przy pierwszej grze).
🗂️ Pełny kod gra_18.py — kliknij aby porównać ze swoim plikiem
import pygame
import random

# === LEKCJA 18: Rekord, pełny ekran startowy — FINAŁ! ===
# Ten plik jest identyczny z gra_asteroidy.py!

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("ASTEROIDY — Pelna Gra!")
zegar = pygame.time.Clock()

KOLOR_TLO     = (0, 0, 25)
KOLOR_STATEK  = (0, 200, 255)
KOLOR_LASER   = (255, 60, 60)
KOLOR_WROG    = (255, 100, 0)
KOLOR_BIALY   = (255, 255, 255)
KOLOR_ZOLTY   = (255, 220, 50)
KOLOR_ZIELONY = (50, 220, 50)
KOLOR_CZERWONY= (255, 60, 60)

stan_gry = "start"

statek_szerokosc = 42
statek_wysokosc = 52
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 90
predkosc_statku = 5

wrog_szerokosc = 44
wrog_wysokosc = 32
wrog_predkosc = 2
numer_fali = 1
poziom = 1

zycia = 3
punkty = 0
najlepszy_wynik = 0

lasery = []
laser_predkosc = 10
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 160

asteroidy = []
wrogowie = []

trafiony_czas = 0
CZAS_TRAFIENIA = 500

gwiazdki = []
for _ in range(80):
    gwiazdki.append([random.randint(0, SZEROKOSC),
                     random.randint(0, WYSOKOSC),
                     random.randint(1, 3)])

font_maly  = pygame.font.SysFont("Arial", 20)
font_sredni= pygame.font.SysFont("Arial", 28)
font_duzy  = pygame.font.SysFont("Arial", 52)
font_wielki= pygame.font.SysFont("Arial", 72)


def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        x = odst * (i + 1) - wrog_szerokosc // 2
        y = 40 + (i % 2) * 38
        nowi.append([x, y, 1])
    return nowi


def nowa_gra():
    global zycia, punkty, numer_fali, wrog_predkosc, poziom
    global lasery, asteroidy, wrogowie, trafiony_czas, statek_x, statek_y
    zycia = 3; punkty = 0; numer_fali = 1; poziom = 1; wrog_predkosc = 2
    lasery = []; trafiony_czas = 0
    statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
    statek_y = WYSOKOSC - 90
    asteroidy.clear()
    dodaj_asteroidy(3)
    wrogowie.clear()
    wrogowie.extend(generuj_fale(numer_fali))


def dodaj_asteroidy(ile):
    for _ in range(ile):
        rozmiar = random.randint(28, 58)
        asteroidy.append([random.randint(0, SZEROKOSC - rozmiar),
                          random.randint(-350, -rozmiar),
                          rozmiar,
                          random.uniform(2.0, 3.5 + poziom * 0.4)])


def ruch_statku(klawisze):
    global statek_x, statek_y
    if klawisze[pygame.K_LEFT] or klawisze[pygame.K_a]:  statek_x -= predkosc_statku
    if klawisze[pygame.K_RIGHT] or klawisze[pygame.K_d]: statek_x += predkosc_statku
    if klawisze[pygame.K_UP] or klawisze[pygame.K_w]:    statek_y -= predkosc_statku
    if klawisze[pygame.K_DOWN] or klawisze[pygame.K_s]:  statek_y += predkosc_statku
    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc


def strzal(teraz):
    global czas_ostatniego_strzalu
    if teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz


def aktualizuj_lasery():
    for laser in lasery:
        laser[1] -= laser_predkosc
    lasery[:] = [l for l in lasery if l[1] > -30]


def aktualizuj_asteroidy():
    for ast in asteroidy:
        ast[1] += ast[3]
        if ast[1] > WYSOKOSC:
            ast[1] = random.randint(-300, -ast[2])
            ast[0] = random.randint(0, SZEROKOSC - ast[2])
            ast[2] = random.randint(28, 58)
            ast[3] = random.uniform(2.0, 3.5 + poziom * 0.4)


def aktualizuj_wrogow():
    for wrog in wrogowie:
        wrog[0] += wrog_predkosc * wrog[2]
        if wrog[0] > SZEROKOSC - wrog_szerokosc: wrog[2] = -1
        if wrog[0] < 0: wrog[2] = 1


def sprawdz_kolizje():
    global zycia, punkty, trafiony_czas, numer_fali, wrog_predkosc
    teraz = pygame.time.get_ticks()
    sr = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    if teraz - trafiony_czas > CZAS_TRAFIENIA:
        for ast in asteroidy:
            if sr.colliderect(pygame.Rect(ast[0], ast[1], ast[2], ast[2])):
                zycia -= 1; trafiony_czas = teraz
                ast[1] = random.randint(-300, -ast[2])
                ast[0] = random.randint(0, SZEROKOSC - ast[2])
    pozostale = []; trafieni = []
    for laser in lasery:
        lr = pygame.Rect(laser[0], laser[1], 6, 22)
        trafil = False
        for j in range(len(wrogowie)):
            if j not in trafieni:
                if lr.colliderect(pygame.Rect(wrogowie[j][0], wrogowie[j][1],
                                              wrog_szerokosc, wrog_wysokosc)):
                    trafieni.append(j); punkty += 20; trafil = True; break
        if not trafil:
            for ast in asteroidy:
                if lr.colliderect(pygame.Rect(ast[0], ast[1], ast[2], ast[2])):
                    punkty += 10
                    ast[1] = random.randint(-300, -ast[2])
                    ast[0] = random.randint(0, SZEROKOSC - ast[2])
                    trafil = True; break
        if not trafil:
            pozostale.append(laser)
    lasery[:] = pozostale
    for j in sorted(trafieni, reverse=True):
        wrogowie.pop(j)
    if len(wrogowie) == 0:
        numer_fali += 1; punkty += 50
        wrog_predkosc = min(2 + numer_fali, 8)
        wrogowie.extend(generuj_fale(numer_fali))


def rysuj_gwiazdki():
    for gw in gwiazdki:
        pygame.draw.circle(ekran, (180, 180, 180), (gw[0], gw[1]), 1)


def rysuj_statek(trafiony):
    kolor = (255, 80, 80) if trafiony else KOLOR_STATEK
    pygame.draw.rect(ekran, kolor, (statek_x + 12, statek_y + 16, 18, 30))
    pygame.draw.polygon(ekran, kolor, [
        (statek_x + 21, statek_y),
        (statek_x, statek_y + 22),
        (statek_x + 42, statek_y + 22)
    ])
    pygame.draw.polygon(ekran, (0, 140, 200), [
        (statek_x, statek_y + 22),
        (statek_x - 8, statek_y + 52),
        (statek_x + 16, statek_y + 40)
    ])
    pygame.draw.polygon(ekran, (0, 140, 200), [
        (statek_x + 42, statek_y + 22),
        (statek_x + 50, statek_y + 52),
        (statek_x + 26, statek_y + 40)
    ])


def rysuj_asteroide(x, y, rozmiar):
    pts = [
        (x + rozmiar * 0.5, y),
        (x + rozmiar, y + rozmiar * 0.3),
        (x + rozmiar * 0.9, y + rozmiar),
        (x + rozmiar * 0.3, y + rozmiar * 0.95),
        (x, y + rozmiar * 0.6),
        (x + rozmiar * 0.1, y + rozmiar * 0.2),
    ]
    pygame.draw.polygon(ekran, (155, 155, 155), pts)
    pygame.draw.polygon(ekran, (120, 120, 120), pts, 2)


def rysuj_wroga(x, y):
    pygame.draw.rect(ekran, KOLOR_WROG, (x + 8, y, wrog_szerokosc - 16, wrog_wysokosc))
    pygame.draw.polygon(ekran, (220, 80, 0), [
        (x + wrog_szerokosc // 2, y + wrog_wysokosc),
        (x, y + 8),
        (x + wrog_szerokosc, y + 8)
    ])
    pygame.draw.circle(ekran, (255, 200, 50), (x + wrog_szerokosc // 2, y + 10), 5)


def wyswietl_hud():
    pygame.draw.rect(ekran, (0, 0, 60), (0, 0, SZEROKOSC, 36))
    ekran.blit(font_maly.render("ZYCIA: " + ("* " * max(zycia, 0)), True, KOLOR_CZERWONY), (10, 9))
    ekran.blit(font_maly.render("PUNKTY: {}".format(punkty), True, KOLOR_ZOLTY), (220, 9))
    ekran.blit(font_maly.render("FALA: {}".format(numer_fali), True, KOLOR_ZIELONY), (420, 9))
    ekran.blit(font_maly.render("WROGOW: {}".format(len(wrogowie)), True, KOLOR_BIALY), (580, 9))


def ekran_startowy():
    ekran.fill(KOLOR_TLO)
    rysuj_gwiazdki()
    tytul = font_wielki.render("ASTEROIDY", True, KOLOR_ZOLTY)
    ekran.blit(tytul, (SZEROKOSC // 2 - tytul.get_width() // 2, 130))
    pod = font_sredni.render("Uratuj galaktyke przed inwazja!", True, KOLOR_BIALY)
    ekran.blit(pod, (SZEROKOSC // 2 - pod.get_width() // 2, 220))
    linie = [
        "Strzalki / WSAD  —  ruch statku",
        "SPACJA           —  strzal laserem",
        "Wrog = 20 pkt   |   Skala = 10 pkt   |   Fala = +50 pkt",
    ]
    for i, l in enumerate(linie):
        n = font_maly.render(l, True, KOLOR_BIALY)
        ekran.blit(n, (SZEROKOSC // 2 - n.get_width() // 2, 310 + i * 32))
    start = font_duzy.render("Nacisnij ENTER aby zaczac!", True, KOLOR_ZIELONY)
    ekran.blit(start, (SZEROKOSC // 2 - start.get_width() // 2, 480))


def ekran_game_over():
    ekran.fill((0, 0, 0))
    rysuj_gwiazdki()
    koniec = font_wielki.render("KONIEC GRY", True, KOLOR_CZERWONY)
    ekran.blit(koniec, (SZEROKOSC // 2 - koniec.get_width() // 2, 150))
    wynik = font_duzy.render("Twoj wynik: {}".format(punkty), True, KOLOR_ZOLTY)
    ekran.blit(wynik, (SZEROKOSC // 2 - wynik.get_width() // 2, 250))
    rekord = font_sredni.render("Rekord: {}".format(najlepszy_wynik), True, KOLOR_BIALY)
    ekran.blit(rekord, (SZEROKOSC // 2 - rekord.get_width() // 2, 320))
    if punkty >= najlepszy_wynik and punkty > 0:
        nr = font_sredni.render("NOWY REKORD!", True, KOLOR_ZIELONY)
        ekran.blit(nr, (SZEROKOSC // 2 - nr.get_width() // 2, 360))
    fala_t = font_maly.render("Dotarles do fali: {}".format(numer_fali), True, KOLOR_BIALY)
    ekran.blit(fala_t, (SZEROKOSC // 2 - fala_t.get_width() // 2, 410))
    pn = font_duzy.render("ENTER = zagraj ponownie", True, KOLOR_ZIELONY)
    ekran.blit(pn, (SZEROKOSC // 2 - pn.get_width() // 2, 480))


while True:
    teraz = pygame.time.get_ticks()

    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()
        if zdarzenie.type == pygame.KEYDOWN:
            if zdarzenie.key == pygame.K_RETURN:
                if stan_gry in ("start", "game_over"):
                    nowa_gra()
                    stan_gry = "gra"

    if stan_gry == "start":
        ekran_startowy()
        pygame.display.flip()
        zegar.tick(60)
        continue

    if stan_gry == "game_over":
        ekran_game_over()
        pygame.display.flip()
        zegar.tick(60)
        continue

    klawisze = pygame.key.get_pressed()
    ruch_statku(klawisze)
    if klawisze[pygame.K_SPACE]:
        strzal(teraz)

    aktualizuj_lasery()
    aktualizuj_asteroidy()
    aktualizuj_wrogow()
    sprawdz_kolizje()

    if zycia <= 0:
        najlepszy_wynik = max(najlepszy_wynik, punkty)
        stan_gry = "game_over"

    trafiony = teraz - trafiony_czas < CZAS_TRAFIENIA

    ekran.fill(KOLOR_TLO)
    rysuj_gwiazdki()

    for ast in asteroidy:
        rysuj_asteroide(ast[0], ast[1], ast[2])
    for wrog in wrogowie:
        rysuj_wroga(wrog[0], wrog[1])
    for laser in lasery:
        pygame.draw.rect(ekran, KOLOR_LASER, (laser[0], laser[1], 6, 22))

    rysuj_statek(trafiony)
    wyswietl_hud()

    pygame.display.flip()
    zegar.tick(60)

Eksperymenty — zmień i sprawdź!
  1. Zagraj dwa razy — czy rekord się aktualizuje?
  2. Zmień komunikat "NOWY REKORD!" na coś własnego z dużą czcionką
  3. Wyświetl rekord już na ekranie startowym (hint: najlepszy_wynik)
  4. Porównaj gra_18.py z gra_asteroidy.py — co jeszcze jest inne?
Zadania dodatkowe
  • Zapisz rekord do pliku: open("rekord.txt", "w").write(str(najlepszy_wynik))
  • Wczytaj rekord przy starcie: open("rekord.txt").read()
  • Dodaj animację "odliczanie 3...2...1..." przed startem gry
  • Porównaj gra_18.py z gra_asteroidy.py i przepisz brakujące różnice
✔️ Sprawdź działającą grę:
  • Wszystkie mechaniki z poprzednich lekcji działają razem
  • Gra jest kompletna i grywalna
6 Etap 6 — Pełna Gra z Funkcjami
Pełna gra

gra_asteroidy.py — Misja Ostateczna

Wszystkie systemy operacyjne! Pełna wersja gry z ekranem startowym, game over, rekordami i funkcjami do organizacji kodu.
Co nowego w pełnej grze
Funkcje — każda odpowiada za jeden obszar gry:
nowa_gra() — resetuje stan gry
ruch_statku(klawisze) — logika ruchu
sprawdz_kolizje() — wszystkie kolizje
rysuj_statek(trafiony) — rysuje wielokątem (nie prostokątem!)
wyswietl_hud() — pasek z życiami i punktami
ekran_startowy() i ekran_game_over()

Stany gry: stan_gry = "start" / "gra" / "game_over"
Rekord: najlepszy_wynik — zapamiętuje najlepszy wynik sesji
Kod — gra_asteroidy.py
import pygame
import random

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("ASTEROIDY — Pelna Gra!")
zegar = pygame.time.Clock()

font_maly = pygame.font.SysFont("Arial", 20)
font_sredni = pygame.font.SysFont("Arial", 28)
font_duzy = pygame.font.SysFont("Arial", 52)
font_wielki = pygame.font.SysFont("Arial", 72)

KOLOR_TLO    = (0, 0, 25)
KOLOR_STATEK = (0, 200, 255)
KOLOR_LASER  = (255, 60, 60)
KOLOR_WROG   = (255, 100, 0)
KOLOR_ASTEROIDA = (155, 155, 155)
KOLOR_BIALY  = (255, 255, 255)
KOLOR_ZOLTY  = (255, 220, 50)
KOLOR_CZERWONY = (255, 60, 60)
KOLOR_ZIELONY = (50, 220, 50)

# ---- Stan gry ----
stan_gry = "start"

statek_szerokosc = 42
statek_wysokosc = 52
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 90
predkosc_statku = 5

zycia = 3
punkty = 0
najlepszy_wynik = 0
poziom = 1
wrog_predkosc = 2

lasery = []
laser_predkosc = 10
czas_ostatniego_strzalu = 0
opoznienie_strzalu = 160

asteroidy = []
wrogowie = []
wrog_szerokosc = 44
wrog_wysokosc = 32
numer_fali = 1

trafiony_czas = 0
CZAS_TRAFIENIA = 500

gwiazdki = []
for _ in range(80):
    gwiazdki.append([random.randint(0, SZEROKOSC),
                     random.randint(0, WYSOKOSC),
                     random.randint(1, 3)])


def resetuj_statek():
    global statek_x, statek_y
    statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
    statek_y = WYSOKOSC - 90


def nowa_gra():
    global zycia, punkty, poziom, numer_fali, wrog_predkosc
    global lasery, asteroidy, wrogowie, trafiony_czas
    zycia = 3
    punkty = 0
    poziom = 1
    numer_fali = 1
    wrog_predkosc = 2
    lasery = []
    trafiony_czas = 0
    resetuj_statek()
    asteroidy.clear()
    dodaj_asteroidy(3)
    wrogowie.clear()
    wrogowie.extend(generuj_fale(numer_fali))


def dodaj_asteroidy(ile):
    for _ in range(ile):
        rozmiar = random.randint(28, 58)
        x = random.randint(0, SZEROKOSC - rozmiar)
        y = random.randint(-350, -rozmiar)
        pred = random.uniform(2.0, 3.5 + poziom * 0.4)
        asteroidy.append([x, y, rozmiar, pred])


def generuj_fale(numer):
    nowi = []
    liczba = 3 + numer
    for i in range(liczba):
        odst = SZEROKOSC // (liczba + 1)
        x = odst * (i + 1) - wrog_szerokosc // 2
        y = 40 + (i % 2) * 38
        nowi.append([x, y, 1])
    return nowi


def ruch_statku(klawisze):
    global statek_x, statek_y
    if klawisze[pygame.K_LEFT] or klawisze[pygame.K_a]:  statek_x -= predkosc_statku
    if klawisze[pygame.K_RIGHT] or klawisze[pygame.K_d]: statek_x += predkosc_statku
    if klawisze[pygame.K_UP] or klawisze[pygame.K_w]:    statek_y -= predkosc_statku
    if klawisze[pygame.K_DOWN] or klawisze[pygame.K_s]:  statek_y += predkosc_statku
    if statek_x < 0: statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc: statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0: statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc: statek_y = WYSOKOSC - statek_wysokosc


def strzal(teraz):
    global czas_ostatniego_strzalu
    if teraz - czas_ostatniego_strzalu > opoznienie_strzalu:
        lasery.append([statek_x + statek_szerokosc // 2 - 3, statek_y])
        czas_ostatniego_strzalu = teraz


def aktualizuj_lasery():
    for laser in lasery:
        laser[1] -= laser_predkosc
    lasery[:] = [l for l in lasery if l[1] > -30]


def aktualizuj_asteroidy():
    for ast in asteroidy:
        ast[1] += ast[3]
        if ast[1] > WYSOKOSC:
            ast[1] = random.randint(-300, -ast[2])
            ast[0] = random.randint(0, SZEROKOSC - ast[2])
            ast[2] = random.randint(28, 58)
            ast[3] = random.uniform(2.0, 3.5 + poziom * 0.4)


def aktualizuj_wrogow():
    for wrog in wrogowie:
        wrog[0] += wrog_predkosc * wrog[2]
        if wrog[0] > SZEROKOSC - wrog_szerokosc: wrog[2] = -1
        if wrog[0] < 0: wrog[2] = 1


def sprawdz_kolizje():
    global zycia, punkty, trafiony_czas, numer_fali, wrog_predkosc
    teraz = pygame.time.get_ticks()
    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)

    if teraz - trafiony_czas > CZAS_TRAFIENIA:
        for ast in asteroidy:
            ast_rect = pygame.Rect(ast[0], ast[1], ast[2], ast[2])
            if statek_rect.colliderect(ast_rect):
                zycia -= 1
                trafiony_czas = teraz
                ast[1] = random.randint(-300, -ast[2])
                ast[0] = random.randint(0, SZEROKOSC - ast[2])

    pozostale_lasery = []
    trafieni_wrogowie = []

    for laser in lasery:
        laser_rect = pygame.Rect(laser[0], laser[1], 6, 22)
        trafil = False

        for j in range(len(wrogowie)):
            if j not in trafieni_wrogowie:
                wrog_rect = pygame.Rect(wrogowie[j][0], wrogowie[j][1],
                                        wrog_szerokosc, wrog_wysokosc)
                if laser_rect.colliderect(wrog_rect):
                    trafieni_wrogowie.append(j)
                    punkty += 20
                    trafil = True
                    break

        if not trafil:
            for ast in asteroidy:
                ast_rect = pygame.Rect(ast[0], ast[1], ast[2], ast[2])
                if laser_rect.colliderect(ast_rect):
                    punkty += 10
                    ast[1] = random.randint(-300, -ast[2])
                    ast[0] = random.randint(0, SZEROKOSC - ast[2])
                    trafil = True
                    break

        if not trafil:
            pozostale_lasery.append(laser)

    lasery[:] = pozostale_lasery
    for j in sorted(trafieni_wrogowie, reverse=True):
        wrogowie.pop(j)

    if len(wrogowie) == 0:
        numer_fali += 1
        punkty += 50
        wrog_predkosc = min(2 + numer_fali, 8)
        wrogowie.extend(generuj_fale(numer_fali))


def rysuj_gwiazdki():
    for gw in gwiazdki:
        pygame.draw.circle(ekran, (180, 180, 180), (gw[0], gw[1]), 1)


def rysuj_statek(trafiony):
    kolor = (255, 80, 80) if trafiony else KOLOR_STATEK
    pygame.draw.polygon(ekran, kolor, [
        (statek_x + 21, statek_y),
        (statek_x, statek_y + 22),
        (statek_x + 42, statek_y + 22)
    ])
    pygame.draw.rect(ekran, kolor, (statek_x + 12, statek_y + 16, 18, 30))
    pygame.draw.polygon(ekran, (0, 140, 200), [
        (statek_x, statek_y + 22),
        (statek_x - 8, statek_y + 52),
        (statek_x + 16, statek_y + 40)
    ])
    pygame.draw.polygon(ekran, (0, 140, 200), [
        (statek_x + 42, statek_y + 22),
        (statek_x + 50, statek_y + 52),
        (statek_x + 26, statek_y + 40)
    ])


def rysuj_asteroide(x, y, rozmiar):
    pts = [
        (x + rozmiar * 0.5, y),
        (x + rozmiar, y + rozmiar * 0.3),
        (x + rozmiar * 0.9, y + rozmiar),
        (x + rozmiar * 0.3, y + rozmiar * 0.95),
        (x, y + rozmiar * 0.6),
        (x + rozmiar * 0.1, y + rozmiar * 0.2),
    ]
    pygame.draw.polygon(ekran, KOLOR_ASTEROIDA, pts)
    pygame.draw.polygon(ekran, (120, 120, 120), pts, 2)


def rysuj_wroga(x, y):
    pygame.draw.rect(ekran, KOLOR_WROG, (x + 8, y, wrog_szerokosc - 16, wrog_wysokosc))
    pygame.draw.polygon(ekran, (220, 80, 0), [
        (x + wrog_szerokosc // 2, y + wrog_wysokosc),
        (x, y + 8),
        (x + wrog_szerokosc, y + 8)
    ])
    pygame.draw.circle(ekran, (255, 200, 50), (x + wrog_szerokosc // 2, y + 10), 5)


def wyswietl_hud():
    pygame.draw.rect(ekran, (0, 0, 60), (0, 0, SZEROKOSC, 36))
    ekran.blit(font_maly.render("ZYCIA: " + ("♥ " * max(zycia, 0)),
                                True, KOLOR_CZERWONY), (10, 9))
    ekran.blit(font_maly.render("PUNKTY: {}".format(punkty),
                                True, KOLOR_ZOLTY), (220, 9))
    ekran.blit(font_maly.render("FALA: {}".format(numer_fali),
                                True, KOLOR_ZIELONY), (420, 9))
    ekran.blit(font_maly.render("WROGOW: {}".format(len(wrogowie)),
                                True, KOLOR_BIALY), (580, 9))


def ekran_startowy():
    ekran.fill(KOLOR_TLO)
    rysuj_gwiazdki()
    tytul = font_wielki.render("ASTEROIDY", True, KOLOR_ZOLTY)
    ekran.blit(tytul, (SZEROKOSC // 2 - tytul.get_width() // 2, 130))
    pod = font_sredni.render("Uratuj galaktyke przed inwazja!", True, KOLOR_BIALY)
    ekran.blit(pod, (SZEROKOSC // 2 - pod.get_width() // 2, 220))
    linie = [
        "Strzalki / WSAD  —  ruch statku",
        "SPACJA           —  strzal laserem",
        "Wrog = 20 pkt   |   Skala = 10 pkt   |   Fala = +50 pkt",
    ]
    for i, l in enumerate(linie):
        n = font_maly.render(l, True, KOLOR_BIALY)
        ekran.blit(n, (SZEROKOSC // 2 - n.get_width() // 2, 310 + i * 32))
    start = font_duzy.render("Nacisnij ENTER aby zaczac!", True, KOLOR_ZIELONY)
    ekran.blit(start, (SZEROKOSC // 2 - start.get_width() // 2, 510))


def ekran_game_over():
    ekran.fill((0, 0, 0))
    rysuj_gwiazdki()
    koniec = font_wielki.render("KONIEC GRY", True, KOLOR_CZERWONY)
    ekran.blit(koniec, (SZEROKOSC // 2 - koniec.get_width() // 2, 150))
    wynik = font_duzy.render("Twoj wynik: {}".format(punkty), True, KOLOR_ZOLTY)
    ekran.blit(wynik, (SZEROKOSC // 2 - wynik.get_width() // 2, 250))
    rekord = font_sredni.render("Rekord: {}".format(najlepszy_wynik), True, KOLOR_BIALY)
    ekran.blit(rekord, (SZEROKOSC // 2 - rekord.get_width() // 2, 320))
    if punkty >= najlepszy_wynik and punkty > 0:
        nr = font_sredni.render("NOWY REKORD!", True, KOLOR_ZIELONY)
        ekran.blit(nr, (SZEROKOSC // 2 - nr.get_width() // 2, 360))
    fala_t = font_maly.render("Dotarles do fali: {}".format(numer_fali), True, KOLOR_BIALY)
    ekran.blit(fala_t, (SZEROKOSC // 2 - fala_t.get_width() // 2, 410))
    pn = font_duzy.render("ENTER = zagraj ponownie", True, KOLOR_ZIELONY)
    ekran.blit(pn, (SZEROKOSC // 2 - pn.get_width() // 2, 480))


# ---- Główna pętla ----
while True:
    teraz = pygame.time.get_ticks()

    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()
        if zdarzenie.type == pygame.KEYDOWN:
            if zdarzenie.key == pygame.K_RETURN:
                if stan_gry in ("start", "game_over"):
                    nowa_gra()
                    stan_gry = "gra"

    if stan_gry == "start":
        ekran_startowy()
        pygame.display.flip()
        zegar.tick(60)
        continue

    if stan_gry == "game_over":
        ekran_game_over()
        pygame.display.flip()
        zegar.tick(60)
        continue

    # ---- Logika gry ----
    klawisze = pygame.key.get_pressed()
    ruch_statku(klawisze)
    if klawisze[pygame.K_SPACE]:
        strzal(teraz)

    aktualizuj_lasery()
    aktualizuj_asteroidy()
    aktualizuj_wrogow()
    sprawdz_kolizje()

    if zycia <= 0:
        najlepszy_wynik = max(najlepszy_wynik, punkty)
        stan_gry = "game_over"

    # ---- Rysowanie ----
    ekran.fill(KOLOR_TLO)
    rysuj_gwiazdki()

    for ast in asteroidy:
        rysuj_asteroide(ast[0], ast[1], ast[2])
    for wrog in wrogowie:
        rysuj_wroga(wrog[0], wrog[1])
    for laser in lasery:
        pygame.draw.rect(ekran, KOLOR_LASER, (laser[0], laser[1], 6, 22))

    trafiony = teraz - trafiony_czas < CZAS_TRAFIENIA
    rysuj_statek(trafiony)
    wyswietl_hud()

    pygame.display.flip()
    zegar.tick(60)
Efekt
Pełna gra z ekranem startowym, HUD, wielokątnymi statkami, falami wrogów, rekordem sesji i ekranem GAME OVER!
Zadania końcowe
  • Dodaj dźwięk strzału: pygame.mixer.Sound("strzal.wav").play()
  • Zapisz rekord do pliku: open("rekord.txt", "w").write(str(punkty))
  • Dodaj tarczę (shield): dodatkowe życie które miga na niebiesko
  • Stwórz własny poziom bonusowy
  • Dodaj wrogów strzelających laserami w dół!
Mini teoria — Funkcje
Funkcja to nazwany blok kodu który możemy wielokrotnie wywołać. def nazwa_funkcji(): — definicja. nazwa_funkcji() — wywołanie. Funkcje pomagają podzielić długi kod na czytelne, małe kawałki. global pozwala funkcji zmieniać zmienne spoza niej — używamy gdy musimy, ale ostrożnie!

🚀 Co dalej? Twoja gra — Twoje zasady
Ukończyłeś kurs. Zbudowałeś prawdziwą grę od zera — okno, statek, asteroidy, wrogów, strzały, kolizje, fale, HUD, ekran końca gry. Zrobiłeś to sam, w Pythonie.

To nie jest mała rzecz. Większość ludzi zatrzymuje się na pierwszym oknie.
🤔 Czy zauważyłeś, że gra jest… za łatwa?
Pewnie tak. Możesz stanąć w jednym miejscu, wciskać spację i nabijać punkty bez wysiłku. Wrogowie poruszają się tylko poziomo — są przewidywalni. Asteroidy lecą z góry — można je ominąć bez ruszania. Gra Cię nie karze za stanie w miejscu.

To jest celowe — kurs uczył mechanik, nie game designu. Teraz Ty jesteś game designerem. Oto co możesz zmienić.
⚡ Wyzwania — od prostego do trudnego
Poziom 1 — zmiana liczb (15 minut każde)
  • Szybsze asteroidy z każdą falą — w aktualizuj_asteroidy() zmień 3.5 + poziom * 0.4 na 3.5 + poziom * 0.8. Poczuj różnicę na fali 5.
  • Więcej asteroid naraz — zmień inicjalizację: zamiast range(2) użyj range(2 + numer_fali // 2). Każde dwie fale = jedna nowa asteroida.
  • Krótszy cooldown strzału — zmień CZAS_TRAFIENIA z 1500ms na 800ms. Gracz dostaje mniej czasu na "schowanie się" po trafieniu.
  • Szybszy ruch wrogów — zmień min(2 + numer_fali, 8) na min(2 + numer_fali * 1.5, 12). Od fali 4 wrogowie są dwa razy szybsi.
Poziom 2 — nowe mechaniki (2–4 godziny każde)
  • Wrogowie strzelają w dół — najważniejsza zmiana. Dodaj listę lasery_wrogow = []. Co N sekund losowy wróg strzela w kierunku aktualnej pozycji gracza. Jeśli laser wroga trafi statek → zycia -= 1.
    Wskazówka: tak jak strzal(teraz) już masz dla gracza — napisz strzal_wroga(teraz) który losuje wroga i dodaje laser do lasery_wrogow.
  • Asteroidy po zestrzeleniu rozpadają się — gdy laser trafi asteroidę o rozmiarze > 35, zamiast ją usuwać — zastąp dwoma mniejszymi (rozmiar // 2) lecącymi w różnych kierunkach. Małe asteroidy poruszają się szybciej.
    Efekt: zestrzelenie dużej asteroidy to decyzja — zrobi Ci dwie małe i szybkie.
  • Wrogowie nurkują — co kilka sekund losowy wróg zmienia kierunek z poziomego na diagonalny — przez 2 sekundy jedzie w stronę gracza (oblicz kąt: dx = gracz_x - wrog_x, dy = gracz_y - wrog_y). Potem wraca do poziomego ruchu.
    Efekt: nie możesz ignorować wrogów — musisz ich obserwować.
  • Asteroidy z boków ekranu — co trzecia nowa asteroida startuje z x = 0 lub x = SZEROKOSC i leci po przekątnej (dodaj ast[4] = predkosc_x dla ruchu poziomego). Środek ekranu przestaje być bezpieczną kryjówką.
Poziom 3 — game design (weekend projektu)
  • Boss co 5 fal — duży wróg (3× rozmiar, 5 HP, porusza się szybko i strzela) zamiast normalnej fali. Pasek HP bossa na ekranie. Zabicie go daje 500 punktów i bonus życia.
  • Power-upy — zniszczone asteroidy losowo upuszczają ikonę (np. żółty kwadrat). Gracz zbiera ją przelatując przez nią: +1 życie, podwójny strzał przez 10 sekund, tarcza przez 5 sekund.
  • Tryb przetrwania — bez punktów za zestrzelenia. Licznik czasu ile przeżyjesz. Każde 10 sekund = nowa fala. Brak końca — grasz dopóki masz życia.
  • Dwie bronie — spacja = normalny laser, Shift = bomba (wybucha po 1 sekundzie, niszczy wszystko w promieniu 80px). Bomby masz 3, odnawia się 1 co 20 sekund.
📚 Co dalej z nauką?
Jeśli chcesz iść dalej w gamedevie i programowaniu:
  • Pygame — oficjalna dokumentacja: pygame.org/docs — teraz rozumiesz połowę z tego co tam jest
  • Więcej Pygame: Real Python — Pygame primer — sprite'y, grupy, animacje klatek
  • Obejrzyj swoją grę oczami gracza — daj zagrać komuś kto jej nie widział. Patrz co robi, co go frustruje, co go cieszy. To jest playtesting — najważniejszy etap game designu.
🎮 Zbudowałeś grę. Napisałeś ~300 linii Pythona. Rozumiesz pętle, funkcje, listy, słowniki, kolizje, timery i renderowanie.

Większość gier na świecie — niezależnie od silnika — rozwiązuje dokładnie te same problemy które właśnie rozwiązałeś: ruch, kolizja, stan gry, punkty, koniec.

Teraz wiesz jak to działa od środka. Reszta to szczegóły.