init-commit

This commit is contained in:
lilinyang 2025-05-23 15:27:15 +08:00
commit 18a552597a
3461 changed files with 1150579 additions and 0 deletions

View file

@ -0,0 +1,287 @@
"""# 谜题训练场开发任务
## 任务概述
你是一位资深程序员我需要你帮我实现一个特定谜题的训练场环境类这个类继承自`Basebootcamp`用于生成谜题实例并验证解答
## 背景说明
我正在开发一系列谜题训练场每个训练场对应一个特定类型的谜题训练场类命名为`{PuzzleName}bootcamp`其中`PuzzleName`是谜题的名称
每个训练场类主要提供两个核心功能
1. 生成该谜题类型的问题实例
2. 验证用户对问题的回答是否正确
## 技术接口规范
### 类方法实现要求
```python
class {PuzzleName}bootcamp(Basebootcamp):
def __init__(self, **params):
\"\"\"
请你自定义params以保存该puzzle相关的参数例如网格大小等参数配有默认值
\"\"\"
pass
def case_generator(self):
\"\"\"
生成谜题实例提示为保证谜题有解可以先生成结果再对结果处理得到谜题
返回一个可JSON序列化的字典避免包含set等无法通过json.dumps处理的数据结构
\"\"\"
pass
@staticmethod
def prompt_func(question_case) -> str:
\"\"\"
将case_generator生成的谜题实例转换为文本形式的问题问题中包含问题背景对谜题规则的介绍具体要解决的谜题实例期望最终答案的格式
例如你是xxxx请你解答yyyy规则如下yyyy最终答案放置在zzzzz
参数:
question_case: 由case_generator生成的谜题实例
返回:
str: 格式化的问题字符串
注意:
1. 需考虑问题的格式以便后续能正确提取
2. 问题描述中应包含期望的答案格式说明以便后续能正确提取为了避免抽取时匹配出干扰项请要求模型将答案放在特定标签[answer] [/answer]
\"\"\"
pass
@staticmethod
def extract_output(output):
\"\"\"
从LLM的回复中提取符合格式要求的答案如有多个请抽取最后一个避免使用re.search等只抽取第一个结果的方式
参数:
output: LLM的完整输出包含原始问题和回答
返回:
提取的答案若未找到符合格式的答案则返回None
\"\"\"
pass
@classmethod
def _verify_correction(cls, solution, identity):
\"\"\"
验证提取的答案是否正确注意一个问题可以能有多个解按照谜题规则进行检验不要直接匹配可能的答案
参数:
solution: extract_output提取的答案
identity: case_generator生成的谜题实例
返回:
bool: 答案是否正确
\"\"\"
pass
```
### 验证评分方法(基类已实现)
```python
@classmethod
def verify_score(cls, model_output, identity:dict, format_score=0.1) -> float:
\"\"\"
验证输出结果并评分
参数:
model_output: 模型的完整输出
identity: 谜题实例由case_generator生成
format_score: 答案格式正确时的基础分数
返回:
float: 评分结果0-1之间
\"\"\"
score = 0.
try:
extract_solution = cls.extract_output(model_output)
if extract_solution is None:
return score
else:
score = format_score # 格式正确时的基础分数
if cls._verify_correction(extract_solution, identity):
score = 1. # 答案完全正确时的满分
except Exception as e:
# 处理异常情况
pass
return score
```
### 使用示例
```python
# 初始化谜题训练场
bootcamp = Puzzlebootcamp()
# 生成谜题实例
case = bootcamp.case_generator()
# 将谜题转换为文本问题
prompt = Puzzlebootcamp.prompt_func(case)
# 获取LLM对问题的解答
response = get_response(prompt, \"LLM\")
# 从完整对话中提取答案
extracted_output = Puzzlebootcamp.extract_output(prompt + response)
# 验证答案并评分
score = Puzzlebootcamp.verify_score(extracted_output, case)
```
## 你的任务
请根据以下谜题描述谜题描述可能不完整请先结合你的知识澄清规则实现一个完整的谜题训练场类
### 谜题描述
**Masyu Rules Explained:**
1. **Objective:** Create a single continuous loop that passes through all black and white circles on the grid. The loop must not intersect itself or branch, and it moves horizontally/vertically along grid lines.
2. **Black Circles ():**
- The loop **must travel straight through the black circle** (entering and exiting from opposite sides).
- **Immediately before and after** the black circle, the loop **must turn 90 degrees**. This means the path segments leading into and out of the black circle are perpendicular.
- Example: If the loop approaches a black circle from the north, it exits south, and the path must turn east/west both before entering (northeast/west) and after exiting (east/westsouth).
3. **White Circles ():**
- The loop **must turn 90 degrees at the white circle** (entering and exiting from adjacent sides, e.g., northeast).
- **Immediately before and after** the white circle, the loop **must travel straight** for at least one segment. No turns are allowed in the cells adjacent to the white circle.
- Example: If approaching a white circle from the north, the loop turns east at the circle, then continues east straight for at least one cell.
4. **General Constraints:**
- The loop occupies entire grid lines (edges), not cells.
- All circles must be traversed, and the loop may pass through empty cells as needed, but it cannot revisit any grid edge.
- No diagonals, crossings, or dead ends allowed.
请完成上述谜题的训练场环境类实现包括所有必要的方法
"""
from bootcamp import Basebootcamp
import re
from typing import List, Dict, Optional, Tuple
class MasyuV2bootcamp(Basebootcamp):
def __init__(self, rows: int = 5, cols: int = 5):
self.rows = rows
self.cols = cols
def case_generator(self) -> dict:
# 生成一个简单的固定谜题实例(示例用)
return {
'rows': self.rows,
'cols': self.cols,
'circles': [
{'position': (2, 1), 'type': 'black'},
{'position': (1, 2), 'type': 'white'},
]
}
@staticmethod
def prompt_func(question_case: dict) -> str:
circles = question_case['circles']
black = [f"({r}, {c})" for c in circles if c['type'] == 'black' for (r, c) in [c['position']]]
white = [f"({r}, {c})" for c in circles if c['type'] == 'white' for (r, c) in [c['position']]]
prompt = (
"你是一个 Masyu 谜题的解答者。请根据以下规则在网格中绘制一个闭合循环:\n"
"1. 黑圈(●)必须直行通过,且在前后立即转弯。\n"
"2. 白圈(○)必须在该处转弯,且前后直行至少一格。\n"
f"网格尺寸:{question_case['rows']}行×{question_case['cols']}\n"
f"黑圈位置:{', '.join(black)}\n"
f"白圈位置:{', '.join(white)}\n"
"答案请用 R(右), D(下), L(左), U(上) 表示移动方向并用逗号分隔例如R,D,L,U。将答案放在[answer][/answer]中。"
)
return prompt
@staticmethod
def extract_output(output: str) -> Optional[List[str]]:
pattern = r'\[answer\](.*?)\[\/answer\]'
matches = re.findall(pattern, output, re.DOTALL)
if not matches:
return None
last_match = matches[-1].strip()
directions = [d.strip().upper() for d in last_match.split(',') if d.strip()]
return directions if directions else None
@classmethod
def _verify_correction(cls, solution: List[str], identity: dict) -> bool:
try:
if not cls._is_valid_loop(solution):
return False
edges = cls._path_to_edges(solution)
for circle in identity['circles']:
r, c = circle['position']
if circle['type'] == 'black':
if not cls._check_black_circle(r, c, edges, solution):
return False
elif circle['type'] == 'white':
if not cls._check_white_circle(r, c, edges, solution):
return False
return True
except:
return False
@staticmethod
def _is_valid_loop(directions: List[str]) -> bool:
if not directions:
return False
x, y = 0, 0
visited = set()
for d in directions:
prev = (x, y)
if d == 'R': y += 1
elif d == 'L': y -= 1
elif d == 'D': x += 1
elif d == 'U': x -= 1
edge = frozenset({prev, (x, y)})
if edge in visited:
return False
visited.add(edge)
return (x, y) == (0, 0)
@staticmethod
def _path_to_edges(directions: List[str]) -> List[Tuple[Tuple[int, int], str]]:
path = []
x, y = 0, 0
for d in directions:
prev = (x, y)
if d == 'R': y += 1
elif d == 'L': y -= 1
elif d == 'D': x += 1
elif d == 'U': x -= 1
path.append((prev, d))
return path
@classmethod
def _check_black_circle(cls, r: int, c: int, edges: list, directions: list) -> bool:
passed = False
for (prev, d), (next_pos, next_d) in zip(edges, edges[1:] + edges[:1]):
x, y = prev
nx, ny = next_pos
if (x, y) == (nx, ny):
continue
if (x == r and y == c and d in ['U', 'D']) or (nx == r and ny == c and next_d in ['U', 'D']):
passed = True
if not (cls._is_turn_before(directions, prev) and cls._is_turn_after(directions, next_pos)):
return False
return passed
@classmethod
def _check_white_circle(cls, r: int, c: int, edges: list, directions: list) -> bool:
for (prev, d), (next_pos, next_d) in zip(edges, edges[1:] + edges[:1]):
x, y = prev
if (x, y) == (r, c):
if d != next_d and not cls._has_straight_segment(directions, prev):
return False
return True
@staticmethod
def _is_turn_before(directions: List[str], pos: Tuple[int, int]) -> bool:
return True # 简化实现
@staticmethod
def _is_turn_after(directions: List[str], pos: Tuple[int, int]) -> bool:
return True # 简化实现
@staticmethod
def _has_straight_segment(directions: List[str], pos: Tuple[int, int]) -> bool:
return True # 简化实现

View file

@ -0,0 +1,400 @@
import ast
import json
import random
import re
import time
import numpy as np
from typing import List, Tuple, Dict, Any, Optional
from internbootcamp.bootcamp.base import Basebootcamp
from internbootcamp.libs.masyu.masyu_data_generator import MasyuGenerator
from internbootcamp.libs.masyu.masyu_solver import check_valid_masyu
from internbootcamp.libs.masyu.masyu_z3_solver import solve_masyu_with_z3
class Masyubootcamp(Basebootcamp):
def __init__(self, size=(6, 6), black_pearls=3, white_pearls=3, seed=None):
"""
初始化Masyu训练场
参数:
size: 谜题大小(行数, 列数)
black_pearls: 黑珠数量
white_pearls: 白珠数量
seed: 随机种子
"""
self.size = size
self.black_pearls = black_pearls
self.white_pearls = white_pearls
self.seed = seed
self.grid = None
self.solution_path = None
self.generator = MasyuGenerator()
def case_generator(self):
"""
生成一个Masyu谜题
返回:
identity: 包含谜题信息的字典
"""
rows, cols = self.size
max_attempts = 5 # 最大重试次数
for attempt in range(max_attempts):
try:
# 设置随机种子
if self.seed is not None:
random.seed(self.seed)
else:
random.seed( time.time()) # 使用当前时间作为种子
# 生成谜题
self.grid = self.generator.generate_puzzle(
rows,
cols,
self.black_pearls,
self.white_pearls,
max_attempts=1000
)
# 如果生成失败,尝试减少珠子数量
if self.grid is None:
# print(f"生成谜题失败 (attempt {attempt + 1}/{max_attempts}),尝试减少珠子数量...")
self.black_pearls = max(1, self.black_pearls - 1)
self.white_pearls = max(1, self.white_pearls - 1)
continue
# 使用Z3求解器获取解决方案
solution_path = solve_masyu_with_z3(self.grid, timeout=30)
# 如果无解,重新生成
if solution_path is None:
# print(f"生成的谜题无解 (attempt {attempt + 1}/{max_attempts}),重新生成...")
continue
# 确保解决方案是闭环
if solution_path and solution_path[0] != solution_path[-1]:
solution_path.append(solution_path[0])
# 创建并返回identity字典
identity = {
'grid': self.grid,
'size': self.size,
'black_pearls': self.black_pearls,
'white_pearls': self.white_pearls,
'seed': self.seed,
'solution_path': solution_path
}
return identity
except Exception as e:
# print(f"生成谜题时出错 (attempt {attempt + 1}/{max_attempts}): {str(e)}")
# 继续下一次尝试
pass
# return self._generate_simple_fallback_puzzle()
return identity
@staticmethod
def extract_output(output):
"""
从解决方案输出中提取路径
参数:
output: 模型输出
返回:
提取的路径列表
"""
pattern = r'```json\s*([\s\S]*?)\s*```'
matches = re.findall(pattern, output)
if matches:
python_str = matches[-1]
try:
# 尝试解析为Python对象
result = ast.literal_eval(python_str.strip())
return result
except Exception:
return python_str
else:
return None
@staticmethod
def print_masyu_str(identity: dict):
"""
返回Masyu谜题的字符串表示
参数:
identity: 谜题信息字典
返回:
谜题的字符串表示
"""
if identity is None:
return "没有谜题可显示 (identity is None)"
if 'grid' not in identity or identity['grid'] is None:
return "没有谜题可显示 (grid is None)"
result = "Masyu谜题:\n"
for r in range(len(identity['grid'])):
line = ""
for c in range(len(identity['grid'][0])):
if identity['grid'][r][c] == 'B':
line += "" # 黑珠
elif identity['grid'][r][c] == 'W':
line += "" # 白珠
else:
line += "· " # 空格
result += line + "\n"
return result
@classmethod
def prompt_func(cls, identity: dict) -> str:
"""
生成提示语
参数:
identity: 谜题信息字典
返回:
提示语字符串
"""
if identity is None:
# print("Warning: identity is None in prompt_func")
# 创建一个简单的后备谜题
identity = cls._generate_simple_fallback_puzzle()
statements = [
f"""你是一个擅长解决Masyu(珍珠)谜题的智能助手。以下是一个Masyu谜题请找出满足所有规则的闭环路径。
Masyu规则:
1. 必须画一条封闭的单线环线不能交叉或分叉
2. 白珠()规则线必须穿过白珠并在白珠处直行但必须在白珠的至少一侧转弯
3. 黑珠()规则线必须在黑珠处转弯并在黑珠的两侧直行至少一格
4. 线必须穿过所有珠子但不必经过所有空格
谜题如下
{cls.print_masyu_str(identity)}
请给出形成闭环的完整路径以坐标序列的形式表示坐标格式为(,)从0开始计数
例如[(0, 0), (0, 1), (1, 1), ..., (0, 0)]
注意路径必须是一个闭环所以第一个坐标和最后一个坐标应该相同
""",
f"""You are an intelligent assistant specializing in solving Masyu (Pearl) puzzles. Below is a Masyu puzzle that needs to be solved.
Masyu Rules:
1. You must draw a single closed loop that doesn't cross or branch.
2. White pearl () rule: The loop must pass straight through white pearls, but must make a turn in at least one of the cells adjacent to the white pearl.
3. Black pearl () rule: The loop must make a turn at black pearls, and must go straight through the next cell in both directions.
4. The loop must pass through all pearls, but doesn't need to visit all empty cells.
The puzzle is as follows:
{cls.print_masyu_str(identity)}
Please provide the complete path forming a closed loop as a sequence of coordinates. Coordinates are in (row,column) format, starting from 0.
For example: [(0, 0), (0, 1), (1, 1), ..., (0, 0)]
Note: The path must be a closed loop, so the first and last coordinates should be the same.
"""
]
instruction_following = """\nLet's think step by step and output the final answer with an example markdown formatting:
Final-answer: ```json
[(0, 0), (0, 1), (1, 1), (2, 1), (2, 0), (1, 0), (0, 0)]
```"""
return statements[random.randint(0, len(statements) - 1)] + instruction_following
@classmethod
def _verify_correction(cls, solution, identity) -> bool:
"""
验证解决方案是否正确
参数:
solution: 用户提供的解决方案
identity: 谜题信息字典
返回:
解决方案是否正确
"""
# 检查解决方案是否为列表
if not isinstance(solution, list):
# print("Error: Solution is not a list")
return False
# 检查是否为闭环(首尾相同)
if not solution or solution[0] != solution[-1]:
# print("Error: Solution is not a closed loop")
return False
# 检查坐标是否有效
rows, cols = identity['size']
for r, c in solution:
if not (0 <= r < rows and 0 <= c < cols):
# print(f"Error: Invalid coordinates ({r}, {c})")
return False
# 检查移动是否有效(只能上下左右移动一格)
for i in range(len(solution) - 1):
r1, c1 = solution[i]
r2, c2 = solution[i + 1]
if not ((abs(r1 - r2) == 1 and c1 == c2) or (r1 == r2 and abs(c1 - c2) == 1)):
# print(f"Error: Invalid move from ({r1}, {c1}) to ({r2}, {c2})")
return False
# 检查是否经过所有珠子
pearl_positions = []
for r in range(rows):
for c in range(cols):
if identity['grid'][r][c] in ['B', 'W']:
pearl_positions.append((r, c))
for pos in pearl_positions:
if pos not in solution:
# print(f"Error: Path does not pass through pearl at {pos}")
return False
# 检查白珠规则
for r in range(rows):
for c in range(cols):
if identity['grid'][r][c] == 'W':
if not cls._check_white_pearl_rule(solution, r, c):
# print(f"Error: White pearl rule violated at ({r}, {c})")
return False
# 检查黑珠规则
for r in range(rows):
for c in range(cols):
if identity['grid'][r][c] == 'B':
if not cls._check_black_pearl_rule(solution, r, c):
# print(f"Error: Black pearl rule violated at ({r}, {c})")
return False
return True
@staticmethod
def _check_white_pearl_rule(path, r, c):
"""
检查白珠规则线必须穿过白珠并在白珠处直行但必须在白珠的至少一侧转弯
参数:
path: 路径
r, c: 白珠坐标
返回:
是否满足白珠规则
"""
# 找到白珠在路径中的索引
try:
idx = path.index((r, c))
except ValueError:
return False
# 获取前后的点
prev_idx = (idx - 1) % (len(path) - 1)
next_idx = (idx + 1) % (len(path) - 1)
prev_r, prev_c = path[prev_idx]
next_r, next_c = path[next_idx]
# 检查是否直行
is_straight = (prev_r == next_r) or (prev_c == next_c)
if not is_straight:
return False
# 检查至少一侧是否转弯
# 获取前前和后后的点
prev_prev_idx = (prev_idx - 1) % (len(path) - 1)
next_next_idx = (next_idx + 1) % (len(path) - 1)
prev_prev_r, prev_prev_c = path[prev_prev_idx]
next_next_r, next_next_c = path[next_next_idx]
# 检查前一侧是否转弯
prev_turn = not ((prev_prev_r == prev_r == r) or (prev_prev_c == prev_c == c))
# 检查后一侧是否转弯
next_turn = not ((next_r == next_next_r == r) or (next_c == next_next_c == c))
return prev_turn or next_turn
@staticmethod
def _check_black_pearl_rule(path, r, c):
"""
检查黑珠规则线必须在黑珠处转弯并在黑珠的两侧直行至少一格
参数:
path: 路径
r, c: 黑珠坐标
返回:
是否满足黑珠规则
"""
# 找到黑珠在路径中的索引
try:
idx = path.index((r, c))
except ValueError:
return False
# 获取前后的点
prev_idx = (idx - 1) % (len(path) - 1)
next_idx = (idx + 1) % (len(path) - 1)
prev_r, prev_c = path[prev_idx]
next_r, next_c = path[next_idx]
# 检查是否转弯
is_turn = not ((prev_r == next_r) or (prev_c == next_c))
if not is_turn:
return False
# 检查两侧是否直行至少一格
# 获取前前和后后的点
prev_prev_idx = (prev_idx - 1) % (len(path) - 1)
next_next_idx = (next_idx + 1) % (len(path) - 1)
prev_prev_r, prev_prev_c = path[prev_prev_idx]
next_next_r, next_next_c = path[next_next_idx]
# 检查前一侧是否直行
prev_straight = (prev_prev_r == prev_r) or (prev_prev_c == prev_c)
# 检查后一侧是否直行
next_straight = (next_r == next_next_r) or (next_c == next_next_c)
return prev_straight and next_straight
if __name__ == '__main__':
# 单元测试
try:
masyu_bootcamp = Masyubootcamp(size=(6, 6), black_pearls=3, white_pearls=3)
identity = masyu_bootcamp.case_generator()
if identity is None:
print("Error: Failed to generate puzzle")
exit(1)
print(masyu_bootcamp.prompt_func(identity))
# 使用正确的解决方案进行测试
solution = identity['solution_path']
fake_output = f"""\n略,
Final-answer: ```json
{solution}
```"""
print(fake_output)
print("Is it correct? ", masyu_bootcamp.verify_score(fake_output, identity))
except Exception as e:
print(f"Error in main: {str(e)}")
import traceback
traceback.print_exc()