实现路径显示

This commit is contained in:
Gary Gan 2025-06-29 18:40:44 +08:00
parent 05b35d4640
commit 460256c1cb
10 changed files with 1155 additions and 53 deletions

72
CONFIG_README.md Normal file
View File

@ -0,0 +1,72 @@
# 配置文件说明
## 概述
`config.py` 包含了整个迷宫游戏的所有配置参数,使得项目更易于维护和自定义。
## 配置参数说明
### UI 界面配置
- `UI_HEIGHT = 1000`: 游戏窗口高度
- `UI_WIDTH = 1500`: 游戏窗口宽度
### 迷宫配置
- `MAZE_SIZE = 800`: 迷宫显示区域大小
- `WALL_SIZE = 50`: 每个墙体/格子的像素大小
### 性能配置
- `FPS = 120`: 游戏帧率
- `AUTO_PLAY_INTERVAL = 30`: 自动播放时每多少帧前进一步
### 按钮尺寸配置
- `BUTTON_START_SIZE = (200, 100)`: 开始按钮尺寸
- `BUTTON_SAVE_SIZE = (80, 80)`: 保存按钮尺寸
- `BUTTON_CONTROL_SIZE = (100, 50)`: 路径控制按钮尺寸
### 字体配置
- `FONT_FILE = "syht.otf"`: 字体文件路径
- `FONT_SIZE = 18`: 字体大小
### 资源路径配置
所有图片资源的路径都定义在这里,便于统一管理:
- `WALL_IMAGE`: 墙体纹理
- `COIN_IMAGE`: 金币纹理
- `TRAP_IMAGE`: 陷阱纹理
- `START_BUTTON_IMAGE`: 开始按钮图片
- `SAVE_BUTTON_IMAGE`: 保存按钮图片
### 颜色配置
预定义了常用的颜色值:
- `COLOR_WHITE`: 白色背景
- `COLOR_BLACK`: 黑色文字
- `COLOR_GRAY`: 灰色按钮
- `COLOR_GREEN`: 绿色高亮
- `COLOR_GOLD`: 金色路径
### 布局配置
`get_button_positions()` 函数返回所有UI元素的位置包括
- 各种按钮的位置
- 文字显示的位置
- 根据窗口大小自动计算相对位置
## 使用方法
### 导入配置
```python
from config import *
```
### 获取按钮位置
```python
button_positions = get_button_positions()
start_x, start_y = button_positions['start_button']
```
### 修改配置
要修改游戏参数,只需编辑 `config.py` 文件即可,无需修改主程序代码。
## 优势
1. **集中管理**: 所有配置参数集中在一个文件中
2. **易于维护**: 修改参数不需要在多个文件中查找
3. **可扩展性**: 新增配置项只需在配置文件中添加
4. **代码整洁**: 主程序代码更加简洁明了
5. **复用性**: 配置可以被多个模块共享使用

138
CSV_SAVE_SYSTEM_README.md Normal file
View File

@ -0,0 +1,138 @@
# CSV存档系统使用说明
## 🎯 新的存档方式
根据您的需求存档系统已经完全重新设计现在只使用CSV格式保存迷宫和路径信息。
### 📁 文件格式
存档文件采用CSV格式路径信息直接嵌入在迷宫数据中
#### 路径标记格式:
- **空地 + 路径**: `0``p10` (表示第10步)
- **金币 + 路径**: `g15``g15p10` (金币15路径第10步)
- **陷阱 + 路径**: `t20``t20p10` (陷阱20路径第10步)
- **机关 + 路径**: `l24``l24p10` (机关24路径第10步)
- **boss + 路径**: `b92``b92p10` (boss92路径第10步)
- **起点 + 路径**: `s``sp1` (起点路径第1步)
- **终点 + 路径**: `e``ep25` (终点路径第25步)
#### 示例CSV内容
```csv
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,g28p11,p10,p9,p14,p16,p17,p18,p19,p20,p21,p22,p23,t14p24,p25,1
1,1,1,g27p8,1,1,1,1,1,1,1,1,1,1,p26,1
1,p5,p6,p7,0,0,0,t14,0,0,1,0,1,p28,p27,1
1,p4,1,1,1,1,1,0,1,t16,1,0,1,ep29,1,1
```
### 🎮 功能特性
#### 1. 保存功能
- **快速保存**: `Ctrl+S` - 自动使用时间戳命名
- **命名保存**: 通过存档界面自定义名称
- **路径完整性**: 保存完整的路径序列到CSV
#### 2. 读档功能
- **CSV解析**: 自动解析CSV中的路径信息
- **路径重建**: 完整恢复路径序列和位置
- **格式兼容**: 支持复杂的路径标记格式
#### 3. 路径处理
- **多路径支持**: 处理一个格子被多次经过的情况(如`p35p37`
- **特殊格子**: 正确处理起点、终点、金币、陷阱等特殊元素
- **顺序保持**: 确保路径顺序完全正确
### 🔧 操作方法
#### 保存游戏:
1. **快速保存**: 按 `Ctrl+S` 自动保存到 `saves/maze_save_时间戳.csv`
2. **命名保存**:
- 点击"读档"按钮
- 按 `N` 键新建存档
- 输入文件名(自动添加.csv后缀
- 按回车确认
#### 加载游戏:
1. 点击"读档"按钮或按 `Ctrl+L`
2. 使用↑↓键选择存档文件
3. 按回车加载选中的存档
4. 游戏会显示完整的路径信息
#### 管理存档:
- **查看列表**: 显示所有CSV存档文件
- **删除存档**: 选中后按 `Delete`
- **按时间排序**: 最新的存档显示在前面
### 📂 存档文件结构
```
saves/
├── maze_save_20250629_174154.csv # 时间戳命名的存档
├── my_best_solution.csv # 自定义命名的存档
└── level_1_complete.csv # 另一个自定义存档
```
### 💡 高级特性
#### 路径重叠处理:
当路径多次经过同一个格子时,系统会正确保存多个路径标记:
- `p35p37` - 表示第35步和第37步都经过这个格子
- `g15p10p20` - 表示金币15第10步和第20步都经过
#### 自动解析:
系统使用正则表达式自动识别所有路径标记:
```python
# 识别 g15p10p20 中的所有路径号10, 20
path_matches = re.findall(r'p(\d+)', cell)
```
### 🎨 界面说明
#### 存档界面:
- **文件格式**: 显示"输入存档名称(.csv"提示
- **路径信息**: 加载时显示"存档加载成功"
- **完整显示**: 加载后立即显示完整路径
#### 快捷键:
- **Ctrl+S**: 保存包含路径的CSV文件
- **Ctrl+L**: 打开读档界面
- **N键**: 新建存档(在存档界面中)
- **Delete**: 删除选中的存档
### 📊 数据完整性
#### 保存时:
- ✅ 完整的迷宫结构
- ✅ 完整的路径序列1到N的所有步骤
- ✅ 特殊元素的属性值
- ✅ 路径与元素的关联关系
#### 加载时:
- ✅ 自动解析所有路径标记
- ✅ 重建完整的路径序列
- ✅ 恢复原始迷宫结构
- ✅ 正确处理路径重叠
### 🔄 兼容性
- **向前兼容**: 支持读取旧版本生成的CSV文件
- **标准格式**: 使用标准CSV格式可用Excel等工具查看
- **跨平台**: 文件在不同操作系统间完全兼容
### 📝 注意事项
1. **文件名**: 会自动添加.csv后缀无需手动输入
2. **路径完整性**: 确保所有路径步骤都被正确保存和加载
3. **特殊字符**: 避免在存档名中使用特殊字符
4. **存储位置**: 所有存档保存在`saves/`目录下
### 🏆 优势
1. **简洁明了**: 路径信息直接可见,易于理解
2. **完全兼容**: 与您现有的CSV工作流程完美集成
3. **数据完整**: 保存所有必要信息,无信息丢失
4. **易于调试**: 可以直接查看和编辑CSV文件
5. **标准格式**: 使用标准CSV格式通用性强
现在您的存档系统完全符合您的需求只保存CSV文件路径信息嵌入其中格式清晰易懂🎉

98
SAVE_SYSTEM_README.md Normal file
View File

@ -0,0 +1,98 @@
# 存档系统使用说明
## 新增功能
### 🎮 完整的存档系统
现在游戏支持完整的存档和读档功能,不仅保存迷宫结构,还会保存路径信息和当前进度。
### 🎯 功能特性
#### 1. 保存功能
- **CSV保存**: 点击 "保存" 按钮保存迷宫结构到CSV文件兼容旧版本
- **完整存档**: 使用 `Ctrl+S` 或点击 "读档" 按钮选择 "新建" 保存完整游戏状态
#### 2. 读档功能
- **存档列表**: 点击 "读档" 按钮查看所有可用存档
- **快速读档**: 使用 `Ctrl+L` 快速打开读档界面
#### 3. 存档管理
- **存档信息**: 显示存档名称、保存时间和迷宫大小
- **删除存档**: 选中存档后按 `Delete` 键删除
- **存档排序**: 按保存时间倒序排列
### 🔧 操作方法
#### 保存游戏
1. **快速保存**: `Ctrl+S` - 使用时间戳自动命名
2. **命名保存**:
- 点击 "读档" 按钮
- 按 `N` 键新建存档
- 输入存档名称
- 按回车确认
#### 加载游戏
1. **打开界面**: 点击 "读档" 按钮或按 `Ctrl+L`
2. **选择存档**: 使用↑↓键选择或直接点击
3. **加载**: 按回车键或双击加载选中的存档
4. **关闭**: 按 `ESC` 关闭界面
#### 管理存档
- **删除存档**: 选中后按 `Delete`
- **查看信息**: 存档列表显示名称和保存时间
### 📁 存档文件结构
存档保存在 `saves/` 目录下:
```
saves/
├── maze_save_20231229_143022.json # 完整存档文件
├── maze_save_20231229_143022_maze.csv # 对应的CSV文件
└── ...
```
#### 存档内容包括:
- **元数据**: 存档名称、时间、迷宫尺寸
- **迷宫数据**: 完整的迷宫结构
- **路径数据**: 完整路径、当前步数、完成状态
### 🎨 界面说明
#### 存档界面控制:
- **↑↓键**: 选择存档
- **回车**: 加载选中的存档
- **Delete**: 删除选中的存档
- **N键**: 新建存档
- **ESC**: 关闭界面
#### 键盘快捷键:
- **Ctrl+S**: 快速保存当前游戏状态
- **Ctrl+L**: 打开读档界面
- **空格**: 路径下一步
- **R键**: 重置路径
- **A键**: 自动播放切换
### 💡 使用技巧
1. **定期保存**: 在路径规划的关键点保存进度
2. **命名规范**: 使用有意义的存档名称便于识别
3. **备份重要存档**: 重要的迷宫解法可以导出CSV备份
4. **清理存档**: 定期删除不需要的存档文件
### 🔄 兼容性
- **向后兼容**: 依然支持CSV格式的迷宫文件
- **数据完整**: 新存档格式包含完整的游戏状态
- **灵活切换**: 可以在新旧保存方式之间自由选择
### 📝 注意事项
1. 存档文件使用JSON格式包含完整的游戏状态
2. 删除存档操作不可撤销,请谨慎操作
3. 存档目录会自动创建,无需手动设置
4. 加载存档会覆盖当前游戏状态
现在你可以:
- 在解谜过程中随时保存进度
- 尝试不同的路径策略
- 保存有趣的迷宫布局
- 与他人分享迷宫存档

61
config.py Normal file
View File

@ -0,0 +1,61 @@
# 游戏配置文件
# UI 界面配置
UI_HEIGHT = 1000
UI_WIDTH = 1500
# 迷宫配置
MAZE_SIZE = 800
WALL_SIZE = 50
# 游戏性能配置
FPS = 120
# 路径播放配置
AUTO_PLAY_INTERVAL = 30 # 每30帧自动前进一步
# 按钮尺寸配置
BUTTON_START_SIZE = (200, 100)
BUTTON_SAVE_SIZE = (80, 80)
BUTTON_CONTROL_SIZE = (100, 50)
# 字体配置
FONT_FILE = "syht.otf"
FONT_SIZE = 18
# 资源路径配置
ASSETS_PATH = "assets"
WALL_IMAGE = f"{ASSETS_PATH}/wall.png"
COIN_IMAGE = f"{ASSETS_PATH}/coin.png"
TRAP_IMAGE = f"{ASSETS_PATH}/trap.png"
START_BUTTON_IMAGE = f"{ASSETS_PATH}/start_button.png"
SAVE_BUTTON_IMAGE = f"{ASSETS_PATH}/save.png"
LOAD_BUTTON_IMAGE = f"{ASSETS_PATH}/load.png"
# 默认文件名
DEFAULT_MAZE_FILE = "maze.csv"
# 颜色配置
COLOR_WHITE = (255, 255, 255)
COLOR_BLACK = (0, 0, 0)
COLOR_GRAY = (200, 200, 200)
COLOR_LIGHT_GRAY = (100, 100, 100)
COLOR_GREEN = (100, 255, 100)
COLOR_GOLD = (255, 215, 0)
# 布局配置
def get_button_positions():
"""返回按钮位置配置"""
control_panel_x = MAZE_SIZE + ((UI_WIDTH - MAZE_SIZE) / 2 - 100)
return {
'start_button': (MAZE_SIZE + 50, 0),
'save_button': (MAZE_SIZE + 50, 110),
'load_button': (MAZE_SIZE + 150, 110),
'next_step_button': (MAZE_SIZE + 50, 200),
'reset_path_button': (MAZE_SIZE + 200, 200),
'auto_play_button': (MAZE_SIZE + 350, 200),
'progress_text': (MAZE_SIZE + 50, 270),
'hint_text': (MAZE_SIZE + 50, 300),
'save_list_area': (MAZE_SIZE + 50, 350, 400, 200) # x, y, width, height
}

188
main.py
View File

@ -4,16 +4,11 @@ from pygame.constants import QUIT
from maze import Maze from maze import Maze
from draw import Button, Toast from draw import Button, Toast
from save_ui import SaveLoadUI
from config import *
import sys import sys
import os import os
UI_HEIGHT = 1000
UI_WIDTH = 1500
MAZE_SIZE = 150
WALL_SIZE = 50
FPS = 120
screen: Surface = None # 窗口实例 screen: Surface = None # 窗口实例
clock = None # 时钟实例 clock = None # 时钟实例
@ -30,52 +25,139 @@ def pygameInit(title: str = "pygame"):
clock = pygame.time.Clock() clock = pygame.time.Clock()
# Initialize font with UTF-8 support # Initialize font with UTF-8 support
pygame.font.init() pygame.font.init()
textFont = pygame.font.Font("syht.otf", 18) textFont = pygame.font.Font(FONT_FILE, FONT_SIZE)
if __name__ == "__main__": if __name__ == "__main__":
pygameInit("maze") pygameInit("maze")
maze = Maze(wall_size=WALL_SIZE, maze_size=MAZE_SIZE, file_name="maze.csv") maze = Maze(wall_size=WALL_SIZE, maze_size=MAZE_SIZE, file_name=DEFAULT_MAZE_FILE)
image_wall = pygame.image.load("assets/wall.png").convert_alpha()
image_wall = pygame.transform.scale(image_wall, (WALL_SIZE, WALL_SIZE)) # 例如缩放到50x50像素 # 加载图片资源
image_wall = pygame.image.load(WALL_IMAGE).convert_alpha()
image_wall = pygame.transform.scale(image_wall, (WALL_SIZE, WALL_SIZE))
image_coin = pygame.image.load("assets/coin.png").convert_alpha() image_coin = pygame.image.load(COIN_IMAGE).convert_alpha()
image_coin = pygame.transform.scale(image_coin, (WALL_SIZE, WALL_SIZE)) # 例如缩放到50x50像素 image_coin = pygame.transform.scale(image_coin, (WALL_SIZE, WALL_SIZE))
image_trap = pygame.image.load("assets/trap.png").convert_alpha() image_trap = pygame.image.load(TRAP_IMAGE).convert_alpha()
image_trap = pygame.transform.scale(image_trap, (WALL_SIZE, WALL_SIZE)) # 例如缩放到50x50像素 image_trap = pygame.transform.scale(image_trap, (WALL_SIZE, WALL_SIZE))
button_start_texture = pygame.image.load("assets/start_button.png").convert_alpha() # 获取按钮位置配置
button_start_texture = pygame.transform.scale(button_start_texture, (200, 100)) button_positions = get_button_positions()
button_start = Button(pygame.rect.Rect(MAZE_SIZE + ((UI_WIDTH - MAZE_SIZE) / 2 - 100), 0, 200, 100), button_start_texture)
button_save_texture = pygame.image.load("assets/save.png").convert_alpha() # 创建按钮
button_save_texture = pygame.transform.scale(button_save_texture, (80, 80)) button_start_texture = pygame.image.load(START_BUTTON_IMAGE).convert_alpha()
button_save = Button(pygame.rect.Rect(MAZE_SIZE + ((UI_WIDTH - MAZE_SIZE) / 2 - 100), 110, 80, 80), button_save_texture) button_start_texture = pygame.transform.scale(button_start_texture, BUTTON_START_SIZE)
button_start = Button(pygame.rect.Rect(*button_positions['start_button'], *BUTTON_START_SIZE), button_start_texture)
button_save_texture = pygame.image.load(SAVE_BUTTON_IMAGE).convert_alpha()
button_save_texture = pygame.transform.scale(button_save_texture, BUTTON_SAVE_SIZE)
button_save = Button(pygame.rect.Rect(*button_positions['save_button'], *BUTTON_SAVE_SIZE), button_save_texture)
# 没有生成迷宫就保存的提示框 button_load_texture = pygame.image.load(LOAD_BUTTON_IMAGE).convert_alpha()
button_load_texture = pygame.transform.scale(button_load_texture, BUTTON_SAVE_SIZE)
button_load = Button(pygame.rect.Rect(*button_positions['load_button'], *BUTTON_SAVE_SIZE), button_load_texture)
# 添加路径控制按钮
button_next_step = Button(pygame.rect.Rect(*button_positions['next_step_button'], *BUTTON_CONTROL_SIZE), None)
button_reset_path = Button(pygame.rect.Rect(*button_positions['reset_path_button'], *BUTTON_CONTROL_SIZE), None)
button_auto_play = Button(pygame.rect.Rect(*button_positions['auto_play_button'], *BUTTON_CONTROL_SIZE), None)
# 提示信息
mes1 = Toast("没有生成迷宫,无法保存", UI_WIDTH, UI_HEIGHT, font=textFont) mes1 = Toast("没有生成迷宫,无法保存", UI_WIDTH, UI_HEIGHT, font=textFont)
mes2 = Toast("迷宫已保存", UI_WIDTH, UI_HEIGHT, font=textFont) mes2 = Toast("迷宫已保存", UI_WIDTH, UI_HEIGHT, font=textFont)
mes3 = Toast("存档已保存", UI_WIDTH, UI_HEIGHT, font=textFont)
mes4 = Toast("存档加载成功", UI_WIDTH, UI_HEIGHT, font=textFont)
mes5 = Toast("加载失败", UI_WIDTH, UI_HEIGHT, font=textFont)
# 创建存档界面
save_ui = SaveLoadUI(textFont)
# 路径控制变量
auto_play = False
auto_play_timer = 0
auto_play_interval = AUTO_PLAY_INTERVAL
running = True running = True
while running: while running:
clock.tick(FPS) # 限制帧数 clock.tick(FPS) # 限制帧数
screen.fill((255, 255, 255)) # 铺底 screen.fill(COLOR_WHITE) # 铺底
# 自动播放逻辑
if auto_play and len(maze.full_path) > 0:
auto_play_timer += 1
if auto_play_timer >= auto_play_interval:
if not maze.next_path_step():
auto_play = False # 路径播放完成后停止自动播放
auto_play_timer = 0
for event in pygame.event.get(): for event in pygame.event.get():
button_start.handle_event(event=event) # 首先让存档界面处理事件
button_save.handle_event(event=event) save_result = save_ui.handle_event(event, maze)
if save_result == "save_success":
if button_start.pressed == True: mes3.show()
maze.generate() elif save_result == "load_success":
mes4.show()
auto_play = False # 加载游戏后停止自动播放
elif save_result == "load_failed":
mes5.show()
if button_save.pressed == True: # 如果存档界面正在显示,不处理其他按钮事件
if len(maze.grid) == 0: if not save_ui.show_save_list:
mes1.show() button_start.handle_event(event=event)
else: button_save.handle_event(event=event)
maze.export_to_csv("maze.csv") button_load.handle_event(event=event)
mes2.text = "迷宫已保存至maze.csv" button_next_step.handle_event(event=event)
mes2.show() button_reset_path.handle_event(event=event)
button_auto_play.handle_event(event=event)
if button_start.pressed == True:
maze.generate()
auto_play = False # 生成新迷宫时停止自动播放
if button_save.pressed == True:
if len(maze.grid) == 0:
mes1.show()
else:
maze.export_to_csv(DEFAULT_MAZE_FILE)
mes2.text = f"迷宫已保存至{DEFAULT_MAZE_FILE}"
mes2.show()
if button_load.pressed == True:
save_ui.update_save_list(maze)
save_ui.toggle_save_list()
# 路径控制
if button_next_step.pressed == True and len(maze.full_path) > 0:
maze.next_path_step()
if button_reset_path.pressed == True and len(maze.full_path) > 0:
maze.reset_path()
auto_play = False
if button_auto_play.pressed == True and len(maze.full_path) > 0:
auto_play = not auto_play
auto_play_timer = 0
# 键盘控制
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and len(maze.full_path) > 0:
maze.next_path_step()
elif event.key == pygame.K_r and len(maze.full_path) > 0:
maze.reset_path()
auto_play = False
elif event.key == pygame.K_a and len(maze.full_path) > 0:
auto_play = not auto_play
auto_play_timer = 0
elif event.key == pygame.K_s and pygame.key.get_pressed()[pygame.K_LCTRL]:
# Ctrl+S 保存包含路径的CSV
if len(maze.grid) > 0:
result = maze.save_game()
if result:
mes3.show()
elif event.key == pygame.K_l and pygame.key.get_pressed()[pygame.K_LCTRL]:
# Ctrl+L 打开读档界面
save_ui.update_save_list(maze)
save_ui.toggle_save_list()
if event.type == QUIT: if event.type == QUIT:
running = False running = False
@ -84,9 +166,45 @@ if __name__ == "__main__":
maze.draw(screen=screen, wall_texture=image_wall, coin_texture=image_coin, trap_texture=image_trap) maze.draw(screen=screen, wall_texture=image_wall, coin_texture=image_coin, trap_texture=image_trap)
button_start.draw(screen=screen) button_start.draw(screen=screen)
button_save.draw(screen=screen) button_save.draw(screen=screen)
button_load.draw(screen=screen)
# 绘制路径控制按钮
if len(maze.full_path) > 0:
# 绘制按钮背景
pygame.draw.rect(screen, COLOR_GRAY, button_next_step.rect)
pygame.draw.rect(screen, COLOR_GRAY, button_reset_path.rect)
pygame.draw.rect(screen, COLOR_GREEN if auto_play else COLOR_GRAY, button_auto_play.rect)
# 绘制按钮文字
next_text = textFont.render("下一步", True, COLOR_BLACK)
reset_text = textFont.render("重置", True, COLOR_BLACK)
auto_text = textFont.render("自动播放" if not auto_play else "停止", True, COLOR_BLACK)
screen.blit(next_text, (button_next_step.rect.x + 10, button_next_step.rect.y + 15))
screen.blit(reset_text, (button_reset_path.rect.x + 25, button_reset_path.rect.y + 15))
screen.blit(auto_text, (button_auto_play.rect.x + 5, button_auto_play.rect.y + 15))
# 显示当前步数信息
progress_text = textFont.render(f"路径进度: {maze.path_step}/{len(maze.full_path)}", True, COLOR_BLACK)
screen.blit(progress_text, button_positions['progress_text'])
# 显示操作提示
if len(maze.full_path) > 0:
hint_text = textFont.render("空格键: 下一步 | R键: 重置 | A键: 自动播放", True, COLOR_LIGHT_GRAY)
screen.blit(hint_text, button_positions['hint_text'])
# 显示快捷键提示
shortcut_text = textFont.render("Ctrl+S: 保存包含路径的CSV | Ctrl+L: 读档", True, COLOR_LIGHT_GRAY)
screen.blit(shortcut_text, (MAZE_SIZE + 50, 330))
mes1.draw(screen=screen) mes1.draw(screen=screen)
mes2.draw(screen=screen) mes2.draw(screen=screen)
mes3.draw(screen=screen)
mes4.draw(screen=screen)
mes5.draw(screen=screen)
# 绘制存档界面(必须在最后绘制以显示在最上层)
save_ui.draw(screen)
pygame.display.flip() pygame.display.flip()
pygame.quit() pygame.quit()

View File

@ -1,16 +1,16 @@
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,0,0,1,0,0,0,0,0,l25,0,t10,0,1,0,1 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,1,0,1,1,1,1,1,1,1,1,1,l15,1,t7,1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1
1,0,0,g26,0,e,t20,s,0,0,0,0,0,0,l25,1 1,0,0,0,1,0,0,t9,1,0,1,0,1,0,0,1
1,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1 1,0,1,1,1,1,l15,1,1,0,1,0,1,0,1,1
1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1 1,b50,0,0,0,0,g11,0,1,0,1,0,l16,0,1,1
1,0,1,1,1,1,0,1,1,1,1,1,g24,1,0,1 1,1,0,1,1,1,1,1,1,0,1,0,1,0,1,1
1,0,0,1,0,t16,0,0,1,0,0,0,0,1,l16,1 1,0,t20,0,0,0,0,0,1,0,0,0,1,0,1,1
1,1,1,1,1,1,1,1,1,1,1,0,1,1,g15,1 1,1,1,1,1,1,1,0,0,0,1,0,1,0,1,1
1,0,t8,1,0,1,0,0,g12,0,1,0,0,1,0,1 1,0,1,0,0,0,1,0,1,0,1,0,1,0,1,1
1,0,0,1,0,1,1,0,1,0,1,0,1,1,0,1 1,0,1,1,1,0,l20,g19,1,t8,1,0,1,l12,1,1
1,0,0,0,0,0,0,0,1,0,1,0,t15,1,l16,1 1,g11,e,0,0,0,1,g13,1,0,1,0,1,0,0,1
1,0,0,1,0,1,0,t16,1,0,1,0,0,1,0,1 1,0,1,1,0,1,1,1,1,0,1,1,1,1,s,1
1,1,0,1,1,1,1,1,1,0,1,0,0,1,0,1 1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,1
1,0,0,0,0,0,0,0,1,0,t5,b89,0,1,0,1 1,0,1,1,1,l22,1,0,1,l27,1,0,0,t17,0,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
2 1 0 0 1 0 0 0 0 0 0 l25 0 0 t10 0 0 1 0 0 1
3 1 1 0 1 1 1 1 1 1 1 1 1 1 l15 1 1 t7 0 1
4 1 0 0 g26 0 0 1 e 0 t20 0 s t9 0 1 0 0 1 0 0 1 0 l25 0 1
5 1 1 0 1 1 1 1 0 l15 1 1 1 0 1 1 0 1 1 0 0 1 1
6 1 0 b50 0 0 0 0 0 g11 0 0 1 0 0 1 0 0 l16 1 0 0 1 1
7 1 0 1 1 0 1 1 1 0 1 1 1 1 0 1 1 0 g24 1 1 0 0 1 1
8 1 0 0 t20 1 0 0 t16 0 0 0 1 0 0 0 0 1 1 0 l16 1 1
9 1 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 0 g15 1 1
10 1 0 t8 1 1 0 0 1 0 0 1 0 g12 1 0 1 0 0 1 1 0 0 1 1
11 1 0 0 1 1 0 1 1 0 1 l20 0 g19 1 0 t8 1 0 1 1 l12 0 1 1
12 1 0 g11 0 e 0 0 0 0 1 0 g13 1 0 1 0 t15 1 1 0 l16 0 1
13 1 0 0 1 1 0 1 0 1 t16 1 1 0 1 0 1 0 1 1 0 s 1
14 1 1 0 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 1 1 0 0 1
15 1 0 0 1 0 1 0 1 0 l22 0 1 0 1 0 l27 t5 1 b89 0 0 1 t17 0 1
16 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

99
maze.py
View File

@ -2,6 +2,7 @@ import pygame
from maze_generator import MazeGenerator from maze_generator import MazeGenerator
from SourceCollector import SourceCollector from SourceCollector import SourceCollector
from tanxin import * from tanxin import *
from simple_save_manager import simple_save_manager
import time import time
class Maze: class Maze:
@ -14,19 +15,109 @@ class Maze:
self.grid = [] self.grid = []
self.generater = MazeGenerator(self.size, self.file_name) self.generater = MazeGenerator(self.size, self.file_name)
self.source_collector = None
self.path_step = 0 # 当前显示到路径的第几步
self.full_path = [] # 完整路径
self.is_path_complete = False # 路径是否完全显示
def generate(self): def generate(self):
seed = int(time.time() * 1000) % (2**32) seed = int(time.time() * 1000) % (2**32)
self.generater.generate(seed=seed) self.generater.generate(seed=seed)
obj = SourceCollector(maze=self.generater.maze) self.source_collector = SourceCollector(maze=self.generater.maze)
obj.run() self.source_collector.run()
self.grid = obj.output_list() self.full_path = self.source_collector.get_path()
print(self.grid) for i in self.full_path:
print(i)
self.path_step = 0
self.is_path_complete = False
self.grid = self.generater.maze # 使用原始迷宫数据
print(f"路径长度: {len(self.full_path)}")
def next_path_step(self):
"""显示路径的下一步"""
if self.path_step < len(self.full_path):
self.path_step += 1
self.update_grid_with_path()
return True
return False
def reset_path(self):
"""重置路径显示"""
self.path_step = 0
self.is_path_complete = False
self.grid = self.generater.maze if self.generater.maze else []
def auto_advance_path(self):
"""自动推进路径显示"""
if not self.is_path_complete:
if not self.next_path_step():
self.is_path_complete = True
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): def export_to_csv(self, filename):
"""导出迷宫到CSV文件兼容旧版本"""
self.generater.export_to_csv(filename=filename) self.generater.export_to_csv(filename=filename)
def save_game(self, save_name=None):
"""保存包含路径信息的迷宫到CSV文件"""
if len(self.grid) == 0:
print("没有生成迷宫,无法保存")
return None
return simple_save_manager.save_maze_with_path(self, save_name)
def load_game(self, save_file):
"""从CSV存档文件加载游戏状态"""
load_data = simple_save_manager.load_maze_from_csv(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']
print(f"成功加载游戏状态,路径长度: {len(self.full_path)}")
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): def draw(self, screen, wall_texture, coin_texture, trap_texture):

142
save_manager.py Normal file
View File

@ -0,0 +1,142 @@
import json
import csv
import os
from datetime import datetime
from config import DEFAULT_MAZE_FILE
class GameSaveManager:
"""游戏存档管理器"""
def __init__(self):
self.save_directory = "saves"
self.ensure_save_directory()
def ensure_save_directory(self):
"""确保存档目录存在"""
if not os.path.exists(self.save_directory):
os.makedirs(self.save_directory)
def save_game_state(self, maze_instance, save_name=None):
"""
保存游戏状态包括迷宫和路径信息
Args:
maze_instance: Maze类的实例
save_name: 存档名称如果为None则使用时间戳
Returns:
str: 保存的文件路径
"""
if save_name is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
save_name = f"maze_save_{timestamp}"
# 创建存档数据结构
save_data = {
"metadata": {
"save_name": save_name,
"save_time": datetime.now().isoformat(),
"maze_size": maze_instance.size,
"wall_size": maze_instance.wall_size,
"maze_display_size": maze_instance.maze_size
},
"maze_data": {
"grid": maze_instance.generater.maze if maze_instance.generater.maze else [],
"generator_name": maze_instance.generater.name if hasattr(maze_instance.generater, 'name') else "Unknown"
},
"path_data": {
"full_path": maze_instance.full_path,
"current_step": maze_instance.path_step,
"is_path_complete": maze_instance.is_path_complete
}
}
# 保存为JSON文件
save_file = os.path.join(self.save_directory, f"{save_name}.json")
try:
with open(save_file, 'w', encoding='utf-8') as f:
json.dump(save_data, f, indent=2, ensure_ascii=False)
# 同时保存CSV格式的迷宫数据兼容性
csv_file = os.path.join(self.save_directory, f"{save_name}_maze.csv")
self.save_maze_csv(maze_instance.generater.maze, csv_file)
print(f"游戏状态已保存至: {os.path.abspath(save_file)}")
return save_file
except Exception as e:
print(f"保存失败: {str(e)}")
return None
def load_game_state(self, save_file):
"""
加载游戏状态
Args:
save_file: 存档文件路径
Returns:
dict: 包含游戏状态的字典如果加载失败返回None
"""
try:
with open(save_file, 'r', encoding='utf-8') as f:
save_data = json.load(f)
print(f"成功加载存档: {save_data['metadata']['save_name']}")
print(f"保存时间: {save_data['metadata']['save_time']}")
return save_data
except Exception as e:
print(f"加载失败: {str(e)}")
return None
def save_maze_csv(self, maze_grid, filename):
"""保存迷宫网格为CSV格式"""
try:
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
for row in maze_grid:
writer.writerow(row)
return True
except Exception as e:
print(f"CSV保存失败: {str(e)}")
return False
def get_save_list(self):
"""获取所有存档文件列表"""
saves = []
if os.path.exists(self.save_directory):
for filename in os.listdir(self.save_directory):
if filename.endswith('.json'):
save_path = os.path.join(self.save_directory, filename)
try:
with open(save_path, 'r', encoding='utf-8') as f:
save_data = json.load(f)
saves.append({
'filename': filename,
'path': save_path,
'name': save_data['metadata']['save_name'],
'save_time': save_data['metadata']['save_time'],
'maze_size': save_data['metadata']['maze_size']
})
except:
continue
return sorted(saves, key=lambda x: x['save_time'], reverse=True)
def delete_save(self, save_file):
"""删除存档文件"""
try:
if os.path.exists(save_file):
os.remove(save_file)
# 同时删除对应的CSV文件
csv_file = save_file.replace('.json', '_maze.csv')
if os.path.exists(csv_file):
os.remove(csv_file)
print(f"存档已删除: {save_file}")
return True
except Exception as e:
print(f"删除失败: {str(e)}")
return False
# 全局存档管理器实例
save_manager = GameSaveManager()

162
save_ui.py Normal file
View File

@ -0,0 +1,162 @@
import pygame
from config import *
class SaveLoadUI:
"""存档和读档界面"""
def __init__(self, font):
self.font = font
self.small_font = pygame.font.Font(FONT_FILE, FONT_SIZE - 4)
self.show_save_list = False
self.save_list = []
self.selected_save = -1
self.scroll_offset = 0
self.max_visible_saves = 8
# 界面元素
button_positions = get_button_positions()
self.list_area = pygame.Rect(*button_positions['save_list_area'])
self.save_input = ""
self.input_active = False
def update_save_list(self, maze):
"""更新存档列表"""
self.save_list = maze.get_save_list()
def toggle_save_list(self):
"""切换存档列表显示状态"""
self.show_save_list = not self.show_save_list
if not self.show_save_list:
self.selected_save = -1
self.input_active = False
def handle_event(self, event, maze):
"""处理界面事件"""
if not self.show_save_list:
return None
if event.type == pygame.KEYDOWN:
if self.input_active:
if event.key == pygame.K_RETURN:
# 保存游戏
if self.save_input.strip():
result = maze.save_game(self.save_input.strip())
if result:
self.update_save_list(maze)
self.save_input = ""
self.input_active = False
return "save_success"
return "save_failed"
elif event.key == pygame.K_ESCAPE:
self.input_active = False
self.save_input = ""
elif event.key == pygame.K_BACKSPACE:
self.save_input = self.save_input[:-1]
else:
if len(self.save_input) < 20: # 限制输入长度
self.save_input += event.unicode
else:
if event.key == pygame.K_UP:
if self.selected_save > 0:
self.selected_save -= 1
elif event.key == pygame.K_DOWN:
if self.selected_save < len(self.save_list) - 1:
self.selected_save += 1
elif event.key == pygame.K_RETURN:
# 加载选中的存档
if 0 <= self.selected_save < len(self.save_list):
save_file = self.save_list[self.selected_save]['path']
if maze.load_game(save_file):
self.show_save_list = False
return "load_success"
return "load_failed"
elif event.key == pygame.K_DELETE:
# 删除选中的存档
if 0 <= self.selected_save < len(self.save_list):
save_file = self.save_list[self.selected_save]['path']
from simple_save_manager import simple_save_manager
if simple_save_manager.delete_save(save_file):
self.update_save_list(maze)
if self.selected_save >= len(self.save_list):
self.selected_save = len(self.save_list) - 1
return "delete_success"
elif event.key == pygame.K_n:
# 新建存档
self.input_active = True
self.save_input = ""
elif event.key == pygame.K_ESCAPE:
self.toggle_save_list()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # 左键点击
mouse_pos = pygame.mouse.get_pos()
# 检查是否点击在存档列表区域
if self.list_area.collidepoint(mouse_pos):
# 计算点击的存档索引
relative_y = mouse_pos[1] - self.list_area.y - 30 # 减去标题高度
if relative_y >= 0:
clicked_index = relative_y // 25 # 每个存档项25像素高
if 0 <= clicked_index < len(self.save_list):
self.selected_save = clicked_index
else:
# 点击外部关闭界面
self.toggle_save_list()
return None
def draw(self, screen):
"""绘制存档界面"""
if not self.show_save_list:
return
# 绘制半透明背景
overlay = pygame.Surface((UI_WIDTH, UI_HEIGHT))
overlay.set_alpha(128)
overlay.fill((0, 0, 0))
screen.blit(overlay, (0, 0))
# 绘制存档列表背景
pygame.draw.rect(screen, COLOR_WHITE, self.list_area)
pygame.draw.rect(screen, COLOR_BLACK, self.list_area, 2)
# 绘制标题
title_text = self.font.render("存档管理", True, COLOR_BLACK)
screen.blit(title_text, (self.list_area.x + 10, self.list_area.y + 5))
# 绘制操作说明
if self.input_active:
hint_text = self.small_font.render("输入存档名称(.csv按回车保存:", True, COLOR_BLACK)
screen.blit(hint_text, (self.list_area.x + 10, self.list_area.y + 30))
# 绘制输入框
input_rect = pygame.Rect(self.list_area.x + 10, self.list_area.y + 50, 300, 25)
pygame.draw.rect(screen, COLOR_WHITE, input_rect)
pygame.draw.rect(screen, COLOR_BLACK, input_rect, 2)
input_text = self.small_font.render(self.save_input + "|", True, COLOR_BLACK)
screen.blit(input_text, (input_rect.x + 5, input_rect.y + 5))
else:
hint_text = self.small_font.render("↑↓选择 回车加载 Delete删除 N新建 ESC关闭", True, COLOR_BLACK)
screen.blit(hint_text, (self.list_area.x + 10, self.list_area.y + 30))
# 绘制存档列表
start_y = self.list_area.y + 55
for i, save_info in enumerate(self.save_list):
y_pos = start_y + i * 25
if y_pos > self.list_area.bottom - 25:
break
# 高亮选中项
if i == self.selected_save:
highlight_rect = pygame.Rect(self.list_area.x + 5, y_pos - 2, self.list_area.width - 10, 22)
pygame.draw.rect(screen, COLOR_GRAY, highlight_rect)
# 显示存档信息
save_text = f"{save_info['name']} - {save_info['save_time'][:16]}"
text_surface = self.small_font.render(save_text, True, COLOR_BLACK)
screen.blit(text_surface, (self.list_area.x + 10, y_pos))
# 如果没有存档,显示提示
if not self.save_list and not self.input_active:
no_save_text = self.small_font.render("没有找到存档文件", True, COLOR_LIGHT_GRAY)
screen.blit(no_save_text, (self.list_area.x + 10, self.list_area.y + 60))

220
simple_save_manager.py Normal file
View File

@ -0,0 +1,220 @@
import csv
import os
from datetime import datetime
from config import DEFAULT_MAZE_FILE
class SimpleSaveManager:
"""简化的存档管理器 - 只保存CSV格式的迷宫文件"""
def __init__(self):
self.save_directory = "saves"
self.ensure_save_directory()
def ensure_save_directory(self):
"""确保存档目录存在"""
if not os.path.exists(self.save_directory):
os.makedirs(self.save_directory)
def save_maze_with_path(self, maze_instance, save_name=None):
"""
保存包含路径信息的迷宫到CSV文件
Args:
maze_instance: Maze类的实例
save_name: 存档名称如果为None则使用时间戳
Returns:
str: 保存的文件路径
"""
if save_name is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
save_name = f"maze_save_{timestamp}"
# 确保文件名以.csv结尾
if not save_name.endswith('.csv'):
save_name += '.csv'
save_file = os.path.join(self.save_directory, save_name)
try:
# 生成包含路径信息的网格
path_grid = self.create_path_grid(maze_instance)
# 保存到CSV文件
with open(save_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
for row in path_grid:
writer.writerow(row)
print(f"迷宫已保存至: {os.path.abspath(save_file)}")
return save_file
except Exception as e:
print(f"保存失败: {str(e)}")
return None
def create_path_grid(self, maze_instance):
"""
创建包含路径信息的网格
Args:
maze_instance: Maze类的实例
Returns:
list: 包含路径信息的二维网格
"""
if not maze_instance.generater.maze or not maze_instance.full_path:
return maze_instance.generater.maze if maze_instance.generater.maze else []
# 从原始迷宫开始
path_grid = [row[:] for row in maze_instance.generater.maze] # 深拷贝
# 将完整路径信息添加到网格中
for idx, (y, x) in enumerate(maze_instance.full_path):
current_cell = path_grid[y][x]
path_info = f"p{idx + 1}" # 路径编号从1开始
if current_cell.startswith('s'):
# 起点:保留起点标记,但也添加路径信息
path_grid[y][x] = f"s{path_info}"
elif current_cell.startswith('e'):
# 终点:保留终点标记,但也添加路径信息
path_grid[y][x] = f"e{path_info}"
elif current_cell.startswith('g'):
# 金币g10p31 格式
path_grid[y][x] = f"{current_cell}{path_info}"
elif current_cell.startswith('t'):
# 陷阱t19p31 格式
path_grid[y][x] = f"{current_cell}{path_info}"
elif current_cell.startswith('l'):
# 机关l24p31 格式
path_grid[y][x] = f"{current_cell}{path_info}"
elif current_cell.startswith('b'):
# bossb92p31 格式
path_grid[y][x] = f"{current_cell}{path_info}"
elif current_cell == '0':
# 空地直接是p31
path_grid[y][x] = path_info
else:
# 其他情况也添加路径信息
path_grid[y][x] = f"{current_cell}{path_info}"
return path_grid
def load_maze_from_csv(self, csv_file):
"""
从CSV文件加载迷宫解析路径信息
Args:
csv_file: CSV文件路径
Returns:
dict: 包含迷宫数据和路径信息的字典
"""
try:
with open(csv_file, 'r', newline='', encoding='utf-8') as f:
reader = csv.reader(f)
grid = [list(row) for row in reader]
# 解析路径信息
path_data = self.extract_path_from_grid(grid)
# 创建原始迷宫(移除路径信息)
original_grid = self.create_original_grid(grid)
result = {
'original_grid': original_grid,
'path_grid': grid,
'path_sequence': path_data['path_sequence'],
'path_positions': path_data['path_positions']
}
print(f"成功从 {os.path.abspath(csv_file)} 加载迷宫")
print(f"路径长度: {len(path_data['path_sequence'])}")
return result
except Exception as e:
print(f"加载失败: {str(e)}")
return None
def extract_path_from_grid(self, grid):
"""从网格中提取路径信息"""
path_positions = {} # {step_number: (y, x)}
for y in range(len(grid)):
for x in range(len(grid[y])):
cell = grid[y][x]
# 查找所有路径信息 p数字
import re
# 使用正则表达式找到所有p后跟数字的模式
path_matches = re.findall(r'p(\d+)', cell)
for path_match in path_matches:
try:
step_number = int(path_match)
path_positions[step_number] = (y, x)
except ValueError:
continue
# 按步数排序创建路径序列
path_sequence = []
for step in sorted(path_positions.keys()):
path_sequence.append(path_positions[step])
return {
'path_sequence': path_sequence,
'path_positions': path_positions
}
def create_original_grid(self, path_grid):
"""从包含路径的网格创建原始迷宫网格"""
original_grid = []
for row in path_grid:
original_row = []
for cell in row:
# 移除路径信息,恢复原始状态
if 'p' in cell:
p_index = cell.find('p')
original_cell = cell[:p_index] if p_index > 0 else '0'
else:
original_cell = cell
original_row.append(original_cell)
original_grid.append(original_row)
return original_grid
def get_save_list(self):
"""获取所有CSV存档文件列表"""
saves = []
if os.path.exists(self.save_directory):
for filename in os.listdir(self.save_directory):
if filename.endswith('.csv'):
file_path = os.path.join(self.save_directory, filename)
try:
stat = os.stat(file_path)
saves.append({
'filename': filename,
'path': file_path,
'name': filename.replace('.csv', ''),
'save_time': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'size': stat.st_size
})
except:
continue
return sorted(saves, key=lambda x: x['save_time'], reverse=True)
def delete_save(self, save_file):
"""删除存档文件"""
try:
if os.path.exists(save_file):
os.remove(save_file)
print(f"存档已删除: {save_file}")
return True
except Exception as e:
print(f"删除失败: {str(e)}")
return False
# 全局简化存档管理器实例
simple_save_manager = SimpleSaveManager()