Pitch: Minerva: desenvolvendo meu próprio motor de xadrez em Java
1. A ideia do projeto
Estou desenvolvendo meu próprio motor de xadrez (chess engine) em Java! Meu objetivo é, além de evoluir meus conhecimentos sobre a stack Java, melhorar minhas habilidades como programador, desafiando-me em um projeto que envolve diferentes aspectos da programação e exige o exercício de várias habilidades.
O projeto se chama Minerva e pode ser acompanhado pelo repositório no GitHub:
Acesse o repositório do Minerva
O nome foi inspirado em Minerva, deusa romana da sabedoria e da guerra, equivalente à deusa grega Atena.

Imagem de referência: Imaginação Fértil.
O Minerva será desenvolvido por etapas. Neste post, vou apresentar como implementei a representação inicial do tabuleiro e das peças, além de um método rudimentar para localizar uma peça a partir da notação das casas do xadrez, como a1 e b2.
2. Representação do tabuleiro
A representação do tabuleiro é uma das bases de um motor de xadrez. Ela é responsável por armazenar a posição das peças e permitir que o programa trabalhe com o estado da partida.
A Chess Programming Wiki, em seu artigo Getting Started, destaca a importância dessa etapa:
A base de um motor de xadrez é a representação do tabuleiro. Esse é o back-end do motor, responsável por controlar como ele acompanha o tabuleiro e as regras do jogo. O primeiro passo para desenvolver um motor de xadrez é criar uma representação completa e livre de erros, que conheça todas as regras do xadrez.
Outro artigo da mesma fonte, Board Representation, explica que um programa de xadrez precisa de uma representação interna para manter as posições das peças durante as buscas, avaliações e partidas.

Imagem de referência: captura do TSCP no terminal.
Para representar o tabuleiro do Minerva, escolhi uma estrutura baseada no conceito de Mailbox, por ser uma abordagem simples de compreender e implementar.
Segundo a Chess Programming Wiki, no artigo sobre Mailbox, essa representação é centrada nas casas do tabuleiro: cada casa possui uma posição correspondente na memória, geralmente em um array, que permite acessar seu conteúdo diretamente.
No meu projeto, comecei com uma matriz de 8 × 8, na qual cada posição representa uma das 64 casas do tabuleiro de xadrez.
O código inicial ficou assim:
public int[][] tabuleiro = new int[][] {
{TORRE_PRETO, CAVALO_PRETO, BISPO_PRETO, RAINHA_PRETO, REI_PRETO, BISPO_PRETO, CAVALO_PRETO, TORRE_PRETO},
{PEAO_PRETO, PEAO_PRETO, PEAO_PRETO, PEAO_PRETO, PEAO_PRETO, PEAO_PRETO, PEAO_PRETO, PEAO_PRETO},
{VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO},
{VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO},
{VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO},
{VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO, VAZIO},
{PEAO_BRANCO, PEAO_BRANCO, PEAO_BRANCO, PEAO_BRANCO, PEAO_BRANCO, PEAO_BRANCO, PEAO_BRANCO, PEAO_BRANCO},
{TORRE_BRANCO, CAVALO_BRANCO, BISPO_BRANCO, RAINHA_BRANCO, REI_BRANCO, BISPO_BRANCO, CAVALO_BRANCO, TORRE_BRANCO}
};
Como representar as peças e suas cores?
Para decidir como representar as peças, consultei também a implementação do TSCP (Tom Kerrigan's Simple Chess Program).
Nessa implementação, são utilizados dois arrays de 64 elementos: um armazena o tipo da peça, enquanto o outro armazena sua cor. Assim, as duas informações ficam separadas.
A estrutura é apresentada na Chess Programming Wiki, na seção dedicada ao TSCP:
int color[64]; /* LIGHT, DARK, or EMPTY */
int piece[64]; /* PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, or EMPTY */
O exemplo também apresenta os arrays preenchidos com a posição inicial das peças:
int piece[64] = {
ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK,
PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN,
ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK
};
int color[64] = {
BLACK, BLACK, BLACK, BLACK, BLACK, BLACK, BLACK, BLACK,
BLACK, BLACK, BLACK, BLACK, BLACK, BLACK, BLACK, BLACK,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE,
WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE
};
Essa é uma referência de outra implementação. No Minerva, optei por uma abordagem diferente: cada combinação de peça e cor possui um valor inteiro próprio, enquanto as casas vazias são representadas por um valor específico.
Tive algumas dificuldades para definir a arquitetura do motor e implementar métodos que permitissem localizar as peças no tabuleiro. Para começar, organizei essa representação em uma classe chamada Pecas:
public class Pecas {
int PEAO_BRANCO = 0;
int CAVALO_BRANCO = 1;
int BISPO_BRANCO = 2;
int TORRE_BRANCO = 3;
int RAINHA_BRANCO = 4;
int REI_BRANCO = 5;
int PEAO_PRETO = 6;
int CAVALO_PRETO = 7;
int BISPO_PRETO = 8;
int TORRE_PRETO = 9;
int RAINHA_PRETO = 10;
int REI_PRETO = 11;
int VAZIO = 12;
}
Dessa forma, cada peça pode ser identificada pelo valor armazenado na matriz, incluindo sua cor, de acordo com as constantes definidas no projeto.
3. Imprimindo o tabuleiro no terminal com ASCII (CLI)
Depois de implementar a representação inicial do tabuleiro, precisei criar uma maneira de visualizar sua posição no terminal.
Para isso, desenvolvi um método de impressão utilizando caracteres ASCII. A implementação pode ser entendida em duas etapas.
3.1. Representação das peças por caracteres
Cada posição do array letras corresponde ao valor numérico utilizado para representar uma peça.
Por exemplo, PEAO_BRANCO possui o valor 0. Portanto, ao acessar letras[0], o programa obtém o caractere 'p'. O valor 12, utilizado para representar uma casa vazia, corresponde ao caractere '.'.
O código ficou assim:
// Cada posição do array corresponde ao valor de uma peça.
// Ex.: letras[0] = 'p' -> PEAO_BRANCO = 0.
char[] letras = {
'p', 'n', 'b', 'r', 'q', 'k',
'P', 'N', 'B', 'R', 'Q', 'K', '.'
};
Assim, em vez de imprimir apenas os números armazenados na matriz, posso utilizar o array letras para exibir o caractere correspondente a cada posição.
3.2. Percorrendo a matriz e imprimindo o tabuleiro
Com o mapeamento dos caracteres pronto, basta percorrer as linhas e colunas da matriz.
Em cada posição, o programa utiliza o valor armazenado na matriz como índice do array letras e imprime o caractere correspondente. Ao terminar uma linha, utiliza System.out.println() para iniciar a próxima.
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
System.out.printf("%c ", letras[arrayTabuleiro[i][j]]);
// System.out.printf("%d ", arrayTabuleiro[i][j]);
}
System.out.println();
}
Dessa maneira, consigo visualizar o conteúdo da matriz no terminal usando caracteres para representar as peças e as casas vazias.
4. Implementação inicial para localizar uma peça pela notação algébrica
Por fim, comecei a implementar um método para encontrar uma peça a partir da notação de uma casa do tabuleiro, como a1, b2 e assim por diante.
Essa implementação ainda é rudimentar. Por enquanto, vou apresentar o código do main e o método responsável pela busca, para mostrar como essa funcionalidade está funcionando nesta etapa do projeto.
4.1. Método main
No main, crio um objeto da classe Tabuleiro, imprimo o tabuleiro no terminal e recebo a notação digitada pelo usuário.
Em seguida, envio a matriz, a letra e o número da casa para o método posicaoPeca().
import java.util.Scanner;
import pacotes.*;
public class Main {
public static void main(String[] args) {
Scanner entrada = new Scanner(System.in);
Tabuleiro t1 = new Tabuleiro();
System.out.println("// Tabuleiro //");
t1.imprimir(t1.tabuleiro);
String notacaoPeca = entrada.nextLine();
t1.posicaoPeca(
t1.tabuleiro,
notacaoPeca.charAt(0),
Character.getNumericValue(notacaoPeca.charAt(1))
);
}
}
4.2. Método posicaoPeca()
O método recebe a matriz do tabuleiro, a letra da casa e o número correspondente.
Primeiro, percorre um array com as letras de a a h para descobrir o índice da coluna correspondente à letra informada.
Depois, percorre a matriz e compara os índices para localizar a posição desejada. Quando encontra a casa correspondente, imprime o valor numérico armazenado nela e seus índices na matriz.
// Notação das casas do xadrez: a1, b2 etc.
public void posicaoPeca(int[][] arrayTabuleiro, char fileira, int coluna) {
char[] letras = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int fileiraNumero = 0;
for (int i = 0; i < 8; i++) {
if (fileira == letras[i]) {
fileiraNumero = i;
break;
}
}
// System.out.printf("Fileira: %d\nColuna: %d\n\n", fileiraNumero, coluna);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (j == fileiraNumero && i == 7 - (coluna - 1)) {
System.out.printf(
"Peça achada: %d\nlocal: [%d][%d]\n",
arrayTabuleiro[i][j],
i,
j
);
break;
}
}
}
}
Essa é a versão inicial do método de localização. Ainda há espaço para evoluir essa funcionalidade conforme o desenvolvimento do Minerva avançar.
Conclusão
Esse é o estado atual de algumas das primeiras etapas do Minerva: a representação inicial do tabuleiro e das peças, a impressão do tabuleiro no terminal e uma primeira implementação para localizar uma peça a partir da notação de uma casa.
O projeto será desenvolvido por partes, e pretendo continuar aprimorando meus conhecimentos de Java e minhas habilidades de programação durante esse processo.
Espero que tenham gostado de acompanhar esse começo! Se puderem dar uma olhada no repositório do Minerva no GitHub, deixar uma estrela, contribuir ou compartilhar algum feedback, ficarei muito agradecido.
Valeu!