diff --git a/game/config.py b/game/config.py index ae65f37..0b482d6 100644 --- a/game/config.py +++ b/game/config.py @@ -66,7 +66,7 @@ ENEMY_DATA = { "healer": {"hp": 150, "speed": 1.8, "reward": 30, "color": COLOR_PINK, "size": 12, "name": "治疗兵", "heal_range": 100, "heal_amount": 30, "heal_interval": 2.0}, } -# Path waypoints (pixel coordinates) +# Path waypoints (pixel coordinates) — default/legacy PATH_WAYPOINTS = [ (0, 180), (160, 180), @@ -79,3 +79,75 @@ PATH_WAYPOINTS = [ (760, 220), (800, 220), ] + +# Level definitions +LEVELS = [ + { + "name": "第一关 - 森林小径", + "color": (44, 72, 44), + "start_gold": 200, + "start_lives": 10, + "waypoints": [ + (0, 180), (160, 180), (160, 340), (400, 340), + (400, 100), (600, 100), (600, 340), (760, 340), + (760, 220), (800, 220), + ], + "waves": [ + [{"type": "normal", "count": 5, "interval": 1.0}], + [{"type": "normal", "count": 6, "interval": 0.8}, {"type": "swarm", "count": 4, "interval": 0.3}], + [{"type": "normal", "count": 5, "interval": 0.8}, {"type": "fast", "count": 3, "interval": 0.6}], + [{"type": "swarm", "count": 10, "interval": 0.25}, {"type": "normal", "count": 5, "interval": 0.7}], + [{"type": "normal", "count": 5, "interval": 0.6}, {"type": "heavy", "count": 2, "interval": 1.5}, {"type": "boss", "count": 1, "interval": 2.0}], + [{"type": "normal", "count": 8, "interval": 0.5}, {"type": "fast", "count": 5, "interval": 0.4}, {"type": "swarm", "count": 8, "interval": 0.2}], + [{"type": "shield", "count": 3, "interval": 1.2}, {"type": "healer", "count": 2, "interval": 1.5}, {"type": "normal", "count": 6, "interval": 0.6}], + [{"type": "heavy", "count": 4, "interval": 0.8}, {"type": "fast", "count": 5, "interval": 0.5}, {"type": "shield", "count": 3, "interval": 1.0}], + ], + }, + { + "name": "第二关 - 沙漠要塞", + "color": (82, 77, 50), + "start_gold": 180, + "start_lives": 8, + "waypoints": [ + (0, 60), (200, 60), (200, 180), (60, 180), + (60, 300), (340, 300), (340, 180), (480, 180), + (480, 300), (620, 300), (620, 100), (800, 100), + ], + "waves": [ + [{"type": "normal", "count": 8, "interval": 0.8}], + [{"type": "fast", "count": 6, "interval": 0.5}, {"type": "swarm", "count": 6, "interval": 0.25}], + [{"type": "normal", "count": 6, "interval": 0.6}, {"type": "heavy", "count": 3, "interval": 1.2}], + [{"type": "swarm", "count": 12, "interval": 0.2}, {"type": "shield", "count": 2, "interval": 1.5}], + [{"type": "fast", "count": 8, "interval": 0.4}, {"type": "healer", "count": 2, "interval": 1.0}], + [{"type": "heavy", "count": 5, "interval": 0.8}, {"type": "normal", "count": 8, "interval": 0.5}], + [{"type": "shield", "count": 4, "interval": 1.0}, {"type": "fast", "count": 6, "interval": 0.4}, {"type": "boss", "count": 1, "interval": 2.0}], + [{"type": "swarm", "count": 15, "interval": 0.15}, {"type": "heavy", "count": 4, "interval": 0.7}], + [{"type": "normal", "count": 10, "interval": 0.4}, {"type": "shield", "count": 4, "interval": 0.8}, {"type": "healer", "count": 3, "interval": 1.0}], + [{"type": "heavy", "count": 6, "interval": 0.6}, {"type": "fast", "count": 10, "interval": 0.3}, {"type": "boss", "count": 2, "interval": 2.0}], + ], + }, + { + "name": "第三关 - 暗夜城堡", + "color": (35, 30, 45), + "start_gold": 150, + "start_lives": 8, + "waypoints": [ + (0, 260), (120, 260), (120, 60), (280, 60), + (280, 180), (440, 180), (440, 60), (560, 60), + (560, 260), (680, 260), (680, 100), (800, 100), + ], + "waves": [ + [{"type": "normal", "count": 8, "interval": 0.7}, {"type": "fast", "count": 4, "interval": 0.5}], + [{"type": "heavy", "count": 4, "interval": 1.0}, {"type": "swarm", "count": 8, "interval": 0.2}], + [{"type": "shield", "count": 4, "interval": 1.0}, {"type": "fast", "count": 6, "interval": 0.4}], + [{"type": "healer", "count": 3, "interval": 1.0}, {"type": "normal", "count": 10, "interval": 0.5}], + [{"type": "swarm", "count": 15, "interval": 0.15}, {"type": "heavy", "count": 5, "interval": 0.8}, {"type": "boss", "count": 1, "interval": 2.0}], + [{"type": "shield", "count": 5, "interval": 0.8}, {"type": "healer", "count": 3, "interval": 0.8}, {"type": "fast", "count": 8, "interval": 0.3}], + [{"type": "heavy", "count": 6, "interval": 0.6}, {"type": "swarm", "count": 12, "interval": 0.15}], + [{"type": "normal", "count": 12, "interval": 0.3}, {"type": "shield", "count": 5, "interval": 0.7}, {"type": "boss", "count": 1, "interval": 2.0}], + [{"type": "fast", "count": 12, "interval": 0.25}, {"type": "healer", "count": 4, "interval": 0.7}], + [{"type": "heavy", "count": 8, "interval": 0.5}, {"type": "shield", "count": 6, "interval": 0.6}, {"type": "swarm", "count": 15, "interval": 0.12}], + [{"type": "normal", "count": 12, "interval": 0.25}, {"type": "heavy", "count": 6, "interval": 0.5}, {"type": "fast", "count": 10, "interval": 0.2}, {"type": "boss", "count": 3, "interval": 2.0}], + ], + }, +] diff --git a/game/enemy.py b/game/enemy.py index a7ff96a..1f677fa 100644 --- a/game/enemy.py +++ b/game/enemy.py @@ -5,7 +5,7 @@ from game.utils import distance class Enemy: - def __init__(self, enemy_type): + def __init__(self, enemy_type, waypoints=None): data = ENEMY_DATA[enemy_type] self.type = enemy_type self.max_hp = data["hp"] @@ -32,7 +32,8 @@ class Enemy: self.heal_interval = data.get("heal_interval", 2.0) self.heal_timer = self.heal_interval - self.x, self.y = PATH_WAYPOINTS[0] + self.waypoints = waypoints or PATH_WAYPOINTS + self.x, self.y = self.waypoints[0] self.waypoint_index = 1 self.slow_timer = 0 @@ -84,12 +85,12 @@ class Enemy: e.hp = min(e.hp + self.heal_amount, e.max_hp) # Movement - if self.waypoint_index >= len(PATH_WAYPOINTS): + if self.waypoint_index >= len(self.waypoints): self.reached_end = True self.alive = False return - tx, ty = PATH_WAYPOINTS[self.waypoint_index] + tx, ty = self.waypoints[self.waypoint_index] dx = tx - self.x dy = ty - self.y dist = math.hypot(dx, dy) diff --git a/game/main.py b/game/main.py index bbb0481..0c25be7 100644 --- a/game/main.py +++ b/game/main.py @@ -4,7 +4,7 @@ from game.config import ( WINDOW_WIDTH, WINDOW_HEIGHT, FPS, COLOR_BG, COLOR_WHITE, INITIAL_GOLD, INITIAL_LIVES, UI_TOP_HEIGHT, UI_BOTTOM_HEIGHT, GAME_HEIGHT, TOWER_DATA, - TOTAL_WAVES, + LEVELS, ) from game.map import GameMap from game.enemy import Enemy @@ -23,19 +23,25 @@ class Game: self.font = pygame.font.SysFont("microsoftyahei", 20) self.small_font = pygame.font.SysFont("microsoftyahei", 16) self.big_font = pygame.font.SysFont("microsoftyahei", 48) - self.reset() + self.title_font = pygame.font.SysFont("microsoftyahei", 36) + self.state = "select" + self.current_level_idx = 0 + self.ui = UI() - def reset(self): - self.game_map = GameMap() + def _start_level(self, level_idx): + level = LEVELS[level_idx] + self.current_level_idx = level_idx + self.game_map = GameMap(level["waypoints"], level["color"]) self.enemies = [] self.towers = [] self.projectiles = [] - self.wave_mgr = WaveManager() + self.wave_mgr = WaveManager(level["waves"], level["waypoints"]) self.ui = UI() - self.gold = INITIAL_GOLD - self.lives = INITIAL_LIVES + self.gold = level["start_gold"] + self.lives = level["start_lives"] self.game_over = False self.won = False + self.state = "playing" def run(self): while True: @@ -48,17 +54,35 @@ class Game: sys.exit() self._handle_event(event) - if not self.game_over: + if self.state == "playing" and not self.game_over: self._update(dt) self._draw() def _handle_event(self, event): + if self.state == "select": + self._handle_select_event(event) + return + + if self.state == "playing": + self._handle_playing_event(event) + return + + if self.state == "game_over": + self._handle_game_over_event(event) + + def _handle_select_event(self, event): if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: pos = event.pos + for i in range(len(LEVELS)): + btn_rect = self._level_button_rect(i) + if btn_rect.collidepoint(pos): + self._start_level(i) + return - if self.game_over: - return + def _handle_playing_event(self, event): + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + pos = event.pos action = self.ui.handle_click(pos, self.gold) if action == "wave": @@ -82,11 +106,43 @@ class Game: self.ui.selected_tower = None if event.type == pygame.KEYDOWN: - if event.key == pygame.K_r and self.game_over: - self.reset() if event.key == pygame.K_ESCAPE: self.ui.selected_tower = None + def _handle_game_over_event(self, event): + if event.type == pygame.KEYDOWN and event.key == pygame.K_r: + self._start_level(self.current_level_idx) + return + + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + pos = event.pos + # Restart button + restart_rect = pygame.Rect(WINDOW_WIDTH // 2 - 80, WINDOW_HEIGHT // 2 + 50, 160, 45) + if restart_rect.collidepoint(pos): + self._start_level(self.current_level_idx) + return + + # Next level button (win only, not last level) + if self.won and self.current_level_idx < len(LEVELS) - 1: + next_rect = pygame.Rect(WINDOW_WIDTH // 2 - 80, WINDOW_HEIGHT // 2 + 105, 160, 45) + if next_rect.collidepoint(pos): + self._start_level(self.current_level_idx + 1) + return + + # Back to select button + select_rect = pygame.Rect(WINDOW_WIDTH // 2 - 80, WINDOW_HEIGHT // 2 + 160, 160, 45) + if select_rect.collidepoint(pos): + self.state = "select" + return + + def _level_button_rect(self, idx): + bw, bh = 300, 100 + col = idx % 3 + total_w = 3 * bw + 2 * 30 + start_x = (WINDOW_WIDTH - total_w) // 2 + start_y = 250 + return pygame.Rect(start_x + col * (bw + 30), start_y, bw, bh) + def _update(self, dt): if self.ui.selected_tower is not None: dt *= 0.3 @@ -101,6 +157,7 @@ class Game: self.lives = 0 self.game_over = True self.won = False + self.state = "game_over" for t in self.towers: t.update(dt, self.enemies, self.projectiles) @@ -116,12 +173,19 @@ class Game: self.enemies = [e for e in self.enemies if e.alive] self.projectiles = [p for p in self.projectiles if p.alive] - if self.wave_mgr.all_waves_done or (self.wave_mgr.current_wave >= TOTAL_WAVES and not self.wave_mgr.wave_active and not self.enemies): + total_waves = self.wave_mgr.total_waves + if self.wave_mgr.all_waves_done or (self.wave_mgr.current_wave >= total_waves and not self.wave_mgr.wave_active and not self.enemies): if not self.game_over: self.game_over = True self.won = True + self.state = "game_over" def _draw(self): + if self.state == "select": + self._draw_select() + return + + # Playing / game_over self.screen.fill(COLOR_BG) self.game_map.draw(self.screen) @@ -142,15 +206,83 @@ class Game: self.wave_mgr.current_wave, self.wave_mgr.wave_active, self.wave_mgr.has_more_waves(), self.font, self.small_font, + self.wave_mgr.total_waves, ) self.ui.draw_slow_overlay(self.screen) - if self.game_over: - self.ui.draw_game_over(self.screen, self.won, self.big_font) + if self.state == "game_over": + self._draw_game_over() pygame.display.flip() + def _draw_select(self): + self.screen.fill(COLOR_BG) + + title = self.title_font.render("塔防游戏 - 选择关卡", True, COLOR_WHITE) + self.screen.blit(title, title.get_rect(center=(WINDOW_WIDTH // 2, 80))) + + subtitle = self.small_font.render("点击关卡开始游戏", True, (180, 180, 180)) + self.screen.blit(subtitle, subtitle.get_rect(center=(WINDOW_WIDTH // 2, 130))) + + level_colors = [(60, 100, 60), (120, 100, 50), (60, 50, 80)] + for i, level in enumerate(LEVELS): + btn_rect = self._level_button_rect(i) + color = level_colors[i % len(level_colors)] + pygame.draw.rect(self.screen, color, btn_rect, border_radius=10) + pygame.draw.rect(self.screen, COLOR_WHITE, btn_rect, 2, border_radius=10) + + name_text = self.font.render(level["name"], True, COLOR_WHITE) + self.screen.blit(name_text, name_text.get_rect(center=(btn_rect.centerx, btn_rect.y + 25))) + + info = f"{len(level['waves'])}波 | {level['start_gold']}金 | {level['start_lives']}命" + info_text = self.small_font.render(info, True, (200, 200, 200)) + self.screen.blit(info_text, info_text.get_rect(center=(btn_rect.centerx, btn_rect.y + 55))) + + diff = ["简单", "中等", "困难"][i] + diff_colors = [COLOR_WHITE, (255, 200, 50), (255, 80, 80)][i] + diff_text = self.small_font.render(diff, True, diff_colors) + self.screen.blit(diff_text, diff_text.get_rect(center=(btn_rect.centerx, btn_rect.y + 80))) + + pygame.display.flip() + + def _draw_game_over(self): + overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA) + overlay.fill((0, 0, 0, 150)) + self.screen.blit(overlay, (0, 0)) + + text = "胜利!" if self.won else "失败!" + color = (255, 215, 0) if self.won else (220, 50, 50) + rendered = self.big_font.render(text, True, color) + self.screen.blit(rendered, rendered.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 - 40))) + + cx = WINDOW_WIDTH // 2 + + # Restart button + restart_rect = pygame.Rect(cx - 80, WINDOW_HEIGHT // 2 + 50, 160, 45) + pygame.draw.rect(self.screen, (80, 120, 80), restart_rect, border_radius=8) + pygame.draw.rect(self.screen, COLOR_WHITE, restart_rect, 2, border_radius=8) + r_text = self.font.render("重新开始", True, COLOR_WHITE) + self.screen.blit(r_text, r_text.get_rect(center=restart_rect.center)) + + # Next level button (only on win, not last level) + if self.won and self.current_level_idx < len(LEVELS) - 1: + next_rect = pygame.Rect(cx - 80, WINDOW_HEIGHT // 2 + 105, 160, 45) + pygame.draw.rect(self.screen, (50, 80, 150), next_rect, border_radius=8) + pygame.draw.rect(self.screen, COLOR_WHITE, next_rect, 2, border_radius=8) + n_text = self.font.render("下一关", True, COLOR_WHITE) + self.screen.blit(n_text, n_text.get_rect(center=next_rect.center)) + + # Back to select + select_rect = pygame.Rect(cx - 80, WINDOW_HEIGHT // 2 + 160, 160, 45) + pygame.draw.rect(self.screen, (100, 100, 100), select_rect, border_radius=8) + pygame.draw.rect(self.screen, COLOR_WHITE, select_rect, 2, border_radius=8) + s_text = self.font.render("选择关卡", True, COLOR_WHITE) + self.screen.blit(s_text, s_text.get_rect(center=select_rect.center)) + + hint = self.small_font.render("或按 R 重新开始", True, (180, 180, 180)) + self.screen.blit(hint, hint.get_rect(center=(cx, WINDOW_HEIGHT // 2 + 225))) + if __name__ == "__main__": Game().run() diff --git a/game/map.py b/game/map.py index 3d99065..c9e0167 100644 --- a/game/map.py +++ b/game/map.py @@ -2,20 +2,21 @@ import pygame import math from game.config import ( CELL_SIZE, GRID_COLS, GRID_ROWS, UI_TOP_HEIGHT, - COLOR_GRASS, COLOR_PATH, COLOR_GRID_LINE, - PATH_WAYPOINTS, + COLOR_GRASS, COLOR_PATH, COLOR_GRID_LINE, PATH_WAYPOINTS, ) class GameMap: - def __init__(self): + def __init__(self, waypoints=None, bg_color=None): + self.waypoints = waypoints or PATH_WAYPOINTS + self.bg_color = bg_color or COLOR_GRASS self.path_cells = set() self._calc_path_cells() def _calc_path_cells(self): - for i in range(len(PATH_WAYPOINTS) - 1): - x1, y1 = PATH_WAYPOINTS[i] - x2, y2 = PATH_WAYPOINTS[i + 1] + for i in range(len(self.waypoints) - 1): + x1, y1 = self.waypoints[i] + x2, y2 = self.waypoints[i + 1] dx = x2 - x1 dy = y2 - y1 dist = math.hypot(dx, dy) @@ -52,9 +53,9 @@ class GameMap: row * CELL_SIZE + UI_TOP_HEIGHT, CELL_SIZE, CELL_SIZE, ) - color = COLOR_PATH if (col, row) in self.path_cells else COLOR_GRASS + color = COLOR_PATH if (col, row) in self.path_cells else self.bg_color pygame.draw.rect(surface, color, rect) pygame.draw.rect(surface, COLOR_GRID_LINE, rect, 1) - for i in range(len(PATH_WAYPOINTS) - 1): - pygame.draw.line(surface, (180, 150, 120), PATH_WAYPOINTS[i], PATH_WAYPOINTS[i + 1], 3) + for i in range(len(self.waypoints) - 1): + pygame.draw.line(surface, (180, 150, 120), self.waypoints[i], self.waypoints[i + 1], 3) diff --git a/game/tower.py b/game/tower.py index 5747547..244e73a 100644 --- a/game/tower.py +++ b/game/tower.py @@ -39,16 +39,19 @@ class Tower: continue d = distance(self.x, self.y, e.x, e.y) if d <= self.range: - progress = e.waypoint_index + (1 - distance(e.x, e.y, *self._wp(e.waypoint_index)) / 100) + wp_idx = min(e.waypoint_index, len(e.waypoints) - 1) + progress = e.waypoint_index + (1 - distance(e.x, e.y, *e.waypoints[wp_idx]) / 100) if progress > best_progress: best_progress = progress best = e self.target = best def _wp(self, idx): + if self.target and idx < len(self.target.waypoints): + return self.target.waypoints[idx] + if self.target: + return self.target.waypoints[-1] from game.config import PATH_WAYPOINTS - if idx < len(PATH_WAYPOINTS): - return PATH_WAYPOINTS[idx] return PATH_WAYPOINTS[-1] def update(self, dt, enemies, projectiles): diff --git a/game/ui.py b/game/ui.py index 094d9e6..6545d79 100644 --- a/game/ui.py +++ b/game/ui.py @@ -4,7 +4,7 @@ from game.config import ( WINDOW_WIDTH, WINDOW_HEIGHT, UI_TOP_HEIGHT, UI_BOTTOM_HEIGHT, CELL_SIZE, COLOR_BLACK, COLOR_WHITE, COLOR_GOLD, COLOR_RED, COLOR_GREEN, COLOR_DARK_GRAY, COLOR_GRAY, COLOR_BG, - TOWER_DATA, INITIAL_LIVES, TOTAL_WAVES, COLOR_LIGHT_BLUE, + TOWER_DATA, COLOR_LIGHT_BLUE, ) @@ -43,7 +43,7 @@ class UI: return None return self.selected_tower - def draw(self, surface, gold, lives, wave_num, wave_active, has_more, font, small_font): + def draw(self, surface, gold, lives, wave_num, wave_active, has_more, font, small_font, total_waves=10): # Top bar pygame.draw.rect(surface, COLOR_DARK_GRAY, (0, 0, WINDOW_WIDTH, UI_TOP_HEIGHT)) @@ -53,7 +53,7 @@ class UI: lives_text = font.render(f"生命: {lives}", True, COLOR_RED if lives <= 3 else COLOR_GREEN) surface.blit(lives_text, (160, 10)) - wave_text = font.render(f"波次: {wave_num}/{TOTAL_WAVES}", True, COLOR_WHITE) + wave_text = font.render(f"波次: {wave_num}/{total_waves}", True, COLOR_WHITE) surface.blit(wave_text, (310, 10)) # Slow-time indicator diff --git a/game/wave.py b/game/wave.py index b876265..02c234a 100644 --- a/game/wave.py +++ b/game/wave.py @@ -1,22 +1,16 @@ from game.enemy import Enemy +from game.config import PATH_WAYPOINTS WAVE_DATA = [ [{"type": "normal", "count": 5, "interval": 1.0}], - [{"type": "normal", "count": 6, "interval": 0.8}, {"type": "swarm", "count": 4, "interval": 0.3}], - [{"type": "normal", "count": 5, "interval": 0.8}, {"type": "fast", "count": 3, "interval": 0.6}], - [{"type": "swarm", "count": 10, "interval": 0.25}, {"type": "normal", "count": 5, "interval": 0.7}], - [{"type": "normal", "count": 5, "interval": 0.6}, {"type": "heavy", "count": 2, "interval": 1.5}, {"type": "boss", "count": 1, "interval": 2.0}], - [{"type": "normal", "count": 8, "interval": 0.5}, {"type": "fast", "count": 5, "interval": 0.4}, {"type": "swarm", "count": 8, "interval": 0.2}], - [{"type": "shield", "count": 3, "interval": 1.2}, {"type": "healer", "count": 2, "interval": 1.5}, {"type": "normal", "count": 6, "interval": 0.6}], - [{"type": "heavy", "count": 4, "interval": 0.8}, {"type": "fast", "count": 5, "interval": 0.5}, {"type": "shield", "count": 3, "interval": 1.0}], - [{"type": "swarm", "count": 15, "interval": 0.15}, {"type": "healer", "count": 3, "interval": 1.0}, {"type": "heavy", "count": 4, "interval": 0.7}], - [{"type": "normal", "count": 10, "interval": 0.3}, {"type": "heavy", "count": 5, "interval": 0.5}, {"type": "fast", "count": 8, "interval": 0.3}, {"type": "shield", "count": 4, "interval": 0.8}, {"type": "healer", "count": 2, "interval": 1.0}, {"type": "boss", "count": 2, "interval": 2.0}], ] class WaveManager: - def __init__(self): + def __init__(self, wave_data=None, waypoints=None): + self.wave_data = wave_data or WAVE_DATA + self.waypoints = waypoints or PATH_WAYPOINTS self.current_wave = 0 self.spawn_queue = [] self.spawn_timer = 0 @@ -24,12 +18,12 @@ class WaveManager: self.all_waves_done = False def start_next_wave(self): - if self.current_wave >= len(WAVE_DATA): + if self.current_wave >= len(self.wave_data): self.all_waves_done = True return False self.spawn_queue = [] - for group in WAVE_DATA[self.current_wave]: + for group in self.wave_data[self.current_wave]: for _ in range(group["count"]): self.spawn_queue.append((group["type"], group["interval"])) @@ -38,6 +32,10 @@ class WaveManager: self.spawn_timer = 0 return True + @property + def total_waves(self): + return len(self.wave_data) + def update(self, dt, enemies): if not self.wave_active: return @@ -50,11 +48,11 @@ class WaveManager: self.spawn_timer -= dt if self.spawn_timer <= 0: enemy_type, interval = self.spawn_queue.pop(0) - enemies.append(Enemy(enemy_type)) + enemies.append(Enemy(enemy_type, self.waypoints)) self.spawn_timer = self.spawn_queue[0][1] if self.spawn_queue else 0 def is_wave_complete(self): return not self.wave_active and self.current_wave > 0 def has_more_waves(self): - return self.current_wave < len(WAVE_DATA) + return self.current_wave < len(self.wave_data)