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! 🌟
- Zbieraj gwiazdki (★) = +10 punktów
- Unikaj kaktusów i skał (czerwone) = -1 życie
- Po trafieniu kot mruga (jest nieśmiertelny przez chwilę)
- Jak stracisz 3 życia = Game Over
- R = restart, Q = wyjście
Co nowego?
import random— losowość! Potrzebna do losowych pozycji gwiazdek i przeszkódcurses.init_pair()— definiowanie kolorów! Para (numer, kolor tekstu, kolor tła)curses.color_pair(1)— użyj koloru nr 1curses.A_BOLD— pogrubieniecurses.A_REVERSE— odwrócone kolory (tekst ↔ tło)- Kolizje — sprawdzamy czy prostokąt kota nachodzi na prostokąt przeszkody
🏆 Wyzwanie
- Porównaj wersję 0.2 i 0.3:
diff src/nyan_cat_v02.py src/nyan_cat.py | head -50 - Zmień kolory! (zamień
COLOR_CYANnaCOLOR_GREENdla kota itd.) - Dodaj nowy typ przeszkody — narysuj swoją w ASCII!
- Zmień ile punktów daje gwiazdka (znajdź
score += 10) - Użyj
history | grep pythonżeby zobaczyć ile razy uruchamiałaś grę