Compare commits

...

2 Commits

Author SHA1 Message Date
133cfeea63 Merge branch 'main' of git.gangary.cn:gary/maze_python 2025-06-30 19:30:35 +08:00
383ab5f7ad 优化回合数 2025-06-30 19:30:29 +08:00
3 changed files with 175 additions and 118 deletions

View File

@ -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的最优攻击序列:")

12
maze.py
View File

@ -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)

View File

@ -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)):
"""放置特殊元素(支持技能触发陷阱)"""
"""放置特殊元素(支持技能触发陷阱)- 不包括BossBoss将在路径生成后放置"""
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()