Rozdział 12: Budujemy Nyan Cata! — Część 4: Platformy i podwójny skok 🪜
Nowe komendy terminala!
ps — lista procesów
ps aux
Pokazuje wszystkie działające procesy (programy) na komputerze! Dużo tego, nie? Przefiltrujmy:
ps aux | grep python
To pokaże tylko procesy związane z Pythonem. Pipe + grep — klasyka!
kill — zakończ proces
Jeśli Twoja gra się zawiesi (zdarza się!), możesz ją zabić:
ps aux | grep nyan_cat
Zobaczysz numer procesu (PID) — to pierwsza liczba. Potem:
kill NUMER_PID
Albo brutalniej: kill -9 NUMER_PID (siłowe zabicie). Ale zwykle Ctrl+C w terminalu wystarczy!
top — monitor systemu na żywo
top
To jak menedżer zadań w Windowsie! Widzisz procesy posortowane po zużyciu CPU. Naciśnij q żeby wyjść. (Pamiętasz htop? To ładniejsza wersja.)
Dodajemy platformy do gry!
cd ~/projekty/nyan-cat-runner
source venv/bin/activate
cp src/nyan_cat.py src/nyan_cat_v03.py
nano src/nyan_cat.py
Zamień cały plik:
#!/usr/bin/env python3
"""
NYAN CAT RUNNER v0.4
Platformy, trudność rośnie, high score!
"""
import curses
import time
import random
# --- STAŁE ---
GRAVITY = 1
JUMP_FORCE = -4
MAX_JUMPS = 2
BASE_SPEED = 0.08
CAT_FRAME_1 = [
" /\\_/\\ ",
" ( o.o ) ",
" > ^ < ",
" /| |\\ ",
"(_| |_)",
]
CAT_FRAME_2 = [
" /\\_/\\ ",
" ( o.o ) ",
" > ^ < ",
" |/ \\| ",
" (_) (_)",
]
CAT_DEAD = [
" /\\_/\\ ",
" ( x.x ) ",
" > ^ < ",
" /| |\\ ",
" (_| |_) ",
]
RAINBOW_COLORS = "~=~=~=~=~="
OBSTACLE_CACTUS = [" ||| ", "/|||\\", " ||| ", " ||| "]
OBSTACLE_ROCK = [" @@@ ", "@@@@@", "@@@@@"]
OBSTACLE_BIRD = ["\\ /", " \\/ ", " /\\ ", "/ \\"]
def create_star(width, floor_y):
return {
"x": width + random.randint(5, 40),
"y": random.randint(3, floor_y - 6),
"symbol": random.choice(["★", "*", "✦"]),
"collected": False,
}
def create_obstacle(width, floor_y, difficulty):
kinds = ["cactus", "rock"]
if difficulty > 2:
kinds.append("bird")
kind = random.choice(kinds)
if kind == "cactus":
art = OBSTACLE_CACTUS
y = floor_y - len(art)
elif kind == "rock":
art = OBSTACLE_ROCK
y = floor_y - len(art)
else: # bird
art = OBSTACLE_BIRD
y = random.randint(3, floor_y - 10)
return {
"x": width + random.randint(20, 60),
"y": y,
"art": art,
"width": max(len(line) for line in art),
"kind": kind,
}
def create_platform(width, floor_y):
length = random.randint(6, 14)
return {
"x": width + random.randint(10, 50),
"y": random.randint(floor_y - 15, floor_y - 7),
"length": length,
"symbol": "═" * length,
}
def check_collision(y1, x1, h1, w1, y2, x2, h2, w2):
return (x1 < x2 + w2 and x1 + w1 > x2 and
y1 < y2 + h2 and y1 + h1 > y2)
def main(stdscr):
curses.curs_set(0)
stdscr.nodelay(True)
stdscr.timeout(int(BASE_SPEED * 1000))
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
curses.init_pair(6, curses.COLOR_WHITE, -1) # platformy
curses.init_pair(7, curses.COLOR_RED, -1) # game over
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
high_score = 0
scroll_x = 0
lives = 3
invincible = 0
difficulty = 1 # rośnie z czasem!
stars = [create_star(width, floor_y) for _ in range(5)]
obstacles = [create_obstacle(width, floor_y, difficulty)]
platforms = [create_platform(width, floor_y) for _ in range(2)]
running = True
game_over = False
while running:
# 1. KLAWISZE
key = stdscr.getch()
if game_over:
if key == ord('r') or key == ord('R'):
cat_y = float(floor_y - 5)
velocity_y = 0.0
jumps_left = MAX_JUMPS
score = 0
lives = 3
difficulty = 1
frame = 0
stars = [create_star(width, floor_y) for _ in range(5)]
obstacles = [create_obstacle(width, floor_y, difficulty)]
platforms = [create_platform(width, floor_y) for _ in range(2)]
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
# Lądowanie na podłodze
on_ground = False
if cat_y >= floor_y - 5:
cat_y = floor_y - 5
velocity_y = 0
jumps_left = MAX_JUMPS
on_ground = True
# Lądowanie na platformach (tylko gdy spada!)
if velocity_y > 0 and not on_ground:
for plat in platforms:
cat_bottom = int(cat_y) + 5
if (cat_bottom >= plat["y"] and cat_bottom <= plat["y"] + 2 and
cat_x + 9 > plat["x"] and cat_x < plat["x"] + plat["length"]):
cat_y = float(plat["y"] - 5)
velocity_y = 0
jumps_left = MAX_JUMPS
on_ground = True
break
if cat_y < 0:
cat_y = 0
velocity_y = 0
# 3. AKTUALIZACJA
frame += 1
scroll_x += 1
# Trudność rośnie!
difficulty = 1 + score // 100
# Prędkość gry rośnie z trudnością
speed = max(0.04, BASE_SPEED - (difficulty - 1) * 0.005)
stdscr.timeout(int(speed * 1000))
if invincible > 0:
invincible -= 1
if frame % 5 == 0:
score += 1
# Przesuń obiekty
move_speed = 1
for star in stars:
star["x"] -= move_speed
for obs in obstacles:
obs["x"] -= move_speed
for plat in platforms:
plat["x"] -= move_speed
# Czyszczenie
stars = [s for s in stars if s["x"] > -5 and not s["collected"]]
obstacles = [o for o in obstacles if o["x"] > -10]
platforms = [p for p in platforms if p["x"] > -20]
# Nowe obiekty
while len(stars) < 5:
stars.append(create_star(width, floor_y))
if len(obstacles) < 1 + difficulty and random.random() < 0.02 * difficulty:
obstacles.append(create_obstacle(width, floor_y, difficulty))
while len(platforms) < 2:
platforms.append(create_platform(width, floor_y))
# 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
# 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
if lives <= 0:
game_over = True
if score > high_score:
high_score = score
# 4. RYSOWANIE
stdscr.erase()
# Nagłówek
diff_str = f"Lv.{difficulty}"
header = f" NYAN CAT RUNNER | ★ {score} | ♥ {'❤' * lives}{'🖤' * (3-lives)} | {diff_str} | HI:{high_score} "
stdscr.addstr(0, 0, header[:width-1], curses.A_BOLD)
# 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
# Platformy
for plat in platforms:
if 0 < plat["x"] < width - 1:
try:
display = plat["symbol"][:width - plat["x"] - 1]
stdscr.addstr(plat["y"], max(0, plat["x"]), display,
curses.color_pair(6) | curses.A_BOLD)
except curses.error:
pass
# Gwiazdki
for star in stars:
if not star["collected"] and 0 < star["x"] < width - 2:
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_COLORS) and 0 <= rainbow_y < height - 1:
try:
stdscr.addstr(rainbow_y, cat_x - len(RAINBOW_COLORS),
RAINBOW_COLORS, curses.color_pair(4) | curses.A_BOLD)
except curses.error:
pass
# Kot
if game_over:
current_cat = CAT_DEAD
color = curses.color_pair(2) # czerwony
elif invincible == 0 or frame % 2 == 0:
current_cat = CAT_FRAME_1 if (frame // 3) % 2 == 0 else CAT_FRAME_2
color = curses.color_pair(3)
else:
current_cat = None
if current_cat:
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, color)
except curses.error:
pass
# Game Over
if game_over:
box_w = 36
box_h = 7
by = height // 2 - box_h // 2
bx = width // 2 - box_w // 2
border = "╔" + "═" * (box_w - 2) + "╗"
empty = "║" + " " * (box_w - 2) + "║"
bottom = "╚" + "═" * (box_w - 2) + "╝"
msg1 = "GAME OVER!"
msg2 = f"Wynik: {score} punktów"
msg3 = "[R] Jeszcze raz [Q] Wyjście"
try:
stdscr.addstr(by, bx, border, curses.color_pair(7) | curses.A_BOLD)
stdscr.addstr(by+1, bx, empty, curses.color_pair(7))
stdscr.addstr(by+2, bx, f"║{msg1:^{box_w-2}}║", curses.color_pair(7) | curses.A_BOLD)
stdscr.addstr(by+3, bx, f"║{msg2:^{box_w-2}}║", curses.color_pair(7))
stdscr.addstr(by+4, bx, empty, curses.color_pair(7))
stdscr.addstr(by+5, bx, f"║{msg3:^{box_w-2}}║", curses.color_pair(7))
stdscr.addstr(by+6, bx, bottom, curses.color_pair(7) | curses.A_BOLD)
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
Nowości!
- Platformy (═══) — można na nich lądować! Skacz na nie żeby zbierać gwiazdki w powietrzu
- Ptaki — latające przeszkody (pojawiają się od poziomu trudności 3)
- Rosnąca trudność — im więcej punktów, tym szybciej i więcej przeszkód
- High Score — najlepszy wynik zapamiętywany w sesji
- Ładny Game Over — z ramką!
- Martwy kot — ( x.x ) gdy przegrana
🏆 Wyzwanie
- Użyj
diff src/nyan_cat_v03.py src/nyan_cat.py | wc -l— ile linii się zmieniło? - Użyj
ps aux | grep pythonpodczas gdy gra działa (w drugim terminalu!) - Zmień rozmiar platform (stała
lengthwcreate_platform) - Dodaj własną przeszkodę ASCII!