优化回合数
This commit is contained in:
parent
b16b429bc8
commit
383ab5f7ad
212
boss_fight.py
212
boss_fight.py
@ -1,10 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import math
|
|
||||||
import heapq
|
import heapq
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
def boss_fight(B, PlayerSkills):
|
def boss_fight(B, PlayerSkills):
|
||||||
"""
|
"""
|
||||||
使用分支限界法求解Boss战斗的最优策略
|
使用改进的A*算法求解Boss战斗的最优策略
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
B: Boss血量序列 [boss1_hp, boss2_hp, ...]
|
B: Boss血量序列 [boss1_hp, boss2_hp, ...]
|
||||||
@ -20,121 +20,116 @@ def boss_fight(B, PlayerSkills):
|
|||||||
if n == 0:
|
if n == 0:
|
||||||
return []
|
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
|
# 如果所有技能伤害为0,则无法击败Boss
|
||||||
if max_avg == 0:
|
if all(skill[0] == 0 for skill in PlayerSkills):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 初始状态:Boss索引0,血量B[0],所有技能冷却为0,回合数0
|
def more_accurate_heuristic(boss_idx, boss_remaining_hp):
|
||||||
start_cooldown = tuple([0] * m)
|
"""更精确的启发式函数"""
|
||||||
total_remaining = sum(B)
|
if boss_idx >= n:
|
||||||
|
return 0
|
||||||
# 启发式函数:当前回合数 + 估计剩余回合数
|
|
||||||
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)
|
|
||||||
|
|
||||||
state_key = (boss_idx, boss_rem, cooldown)
|
# 计算剩余总血量
|
||||||
|
remaining_hp = boss_remaining_hp + sum(B[boss_idx + 1:])
|
||||||
# 状态剪枝:如果已有更好的状态到达当前状态,跳过
|
|
||||||
if visited.get(state_key, float('inf')) < round_count:
|
# 考虑技能的平均有效伤害(考虑冷却时间)
|
||||||
|
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
|
continue
|
||||||
|
|
||||||
# 目标状态:所有Boss都被击败
|
# 生成所有可能的后继状态
|
||||||
if boss_idx >= n:
|
neighbors = []
|
||||||
return boss_paths
|
|
||||||
|
|
||||||
# 动作1:等待(所有技能冷却时间-1,回合数+1)
|
# 选项1: 等待
|
||||||
new_cooldown_list = [max(0, c - 1) for c in cooldown]
|
new_cooldown = tuple(max(0, cd - 1) for cd in cooldown)
|
||||||
new_cooldown = tuple(new_cooldown_list)
|
neighbors.append((-1, boss_idx, boss_hp, new_cooldown))
|
||||||
new_round = round_count + 1
|
|
||||||
new_state_key = (boss_idx, boss_rem, new_cooldown)
|
|
||||||
|
|
||||||
# 如果这个等待状态更优,则加入队列
|
# 选项2: 使用每个可用技能
|
||||||
if new_state_key not in visited or visited[new_state_key] > new_round:
|
for skill_idx in range(m):
|
||||||
visited[new_state_key] = new_round
|
if cooldown[skill_idx] == 0: # 技能可用
|
||||||
|
damage, skill_cooldown = PlayerSkills[skill_idx]
|
||||||
# 重新计算启发式值
|
new_hp = boss_hp - damage
|
||||||
total_remaining = boss_rem + sum(B[boss_idx+1:])
|
new_boss_idx = boss_idx
|
||||||
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
|
|
||||||
|
|
||||||
# 重新计算启发式值
|
# 更新冷却时间
|
||||||
total_remaining = new_boss_rem + sum(B[new_boss_idx+1:])
|
new_cooldown = list(max(0, cd - 1) for cd in cooldown)
|
||||||
estimate_rounds = math.ceil(total_remaining / max_avg) if max_avg > 0 else 0
|
new_cooldown[skill_idx] = skill_cooldown
|
||||||
new_f = new_round + estimate_rounds
|
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 # 无解
|
return None # 无解
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# 测试用例
|
# 测试用例
|
||||||
B = [10, 20, 30, 40, 50]
|
B = [19, 17, 14, 19]
|
||||||
PlayerSkills = [(5, 2), (8, 3), (3, 1)]
|
PlayerSkills = [(6, 2), (9, 5), (5, 3), (4, 3), (2, 0)]
|
||||||
|
|
||||||
print("Boss血量序列:", B)
|
print("Boss血量序列:", B)
|
||||||
print("玩家技能 (伤害, 冷却):", PlayerSkills)
|
print("玩家技能 (伤害, 冷却):", PlayerSkills)
|
||||||
@ -142,8 +137,19 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
result = boss_fight(B, PlayerSkills)
|
result = boss_fight(B, PlayerSkills)
|
||||||
|
|
||||||
for i in result:
|
if result:
|
||||||
print(i)
|
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:
|
# if result:
|
||||||
# print("每个Boss的最优攻击序列:")
|
# print("每个Boss的最优攻击序列:")
|
||||||
|
12
maze.py
12
maze.py
@ -5,6 +5,7 @@ from tanxin import *
|
|||||||
from simple_save_manager import simple_save_manager
|
from simple_save_manager import simple_save_manager
|
||||||
from config import UI_WIDTH
|
from config import UI_WIDTH
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
class Maze:
|
class Maze:
|
||||||
def __init__(self, wall_size, maze_size, file_name):
|
def __init__(self, wall_size, maze_size, file_name):
|
||||||
@ -72,6 +73,17 @@ class Maze:
|
|||||||
self.source_collector.run()
|
self.source_collector.run()
|
||||||
self.full_path = self.source_collector.get_path()
|
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 # 使用原始迷宫数据
|
self.grid = self.generater.maze # 使用原始迷宫数据
|
||||||
print(self.grid)
|
print(self.grid)
|
||||||
|
@ -127,14 +127,14 @@ class MazeGenerator:
|
|||||||
cells.append((i, j))
|
cells.append((i, j))
|
||||||
return cells
|
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)):
|
mechanisms_range=(2, 6), skill_traps=5,gold_range=(3,8)):
|
||||||
"""放置特殊元素(支持技能触发陷阱)"""
|
"""放置特殊元素(支持技能触发陷阱)- 不包括Boss,Boss将在路径生成后放置"""
|
||||||
available = self.get_available_cells()
|
available = self.get_available_cells()
|
||||||
random.shuffle(available)
|
random.shuffle(available)
|
||||||
|
|
||||||
# 计算所需单元格数量
|
# 计算所需单元格数量(不包括Boss)
|
||||||
required = 2 + boss_count + random.randint(*traps_range) + random.randint(*mechanisms_range) + skill_traps + random.randint(*gold_range)
|
required = 2 + random.randint(*traps_range) + random.randint(*mechanisms_range) + skill_traps + random.randint(*gold_range)
|
||||||
if len(available) < required:
|
if len(available) < required:
|
||||||
raise ValueError(f"空间不足,需要{required}个单元格,实际可用{len(available)}")
|
raise ValueError(f"空间不足,需要{required}个单元格,实际可用{len(available)}")
|
||||||
|
|
||||||
@ -145,12 +145,13 @@ class MazeGenerator:
|
|||||||
self.maze[start[0]][start[1]] = self.START
|
self.maze[start[0]][start[1]] = self.START
|
||||||
self.maze[end[0]][end[1]] = self.END
|
self.maze[end[0]][end[1]] = self.END
|
||||||
|
|
||||||
# 放置BOSS
|
# 注释掉Boss放置,Boss将在路径生成后放置
|
||||||
for _ in range(boss_count):
|
# # 放置BOSS
|
||||||
pos = available.pop()
|
# for _ in range(boss_count):
|
||||||
val = random.randint(50, 100)
|
# pos = available.pop()
|
||||||
self.special_elements.append((pos[0], pos[1], f"{self.BOSS}{val}"))
|
# val = random.randint(50, 100)
|
||||||
self.maze[pos[0]][pos[1]] = f"{self.BOSS}{val}"
|
# 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)
|
traps = random.randint(*traps_range)
|
||||||
@ -175,9 +176,9 @@ class MazeGenerator:
|
|||||||
self.special_elements.append((pos[0], pos[1], f"{self.GOLD}{val}"))
|
self.special_elements.append((pos[0], pos[1], f"{self.GOLD}{val}"))
|
||||||
self.maze[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):
|
mechanisms_range=(2, 6), skill_traps=5):
|
||||||
"""生成迷宫主方法"""
|
"""生成迷宫主方法 - Boss将在路径生成后单独放置"""
|
||||||
# 清除之前的历史记录
|
# 清除之前的历史记录
|
||||||
self.history_mazes = []
|
self.history_mazes = []
|
||||||
self.special_elements = []
|
self.special_elements = []
|
||||||
@ -186,8 +187,8 @@ class MazeGenerator:
|
|||||||
self.initialize_maze()
|
self.initialize_maze()
|
||||||
self.create_maze(1, 1, self.size - 2, self.size - 2)
|
self.create_maze(1, 1, self.size - 2, self.size - 2)
|
||||||
self.patch_maze_edges() # 自动修正边界
|
self.patch_maze_edges() # 自动修正边界
|
||||||
self.place_special_elements(boss_count, traps_range, mechanisms_range, skill_traps)
|
self.place_special_elements(0, traps_range, mechanisms_range, skill_traps) # boss_count=0
|
||||||
print(f"成功生成迷宫: {self.name}")
|
print(f"成功生成迷宫: {self.name} (Boss将在路径生成后放置)")
|
||||||
|
|
||||||
def export_to_csv(self, filename):
|
def export_to_csv(self, filename):
|
||||||
"""导出迷宫到CSV文件"""
|
"""导出迷宫到CSV文件"""
|
||||||
@ -359,6 +360,45 @@ class MazeGenerator:
|
|||||||
json_maze.append(json_row)
|
json_maze.append(json_row)
|
||||||
return json_maze
|
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():
|
def main():
|
||||||
# 示例1: 生成带技能陷阱的迷宫
|
# 示例1: 生成带技能陷阱的迷宫
|
||||||
generator = MazeGenerator(
|
generator = MazeGenerator(
|
||||||
@ -384,4 +424,3 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user