mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-23 16:55:02 +00:00
352 lines
13 KiB
Python
Executable file
352 lines
13 KiB
Python
Executable file
"""# 谜题训练场开发任务
|
||
|
||
## 任务概述
|
||
你是一位资深程序员,我需要你帮我实现一个特定谜题的训练场环境类。这个类继承自`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)
|
||
```
|
||
|
||
## 你的任务
|
||
请根据以下谜题描述(谜题描述可能不完整,请先结合你的知识澄清规则),实现一个完整的谜题训练场类:
|
||
|
||
### 谜题描述
|
||
|
||
Objective: Place light bulbs in white grid cells such that every white cell is illuminated, adhering to the following rules:
|
||
|
||
1. **Illumination**: Each bulb lights its entire row and column until blocked by a black cell. Bulbs do not block light (e.g., a bulb in cell A does not prevent light from reaching cells beyond it unless another black cell intervenes).
|
||
|
||
2. **No Direct Exposure**: Two bulbs cannot illuminate each other. If two bulbs are placed in the same row or column, there must be at least one black cell between them to block their line of sight.
|
||
|
||
3. **Numbered Black Cells**:
|
||
- Black cells with numbers (0–4) specify the **exact** number of bulbs required in orthogonally adjacent white cells (up, down, left, right).
|
||
- Example: A \"3\" means exactly three of its adjacent white cells must contain bulbs.
|
||
- A \"0\" means no bulbs can be placed in adjacent white cells.
|
||
|
||
4. **Unnumbered Black Cells**: Black cells without numbers impose no restrictions on adjacent bulbs.
|
||
|
||
5. **Full Coverage**: All white cells must be illuminated by at least one bulb. Bulbs can illuminate multiple cells, but every white cell must lie in the \"light path\" of at least one bulb.
|
||
|
||
6. **Bulb Placement**: Bulbs can only be placed in white cells, never in black cells.
|
||
|
||
|
||
请完成上述谜题的训练场环境类实现,包括所有必要的方法。
|
||
"""
|
||
|
||
from internbootcamp.bootcamp.base import Basebootcamp
|
||
import json
|
||
import random
|
||
import re
|
||
import ast
|
||
from itertools import combinations
|
||
|
||
class LightUpbootcamp(Basebootcamp):
|
||
def __init__(self, **params):
|
||
self.rows = params.get('rows', 5)
|
||
self.cols = params.get('cols', 5)
|
||
self.max_attempts = params.get('max_attempts', 100)
|
||
self.min_black_cells = params.get('min_black_cells', 3)
|
||
self.max_black_cells = params.get('max_black_cells', 8)
|
||
|
||
def case_generator(self):
|
||
# 生成有效谜题的简单实现(示例性质,实际需要更健壮的实现)
|
||
# 这里采用一个简化方法:生成一个包含中心灯泡和必要黑色格子的网格
|
||
grid = [['W' for _ in range(self.cols)] for _ in range(self.rows)]
|
||
bulbs = []
|
||
|
||
# 随机生成一些黑格子
|
||
black_cells = random.randint(self.min_black_cells, self.max_black_cells)
|
||
for _ in range(black_cells):
|
||
x = random.randint(0, self.rows-1)
|
||
y = random.randint(0, self.cols-1)
|
||
grid[x][y] = 'B'
|
||
|
||
# 尝试放置灯泡(示例位置)
|
||
# 注意:这是简化实现,实际需要确保符合所有规则
|
||
if self.rows >= 3 and self.cols >= 3:
|
||
x, y = self.rows//2, self.cols//2
|
||
if grid[x][y] == 'W':
|
||
bulbs.append((x, y))
|
||
grid[x][y] = 'B' # 转换为黑格子来防止互相照射(示例逻辑)
|
||
|
||
# 设置黑格子数字(示例逻辑)
|
||
for i in range(self.rows):
|
||
for j in range(self.cols):
|
||
if grid[i][j] == 'B':
|
||
count = 0
|
||
# 检查四个方向
|
||
dirs = [(-1,0), (1,0), (0,-1), (0,1)]
|
||
for dx, dy in dirs:
|
||
ni, nj = i+dx, j+dy
|
||
if 0 <= ni < self.rows and 0 <= nj < self.cols:
|
||
if (ni, nj) in bulbs:
|
||
count += 1
|
||
if count > 0:
|
||
grid[i][j] = f'B{count}'
|
||
|
||
# 转换为可JSON序列化的格式
|
||
return {
|
||
'grid': grid,
|
||
'rows': self.rows,
|
||
'cols': self.cols
|
||
}
|
||
|
||
@staticmethod
|
||
def prompt_func(question_case):
|
||
grid = question_case['grid']
|
||
rows = question_case['rows']
|
||
cols = question_case['cols']
|
||
|
||
prompt = """你是一个专业灯谜解题者,请根据以下规则在网格中放置灯泡:
|
||
|
||
规则:
|
||
1. 灯泡只能放在白色格子(□)中,放置后可以照亮整行和整列直到被黑色格子阻挡
|
||
2. 任何两个灯泡不能互相照射(同一行/列直接可见)
|
||
3. 数字黑色格子(如■3)表示相邻(上下左右)的白色格子中必须放置正好对应数量的灯泡
|
||
4. 所有白色格子必须被至少一个灯泡照亮
|
||
|
||
网格布局(行号0-{},列号0-{}):
|
||
""".format(rows-1, cols-1)
|
||
|
||
# 构建网格可视化
|
||
for i in range(rows):
|
||
line = []
|
||
for j in range(cols):
|
||
cell = grid[i][j]
|
||
if cell == 'W':
|
||
line.append('□')
|
||
elif cell.startswith('B'):
|
||
if len(cell) > 1 and cell[1].isdigit():
|
||
line.append(f'■{cell[1]}')
|
||
else:
|
||
line.append('■')
|
||
else:
|
||
line.append('?')
|
||
prompt += f"行{i}:" + " ".join(line) + "\n"
|
||
|
||
prompt += "\n请将答案的灯泡坐标列表放在[answer]标签内,例如:[answer][(1,2), (3,4)][/answer]"
|
||
return prompt
|
||
|
||
@staticmethod
|
||
def extract_output(output):
|
||
# 使用非贪婪匹配查找最后一个answer标签
|
||
matches = re.findall(r'\[answer\](.*?)\[/answer\]', output, re.DOTALL)
|
||
if not matches:
|
||
return None
|
||
|
||
try:
|
||
# 清理字符串并解析
|
||
last_match = matches[-1].strip().replace(' ', '').replace('\n', '')
|
||
return ast.literal_eval(last_match)
|
||
except:
|
||
return None
|
||
|
||
@classmethod
|
||
def _verify_correction(cls, solution, identity):
|
||
grid = identity['grid']
|
||
rows = identity['rows']
|
||
cols = identity['cols']
|
||
bulbs = solution
|
||
|
||
# 验证1: 所有灯泡在白色格子
|
||
for x, y in bulbs:
|
||
if not (0 <= x < rows and 0 <= y < cols):
|
||
return False
|
||
if not grid[x][y].startswith('W'):
|
||
return False
|
||
|
||
# 验证2: 数字黑格子条件
|
||
for i in range(rows):
|
||
for j in range(cols):
|
||
cell = grid[i][j]
|
||
if cell.startswith('B') and len(cell) > 1 and cell[1].isdigit():
|
||
required = int(cell[1])
|
||
count = 0
|
||
# 检查四个方向
|
||
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
|
||
ni, nj = i+dx, j+dy
|
||
if 0 <= ni < rows and 0 <= nj < cols:
|
||
if (ni, nj) in bulbs:
|
||
count += 1
|
||
if count != required:
|
||
return False
|
||
|
||
# 验证3: 所有白色格子被照亮
|
||
for i in range(rows):
|
||
for j in range(cols):
|
||
if grid[i][j].startswith('W'):
|
||
illuminated = False
|
||
for bx, by in bulbs:
|
||
# 检查行可见
|
||
if bx == i:
|
||
min_y = min(by, j)
|
||
max_y = max(by, j)
|
||
blocked = False
|
||
for y in range(min_y+1, max_y):
|
||
if grid[bx][y].startswith('B'):
|
||
blocked = True
|
||
break
|
||
if not blocked:
|
||
illuminated = True
|
||
break
|
||
# 检查列可见
|
||
if by == j:
|
||
min_x = min(bx, i)
|
||
max_x = max(bx, i)
|
||
blocked = False
|
||
for x in range(min_x+1, max_x):
|
||
if grid[x][by].startswith('B'):
|
||
blocked = True
|
||
break
|
||
if not blocked:
|
||
illuminated = True
|
||
break
|
||
if not illuminated:
|
||
return False
|
||
|
||
# 验证4: 灯泡之间无冲突
|
||
for (x1, y1), (x2, y2) in combinations(bulbs, 2):
|
||
if x1 == x2:
|
||
min_y = min(y1, y2)
|
||
max_y = max(y1, y2)
|
||
blocked = False
|
||
for y in range(min_y+1, max_y):
|
||
if grid[x1][y].startswith('B'):
|
||
blocked = True
|
||
break
|
||
if not blocked:
|
||
return False
|
||
elif y1 == y2:
|
||
min_x = min(x1, x2)
|
||
max_x = max(x1, x2)
|
||
blocked = False
|
||
for x in range(min_x+1, max_x):
|
||
if grid[x][y1].startswith('B'):
|
||
blocked = True
|
||
break
|
||
if not blocked:
|
||
return False
|
||
|
||
return True
|