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

Todas as postagens da série ANKOBACHEN

Last Update: 12/06/2024

Tagged in:

, , , ,