From 383ab5f7adb853eb4b39605c88ca25af40bac178 Mon Sep 17 00:00:00 2001 From: Gary Gan Date: Mon, 30 Jun 2025 19:30:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=9E=E5=90=88=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- boss_fight.py | 212 ++++++++++++++++++++++++---------------------- maze.py | 12 +++ maze_generator.py | 69 +++++++++++---- 3 files changed, 175 insertions(+), 118 deletions(-) diff --git a/boss_fight.py b/boss_fight.py index 857c66d..f04b110 100644 --- a/boss_fight.py +++ b/boss_fight.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -import math import heapq +from collections import defaultdict def boss_fight(B, PlayerSkills): """ - 使用分支限界法求解Boss战斗的最优策略 + 使用改进的A*算法求解Boss战斗的最优策略 参数: B: Boss血量序列 [boss1_hp, boss2_hp, ...] @@ -20,121 +20,116 @@ def boss_fight(B, PlayerSkills): if n == 0: return [] - # 计算最大平均伤害(用于启发式估计) - max_avg = 0.0 - 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: + if all(skill[0] == 0 for skill in PlayerSkills): return None - # 初始状态:Boss索引0,血量B[0],所有技能冷却为0,回合数0 - start_cooldown = tuple([0] * m) - total_remaining = sum(B) - - # 启发式函数:当前回合数 + 估计剩余回合数 - f_value = math.ceil(total_remaining / max_avg) if max_avg > 0 else total_remaining - - # 初始化优先队列 (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 - - # A*搜索主循环 - while queue: - # 弹出f_value最小的状态进行扩展 - f_value, round_count, boss_idx, boss_rem, cooldown, boss_paths = heapq.heappop(queue) + def more_accurate_heuristic(boss_idx, boss_remaining_hp): + """更精确的启发式函数""" + if boss_idx >= n: + return 0 - state_key = (boss_idx, boss_rem, cooldown) - - # 状态剪枝:如果已有更好的状态到达当前状态,跳过 - if visited.get(state_key, float('inf')) < round_count: + # 计算剩余总血量 + remaining_hp = boss_remaining_hp + sum(B[boss_idx + 1:]) + + # 考虑技能的平均有效伤害(考虑冷却时间) + total_damage_per_turn = 0 + for damage, cooldown in PlayerSkills: + if damage > 0: + # 有效伤害 = 伤害 / (冷却时间 + 1) + effective_damage = damage / (cooldown + 1) + total_damage_per_turn += effective_damage + + if total_damage_per_turn > 0: + return max(1, int((remaining_hp + total_damage_per_turn - 1) / total_damage_per_turn)) + return remaining_hp + + # 使用更紧密的优先队列来保证最优性 + # 状态: (boss_idx, boss_hp, cooldown_tuple) + start_state = (0, B[0], tuple([0] * m)) + + # 优先队列: (f_score, g_score, state, path) + queue = [(more_accurate_heuristic(0, B[0]), 0, start_state, [])] + + # 记录到达每个状态的最短路径长度 + g_score = {start_state: 0} + + while queue: + f, g, (boss_idx, boss_hp, cooldown), path = heapq.heappop(queue) + + # 如果所有Boss都被击败 + if boss_idx >= n: + # 重构路径为每个Boss的序列 + result_paths = [[] for _ in range(n)] + current_boss = 0 + current_hp = B[0] + + for action in path: + result_paths[current_boss].append(action) + if action != -1: + damage = PlayerSkills[action][0] + current_hp -= damage + if current_hp <= 0: + current_boss += 1 + if current_boss < n: + current_hp = B[current_boss] + + return result_paths + + current_state = (boss_idx, boss_hp, cooldown) + + # 如果已经找到到达此状态的更短路径,跳过 + if current_state in g_score and g_score[current_state] < g: continue - # 目标状态:所有Boss都被击败 - if boss_idx >= n: - return boss_paths + # 生成所有可能的后继状态 + neighbors = [] - # 动作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) + # 选项1: 等待 + new_cooldown = tuple(max(0, cd - 1) for cd in cooldown) + neighbors.append((-1, boss_idx, boss_hp, 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 - - # 复制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)) - - # 动作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 - damage - - # 复制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 - if new_boss_idx < n: - new_boss_rem = B[new_boss_idx] # 切换到下一个Boss - else: - new_boss_rem = 0 # 所有Boss都被击败 - - # 更新技能冷却时间 - new_cooldown_list = [max(0, c - 1) for c in cooldown] - new_cooldown_list[skill_idx] = skill_cooldown # 设置使用技能的冷却时间 - new_cooldown = tuple(new_cooldown_list) - new_round = round_count + 1 - - # 如果所有Boss都被击败,直接返回结果 - if new_boss_idx >= n: - 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 + # 选项2: 使用每个可用技能 + for skill_idx in range(m): + if cooldown[skill_idx] == 0: # 技能可用 + damage, skill_cooldown = PlayerSkills[skill_idx] + new_hp = boss_hp - damage + new_boss_idx = boss_idx - # 重新计算启发式值 - 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 + # 更新冷却时间 + new_cooldown = list(max(0, cd - 1) for cd in cooldown) + new_cooldown[skill_idx] = skill_cooldown + new_cooldown = tuple(new_cooldown) - heapq.heappush(queue, (new_f, new_round, new_boss_idx, new_boss_rem, new_cooldown, new_boss_paths)) + # 检查Boss是否被击败 + if new_hp <= 0: + new_boss_idx += 1 + if new_boss_idx < n: + new_hp = B[new_boss_idx] + + neighbors.append((skill_idx, new_boss_idx, new_hp, new_cooldown)) + + # 处理每个邻居状态 + for action, new_boss_idx, new_boss_hp, new_cooldown in neighbors: + new_state = (new_boss_idx, new_boss_hp, new_cooldown) + tentative_g = g + 1 + + # 如果找到更好的路径到达邻居状态 + if new_state not in g_score or tentative_g < g_score[new_state]: + g_score[new_state] = tentative_g + h = more_accurate_heuristic(new_boss_idx, new_boss_hp) + f_score = tentative_g + h + new_path = path + [action] + + heapq.heappush(queue, (f_score, tentative_g, new_state, new_path)) return None # 无解 if __name__ == '__main__': # 测试用例 - B = [10, 20, 30, 40, 50] - PlayerSkills = [(5, 2), (8, 3), (3, 1)] + B = [19, 17, 14, 19] + PlayerSkills = [(6, 2), (9, 5), (5, 3), (4, 3), (2, 0)] print("Boss血量序列:", B) print("玩家技能 (伤害, 冷却):", PlayerSkills) @@ -142,8 +137,19 @@ if __name__ == '__main__': 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}: {boss_sequence}") + + # 合并所有序列 + full_sequence = [] + for boss_sequence in result: + full_sequence.extend(boss_sequence) + print(f"完整序列: {''.join(map(str, full_sequence))}") + print(f"总步数: {len(full_sequence)}") + else: + print("无解 - 无法击败所有Boss") # if result: # print("每个Boss的最优攻击序列:") diff --git a/maze.py b/maze.py index 3dde6d6..ea17d84 100644 --- a/maze.py +++ b/maze.py @@ -5,6 +5,7 @@ from tanxin import * from simple_save_manager import simple_save_manager from config import UI_WIDTH import time +import random class Maze: def __init__(self, wall_size, maze_size, file_name): @@ -72,6 +73,17 @@ class Maze: self.source_collector.run() self.full_path = self.source_collector.get_path() + # 在路径生成完成后,在路径上放置Boss + if self.full_path and len(self.full_path) > 2: + placed_bosses = self.generater.place_boss_on_path(self.full_path, boss_count=1) + if placed_bosses: + print(f"已在路径上放置 {len(placed_bosses)} 个Boss") + # 设置Boss数据和玩家技能 + self.boss_data = [random.randint(50, 200) for boss in placed_bosses] # 随机生成Boss血量 + self.player_skills = [(random.randint(8, 20), random.randint(2, 5)) for _ in range(random.randint(3, 6))] # 随机生成技能:(伤害, 冷却时间) + print(f"Boss数据: {self.boss_data}") + print(f"玩家技能: {self.player_skills}") + # 设置显示状态 self.grid = self.generater.maze # 使用原始迷宫数据 print(self.grid) diff --git a/maze_generator.py b/maze_generator.py index 3d00d59..963c0ce 100644 --- a/maze_generator.py +++ b/maze_generator.py @@ -127,14 +127,14 @@ class MazeGenerator: cells.append((i, j)) return cells - def place_special_elements(self, boss_count=1, traps_range=(3, 8), + def place_special_elements(self, boss_count=0, traps_range=(3, 8), mechanisms_range=(2, 6), skill_traps=5,gold_range=(3,8)): - """放置特殊元素(支持技能触发陷阱)""" + """放置特殊元素(支持技能触发陷阱)- 不包括Boss,Boss将在路径生成后放置""" available = self.get_available_cells() random.shuffle(available) - # 计算所需单元格数量 - required = 2 + boss_count + random.randint(*traps_range) + random.randint(*mechanisms_range) + skill_traps + random.randint(*gold_range) + # 计算所需单元格数量(不包括Boss) + required = 2 + random.randint(*traps_range) + random.randint(*mechanisms_range) + skill_traps + random.randint(*gold_range) if len(available) < required: raise ValueError(f"空间不足,需要{required}个单元格,实际可用{len(available)}") @@ -145,12 +145,13 @@ class MazeGenerator: self.maze[start[0]][start[1]] = self.START self.maze[end[0]][end[1]] = self.END - # 放置BOSS - for _ in range(boss_count): - pos = available.pop() - val = random.randint(50, 100) - self.special_elements.append((pos[0], pos[1], f"{self.BOSS}{val}")) - self.maze[pos[0]][pos[1]] = f"{self.BOSS}{val}" + # 注释掉Boss放置,Boss将在路径生成后放置 + # # 放置BOSS + # for _ in range(boss_count): + # pos = available.pop() + # val = random.randint(50, 100) + # self.special_elements.append((pos[0], pos[1], f"{self.BOSS}{val}")) + # self.maze[pos[0]][pos[1]] = f"{self.BOSS}{val}" # 放置普通陷阱 traps = random.randint(*traps_range) @@ -175,9 +176,9 @@ class MazeGenerator: self.special_elements.append((pos[0], pos[1], f"{self.GOLD}{val}")) self.maze[pos[0]][pos[1]] = f"{self.GOLD}{val}" - def generate(self, seed=None, boss_count=1, traps_range=(3, 8), + def generate(self, seed=None, traps_range=(3, 8), mechanisms_range=(2, 6), skill_traps=5): - """生成迷宫主方法""" + """生成迷宫主方法 - Boss将在路径生成后单独放置""" # 清除之前的历史记录 self.history_mazes = [] self.special_elements = [] @@ -186,8 +187,8 @@ class MazeGenerator: self.initialize_maze() self.create_maze(1, 1, self.size - 2, self.size - 2) self.patch_maze_edges() # 自动修正边界 - self.place_special_elements(boss_count, traps_range, mechanisms_range, skill_traps) - print(f"成功生成迷宫: {self.name}") + self.place_special_elements(0, traps_range, mechanisms_range, skill_traps) # boss_count=0 + print(f"成功生成迷宫: {self.name} (Boss将在路径生成后放置)") def export_to_csv(self, filename): """导出迷宫到CSV文件""" @@ -359,6 +360,45 @@ class MazeGenerator: json_maze.append(json_row) return json_maze + def place_boss_on_path(self, path, boss_count=1): + """ + 在给定路径上随机放置Boss + + Args: + path: 路径列表,格式为[(y1, x1), (y2, x2), ...] + boss_count: 要放置的Boss数量,默认为1 + + Returns: + list: 放置Boss的位置列表 + """ + if not path or len(path) < 3: + print("路径太短,无法放置Boss") + return [] + + # 排除起点和终点,只在中间路径上放置Boss + middle_path = path[1:-1] # 去掉第一个(起点)和最后一个(终点) + + if len(middle_path) < boss_count: + print(f"路径中间位置不足,只能放置{len(middle_path)}个Boss") + boss_count = len(middle_path) + + # 随机选择Boss放置位置 + boss_positions = random.sample(middle_path, boss_count) + placed_bosses = [] + + for pos in boss_positions: + y, x = pos + # 只在通路位置放置Boss,避免覆盖特殊元素 + if self.maze[y][x] == self.ROUTE or str(self.maze[y][x]).startswith('p'): + val = random.randint(50, 100) + boss_element = f"{self.BOSS}{val}" + self.maze[y][x] = boss_element + self.special_elements.append((y, x, boss_element)) + placed_bosses.append((y, x, val)) + print(f"在路径位置 ({y}, {x}) 放置Boss,血量: {val}") + + return placed_bosses + def main(): # 示例1: 生成带技能陷阱的迷宫 generator = MazeGenerator( @@ -384,4 +424,3 @@ def main(): if __name__ == "__main__": main() - \ No newline at end of file