Загрузка данных
import pygame
import random
import math
import sys
import os
pygame.init()
pygame.font.init()
# --- Конфигурация экрана и сетки ---
WIDTH, HEIGHT = 1150, 680
GRID_COLS, GRID_ROWS = 9, 5
CELL_W, CELL_H = 88, 96
GRID_X, GRID_Y = 280, 140
FPS = 60
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Plants vs. Zombies: Deluxe Python Edition")
clock = pygame.time.Clock()
# Шрифты
f_micro = pygame.font.SysFont("Verdana", 11, bold=True)
f_small = pygame.font.SysFont("Verdana", 14, bold=True)
f_med = pygame.font.SysFont("Verdana", 20, bold=True)
f_big = pygame.font.SysFont("Verdana", 38, bold=True)
# База данных всех доступных растений
PLANT_SPECS = {
"peashooter": {"cost": 100, "hp": 200, "cd": 120, "name": "Peashooter"},
"sunflower": {"cost": 50, "hp": 150, "cd": 120, "name": "Sunflower"},
"cherrybomb": {"cost": 150, "hp": 600, "cd": 600, "name": "Cherry Bomb"},
"wallnut": {"cost": 50, "hp": 800, "cd": 450, "name": "Wall-nut"},
"snowpea": {"cost": 175, "hp": 200, "cd": 150, "name": "Snow Pea"},
"repeater": {"cost": 200, "hp": 200, "cd": 150, "name": "Repeater"},
"potatomine": {"cost": 25, "hp": 100, "cd": 350, "name": "Potato Mine"},
"chomper": {"cost": 150, "hp": 250, "cd": 200, "name": "Chomper"}
}
# --- Менеджер Ресурсов и Автогенератор Спрайтов ---
class AssetVault:
def __init__(self):
self.images = {}
os.makedirs("assets", exist_ok=True)
self.preload_all()
def get(self, key):
return self.images.get(key)
def preload_all(self):
needed = [
"peashooter", "sunflower", "cherrybomb", "wallnut",
"snowpea", "repeater", "potatomine", "chomper",
"zombie_regular", "zombie_cone", "zombie_bucket", "zombie_gargantuar",
"shovel", "sun", "pea", "ice_pea", "lawnmower"
]
for item in needed:
path = os.path.join("assets", f"{item}.png")
if os.path.exists(path):
self.images[item] = pygame.image.load(path).convert_alpha()
else:
self.images[item] = self.synthesize_asset(item)
def synthesize_asset(self, item):
surf = pygame.Surface((CELL_W, CELL_H), pygame.SRCALPHA)
cx, cy = CELL_W // 2, CELL_H // 2
if item in ["peashooter", "repeater", "snowpea"]:
col = (100, 210, 255) if item == "snowpea" else (60, 180, 50)
pygame.draw.ellipse(surf, (30, 120, 30), (cx - 25, cy + 18, 50, 16))
pygame.draw.line(surf, (40, 140, 40), (cx, cy + 20), (cx, cy), 6)
pygame.draw.circle(surf, col, (cx - 4, cy - 8), 20)
pygame.draw.rect(surf, col, (cx + 6, cy - 16, 20, 16), border_radius=4)
pygame.draw.circle(surf, (20, 70, 20), (cx + 26, cy - 8), 8)
pygame.draw.circle(surf, (255, 255, 255), (cx + 2, cy - 14), 5)
pygame.draw.circle(surf, (0, 0, 0), (cx + 4, cy - 14), 2)
if item == "repeater":
# Листья на затылке Репитера
pygame.draw.polygon(surf, (40, 150, 30), [(cx - 20, cy - 16), (cx - 36, cy - 26), (cx - 24, cy - 6)])
elif item == "snowpea":
pygame.draw.polygon(surf, (180, 240, 255), [(cx - 20, cy - 16), (cx - 32, cy - 22), (cx - 22, cy - 8)])
elif item == "sunflower":
for ang in range(0, 360, 30):
rad = math.radians(ang)
px = cx + math.cos(rad) * 22
py = (cy - 6) + math.sin(rad) * 22
pygame.draw.circle(surf, (255, 205, 10), (int(px), int(py)), 9)
pygame.draw.circle(surf, (140, 75, 25), (cx, cy - 6), 18)
pygame.draw.circle(surf, (0, 0, 0), (cx - 6, cy - 10), 3)
pygame.draw.circle(surf, (0, 0, 0), (cx + 6, cy - 10), 3)
pygame.draw.arc(surf, (255, 255, 255), (cx - 8, cy - 8, 16, 10), math.pi, 2 * math.pi, 2)
elif item == "wallnut":
pygame.draw.ellipse(surf, (165, 95, 45), (cx - 22, cy - 28, 44, 60))
pygame.draw.circle(surf, (255, 255, 255), (cx - 7, cy - 12), 6)
pygame.draw.circle(surf, (255, 255, 255), (cx + 7, cy - 12), 6)
pygame.draw.circle(surf, (0, 0, 0), (cx - 5, cy - 12), 3)
pygame.draw.circle(surf, (0, 0, 0), (cx + 9, cy - 12), 3)
elif item == "cherrybomb":
pygame.draw.line(surf, (20, 100, 20), (cx - 12, cy + 2), (cx, cy - 20), 4)
pygame.draw.line(surf, (20, 100, 20), (cx + 12, cy - 4), (cx, cy - 20), 4)
pygame.draw.circle(surf, (210, 25, 25), (cx - 14, cy + 6), 17)
pygame.draw.circle(surf, (235, 30, 30), (cx + 14, cy), 16)
elif item == "potatomine":
pygame.draw.ellipse(surf, (180, 140, 90), (cx - 16, cy + 2, 32, 22))
pygame.draw.circle(surf, (255, 50, 50), (cx, cy - 4), 5) # Индикатор мины
elif item == "chomper":
pygame.draw.ellipse(surf, (130, 40, 160), (cx - 22, cy - 26, 44, 46))
pygame.draw.polygon(surf, (255, 255, 255), [(cx - 16, cy - 4), (cx - 10, cy + 6), (cx - 4, cy - 4)])
pygame.draw.polygon(surf, (255, 255, 255), [(cx + 4, cy - 4), (cx + 10, cy + 6), (cx + 16, cy - 4)])
elif "zombie" in item:
tint = (130, 165, 125)
# Тело и костюм
pygame.draw.rect(surf, (70, 60, 55), (cx - 12, cy - 8, 24, 34), border_radius=4)
pygame.draw.line(surf, (40, 40, 70), (cx - 6, cy + 24), (cx - 8, cy + 42), 6)
pygame.draw.line(surf, (40, 40, 70), (cx + 6, cy + 24), (cx + 8, cy + 42), 6)
pygame.draw.line(surf, tint, (cx - 4, cy - 2), (cx - 24, cy - 2), 5)
# Голова
pygame.draw.circle(surf, tint, (cx, cy - 22), 16)
pygame.draw.circle(surf, (255, 0, 0), (cx - 6, cy - 24), 3)
pygame.draw.circle(surf, (255, 0, 0), (cx + 4, cy - 24), 3)
if "cone" in item:
pygame.draw.polygon(surf, (255, 130, 0), [(cx - 12, cy - 32), (cx + 12, cy - 32), (cx, cy - 64)])
elif "bucket" in item:
pygame.draw.rect(surf, (170, 170, 180), (cx - 12, cy - 48, 24, 22), border_radius=2)
elif "gargantuar" in item:
s_boss = pygame.Surface((CELL_W + 30, CELL_H + 40), pygame.SRCALPHA)
bcx, bcy = s_boss.get_width() // 2, s_boss.get_height() // 2
pygame.draw.rect(s_boss, (50, 45, 40), (bcx - 26, bcy - 10, 52, 60), border_radius=6)
pygame.draw.circle(s_boss, tint, (bcx, bcy - 30), 24)
pygame.draw.rect(s_boss, (90, 50, 20), (bcx - 42, bcy - 60, 16, 95), border_radius=3)
return s_boss
elif item == "shovel":
surf_sh = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.line(surf_sh, (150, 90, 40), (10, 40), (35, 15), 6)
pygame.draw.polygon(surf_sh, (180, 190, 200), [(30, 20), (45, 5), (35, -5), (20, 10)])
return surf_sh
elif item == "sun":
s_sun = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.circle(s_sun, (255, 170, 0), (25, 25), 22)
pygame.draw.circle(s_sun, (255, 230, 40), (25, 25), 18)
return s_sun
elif item == "pea":
s_pea = pygame.Surface((18, 18), pygame.SRCALPHA)
pygame.draw.circle(s_pea, (50, 205, 50), (9, 9), 8)
pygame.draw.circle(s_pea, (255, 255, 255), (6, 6), 2)
return s_pea
elif item == "ice_pea":
s_ipea = pygame.Surface((18, 18), pygame.SRCALPHA)
pygame.draw.circle(s_ipea, (80, 210, 255), (9, 9), 8)
pygame.draw.circle(s_ipea, (255, 255, 255), (6, 6), 2)
return s_ipea
elif item == "lawnmower":
s_mow = pygame.Surface((60, 50), pygame.SRCALPHA)
pygame.draw.rect(s_mow, (200, 30, 30), (10, 15, 40, 25), border_radius=4)
pygame.draw.circle(s_mow, (40, 40, 40), (18, 42), 8)
pygame.draw.circle(s_mow, (40, 40, 40), (42, 42), 8)
pygame.draw.line(s_mow, (180, 180, 180), (14, 15), (4, -2), 4)
return s_mow
return surf
vault = AssetVault()
# --- Сущности Игры ---
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
def update(self):
if self.y < self.target_y:
self.y += 2.5
self.timer += 1
if self.timer > 650:
self.alive = False
def draw(self, surf):
img = vault.get("sun")
surf.blit(img, (self.x - img.get_width() // 2, self.y - img.get_height() // 2))
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.5
self.damage = 22
self.alive = True
def update(self):
self.x += self.speed
if self.x > WIDTH:
self.alive = False
def draw(self, surf):
img = vault.get("ice_pea" if self.is_ice else "pea")
surf.blit(img, (self.x - 9, self.y - 9))
class Lawnmower:
def __init__(self, row):
self.row = row
self.x = GRID_X - 65
self.y = GRID_Y + row * CELL_H + 20
self.active = False
self.used = False
def update(self, zombies):
if self.active:
self.x += 12
for z in zombies:
if z.row == self.row and abs(z.x - self.x) < 45:
z.take_damage(9999)
if self.x > WIDTH + 60:
self.used = True
else:
for z in zombies:
if z.row == self.row and z.x <= self.x + 35:
self.active = True
break
def draw(self, surf):
if not self.used:
surf.blit(vault.get("lawnmower"), (self.x, self.y))
class Plant:
def __init__(self, col, row, p_type):
self.col, self.row = col, row
self.type = p_type
self.hp = PLANT_SPECS[p_type]["hp"]
self.x = GRID_X + col * CELL_W
self.y = GRID_Y + row * CELL_H
self.action_timer = 0
self.alive = True
self.armed = False if p_type == "potatomine" else True
self.chewing_timer = 0
def update(self, game):
self.action_timer += 1
if self.type == "sunflower" and self.action_timer >= 450:
game.suns.append(Sun(self.x + CELL_W // 2, self.y + 20))
self.action_timer = 0
elif self.type in ["peashooter", "snowpea", "repeater"]:
has_zombie = any(z.row == self.row and z.x > self.x for z in game.zombies)
if has_zombie:
if self.type == "repeater" and (self.action_timer == 90 or self.action_timer == 105):
game.projectiles.append(Projectile(self.x + CELL_W - 10, self.y + 35, self.row, False))
if self.action_timer == 105:
self.action_timer = 0
elif self.type in ["peashooter", "snowpea"] and self.action_timer >= 90:
is_ice = (self.type == "snowpea")
game.projectiles.append(Projectile(self.x + CELL_W - 10, self.y + 35, self.row, is_ice))
self.action_timer = 0
elif self.type == "cherrybomb" and self.action_timer >= 100:
self.alive = False
game.trigger_explosion(self.x + CELL_W // 2, self.y + CELL_H // 2, self.col, self.row)
elif self.type == "potatomine":
if not self.armed and self.action_timer >= 300: # 5 сек взвод
self.armed = True
if self.armed:
for z in game.zombies:
if z.row == self.row and abs((self.x + CELL_W // 2) - z.x) < 30:
z.take_damage(1800)
self.alive = False
break
elif self.type == "chomper":
if self.chewing_timer > 0:
self.chewing_timer -= 1
else:
for z in game.zombies:
if z.row == self.row and 0 < (z.x - self.x) < CELL_W * 1.4:
if z.type != "gargantuar":
z.take_damage(9999)
self.chewing_timer = 600 # 10 сек пережевывания
break
def draw(self, surf):
img = vault.get(self.type)
if img:
surf.blit(img, (self.x, self.y))
class Zombie:
def __init__(self, row, z_type="regular"):
self.row = row
self.type = z_type
self.x = WIDTH + random.randint(20, 70)
self.y = GRID_Y + row * CELL_H + 8
self.alive = True
self.frozen_timer = 0
if z_type == "regular":
self.hp, self.speed, self.dps = 150, 0.35, 0.6
elif z_type == "conehead":
self.hp, self.speed, self.dps = 320, 0.35, 0.6
elif z_type == "buckethead":
self.hp, self.speed, self.dps = 650, 0.35, 0.6
elif z_type == "gargantuar":
self.hp, self.speed, self.dps = 1800, 0.22, 999
self.y -= 30
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
eating = False
for p in plants:
if p.row == self.row and abs(self.x - (p.x + 30)) < 25:
eating = True
p.hp -= self.dps
break
if not eating:
self.x -= spd
def draw(self, surf):
name = f"zombie_{self.type.replace('head', '')}"
img = vault.get(name if self.type != "regular" else "zombie_regular")
if img:
if self.frozen_timer > 0:
tinted = img.copy()
tinted.fill((60, 120, 255, 120), special_flags=pygame.BLEND_RGBA_ADD)
surf.blit(tinted, (self.x - 30, self.y))
else:
surf.blit(img, (self.x - 30, self.y))
# --- Главный Контроллер Игры ---
class GameManager:
def __init__(self):
self.state = "MENU" # MENU, PLAY, ALMANAC, PAUSE, GAMEOVER, WIN
self.sun = 150
self.selected_tool = None # тип семени или 'shovel'
self.deck = ["peashooter", "sunflower", "wallnut", "cherrybomb", "snowpea", "repeater", "potatomine", "chomper"]
self.cooldowns = {k: 0 for k in self.deck}
self.grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
self.plants = []
self.zombies = []
self.projectiles = []
self.suns = []
self.mowers = []
self.wave_idx = 0
self.wave_timer = 0
self.sky_sun_timer = 0
self.level_waves = [
["regular", "regular"],
["regular", "conehead"],
["conehead", "buckethead", "regular"],
["buckethead", "conehead", "buckethead"],
["gargantuar", "buckethead", "conehead", "regular"]
]
def reset_battle(self):
self.sun = 200
self.selected_tool = None
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.mowers = [Lawnmower(r) for r in range(GRID_ROWS)]
self.cooldowns = {k: 0 for k in self.deck}
self.wave_idx = 0
self.wave_timer = 300
self.state = "PLAY"
def trigger_explosion(self, cx, cy, col, row):
for r in range(max(0, row - 1), min(GRID_ROWS, row + 2)):
for z in self.zombies:
if z.row == r and abs(z.x - cx) < CELL_W * 1.5:
z.take_damage(1800)
def handle_click(self, pos):
if self.state == "MENU":
if 460 <= pos[0] <= 690 and 280 <= pos[1] <= 340:
self.reset_battle()
elif 460 <= pos[0] <= 690 and 360 <= pos[1] <= 420:
self.state = "ALMANAC"
elif 460 <= pos[0] <= 690 and 440 <= pos[1] <= 500:
pygame.quit(); sys.exit()
return
if self.state == "ALMANAC":
if 500 <= pos[0] <= 650 and 590 <= pos[1] <= 640:
self.state = "MENU"
return
if self.state == "PLAY":
# Сбор солнца
for s in self.suns:
if math.hypot(pos[0] - s.x, pos[1] - s.y) <= s.radius + 12:
self.sun += 25
s.alive = False
return
# Выбор Лопаты (координаты: x=750, y=15, w=60, h=65)
shovel_r = pygame.Rect(750, 15, 60, 65)
if shovel_r.collidepoint(pos):
self.selected_tool = "shovel" if self.selected_tool != "shovel" else None
return
# Выбор карты семян
for idx, p_type in enumerate(self.deck):
r = pygame.Rect(110 + idx * 76, 12, 70, 92)
if r.collidepoint(pos):
cost = PLANT_SPECS[p_type]["cost"]
if self.sun >= cost and self.cooldowns[p_type] == 0:
self.selected_tool = 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_tool == "shovel":
if self.grid[row][col] is not None:
plant_to_del = self.grid[row][col]
self.plants.remove(plant_to_del)
self.grid[row][col] = None
self.selected_tool = None
return
# Посадка Растения
if self.selected_tool and self.selected_tool != "shovel":
if self.grid[row][col] is None:
cost = PLANT_SPECS[self.selected_tool]["cost"]
plant = Plant(col, row, self.selected_tool)
self.plants.append(plant)
self.grid[row][col] = plant
self.sun -= cost
self.cooldowns[self.selected_tool] = PLANT_SPECS[self.selected_tool]["cd"]
self.selected_tool = 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 >= 520:
if self.wave_idx < len(self.level_waves):
wave = self.level_waves[self.wave_idx]
for z_type in wave:
self.zombies.append(Zombie(random.randint(0, GRID_ROWS - 1), z_type))
self.wave_idx += 1
self.wave_timer = 0
elif len(self.zombies) == 0:
self.state = "WIN"
for p in self.plants: p.update(self)
for pr in self.projectiles: pr.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 pr in self.projectiles:
for z in self.zombies:
if pr.row == z.row and abs(pr.x - z.x) < 25:
z.take_damage(pr.damage, freeze=pr.is_ice)
pr.alive = False
break
for z in self.zombies:
if z.x < GRID_X - 60:
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 = [pr for pr in self.projectiles if pr.alive]
self.zombies = [z for z in self.zombies if z.alive]
self.suns = [s for s in self.suns if s.alive]
def draw(self, surf):
if self.state == "MENU":
surf.fill((40, 75, 30))
t = f_big.render("PLANTS VS. ZOMBIES", True, (240, 210, 40))
surf.blit(t, (WIDTH // 2 - t.get_width() // 2, 120))
# Кнопки
for idx, text in enumerate(["START ADVENTURE", "ALMANAC", "EXIT"]):
btn_r = pygame.Rect(460, 280 + idx * 80, 230, 58)
pygame.draw.rect(surf, (140, 80, 40), btn_r, border_radius=10)
pygame.draw.rect(surf, (60, 30, 10), btn_r, 3, border_radius=10)
lbl = f_small.render(text, True, (255, 255, 255))
surf.blit(lbl, (btn_r.centerx - lbl.get_width() // 2, btn_r.centery - lbl.get_height() // 2))
return
if self.state == "ALMANAC":
surf.fill((35, 45, 50))
lbl = f_big.render("SUBURBAN ALMANAC", True, (240, 210, 40))
surf.blit(lbl, (WIDTH // 2 - lbl.get_width() // 2, 30))
for idx, (p_type, spec) in enumerate(PLANT_SPECS.items()):
x = 100 + (idx % 4) * 240
y = 120 + (idx // 4) * 220
pygame.draw.rect(surf, (60, 75, 80), (x, y, 220, 190), border_radius=8)
img = vault.get(p_type)
if img: surf.blit(img, (x + 15, y + 15))
n = f_small.render(spec["name"], True, (255, 255, 255))
c = f_micro.render(f"Cost: {spec['cost']} | HP: {spec['hp']}", True, (255, 220, 80))
surf.blit(n, (x + 10, y + 125))
surf.blit(c, (x + 10, y + 155))
b_back = pygame.Rect(500, 590, 150, 45)
pygame.draw.rect(surf, (140, 60, 40), b_back, border_radius=6)
txt = f_small.render("BACK", True, (255, 255, 255))
surf.blit(txt, (b_back.centerx - txt.get_width() // 2, b_back.centery - txt.get_height() // 2))
return
# Игровой газон
surf.fill((65, 115, 45))
for r in range(GRID_ROWS):
for c in range(GRID_COLS):
col = (110, 180, 35) if (r + c) % 2 == 0 else (95, 165, 30)
pygame.draw.rect(surf, col, (GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H))
pygame.draw.rect(surf, (65, 125, 20), (GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H), 1)
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 pr in self.projectiles: pr.draw(surf)
for s in self.suns: s.draw(surf)
# Верхняя панель инвентаря
pygame.draw.rect(surf, (100, 65, 35), (0, 0, WIDTH, 115))
# Счетчик солнца
pygame.draw.rect(surf, (220, 200, 150), (15, 12, 80, 90), border_radius=6)
surf.blit(vault.get("sun"), (30, 18))
s_lbl = f_med.render(str(self.sun), True, (50, 30, 10))
surf.blit(s_lbl, (55 - s_lbl.get_width() // 2, 70))
# Карточки семян
for idx, p_type in enumerate(self.deck):
x = 110 + idx * 76
card_r = pygame.Rect(x, 12, 70, 92)
cost = PLANT_SPECS[p_type]["cost"]
cd = self.cooldowns[p_type]
can_afford = self.sun >= cost and cd == 0
col = (230, 220, 180) if can_afford else (130, 120, 110)
if self.selected_tool == p_type: col = (255, 255, 120)
pygame.draw.rect(surf, col, card_r, border_radius=6)
pygame.draw.rect(surf, (40, 25, 10), card_r, 2, border_radius=6)
t_name = f_micro.render(PLANT_SPECS[p_type]["name"][:7], True, (0, 0, 0))
t_cost = f_small.render(str(cost), True, (160, 20, 20))
surf.blit(t_name, (x + 35 - t_name.get_width() // 2, 18))
surf.blit(t_cost, (x + 35 - t_cost.get_width() // 2, 76))
if cd > 0:
h = int(92 * (cd / PLANT_SPECS[p_type]["cd"]))
cd_surf = pygame.Surface((70, h), pygame.SRCALPHA)
cd_surf.fill((0, 0, 0, 160))
surf.blit(cd_surf, (x, 12 + (92 - h)))
# Лопата
sh_r = pygame.Rect(750, 15, 60, 65)
sh_col = (255, 255, 120) if self.selected_tool == "shovel" else (140, 90, 50)
pygame.draw.rect(surf, sh_col, sh_r, border_radius=6)
pygame.draw.rect(surf, (40, 20, 10), sh_r, 2, border_radius=6)
surf.blit(vault.get("shovel"), (755, 22))
# Индикатор волн
pygame.draw.rect(surf, (30, 30, 30), (WIDTH - 220, 55, 190, 20), border_radius=5)
prog = min(1.0, self.wave_idx / len(self.level_waves))
pygame.draw.rect(surf, (40, 220, 40), (WIDTH - 220, 55, int(190 * prog), 20), border_radius=5)
w_lbl = f_micro.render("WAVE PROGRESS", True, (255, 255, 255))
surf.blit(w_lbl, (WIDTH - 125 - w_lbl.get_width() // 2, 58))
# Модальные окна
if self.state == "PAUSE":
s = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
s.fill((0, 0, 0, 160))
surf.blit(s, (0, 0))
txt = f_big.render("GAME PAUSED", True, (255, 255, 255))
surf.blit(txt, (WIDTH // 2 - txt.get_width() // 2, HEIGHT // 2 - 40))
elif self.state == "GAMEOVER":
s = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
s.fill((100, 0, 0, 200))
surf.blit(s, (0, 0))
txt = f_big.render("THE ZOMBIES ATE YOUR BRAINS!", True, (255, 80, 80))
surf.blit(txt, (WIDTH // 2 - txt.get_width() // 2, HEIGHT // 2 - 30))
elif self.state == "WIN":
s = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
s.fill((20, 90, 20, 200))
surf.blit(s, (0, 0))
txt = f_big.render("VICTORY! LAWN DEFENDED!", True, (240, 220, 50))
surf.blit(txt, (WIDTH // 2 - txt.get_width() // 2, HEIGHT // 2 - 30))
# --- Запуск ---
def main():
gm = GameManager()
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.reset_battle()
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()