Especialização em Sistemas com Python 3.12+ • Capítulo 19


🎯 Demonstração Prática: Mini Motor de Xadrez POO com Torre, Rei e Renderização Unicode

O exemplo abaixo implementa um núcleo funcional do motor de xadrez em Python orientado a objetos:

  • Conversão bidirecional entre notação algébrica internacional (a1 a h8) e matriz $8 \times 8$.
  • Tabuleiro gerenciando posições e peças com encapsulamento estrito.
  • Hierarquia polimórfica com classe abstrata Peca, especializando a Torre (peça deslizante) e o Rei (peça de passo).
  • Renderização visual no console utilizando símbolos Unicode autênticos (♔, ♚, ♖, ♜).
"""
demonstracao_motor_xadrez.py
Motor de Xadrez Orientado a Objetos Puro com Tabuleiro 8x8, Torre, Rei e Notação Algébrica.
Compatível com Python 3.12+.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum, auto
from typing import Final

# ==============================================================================
# 1. ENUMERAÇÕES E COORDENADAS
# ==============================================================================
class Cor(Enum):
    BRANCA = auto()
    PRETA = auto()

@dataclass(frozen=True, slots=True)
class Posicao:
    linha: int
    coluna: int

    def __str__(self) -> str:
        return f"[{self.linha}, {self.coluna}]"

@dataclass(frozen=True, slots=True)
class PosicaoXadrez:
    coluna: str # 'a' a 'h'
    linha: int  # 1 a 8

    def para_posicao_matriz(self) -> Posicao:
        r = 8 - self.linha
        c = ord(self.coluna.lower()) - ord('a')
        return Posicao(r, c)

    @classmethod
    def de_posicao_matriz(cls, pos: Posicao) -> PosicaoXadrez:
        col = chr(ord('a') + pos.coluna)
        lin = 8 - pos.linha
        return cls(coluna=col, linha=lin)

    def __str__(self) -> str:
        return f"{self.coluna}{self.linha}"

# ==============================================================================
# 2. HIERARQUIA DE PEÇAS POLIMÓRFICAS
# ==============================================================================
class Peca(ABC):
    def __init__(self, tabuleiro: Tabuleiro, cor: Cor) -> None:
        self.tabuleiro: Tabuleiro = tabuleiro
        self.cor: Cor = cor

    @abstractmethod
    def simbolo(self) -> str:
        ...

    @abstractmethod
    def movimentos_possiveis(self, pos_atual: Posicao) -> list[Posicao]:
        ...

class Torre(Peca):
    def simbolo(self) -> str:
        return "♖" if self.cor == Cor.BRANCA else "♜"

    def movimentos_possiveis(self, pos_atual: Posicao) -> list[Posicao]:
        movs: list[Posicao] = []
        # Direções retas ortogonais: Cima, Baixo, Esquerda, Direita
        direcoes = [(-1, 0), (1, 0), (0, -1), (0, 1)]

        for dr, dc in direcoes:
            r, c = pos_atual.linha + dr, pos_atual.coluna + dc
            while 0 <= r < 8 and 0 <= c < 8:
                destino = Posicao(r, c)
                peca_alvo = self.tabuleiro.obter_peca(destino)
                if peca_alvo is None:
                    movs.append(destino)
                elif peca_alvo.cor != self.cor:
                    movs.append(destino) # Captura permitida
                    break
                else:
                    break # Bloqueada por peça amiga
                r += dr
                c += dc
        return movs

class Rei(Peca):
    def simbolo(self) -> str:
        return "♔" if self.cor == Cor.BRANCA else "♚"

    def movimentos_possiveis(self, pos_atual: Posicao) -> list[Posicao]:
        movs: list[Posicao] = []
        # Vizinhança direta de 8 casas
        deltas = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]

        for dr, dc in deltas:
            r, c = pos_atual.linha + dr, pos_atual.coluna + dc
            if 0 <= r < 8 and 0 <= c < 8:
                destino = Posicao(r, c)
                alvo = self.tabuleiro.obter_peca(destino)
                if alvo is None or alvo.cor != self.cor:
                    movs.append(destino)
        return movs

# ==============================================================================
# 3. TABULEIRO 8x8 E ORQUESTRADOR
# ==============================================================================
class Tabuleiro:
    def __init__(self) -> None:
        # Matriz 8x8 contendo ponteiros para Peca ou None
        self._grade: list[list[Peca | None]] = [[None for _ in range(8)] for _ in range(8)]

    def obter_peca(self, pos: Posicao) -> Peca | None:
        return self._grade[pos.linha][pos.coluna]

    def posicionar_peca(self, peca: Peca, pos: Posicao) -> None:
        self._grade[pos.linha][pos.coluna] = peca

    def remover_peca(self, pos: Posicao) -> Peca | None:
        peca = self.obter_peca(pos)
        self._grade[pos.linha][pos.coluna] = None
        return peca

    def exibir(self) -> None:
        print("\n    a b c d e f g h")
        print("  +-----------------+")
        for r in range(8):
            num_linha = 8 - r
            linha_str = [f"{num_linha} |"]
            for c in range(8):
                peca = self._grade[r][c]
                linha_str.append(peca.simbolo() if peca else "·")
            linha_str.append(f"| {num_linha}")
            print(" ".join(linha_str))
        print("  +-----------------+")
        print("    a b c d e f g h\n")

# ==============================================================================
# 4. EXECUÇÃO DEMONSTRATIVA
# ==============================================================================
if __name__ == "__main__":
    print("=" * 80)
    print("      MOTOR DE XADREZ POO EM PYTHON 3.12+ (UNICODE ENGINE)")
    print("=" * 80)

    tabuleiro = Tabuleiro()

    # Montagem de um cenário tático de demonstração
    pos_rei_branco = PosicaoXadrez("e", 1).para_posicao_matriz()
    pos_torre_branca = PosicaoXadrez("a", 1).para_posicao_matriz()
    pos_rei_preto = PosicaoXadrez("e", 8).para_posicao_matriz()
    pos_torre_preta = PosicaoXadrez("h", 8).para_posicao_matriz()

    rei_b = Rei(tabuleiro, Cor.BRANCA)
    torre_b = Torre(tabuleiro, Cor.BRANCA)
    rei_p = Rei(tabuleiro, Cor.PRETA)
    torre_p = Torre(tabuleiro, Cor.PRETA)

    tabuleiro.posicionar_peca(rei_b, pos_rei_branco)
    tabuleiro.posicionar_peca(torre_b, pos_torre_branca)
    tabuleiro.posicionar_peca(rei_p, pos_rei_preto)
    tabuleiro.posicionar_peca(torre_p, pos_torre_preta)

    print(">> Estado Inicial do Tabuleiro:")
    tabuleiro.exibir()

    # Cálculo dos movimentos possíveis da Torre Branca em 'a1'
    movimentos_torre = torre_b.movimentos_possiveis(pos_torre_branca)
    movimentos_algebricos = [str(PosicaoXadrez.de_posicao_matriz(m)) for m in movimentos_torre]

    print(f"Torre Branca em 'a1': {len(movimentos_algebricos)} movimentos legais disponíveis:")
    print(f"Casas alcançáveis: {', '.join(sorted(movimentos_algebricos))}")
    print("-" * 80)

    # Executa movimento da Torre Branca de 'a1' para 'a5'
    pos_destino_a5 = PosicaoXadrez("a", 5).para_posicao_matriz()
    print(">> Executando jogada da Torre Branca: a1 -> a5")
    tabuleiro.remover_peca(pos_torre_branca)
    tabuleiro.posicionar_peca(torre_b, pos_destino_a5)

    tabuleiro.exibir()
    print("=" * 80)