植物大战僵尸python代码

import pygame
import sys
import random
import math
from enum import Enum
from typing import List, Tuple, Optional, Dict, Any

--- 1. 初始化和常量定义 ---

pygame.init()

游戏常量

SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
GRID_SIZE = 85
GRID_ROWS = 5
GRID_COLS = 9
LAWN_TOP = 120
LAWN_LEFT = 180
FPS = 60
UI_TOP_PANEL_HEIGHT = 100

颜色定义

C_BLACK = (0, 0, 0)
C_WHITE = (255, 255, 255)
C_RED = (255, 0, 0)
C_GREEN = (0, 200, 0)
C_BLUE = (0, 0, 255)
C_SKY_BLUE = (135, 206, 250)
C_GRASS_LIGHT = (0, 180, 0)
C_GRASS_DARK = (0, 160, 0)
C_WATER_LIGHT = (50, 150, 255)
C_WATER_DARK = (40, 140, 245)
C_BROWN = (139, 69, 19)
C_DARK_BROWN = (101, 67, 33)
C_SUN_YELLOW = (255, 255, 0)
C_SUN_ORANGE = (255, 165, 0)
C_PEA_GREEN = (124, 252, 0)
C_PEA_DARK_GREEN = (0, 100, 0)
C_SNOW_PEA_BLUE = (0, 191, 255)
C_ZOMBIE_SKIN = (202, 224, 192)
C_ZOMBIE_CLOTHES = (100, 80, 50)
C_CONE_ORANGE = (255, 127, 80)
C_BUCKET_GRAY = (192, 192, 192)
C_PUMPKIN_ORANGE = (244, 164, 96)
C_GOLD = (255, 215, 0)
C_YELLOW = (255, 255, 0)

字体初始化

try:
FONT_PATH = "C:/Windows/Fonts/msyh.ttc"
FONT = pygame.font.Font(FONT_PATH, 36)
SMALL_FONT = pygame.font.Font(FONT_PATH, 24)
TINY_FONT = pygame.font.Font(FONT_PATH, 18)
TITLE_FONT = pygame.font.Font(FONT_PATH, 72)
except:
FONT = pygame.font.Font(None, 48)
SMALL_FONT = pygame.font.Font(None, 32)
TINY_FONT = pygame.font.Font(None, 24)
TITLE_FONT = pygame.font.Font(None, 90)

--- 2. 辅助类和枚举 ---

class GameState(Enum):
MENU = "menu"
PLAYING = "playing"
PAUSED = "paused"
GAME_OVER = "game_over"
VICTORY = "victory"
MODE_SELECT = "mode_select" # 新增模式选择状态

class TileType(Enum):
GRASS = "grass"
# 移除 WATER 类型
# WATER = "water"

class PlantType(Enum):
SUNFLOWER = "sunflower"
PEASHOOTER = "peashooter"
WALL_NUT = "wall_nut"
SNOW_PEA = "snow_pea"
PUMPKIN = "pumpkin"
CORN_CANNON = "corn_cannon"

class ZombieType(Enum):
NORMAL = "normal"
CONEHEAD = "conehead"
BUCKETHEAD = "buckethead"

class SpriteFactory:
"""通过代码生成所有游戏元素的像素图像"""

@staticmethod
def create_surface(size, key_color=None):
    surf = pygame.Surface(size)
    if key_color:
        surf.set_colorkey(key_color)
    else:
        surf = surf.convert_alpha()
        surf.fill((0, 0, 0, 0))
    return surf

@staticmethod
def create_sunflower():
    surf = SpriteFactory.create_surface((70, 70))
    # 茎
    pygame.draw.rect(surf, C_PEA_GREEN, (32, 45, 6, 25))
    # 花心
    pygame.draw.circle(surf, C_DARK_BROWN, (35, 35), 15)
    # 花瓣
    for i in range(8):
        angle = i * (math.pi / 4)
        x = 35 + math.cos(angle) * 25
        y = 35 + math.sin(angle) * 25
        pygame.draw.circle(surf, C_SUN_YELLOW, (x, y), 10)
    return surf

@staticmethod
def create_peashooter():
    surf = SpriteFactory.create_surface((70, 70))
    # 茎和叶子
    pygame.draw.rect(surf, C_PEA_GREEN, (32, 40, 6, 30))
    pygame.draw.ellipse(surf, C_PEA_GREEN, (15, 55, 40, 15))
    # 头部和嘴巴
    pygame.draw.circle(surf, C_PEA_GREEN, (35, 30), 20)
    pygame.draw.circle(surf, C_PEA_DARK_GREEN, (55, 30), 10)
    return surf

@staticmethod
def create_snow_pea():
    surf = SpriteFactory.create_surface((70, 70))
    # 茎和叶子
    pygame.draw.rect(surf, C_SNOW_PEA_BLUE, (32, 40, 6, 30))
    pygame.draw.ellipse(surf, C_SNOW_PEA_BLUE, (15, 55, 40, 15))
    # 头部和嘴巴
    pygame.draw.circle(surf, C_SNOW_PEA_BLUE, (35, 30), 20)
    pygame.draw.circle(surf, C_BLUE, (55, 30), 10)
    return surf

@staticmethod
def create_wall_nut():
    surf = SpriteFactory.create_surface((70, 70))
    pygame.draw.ellipse(surf, C_BROWN, (5, 5, 60, 65))
    pygame.draw.ellipse(surf, C_BLACK, (5, 5, 60, 65), 3)
    # 眼睛
    pygame.draw.circle(surf, C_WHITE, (25, 35), 8)
    pygame.draw.circle(surf, C_WHITE, (45, 35), 8)
    pygame.draw.circle(surf, C_BLACK, (25, 35), 4)
    pygame.draw.circle(surf, C_BLACK, (45, 35), 4)
    return surf

@staticmethod
def create_pumpkin():
    surf = SpriteFactory.create_surface((80, 80))
    pygame.draw.ellipse(surf, C_PUMPKIN_ORANGE, (5, 5, 70, 70))
    pygame.draw.ellipse(surf, C_BLACK, (5, 5, 70, 70), 3)
    # 眼睛和嘴
    pygame.draw.polygon(surf, C_BLACK, [(25, 25), (35, 40), (15, 40)])
    pygame.draw.polygon(surf, C_BLACK, [(55, 25), (65, 40), (45, 40)])
    pygame.draw.rect(surf, C_BLACK, (25, 55, 30, 8))
    return surf

@staticmethod
def create_corn_cannon():
    surf = SpriteFactory.create_surface((70, 70))
    # 绘制玉米加农炮
    pygame.draw.rect(surf, C_BROWN, (30, 40, 10, 30))  # 炮管
    pygame.draw.circle(surf, C_YELLOW, (35, 30), 20)  # 炮身
    return surf

@staticmethod
def create_zombie(zombie_type: ZombieType):
    surf = SpriteFactory.create_surface((70, 90))
    # 身体和头部
    pygame.draw.rect(surf, C_ZOMBIE_CLOTHES, (20, 40, 30, 50))
    pygame.draw.circle(surf, C_ZOMBIE_SKIN, (35, 25), 20)
    # 眼睛
    pygame.draw.circle(surf, C_WHITE, (30, 25), 5)
    pygame.draw.circle(surf, C_WHITE, (45, 25), 5)
    pygame.draw.circle(surf, C_BLACK, (30, 25), 2)
    pygame.draw.circle(surf, C_BLACK, (45, 25), 2)
    # 附加物
    if zombie_type == ZombieType.CONEHEAD:
        pygame.draw.polygon(surf, C_CONE_ORANGE, [(35, -10), (15, 30), (55, 30)])
    elif zombie_type == ZombieType.BUCKETHEAD:
        pygame.draw.rect(surf, C_BUCKET_GRAY, (10, 0, 50, 35))
        pygame.draw.rect(surf, C_BLACK, (10, 0, 50, 35), 2)
    return surf

@staticmethod
def create_sun():
    surf = SpriteFactory.create_surface((50, 50))
    pygame.draw.circle(surf, C_SUN_ORANGE, (25, 25), 18)
    pygame.draw.circle(surf, C_SUN_YELLOW, (25, 25), 15)
    return surf

@staticmethod
def create_bullet(bullet_type: str):
    size = 15
    surf = SpriteFactory.create_surface((size, size))
    if bullet_type == "normal":
        color = C_PEA_GREEN
    elif bullet_type == "snow":
        color = C_SNOW_PEA_BLUE
    elif bullet_type == "corn":
        color = C_YELLOW
    pygame.draw.circle(surf, color, (size // 2, size // 2), size // 2)
    return surf

@staticmethod
def create_shovel():
    surf = SpriteFactory.create_surface((60, 60))
    pygame.draw.rect(surf, C_BROWN, (27, 20, 6, 40))  # Handle
    pygame.draw.rect(surf, C_BUCKET_GRAY, (15, 0, 30, 20))  # Blade
    return surf

@staticmethod
def create_lawnmower():
    surf = SpriteFactory.create_surface((70, 60))
    pygame.draw.rect(surf, C_RED, (0, 0, 60, 50), border_radius=10)
    pygame.draw.rect(surf, C_BLACK, (50, 5, 15, 40))  # Engine
    pygame.draw.circle(surf, C_WHITE, (15, 25), 10)  # Logo
    return surf

--- 3. 游戏对象类 ---

class GameObject:
"""所有游戏对象的基类"""

def __init__(self, row: int, col: int, health: int):
    self.row = row
    self.col = col
    self.x = LAWN_LEFT + col * GRID_SIZE + GRID_SIZE // 2
    self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
    self.health = health
    self.max_health = health
    self.alive = True
    self.animation_tick = random.randint(0, 50)

def draw_health_bar(self, screen: pygame.Surface, y_offset: int = -40):
    if self.health < self.max_health:
        bar_width = 50
        bar_height = 5
        bar_x = self.x - bar_width // 2
        bar_y = self.y + y_offset
        health_ratio = self.health / self.max_health
        pygame.draw.rect(screen, C_RED, (bar_x, bar_y, bar_width, bar_height))
        pygame.draw.rect(screen, C_GREEN, (bar_x, bar_y, int(bar_width * health_ratio), bar_height))
        pygame.draw.rect(screen, C_BLACK, (bar_x, bar_y, bar_width, bar_height), 1)

def take_damage(self, amount: int):
    self.health -= amount
    if self.health <= 0:
        self.alive = False

class Plant(GameObject):
"""植物类"""
plant_data = {
PlantType.SUNFLOWER: {"cost": 50, "health": 80},
PlantType.PEASHOOTER: {"cost": 100, "health": 100},
PlantType.WALL_NUT: {"cost": 50, "health": 1000},
PlantType.SNOW_PEA: {"cost": 175, "health": 100},
PlantType.PUMPKIN: {"cost": 125, "health": 800},
PlantType.CORN_CANNON: {"cost": 300, "health": 150, "attack_interval": 5000}
}

def __init__(self, plant_type: PlantType, row: int, col: int, sprite: pygame.Surface):
    data = self.plant_data[plant_type]
    super().__init__(row, col, data["health"])
    self.plant_type = plant_type
    self.cost = data["cost"]
    self.image = sprite
    self.last_action_time = pygame.time.get_ticks()
    self.pumpkin_shield = None

def update(self, game: 'Game'):
    current_time = pygame.time.get_ticks()
    self.animation_tick += 1

    if self.plant_type == PlantType.SUNFLOWER:
        if current_time - self.last_action_time > 8000:
            game.add_sun(self.x, self.y, 50)  # 向日葵生成阳光值从 25 增加到 50
            self.last_action_time = current_time
    elif self.plant_type in [PlantType.PEASHOOTER, PlantType.SNOW_PEA]:
        if current_time - self.last_action_time > 1500:
            has_zombie_in_row = any(z.row == self.row and z.x > self.x for z in game.zombies)
            if has_zombie_in_row:
                bullet_type = "snow" if self.plant_type == PlantType.SNOW_PEA else "normal"
                game.add_bullet(self.row, self.x + 20, bullet_type)
                self.last_action_time = current_time
    elif self.plant_type == PlantType.CORN_CANNON:
        if current_time - self.last_action_time > self.plant_data[PlantType.CORN_CANNON]["attack_interval"]:
            has_zombie_in_row = any(z.row == self.row and z.x > self.x for z in game.zombies)
            if has_zombie_in_row:
                game.add_bullet(self.row, self.x + 20, "corn")
                self.last_action_time = current_time

def draw(self, screen: pygame.Surface):
    if self.pumpkin_shield:
        self.pumpkin_shield.draw_as_shield(screen, self.x, self.y)

    img_rect = self.image.get_rect(center=(self.x, self.y))

    offset = int(math.sin(self.animation_tick * 0.1) * 3)
    img_rect.y += offset

    screen.blit(self.image, img_rect)
    self.draw_health_bar(screen)

class Pumpkin(Plant):
"""南瓜头类"""

def __init__(self, row: int, col: int, sprite: pygame.Surface):
    super().__init__(PlantType.PUMPKIN, row, col, sprite)

def draw_as_shield(self, screen: pygame.Surface, x: int, y: int):
    img_rect = self.image.get_rect(center=(x, y))
    screen.blit(self.image, img_rect)
    self.x, self.y = x, y
    self.draw_health_bar(screen, y_offset=-45)

class Zombie(GameObject):
"""僵尸类"""
zombie_data = {
ZombieType.NORMAL: {"health": 100, "speed": 0.6, "damage": 20},
ZombieType.CONEHEAD: {"health": 250, "speed": 0.6, "damage": 20},
ZombieType.BUCKETHEAD: {"health": 500, "speed": 0.6, "damage": 20},
}

def __init__(self, zombie_type: ZombieType, row: int, sprite: pygame.Surface):
    data = self.zombie_data[zombie_type]
    super().__init__(row, 9, data["health"])
    self.x = SCREEN_WIDTH - 20
    self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
    self.zombie_type = zombie_type
    self.speed = data["speed"]
    self.damage = data["damage"]
    self.image = sprite
    self.is_attacking = False
    self.slow_timer = 0
    self.last_attack_time = 0

def update(self, game: 'Game'):
    self.animation_tick += 1
    current_speed = self.speed
    if self.slow_timer > 0:
        current_speed *= 0.5
        self.slow_timer -= 1

    self.is_attacking = False
    target_plant = None

    for plant in game.plants:
        if plant.row == self.row:
            plant_x = LAWN_LEFT + plant.col * GRID_SIZE + GRID_SIZE // 2
            if abs(self.x - plant_x) < 30:
                target_plant = plant
                break

    if target_plant:
        self.is_attacking = True
        current_time = pygame.time.get_ticks()
        if current_time - self.last_attack_time > 1000:
            if target_plant.pumpkin_shield:
                target_plant.pumpkin_shield.take_damage(self.damage)
            else:
                target_plant.take_damage(self.damage)
            self.last_attack_time = current_time

    if not self.is_attacking:
        self.x -= current_speed

    if self.x < LAWN_LEFT - 20:
        game.state = GameState.GAME_OVER

def draw(self, screen: pygame.Surface):
    img_rect = self.image.get_rect(center=(int(self.x), self.y))

    if self.is_attacking:
        offset = int(math.sin(self.animation_tick * 0.5) * 5)
        img_rect.x += offset

    screen.blit(self.image, img_rect)
    if self.slow_timer > 0:
        ice_overlay = pygame.Surface(img_rect.size, pygame.SRCALPHA)
        ice_overlay.fill((100, 100, 255, 100))
        screen.blit(ice_overlay, img_rect.topleft)

    self.draw_health_bar(screen)

class Bullet:
"""子弹类"""

def __init__(self, row: int, x: int, bullet_type: str, sprite: pygame.Surface):
    self.row = row
    self.x = x
    self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
    self.speed = 8
    self.bullet_type = bullet_type
    self.damage = 20 if bullet_type != "corn" else 50
    self.active = True
    self.image = sprite

def update(self, game: 'Game'):
    self.x += self.speed
    if self.x > SCREEN_WIDTH:
        self.active = False

    for zombie in game.zombies[:]:
        if zombie.row == self.row and self.active and abs(self.x - zombie.x) < 20:
            zombie.take_damage(self.damage)
            if self.bullet_type == "snow":
                zombie.slow_timer = 180
            game.add_particles(self.x, self.y, 5, C_WHITE, (1, 3), ((-2, 2), (-2, 2)))

def draw(self, screen: pygame.Surface):
    img_rect = self.image.get_rect(center=(int(self.x), self.y))
    screen.blit(self.image, img_rect)

class Sun:
"""阳光类"""

def __init__(self, x: int, y: int, value: int, sprite: pygame.Surface, is_natural: bool = False):
    self.x = x
    self.y = y
    self.value = value
    self.image = sprite
    self.rect = self.image.get_rect(center=(x, y))
    self.collected = False
    self.spawn_time = pygame.time.get_ticks()
    self.life_span = 8000
    self.is_natural = is_natural
    if is_natural:
        self.target_y = random.randint(LAWN_TOP, SCREEN_HEIGHT - 50)
        self.fall_speed = 1.5
    else:
        self.target_y = y
        self.fall_speed = 0

def update(self, game):  # 修改此处,接受 game 参数
    if self.is_natural and self.y < self.target_y:
        self.y += self.fall_speed
        self.rect.centery = int(self.y)
    if pygame.time.get_ticks() - self.spawn_time > self.life_span:
        self.collected = True

def draw(self, screen: pygame.Surface):
    screen.blit(self.image, self.rect)

def check_click(self, pos: Tuple[int, int]) -> bool:
    if self.rect.collidepoint(pos):
        self.collected = True
        return True
    return False

class LawnMower:
"""草地剪草车类"""

def __init__(self, row: int, sprite: pygame.Surface):
    self.row = row
    self.image = sprite
    self.x = LAWN_LEFT - GRID_SIZE // 2
    self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
    self.rect = self.image.get_rect(center=(self.x, self.y))
    self.is_active = False
    self.speed = 10
    self.triggered = False  # 新增属性,标记割草机是否已触发

def update(self, game: 'Game'):
    if not self.is_active and not self.triggered:
        for z in game.zombies:
            if z.row == self.row and z.x < self.x + 20:
                self.is_active = True
                self.triggered = True  # 标记割草机已触发
                break
    if self.is_active:
        self.x += self.speed
        self.rect.centerx = int(self.x)
        for z in game.zombies[:]:
            if z.row == self.row and self.rect.colliderect(z.image.get_rect(center=(z.x, z.y))):
                z.alive = False
        if self.x > SCREEN_WIDTH + 50:
            game.lawnmowers.remove(self)

def draw(self, screen: pygame.Surface):
    screen.blit(self.image, self.rect)

class Particle:
"""粒子类,用于处理粒子效果"""
def init(self, x, y, size, color, velocity):
self.x = x
self.y = y
self.size = size
self.color = color
self.velocity = velocity
self.lifetime = 30 # 粒子存活帧数

def update(self):
    self.x += self.velocity[0]
    self.y += self.velocity[1]
    self.lifetime -= 1
    if self.lifetime <= 0:
        return False
    return True

def draw(self, screen):
    pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.size)

--- 4. UI 和管理器类 ---

class Button:
"""通用按钮类"""

def __init__(self, x, y, width, height, text, font, action):
    self.rect = pygame.Rect(x, y, width, height)
    self.text = text
    self.font = font
    self.action = action
    self.color = C_BROWN
    self.hover_color = C_GREEN
    self.is_hovered = False

def handle_event(self, event: pygame.event.Event):
    if event.type == pygame.MOUSEMOTION:
        self.is_hovered = self.rect.collidepoint(event.pos)
    if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and self.is_hovered:
        self.action()

def draw(self, screen: pygame.Surface):
    color = self.hover_color if self.is_hovered else self.color
    pygame.draw.rect(screen, color, self.rect, border_radius=10)
    pygame.draw.rect(screen, C_BLACK, self.rect, 2, border_radius=10)
    text_surf = self.font.render(self.text, True, C_WHITE)
    text_rect = text_surf.get_rect(center=self.rect.center)
    screen.blit(text_surf, text_rect)

class PlantCard:
"""植物选择卡片类"""

def __init__(self, x: int, y: int, plant_type: PlantType, sprite: pygame.Surface):
    self.rect = pygame.Rect(x, y, 70, 85)
    self.plant_type = plant_type
    self.data = Plant.plant_data[plant_type]
    self.cost = self.data["cost"]
    self.image = sprite

def draw(self, screen: pygame.Surface, sun_count: int, selected_plant_type: Optional[PlantType]):
    pygame.draw.rect(screen, C_DARK_BROWN, self.rect, border_radius=5)

    img_rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery - 10))
    screen.blit(self.image, img_rect)

    cost_text = TINY_FONT.render(str(self.cost), True, C_SUN_YELLOW)
    screen.blit(cost_text, (self.rect.x + 5, self.rect.y + 65))

    if sun_count < self.cost:
        s = pygame.Surface(self.rect.size, pygame.SRCALPHA)
        s.fill((100, 100, 100, 150))
        screen.blit(s, self.rect.topleft)

    if selected_plant_type == self.plant_type:
        pygame.draw.rect(screen, C_GOLD, self.rect, 3, border_radius=5)

class UIManager:
"""UI管理器"""

def __init__(self, game: 'Game'):
    self.game = game
    self.sprites = game.sprites

    self.plant_cards = []
    card_x = 20
    plant_types_in_bar = [pt for pt in PlantType if pt != PlantType.PUMPKIN]
    for pt in plant_types_in_bar:
        self.plant_cards.append(PlantCard(card_x, 10, pt, self.sprites[pt.value]))
        card_x += 80

    self.shovel_sprite = self.sprites["shovel"]
    self.shovel_rect = self.shovel_sprite.get_rect(topleft=(card_x + 20, 15))

def draw_top_panel(self):
    panel_rect = pygame.Rect(0, 0, SCREEN_WIDTH, UI_TOP_PANEL_HEIGHT)
    pygame.draw.rect(self.game.screen, C_BROWN, panel_rect)
    pygame.draw.line(self.game.screen, C_BLACK, (0, UI_TOP_PANEL_HEIGHT), (SCREEN_WIDTH, UI_TOP_PANEL_HEIGHT), 3)

    sun_icon = self.sprites["sun"]
    self.game.screen.blit(sun_icon, (SCREEN_WIDTH - 250, 25))
    sun_text = FONT.render(f"{self.game.sun_count}", True, C_BLACK)
    self.game.screen.blit(sun_text, (SCREEN_WIDTH - 190, 30))

    for card in self.plant_cards:
        card.draw(self.game.screen, self.game.sun_count, self.game.selected_plant_type)

    self.game.screen.blit(self.shovel_sprite, self.shovel_rect)
    if self.game.shovel_selected:
        pygame.draw.rect(self.game.screen, C_GOLD, self.shovel_rect, 3)

def draw_game_messages(self):
    wave_text = FONT.render(f"WAVE: {self.game.wave}", True, C_RED)
    self.game.screen.blit(wave_text, (SCREEN_WIDTH - 250, 750))

def check_click(self, pos: Tuple[int, int]):
    for card in self.plant_cards:
        if card.rect.collidepoint(pos):
            if self.game.sun_count >= card.cost:
                self.game.selected_plant_type = card.plant_type
                self.game.shovel_selected = False
                return

    if self.shovel_rect.collidepoint(pos):
        self.game.shovel_selected = True
        self.game.selected_plant_type = None

--- 5. 主游戏类 ---

class Game:
"""主游戏类"""

def __init__(self):
    self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("植物大战僵尸 - 纯代码像素版")
    self.clock = pygame.time.Clock()
    self.state = GameState.MODE_SELECT  # 初始状态设为模式选择
    self.sprites = self.create_all_sprites()

    self.plants: List[Plant] = []
    self.zombies: List[Zombie] = []
    self.bullets: List[Bullet] = []
    self.suns: List[Sun] = []
    self.lawnmowers: List[LawnMower] = []
    self.particles = []  # 初始化粒子列表

    self.sun_count = 500  # 初始阳光从 150 增加到 500
    self.wave = 0
    self.zombie_spawn_timer = 0
    self.natural_sun_timer = 0
    self.selected_plant_type: Optional[PlantType] = None
    self.shovel_selected = False

    self.ui_manager = UIManager(self)
    self.menu_buttons = self._create_menu_buttons()
    self.mode_select_buttons = self._create_mode_select_buttons()
    self.grid_tile_types = self._create_level_map()

def create_all_sprites(self) -> Dict[str, pygame.Surface]:
    sprites = {}
    for pt in PlantType:
        sprites[pt.value] = getattr(SpriteFactory, f"create_{pt.value}")()
    for zt in ZombieType:
        sprites[zt.value] = SpriteFactory.create_zombie(zt)
    sprites["sun"] = SpriteFactory.create_sun()
    sprites["bullet_normal"] = SpriteFactory.create_bullet("normal")
    sprites["bullet_snow"] = SpriteFactory.create_bullet("snow")
    sprites["bullet_corn"] = SpriteFactory.create_bullet("corn")
    sprites["shovel"] = SpriteFactory.create_shovel()
    sprites["lawnmower"] = SpriteFactory.create_lawnmower()
    return sprites

def _create_menu_buttons(self) -> List[Button]:
    start_button = Button(SCREEN_WIDTH // 2 - 150, 300, 300, 80, "开始游戏", FONT, self.start_game)
    quit_button = Button(SCREEN_WIDTH // 2 - 150, 400, 300, 80, "退出游戏", FONT, self.quit_game)
    return [start_button, quit_button]

def _create_mode_select_buttons(self) -> List[Button]:
    """创建模式选择按钮"""
    easy_mode_button = Button(SCREEN_WIDTH // 2 - 150, 300, 300, 80, "简单模式", FONT, lambda: self.start_game(mode="easy"))
    hard_mode_button = Button(SCREEN_WIDTH // 2 - 150, 400, 300, 80, "困难模式", FONT, lambda: self.start_game(mode="hard"))
    quit_button = Button(SCREEN_WIDTH // 2 - 150, 500, 300, 80, "退出游戏", FONT, self.quit_game)
    return [easy_mode_button, hard_mode_button, quit_button]

def start_game(self, mode="easy"):
    """根据模式开始游戏"""
    self.reset_game()
    self.state = GameState.PLAYING
    # 可根据不同模式调整游戏参数
    if mode == "hard":
        self.zombie_spawn_timer = 5000  # 示例参数调整

def _create_level_map(self) -> List[List[TileType]]:
    # 全部设为草地
    grid = [[TileType.GRASS for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
    return grid

def reset_game(self):
    self.plants.clear()
    self.zombies.clear()
    self.bullets.clear()
    self.suns.clear()
    self.lawnmowers = [LawnMower(i, self.sprites["lawnmower"]) for i in range(GRID_ROWS)]
    self.sun_count = 500  # 初始阳光从 150 增加到 500
    self.wave = 0
    self.selected_plant_type = None
    self.shovel_selected = False
    self._next_wave()

def quit_game(self):
    pygame.quit()
    sys.exit()

def _next_wave(self):
    self.wave += 1
    self.zombie_spawn_timer = pygame.time.get_ticks()

def handle_events(self):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            self.quit_game()

        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            if self.state == GameState.PLAYING:
                self.state = GameState.PAUSED
            elif self.state == GameState.PAUSED:
                self.state = GameState.PLAYING

        if self.state == GameState.MODE_SELECT:
            for btn in self.mode_select_buttons:
                btn.handle_event(event)
        elif self.state == GameState.MENU:
            for btn in self.menu_buttons:
                btn.handle_event(event)
        elif self.state == GameState.PLAYING:
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                self.handle_game_click(event.pos)

def handle_game_click(self, pos: Tuple[int, int]):
    self.ui_manager.check_click(pos)

    for sun in self.suns[:]:
        if sun.check_click(pos):
            self.sun_count += sun.value
            self.suns.remove(sun)
            return

    if LAWN_LEFT < pos[0] < LAWN_LEFT + GRID_COLS * GRID_SIZE and \
            LAWN_TOP < pos[1] < LAWN_TOP + GRID_ROWS * GRID_SIZE:
        col = (pos[0] - LAWN_LEFT) // GRID_SIZE
        row = (pos[1] - LAWN_TOP) // GRID_SIZE
        self.handle_grid_click(row, col)

def handle_grid_click(self, row: int, col: int):
    if self.shovel_selected:
        for plant in self.plants[:]:
            if plant.row == row and plant.col == col:
                self.sun_count += plant.cost // 3
                self.plants.remove(plant)
                if plant.pumpkin_shield:
                    self.plants.remove(plant.pumpkin_shield)
                self.shovel_selected = False
        return

    if self.selected_plant_type:
        plant_type = self.selected_plant_type
        plant_cost = Plant.plant_data[plant_type]['cost']

        if self.sun_count < plant_cost:
            return

        existing_plant = next((p for p in self.plants if p.row == row and p.col == col), None)

        if existing_plant:
            if plant_type == PlantType.PUMPKIN and not existing_plant.pumpkin_shield:
                new_pumpkin = Pumpkin(row, col, self.sprites[plant_type.value])
                existing_plant.pumpkin_shield = new_pumpkin
                self.plants.append(new_pumpkin)
                self.sun_count -= plant_cost
                self.selected_plant_type = None
            return

        # 因为全是草地,简化逻辑
        new_plant = Plant(plant_type, row, col, self.sprites[plant_type.value])
        self.plants.append(new_plant)
        self.sun_count -= plant_cost
        self.selected_plant_type = None

def update(self):
    for obj_list in [self.plants, self.zombies, self.bullets, self.suns, self.lawnmowers, self.particles]:
        for obj in obj_list[:]:
            if hasattr(obj, 'update'):
                if isinstance(obj, Particle):
                    if not obj.update():
                        obj_list.remove(obj)
                else:
                    obj.update(self)
            if hasattr(obj, 'alive') and not obj.alive:
                obj_list.remove(obj)
            elif hasattr(obj, 'collected') and obj.collected:
                obj_list.remove(obj)

    for p in self.plants[:]:
        if p.pumpkin_shield and not p.pumpkin_shield.alive:
            self.plants.remove(p.pumpkin_shield)
            p.pumpkin_shield = None
        if not p.alive:
            self.plants.remove(p)

    if pygame.time.get_ticks() - self.natural_sun_timer > 7000:  # 自然阳光生成间隔从 10000 缩短到 7000 毫秒
        x = random.randint(LAWN_LEFT, SCREEN_WIDTH - 50)
        self.add_sun(x, 50, 75, is_natural=True)  # 自然阳光值从 25 增加到 75
        self.natural_sun_timer = pygame.time.get_ticks()

    if self.wave < 10 and pygame.time.get_ticks() - self.zombie_spawn_timer > (15000 - self.wave * 1000):
        # 波数越多,每波生成的僵尸数量越多
        num_zombies = self.wave + 1
        for _ in range(num_zombies):
            self.spawn_zombie()
        self._next_wave()
    elif self.wave >= 10 and not self.zombies:
        self.state = GameState.VICTORY

def draw(self):
    self.screen.fill(C_SKY_BLUE)
    self.draw_grid()
    all_objects = sorted(self.plants + self.zombies, key=lambda obj: obj.y)
    for obj in all_objects:
        obj.draw(self.screen)
    for mower in self.lawnmowers:
        mower.draw(self.screen)
    for bullet in self.bullets:
        bullet.draw(self.screen)
    for sun in self.suns:
        sun.draw(self.screen)
    for particle in self.particles:
        particle.draw(self.screen)
    self.ui_manager.draw_top_panel()
    self.ui_manager.draw_game_messages()
    self.draw_cursor()

def draw_grid(self):
    for r in range(GRID_ROWS):
        for c in range(GRID_COLS):
            rect = pygame.Rect(LAWN_LEFT + c * GRID_SIZE, LAWN_TOP + r * GRID_SIZE, GRID_SIZE, GRID_SIZE)
            # 全是草地
            tile_type = TileType.GRASS
            color = (C_GRASS_LIGHT, C_GRASS_DARK)
            pygame.draw.rect(self.screen, color[(r + c) % 2], rect)
            pygame.draw.rect(self.screen, C_BLACK, rect, 1)

def draw_cursor(self):
    pos = pygame.mouse.get_pos()
    if self.selected_plant_type:
        sprite = self.sprites[self.selected_plant_type.value]
        self.screen.blit(sprite, sprite.get_rect(center=pos))
    elif self.shovel_selected:
        sprite = self.sprites["shovel"]
        self.screen.blit(sprite, sprite.get_rect(center=pos))

def run(self):
    while True:
        self.handle_events()
        if self.state == GameState.MODE_SELECT:
            self.screen.fill(C_SKY_BLUE)
            title_text = TITLE_FONT.render("选择游戏模式", True, C_WHITE)
            self.screen.blit(title_text, (SCREEN_WIDTH // 2 - title_text.get_width() // 2, 100))
            # 绘制 04 数字
            number_text = TITLE_FONT.render("04", True, C_RED)  # 使用红色使其更显眼
            number_x = SCREEN_WIDTH // 2 - number_text.get_width() // 2
            number_y = 200  # 调整垂直位置
            self.screen.blit(number_text, (number_x, number_y))
            for btn in self.mode_select_buttons:
                btn.draw(self.screen)
        elif self.state == GameState.MENU:
            self.screen.fill(C_SKY_BLUE)
            title_text = TITLE_FONT.render("像素植物大战僵尸", True, C_WHITE)
            self.screen.blit(title_text, (SCREEN_WIDTH // 2 - title_text.get_width() // 2, 100))
            # 绘制 04 数字
            number_text = TITLE_FONT.render("04", True, C_RED)  # 使用红色使其更显眼
            number_x = SCREEN_WIDTH // 2 - number_text.get_width() // 2
            number_y = 200  # 调整垂直位置
            self.screen.blit(number_text, (number_x, number_y))
            for btn in self.menu_buttons:
                btn.draw(self.screen)
        elif self.state == GameState.PLAYING:
            self.update()
            self.draw()
        elif self.state == GameState.PAUSED:
            pause_text = TITLE_FONT.render("已暂停", True, C_WHITE)
            self.screen.blit(pause_text, (SCREEN_WIDTH // 2 - pause_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50))
        elif self.state == GameState.GAME_OVER:
            go_text = TITLE_FONT.render("游戏结束", True, C_RED)
            self.screen.blit(go_text, (SCREEN_WIDTH // 2 - go_text.get_width() // 2, SCREEN_HEIGHT // 2 - 100))
            back_button = Button(SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2 + 50, 300, 80, "返回模式选择", FONT, self.return_to_mode_select)
            back_button.handle_event(pygame.event.poll())
            back_button.draw(self.screen)
        elif self.state == GameState.VICTORY:
            victory_text = TITLE_FONT.render("游戏胜利", True, C_GREEN)
            self.screen.blit(victory_text, (SCREEN_WIDTH // 2 - victory_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50))

        pygame.display.flip()
        self.clock.tick(FPS)

def return_to_mode_select(self):
    """返回模式选择界面"""
    self.state = GameState.MODE_SELECT
    self.reset_game()

def spawn_zombie(self):
    row = random.randint(0, GRID_ROWS - 1)
    zombie_type = random.choice(list(ZombieType))
    self.zombies.append(Zombie(zombie_type, row, self.sprites[zombie_type.value]))

def add_particles(self, x, y, count, color, size_range, velocity_range):
    """添加粒子效果"""
    for _ in range(count):
        size = random.randint(*size_range)
        vx = random.randint(*velocity_range[0])
        vy = random.randint(*velocity_range[1])
        particle = Particle(x, y, size, color, (vx, vy))
        self.particles.append(particle)

def add_sun(self, x, y, value, is_natural=False):
    self.suns.append(Sun(x, y, value, self.sprites["sun"], is_natural))

def add_bullet(self, row, x, bullet_type):
    self.bullets.append(Bullet(row, x, bullet_type, self.sprites[f"bullet_{bullet_type}"]))

if name == "main":
game = Game()
game.run()



posted @ 2025-06-22 19:27  渔樵伴夜归客  阅读(304)  评论(0)    收藏  举报