
Por que a ordem das verificações importa
Mesmo que a IA leia as coordenadas corretamente, não há garantia de que a jogada seja legal. A verificação das regras deve ser um código determinístico, separado das probabilidades do modelo. O mesmo tabuleiro e a mesma coordenada precisam sempre dar o mesmo resultado; caso contrário, testes e reprodução de falhas ficam impossíveis.
Etapa 1: Verificar o intervalo e o ponto vazio
Primeiro, verifique se a linha e a coluna estão no intervalo de 0 a 18. Em seguida, veja se a interseção está vazia. Se falhar aqui, não é preciso copiar o tabuleiro nem remover pedras, então as verificações mais baratas vêm primeiro.
Etapa 2: Calcular capturas em um tabuleiro temporário
Copie o tabuleiro atual, coloque uma pedra na coordenada candidata e encontre os grupos adversários acima, abaixo, à esquerda e à direita. Todo grupo com zero liberdades é removido do tabuleiro temporário. Vários grupos podem ser capturados ao mesmo tempo, então a verificação não pode parar na primeira captura.
- Colocar a pedra candidata no tabuleiro temporário.
- Encontrar os grupos adversários adjacentes e suas liberdades.
- Remover todos os grupos adversários sem liberdades.
- Contar de novo as liberdades do próprio grupo da pedra candidata.
Etapa 3: Distinguir suicídio de ko
Se o próprio grupo continuar com zero liberdades depois de remover as pedras adversárias, a jogada é suicídio e é recusada. Se a captura criou novas liberdades, a jogada é legal. Por fim, compare o tabuleiro temporário inteiro com o tabuleiro anterior para bloquear a recaptura imediata do ko. Proibir permanentemente uma única coordenada bloquearia também jogadas legais depois que o tabuleiro mudou, então é preciso comparar os estados do tabuleiro.
Etapa 4: Devolver o motivo da recusa na requisição ao modelo
Não coloque apenas a coordenada ilegal em uma lista de exclusão; registre também o motivo, como "já existe uma pedra", "suicídio" ou "repetição de ko". A nova requisição informa explicitamente a coordenada recusada para reduzir respostas repetidas. Se o número máximo de tentativas for ultrapassado, o servidor não faz uma jogada aleatória; ele avisa o usuário sobre a falha e grava o tabuleiro e as respostas no log de erros.
Testes que você precisa criar
- Um tabuleiro em que uma única pedra do canto é capturada
- Um tabuleiro em que dois grupos são capturados ao mesmo tempo
- Uma jogada que captura e, por isso, não é suicídio
- Uma jogada que realmente é suicídio
- Um ko em que só a recaptura imediata é proibida
Esses cinco tabuleiros devem poder ser verificados chamando apenas as funções de regras, sem o modelo.
O código real desta etapa
Este é o motor de regras do cliente Qt. Ele verifica primeiro o intervalo e o ponto vazio, captura todas as pedras adversárias em um tabuleiro copiado, depois verifica o suicídio e, por fim, bloqueia o ko comparando com todos os tabuleiros já vistos. O motivo da recusa é devolvido 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 usado: Qwen3-VL-8B-Instruct + adaptador LoRA (lora_mix_qwen_r5). O código-fonte completo e a descrição do modelo estão no post "8. O código-fonte completo e os modelos usados".
COMMENTS · CHAPTER 04
Verificando com o motor de regras a jogada escolhida pela IA 0 comentários
Ainda não há comentários. Deixe uma pergunta ou algo que queira acrescentar.
Para publicar um comentário, comece com o Google e registre um apelido e um e-mail.
Começar com o Google