Quem estuda xadrez, ou joga com alguma frequência, principalmente online, possivelmente conhece sobre o FEN. É um acrônimo para Forsyth-Edwards Notation. Uma notação pra posições de xadrez. Ela consegue descrever a posição das peças e algumas outras informações. Com isso, é possível guardar o estado de uma partida para montá-la depois, continuar jogando, estudar variantes, etc.
O FEN se tornou um padrão de fato na cena exandrística. As principais plataformas online e aplicativos são capazes ler e escrever posições em FEN. Todas as informações necessárias para manter em uma partida padrão de xadrez estão disponíveis no FEN.
- Posição de cada peça
- Qual cor faz a próxima jogada
- Quais movimentos de roque estão disponíveis
- peque e grande roque das peças pretas
- peque e grande roque das peças brancas
- Se, no próximo lance, o en passant está disponível
- Contador de lances sem movimento de peão e captura de peça -utilizado para a regra do empate quando não há movimento de peão nem captura de peça durante 100 lances consecutivos
- Contador geral de jogadoas completas – incrementado a cada jogada das peças pretas
ANKOBACHEN
Implementa um programa que faz a leitura do FEN é bem simples e não requer algoritmos mais complicados. A leitura da string pode acontecer caractere após caractere, onde cada informação lida tem uma utilidade bem definida. Além disso, detectar erros na FEN é trivial e parte da interpretação natural das informações que são lidas.
Um algoritmo básico para realizar a leitura de qualquer FEN em um cenário feliz, isto é, onde se espera que a FEN será válida, é bem simples. O código abaixo ilustra isso, com acréscimo de vários verificações para detectar possíveis erros.
init {
var charIndex = 0
var rowNumber = 0
var colNumber = 0
fun skipSpaces() {
while (charIndex < value.length && value[charIndex] == ' ') {
charIndex++
}
}
// reading piece placement
while (true) {
if (charIndex >= value.length) {
throwInvalidPiecePlacement(
fen = value
)
}
if (value[charIndex] == ' ') {
break
}
if (value[charIndex] == '/') {
if (colNumber != 8) {
throwInvalidPiecePlacement(
fen = value
)
}
charIndex++
rowNumber++
if (rowNumber > 7) {
throwInvalidPiecePlacement(
fen = value
)
}
colNumber = 0
continue
}
if (value[charIndex] in '1'..'8') {
colNumber += value[charIndex] - '0'
if (colNumber > 8) {
throwInvalidPiecePlacement(
fen = value
)
}
charIndex++
continue
}
val piece = try {
Piece.fromSymbol(
symbol = value[charIndex]
)
} catch (ex: IllegalArgumentException) {
throwInvalidCharacter(
fen = value,
char = value[charIndex],
charIndex = charIndex
)
}
val squareIndex = Square.rowColToIndex(
row = rowNumber,
col = colNumber
)
piecesColorsBitboards[piece.color.getIndex()] = piecesColorsBitboards[piece.color.getIndex()]
.activateBit(squareIndex)
piecesBitboards[piece.type.getIndex()] = piecesBitboards[piece.type.getIndex()].activateBit(squareIndex)
colNumber++
charIndex++
}
if (rowNumber != 7 || colNumber != 8) {
throwInvalidPiecePlacement(
fen = value
)
}
// reading side to move
skipSpaces()
color = try {
Color.fromSymbol(value[charIndex]).also {
charIndex++
}
} catch (ex: IllegalArgumentException) {
throwInvalidCharacter(
fen = value,
char = value[charIndex],
charIndex = charIndex,
cause = ex
)
}
// reading castling flags
skipSpaces()
if (charIndex >= value.length || value[charIndex] == '-') {
charIndex++
} else {
if (value[charIndex] == Piece.WHITE_KING.symbol) {
flags = flags.activateBit(Square.G1.getIndex())
charIndex++
}
if (value[charIndex] == Piece.WHITE_QUEEN.symbol) {
flags = flags.activateBit(Square.C1.getIndex())
charIndex++
}
if (value[charIndex] == Piece.BLACK_KING.symbol) {
flags = flags.activateBit(Square.G8.getIndex())
charIndex++
}
if (value[charIndex] == Piece.BLACK_QUEEN.symbol) {
flags = flags.activateBit(Square.C8.getIndex())
charIndex++
}
if (charIndex < value.length && value[charIndex] != ' ') {
throwInvalidCharacter(
fen = value,
char = value[charIndex],
charIndex = charIndex
)
}
}
// reading en passant target square
skipSpaces()
if (charIndex >= value.length || value[charIndex] == '-') {
charIndex++
} else {
val file = value[charIndex]
if (file !in FileSymbols.FILE_A..FileSymbols.FILE_H) {
throwInvalidCharacter(
fen = value,
char = value[charIndex],
charIndex = charIndex,
)
}
charIndex++
if (charIndex >= value.length) {
throw FENException(
fen = value,
message = "FEN string has invalid en passant target square information"
)
}
val rank = value[charIndex] - '0'
if (rank !in RankNumbers.RANK_1..RankNumbers.RANK_8) {
throwInvalidCharacter(
fen = value,
char = value[charIndex],
charIndex = charIndex,
)
}
charIndex++
val enPassantTarget = Square.fromAlgebraicCoordinate(
file = file,
rank = rank
)
flags = flags.activateBit(enPassantTarget.getIndex())
}
// reading half move clock
skipSpaces()
if (charIndex >= value.length || value[charIndex] == '-') {
charIndex++
} else {
val startIndex = charIndex
while (charIndex < value.length && value[charIndex] != ' ') {
charIndex++
}
halfMoveClock = value.substring(startIndex, charIndex).toInt()
}
// reading full move counter
skipSpaces()
if (charIndex >= value.length || value[charIndex] == '-') {
charIndex++
} else {
val startIndex = charIndex
while (charIndex < value.length && value[charIndex] != ' ') {
charIndex++
}
fullMoveCount = value.substring(startIndex, charIndex).toInt()
}
}
A implementação codifica a FEN dentro de bits de variáveis inteiras, os bitboards. Essas variáveis serão usadas posteriormente para criar uma representação de um tabuleiro de xadrez.
Do código, é possível notar que a leitura da FEN é feita desde o início até o final, caractere após caractere, com uma exceção sendo lançada sempre que uma informação esperada para uma posição é diferente do esperado.
A implementação completa está disponível no GitHub: https://github.com/welyab/ankobachen/blob/main/src/main/kotlin/com/welyab/ankobachen/fen/FEN.kt
Referências
- Forsyth-Edwards Notation – https://www.chessprogramming.org/Forsyth-Edwards_Notation