Rozdział 11: Budujemy Nyan Cata! — Część 3: Gwiazdki i przeszkody ⭐

Czas na najważniejsze elementy gry — coś do zbierania i coś do unikania!

Ale najpierw nauczmy się nowej komendy terminalowej!

Nowa komenda: diff

diff porównuje dwa pliki i pokazuje różnice. Zróbmy backup przed zmianami:

cp src/nyan_cat.py src/nyan_cat_v02.py

Teraz po edycji będziesz mogła zobaczyć co się zmieniło:

diff src/nyan_cat_v02.py src/nyan_cat.py

Nowa komenda: history

history

Pokazuje wszystkie komendy, które wpisywałaś! Możesz wyszukać konkretne:

history | grep "nano"

To pokaże wszystkie razy gdy używałaś nano. Pipe i grep w akcji!

Możesz też użyć Ctrl + R w terminalu — wpisuj fragment komendy i terminal wyszuka ją w historii. Super przydatne!

Teraz aktualizujmy grę!

nano src/nyan_cat.py

Znów zamień cały plik:

#!/usr/bin/env python3
"""
NYAN CAT RUNNER v0.3
Gwiazdki i przeszkody!
"""
import curses
import time
import random

# --- STAŁE ---
GRAVITY = 1
JUMP_FORCE = -4
MAX_JUMPS = 2
GAME_SPEED = 0.08

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

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

RAINBOW = "~=~=~=~=~="

# Typy przeszkód
OBSTACLE_CACTUS = [
    " ||| ",
    "/|||\\ ",
    " ||| ",
    " ||| ",
]

OBSTACLE_ROCK = [
    " @@@ ",
    "@@@@@",
    "@@@@@",
]


def create_star(width, floor_y):
    """Stwórz gwiazdkę w losowej pozycji"""
    return {
        "x": width + random.randint(0, 30),
        "y": random.randint(3, floor_y - 6),
        "symbol": random.choice(["★", "*", "✦", "⭐"]),
        "collected": False,
    }


def create_obstacle(width, floor_y):
    """Stwórz przeszkodę"""
    kind = random.choice(["cactus", "rock"])
    if kind == "cactus":
        art = OBSTACLE_CACTUS
    else:
        art = OBSTACLE_ROCK

    return {
        "x": width + random.randint(10, 50),
        "y": floor_y - len(art),
        "art": art,
        "width": max(len(line) for line in art),
    }


def check_collision(cat_y, cat_x, cat_h, cat_w, obj_y, obj_x, obj_h, obj_w):
    """Sprawdź czy dwa prostokąty na siebie nachodzą"""
    return (cat_x < obj_x + obj_w and
            cat_x + cat_w > obj_x and
            cat_y < obj_y + obj_h and
            cat_y + cat_h > obj_y)


def main(stdscr):
    # --- USTAWIENIA ---
    curses.curs_set(0)
    stdscr.nodelay(True)
    stdscr.timeout(int(GAME_SPEED * 1000))

    # Inicjalizacja kolorów!
    curses.start_color()
    curses.use_default_colors()
    curses.init_pair(1, curses.COLOR_YELLOW, -1)   # gwiazdki
    curses.init_pair(2, curses.COLOR_RED, -1)       # przeszkody
    curses.init_pair(3, curses.COLOR_CYAN, -1)      # kot
    curses.init_pair(4, curses.COLOR_MAGENTA, -1)   # tęcza
    curses.init_pair(5, curses.COLOR_GREEN, -1)     # podłoga

    height, width = stdscr.getmaxyx()

    # --- STAN GRY ---
    cat_x = 5
    floor_y = height - 3
    cat_y = float(floor_y - 5)
    velocity_y = 0.0
    jumps_left = MAX_JUMPS
    frame = 0
    score = 0
    scroll_x = 0
    lives = 3
    invincible = 0  # klatki nieśmiertelności po trafieniu

    # Gwiazdki i przeszkody
    stars = [create_star(width, floor_y) for _ in range(5)]
    obstacles = [create_obstacle(width, floor_y)]

    # --- PĘTLA GRY ---
    running = True
    game_over = False

    while running:
        # 1. KLAWISZE
        key = stdscr.getch()

        if game_over:
            if key == ord('r') or key == ord('R'):
                # Restart!
                cat_y = float(floor_y - 5)
                velocity_y = 0.0
                jumps_left = MAX_JUMPS
                score = 0
                lives = 3
                stars = [create_star(width, floor_y) for _ in range(5)]
                obstacles = [create_obstacle(width, floor_y)]
                game_over = False
                invincible = 0
            elif key == ord('q') or key == ord('Q'):
                running = False
            continue

        if key == ord('q') or key == ord('Q'):
            running = False
            continue

        if key == ord(' ') or key == curses.KEY_UP:
            if jumps_left > 0:
                velocity_y = JUMP_FORCE
                jumps_left -= 1

        # 2. FIZYKA
        velocity_y += GRAVITY
        cat_y += velocity_y

        if cat_y >= floor_y - 5:
            cat_y = floor_y - 5
            velocity_y = 0
            jumps_left = MAX_JUMPS

        if cat_y < 0:
            cat_y = 0
            velocity_y = 0

        # 3. AKTUALIZACJA
        frame += 1
        scroll_x += 1

        if invincible > 0:
            invincible -= 1

        # Przesuń gwiazdki
        for star in stars:
            star["x"] -= 1

        # Przesuń przeszkody
        for obs in obstacles:
            obs["x"] -= 1

        # Usuń gwiazdki/przeszkody które wyleciały za ekran
        stars = [s for s in stars if s["x"] > -5 and not s["collected"]]
        obstacles = [o for o in obstacles if o["x"] > -10]

        # Dodaj nowe gwiazdki
        while len(stars) < 5:
            stars.append(create_star(width, floor_y))

        # Dodaj nowe przeszkody
        if len(obstacles) < 2 and random.random() < 0.02:
            obstacles.append(create_obstacle(width, floor_y))

        # Sprawdź kolizje z gwiazdkami
        for star in stars:
            if not star["collected"]:
                if abs(star["x"] - cat_x) < 8 and abs(star["y"] - int(cat_y) - 2) < 3:
                    star["collected"] = True
                    score += 10

        # Sprawdź kolizje z przeszkodami
        if invincible == 0:
            for obs in obstacles:
                if check_collision(
                    int(cat_y), cat_x, 5, 9,
                    obs["y"], obs["x"], len(obs["art"]), obs["width"]
                ):
                    lives -= 1
                    invincible = 30  # 30 klatek nieśmiertelności
                    if lives <= 0:
                        game_over = True

        # 4. RYSOWANIE
        stdscr.erase()

        # Nagłówek
        header = f" NYAN CAT RUNNER | ★ {score} | ♥ {'❤' * lives} | 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, curses.color_pair(5))
        except curses.error:
            pass

        # Gwiazdki
        for star in stars:
            if not star["collected"] and 0 < star["x"] < width - 1:
                try:
                    stdscr.addstr(star["y"], star["x"], star["symbol"],
                                curses.color_pair(1) | curses.A_BOLD)
                except curses.error:
                    pass

        # Przeszkody
        for obs in obstacles:
            for i, line in enumerate(obs["art"]):
                y = obs["y"] + i
                x = obs["x"]
                if 0 <= y < height - 1 and 0 < x < width - len(line):
                    try:
                        stdscr.addstr(y, x, line, curses.color_pair(2))
                    except curses.error:
                        pass

        # Tęcza
        rainbow_y = int(cat_y) + 2
        if cat_x > len(RAINBOW) and 0 <= rainbow_y < height - 1:
            try:
                stdscr.addstr(rainbow_y, cat_x - len(RAINBOW), RAINBOW,
                            curses.color_pair(4))
            except curses.error:
                pass

        # Kot (mruga gdy nieśmiertelny)
        if invincible == 0 or frame % 2 == 0:
            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, curses.color_pair(3))
                    except curses.error:
                        pass

        # Game Over
        if game_over:
            msg1 = "  GAME OVER!  "
            msg2 = f"  Wynik: {score} punktów  "
            msg3 = "  [R] Jeszcze raz  [Q] Wyjście  "
            cy = height // 2
            cx1 = (width - len(msg1)) // 2
            cx2 = (width - len(msg2)) // 2
            cx3 = (width - len(msg3)) // 2
            try:
                stdscr.addstr(cy - 1, cx1, msg1, curses.A_REVERSE | curses.A_BOLD)
                stdscr.addstr(cy, cx2, msg2, curses.A_REVERSE)
                stdscr.addstr(cy + 1, cx3, msg3, curses.A_REVERSE)
            except curses.error:
                pass

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

        stdscr.refresh()

curses.wrapper(main)

Uruchom!

python3 src/nyan_cat.py

KOLORY! GWIAZDKI! PRZESZKODY! 🌟

Co nowego?

🏆 Wyzwanie

  1. Porównaj wersję 0.2 i 0.3: diff src/nyan_cat_v02.py src/nyan_cat.py | head -50
  2. Zmień kolory! (zamień COLOR_CYAN na COLOR_GREEN dla kota itd.)
  3. Dodaj nowy typ przeszkody — narysuj swoją w ASCII!
  4. Zmień ile punktów daje gwiazdka (znajdź score += 10)
  5. Użyj history | grep python żeby zobaczyć ile razy uruchamiałaś grę