Загрузка данных
import pygame
import random
import sys
# --- Инициализация и настройки ---
pygame.init()
WIDTH, HEIGHT = 1000, 600
GRID_COLS, GRID_ROWS = 9, 5
CELL_W, CELL_H = 90, 90
GRID_X, GRID_Y = 140, 100
FPS = 60
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Plants vs. Zombies - Python Edition")
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18, bold=True)
big_font = pygame.font.SysFont("Arial", 42, bold=True)
# Палитра
COLOR_BG = (135, 206, 235)
COLOR_LAWN_LIGHT = (118, 186, 27)
COLOR_LAWN_DARK = (104, 169, 21)
COLOR_UI = (80, 50, 20)
COLOR_WHITE = (255, 255, 255)
COLOR_BLACK = (0, 0, 0)
COLOR_SUN = (255, 215, 0)
# --- Игровые сущности ---
class Sun:
def __init__(self, x, y, target_y=None):
self.x = x
self.y = y
self.target_y = target_y if target_y else y
self.radius = 20
self.alive = True
self.timer = 0
def update(self):
if self.y < self.target_y:
self.y += 2
self.timer += 1
if self.timer > 600: # Исчезает через 10 сек
self.alive = False
def draw(self, surface):
pygame.draw.circle(surface, (255, 165, 0), (int(self.x), int(self.y)), self.radius + 3)
pygame.draw.circle(surface, COLOR_SUN, (int(self.x), int(self.y)), self.radius)
class Projectile:
def __init__(self, x, y, row):
self.x = x
self.y = y
self.row = row
self.speed = 6
self.damage = 20
self.alive = True
def update(self):
self.x += self.speed
if self.x > WIDTH:
self.alive = False
def draw(self, surface):
pygame.draw.circle(surface, (50, 205, 50), (int(self.x), int(self.y)), 8)
pygame.draw.circle(surface, (255, 255, 255), (int(self.x) - 2, int(self.y) - 2), 3)
class Plant:
def __init__(self, col, row, p_type):
self.col = col
self.row = row
self.type = p_type
self.x = GRID_X + col * CELL_W + CELL_W // 2
self.y = GRID_Y + row * CELL_H + CELL_H // 2
self.hp = 100 if p_type != "wallnut" else 400
self.max_hp = self.hp
self.cooldown = 0
def update(self, game):
self.cooldown += 1
if self.type == "sunflower" and self.cooldown >= 420: # ~7 секунд
game.suns.append(Sun(self.x, self.y - 10))
self.cooldown = 0
elif self.type == "peashooter" and self.cooldown >= 90: # ~1.5 секунды
# Стрелять, только если на линии есть зомби
if any(z.row == self.row and z.x > self.x for z in game.zombies):
game.projectiles.append(Projectile(self.x + 20, self.y - 8, self.row))
self.cooldown = 0
def draw(self, surface):
if self.type == "peashooter":
pygame.draw.circle(surface, (34, 139, 34), (self.x, self.y), 24)
pygame.draw.rect(surface, (0, 100, 0), (self.x + 10, self.y - 12, 18, 14), border_radius=4)
pygame.draw.circle(surface, (0, 0, 0), (self.x + 6, self.y - 6), 4)
elif self.type == "sunflower":
for angle in range(0, 360, 45):
rad = angle * 3.14159 / 180
ox = self.x + int(16 * pygame.math.Vector2(1, 0).rotate(angle).x)
oy = self.y + int(16 * pygame.math.Vector2(1, 0).rotate(angle).y)
pygame.draw.circle(surface, COLOR_SUN, (ox, oy), 8)
pygame.draw.circle(surface, (139, 69, 19), (self.x, self.y), 16)
elif self.type == "wallnut":
w = int(22 * (self.hp / self.max_hp * 0.4 + 0.6))
pygame.draw.ellipse(surface, (160, 82, 45), (self.x - w, self.y - 28, w * 2, 56))
pygame.draw.circle(surface, COLOR_BLACK, (self.x - 6, self.y - 8), 3)
pygame.draw.circle(surface, COLOR_BLACK, (self.x + 6, self.y - 8), 3)
class Zombie:
def __init__(self, row):
self.row = row
self.x = WIDTH + 30
self.y = GRID_Y + row * CELL_H + CELL_H // 2
self.speed = 0.35
self.hp = 100
self.eating = False
self.alive = True
def update(self, plants):
self.eating = False
# Проверка поедания растений
for plant in plants:
if plant.row == self.row and abs((plant.x + 15) - self.x) < 25:
self.eating = True
plant.hp -= 0.5
break
if not self.eating:
self.x -= self.speed
if self.hp <= 0:
self.alive = False
def draw(self, surface):
# Тело
pygame.draw.rect(surface, (47, 79, 79), (self.x - 12, self.y - 10, 24, 35))
# Голова
pygame.draw.circle(surface, (143, 188, 143), (int(self.x), int(self.y) - 22), 16)
# Глаза
pygame.draw.circle(surface, (255, 0, 0), (int(self.x) - 5, int(self.y) - 24), 3)
pygame.draw.circle(surface, (255, 0, 0), (int(self.x) + 5, int(self.y) - 24), 3)
# --- Игровой Контроллер ---
class Game:
def __init__(self):
self.sun_score = 150
self.grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
self.plants = []
self.zombies = []
self.projectiles = []
self.suns = []
self.selected_tool = None
self.sky_sun_timer = 0
self.zombie_timer = 0
self.game_over = False
self.cards = [
{"type": "sunflower", "cost": 50, "rect": pygame.Rect(20, 110, 100, 60)},
{"type": "peashooter", "cost": 100, "rect": pygame.Rect(20, 180, 100, 60)},
{"type": "wallnut", "cost": 50, "rect": pygame.Rect(20, 250, 100, 60)},
]
def handle_click(self, pos):
# 1. Клик по падающим солнышкам
for sun in self.suns:
if pygame.math.Vector2(pos[0] - sun.x, pos[1] - sun.y).length() <= sun.radius:
self.sun_score += 25
sun.alive = False
return
# 2. Выбор растения в UI
for card in self.cards:
if card["rect"].collidepoint(pos):
if self.sun_score >= card["cost"]:
self.selected_tool = card["type"]
return
# 3. Высадка на поле
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 and self.grid[row][col] is None:
cost = next(c["cost"] for c in self.cards if c["type"] == self.selected_tool)
if self.sun_score >= cost:
p = Plant(col, row, self.selected_tool)
self.plants.append(p)
self.grid[row][col] = p
self.sun_score -= cost
self.selected_tool = None
def update(self):
if self.game_over:
return
# Солнечный свет с неба
self.sky_sun_timer += 1
if self.sky_sun_timer >= 350:
target_y = random.randint(GRID_Y, HEIGHT - 80)
self.suns.append(Sun(random.randint(GRID_X, WIDTH - 60), 0, target_y))
self.sky_sun_timer = 0
# Спавн зомби
self.zombie_timer += 1
if self.zombie_timer >= 280:
self.zombies.append(Zombie(random.randint(0, GRID_ROWS - 1)))
self.zombie_timer = 0
# Обновление сущностей
for plant in self.plants:
plant.update(self)
for proj in self.projectiles:
proj.update()
for zombie in self.zombies:
zombie.update(self.plants)
if zombie.x < GRID_X - 10:
self.game_over = True
for sun in self.suns:
sun.update()
# Коллизии: Снаряды -> Зомби
for proj in self.projectiles:
for zombie in self.zombies:
if proj.row == zombie.row and abs(proj.x - zombie.x) < 20:
zombie.hp -= proj.damage
proj.alive = False
break
# Очистка мертвых объектов
for p in self.plants:
if p.hp <= 0:
self.grid[p.row][p.col] = None
self.plants = [p for p in self.plants if p.hp > 0]
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]
def draw(self, surface):
surface.fill(COLOR_BG)
# Лужайка
for r in range(GRID_ROWS):
for c in range(GRID_COLS):
color = COLOR_LAWN_LIGHT if (r + c) % 2 == 0 else COLOR_LAWN_DARK
rect = pygame.Rect(GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H)
pygame.draw.rect(surface, color, rect)
pygame.draw.rect(surface, (60, 110, 20), rect, 1)
# UI Панель
pygame.draw.rect(surface, COLOR_UI, (0, 0, WIDTH, 80))
sun_text = font.render(f"Sun: {self.sun_score}", True, COLOR_SUN)
surface.blit(sun_text, (25, 30))
# Карточки покупки
for card in self.cards:
is_active = self.selected_tool == card["type"]
can_afford = self.sun_score >= card["cost"]
c_color = (220, 200, 160) if can_afford else (120, 110, 100)
if is_active:
c_color = (255, 255, 100)
pygame.draw.rect(surface, c_color, card["rect"], border_radius=6)
pygame.draw.rect(surface, COLOR_BLACK, card["rect"], 2, border_radius=6)
txt = font.render(f"{card['type'][:4].upper()}", True, COLOR_BLACK)
cost_txt = font.render(f"{card['cost']}", True, (139, 0, 0))
surface.blit(txt, (card["rect"].x + 10, card["rect"].y + 10))
surface.blit(cost_txt, (card["rect"].x + 10, card["rect"].y + 35))
# Отрисовка объектов
for plant in self.plants:
plant.draw(surface)
for zombie in self.zombies:
zombie.draw(surface)
for proj in self.projectiles:
proj.draw(surface)
for sun in self.suns:
sun.draw(surface)
# Экран поражения
if self.game_over:
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
surface.blit(overlay, (0, 0))
t = big_font.render("THE ZOMBIES ATE YOUR BRAINS!", True, (220, 20, 60))
surface.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 20))
# --- Главный цикл ---
def main():
game = Game()
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
game.handle_click(pygame.mouse.get_pos())
game.update()
game.draw(screen)
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()