
Por qué hace falta un orden de comprobación
Aunque la IA lea bien las coordenadas, no hay garantía de que su jugada sea legal. La comprobación de reglas debe ser código determinista, separado de las probabilidades del modelo. El mismo tablero y la misma coordenada deben dar siempre el mismo resultado; si no, es imposible hacer pruebas y reproducir los fallos.
Paso 1: Comprobar el rango y el punto vacío
Primero se comprueba que la fila y la columna estén en el rango de 0 a 18. Después se mira si esa intersección está vacía. Si esto falla, no hace falta copiar el tablero ni retirar piedras, así que las comprobaciones más baratas van primero.
Paso 2: Calcular las capturas en un tablero temporal
Se copia el tablero actual, se coloca una piedra en la coordenada candidata y se buscan los grupos rivales de arriba, abajo, izquierda y derecha. Si un grupo tiene cero libertades, se retira del tablero temporal. Se pueden capturar varios grupos a la vez, así que la comprobación no debe terminar en la primera captura.
- Colocar la piedra candidata en el tablero temporal.
- Buscar los grupos rivales adyacentes y sus libertades.
- Retirar todos los grupos rivales sin libertades.
- Volver a contar las libertades del grupo propio al que pertenece la piedra candidata.
Paso 3: Distinguir el suicidio del ko
Si después de retirar las piedras rivales el grupo propio sigue teniendo cero libertades, es suicidio y se rechaza. Si la captura creó nuevas libertades, la jugada es legal. Por último, se compara todo el tablero temporal con el tablero anterior para impedir la recaptura inmediata del ko. Prohibir de forma permanente una sola coordenada bloquearía también jugadas legales después de que el tablero haya cambiado, así que hay que comparar estados del tablero.
Paso 4: Devolver el motivo del rechazo en la petición al modelo
No metas solo la coordenada ilegal en una lista de exclusión; registra también el motivo, como "ya hay una piedra", "suicidio" o "repetición de ko". En la nueva petición se indica la coordenada rechazada para reducir la repetición de la misma respuesta. Si se supera el número máximo de reintentos, el servidor no juega una jugada aleatoria, sino que informa del fallo al usuario y guarda el tablero y las respuestas en el registro de errores.
Pruebas que debes escribir
- Un tablero donde se captura una sola piedra de esquina
- Un tablero donde se capturan dos grupos a la vez
- Una jugada que captura al rival y por eso no es suicidio
- Una jugada que sí es suicidio
- Un ko donde solo está prohibida la recaptura inmediata
Estos cinco tableros deben poder comprobarse llamando solo a las funciones de reglas, sin el modelo.
El código real de este paso
Este es el motor de reglas del cliente Qt. Primero mira el rango y el punto vacío, captura todas las piedras rivales en un tablero copiado, luego comprueba el suicidio y, por último, impide el ko comparando con todos los tableros que han aparecido antes. El motivo del rechazo se devuelve como 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;
}Modelo utilizado: Qwen3-VL-8B-Instruct + adaptador LoRA (lora_mix_qwen_r5). El código fuente completo y la descripción del modelo están en la entrada "8. El código fuente completo y los modelos utilizados".
COMMENTS · CHAPTER 04
Comprobar con el motor de reglas la jugada elegida por la IA 0 comentarios
Todavía no hay comentarios. Deja una pregunta o algo que quieras añadir.
Para publicar un comentario, empieza con Google y registra un apodo y un correo.
Empezar con Google