diff --git a/README_3x3_GREEDY.md b/README_3x3_GREEDY.md new file mode 100644 index 0000000..8d0dfd5 --- /dev/null +++ b/README_3x3_GREEDY.md @@ -0,0 +1,267 @@ +# 3x3视野贪心资源收集算法使用指南 + +## 概述 + +本项目实现了一个基于3x3视野的贪心算法,用于在迷宫环境中收集资源。该算法的核心特点是: + +- **视野限制**:每次移动时只考虑当前位置周围3x3范围内的单元格 +- **贪心策略**:在可见范围内选择价值最高的资源 +- **移动限制**:只能进行上下左右四个方向的移动 +- **智能探索**:当视野内没有资源时,会进行探索性移动寻找新资源 + +## 文件说明 + +### 主要文件 + +1. **`greedy_3x3_algorithm.py`** - 独立的3x3视野贪心算法模块 +2. **`tanxin.py`** - 原有的贪心算法实现,已集成新的3x3算法 +3. **`greedy_resource_collector.py`** - 完整版本的贪心收集器(支持全局搜索) +4. **`strict_3x3_greedy.py`** - 严格的3x3视野算法演示 + +### 演示文件 + +- **`greedy_resource_collector.py`** - 包含演示的完整贪心算法 +- **`strict_3x3_greedy.py`** - 专门的3x3算法演示 + +## 使用方法 + +### 1. 基本使用 + +```python +from greedy_3x3_algorithm import Greedy3x3Algorithm + +# 创建迷宫(二维数组,[y][x]格式) +maze = [ + ['s', '0', 'g5', '1', 't3'], # s=起点, g5=价值5的金币, 1=墙壁, t3=损失3的陷阱 + ['0', '1', '0', '0', 'g2'], # 0=可通行路径 + ['g3', '0', '1', 't2', '0'], + ['0', 't1', '0', '0', 'g4'], + ['1', '0', 'g1', '0', 'e'] # e=终点 +] + +# 创建算法实例 +algorithm = Greedy3x3Algorithm(maze, debug=True) + +# 运行算法 +result = algorithm.run() + +# 查看结果 +print(f"总价值: {result['total_value']}") +print(f"收集资源数量: {result['resources_count']}") +print(f"移动步数: {result['total_moves']}") + +# 打印详细结果 +algorithm.print_result() +``` + +### 2. 在现有项目中集成 + +```python +from tanxin import Greedy3x3ResourceCollector + +# 使用现有的迷宫数据 +map_data = your_maze_data # 二维数组格式 + +# 创建收集器 +collector = Greedy3x3ResourceCollector(map_data) + +# 运行收集算法 +result = collector.run_3x3_greedy_collection() + +# 获取兼容格式的路径 (y, x格式) +path_yx = collector.get_path() +total_reward = collector.get_total_reward() +marked_map = collector.add_path_to_map() +``` + +### 3. 运行演示 + +```bash +# 运行独立算法演示 +python greedy_3x3_algorithm.py + +# 运行集成演示 +echo "2" | python tanxin.py + +# 运行比较演示 +python strict_3x3_greedy.py +``` + +## 算法详解 + +### 地图元素说明 + +- **`s`** - 起点 (Start) +- **`e`** - 终点 (End) +- **`g数字`** - 金币资源,数字表示价值(如 `g5` = 价值5的金币) +- **`t数字`** - 陷阱资源,数字表示损失(如 `t3` = 损失3的陷阱) +- **`0`** - 可通行的路径 +- **`1`** - 墙壁,无法通过 +- **`l`** - 锁/机关 +- **`b`** - Boss + +### 算法流程 + +1. **初始化**:从起点开始,初始化路径和收集状态 +2. **视野扫描**:获取当前位置3x3范围内的所有单元格 +3. **资源评估**: + - 优先选择正价值资源(金币) + - 如果没有正价值资源,选择损失最小的负价值资源(陷阱) +4. **移动决策**: + - 如果找到资源,移动到该位置并收集 + - 如果没有资源,进行探索性移动到未访问的位置 +5. **循环执行**:重复步骤2-4直到满足停止条件 + +### 停止条件 + +- 达到最大移动步数(默认1000步) +- 连续多步(默认20步)无法找到新资源 +- 无法进行任何移动 + +## 算法参数 + +### Greedy3x3Algorithm 参数 + +```python +algorithm = Greedy3x3Algorithm( + maze, # 必需:迷宫地图 + start_pos=None, # 可选:起始位置(x,y),默认自动寻找 + end_pos=None, # 可选:目标位置(x,y),默认自动寻找 + debug=False # 可选:是否输出调试信息 +) + +result = algorithm.run( + max_moves=1000, # 最大移动步数 + max_stuck=20 # 连续无资源的最大步数 +) +``` + +## 结果格式 + +算法执行后返回的结果字典包含: + +```python +{ + 'path': [(x1,y1), (x2,y2), ...], # 移动路径 (x,y格式) + 'path_yx_format': [(y1,x1), (y2,x2), ...], # 移动路径 (y,x格式,兼容现有代码) + 'collected_resources': [ # 收集的资源详情 + { + 'position': (x, y), + 'type': 'g5', + 'value': 5 + }, + ... + ], + 'total_value': 15, # 资源总价值 + 'total_moves': 25, # 总移动步数 + 'resources_count': 8, # 收集资源数量 + 'start_pos': (0, 0), # 起始位置 + 'end_pos': (4, 4), # 目标位置 + 'final_pos': (2, 3), # 最终位置 + 'explored_positions_count': 15, # 探索位置数量 + 'algorithm_name': '3x3视野贪心算法' +} +``` + +## 性能特点 + +### 优势 + +1. **局部最优**:在有限视野内做出最优决策 +2. **探索能力**:能够智能探索未知区域 +3. **资源优先**:优先收集高价值资源,避免低价值陷阱 +4. **防死循环**:具备多种机制防止无限循环 + +### 限制 + +1. **视野限制**:可能错过视野外的高价值资源 +2. **局部陷阱**:可能陷入局部最优解 +3. **路径效率**:为了收集资源,路径可能不是最短的 + +## 测试用例 + +项目包含多个测试迷宫: + +### 简单测试迷宫 + +```python +simple_maze = [ + ['s', '0', 'g5', '1', 't3'], + ['0', '1', '0', '0', 'g2'], + ['g3', '0', '1', 't2', '0'], + ['0', 't1', '0', '0', 'g4'], + ['1', '0', 'g1', '0', 'e'] +] +``` + +### 复杂测试迷宫 + +```python +complex_maze = [ + ['s', '0', 'g5', '1', 't3', '0', 'g8'], + ['0', '1', '0', '0', 'g2', '1', '0'], + ['g3', '0', '1', 't2', '0', '0', 'g6'], + ['0', 't1', '0', '0', 'g4', '1', '0'], + ['1', '0', 'g1', '0', '0', '0', 't5'], + ['0', 'g7', '0', '1', '0', 'g9', '0'], + ['t4', '0', '0', '0', '1', '0', 'e'] +] +``` + +## 扩展开发 + +### 自定义评估函数 + +可以通过继承 `Greedy3x3Algorithm` 类来自定义资源评估逻辑: + +```python +class CustomGreedy3x3(Greedy3x3Algorithm): + def _evaluate_resource_value(self, cell): + # 自定义评估逻辑 + if cell.startswith('special'): + return 100 # 特殊资源高价值 + return super()._evaluate_resource_value(cell) +``` + +### 自定义移动策略 + +```python +class CustomGreedy3x3(Greedy3x3Algorithm): + def _find_exploration_target(self): + # 自定义探索策略 + adjacent = self.get_adjacent_positions(self.current_pos) + # 选择最靠近终点的位置 + if adjacent: + return min(adjacent, key=lambda pos: + abs(pos[0] - self.end_pos[0]) + abs(pos[1] - self.end_pos[1])) + return None +``` + +## 常见问题 + +### Q: 算法为什么会在某些位置反复移动? + +A: 这是因为算法在视野内找不到新资源时,会进行探索性移动。连续20步无资源后会自动停止。 + +### Q: 如何提高算法的收集效率? + +A: 可以调整参数: +- 减小 `max_stuck` 参数,减少无效探索 +- 增加迷宫中的资源密度 +- 优化迷宫布局,减少死胡同 + +### Q: 算法支持什么样的迷宫格式? + +A: 支持二维列表格式,使用 `[y][x]` 索引方式。单元格内容为字符串。 + +### Q: 如何集成到现有项目? + +A: 可以使用 `tanxin.py` 中的 `Greedy3x3ResourceCollector` 类,它与现有代码兼容。 + +## 版本信息 + +- **版本**: 1.0 +- **作者**: GitHub Copilot +- **创建日期**: 2025年1月 +- **Python版本**: 3.6+ +- **依赖**: 无额外依赖,使用Python标准库 diff --git a/config.py b/config.py index d9b9a88..d36ba58 100644 --- a/config.py +++ b/config.py @@ -57,6 +57,9 @@ def get_button_positions(maze_display_size=MAZE_SIZE): 'next_step_button': (control_panel_x, 200), 'reset_path_button': (control_panel_x + 120, 200), 'auto_play_button': (control_panel_x + 250, 200), + # 贪心搜索按钮 + 'greedy_search_button': (control_panel_x + 380, 200), + 'greedy_auto_play_button': (control_panel_x + 500, 200), # 历史迭代控制按钮 'history_prev_button': (control_panel_x, 260), 'history_next_button': (control_panel_x + 120, 260), @@ -67,5 +70,6 @@ def get_button_positions(maze_display_size=MAZE_SIZE): 'history_progress_text': (control_panel_x, 350), 'hint_text': (control_panel_x, 380), 'shortcut_text': (control_panel_x, 410), - 'save_list_area': (control_panel_x, 440, 400, 330) # x, y, width, height - 增加高度到330 + 'greedy_info_text': (control_panel_x, 440), # 贪心算法信息显示位置 + 'save_list_area': (control_panel_x, 470, 400, 300) # x, y, width, height - 调整位置和高度 } diff --git a/greedy_3x3_algorithm.py b/greedy_3x3_algorithm.py new file mode 100644 index 0000000..d423bbc --- /dev/null +++ b/greedy_3x3_algorithm.py @@ -0,0 +1,380 @@ +""" +3x3视野贪心资源收集算法 + +该模块实现了一个基于3x3视野的贪心算法,用于在迷宫中收集资源。 +算法特点: +1. 每次移动时只考虑当前位置周围3x3范围内的资源 +2. 优先选择正价值资源(金币),如果没有则选择损失最小的负价值资源(陷阱) +3. 只能进行上下左右四个方向的移动 +4. 当视野内没有资源时,会进行探索性移动 +5. 避免无限循环,当连续多步无法找到新资源时会自动停止 + +使用方法: +```python +from greedy_3x3_algorithm import Greedy3x3Algorithm + +# 创建迷宫(二维数组,[y][x]格式) +maze = [ + ['s', '0', 'g5', '1', 't3'], + ['0', '1', '0', '0', 'g2'], + ['g3', '0', '1', 't2', '0'], + ['0', 't1', '0', '0', 'g4'], + ['1', '0', 'g1', '0', 'e'] +] + +# 创建算法实例并运行 +algorithm = Greedy3x3Algorithm(maze) +result = algorithm.run() + +# 获取结果 +print(f"总价值: {result['total_value']}") +print(f"路径: {result['path']}") +``` +""" + +import copy +from collections import deque + + +class Greedy3x3Algorithm: + """ + 3x3视野贪心资源收集算法 + + 该算法在每个位置只考虑周围3x3范围内的资源,选择价值最高的资源进行收集。 + 如果视野内没有资源,则进行探索性移动。 + """ + + def __init__(self, maze, start_pos=None, end_pos=None, debug=False): + """ + 初始化算法 + + Args: + maze: 二维列表,表示迷宫地图 ([y][x]格式) + start_pos: 起始位置 (x, y),默认自动寻找's' + end_pos: 目标位置 (x, y),默认自动寻找'e' + debug: 是否输出调试信息 + """ + self.original_maze = copy.deepcopy(maze) + self.maze = copy.deepcopy(maze) + self.rows = len(maze) + self.cols = len(maze[0]) if self.rows > 0 else 0 + self.debug = debug + + # 寻找起始位置和目标位置 + self.start_pos = start_pos or self._find_position('s') + self.end_pos = end_pos or self._find_position('e') + + if not self.start_pos: + raise ValueError("无法找到起始位置 's'") + if not self.end_pos: + raise ValueError("无法找到目标位置 'e'") + + # 初始化状态 + self.current_pos = self.start_pos + self.path = [self.start_pos] + self.collected_resources = [] + self.total_value = 0 + self.visited_resources = set() + self.explored_positions = set([self.start_pos]) + + if self.debug: + print(f"3x3视野贪心算法初始化") + print(f"迷宫大小: {self.rows}x{self.cols}") + print(f"起始位置: {self.start_pos}") + print(f"目标位置: {self.end_pos}") + + def _find_position(self, target): + """寻找地图中指定字符的位置,返回(x, y)格式""" + for y in range(self.rows): + for x in range(self.cols): + if self.maze[y][x].lower() == target.lower(): + return (x, y) + return None + + def get_3x3_vision(self, pos): + """ + 获取以pos为中心的3x3视野范围内的所有单元格 + + Args: + pos: 当前位置 (x, y) + + Returns: + dict: {(x, y): cell_value} 形式的字典 + """ + x, y = pos + vision = {} + + # 遍历3x3范围 + for dx in range(-1, 2): + for dy in range(-1, 2): + new_x, new_y = x + dx, y + dy + + # 检查边界 + if 0 <= new_x < self.cols and 0 <= new_y < self.rows: + vision[(new_x, new_y)] = self.maze[new_y][new_x] + + return vision + + def get_adjacent_positions(self, pos): + """ + 获取当前位置的上下左右四个相邻位置 + + Args: + pos: 当前位置 (x, y) + + Returns: + list: 可移动的相邻位置列表 + """ + x, y = pos + adjacent = [] + + # 上下左右四个方向 + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # 上、下、左、右 + + for dx, dy in directions: + new_x, new_y = x + dx, y + dy + + # 检查边界和可移动性 + if (0 <= new_x < self.cols and + 0 <= new_y < self.rows and + self._can_move_to((new_x, new_y))): + adjacent.append((new_x, new_y)) + + return adjacent + + def _can_move_to(self, pos): + """检查是否可以移动到指定位置""" + x, y = pos + cell = self.maze[y][x] + return cell != '1' # 不能移动到墙壁 + + def _evaluate_resource_value(self, cell): + """评估资源的价值""" + if cell.startswith('g'): + try: + return int(cell[1:]) + except ValueError: + return 0 + elif cell.startswith('t'): + try: + return -int(cell[1:]) + except ValueError: + return 0 + else: + return 0 + + def _find_best_resource_in_vision(self): + """ + 在3x3视野范围内找到价值最高的可到达资源 + + Returns: + tuple: (最佳资源位置, 资源价值) 或 (None, 0) + """ + vision = self.get_3x3_vision(self.current_pos) + adjacent_positions = self.get_adjacent_positions(self.current_pos) + + best_pos = None + best_value = float('-inf') + + # 只考虑相邻且在视野内的位置 + for pos in adjacent_positions: + if pos in vision and pos not in self.visited_resources: + cell = vision[pos] + value = self._evaluate_resource_value(cell) + + if value != 0 and value > best_value: + best_value = value + best_pos = pos + + return best_pos, best_value if best_pos else 0 + + def _find_exploration_target(self): + """当视野内没有资源时,寻找探索目标""" + adjacent = self.get_adjacent_positions(self.current_pos) + + # 优先选择未探索的位置 + unexplored = [pos for pos in adjacent if pos not in self.explored_positions] + if unexplored: + return unexplored[0] + + # 如果所有相邻位置都探索过,选择任意一个 + if adjacent: + return adjacent[0] + + return None + + def _collect_resource(self, pos): + """收集指定位置的资源""" + x, y = pos + cell = self.maze[y][x] + value = self._evaluate_resource_value(cell) + + if value != 0: + self.collected_resources.append({ + 'position': pos, + 'type': cell, + 'value': value + }) + self.total_value += value + self.visited_resources.add(pos) + + if self.debug: + print(f"收集资源: 位置{pos}, 类型{cell}, 价值{value}, 总价值{self.total_value}") + + def run(self, max_moves=1000, max_stuck=20): + """ + 运行3x3视野贪心资源收集算法 + + Args: + max_moves: 最大移动步数,防止无限循环 + max_stuck: 连续无资源的最大步数 + + Returns: + dict: 包含路径、收集的资源等信息的结果字典 + """ + if self.debug: + print("\\n开始3x3视野贪心资源收集...") + + moves = 0 + stuck_count = 0 + + while moves < max_moves and stuck_count < max_stuck: + moves += 1 + + # 在3x3视野内寻找最佳资源 + best_resource_pos, best_value = self._find_best_resource_in_vision() + + if best_resource_pos is not None: + if self.debug: + print(f"第{moves}步: 发现视野内资源 位置{best_resource_pos}, 价值{best_value}") + + # 移动到资源位置并收集 + self.current_pos = best_resource_pos + self.path.append(best_resource_pos) + self.explored_positions.add(best_resource_pos) + self._collect_resource(best_resource_pos) + + stuck_count = 0 # 重置无资源计数 + else: + # 视野内没有资源,进行探索性移动 + exploration_target = self._find_exploration_target() + + if exploration_target: + if self.debug: + print(f"第{moves}步: 视野内无资源,探索移动到 {exploration_target}") + self.current_pos = exploration_target + self.path.append(exploration_target) + self.explored_positions.add(exploration_target) + stuck_count += 1 + else: + if self.debug: + print(f"第{moves}步: 无法进行任何移动,结束收集") + break + + if self.debug: + if moves >= max_moves: + print(f"达到最大移动步数 {max_moves},结束收集") + elif stuck_count >= max_stuck: + print(f"连续 {max_stuck} 步未找到资源,结束收集") + print("3x3视野资源收集完成!") + + return self._get_result() + + def _get_result(self): + """获取算法执行结果""" + return { + 'path': self.path.copy(), + 'path_yx_format': [(y, x) for (x, y) in self.path], # 兼容现有代码的(y,x)格式 + 'collected_resources': self.collected_resources.copy(), + 'total_value': self.total_value, + 'total_moves': len(self.path) - 1, + 'resources_count': len(self.collected_resources), + 'start_pos': self.start_pos, + 'end_pos': self.end_pos, + 'final_pos': self.current_pos, + 'explored_positions_count': len(self.explored_positions), + 'algorithm_name': '3x3视野贪心算法' + } + + def get_marked_maze(self): + """ + 获取标记了路径的迷宫 + + Returns: + list: 标记后的迷宫,S=起点, E=终点, *=已收集资源, .=路径 + """ + marked_maze = copy.deepcopy(self.original_maze) + + # 标记路径点 + for i, (x, y) in enumerate(self.path): + if (x, y) == self.start_pos: + marked_maze[y][x] = 'S' # 起点 + elif (x, y) == self.end_pos: + marked_maze[y][x] = 'E' # 终点 + elif (x, y) in [r['position'] for r in self.collected_resources]: + marked_maze[y][x] = '*' # 已收集资源 + else: + marked_maze[y][x] = '.' # 路径点 + + return marked_maze + + def print_result(self): + """打印算法执行结果""" + result = self._get_result() + + print("\\n=== 3x3视野贪心算法执行结果 ===") + print(f"起始位置: {result['start_pos']}") + print(f"最终位置: {result['final_pos']}") + print(f"总移动步数: {result['total_moves']}") + print(f"探索位置数: {result['explored_positions_count']}") + print(f"收集资源数量: {result['resources_count']}") + print(f"资源总价值: {result['total_value']}") + + if result['collected_resources']: + print("\\n收集的资源详情:") + for i, resource in enumerate(result['collected_resources'], 1): + print(f" {i}. 位置{resource['position']}: {resource['type']} (价值: {resource['value']})") + else: + print("\\n未收集到任何资源") + + # 显示标记后的迷宫 + marked_maze = self.get_marked_maze() + print("\\n标记路径后的迷宫:") + print("S: 起点, E: 终点, *: 已收集资源, .: 路径") + for row in marked_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + +def demo(): + """演示函数""" + # 创建示例迷宫 + demo_maze = [ + ['s', '0', 'g5', '1', 't3'], + ['0', '1', '0', '0', 'g2'], + ['g3', '0', '1', 't2', '0'], + ['0', 't1', '0', '0', 'g4'], + ['1', '0', 'g1', '0', 'e'] + ] + + print("=== 3x3视野贪心算法演示 ===") + print("迷宫说明:") + print(" s: 起点, e: 终点") + print(" g数字: 金币资源 (正收益)") + print(" t数字: 陷阱资源 (负收益)") + print(" 0: 可通行路径, 1: 墙壁") + print("\\n原始迷宫:") + for row in demo_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + # 运行算法 + algorithm = Greedy3x3Algorithm(demo_maze, debug=True) + result = algorithm.run() + + # 打印结果 + algorithm.print_result() + + return algorithm, result + + +if __name__ == "__main__": + demo() diff --git a/greedy_resource_collector.py b/greedy_resource_collector.py new file mode 100644 index 0000000..0095a7d --- /dev/null +++ b/greedy_resource_collector.py @@ -0,0 +1,448 @@ +import copy +from collections import deque + +class GreedyResourceCollector: + """ + 基于贪心算法的资源收集器 + 每次移动时选择3x3视野范围内最高价值的资源 + 只能进行上下左右移动 + """ + + def __init__(self, maze, start_pos=None, end_pos=None): + """ + 初始化贪心资源收集器 + + Args: + maze: 迷宫地图,2D列表 + start_pos: 起始位置 (row, col),如果为None则自动寻找 + end_pos: 目标位置 (row, col),如果为None则自动寻找 + """ + self.original_maze = copy.deepcopy(maze) + self.maze = copy.deepcopy(maze) + self.rows = len(maze) + self.cols = len(maze[0]) if self.rows > 0 else 0 + + # 寻找起始位置和目标位置 + self.start_pos = start_pos or self._find_position('s') + self.end_pos = end_pos or self._find_position('e') + + if not self.start_pos: + raise ValueError("无法找到起始位置 's'") + if not self.end_pos: + raise ValueError("无法找到目标位置 'e'") + + self.current_pos = self.start_pos + self.path = [self.start_pos] + self.collected_resources = [] + self.total_value = 0 + self.visited_resources = set() + + print(f"起始位置: {self.start_pos}") + print(f"目标位置: {self.end_pos}") + + def _find_position(self, target): + """寻找地图中指定字符的位置""" + for i in range(self.rows): + for j in range(self.cols): + if self.maze[i][j].lower() == target.lower(): + return (i, j) + return None + + def get_3x3_vision(self, pos): + """ + 获取以pos为中心的3x3视野范围内的所有单元格 + + Args: + pos: 当前位置 (row, col) + + Returns: + dict: {(row, col): cell_value} 形式的字典 + """ + row, col = pos + vision = {} + + # 遍历3x3范围 + for dr in range(-1, 2): + for dc in range(-1, 2): + new_row, new_col = row + dr, col + dc + + # 检查边界 + if 0 <= new_row < self.rows and 0 <= new_col < self.cols: + vision[(new_row, new_col)] = self.maze[new_row][new_col] + + return vision + + def get_adjacent_cells(self, pos): + """ + 获取当前位置的上下左右四个相邻位置 + + Args: + pos: 当前位置 (row, col) + + Returns: + list: 可移动的相邻位置列表 + """ + row, col = pos + adjacent = [] + + # 上下左右四个方向 + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + + for dr, dc in directions: + new_row, new_col = row + dr, col + dc + + # 检查边界和可移动性 + if (0 <= new_row < self.rows and + 0 <= new_col < self.cols and + self.can_move_to((new_row, new_col))): + adjacent.append((new_row, new_col)) + + return adjacent + + def can_move_to(self, pos): + """ + 检查是否可以移动到指定位置 + + Args: + pos: 目标位置 (row, col) + + Returns: + bool: 是否可以移动 + """ + row, col = pos + cell = self.maze[row][col] + + # 不能移动到墙壁 + if cell == '1': + return False + + return True + + def evaluate_resource_value(self, cell): + """ + 评估资源的价值 + + Args: + cell: 单元格内容 + + Returns: + int: 资源价值,正数表示收益,负数表示损失 + """ + if cell.startswith('g'): + # 金币资源,提取数值 + try: + return int(cell[1:]) + except ValueError: + return 0 + elif cell.startswith('t'): + # 陷阱资源,提取数值并取负 + try: + return -int(cell[1:]) + except ValueError: + return 0 + else: + # 其他类型单元格没有资源价值 + return 0 + + def find_best_resource_in_vision(self): + """ + 在3x3视野范围内找到价值最高的可到达资源 + 如果视野内没有正价值资源,则考虑移动到更有利的位置 + + Returns: + tuple: (最佳资源位置, 资源价值) 或 (None, 0) + """ + vision = self.get_3x3_vision(self.current_pos) + + best_pos = None + best_value = float('-inf') + + # 首先只考虑正价值资源 + for pos, cell in vision.items(): + # 跳过已访问的资源 + if pos in self.visited_resources: + continue + + # 跳过当前位置 + if pos == self.current_pos: + continue + + # 跳过不可移动的位置 + if not self.can_move_to(pos): + continue + + # 检查是否为资源 + value = self.evaluate_resource_value(cell) + if value > 0 and value > best_value: # 只考虑正价值资源 + # 确保可以到达这个位置 + path_to_resource = self.find_path_to_target(pos) + if path_to_resource: # 可以到达 + best_value = value + best_pos = pos + + # 如果视野内没有正价值资源,考虑负价值资源(陷阱) + if best_pos is None: + for pos, cell in vision.items(): + if pos in self.visited_resources or pos == self.current_pos: + continue + if not self.can_move_to(pos): + continue + + value = self.evaluate_resource_value(cell) + if value < 0 and value > best_value: # 选择损失最小的陷阱 + path_to_resource = self.find_path_to_target(pos) + if path_to_resource: + best_value = value + best_pos = pos + + # 如果视野内完全没有资源,寻找最近的正价值资源 + if best_pos is None: + best_pos, best_value = self.find_nearest_valuable_resource() + + return best_pos, best_value if best_pos else 0 + + def find_nearest_valuable_resource(self): + """ + 在整个地图上寻找最近的高价值正资源 + + Returns: + tuple: (最佳资源位置, 资源价值) 或 (None, 0) + """ + best_pos = None + best_score = float('-inf') + + for i in range(self.rows): + for j in range(self.cols): + pos = (i, j) + cell = self.maze[i][j] + + # 跳过已访问的资源 + if pos in self.visited_resources: + continue + + # 跳过当前位置 + if pos == self.current_pos: + continue + + # 检查是否为正价值资源 + value = self.evaluate_resource_value(cell) + if value > 0: # 只考虑正价值资源 + # 计算到达该资源的路径 + path_to_resource = self.find_path_to_target(pos) + if path_to_resource: + # 计算性价比:价值/距离 + distance = len(path_to_resource) + score = value / distance + + if score > best_score: + best_score = score + best_pos = pos + + if best_pos: + value = self.evaluate_resource_value(self.maze[best_pos[0]][best_pos[1]]) + return best_pos, value + + return None, 0 + + def find_path_to_target(self, target_pos): + """ + 使用BFS找到到目标位置的最短路径 + + Args: + target_pos: 目标位置 (row, col) + + Returns: + list: 从当前位置到目标位置的路径(不包含当前位置) + """ + if self.current_pos == target_pos: + return [] + + queue = deque([(self.current_pos, [])]) + visited = {self.current_pos} + + while queue: + pos, path = queue.popleft() + + # 获取相邻位置 + for next_pos in self.get_adjacent_cells(pos): + if next_pos in visited: + continue + + new_path = path + [next_pos] + + # 找到目标 + if next_pos == target_pos: + return new_path + + visited.add(next_pos) + queue.append((next_pos, new_path)) + + return [] # 无法到达 + + def collect_resource(self, pos): + """ + 收集指定位置的资源 + + Args: + pos: 资源位置 (row, col) + """ + row, col = pos + cell = self.maze[row][col] + value = self.evaluate_resource_value(cell) + + if value != 0: + self.collected_resources.append({ + 'position': pos, + 'type': cell, + 'value': value + }) + self.total_value += value + self.visited_resources.add(pos) + + print(f"收集资源: 位置{pos}, 类型{cell}, 价值{value}, 总价值{self.total_value}") + + def run_greedy_collection(self): + """ + 运行贪心资源收集算法 + + Returns: + dict: 包含路径、收集的资源等信息 + """ + print("开始贪心资源收集...") + + while True: + # 在3x3视野内寻找最佳资源 + best_resource_pos, best_value = self.find_best_resource_in_vision() + + if best_resource_pos is None: + print("视野内没有更多资源可收集") + break + + print(f"发现最佳资源: 位置{best_resource_pos}, 价值{best_value}") + + # 计算到最佳资源的路径 + path_to_resource = self.find_path_to_target(best_resource_pos) + + if not path_to_resource: + print(f"无法到达资源位置{best_resource_pos}") + self.visited_resources.add(best_resource_pos) # 标记为无法到达 + continue + + # 移动到资源位置 + for next_pos in path_to_resource: + self.current_pos = next_pos + self.path.append(next_pos) + print(f"移动到: {next_pos}") + + # 收集资源 + self.collect_resource(best_resource_pos) + + print("资源收集完成!") + return self.get_collection_result() + + def get_collection_result(self): + """ + 获取收集结果 + + Returns: + dict: 包含路径、资源、统计信息的字典 + """ + return { + 'path': self.path.copy(), + 'collected_resources': self.collected_resources.copy(), + 'total_value': self.total_value, + 'total_moves': len(self.path) - 1, + 'resources_count': len(self.collected_resources), + 'start_pos': self.start_pos, + 'end_pos': self.end_pos, + 'final_pos': self.current_pos + } + + def print_result_summary(self): + """打印收集结果摘要""" + result = self.get_collection_result() + + print("\n=== 贪心资源收集结果摘要 ===") + print(f"起始位置: {result['start_pos']}") + print(f"最终位置: {result['final_pos']}") + print(f"总移动步数: {result['total_moves']}") + print(f"收集资源数量: {result['resources_count']}") + print(f"资源总价值: {result['total_value']}") + + print("\n收集的资源详情:") + for i, resource in enumerate(result['collected_resources'], 1): + print(f" {i}. 位置{resource['position']}: {resource['type']} (价值: {resource['value']})") + + print(f"\n完整移动路径: {' -> '.join(map(str, result['path']))}") + + def visualize_path_on_maze(self): + """ + 在迷宫上可视化移动路径 + + Returns: + list: 标记了路径的迷宫副本 + """ + visual_maze = copy.deepcopy(self.original_maze) + + # 标记路径 + for i, pos in enumerate(self.path): + row, col = pos + if pos == self.start_pos: + visual_maze[row][col] = 'S' # 起点 + elif pos == self.end_pos: + visual_maze[row][col] = 'E' # 终点 + elif pos in [r['position'] for r in self.collected_resources]: + # 已收集的资源位置 + visual_maze[row][col] = '*' + else: + # 路径点 + visual_maze[row][col] = str(i % 10) + + return visual_maze + + def print_visual_maze(self): + """打印可视化的迷宫""" + visual_maze = self.visualize_path_on_maze() + + print("\n=== 路径可视化迷宫 ===") + print("S: 起点, E: 终点, *: 已收集资源, 数字: 路径步骤") + for row in visual_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + +def demo_greedy_resource_collection(): + """演示贪心资源收集算法""" + + # 创建一个示例迷宫 + demo_maze = [ + ['s', '0', 'g5', '1', 't3'], + ['0', '1', '0', '0', 'g2'], + ['g3', '0', '1', 't2', '0'], + ['0', 't1', '0', '0', 'g4'], + ['1', '0', 'g1', '0', 'e'] + ] + + print("=== 贪心资源收集算法演示 ===") + print("迷宫说明:") + print(" s: 起点, e: 终点") + print(" g数字: 金币资源 (正收益)") + print(" t数字: 陷阱资源 (负收益)") + print(" 0: 可通行路径, 1: 墙壁") + print("\n原始迷宫:") + for row in demo_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + # 创建贪心收集器并运行 + collector = GreedyResourceCollector(demo_maze) + result = collector.run_greedy_collection() + + # 打印结果 + collector.print_result_summary() + collector.print_visual_maze() + + return collector, result + + +if __name__ == "__main__": + # 运行演示 + collector, result = demo_greedy_resource_collection() diff --git a/main.py b/main.py index cdf00b9..5578ada 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ from draw import Button, Toast from save_ui import SaveLoadUI from boss_fight_ui import BossFightUI from mechanism_ui import MechanismUI +from greedy_3x3_algorithm import Greedy3x3Algorithm from config import * import sys import os @@ -50,6 +51,10 @@ def create_buttons(button_positions): 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) + # 贪心搜索按钮 + button_greedy_search = Button(pygame.rect.Rect(*button_positions['greedy_search_button'], *BUTTON_CONTROL_SIZE), None) + button_greedy_auto_play = Button(pygame.rect.Rect(*button_positions['greedy_auto_play_button'], *BUTTON_CONTROL_SIZE), None) + # 历史迭代控制按钮 button_history_prev = Button(pygame.rect.Rect(*button_positions['history_prev_button'], *BUTTON_CONTROL_SIZE), None) button_history_next = Button(pygame.rect.Rect(*button_positions['history_next_button'], *BUTTON_CONTROL_SIZE), None) @@ -63,6 +68,8 @@ def create_buttons(button_positions): 'next_step': button_next_step, 'reset_path': button_reset_path, 'auto_play': button_auto_play, + 'greedy_search': button_greedy_search, + 'greedy_auto_play': button_greedy_auto_play, 'history_prev': button_history_prev, 'history_next': button_history_next, 'history_auto': button_history_auto, @@ -95,6 +102,7 @@ if __name__ == "__main__": mes4 = Toast("存档加载成功", UI_WIDTH, UI_HEIGHT, font=textFont) mes5 = Toast("加载失败", UI_WIDTH, UI_HEIGHT, font=textFont) mes_boss = Toast("遇到Boss!准备战斗!", UI_WIDTH, UI_HEIGHT, font=textFont, duration=3, color=(255, 255, 0), bg_color=(255, 0, 0)) + mes_greedy = Toast("贪心搜索完成!", UI_WIDTH, UI_HEIGHT, font=textFont, duration=3, color=(255, 255, 255), bg_color=(0, 128, 0)) # 创建存档界面 save_ui = SaveLoadUI(textFont) @@ -111,6 +119,11 @@ if __name__ == "__main__": auto_play_timer = 0 auto_play_interval = AUTO_PLAY_INTERVAL + # 贪心算法控制变量 + greedy_auto_play = False + greedy_auto_timer = 0 + greedy_auto_interval = AUTO_PLAY_INTERVAL + # 历史迭代控制变量 history_auto_play = False history_auto_timer = 0 @@ -152,6 +165,15 @@ if __name__ == "__main__": auto_play = False # 遇到机关时停止自动播放 auto_play_timer = 0 + # 贪心算法自动播放逻辑 + if greedy_auto_play and hasattr(maze, 'greedy_path') and maze.greedy_path and not maze.show_history and not boss_fight_ui.is_showing and not mechanism_ui.is_showing: + greedy_auto_timer += 1 + if greedy_auto_timer >= greedy_auto_interval: + has_next = maze.next_greedy_step() + if not has_next: + greedy_auto_play = False # 贪心路径播放完成后停止自动播放 + greedy_auto_timer = 0 + # 历史迭代自动播放逻辑 if history_auto_play and len(maze.history_mazes) > 0 and maze.show_history: history_auto_timer += 1 @@ -188,6 +210,10 @@ if __name__ == "__main__": buttons['reset_path'].handle_event(event=event) buttons['auto_play'].handle_event(event=event) + # 贪心搜索按钮 + buttons['greedy_search'].handle_event(event=event) + buttons['greedy_auto_play'].handle_event(event=event) + # 历史迭代控制按钮 buttons['history_prev'].handle_event(event=event) buttons['history_next'].handle_event(event=event) @@ -263,6 +289,18 @@ if __name__ == "__main__": auto_play = not auto_play auto_play_timer = 0 + # 贪心搜索控制 + if buttons['greedy_search'].pressed == True and len(maze.grid) > 0: + success = maze.run_greedy_search() + if success: + greedy_auto_play = False # 重新生成路径时停止自动播放 + mes_greedy.text = f"贪心搜索完成!收集{len(maze.greedy_result['collected_resources'])}个资源,总价值{maze.greedy_result['total_value']}" + mes_greedy.show() + + if buttons['greedy_auto_play'].pressed == True and hasattr(maze, 'greedy_path') and maze.greedy_path: + greedy_auto_play = not greedy_auto_play + greedy_auto_timer = 0 + # 历史迭代控制 if buttons['history_prev'].pressed == True and len(maze.history_mazes) > 0 and maze.show_history: maze.prev_history_step() @@ -326,6 +364,18 @@ if __name__ == "__main__": # Ctrl+L 打开读档界面 save_ui.update_save_list(maze) save_ui.toggle_save_list() + # 贪心算法快捷键 + elif event.key == pygame.K_g and len(maze.grid) > 0: + # G键 运行贪心搜索 + success = maze.run_greedy_search() + if success: + greedy_auto_play = False + mes_greedy.text = f"贪心搜索完成!收集{len(maze.greedy_result['collected_resources'])}个资源,总价值{maze.greedy_result['total_value']}" + mes_greedy.show() + elif event.key == pygame.K_g and pygame.key.get_pressed()[pygame.K_LSHIFT] and hasattr(maze, 'greedy_path') and maze.greedy_path: + # Shift+G 贪心自动播放 + greedy_auto_play = not greedy_auto_play + greedy_auto_timer = 0 if event.type == QUIT: running = False @@ -355,6 +405,25 @@ if __name__ == "__main__": # 显示当前步数信息 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.grid) > 0: + # 绘制按钮背景 + pygame.draw.rect(screen, COLOR_GOLD, buttons['greedy_search'].rect) + + # 绘制按钮文字 + greedy_text = textFont.render("贪心搜索", True, COLOR_BLACK) + screen.blit(greedy_text, (buttons['greedy_search'].rect.x + 5, buttons['greedy_search'].rect.y + 15)) + + # 绘制贪心自动播放按钮(仅在有贪心路径时显示) + if hasattr(maze, 'greedy_path') and maze.greedy_path: + pygame.draw.rect(screen, COLOR_GREEN if greedy_auto_play else COLOR_GRAY, buttons['greedy_auto_play'].rect) + greedy_auto_text = textFont.render("贪心播放" if not greedy_auto_play else "停止", True, COLOR_BLACK) + screen.blit(greedy_auto_text, (buttons['greedy_auto_play'].rect.x + 5, buttons['greedy_auto_play'].rect.y + 15)) + + # 显示贪心算法信息 + greedy_info_text = textFont.render(maze.get_greedy_progress(), True, COLOR_BLACK) + screen.blit(greedy_info_text, button_positions['greedy_info_text']) # 绘制历史迭代控制按钮(仅在有历史且为历史模式时显示) if len(maze.history_mazes) > 0 and maze.show_history: @@ -384,11 +453,14 @@ if __name__ == "__main__": # 显示操作提示 if len(maze.full_path) > 0 and not maze.show_history: - hint_text = textFont.render("空格键: 下一步 | R键: 重置 | A键: 自动播放", True, COLOR_LIGHT_GRAY) + hint_text = textFont.render("空格键: 下一步 | R键: 重置 | A键: 自动播放 | G键: 贪心搜索", True, COLOR_LIGHT_GRAY) screen.blit(hint_text, button_positions['hint_text']) elif len(maze.history_mazes) > 0 and maze.show_history: hint_text = textFont.render("←→键: 历史步骤 | P键: 自动播放 | H键: 切换模式", True, COLOR_LIGHT_GRAY) screen.blit(hint_text, button_positions['hint_text']) + elif len(maze.grid) > 0: + hint_text = textFont.render("G键: 运行贪心搜索 | Shift+G: 贪心自动播放", True, COLOR_LIGHT_GRAY) + screen.blit(hint_text, button_positions['hint_text']) # 显示快捷键提示 shortcut_text = textFont.render("Ctrl+S: 保存JSON | Shift+S: 保存CSV | Ctrl+L: 读档", True, COLOR_LIGHT_GRAY) @@ -400,6 +472,7 @@ if __name__ == "__main__": mes4.draw(screen=screen) mes5.draw(screen=screen) mes_boss.draw(screen=screen) + mes_greedy.draw(screen=screen) # 绘制存档界面(必须在最后绘制以显示在最上层) save_ui.draw(screen) diff --git a/maze.py b/maze.py index a04f455..3c78088 100644 --- a/maze.py +++ b/maze.py @@ -3,11 +3,12 @@ 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 = "/home/guan/dev/python_dev/maze_python/saves/input/input_case_1.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 @@ -359,6 +360,10 @@ class Maze: # 绘制迷宫边界线(动态位置) 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): """切换历史展示模式""" @@ -414,8 +419,125 @@ class Maze: 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) diff --git a/strict_3x3_greedy.py b/strict_3x3_greedy.py new file mode 100644 index 0000000..2036492 --- /dev/null +++ b/strict_3x3_greedy.py @@ -0,0 +1,337 @@ +import copy +from collections import deque + +class Strict3x3GreedyCollector: + """ + 严格的3x3视野贪心资源收集器 + 每次移动时只考虑3x3视野范围内的资源 + 如果视野内没有资源,则随机移动探索 + """ + + def __init__(self, maze, start_pos=None, end_pos=None): + """初始化收集器""" + self.original_maze = copy.deepcopy(maze) + self.maze = copy.deepcopy(maze) + self.rows = len(maze) + self.cols = len(maze[0]) if self.rows > 0 else 0 + + # 寻找起始位置和目标位置 + self.start_pos = start_pos or self._find_position('s') + self.end_pos = end_pos or self._find_position('e') + + if not self.start_pos: + raise ValueError("无法找到起始位置 's'") + if not self.end_pos: + raise ValueError("无法找到目标位置 'e'") + + self.current_pos = self.start_pos + self.path = [self.start_pos] + self.collected_resources = [] + self.total_value = 0 + self.visited_resources = set() + self.explored_positions = set([self.start_pos]) + + print(f"严格3x3视野模式") + print(f"起始位置: {self.start_pos}") + print(f"目标位置: {self.end_pos}") + + def _find_position(self, target): + """寻找地图中指定字符的位置""" + for i in range(self.rows): + for j in range(self.cols): + if self.maze[i][j].lower() == target.lower(): + return (i, j) + return None + + def get_3x3_vision(self, pos): + """获取以pos为中心的3x3视野范围内的所有单元格""" + row, col = pos + vision = {} + + # 遍历3x3范围 + for dr in range(-1, 2): + for dc in range(-1, 2): + new_row, new_col = row + dr, col + dc + + # 检查边界 + if 0 <= new_row < self.rows and 0 <= new_col < self.cols: + vision[(new_row, new_col)] = self.maze[new_row][new_col] + + return vision + + def get_adjacent_cells(self, pos): + """获取当前位置的上下左右四个相邻位置""" + row, col = pos + adjacent = [] + + # 上下左右四个方向 + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + + for dr, dc in directions: + new_row, new_col = row + dr, col + dc + + # 检查边界和可移动性 + if (0 <= new_row < self.rows and + 0 <= new_col < self.cols and + self.can_move_to((new_row, new_col))): + adjacent.append((new_row, new_col)) + + return adjacent + + def can_move_to(self, pos): + """检查是否可以移动到指定位置""" + row, col = pos + cell = self.maze[row][col] + # 不能移动到墙壁 + return cell != '1' + + def evaluate_resource_value(self, cell): + """评估资源的价值""" + if cell.startswith('g'): + try: + return int(cell[1:]) + except ValueError: + return 0 + elif cell.startswith('t'): + try: + return -int(cell[1:]) + except ValueError: + return 0 + else: + return 0 + + def find_best_resource_in_3x3_vision(self): + """ + 严格在3x3视野范围内找到价值最高的资源 + + Returns: + tuple: (最佳资源位置, 资源价值) 或 (None, 0) + """ + vision = self.get_3x3_vision(self.current_pos) + + best_pos = None + best_value = float('-inf') + + for pos, cell in vision.items(): + # 跳过已访问的资源 + if pos in self.visited_resources: + continue + + # 跳过当前位置 + if pos == self.current_pos: + continue + + # 跳过不可移动的位置 + if not self.can_move_to(pos): + continue + + # 检查是否可以直接到达(相邻位置) + if pos not in self.get_adjacent_cells(self.current_pos): + continue + + # 检查是否为资源 + value = self.evaluate_resource_value(cell) + if value != 0 and value > best_value: + best_value = value + best_pos = pos + + return best_pos, best_value if best_pos else 0 + + def find_exploration_target(self): + """ + 当视野内没有资源时,寻找探索目标 + 优先选择未探索过的位置 + """ + adjacent = self.get_adjacent_cells(self.current_pos) + + # 优先选择未探索的位置 + unexplored = [pos for pos in adjacent if pos not in self.explored_positions] + if unexplored: + return unexplored[0] # 选择第一个未探索的位置 + + # 如果所有相邻位置都探索过,选择任意一个 + if adjacent: + return adjacent[0] + + return None + + def collect_resource(self, pos): + """收集指定位置的资源""" + row, col = pos + cell = self.maze[row][col] + value = self.evaluate_resource_value(cell) + + if value != 0: + self.collected_resources.append({ + 'position': pos, + 'type': cell, + 'value': value + }) + self.total_value += value + self.visited_resources.add(pos) + + print(f"收集资源: 位置{pos}, 类型{cell}, 价值{value}, 总价值{self.total_value}") + + def run_strict_3x3_collection(self, max_moves=1000): + """ + 运行严格3x3视野贪心资源收集算法 + + Args: + max_moves: 最大移动步数,防止无限循环 + + Returns: + dict: 包含路径、收集的资源等信息 + """ + print("\\n开始严格3x3视野贪心资源收集...") + + moves = 0 + stuck_count = 0 # 连续无法找到资源的次数 + max_stuck = 20 # 最大连续无资源次数 + + while moves < max_moves and stuck_count < max_stuck: + moves += 1 + + # 在3x3视野内寻找最佳资源 + best_resource_pos, best_value = self.find_best_resource_in_3x3_vision() + + if best_resource_pos is not None: + print(f"第{moves}步: 发现视野内资源 位置{best_resource_pos}, 价值{best_value}") + + # 移动到资源位置并收集 + self.current_pos = best_resource_pos + self.path.append(best_resource_pos) + self.explored_positions.add(best_resource_pos) + self.collect_resource(best_resource_pos) + + stuck_count = 0 # 重置无资源计数 + else: + # 视野内没有资源,进行探索性移动 + exploration_target = self.find_exploration_target() + + if exploration_target: + print(f"第{moves}步: 视野内无资源,探索移动到 {exploration_target}") + self.current_pos = exploration_target + self.path.append(exploration_target) + self.explored_positions.add(exploration_target) + stuck_count += 1 + else: + print(f"第{moves}步: 无法进行任何移动,结束收集") + break + + if moves >= max_moves: + print(f"达到最大移动步数 {max_moves},结束收集") + elif stuck_count >= max_stuck: + print(f"连续 {max_stuck} 步未找到资源,结束收集") + + print("严格3x3视野资源收集完成!") + return self.get_collection_result() + + def get_collection_result(self): + """获取收集结果""" + return { + 'path': self.path.copy(), + 'collected_resources': self.collected_resources.copy(), + 'total_value': self.total_value, + 'total_moves': len(self.path) - 1, + 'resources_count': len(self.collected_resources), + 'start_pos': self.start_pos, + 'end_pos': self.end_pos, + 'final_pos': self.current_pos, + 'explored_positions': len(self.explored_positions) + } + + def print_result_summary(self): + """打印收集结果摘要""" + result = self.get_collection_result() + + print("\\n=== 严格3x3视野贪心收集结果摘要 ===") + print(f"起始位置: {result['start_pos']}") + print(f"最终位置: {result['final_pos']}") + print(f"总移动步数: {result['total_moves']}") + print(f"探索位置数: {result['explored_positions']}") + print(f"收集资源数量: {result['resources_count']}") + print(f"资源总价值: {result['total_value']}") + + print("\\n收集的资源详情:") + for i, resource in enumerate(result['collected_resources'], 1): + print(f" {i}. 位置{resource['position']}: {resource['type']} (价值: {resource['value']})") + + # 显示路径的关键点 + path_points = result['path'] + if len(path_points) <= 10: + path_str = ' -> '.join(map(str, path_points)) + else: + path_str = f"{path_points[0]} -> ... -> {path_points[-1]} (共{len(path_points)}个位置)" + print(f"\\n移动路径: {path_str}") + + def visualize_path_on_maze(self): + """在迷宫上可视化移动路径""" + visual_maze = copy.deepcopy(self.original_maze) + + # 标记路径 + for i, pos in enumerate(self.path): + row, col = pos + if pos == self.start_pos: + visual_maze[row][col] = 'S' # 起点 + elif pos in [r['position'] for r in self.collected_resources]: + # 已收集的资源位置 + visual_maze[row][col] = '*' + elif i == len(self.path) - 1: + # 最终位置 + visual_maze[row][col] = 'F' + else: + # 路径点 + visual_maze[row][col] = '.' + + return visual_maze + + def print_visual_maze(self): + """打印可视化的迷宫""" + visual_maze = self.visualize_path_on_maze() + + print("\\n=== 严格3x3视野路径可视化迷宫 ===") + print("S: 起点, F: 终点, *: 已收集资源, .: 路径") + for row in visual_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + +def compare_algorithms(): + """比较不同算法的效果""" + + # 创建一个更大的示例迷宫 + demo_maze = [ + ['s', '0', 'g5', '1', 't3', '0', 'g8'], + ['0', '1', '0', '0', 'g2', '1', '0'], + ['g3', '0', '1', 't2', '0', '0', 'g6'], + ['0', 't1', '0', '0', 'g4', '1', '0'], + ['1', '0', 'g1', '0', '0', '0', 't5'], + ['0', 'g7', '0', '1', '0', 'g9', '0'], + ['t4', '0', '0', '0', '1', '0', 'e'] + ] + + print("=== 算法比较演示 ===") + print("迷宫说明:") + print(" s: 起点, e: 终点") + print(" g数字: 金币资源 (正收益)") + print(" t数字: 陷阱资源 (负收益)") + print(" 0: 可通行路径, 1: 墙壁") + print("\\n原始迷宫:") + for row in demo_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + print("\\n" + "="*60) + print("严格3x3视野贪心算法:") + print("="*60) + + # 运行严格3x3视野算法 + strict_collector = Strict3x3GreedyCollector(demo_maze) + strict_result = strict_collector.run_strict_3x3_collection() + strict_collector.print_result_summary() + strict_collector.print_visual_maze() + + return strict_collector, strict_result + + +if __name__ == "__main__": + # 运行比较演示 + strict_collector, strict_result = compare_algorithms() diff --git a/tanxin.py b/tanxin.py index fc413b2..b9d3605 100644 --- a/tanxin.py +++ b/tanxin.py @@ -1,8 +1,8 @@ import math from maze import * import math - -import math +import copy +from collections import deque class GreedyPlayer: @@ -173,7 +173,338 @@ class GreedyPlayer: return self.total_reward +class Greedy3x3ResourceCollector: + """ + 基于3x3视野的贪心资源收集器 + 每次移动时选择3x3视野范围内最高价值的资源 + 只能进行上下左右移动 + """ + def __init__(self, map_data, start=None, end=None): + """ + 初始化3x3视野贪心资源收集器 + + Args: + map_data: 迷宫地图,2D列表 (注意:这里是[y][x]格式) + start: 起始位置 (x, y),如果为None则自动寻找 + end: 目标位置 (x, y),如果为None则自动寻找 + """ + self.original_map = copy.deepcopy(map_data) + self.map_data = copy.deepcopy(map_data) + self.rows = len(map_data) + self.cols = len(map_data[0]) if self.rows > 0 else 0 + + # 寻找起始位置和目标位置 + self.start = start or self._find_position('s') + self.end = end or self._find_position('e') + + if not self.start: + raise ValueError("无法找到起始位置 's'") + if not self.end: + raise ValueError("无法找到目标位置 'e'") + + self.current_pos = self.start + self.path = [self.start] + self.collected_resources = [] + self.total_value = 0 + self.visited_resources = set() + self.explored_positions = set([self.start]) + + print(f"3x3视野贪心算法初始化") + print(f"起始位置: {self.start}") + print(f"目标位置: {self.end}") + + def _find_position(self, target): + """寻找地图中指定字符的位置,返回(x, y)格式""" + for y in range(self.rows): + for x in range(self.cols): + if self.map_data[y][x].lower() == target.lower(): + return (x, y) + return None + + def get_3x3_vision(self, pos): + """ + 获取以pos为中心的3x3视野范围内的所有单元格 + + Args: + pos: 当前位置 (x, y) + + Returns: + dict: {(x, y): cell_value} 形式的字典 + """ + x, y = pos + vision = {} + + # 遍历3x3范围 + for dx in range(-1, 2): + for dy in range(-1, 2): + new_x, new_y = x + dx, y + dy + + # 检查边界 + if 0 <= new_x < self.cols and 0 <= new_y < self.rows: + vision[(new_x, new_y)] = self.map_data[new_y][new_x] + + return vision + + def get_adjacent_cells(self, pos): + """ + 获取当前位置的上下左右四个相邻位置 + + Args: + pos: 当前位置 (x, y) + + Returns: + list: 可移动的相邻位置列表 + """ + x, y = pos + adjacent = [] + + # 上下左右四个方向 + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # 上、下、左、右 + + for dx, dy in directions: + new_x, new_y = x + dx, y + dy + + # 检查边界和可移动性 + if (0 <= new_x < self.cols and + 0 <= new_y < self.rows and + self.can_move_to((new_x, new_y))): + adjacent.append((new_x, new_y)) + + return adjacent + + def can_move_to(self, pos): + """ + 检查是否可以移动到指定位置 + + Args: + pos: 目标位置 (x, y) + + Returns: + bool: 是否可以移动 + """ + x, y = pos + cell = self.map_data[y][x] + + # 不能移动到墙壁 + if cell == '1': + return False + + return True + + def evaluate_resource_value(self, cell): + """ + 评估资源的价值 + + Args: + cell: 单元格内容 + + Returns: + int: 资源价值,正数表示收益,负数表示损失 + """ + if cell.startswith('g'): + # 金币资源,提取数值 + try: + return int(cell[1:]) + except ValueError: + return 0 + elif cell.startswith('t'): + # 陷阱资源,提取数值并取负 + try: + return -int(cell[1:]) + except ValueError: + return 0 + else: + # 其他类型单元格没有资源价值 + return 0 + + def find_best_resource_in_3x3_vision(self): + """ + 在3x3视野范围内找到价值最高的可到达资源 + + Returns: + tuple: (最佳资源位置, 资源价值) 或 (None, 0) + """ + vision = self.get_3x3_vision(self.current_pos) + + best_pos = None + best_value = float('-inf') + + # 首先尝试找正价值资源 + for pos, cell in vision.items(): + # 跳过已访问的资源 + if pos in self.visited_resources: + continue + + # 跳过当前位置 + if pos == self.current_pos: + continue + + # 跳过不可移动的位置 + if not self.can_move_to(pos): + continue + + # 检查是否可以直接到达(相邻位置) + if pos not in self.get_adjacent_cells(self.current_pos): + continue + + # 检查是否为资源 + value = self.evaluate_resource_value(cell) + if value > 0 and value > best_value: # 优先选择正价值资源 + best_value = value + best_pos = pos + + # 如果没有正价值资源,考虑负价值资源(选择损失最小的) + if best_pos is None: + for pos, cell in vision.items(): + if pos in self.visited_resources or pos == self.current_pos: + continue + if not self.can_move_to(pos): + continue + if pos not in self.get_adjacent_cells(self.current_pos): + continue + + value = self.evaluate_resource_value(cell) + if value < 0 and value > best_value: # 选择损失最小的陷阱 + best_value = value + best_pos = pos + + return best_pos, best_value if best_pos else 0 + + def find_exploration_target(self): + """ + 当视野内没有资源时,寻找探索目标 + 优先选择未探索过的位置 + """ + adjacent = self.get_adjacent_cells(self.current_pos) + + # 优先选择未探索的位置 + unexplored = [pos for pos in adjacent if pos not in self.explored_positions] + if unexplored: + return unexplored[0] # 选择第一个未探索的位置 + + # 如果所有相邻位置都探索过,选择任意一个 + if adjacent: + return adjacent[0] + + return None + + def collect_resource(self, pos): + """ + 收集指定位置的资源 + + Args: + pos: 资源位置 (x, y) + """ + x, y = pos + cell = self.map_data[y][x] + value = self.evaluate_resource_value(cell) + + if value != 0: + self.collected_resources.append({ + 'position': pos, + 'type': cell, + 'value': value + }) + self.total_value += value + self.visited_resources.add(pos) + + print(f"收集资源: 位置{pos}, 类型{cell}, 价值{value}, 总价值{self.total_value}") + + def run_3x3_greedy_collection(self, max_moves=1000): + """ + 运行3x3视野贪心资源收集算法 + + Args: + max_moves: 最大移动步数,防止无限循环 + + Returns: + dict: 包含路径、收集的资源等信息 + """ + print("\\n开始3x3视野贪心资源收集...") + + moves = 0 + stuck_count = 0 # 连续无法找到资源的次数 + max_stuck = 20 # 最大连续无资源次数 + + while moves < max_moves and stuck_count < max_stuck: + moves += 1 + + # 在3x3视野内寻找最佳资源 + best_resource_pos, best_value = self.find_best_resource_in_3x3_vision() + + if best_resource_pos is not None: + print(f"第{moves}步: 发现视野内资源 位置{best_resource_pos}, 价值{best_value}") + + # 移动到资源位置并收集 + self.current_pos = best_resource_pos + self.path.append(best_resource_pos) + self.explored_positions.add(best_resource_pos) + self.collect_resource(best_resource_pos) + + stuck_count = 0 # 重置无资源计数 + else: + # 视野内没有资源,进行探索性移动 + exploration_target = self.find_exploration_target() + + if exploration_target: + print(f"第{moves}步: 视野内无资源,探索移动到 {exploration_target}") + self.current_pos = exploration_target + self.path.append(exploration_target) + self.explored_positions.add(exploration_target) + stuck_count += 1 + else: + print(f"第{moves}步: 无法进行任何移动,结束收集") + break + + if moves >= max_moves: + print(f"达到最大移动步数 {max_moves},结束收集") + elif stuck_count >= max_stuck: + print(f"连续 {max_stuck} 步未找到资源,结束收集") + + print("3x3视野资源收集完成!") + return self.get_collection_result() + + def get_collection_result(self): + """获取收集结果""" + return { + 'path': self.path.copy(), + 'collected_resources': self.collected_resources.copy(), + 'total_value': self.total_value, + 'total_moves': len(self.path) - 1, + 'resources_count': len(self.collected_resources), + 'start_pos': self.start, + 'end_pos': self.end, + 'final_pos': self.current_pos, + 'explored_positions': len(self.explored_positions) + } + + def get_path(self): + """返回路径,转换为(y, x)格式以兼容现有代码""" + # 将(x, y)格式的路径转换为(y, x)格式 + return [(y, x) for (x, y) in self.path] + + def get_total_reward(self): + """返回总收益""" + return self.total_value + + def add_path_to_map(self): + """在地图上标记路径""" + marked_map = [row.copy() for row in self.map_data] + + # 标记路径点 + for i, (x, y) in enumerate(self.path): + if marked_map[y][x] == 's': + marked_map[y][x] = 'S' # 标记起点 + elif marked_map[y][x] == 'e': + marked_map[y][x] = 'E' # 标记终点 + elif (x, y) in [r['position'] for r in self.collected_resources]: + marked_map[y][x] = '*' # 标记已收集资源 + else: + marked_map[y][x] = '.' # 标记路径点 + + self.marked_map = marked_map + return marked_map # 使用示例 @@ -191,19 +522,95 @@ def main(): map_data = obj.read_csv() see = MazeGenerator(1,filename ='demo.csv') see.read_from_csv() + print("=== 原始迷宫 ===") see.print_maze() + + print("\\n" + "="*60) + print("使用传统贪心算法:") + print("="*60) player = GreedyPlayer(map_data) player.find_path() see.maze = player.marked_map see.print_maze() + print(f"传统贪心算法总收益: {player.get_total_reward()}") + + print("\\n" + "="*60) + print("使用3x3视野贪心算法:") + print("="*60) + # 使用新的3x3视野算法 + greedy_3x3 = Greedy3x3ResourceCollector(map_data) + result = greedy_3x3.run_3x3_greedy_collection() + + # 显示结果 + see.maze = greedy_3x3.add_path_to_map() + see.print_maze() + + print(f"\\n3x3视野算法结果:") + print(f" 总移动步数: {result['total_moves']}") + print(f" 收集资源数量: {result['resources_count']}") + print(f" 资源总价值: {result['total_value']}") + print(f" 探索位置数: {result['explored_positions']}") + + print("\\n收集的资源详情:") + for i, resource in enumerate(result['collected_resources'], 1): + print(f" {i}. 位置{resource['position']}: {resource['type']} (价值: {resource['value']})") +def demo_3x3_greedy(): + """演示3x3视野贪心算法""" + + # 创建一个示例迷宫 + demo_maze = [ + ['s', '0', 'g5', '1', 't3'], + ['0', '1', '0', '0', 'g2'], + ['g3', '0', '1', 't2', '0'], + ['0', 't1', '0', '0', 'g4'], + ['1', '0', 'g1', '0', 'e'] + ] + + print("=== 3x3视野贪心算法演示 ===") + print("迷宫说明:") + print(" s: 起点, e: 终点") + print(" g数字: 金币资源 (正收益)") + print(" t数字: 陷阱资源 (负收益)") + print(" 0: 可通行路径, 1: 墙壁") + print("\\n原始迷宫:") + for row in demo_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + # 使用3x3视野贪心算法 + collector = Greedy3x3ResourceCollector(demo_maze) + result = collector.run_3x3_greedy_collection() + + # 显示标记后的迷宫 + marked_maze = collector.add_path_to_map() + print("\\n标记路径后的迷宫:") + print("S: 起点, E: 终点, *: 已收集资源, .: 路径") + for row in marked_maze: + print(' '.join(f"{cell:>2}" for cell in row)) + + print(f"\\n算法结果:") + print(f" 总移动步数: {result['total_moves']}") + print(f" 收集资源数量: {result['resources_count']}") + print(f" 资源总价值: {result['total_value']}") + + return collector, result if __name__ == "__main__": - main() + # 运行演示 + print("选择运行模式:") + print("1. 完整迷宫生成和算法比较") + print("2. 简单3x3视野算法演示") + + choice = input("请输入选择 (1 或 2,默认为 2): ").strip() + + if choice == "1": + main() + else: + demo_3x3_greedy()