
为什么需要检查顺序
即使AI正确读取了坐标,也不能保证那一手是合法的。规则检查必须是与模型概率分离的确定性代码。输入相同的盘面和相同的坐标,必须始终得到相同的结果,这样才能进行测试和故障复现。
第1步:确认范围和空点
先检查行和列是否在0~18的范围内。接着看该交叉点是否为空。如果在这里就失败了,就没有必要复制棋盘或移除棋子,所以把成本最低的检查放在前面。
第2步:在临时棋盘上计算提子
复制当前棋盘,在候选坐标上放一颗棋子,然后查找其上下左右的对方棋块。如果某个棋块的气为0,就从临时棋盘上移除。因为可能同时提掉多个棋块,所以不能在第一次提子时就结束检查。
- 把候选棋子放到临时棋盘上。
- 查找相邻的对方棋块及其气。
- 移除所有没有气的对方棋块。
- 重新计算候选棋子所在己方棋块的气。
第3步:区分自杀和劫
如果移除对方棋子之后,己方棋块的气仍然为0,就是自杀,予以拒绝。如果因为提子产生了新的气,就是合法着法。最后,比较整个临时棋盘是否与上一盘面相同,以阻止立即回提的劫。永久禁止某一个坐标的做法,会连盘面变化之后的合法落子也一起禁止,所以必须比较盘面状态。
第4步:把拒绝理由反馈给模型请求
不要只把非法坐标放进排除列表,还要一并记录理由,比如“已经有棋子”“自杀”“劫的重复”。重新请求时要明确写出被拒绝的坐标,以减少相同回答的重复。如果超过最大重试次数,服务器不会随便下一手,而是告知用户失败,并把盘面和回复记入故障日志。
一定要写的测试
- 角上一颗棋子被提掉的盘面
- 同时提掉两个棋块的盘面
- 因为提掉对方而不算自杀的着法
- 真正是自杀的着法
- 只禁止立即回提的劫
这五个盘面必须能够不借助模型、只调用规则函数就进行确认。
这一步的实际代码
这是Qt客户端的规则引擎。它先查看范围和空点,在复制的棋盘上提掉所有对方棋子之后检查自杀,最后与过去出现过的所有盘面进行比较来阻止劫。拒绝理由以MoveRejectReason返回。
src/goboard.cpp
bool GoBoard::isLegalMove(int row, int col, Stone who, MoveRejectReason *outReason) const
{
auto setReason = [outReason](MoveRejectReason r) {
if (outReason)
*outReason = r;
};
setReason(MoveRejectReason::None);
if (m_status != GameStatus::Playing || m_phase != GamePhase::Playing) {
setReason(MoveRejectReason::GameNotPlaying);
return false;
}
const int N = m_size;
if (!inBounds(N, row, col)) {
setReason(MoveRejectReason::OutOfBounds);
return false;
}
if (who != Stone::Black && who != Stone::White) {
setReason(MoveRejectReason::OutOfBounds);
return false;
}
if (m_cells[row * N + col] != Stone::Empty) {
setReason(MoveRejectReason::Occupied);
return false;
}
GoBoard sim = *this; // 복사 후 시뮬레이션
const Stone opp = opponent(who);
sim.m_cells[row * N + col] = who;
int captured = 0;
QVector<int> visited(N * N, 0);
for (int d = 0; d < 4; ++d) {
const int nr = row + kDR[d];
const int nc = col + kDC[d];
if (!inBounds(N, nr, nc))
continue;
const int nidx = nr * N + nc;
if (sim.m_cells[nidx] != opp || visited[nidx])
continue;
QVector<int> grp;
QVector<int> libs;
sim.collectGroup(nr, nc, &grp, &libs);
for (int gi : grp)
visited[gi] = 1;
if (libs.isEmpty()) {
for (int gi : grp) {
sim.m_cells[gi] = Stone::Empty;
++captured;
}
}
}
if (sim.liberties(row, col) == 0) {
setReason(MoveRejectReason::Suicide);
return false;
}
if (sim.wouldRecreatePastPosition(sim.packed())) {
setReason(MoveRejectReason::KoViolation);
return false;
}
Q_UNUSED(captured);
return true;
}使用的模型:Qwen3-VL-8B-Instruct + LoRA适配器(lora_mix_qwen_r5)。完整源代码和模型说明可以在“8. 完整源代码与使用的模型”一文中查看。
COMMENTS · CHAPTER 04
用规则引擎检查AI选的着法 评论 0
还没有评论。阅读时有疑问或想补充的内容,欢迎留言。
发表评论前,请用 Google 开始并登记昵称和邮箱。
用 Google 开始