实现Boss战简单UI
This commit is contained in:
parent
120182ea68
commit
b16b429bc8
150
boss_fight.py
150
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
|
||||
|
||||
# 初始状态:boss索引0,血量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)
|
||||
|
||||
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")
|
270
boss_fight_ui.py
Normal file
270
boss_fight_ui.py
Normal file
@ -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))
|
57
main.py
57
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()
|
||||
|
||||
|
35
maze.py
35
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
|
||||
|
||||
|
123
md/BOSS_ENCOUNTER_GUIDE.md
Normal file
123
md/BOSS_ENCOUNTER_GUIDE.md
Normal file
@ -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. 在战斗界面中尝试各种控制操作
|
||||
|
||||
现在系统应该完全正常工作了!🎉
|
118
md/BOSS_FIGHT_README.md
Normal file
118
md/BOSS_FIGHT_README.md
Normal file
@ -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. **用户友好**: 直观的界面和操作提示
|
@ -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', []) # 玩家技能序列
|
||||
}
|
||||
|
||||
# 如果有路径数据,也加载路径信息
|
||||
|
Loading…
Reference in New Issue
Block a user