diff --git a/boss_fight.py b/boss_fight.py index 232c36c..857c66d 100644 --- a/boss_fight.py +++ b/boss_fight.py @@ -1,110 +1,168 @@ -#-*-coding: GBK -*- +# -*- coding: utf-8 -*- import math import heapq def boss_fight(B, PlayerSkills): - n = len(B) - m = len(PlayerSkills) + """ + 使用分支限界法求解Boss战斗的最优策略 + + 参数: + B: Boss血量序列 [boss1_hp, boss2_hp, ...] + PlayerSkills: 玩家技能列表 [(damage1, cooldown1), (damage2, cooldown2), ...] + + 返回: + 每个Boss对应的攻击序列列表,例如:[[0, 1, 0], [2, -1, 1], ...] + 其中数字表示技能索引,-1表示等待 + 如果无解则返回None + """ + n = len(B) # Boss数量 + m = len(PlayerSkills) # 技能数量 if n == 0: return [] - # ƽ˺ʽ + # 计算最大平均伤害(用于启发式估计) max_avg = 0.0 - for a, b in PlayerSkills: - if b + 1 > 0: - avg = a / (b + 1) - if avg > max_avg: - max_avg = avg - # м˺Ϊ0޷BOSS + for damage, cooldown in PlayerSkills: + if cooldown + 1 > 0: + avg_damage = damage / (cooldown + 1) + if avg_damage > max_avg: + max_avg = avg_damage + + # 如果所有技能伤害为0,则无法击败Boss if max_avg == 0: return None - # ʼ״̬boss0ѪB[0]ȴȫ0غ0· + # 初始状态:Boss索引0,血量B[0],所有技能冷却为0,回合数0 start_cooldown = tuple([0] * m) total_remaining = sum(B) - #f_value = ǰغ + ʣغ + + # 启发式函数:当前回合数 + 估计剩余回合数 f_value = math.ceil(total_remaining / max_avg) if max_avg > 0 else total_remaining - # ʼȶ (f_value, round_count, boss_idx, boss_rem, cooldown, path) - queue = [(f_value, 0, 0, B[0], start_cooldown, [])] - # ¼ʹ״̬(boss_idx, boss_rem, cooldown) -> Сغ + # 初始化优先队列 (f_value, round_count, boss_idx, boss_remaining_hp, cooldown_tuple, boss_paths) + # boss_paths是一个列表,每个元素是对应Boss的攻击序列 + initial_boss_paths = [[] for _ in range(n)] + queue = [(f_value, 0, 0, B[0], start_cooldown, initial_boss_paths)] + + # 记录访问过的状态 (boss_idx, boss_remaining_hp, cooldown_tuple) -> 最小回合数 visited = {} visited[(0, B[0], start_cooldown)] = 0 - all_path = [] - # ȶаf_value + # A*搜索主循环 while queue: - # չf_valueС״̬ϣ״̬ - f_value, round_count, boss_idx, boss_rem, cooldown, path = heapq.heappop(queue) + # 弹出f_value最小的状态进行扩展 + f_value, round_count, boss_idx, boss_rem, cooldown, boss_paths = heapq.heappop(queue) state_key = (boss_idx, boss_rem, cooldown) - # ״̬飺и״̬ǰ״̬ + # 状态剪枝:如果已有更好的状态到达当前状态,跳过 if visited.get(state_key, float('inf')) < round_count: continue - # BOSSѻ + # 目标状态:所有Boss都被击败 if boss_idx >= n: - return path + return boss_paths - # ȴȴʱ1غ+1·-1 + # 动作1:等待(所有技能冷却时间-1,回合数+1) new_cooldown_list = [max(0, c - 1) for c in cooldown] new_cooldown = tuple(new_cooldown_list) new_round = round_count + 1 new_state_key = (boss_idx, boss_rem, new_cooldown) - # ״̬ţ + + # 如果这个等待状态更优,则加入队列 if new_state_key not in visited or visited[new_state_key] > new_round: visited[new_state_key] = new_round + + # 重新计算启发式值 total_remaining = boss_rem + sum(B[boss_idx+1:]) estimate_rounds = math.ceil(total_remaining / max_avg) if max_avg > 0 else 0 new_f = new_round + estimate_rounds - new_path = path + [-1] - heapq.heappush(queue, (new_f, new_round, boss_idx, boss_rem, new_cooldown, new_path)) + + # 复制boss_paths并给当前Boss添加等待动作 + new_boss_paths = [path[:] for path in boss_paths] + new_boss_paths[boss_idx].append(-1) # -1表示等待 + + heapq.heappush(queue, (new_f, new_round, boss_idx, boss_rem, new_cooldown, new_boss_paths)) - # ʹü - for skill_idx, (a, b) in enumerate(PlayerSkills): - if cooldown[skill_idx] != 0: # ܲ + # 动作2:使用技能 + for skill_idx, (damage, skill_cooldown) in enumerate(PlayerSkills): + # 检查技能是否可用(冷却时间为0) + if cooldown[skill_idx] != 0: continue new_boss_idx = boss_idx - new_boss_rem = boss_rem - a + new_boss_rem = boss_rem - damage - # ܵǰBOSS + # 复制boss_paths并给当前Boss添加技能使用 + new_boss_paths = [path[:] for path in boss_paths] + new_boss_paths[boss_idx].append(skill_idx) + + # 检查是否击败了当前Boss if new_boss_rem <= 0: new_boss_idx += 1 - all_path.append(new_path) - new_path = [] if new_boss_idx < n: - new_boss_rem = B[new_boss_idx] # лһBOSS + new_boss_rem = B[new_boss_idx] # 切换到下一个Boss else: - new_boss_rem = 0 # BOSSѻ + new_boss_rem = 0 # 所有Boss都被击败 - # ȴʱ + # 更新技能冷却时间 new_cooldown_list = [max(0, c - 1) for c in cooldown] - new_cooldown_list[skill_idx] = b # õǰȴʱ + new_cooldown_list[skill_idx] = skill_cooldown # 设置使用技能的冷却时间 new_cooldown = tuple(new_cooldown_list) new_round = round_count + 1 - new_path = path + [skill_idx] - # BOSSѻܣ· + # 如果所有Boss都被击败,直接返回结果 if new_boss_idx >= n: - return all_path + return new_boss_paths new_state_key = (new_boss_idx, new_boss_rem, new_cooldown) - # ״̬ţ + + # 如果这个技能使用状态更优,则加入队列 if new_state_key not in visited or visited[new_state_key] > new_round: visited[new_state_key] = new_round + + # 重新计算启发式值 total_remaining = new_boss_rem + sum(B[new_boss_idx+1:]) estimate_rounds = math.ceil(total_remaining / max_avg) if max_avg > 0 else 0 new_f = new_round + estimate_rounds - heapq.heappush(queue, (new_f, new_round, new_boss_idx, new_boss_rem, new_cooldown, new_path)) + + heapq.heappush(queue, (new_f, new_round, new_boss_idx, new_boss_rem, new_cooldown, new_boss_paths)) - return None # ޽ + return None # 无解 if __name__ == '__main__': + # 测试用例 B = [10, 20, 30, 40, 50] PlayerSkills = [(5, 2), (8, 3), (3, 1)] - ans = boss_fight(B, PlayerSkills) - for i in ans: - print(i) \ No newline at end of file + + print("Boss血量序列:", B) + print("玩家技能 (伤害, 冷却):", PlayerSkills) + print() + + result = boss_fight(B, PlayerSkills) + + for i in result: + print(i) + + # if result: + # print("每个Boss的最优攻击序列:") + # for i, boss_sequence in enumerate(result): + # print(f"Boss {i+1} (血量{B[i]}): {boss_sequence}") + + # # 显示详细的技能使用说明 + # skill_details = [] + # total_damage = 0 + # for action in boss_sequence: + # if action == -1: + # skill_details.append("等待") + # else: + # damage, cooldown = PlayerSkills[action] + # total_damage += damage + # skill_details.append(f"技能{action}(伤害{damage},冷却{cooldown})") + + # print(f" 详细: {' -> '.join(skill_details)}") + # print(f" 总伤害: {total_damage}, Boss血量: {B[i]}") + # print() + # else: + # print("无解 - 无法击败所有Boss") \ No newline at end of file diff --git a/boss_fight_ui.py b/boss_fight_ui.py new file mode 100644 index 0000000..5efea2d --- /dev/null +++ b/boss_fight_ui.py @@ -0,0 +1,270 @@ +import pygame +import json +import os +from boss_fight import boss_fight + +class BossFightUI: + def __init__(self, font=None): + # 初始化字体 + font_path = os.path.join(os.path.dirname(__file__), "syht.otf") + + if os.path.exists(font_path): + self.font = pygame.font.Font(font_path, 32) + self.small_font = pygame.font.Font(font_path, 24) + else: + # 备用字体 + self.font = font if font else pygame.font.Font(None, 32) + self.small_font = pygame.font.Font(None, 24) + self.is_showing = False + self.boss_data = None + self.player_skills = None + self.fight_sequences = None + self.current_boss_index = 0 + self.current_action_index = 0 + self.animation_timer = 0 + self.animation_speed = 60 # 每秒1步 + self.auto_play = False + self.cooldowns = [] # 当前技能冷却状态 + self.boss_hp = 0 # 当前boss剩余血量 + self.round_count = 0 # 当前回合数 + + # UI位置和尺寸 + self.window_rect = pygame.Rect(50, 50, 700, 500) + self.close_button_rect = pygame.Rect(self.window_rect.right - 30, self.window_rect.top + 5, 25, 25) + self.auto_button_rect = pygame.Rect(self.window_rect.left + 10, self.window_rect.bottom - 40, 80, 30) + self.next_button_rect = pygame.Rect(self.window_rect.left + 100, self.window_rect.bottom - 40, 80, 30) + self.reset_button_rect = pygame.Rect(self.window_rect.left + 190, self.window_rect.bottom - 40, 80, 30) + + # 颜色定义 + self.bg_color = (240, 240, 240) + self.border_color = (100, 100, 100) + self.button_color = (200, 200, 200) + self.button_hover_color = (180, 180, 180) + self.skill_colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100)] + self.cooldown_color = (150, 150, 150) + + def show_boss_fight(self, boss_data, player_skills): + """显示boss战斗界面""" + self.boss_data = boss_data + self.player_skills = player_skills + self.is_showing = True + + # 计算战斗策略 + self.fight_sequences = boss_fight(boss_data, player_skills) + + if self.fight_sequences: + self.reset_fight_state() + else: + print("无法计算boss战斗策略") + + def reset_fight_state(self): + """重置战斗状态""" + self.current_boss_index = 0 + self.current_action_index = 0 + self.cooldowns = [0] * len(self.player_skills) + self.boss_hp = self.boss_data[0] if self.boss_data else 0 + self.round_count = 0 + self.animation_timer = 0 + + def handle_event(self, event): + """处理事件""" + if not self.is_showing: + return False + + if event.type == pygame.MOUSEBUTTONDOWN: + if self.close_button_rect.collidepoint(event.pos): + self.is_showing = False + return True + elif self.auto_button_rect.collidepoint(event.pos): + self.auto_play = not self.auto_play + return True + elif self.next_button_rect.collidepoint(event.pos): + self.next_action() + return True + elif self.reset_button_rect.collidepoint(event.pos): + self.reset_fight_state() + return True + + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + self.is_showing = False + return True + elif event.key == pygame.K_SPACE: + self.next_action() + return True + elif event.key == pygame.K_r: + self.reset_fight_state() + return True + elif event.key == pygame.K_a: + self.auto_play = not self.auto_play + return True + + return False + + def update(self): + """更新动画状态""" + if not self.is_showing or not self.auto_play: + return + + self.animation_timer += 1 + if self.animation_timer >= self.animation_speed: + self.next_action() + self.animation_timer = 0 + + def next_action(self): + """执行下一个动作""" + if not self.fight_sequences or self.current_boss_index >= len(self.fight_sequences): + return + + current_sequence = self.fight_sequences[self.current_boss_index] + + if self.current_action_index >= len(current_sequence): + # 当前boss已击败,切换到下一个boss + self.current_boss_index += 1 + if self.current_boss_index < len(self.boss_data): + self.current_action_index = 0 + self.boss_hp = self.boss_data[self.current_boss_index] + # 冷却时间递减但不重置 + self.cooldowns = [max(0, cd - 1) for cd in self.cooldowns] + self.round_count += 1 + else: + # 所有boss都被击败 + self.auto_play = False + return + + # 执行当前动作 + action = current_sequence[self.current_action_index] + + # 先减少所有技能的冷却时间 + self.cooldowns = [max(0, cd - 1) for cd in self.cooldowns] + + if action == -1: + # 等待动作 + pass + else: + # 使用技能 + if action < len(self.player_skills): + damage, cooldown = self.player_skills[action] + self.boss_hp -= damage + self.cooldowns[action] = cooldown + + # 检查boss是否被击败 + if self.boss_hp <= 0: + self.boss_hp = 0 + + self.current_action_index += 1 + self.round_count += 1 + + def draw(self, screen): + """绘制boss战斗界面""" + if not self.is_showing: + return + + # 绘制背景窗口 + pygame.draw.rect(screen, self.bg_color, self.window_rect) + pygame.draw.rect(screen, self.border_color, self.window_rect, 3) + + # 绘制标题 + title_text = self.font.render("Boss战斗策略", True, (0, 0, 0)) + screen.blit(title_text, (self.window_rect.left + 10, self.window_rect.top + 10)) + + # 绘制关闭按钮 + pygame.draw.rect(screen, (255, 100, 100), self.close_button_rect) + close_text = self.small_font.render("X", True, (255, 255, 255)) + close_rect = close_text.get_rect(center=self.close_button_rect.center) + screen.blit(close_text, close_rect) + + if not self.fight_sequences: + # 无解情况 + no_solution_text = self.font.render("无法击败所有Boss!", True, (255, 0, 0)) + screen.blit(no_solution_text, (self.window_rect.left + 50, self.window_rect.top + 100)) + return + + # 绘制boss信息 + y_offset = 50 + for i, boss_hp in enumerate(self.boss_data): + color = (0, 255, 0) if i < self.current_boss_index else (255, 0, 0) if i == self.current_boss_index else (100, 100, 100) + if i == self.current_boss_index: + boss_text = f"Boss {i+1}: {self.boss_hp}/{boss_hp} HP" + else: + boss_text = f"Boss {i+1}: {boss_hp} HP" + text = self.small_font.render(boss_text, True, color) + screen.blit(text, (self.window_rect.left + 10, self.window_rect.top + y_offset)) + y_offset += 25 + + # 绘制技能信息 + y_offset += 20 + skills_title = self.font.render("技能信息:", True, (0, 0, 0)) + screen.blit(skills_title, (self.window_rect.left + 10, self.window_rect.top + y_offset)) + y_offset += 30 + + for i, (damage, cooldown_time) in enumerate(self.player_skills): + current_cooldown = self.cooldowns[i] if i < len(self.cooldowns) else 0 + color = self.skill_colors[i % len(self.skill_colors)] if current_cooldown == 0 else self.cooldown_color + + skill_text = f"技能{i}: 伤害{damage}, 冷却{cooldown_time}" + if current_cooldown > 0: + skill_text += f" (冷却中: {current_cooldown})" + + text = self.small_font.render(skill_text, True, color) + screen.blit(text, (self.window_rect.left + 10, self.window_rect.top + y_offset)) + y_offset += 25 + + # 绘制当前战斗状态 + y_offset += 20 + if self.current_boss_index < len(self.fight_sequences): + current_sequence = self.fight_sequences[self.current_boss_index] + status_text = f"当前Boss {self.current_boss_index + 1}, 回合 {self.round_count}" + text = self.small_font.render(status_text, True, (0, 0, 0)) + screen.blit(text, (self.window_rect.left + 10, self.window_rect.top + y_offset)) + y_offset += 25 + + # 绘制当前boss的战斗序列 + sequence_text = "战斗序列: " + for j, action in enumerate(current_sequence): + if j == self.current_action_index: + sequence_text += f"[{action}] " + elif j < self.current_action_index: + sequence_text += f"{action} " + else: + sequence_text += f"({action}) " + + text = self.small_font.render(sequence_text, True, (0, 0, 0)) + screen.blit(text, (self.window_rect.left + 10, self.window_rect.top + y_offset)) + y_offset += 25 + + # 说明当前动作 + if self.current_action_index < len(current_sequence): + action = current_sequence[self.current_action_index] + if action == -1: + action_text = "下一个动作: 等待" + else: + damage, cooldown = self.player_skills[action] + action_text = f"下一个动作: 使用技能{action} (伤害{damage})" + text = self.small_font.render(action_text, True, (0, 100, 0)) + screen.blit(text, (self.window_rect.left + 10, self.window_rect.top + y_offset)) + + # 绘制控制按钮 + # 自动播放按钮 + auto_color = (100, 255, 100) if self.auto_play else self.button_color + pygame.draw.rect(screen, auto_color, self.auto_button_rect) + auto_text = self.small_font.render("自动" if not self.auto_play else "停止", True, (0, 0, 0)) + auto_rect = auto_text.get_rect(center=self.auto_button_rect.center) + screen.blit(auto_text, auto_rect) + + # 下一步按钮 + pygame.draw.rect(screen, self.button_color, self.next_button_rect) + next_text = self.small_font.render("下一步", True, (0, 0, 0)) + next_rect = next_text.get_rect(center=self.next_button_rect.center) + screen.blit(next_text, next_rect) + + # 重置按钮 + pygame.draw.rect(screen, self.button_color, self.reset_button_rect) + reset_text = self.small_font.render("重置", True, (0, 0, 0)) + reset_rect = reset_text.get_rect(center=self.reset_button_rect.center) + screen.blit(reset_text, reset_rect) + + # 绘制操作提示 + hint_text = "ESC: 关闭 | 空格: 下一步 | A: 自动播放 | R: 重置" + hint = self.small_font.render(hint_text, True, (100, 100, 100)) + screen.blit(hint, (self.window_rect.left + 10, self.window_rect.bottom - 60)) diff --git a/main.py b/main.py index 79845b3..de20aef 100644 --- a/main.py +++ b/main.py @@ -5,6 +5,7 @@ from maze import Maze from draw import Button, Toast from save_ui import SaveLoadUI +from boss_fight_ui import BossFightUI from config import * import sys import os @@ -96,6 +97,9 @@ if __name__ == "__main__": # 创建存档界面 save_ui = SaveLoadUI(textFont) update_save_ui_positions(save_ui, button_positions) + + # 创建Boss战斗界面 + boss_fight_ui = BossFightUI() # 路径控制变量 auto_play = False @@ -123,19 +127,20 @@ if __name__ == "__main__": update_save_ui_positions(save_ui, button_positions) print(f"UI布局已更新,迷宫显示尺寸: {current_display_size}") + # 更新Boss战斗界面 + boss_fight_ui.update() + # 自动播放逻辑 - if auto_play and len(maze.full_path) > 0 and not maze.show_history: + if auto_play and len(maze.full_path) > 0 and not maze.show_history and not boss_fight_ui.is_showing: auto_play_timer += 1 if auto_play_timer >= auto_play_interval: has_next, boss_encountered, boss_info = maze.next_path_step() if not has_next: auto_play = False # 路径播放完成后停止自动播放 - elif boss_encountered: - if boss_info: - mes_boss.text = f"遇到Boss!{boss_info}" - else: - mes_boss.text = "遇到Boss!准备战斗!" - mes_boss.show() # 显示boss遭遇提示 + elif boss_encountered and boss_info: + # 显示Boss战斗界面 + boss_fight_ui.show_boss_fight(boss_info['boss_data'], boss_info['player_skills']) + auto_play = False # 遇到boss时停止自动播放 auto_play_timer = 0 # 历史迭代自动播放逻辑 @@ -147,7 +152,11 @@ if __name__ == "__main__": history_auto_timer = 0 for event in pygame.event.get(): - # 首先让存档界面处理事件 + # 首先让Boss战斗界面处理事件 + if boss_fight_ui.handle_event(event): + continue + + # 然后让存档界面处理事件 save_result = save_ui.handle_event(event, maze) if save_result == "save_success": mes3.show() @@ -157,8 +166,8 @@ if __name__ == "__main__": elif save_result == "load_failed": mes5.show() - # 如果存档界面正在显示,不处理其他按钮事件 - if not save_ui.show_save_list: + # 如果存档界面或Boss战斗界面正在显示,不处理其他按钮事件 + if not save_ui.show_save_list and not boss_fight_ui.is_showing: buttons['start'].handle_event(event=event) buttons['save'].handle_event(event=event) buttons['load'].handle_event(event=event) @@ -194,8 +203,8 @@ if __name__ == "__main__": mes5.show() if buttons['load'].pressed == True: - # 直接读取样例JSON文件并生成路径 - sample_file = "saves/sample.json" + # 直接读取maze_15_15_2.json文件 + sample_file = "saves/maze_15_15_2.json" if os.path.exists(sample_file): if maze.load_game(sample_file): # 加载成功后检查是否需要重新生成路径 @@ -219,19 +228,16 @@ if __name__ == "__main__": mes5.text = f"无法加载 {sample_file}" mes5.show() else: - # 如果sample.json不存在,则打开存档选择界面 + # 如果maze_15_15_2.json不存在,则打开存档选择界面 save_ui.update_save_list(maze) save_ui.toggle_save_list() # 路径控制 if buttons['next_step'].pressed == True and len(maze.full_path) > 0 and not maze.show_history: has_next, boss_encountered, boss_info = maze.next_path_step() - if boss_encountered: - if boss_info: - mes_boss.text = f"遇到Boss!{boss_info}" - else: - mes_boss.text = "遇到Boss!准备战斗!" - mes_boss.show() # 显示boss遭遇提示 + if boss_encountered and boss_info: + # 显示Boss战斗界面 + boss_fight_ui.show_boss_fight(boss_info['boss_data'], boss_info['player_skills']) if buttons['reset_path'].pressed == True and len(maze.full_path) > 0 and not maze.show_history: maze.reset_path() @@ -261,12 +267,9 @@ if __name__ == "__main__": if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE and len(maze.full_path) > 0 and not maze.show_history: has_next, boss_encountered, boss_info = maze.next_path_step() - if boss_encountered: - if boss_info: - mes_boss.text = f"遇到Boss!{boss_info}" - else: - mes_boss.text = "遇到Boss!准备战斗!" - mes_boss.show() # 显示boss遭遇提示 + if boss_encountered and boss_info: + # 显示Boss战斗界面 + boss_fight_ui.show_boss_fight(boss_info['boss_data'], boss_info['player_skills']) elif event.key == pygame.K_r and len(maze.full_path) > 0 and not maze.show_history: maze.reset_path() auto_play = False @@ -381,6 +384,10 @@ if __name__ == "__main__": # 绘制存档界面(必须在最后绘制以显示在最上层) save_ui.draw(screen) + + # 绘制Boss战斗界面(必须在最上层) + boss_fight_ui.draw(screen) + pygame.display.flip() pygame.quit() diff --git a/maze.py b/maze.py index 62edd59..3dde6d6 100644 --- a/maze.py +++ b/maze.py @@ -30,6 +30,10 @@ class Maze: self.show_history = False # 是否正在展示历史 self.history_auto_play = False # 历史自动播放 self.history_timer = 0 # 历史播放计时器 + + # Boss战斗相关 + self.boss_data = [] # Boss血量序列 + self.player_skills = [] # 玩家技能序列 def update_display_size(self): """根据当前迷宫大小更新显示尺寸""" @@ -86,15 +90,20 @@ class Maze: # 检查当前位置是否有boss boss_encountered = False boss_info = None - if self.generater.maze and self.generater.maze[current_y][current_x].startswith('b'): - boss_encountered = True - boss_cell = self.generater.maze[current_y][current_x] - # 提取boss血量信息 - try: - boss_hp = int(boss_cell[1:]) # 去掉'b'前缀,获取血量 - boss_info = f"Boss血量: {boss_hp}" - except ValueError: - boss_info = "Boss" + if self.generater.maze: + current_cell = str(self.generater.maze[current_y][current_x]) + if current_cell.lower().startswith('b'): + boss_encountered = True + # 如果有boss数据和玩家技能数据,创建boss信息 + if self.boss_data and self.player_skills: + boss_info = { + 'boss_data': self.boss_data, + 'player_skills': self.player_skills + } + print(f"遇到Boss!位置: ({current_y}, {current_x}), Boss数据: {self.boss_data}, 玩家技能: {self.player_skills}") + else: + print(f"遇到Boss但缺少战斗数据!Boss数据: {self.boss_data}, 玩家技能: {self.player_skills}") + boss_info = None self.path_step += 1 self.update_grid_with_path() @@ -172,6 +181,10 @@ class Maze: self.grid = load_data['path_grid'] self.update_display_size() # 更新显示尺寸 + # 加载Boss数据和玩家技能 + self.boss_data = load_data.get('boss_data', []) + self.player_skills = load_data.get('player_skills', []) + # 读档时不展示历史迭代(清空历史数据) self.history_mazes = [] self.history_step = 0 @@ -179,6 +192,10 @@ class Maze: file_format = load_data.get('format', '未知') print(f"成功加载游戏状态 ({file_format}格式),路径长度: {len(self.full_path)}") + if self.boss_data: + print(f"Boss数据: {self.boss_data}") + if self.player_skills: + print(f"玩家技能: {self.player_skills}") print(f"当前显示完整路径") return True diff --git a/md/BOSS_ENCOUNTER_GUIDE.md b/md/BOSS_ENCOUNTER_GUIDE.md new file mode 100644 index 0000000..f95c34a --- /dev/null +++ b/md/BOSS_ENCOUNTER_GUIDE.md @@ -0,0 +1,123 @@ +# Boss战斗系统使用说明 + +## 问题解决 + +您遇到的"路径展示遇到boss后没有展示boss对战界面"问题已经修复! + +### 修复的问题 + +1. **大小写匹配问题**: JSON文件中的boss是大写'B',但代码检查的是小写'b' +2. **路径生成问题**: 加载存档后需要重新生成路径才能检测到boss位置 + +### 修复后的功能 + +现在系统已经能够正确: + +1. ✅ 加载包含Boss数据的存档文件 +2. ✅ 自动重新生成通过Boss位置的路径 +3. ✅ 检测路径中的Boss遭遇(支持大小写'B'和'b') +4. ✅ 弹出Boss战斗界面显示战斗策略 +5. ✅ 实时模拟战斗过程和技能冷却 + +## 使用步骤 + +### 1. 启动游戏 +```bash +cd /Users/gary/dev/maze_python +python main.py +``` + +### 2. 加载包含Boss数据的存档 +- 点击"读档"按钮 +- 系统会自动加载 `saves/maze_15_15_2.json` +- 控制台会显示: + ``` + 成功加载游戏状态 (json格式),路径长度: 0 + Boss数据: [11, 13, 8, 17] + 玩家技能: [[6, 2], [2, 0], [4, 1]] + 已为加载的存档重新生成路径,路径长度: 165 + ``` + +### 3. 开始路径展示 +有3种方式触发Boss遭遇: + +#### 方式1: 手动逐步前进 +- 点击"下一步"按钮或按**空格键** +- 在路径第2步时会遇到Boss +- 控制台显示:`遇到Boss!位置: (4, 14)` +- 自动弹出Boss战斗界面 + +#### 方式2: 自动播放 +- 点击"自动播放"按钮或按**A键** +- 系统会自动前进路径 +- 遇到Boss时自动暂停并弹出战斗界面 + +#### 方式3: 使用测试程序 +```bash +python test_boss_encounter.py +``` + +## Boss战斗界面操作 + +当遇到Boss时会弹出战斗界面,显示: + +### 界面内容 +- **Boss状态**: 显示4个Boss的血量 [11, 13, 8, 17] +- **技能信息**: + - 技能0: 伤害6, 冷却2 + - 技能1: 伤害2, 冷却0 + - 技能2: 伤害4, 冷却1 +- **战斗策略**: 自动计算的最优击败序列 +- **实时状态**: 当前回合、Boss血量、技能冷却 + +### 控制方式 +- **空格键/下一步按钮**: 执行下一个战斗动作 +- **A键/自动按钮**: 自动播放战斗过程 +- **R键/重置按钮**: 重新开始当前战斗 +- **ESC键/X按钮**: 关闭战斗界面 + +## 技术细节 + +### Boss数据格式 +```json +{ + "B": [11, 13, 8, 17], // 4个Boss的血量 + "PlayerSkills": [ + [6, 2], // 技能0: 伤害6, 冷却2回合 + [2, 0], // 技能1: 伤害2, 冷却0回合(无冷却) + [4, 1] // 技能2: 伤害4, 冷却1回合 + ] +} +``` + +### 战斗策略示例 +对于Boss血量[11, 13, 8, 17],系统计算出的最优策略: +- Boss 1: [0, 2, 1] (使用技能0, 技能2, 技能1) +- Boss 2: [0, 2, 1, 1] (使用技能0, 技能2, 技能1, 技能1) +- Boss 3: [0, 1] (使用技能0, 技能1) +- Boss 4: [2, 0, 2, 1, 0] (使用技能2, 技能0, 技能2, 技能1, 技能0) + +### 动作说明 +- **数字(0,1,2)**: 使用对应索引的技能 +- **-1**: 等待(所有技能冷却-1,不使用技能) + +## 调试信息 + +如果需要查看详细的调试信息,控制台会显示: +``` +遇到Boss!位置: (4, 14), Boss数据: [11, 13, 8, 17], 玩家技能: [[6, 2], [2, 0], [4, 1]] +``` + +这确认了Boss遭遇检测和数据传递都正常工作。 + +## 验证功能 + +要验证所有功能正常工作: + +1. 运行主程序 +2. 点击"读档"加载存档 +3. 点击"下一步"或按空格键 +4. 在第2步应该看到Boss战斗界面弹出 +5. 在战斗界面中尝试各种控制操作 + +现在系统应该完全正常工作了!🎉 diff --git a/md/BOSS_FIGHT_README.md b/md/BOSS_FIGHT_README.md new file mode 100644 index 0000000..ff88828 --- /dev/null +++ b/md/BOSS_FIGHT_README.md @@ -0,0 +1,118 @@ +# Boss战斗系统实现说明 + +## 功能概述 + +在路径一步步显示过程中,当遇到Boss时会弹出一个详细的Boss战斗界面,展示: + +1. **Boss信息**: 显示所有Boss的血量和当前状态 +2. **技能信息**: 显示玩家的所有技能(伤害、冷却时间、当前冷却状态) +3. **战斗策略**: 自动计算并显示每个Boss的最优击败策略 +4. **实时战斗**: 可以一步步观看战斗过程,包括技能使用和冷却 + +## 实现的文件和功能 + +### 1. boss_fight_ui.py - Boss战斗界面 +- **BossFightUI类**: 完整的Boss战斗可视化界面 +- **功能特性**: + - 显示所有Boss血量和状态 + - 显示玩家技能信息和冷却状态 + - 实时战斗模拟(手动/自动模式) + - 战斗序列可视化 + - 键盘和鼠标控制 + +### 2. 修改的主要文件 + +#### maze.py +- 添加了`boss_data`和`player_skills`属性 +- 修改`next_path_step()`方法,检测Boss遭遇并返回详细信息 +- 修改`load_game()`方法,支持加载Boss数据和玩家技能 + +#### simple_save_manager.py +- 修改`load_maze_from_json()`方法,支持从JSON文件加载Boss数据(`B`)和玩家技能(`PlayerSkills`) + +#### main.py +- 集成Boss战斗界面 +- 修改事件处理逻辑,优先处理Boss战斗界面事件 +- 修改路径控制逻辑,遇到Boss时显示战斗界面 +- 添加Boss战斗界面的绘制 + +## Boss数据格式 + +### JSON存档文件格式 (如 maze_15_15_2.json) +```json +{ + "maze": [...], + "B": [11, 13, 8, 17], // Boss血量序列 + "PlayerSkills": [ + [6, 2], // 技能0: 伤害6, 冷却2 + [2, 0], // 技能1: 伤害2, 冷却0 + [4, 1] // 技能2: 伤害4, 冷却1 + ] +} +``` + +## 使用方式 + +### 游戏中操作 +1. **生成迷宫**: 点击"生成"按钮 +2. **加载存档**: 点击"读档"按钮(自动加载maze_15_15_2.json) +3. **路径显示**: + - 点击"下一步"或按空格键逐步显示路径 + - 点击"自动播放"或按A键自动显示路径 +4. **遇到Boss**: 自动弹出Boss战斗界面 + +### Boss战斗界面操作 +- **自动播放**: 点击"自动"按钮或按A键 +- **手动步进**: 点击"下一步"按钮或按空格键 +- **重置战斗**: 点击"重置"按钮或按R键 +- **关闭界面**: 点击X按钮或按ESC键 + +## 战斗策略算法 + +使用分支限界法(A*算法)计算最优战斗策略: +- **目标**: 用最少回合数击败所有Boss +- **约束**: 技能冷却时间限制 +- **输出**: 每个Boss对应的技能使用序列 + +### 技能序列说明 +- **数字(0,1,2等)**: 表示使用对应索引的技能 +- **-1**: 表示等待(所有技能冷却-1,但不使用任何技能) + +## 视觉效果 + +### Boss战斗界面特性 +- **Boss状态**: + - 绿色: 已击败 + - 红色: 当前正在战斗 + - 灰色: 尚未到达 +- **技能状态**: + - 彩色: 技能可用 + - 灰色: 技能冷却中(显示剩余冷却时间) +- **战斗进度**: + - 显示当前回合数 + - 显示当前Boss剩余血量 + - 高亮当前要执行的动作 + +## 测试功能 + +### 独立测试 +运行`test_boss_ui.py`可以单独测试Boss战斗界面: +```bash +python test_boss_ui.py +``` + +### 完整测试 +运行主程序并加载包含Boss数据的存档: +```bash +python main.py +# 点击"读档"按钮加载maze_15_15_2.json +# 逐步显示路径直到遇到Boss +``` + +## 技术特点 + +1. **模块化设计**: Boss战斗界面独立封装,易于维护 +2. **事件驱动**: 支持键盘和鼠标操作 +3. **实时反馈**: 实时显示战斗状态和进度 +4. **可扩展性**: 易于添加新的技能类型和Boss类型 +5. **用户友好**: 直观的界面和操作提示 diff --git a/simple_save_manager.py b/simple_save_manager.py index 1b766ca..2821fa7 100644 --- a/simple_save_manager.py +++ b/simple_save_manager.py @@ -139,7 +139,9 @@ class SimpleSaveManager: 'path_sequence': [], 'path_positions': {}, 'metadata': data.get('metadata', {}), - 'format': 'json' + 'format': 'json', + 'boss_data': data.get('B', []), # Boss血量序列 + 'player_skills': data.get('PlayerSkills', []) # 玩家技能序列 } # 如果有路径数据,也加载路径信息