Загрузка данных
import pygame
import pygame.gfxdraw
import random
import math
import sys
pygame.init()
# --- Размеры экрана и сетки ---
WIDTH, HEIGHT = 1100, 650
GRID_COLS, GRID_ROWS = 9, 5
CELL_W, CELL_H = 88, 92
GRID_X, GRID_Y = 250, 140
FPS = 60
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Plants vs. Zombies - Master Edition")
clock = pygame.time.Clock()
# Шрифты
f_tiny = pygame.font.SysFont("Arial", 12, bold=True)
f_small = pygame.font.SysFont("Arial", 16, bold=True)
f_med = pygame.font.SysFont("Arial", 22, bold=True)
f_big = pygame.font.SysFont("Arial", 44, bold=True)
# База данных всех доступных растений
PLANT_DATA = {
"peashooter": {"cost": 100, "hp": 150, "cd": 120, "name": "Peashooter", "desc": "Shoots peas at zombies"},
"sunflower": {"cost": 50, "hp": 120, "cd": 120, "name": "Sunflower", "desc": "Generates extra sun"},
"cherrybomb": {"cost": 150, "hp": 1000,"cd": 600, "name": "Cherry Bomb","desc": "Explodes in 3x3 area"},
"wallnut": {"cost": 50, "hp": 500, "cd": 400, "name": "Wall-nut", "desc": "Blocks incoming zombies"},
"snowpea": {"cost": 175, "hp": 150, "cd": 150, "name": "Snow Pea", "desc": "Shoots chilling peas"}
}
# Конфигурация уровней (волны зомби)
LEVELS = [
{
"name": "Level 1: Back to the Lawn",
"sun": 150,
"waves": [
["regular", "regular"],
["regular", "conehead"],
["regular", "conehead", "regular"]
]
},
{
"name": "Level 2: Armored Threat",
"sun": 200,
"waves": [
["regular", "conehead"],
["conehead", "buckethead"],
["buckethead", "conehead", "regular", "buckethead"]
]
},
{
"name": "Level 3: Gargantuar's Wrath (BOSS)",
"sun": 300,
"waves": [
["buckethead", "conehead", "buckethead"],
["gargantuar"],
["gargantuar", "buckethead", "conehead"]
]
}
]
# --- Функции качественной отрисовки ---
def draw_smooth_circle(surf, x, y, r, color):
pygame.gfxdraw.aacircle(surf, int(x), int(y), int(r), color)
pygame.gfxdraw.filled_circle(surf, int(x), int(y), int(r), color)
def draw_smooth_ellipse(surf, rect, color):
pygame.draw.ellipse(surf, color, rect)
# --- Игровые сущности ---
class Particle:
def __init__(self, x, y, color, size, vx, vy, life):
self.x, self.y = x, y
self.color = color
self.size = size
self.vx, self.vy = vx, vy
self.life = life
self.max_life = life
def update(self):
self.x += self.vx
self.y += self.vy
self.life -= 1
def draw(self, surf):
if self.life > 0:
alpha = int((self.life / self.max_life) * 255)
s = pygame.Surface((self.size * 2, self.size * 2), pygame.SRCALPHA)
pygame.draw.circle(s, (*self.color, alpha), (self.size, self.size), self.size)
surf.blit(s, (self.x - self.size, self.y - self.size))
class Lawnmower:
def __init__(self, row):
self.row = row
self.x = GRID_X - 60
self.y = GRID_Y + row * CELL_H + CELL_H // 2
self.active = False
self.used = False
def update(self, zombies):
if self.active:
self.x += 10
for z in zombies:
if z.row == self.row and abs(z.x - self.x) < 40:
z.take_damage(9999)
if self.x > WIDTH + 50:
self.used = True
else:
for z in zombies:
if z.row == self.row and z.x <= self.x + 30:
self.active = True
break
def draw(self, surf):
if self.used: return
x, y = int(self.x), int(self.y)
pygame.draw.rect(surf, (180, 20, 20), (x - 20, y - 15, 35, 25), border_radius=4)
pygame.draw.circle(surf, (40, 40, 40), (x - 12, y + 12), 8)
pygame.draw.circle(surf, (40, 40, 40), (x + 10, y + 12), 8)
pygame.draw.line(surf, (200, 200, 200), (x - 15, y - 10), (x - 25, y - 30), 4)
class Sun:
def __init__(self, x, y, target_y=None):
self.x, self.y = x, y
self.target_y = target_y if target_y else y
self.radius = 22
self.alive = True
self.timer = 0
self.pulse = 0.0
def update(self):
if self.y < self.target_y:
self.y += 2
self.timer += 1
self.pulse += 0.08
if self.timer > 650:
self.alive = False
def draw(self, surf):
r = self.radius + math.sin(self.pulse) * 3
cx, cy = int(self.x), int(self.y)
# Лучи
for i in range(8):
ang = self.pulse + i * (math.pi / 4)
px = cx + math.cos(ang) * (r + 8)
py = cy + math.sin(ang) * (r + 8)
pygame.draw.circle(surf, (255, 180, 0), (int(px), int(py)), 6)
draw_smooth_circle(surf, cx, cy, r + 2, (255, 140, 0))
draw_smooth_circle(surf, cx, cy, r, (255, 225, 30))
draw_smooth_circle(surf, cx - 4, cy - 4, 3, (255, 255, 255))
class Projectile:
def __init__(self, x, y, row, is_ice=False):
self.x, self.y = x, y
self.row = row
self.is_ice = is_ice
self.speed = 7
self.damage = 25
self.alive = True
def update(self):
self.x += self.speed
if self.x > WIDTH:
self.alive = False
def draw(self, surf):
color = (60, 200, 255) if self.is_ice else (50, 205, 50)
draw_smooth_circle(surf, self.x, self.y, 8, color)
draw_smooth_circle(surf, self.x - 2, self.y - 2, 3, (255, 255, 255))
class Plant:
def __init__(self, col, row, p_type):
self.col, self.row = col, row
self.type = p_type
self.data = PLANT_DATA[p_type]
self.hp = self.data["hp"]
self.max_hp = self.hp
self.x = GRID_X + col * CELL_W + CELL_W // 2
self.y = GRID_Y + row * CELL_H + CELL_H // 2
self.action_timer = 0
self.anim_phase = random.random() * 6.0
self.alive = True
def update(self, game):
self.anim_phase += 0.05
self.action_timer += 1
if self.type == "sunflower" and self.action_timer >= 450:
game.suns.append(Sun(self.x + random.randint(-15, 15), self.y - 15))
self.action_timer = 0
elif self.type in ["peashooter", "snowpea"]:
# Проверяем наличие зомби впереди на той же линии
has_targets = any(z.row == self.row and z.x > self.x for z in game.zombies)
if has_targets and self.action_timer >= 90:
is_ice = (self.type == "snowpea")
game.projectiles.append(Projectile(self.x + 24, self.y - 8, self.row, is_ice))
self.action_timer = 0
elif self.type == "cherrybomb":
if self.action_timer >= 120: # Взрыв через 2 секунды
self.alive = False
game.spawn_explosion(self.x, self.y, self.col, self.row)
def draw(self, surf):
x, y = self.x, self.y + int(math.sin(self.anim_phase) * 2)
# Стебель и базовые листья у земли
pygame.draw.ellipse(surf, (34, 120, 34), (x - 20, y + 18, 18, 10))
pygame.draw.ellipse(surf, (34, 120, 34), (x + 2, y + 18, 18, 10))
pygame.draw.line(surf, (34, 139, 34), (x, y + 20), (x, y), 6)
if self.type in ["peashooter", "snowpea"]:
head_col = (100, 200, 255) if self.type == "snowpea" else (50, 180, 50)
# Голова
draw_smooth_circle(surf, x, y - 8, 18, head_col)
# Ствол (раструб)
pygame.draw.rect(surf, head_col, (x + 6, y - 15, 18, 14), border_radius=4)
draw_smooth_circle(surf, x + 24, y - 8, 7, (20, 80, 20))
# Глаза
draw_smooth_circle(surf, x + 4, y - 14, 4, (255, 255, 255))
draw_smooth_circle(surf, x + 6, y - 14, 2, (0, 0, 0))
if self.type == "snowpea":
# Ледяные кристаллы сзади
for angle in [-20, 0, 20]:
draw_smooth_circle(surf, x - 14 + angle//2, y - 14 + angle, 5, (180, 235, 255))
elif self.type == "sunflower":
# Лепестки
for i in range(12):
a = self.anim_phase * 0.5 + i * (math.pi / 6)
lx = x + math.cos(a) * 20
ly = (y - 8) + math.sin(a) * 20
draw_smooth_circle(surf, lx, ly, 7, (255, 200, 0))
# Сердцевина
draw_smooth_circle(surf, x, y - 8, 15, (139, 69, 19))
# Улыбающееся лицо
draw_smooth_circle(surf, x - 4, y - 12, 3, (0, 0, 0))
draw_smooth_circle(surf, x + 4, y - 12, 3, (0, 0, 0))
pygame.draw.arc(surf, (0, 0, 0), (x - 6, y - 10, 12, 8), math.pi, 2 * math.pi, 2)
elif self.type == "wallnut":
# Стенорех
pygame.draw.ellipse(surf, (160, 82, 45), (x - 18, y - 26, 36, 52))
draw_smooth_circle(surf, x - 6, y - 12, 5, (255, 255, 255))
draw_smooth_circle(surf, x + 6, y - 12, 5, (255, 255, 255))
draw_smooth_circle(surf, x - 4, y - 12, 2, (0, 0, 0))
draw_smooth_circle(surf, x + 4, y - 12, 2, (0, 0, 0))
# Трещины при получении урона
if self.hp < self.max_hp * 0.6:
pygame.draw.line(surf, (80, 40, 20), (x - 6, y), (x + 2, y + 8), 2)
if self.hp < self.max_hp * 0.3:
pygame.draw.line(surf, (80, 40, 20), (x + 4, y - 4), (x + 10, y + 6), 2)
elif self.type == "cherrybomb":
scale = 1.0 + (self.action_timer / 120) * 0.3
r = int(14 * scale)
c1_x, c1_y = x - 10, y
c2_x, c2_y = x + 10, y - 4
# Стебель
pygame.draw.line(surf, (0, 100, 0), (c1_x, c1_y), (x, y - 24), 3)
pygame.draw.line(surf, (0, 100, 0), (c2_x, c2_y), (x, y - 24), 3)
# Вишни
draw_smooth_circle(surf, c1_x, c1_y, r, (200, 20, 20))
draw_smooth_circle(surf, c2_x, c2_y, r, (220, 20, 20))
class Zombie:
def __init__(self, row, z_type="regular"):
self.row = row
self.type = z_type
self.x = WIDTH + random.randint(20, 80)
self.y = GRID_Y + row * CELL_H + CELL_H // 2
self.eating = False
self.alive = True
self.frozen_timer = 0
self.walk_cycle = 0.0
if z_type == "regular":
self.hp = 120
self.speed = 0.35
self.damage = 0.6
elif z_type == "conehead":
self.hp = 280
self.speed = 0.35
self.damage = 0.6
elif z_type == "buckethead":
self.hp = 550
self.speed = 0.35
self.damage = 0.6
elif z_type == "gargantuar":
self.hp = 1600
self.speed = 0.22
self.damage = 999 # Сминает растение сразу
def take_damage(self, dmg, freeze=False):
self.hp -= dmg
if freeze:
self.frozen_timer = 180
if self.hp <= 0:
self.alive = False
def update(self, plants):
if self.frozen_timer > 0:
self.frozen_timer -= 1
spd = self.speed * 0.5
else:
spd = self.speed
self.eating = False
self.walk_cycle += 0.06
# Поиск растений для атаки
for plant in plants:
if plant.row == self.row and abs((plant.x + 10) - self.x) < 25:
self.eating = True
plant.hp -= self.damage
break
if not self.eating:
self.x -= spd
def draw(self, surf):
x = int(self.x)
y = int(self.y)
wobble = math.sin(self.walk_cycle) * 3
tint = (130, 180, 255) if self.frozen_timer > 0 else (120, 150, 120)
if self.type == "gargantuar":
# Гигант Гаргантюа
gy = y - 20
pygame.draw.rect(surf, (60, 50, 40), (x - 25, gy - 20, 50, 65), border_radius=6) # Тело
draw_smooth_circle(surf, x, gy - 40, 26, tint) # Огромная голова
draw_smooth_circle(surf, x - 8, gy - 44, 4, (255, 0, 0))
# Телеграфный столб/дубина в руках
pygame.draw.rect(surf, (80, 40, 15), (x - 45, gy - 60, 16, 90), border_radius=3)
else:
# Обычные зомби и в броне
# Ноги
pygame.draw.line(surf, (40, 40, 80), (x - 5, y + 20), (x - 10 + int(wobble), y + 38), 6)
pygame.draw.line(surf, (40, 40, 80), (x + 5, y + 20), (x + 8 - int(wobble), y + 38), 6)
# Рубашка/пиджак
pygame.draw.rect(surf, (70, 60, 50), (x - 12, y - 10, 24, 32), border_radius=4)
# Руки вытянуты вперед
pygame.draw.line(surf, tint, (x - 5, y - 4), (x - 22, y - 4 + int(wobble)), 5)
# Голова
draw_smooth_circle(surf, x, y - 24, 15, tint)
# Красные глаза
draw_smooth_circle(surf, x - 5, y - 26, 3, (255, 0, 0))
draw_smooth_circle(surf, x + 3, y - 26, 3, (255, 0, 0))
# Головные уборы
if self.type == "conehead":
points = [(x - 12, y - 32), (x + 12, y - 32), (x, y - 62)]
pygame.draw.polygon(surf, (255, 140, 0), points)
elif self.type == "buckethead":
pygame.draw.rect(surf, (160, 160, 170), (x - 12, y - 46, 24, 22), border_radius=2)
# --- Главный Контроллер Игры ---
class GameManager:
def __init__(self):
self.state = "MENU" # MENU, CHOOSER, PLAY, PAUSE, WIN, GAMEOVER
self.current_level_idx = 0
self.sun = 150
self.selected_seed = None
self.all_seeds = list(PLANT_DATA.keys())
self.deck = ["peashooter", "sunflower", "cherrybomb", "wallnut", "snowpea"]
self.cooldowns = {k: 0 for k in self.all_seeds}
self.grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
self.plants = []
self.zombies = []
self.projectiles = []
self.suns = []
self.particles = []
self.mowers = []
self.waves_data = []
self.wave_index = 0
self.wave_timer = 0
self.sky_sun_timer = 0
def start_level(self, idx):
self.current_level_idx = idx
lvl = LEVELS[idx]
self.sun = lvl["sun"]
self.grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
self.plants.clear()
self.zombies.clear()
self.projectiles.clear()
self.suns.clear()
self.particles.clear()
self.mowers = [Lawnmower(r) for r in range(GRID_ROWS)]
self.waves_data = lvl["waves"]
self.wave_index = 0
self.wave_timer = 300 # Пауза перед первой волной
self.cooldowns = {k: 0 for k in self.all_seeds}
self.selected_seed = None
self.state = "PLAY"
def spawn_explosion(self, cx, cy, c_col, c_row):
# Взрыв в радиусе 3x3 клетки
for r in range(max(0, c_row - 1), min(GRID_ROWS, c_row + 2)):
for z in self.zombies:
if z.row == r and abs(z.x - cx) < CELL_W * 1.5:
z.take_damage(1800)
# Частицы эффекта
for _ in range(40):
ang = random.uniform(0, math.pi * 2)
spd = random.uniform(2, 8)
col = random.choice([(255, 50, 0), (255, 180, 0), (80, 80, 80)])
self.particles.append(Particle(cx, cy, col, random.randint(4, 8), math.cos(ang) * spd, math.sin(ang) * spd, 35))
def handle_click(self, pos):
# 1. Сбор солнца
for s in self.suns:
if math.hypot(pos[0] - s.x, pos[1] - s.y) <= s.radius + 6:
self.sun += 25
s.alive = False
return
# 2. Меню Магазина / Колоды
if self.state == "CHOOSER":
# Кнопка 'Let's Rock'
if 480 <= pos[0] <= 620 and 560 <= pos[1] <= 610:
if len(self.deck) > 0:
self.start_level(self.current_level_idx)
return
# Выбор карт в колоду
for idx, p_type in enumerate(self.all_seeds):
card_r = pygame.Rect(180 + (idx % 4) * 110, 200 + (idx // 4) * 130, 95, 115)
if card_r.collidepoint(pos):
if p_type in self.deck:
self.deck.remove(p_type)
elif len(self.deck) < 5:
self.deck.append(p_type)
return
# 3. Карточки верхней панели
if self.state == "PLAY":
for idx, p_type in enumerate(self.deck):
card_r = pygame.Rect(100 + idx * 75, 15, 68, 88)
if card_r.collidepoint(pos):
cost = PLANT_DATA[p_type]["cost"]
if self.sun >= cost and self.cooldowns[p_type] == 0:
self.selected_seed = p_type
return
# Высадка на поле
if GRID_X <= pos[0] < GRID_X + GRID_COLS * CELL_W and GRID_Y <= pos[1] < GRID_Y + GRID_ROWS * CELL_H:
col = (pos[0] - GRID_X) // CELL_W
row = (pos[1] - GRID_Y) // CELL_H
if self.selected_seed and self.grid[row][col] is None:
cost = PLANT_DATA[self.selected_seed]["cost"]
p = Plant(col, row, self.selected_seed)
self.plants.append(p)
self.grid[row][col] = p
self.sun -= cost
self.cooldowns[self.selected_seed] = PLANT_DATA[self.selected_seed]["cd"]
self.selected_seed = None
def update(self):
if self.state != "PLAY":
return
# Перезарядка карточек
for k in self.cooldowns:
if self.cooldowns[k] > 0:
self.cooldowns[k] -= 1
# Естественный спавн солнца
self.sky_sun_timer += 1
if self.sky_sun_timer >= 320:
tx = random.randint(GRID_X, GRID_X + GRID_COLS * CELL_W - 40)
ty = random.randint(GRID_Y + 50, HEIGHT - 80)
self.suns.append(Sun(tx, 0, ty))
self.sky_sun_timer = 0
# Управление волнами зомби
self.wave_timer += 1
if self.wave_timer >= 500:
if self.wave_index < len(self.waves_data):
wave = self.waves_data[self.wave_index]
for z_type in wave:
r = random.randint(0, GRID_ROWS - 1)
self.zombies.append(Zombie(r, z_type))
self.wave_index += 1
self.wave_timer = 0
elif len(self.zombies) == 0:
# Все волны побеждены
if self.current_level_idx + 1 < len(LEVELS):
self.current_level_idx += 1
self.state = "CHOOSER"
else:
self.state = "WIN"
# Обновление сущностей
for p in self.plants: p.update(self)
for proj in self.projectiles: proj.update()
for z in self.zombies: z.update(self.plants)
for s in self.suns: s.update()
for m in self.mowers: m.update(self.zombies)
for part in self.particles: part.update()
# Столкновения: снаряды и зомби
for proj in self.projectiles:
for z in self.zombies:
if proj.row == z.row and abs(proj.x - z.x) < 25:
z.take_damage(proj.damage, freeze=proj.is_ice)
proj.alive = False
for _ in range(4):
col = (100, 220, 255) if proj.is_ice else (80, 220, 80)
self.particles.append(Particle(proj.x, proj.y, col, 3, random.uniform(-2, 2), random.uniform(-2, 2), 15))
break
# Прорыв обороны зомби
for z in self.zombies:
if z.x < GRID_X - 70:
self.state = "GAMEOVER"
# Очистка мертвых сущностей
for p in self.plants:
if p.hp <= 0 or not p.alive:
self.grid[p.row][p.col] = None
self.plants = [p for p in self.plants if p.hp > 0 and p.alive]
self.projectiles = [p for p in self.projectiles if p.alive]
self.zombies = [z for z in self.zombies if z.alive]
self.suns = [s for s in self.suns if s.alive]
self.particles = [p for p in self.particles if p.life > 0]
def draw(self, surf):
# 1. Отрисовка газона
surf.fill((85, 145, 60))
for r in range(GRID_ROWS):
for c in range(GRID_COLS):
rect = pygame.Rect(GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H)
col = (112, 185, 30) if (r + c) % 2 == 0 else (98, 168, 24)
pygame.draw.rect(surf, col, rect)
# Разделительные линии
for r in range(GRID_ROWS + 1):
pygame.draw.line(surf, (70, 130, 20), (GRID_X, GRID_Y + r * CELL_H), (GRID_X + GRID_COLS * CELL_W, GRID_Y + r * CELL_H), 2)
# 2. Объекты на поле
for m in self.mowers: m.draw(surf)
for p in self.plants: p.draw(surf)
for z in self.zombies: z.draw(surf)
for proj in self.projectiles: proj.draw(surf)
for s in self.suns: s.draw(surf)
for part in self.particles: part.draw(surf)
# 3. Верхняя панель (HUD)
pygame.draw.rect(surf, (110, 75, 45), (0, 0, WIDTH, 110))
pygame.draw.rect(surf, (60, 40, 20), (0, 105, WIDTH, 5))
# Счетчик солнца
pygame.draw.rect(surf, (220, 200, 140), (15, 15, 75, 80), border_radius=6)
pygame.draw.circle(surf, (255, 190, 0), (52, 40), 16)
sun_lbl = f_med.render(str(self.sun), True, (50, 30, 0))
surf.blit(sun_lbl, (52 - sun_lbl.get_width() // 2, 62))
# Карточки семян
for idx, p_type in enumerate(self.deck):
x = 100 + idx * 75
card_r = pygame.Rect(x, 15, 68, 88)
cost = PLANT_DATA[p_type]["cost"]
cd = self.cooldowns[p_type]
max_cd = PLANT_DATA[p_type]["cd"]
can_buy = (self.sun >= cost and cd == 0)
base_col = (205, 185, 135) if can_buy else (130, 120, 105)
if self.selected_seed == p_type:
base_col = (255, 255, 120)
pygame.draw.rect(surf, base_col, card_r, border_radius=5)
pygame.draw.rect(surf, (50, 30, 10), card_r, 2, border_radius=5)
name_t = f_tiny.render(p_type[:6].upper(), True, (0, 0, 0))
cost_t = f_small.render(str(cost), True, (150, 20, 20))
surf.blit(name_t, (x + 34 - name_t.get_width() // 2, 22))
surf.blit(cost_t, (x + 34 - cost_t.get_width() // 2, 75))
# Затемнение отката (cooldown)
if cd > 0:
pct = cd / max_cd
h = int(88 * pct)
s = pygame.Surface((68, h), pygame.SRCALPHA)
s.fill((0, 0, 0, 150))
surf.blit(s, (x, 15 + (88 - h)))
# Прогресс уровня
lvl_info = LEVELS[self.current_level_idx]
title_t = f_small.render(lvl_info["name"], True, (255, 255, 255))
surf.blit(title_t, (WIDTH - title_t.get_width() - 25, 20))
# Индикатор волны
total_w = len(self.waves_data)
bar_w = 180
pygame.draw.rect(surf, (40, 40, 40), (WIDTH - bar_w - 25, 50, bar_w, 18), border_radius=4)
progress = min(1.0, self.wave_index / max(1, total_w))
pygame.draw.rect(surf, (50, 200, 50), (WIDTH - bar_w - 25, 50, int(bar_w * progress), 18), border_radius=4)
w_t = f_tiny.render("WAVE PROGRESS", True, (255, 255, 255))
surf.blit(w_t, (WIDTH - bar_w // 2 - 25 - w_t.get_width() // 2, 53))
# 4. Экраны состояний
if self.state == "CHOOSER":
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 190))
surf.blit(overlay, (0, 0))
t = f_big.render("CHOOSE YOUR SEEDS", True, (255, 220, 0))
surf.blit(t, (WIDTH // 2 - t.get_width() // 2, 70))
for idx, p_type in enumerate(self.all_seeds):
rx = 180 + (idx % 4) * 110
ry = 200 + (idx // 4) * 130
r = pygame.Rect(rx, ry, 95, 115)
chosen = p_type in self.deck
col = (180, 240, 180) if chosen else (180, 160, 140)
pygame.draw.rect(surf, col, r, border_radius=6)
pygame.draw.rect(surf, (0, 0, 0), r, 2, border_radius=6)
n = f_small.render(p_type.capitalize(), True, (0, 0, 0))
c = f_small.render(f"${PLANT_DATA[p_type]['cost']}", True, (150, 0, 0))
surf.blit(n, (rx + 47 - n.get_width() // 2, ry + 15))
surf.blit(c, (rx + 47 - c.get_width() // 2, ry + 80))
# Кнопка 'Let's Rock'
btn_r = pygame.Rect(480, 560, 140, 50)
pygame.draw.rect(surf, (40, 160, 40), btn_r, border_radius=8)
btn_t = f_med.render("LET'S ROCK", True, (255, 255, 255))
surf.blit(btn_t, (550 - btn_t.get_width() // 2, 572))
elif self.state == "PAUSE":
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 160))
surf.blit(overlay, (0, 0))
t = f_big.render("PAUSED (Press P to resume)", True, (255, 255, 255))
surf.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 30))
elif self.state == "GAMEOVER":
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((80, 0, 0, 200))
surf.blit(overlay, (0, 0))
t = f_big.render("THE ZOMBIES ATE YOUR BRAINS!", True, (255, 60, 60))
st = f_med.render("Press R to Retry", True, (255, 255, 255))
surf.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 40))
surf.blit(st, (WIDTH // 2 - st.get_width() // 2, HEIGHT // 2 + 25))
elif self.state == "WIN":
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((20, 80, 20, 200))
surf.blit(overlay, (0, 0))
t = f_big.render("YOU DEFENDED THE GARDEN!", True, (255, 220, 0))
surf.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 30))
# --- Главный цикл ---
def main():
gm = GameManager()
gm.state = "CHOOSER"
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key in [pygame.K_p, pygame.K_ESCAPE]:
if gm.state == "PLAY": gm.state = "PAUSE"
elif gm.state == "PAUSE": gm.state = "PLAY"
elif event.key == pygame.K_r and gm.state in ["GAMEOVER", "WIN"]:
gm.start_level(gm.current_level_idx)
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
gm.handle_click(pygame.mouse.get_pos())
gm.update()
gm.draw(screen)
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()