Rozdział 10: Budujemy Nyan Cata! — Część 2: Ruch i sterowanie 🎮

Teraz dodajmy pętlę gry — serce każdej gry! Kot będzie się poruszał, a Ty będziesz mogła sterować skokami.

Aktualizujemy plik gry

nano src/nyan_cat.py

Zamień cały plik na nowy kod (skasuj wszystko Ctrl+K i wpisz/wklej):

#!/usr/bin/env python3
"""
NYAN CAT RUNNER v0.2
Teraz z ruchem i skokami!
"""
import curses
import time

# --- STAŁE (wartości które nie zmieniają się w trakcie gry) ---
GRAVITY = 1        # siła grawitacji (im więcej, tym szybciej spada)
JUMP_FORCE = -4    # siła skoku (ujemna bo w terminalu y=0 jest NA GÓRZE!)
MAX_JUMPS = 2      # ile razy można skoczyć (2 = podwójny skok!)
GAME_SPEED = 0.08  # czas między klatkami (mniej = szybciej)

# Ramki animacji kota (2 klatki na nogi!)
CAT_FRAME_1 = [
    "  /\\_/\\  ",
    " ( o.o ) ",
    "  > ^ <  ",
    " /|   |\\ ",
    "(_|   |_)",
]

CAT_FRAME_2 = [
    "  /\\_/\\  ",
    " ( o.o ) ",
    "  > ^ <  ",
    "  |/  \\| ",
    " (_)  (_)",
]

# Tęczowy ogon!
RAINBOW = "~=~=~=~=~="


def main(stdscr):
    # --- USTAWIENIA EKRANU ---
    curses.curs_set(0)             # ukryj kursor
    stdscr.nodelay(True)           # getch() nie czeka — gra działa cały czas!
    stdscr.timeout(int(GAME_SPEED * 1000))  # timeout w milisekundach

    height, width = stdscr.getmaxyx()

    # --- STAN GRY ---
    cat_x = 5
    floor_y = height - 3           # pozycja podłogi
    cat_y = float(floor_y - 5)     # kot stoi na podłodze (float bo będziemy dodawać ułamki)
    velocity_y = 0.0               # prędkość w pionie (0 = stoi)
    jumps_left = MAX_JUMPS         # ile skoków zostało
    frame = 0                      # numer klatki animacji
    score = 0                      # punkty (na razie za czas)
    scroll_x = 0                   # jak daleko "przescrollował" się świat

    # --- PĘTLA GRY ---
    running = True
    while running:
        # 1. ODCZYTAJ KLAWISZE
        key = stdscr.getch()

        if key == ord('q') or key == ord('Q'):
            # Q = wyjście z gry
            running = False
            continue

        if key == ord(' ') or key == curses.KEY_UP:
            # Spacja lub strzałka w górę = SKOK
            if jumps_left > 0:
                velocity_y = JUMP_FORCE
                jumps_left -= 1

        # 2. FIZYKA
        # Grawitacja ciągnie kota w dół
        velocity_y += GRAVITY
        cat_y += velocity_y

        # Nie spadaj poniżej podłogi!
        if cat_y >= floor_y - 5:
            cat_y = floor_y - 5
            velocity_y = 0
            jumps_left = MAX_JUMPS  # reset skoków po lądowaniu

        # Nie wylatuj ponad ekran!
        if cat_y < 0:
            cat_y = 0
            velocity_y = 0

        # 3. AKTUALIZUJ STAN GRY
        frame += 1
        scroll_x += 1
        if frame % 5 == 0:  # co 5 klatek = punkt
            score += 1

        # 4. RYSUJ WSZYSTKO!
        stdscr.erase()  # wyczyść ekran (szybsze niż clear())

        # Tytuł i punkty
        header = f" NYAN CAT RUNNER | Punkty: {score} | Skoki: {'⬆' * jumps_left} "
        stdscr.addstr(0, 0, header[:width-1])

        # Podłoga
        floor_line = ""
        for i in range(width - 1):
            if (i + scroll_x) % 4 == 0:
                floor_line += "█"
            else:
                floor_line += "▄"
        try:
            stdscr.addstr(floor_y, 0, floor_line)
        except curses.error:
            pass

        # Tęczowy ogon za kotem
        rainbow_y = int(cat_y) + 2  # środek kota
        if cat_x > len(RAINBOW):
            try:
                stdscr.addstr(rainbow_y, cat_x - len(RAINBOW), RAINBOW)
            except curses.error:
                pass

        # Kot (animacja — zmienia nogi co kilka klatek)
        current_cat = CAT_FRAME_1 if (frame // 3) % 2 == 0 else CAT_FRAME_2
        for i, line in enumerate(current_cat):
            y_pos = int(cat_y) + i
            if 0 <= y_pos < height - 1:
                try:
                    stdscr.addstr(y_pos, cat_x, line)
                except curses.error:
                    pass

        # Instrukcja
        help_text = "[SPACJA/↑]=Skok  [Q]=Wyjście"
        try:
            stdscr.addstr(height - 1, 0, help_text[:width-1])
        except curses.error:
            pass

        # Odśwież!
        stdscr.refresh()

# Start!
curses.wrapper(main)

Zapisz i uruchom:

python3 src/nyan_cat.py

WOW! Kot się rusza! 🎮

Co nowego się tu dzieje?

Pętla gry

Każda gra ma taką pętlę:

  1. Odczytaj klawisze — co gracz robi?
  2. Fizyka — grawitacja, kolizje
  3. Aktualizuj — zmień stan gry
  4. Rysuj — pokaż nowy obraz

Ta pętla wykonuje się ~12 razy na sekundę (bo GAME_SPEED = 0.08).

stdscr.nodelay(True) i timeout()

Normalne getch() zatrzymuje program i czeka na klawisz. Ale w grze nie możemy czekać — wszystko musi się dziać cały czas! nodelay(True) sprawia, że getch() zwraca -1 jeśli nic nie naciśnięto (zamiast czekać). timeout() kontroluje jak długo czeka.

Grawitacja

velocity_y to prędkość w pionie. Grawitacja dodaje do niej co klatkę. Skok ustawia ją na ujemną wartość (bo w terminalu y rośnie w dół!).

try/except curses.error: pass

To obsługa błędów — jeśli próbujemy rysować poza ekranem, curses wyrzuca błąd. try/except łapie go i ignoruje.

🏆 Wyzwanie

  1. Zmień siłę skoku (stała JUMP_FORCE) — co się stanie z -6? A z -2?
  2. Zmień grawitację — jak wpływa na grę?
  3. Zmień MAX_JUMPS na 3 — potrójny skok!
  4. Zmień GAME_SPEED — spróbuj 0.05 (szybciej) i 0.15 (wolniej)