544 lines
23 KiB
Python
544 lines
23 KiB
Python
import pygame
|
||
from maze_generator import MazeGenerator
|
||
from SourceCollector import SourceCollector
|
||
from tanxin import *
|
||
from simple_save_manager import simple_save_manager
|
||
from greedy_3x3_algorithm import Greedy3x3Algorithm
|
||
from config import UI_WIDTH
|
||
import time
|
||
import random
|
||
import json
|
||
INPUT_DATA = "/Users/gary/dev/maze_python/saves/input_case_1.json"
|
||
class Maze:
|
||
def __init__(self, wall_size, maze_size, file_name):
|
||
self.wall_size = wall_size
|
||
self.maze_size = maze_size
|
||
self.size = int(maze_size / wall_size)
|
||
self.file_name = file_name
|
||
|
||
# 动态显示尺寸(会根据实际地图大小调整)
|
||
self.actual_display_size = maze_size
|
||
self.actual_wall_size = wall_size
|
||
|
||
self.grid = []
|
||
self.generater = MazeGenerator(self.size, self.file_name)
|
||
self.source_collector = None
|
||
self.path_step = 0 # 当前显示到路径的第几步
|
||
self.full_path = [] # 完整路径
|
||
self.is_path_complete = False # 路径是否完全显示
|
||
|
||
# 历史迭代展示相关
|
||
self.history_mazes = [] # 迷宫生成历史
|
||
self.history_step = 0 # 当前历史步骤
|
||
self.show_history = False # 是否正在展示历史
|
||
self.history_auto_play = False # 历史自动播放
|
||
self.history_timer = 0 # 历史播放计时器
|
||
|
||
# Boss战斗相关
|
||
self.boss_data = [] # Boss血量序列
|
||
self.player_skills = [] # 玩家技能序列
|
||
|
||
# 已遇到的Boss和机关位置记录(避免重复触发)
|
||
self.encountered_bosses = set() # 已遇到的Boss位置 (y, x)
|
||
self.encountered_mechanisms = set() # 已遇到的机关位置 (y, x)
|
||
|
||
def update_display_size(self):
|
||
"""根据当前迷宫大小更新显示尺寸"""
|
||
if len(self.grid) > 0:
|
||
self.size = len(self.grid)
|
||
# 计算合适的墙壁大小,确保迷宫不会太大或太小
|
||
max_display_size = min(800, UI_WIDTH - 400) # 留出400像素给控制面板
|
||
min_wall_size = 20
|
||
max_wall_size = 60
|
||
|
||
# 根据迷宫大小计算墙壁尺寸
|
||
ideal_wall_size = max_display_size // self.size
|
||
self.actual_wall_size = max(min_wall_size, min(max_wall_size, ideal_wall_size))
|
||
self.actual_display_size = self.size * self.actual_wall_size
|
||
|
||
print(f"迷宫大小: {self.size}x{self.size}, 墙壁尺寸: {self.actual_wall_size}, 显示尺寸: {self.actual_display_size}")
|
||
|
||
def get_actual_display_size(self):
|
||
"""获取当前实际显示尺寸"""
|
||
return self.actual_display_size
|
||
|
||
|
||
def generate(self):
|
||
# 重置所有状态
|
||
self.reset_all_states()
|
||
|
||
# 生成新迷宫
|
||
seed = int(time.time() * 1000) % (2**32)
|
||
self.generater.generate(seed=seed)
|
||
|
||
# 获取生成历史
|
||
self.history_mazes = self.generater.get_history_mazes()
|
||
|
||
# 生成路径
|
||
self.source_collector = SourceCollector(maze=self.generater.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")
|
||
with open(INPUT_DATA, 'r', encoding='utf-8') as f:
|
||
file = json.load(f)
|
||
|
||
# 设置Boss数据和玩家技能
|
||
self.boss_data = list(file['B'])
|
||
self.player_skills = [list(skill) for skill in file['PlayerSkills']]
|
||
print(f"Boss数据: {self.boss_data}")
|
||
print(f"玩家技能: {self.player_skills}")
|
||
|
||
# 在路径上放置机关(只放置1个)
|
||
placed_mechanisms = self.generater.place_mechanisms_on_path(self.full_path, mechanisms_count=1)
|
||
if placed_mechanisms:
|
||
print(f"已在路径上放置 {len(placed_mechanisms)} 个机关")
|
||
|
||
# 设置显示状态
|
||
self.grid = self.generater.maze # 使用原始迷宫数据
|
||
print(self.grid)
|
||
self.update_display_size() # 更新显示尺寸
|
||
|
||
print(f"路径长度: {len(self.full_path)}")
|
||
print(f"生成历史步数: {len(self.history_mazes)}")
|
||
print("新迷宫生成完成")
|
||
|
||
def next_path_step(self):
|
||
"""显示路径的下一步"""
|
||
if self.path_step < len(self.full_path):
|
||
# 获取当前要显示的位置
|
||
current_y, current_x = self.full_path[self.path_step]
|
||
current_position = (current_y, current_x)
|
||
|
||
# 检查当前位置是否有boss或机关
|
||
boss_encountered = False
|
||
mechanism_encountered = False
|
||
boss_info = None
|
||
mechanism_info = None
|
||
|
||
if self.generater.maze:
|
||
current_cell = str(self.generater.maze[current_y][current_x])
|
||
|
||
# 检查Boss(只有在之前没有遇到过这个位置的Boss时才触发)
|
||
if current_cell.lower().startswith('b') and current_position not in self.encountered_bosses:
|
||
boss_encountered = True
|
||
self.encountered_bosses.add(current_position) # 记录已遇到的Boss位置
|
||
# 如果有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
|
||
|
||
# 检查机关(只有在之前没有遇到过这个位置的机关时才触发)
|
||
elif current_cell.lower().startswith('l') and current_position not in self.encountered_mechanisms:
|
||
mechanism_encountered = True
|
||
self.encountered_mechanisms.add(current_position) # 记录已遇到的机关位置
|
||
mechanism_info = {
|
||
'position': (current_y, current_x),
|
||
'cell_value': current_cell
|
||
}
|
||
print(f"遇到机关!位置: ({current_y}, {current_x}), 机关值: {current_cell}")
|
||
|
||
self.path_step += 1
|
||
self.update_grid_with_path()
|
||
|
||
# 返回是否有下一步、是否遇到boss、boss信息、是否遇到机关、机关信息
|
||
return True, boss_encountered, boss_info, mechanism_encountered, mechanism_info
|
||
return False, False, None, False, None
|
||
|
||
def reset_path(self):
|
||
"""重置路径显示"""
|
||
self.path_step = 0
|
||
self.is_path_complete = False
|
||
self.grid = self.generater.maze if self.generater.maze else []
|
||
|
||
# 重置已遇到的Boss和机关记录,允许重新体验
|
||
self.encountered_bosses = set()
|
||
self.encountered_mechanisms = set()
|
||
print("路径重置,Boss和机关遭遇记录已清除")
|
||
|
||
def auto_advance_path(self):
|
||
"""自动推进路径显示"""
|
||
if not self.is_path_complete:
|
||
has_next, boss_encountered, boss_info = self.next_path_step()
|
||
if not has_next:
|
||
self.is_path_complete = True
|
||
# 注意:这里不处理boss遭遇,因为这个方法可能不在主循环中调用
|
||
return boss_encountered, boss_info
|
||
return False, None
|
||
|
||
def update_grid_with_path(self):
|
||
"""根据当前步数更新网格显示"""
|
||
if not self.full_path or not self.generater.maze:
|
||
return
|
||
|
||
# 从原始迷宫开始
|
||
self.grid = [row[:] for row in self.generater.maze] # 深拷贝
|
||
|
||
# 只显示到当前步数的路径
|
||
for idx in range(min(self.path_step, len(self.full_path))):
|
||
y, x = self.full_path[idx]
|
||
if self.grid[y][x].startswith('s') or self.grid[y][x].startswith('e'):
|
||
continue
|
||
if self.grid[y][x].startswith('g') or self.grid[y][x].startswith('t'):
|
||
self.grid[y][x] = f"{self.grid[y][x]}p{idx}"
|
||
continue
|
||
self.grid[y][x] = f"p{idx}"
|
||
|
||
def export_to_csv(self, filename):
|
||
"""导出迷宫到CSV文件(兼容旧版本)"""
|
||
self.generater.export_to_csv(filename=filename)
|
||
|
||
def save_game(self, save_name=None, format_type="json"):
|
||
"""保存包含路径信息的迷宫到文件"""
|
||
if len(self.grid) == 0:
|
||
print("没有生成迷宫,无法保存")
|
||
return None
|
||
|
||
return simple_save_manager.save_maze_with_path(self, save_name, format_type)
|
||
|
||
def load_game(self, save_file):
|
||
"""从存档文件加载游戏状态(支持JSON和CSV格式)"""
|
||
load_data = simple_save_manager.load_maze_from_file(save_file)
|
||
if load_data is None:
|
||
return False
|
||
|
||
try:
|
||
# 恢复迷宫数据
|
||
self.generater.maze = load_data['original_grid']
|
||
self.size = len(load_data['original_grid'])
|
||
|
||
# 恢复路径数据
|
||
self.full_path = load_data['path_sequence']
|
||
self.path_step = len(self.full_path) # 显示完整路径
|
||
self.is_path_complete = True
|
||
|
||
# 重新创建SourceCollector以便后续操作
|
||
self.source_collector = SourceCollector(maze=self.generater.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
|
||
self.show_history = False
|
||
|
||
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
|
||
|
||
except Exception as e:
|
||
print(f"加载游戏状态失败: {str(e)}")
|
||
return False
|
||
|
||
def get_save_list(self):
|
||
"""获取所有可用的存档列表"""
|
||
return simple_save_manager.get_save_list()
|
||
|
||
|
||
|
||
def draw(self, screen, wall_texture, coin_texture, trap_texture, boss_texture, lock_texture):
|
||
if len(self.grid) == 0:
|
||
return
|
||
|
||
# 使用动态计算的墙壁尺寸
|
||
tile_size = self.actual_wall_size
|
||
|
||
# 根据需要缩放贴图
|
||
if wall_texture.get_width() != tile_size:
|
||
wall_texture = pygame.transform.scale(wall_texture, (tile_size, tile_size))
|
||
coin_texture = pygame.transform.scale(coin_texture, (tile_size, tile_size))
|
||
trap_texture = pygame.transform.scale(trap_texture, (tile_size, tile_size))
|
||
boss_texture = pygame.transform.scale(boss_texture, (tile_size, tile_size))
|
||
lock_texture = pygame.transform.scale(lock_texture, (tile_size, tile_size))
|
||
|
||
for y in range(self.size):
|
||
for x in range(self.size):
|
||
if self.grid[y][x] == '1':
|
||
screen.blit(wall_texture, (x * tile_size, y * tile_size))
|
||
continue
|
||
if self.grid[y][x].startswith('b'):
|
||
screen.blit(boss_texture, (x * tile_size, y * tile_size))
|
||
continue
|
||
if self.grid[y][x].startswith('l'):
|
||
screen.blit(lock_texture, (x * tile_size, y * tile_size))
|
||
continue
|
||
if self.grid[y][x].startswith('g'):
|
||
screen.blit(coin_texture, (x * tile_size, y * tile_size))
|
||
|
||
font = pygame.font.SysFont(None, tile_size // 2)
|
||
path = self.grid[y][x].rfind('p')
|
||
if path == -1:
|
||
continue
|
||
path = self.grid[y][x][path+1:]
|
||
|
||
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||
radius = tile_size // 3
|
||
|
||
text = font.render(path, True, (255, 255, 255))
|
||
text_rect = text.get_rect(center=center)
|
||
screen.blit(text, text_rect)
|
||
|
||
if self.grid[y][x].startswith('t'):
|
||
screen.blit(trap_texture, (x * tile_size, y * tile_size))
|
||
|
||
font = pygame.font.SysFont(None, tile_size // 2)
|
||
path = self.grid[y][x].rfind('p')
|
||
if path == -1:
|
||
continue
|
||
path = self.grid[y][x][path+1:]
|
||
|
||
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||
radius = tile_size // 3
|
||
|
||
text = font.render(path, True, (255, 255, 255))
|
||
text_rect = text.get_rect(center=center)
|
||
screen.blit(text, text_rect)
|
||
|
||
if self.grid[y][x].startswith('|') or self.grid[y][x].startswith('-'):
|
||
font = pygame.font.SysFont(None, tile_size // 2)
|
||
num = int(self.grid[y][x][1:])
|
||
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||
radius = tile_size // 3
|
||
pygame.draw.circle(screen, (255, 215, 0), center, radius)
|
||
if num:
|
||
text = font.render(str(num), True, (0, 0, 0))
|
||
text_rect = text.get_rect(center=center)
|
||
screen.blit(text, text_rect)
|
||
continue
|
||
if self.grid[y][x].startswith('s'):
|
||
font = pygame.font.SysFont(None, tile_size // 2)
|
||
text = "s"
|
||
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||
radius = tile_size // 3
|
||
pygame.draw.circle(screen, (255, 215, 0), center, radius)
|
||
if text:
|
||
text = font.render(text, True, (0, 0, 0))
|
||
text_rect = text.get_rect(center=center)
|
||
screen.blit(text, text_rect)
|
||
continue
|
||
if self.grid[y][x].startswith('e'):
|
||
font = pygame.font.SysFont(None, tile_size // 2)
|
||
text = "e"
|
||
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||
radius = tile_size // 3
|
||
pygame.draw.circle(screen, (255, 215, 0), center, radius)
|
||
if text:
|
||
text = font.render(text, True, (0, 0, 0))
|
||
text_rect = text.get_rect(center=center)
|
||
screen.blit(text, text_rect)
|
||
if self.grid[y][x].startswith('p'):
|
||
font = pygame.font.SysFont(None, tile_size // 2)
|
||
text = self.grid[y][x][1:]
|
||
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||
radius = tile_size // 3
|
||
pygame.draw.circle(screen, (255, 215, 0), center, radius)
|
||
if text:
|
||
text = font.render(text, True, (0, 0, 0))
|
||
text_rect = text.get_rect(center=center)
|
||
screen.blit(text, text_rect)
|
||
|
||
# 绘制迷宫边界线(动态位置)
|
||
pygame.draw.line(screen, (0, 0, 0), (self.actual_display_size, 0), (self.actual_display_size, self.actual_display_size), 3)
|
||
|
||
# 绘制贪心路径
|
||
if hasattr(self, 'greedy_path') and self.greedy_path and hasattr(self, 'greedy_step'):
|
||
self._draw_greedy_path(screen, tile_size)
|
||
|
||
def toggle_history_mode(self):
|
||
"""切换历史展示模式"""
|
||
if len(self.history_mazes) > 0:
|
||
self.show_history = not self.show_history
|
||
if self.show_history:
|
||
# 切换到历史模式,显示当前历史步骤
|
||
self.update_grid_with_history()
|
||
else:
|
||
# 切换到路径模式,显示当前路径步骤
|
||
self.update_grid_with_path()
|
||
print(f"切换到{'历史' if self.show_history else '路径'}模式")
|
||
|
||
def next_history_step(self):
|
||
"""显示历史的下一步"""
|
||
if self.history_step < len(self.history_mazes) - 1:
|
||
self.history_step += 1
|
||
self.update_grid_with_history()
|
||
return True
|
||
return False
|
||
|
||
def prev_history_step(self):
|
||
"""显示历史的上一步"""
|
||
if self.history_step > 0:
|
||
self.history_step -= 1
|
||
self.update_grid_with_history()
|
||
return True
|
||
return False
|
||
|
||
def update_grid_with_history(self):
|
||
"""根据当前历史步数更新网格显示"""
|
||
if not self.history_mazes or self.history_step >= len(self.history_mazes):
|
||
return
|
||
|
||
# 显示指定历史步骤的迷宫状态
|
||
self.grid = [row[:] for row in self.history_mazes[self.history_step]] # 深拷贝
|
||
|
||
def reset_all_states(self):
|
||
"""重置所有状态(用于生成新迷宫时)"""
|
||
# 历史相关状态
|
||
self.history_mazes = []
|
||
self.history_step = 0
|
||
self.show_history = False
|
||
self.history_auto_play = False
|
||
self.history_timer = 0
|
||
|
||
# 路径相关状态
|
||
self.path_step = 0
|
||
self.is_path_complete = False
|
||
self.full_path = []
|
||
|
||
# 重置已遇到的Boss和机关记录
|
||
self.encountered_bosses = set()
|
||
self.encountered_mechanisms = set()
|
||
|
||
# 重置贪心算法相关状态
|
||
if hasattr(self, 'greedy_path'):
|
||
delattr(self, 'greedy_path')
|
||
if hasattr(self, 'greedy_result'):
|
||
delattr(self, 'greedy_result')
|
||
if hasattr(self, 'greedy_step'):
|
||
delattr(self, 'greedy_step')
|
||
if hasattr(self, 'is_greedy_path_complete'):
|
||
delattr(self, 'is_greedy_path_complete')
|
||
|
||
# 显示相关状态
|
||
self.grid = []
|
||
|
||
print("所有状态已重置")
|
||
|
||
def run_greedy_search(self):
|
||
"""运行3x3视野贪心搜索算法"""
|
||
if not self.grid:
|
||
print("没有迷宫数据,无法运行贪心搜索")
|
||
return False
|
||
|
||
try:
|
||
# 创建贪心算法实例
|
||
algorithm = Greedy3x3Algorithm(self.grid, debug=True)
|
||
|
||
# 运行算法
|
||
result = algorithm.run()
|
||
|
||
# 将结果转换为路径格式 (y, x)
|
||
self.greedy_path = result['path_yx_format']
|
||
self.greedy_result = result
|
||
self.greedy_step = 0
|
||
self.is_greedy_path_complete = False
|
||
|
||
print(f"贪心搜索完成!")
|
||
print(f"收集资源数量: {result['resources_count']}")
|
||
print(f"资源总价值: {result['total_value']}")
|
||
print(f"移动步数: {result['total_moves']}")
|
||
print(f"路径长度: {len(self.greedy_path)}")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"贪心搜索失败: {e}")
|
||
return False
|
||
|
||
def next_greedy_step(self):
|
||
"""显示贪心路径的下一步"""
|
||
if not hasattr(self, 'greedy_path') or not self.greedy_path:
|
||
return False
|
||
|
||
if self.greedy_step < len(self.greedy_path):
|
||
self.greedy_step += 1
|
||
if self.greedy_step >= len(self.greedy_path):
|
||
self.is_greedy_path_complete = True
|
||
return True
|
||
return False
|
||
|
||
def reset_greedy_path(self):
|
||
"""重置贪心路径显示"""
|
||
self.greedy_step = 0
|
||
self.is_greedy_path_complete = False
|
||
|
||
def get_greedy_progress(self):
|
||
"""获取贪心路径进度信息"""
|
||
if not hasattr(self, 'greedy_path') or not self.greedy_path:
|
||
return "贪心路径: 未生成"
|
||
|
||
if hasattr(self, 'greedy_result'):
|
||
result = self.greedy_result
|
||
return (f"贪心路径: {self.greedy_step}/{len(self.greedy_path)} | "
|
||
f"资源: {result['resources_count']} | "
|
||
f"价值: {result['total_value']}")
|
||
else:
|
||
return f"贪心路径: {self.greedy_step}/{len(self.greedy_path)}"
|
||
|
||
def _draw_greedy_path(self, screen, tile_size):
|
||
"""绘制贪心算法路径"""
|
||
if not self.greedy_path:
|
||
return
|
||
|
||
# 绘制已走过的路径
|
||
for i in range(min(self.greedy_step, len(self.greedy_path))):
|
||
y, x = self.greedy_path[i]
|
||
|
||
# 绘制路径点
|
||
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||
|
||
# 起点用绿色圆圈
|
||
if i == 0:
|
||
pygame.draw.circle(screen, (0, 255, 0), center, tile_size // 4, 3)
|
||
# 当前位置用红色圆圈
|
||
elif i == self.greedy_step - 1:
|
||
pygame.draw.circle(screen, (255, 0, 0), center, tile_size // 4, 3)
|
||
# 其他位置用蓝色小圆点
|
||
else:
|
||
pygame.draw.circle(screen, (0, 0, 255), center, tile_size // 6)
|
||
|
||
# 绘制路径连线
|
||
if i > 0:
|
||
prev_y, prev_x = self.greedy_path[i - 1]
|
||
prev_center = (prev_x * tile_size + tile_size // 2, prev_y * tile_size + tile_size // 2)
|
||
pygame.draw.line(screen, (100, 100, 255), prev_center, center, 2)
|
||
|
||
# 如果有贪心算法结果,绘制收集的资源位置
|
||
if hasattr(self, 'greedy_result') and self.greedy_result:
|
||
for resource in self.greedy_result['collected_resources']:
|
||
pos_x, pos_y = resource['position'] # 注意:贪心算法返回的是(x, y)格式
|
||
center = (pos_x * tile_size + tile_size // 2, pos_y * tile_size + tile_size // 2)
|
||
|
||
# 用金色圆圈标记已收集的资源
|
||
if resource['value'] > 0:
|
||
pygame.draw.circle(screen, (255, 215, 0), center, tile_size // 3, 2)
|
||
else:
|
||
pygame.draw.circle(screen, (255, 100, 100), center, tile_size // 3, 2)
|
||
|
||
# 显示资源价值
|
||
font = pygame.font.SysFont(None, max(12, tile_size // 4))
|
||
value_text = font.render(str(resource['value']), True, (255, 255, 255))
|
||
text_rect = value_text.get_rect(center=center)
|
||
screen.blit(value_text, text_rect)
|
||
|