Olá pessoal. Como estamos?!
Depois de muito tempo sem tocar no código desse engine de Xadrez, estou retomando as atividades. Meu desejo era deixar o projeto parado até que o @juanplopes fizesse um refactoring/review. Entretanto, atendendo a uma “solicitação” do @leandronet resolvi reconsiderar minha posição.
Até aqui, trabalhamos bastante da representação de dados relacionados ao tabuleiro. Além disso, implementamos a lógica para geração de movimentos válidos, considerando, inclusive: xeques (descobertos), escapadas, enpassant.
Hoje, vamos implementar a verificação para possibilidade de roque.
O código-fonte produzido até agora está em https://github.com/ElemarJR/StrongChess.
Se desejar consultar os posts anteriores, considere http://elemarjr.net/tag/xadrez.
Explicando o Roque
Roque, no xadrez, é uma jogada que envolve a movimentação de duas peças no mesmo lance. Sua função é, normalmente, proteger o Rei ao deslocá-lo para um dos cantos do tabuleiro e conectar as torres na primeira fileira.
Há dois tipos de roque:
Roque pequeno
No roque pequeno, o Rei movimenta-se duas casas em direção à Torre da ala do Rei (o Rei branco vai para a casa g1, e o Rei preto vai para a casa g8). Em seguida a Torre é colocada na casa que o Rei saltou (a Torre branca vai para f1, e a Torre preta vai para f8).
![]()
Roque grande
No roque grande, o Rei faz Roque com a Torre da ala da Dama (o Rei branco vai para a casa c1, e o Rei preto vai para a casa c8). A Torre vai para a casa que o Rei pulou (a Torre branca vai para a casa d1, e a Torre preta vai para a casa d8).
![]()
Condições para que ocorra o Roque
Para se poder efetuar um roque, as seguintes condições são necessárias:
- Rei não foi movido desde o início do jogo;
- Torre a usar no roque nunca foi movida;
- Rei não está em Xeque;
- Nenhuma das casas pelas quais o Rei irá passar ou ficar está sob ataque;
- Casas entre o rei e a torre estão desocupadas.
Entendido!? Go code!
REGRA 1: O rei não foi movido desde o início do jogo
No nosso engine, temos uma struct, chamada Side (veja no Github), que contém a informação sobre a posição das peças de um “lado” no tabuleiro.
Para atender a essa regra no Xadrez, adiciono uma propriedade bool, somente leitura, nessa struct com a informação se o rei foi ou não movimentado. O valor (padrão) dessa propriedade é definido no construtor. Além disso, mesmo que a informação passada para o constutor indique que o rei não foi movimentado, se ele não estiver na posição original, deverá retornar false.
Começamos pelos testes.
[Test]
public void KingWasMoved_SideWithKingInE1AndNoMoveInformation_ReturnsFalse()
{
var white = new Side("E1",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new WhitePawns());
white.KingWasMoved.Should().Be(false);
}
Agora, o mesmo teste para peças pretas.
[Test]
public void KingWasMoved_SideWithBlackKingInE8AndNoMoveInformation_ReturnsFalse()
{
var black = new Side("E8",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new BlackPawns());
black.KingWasMoved.Should().Be(false);
}
Movendo os reis…
[Test]
public void KingWasMoved_SideWithWhiteKingInE2AndNoMoveInformation_ReturnsTrue()
{
var white = new Side("E2",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new WhitePawns());
white.KingWasMoved.Should().Be(true);
}
[Test]
public void KingWasMoved_SideWithBlackKingInE7AndNoMoveInformation_ReturnsTrue()
{
var black = new Side("E7",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new BlackPawns());
black.KingWasMoved.Should().Be(true);
}
Considerando a alteração do construtor para receber a informação de que a peça foi movimentada.
[Test]
public void KingWasMoved_SideWithWhiteKingInE1WhereKingWasMoved_ReturnsTrue()
{
var white = new Side("E1",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new WhitePawns(),
kingWasMoved: true);
white.KingWasMoved.Should().Be(true);
}
[Test]
public void KingWasMoved_SideWithBlackKingInE8WhereKingWasMoved_ReturnsTrue()
{
var black = new Side("E8",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new BlackPawns(),
true);
black.KingWasMoved.Should().Be(true);
}
Testes escritos, vamos a implementação. Primeiro a propriedade:
public bool _kingWasMoved;
public bool KingWasMoved
{
get
{
if (_kingWasMoved) return true;
if (this.IsWhite)
return (KingLocation.AsBoard != Bitboard.With.E1.Build());
else
return (KingLocation.AsBoard != Bitboard.With.E8.Build());
}
}
Repare como considero a “cor” do lado para acertar posição.
Agora, o construtor:
public Side(
Square kingLocation,
PieceSet<Queen> queens,
PieceSet<Bishop> bishops,
PieceSet<Knight> knights,
PieceSet<Rook> rooks,
IPawns pawns,
bool kingWasMoved = false
)
: this()
{
_King = new PieceSet<King>(kingLocation.AsBoard);
Queens = queens;
Rooks = rooks;
Bishops = bishops;
Knights = knights;
Pawns = pawns;
Occupation = queens.Locations | rooks.Locations | _King.Locations |
bishops.Locations | knights.Locations | pawns.Locations;
this._kingWasMoved = kingWasMoved;
}
Executando os testes:
Perfeito!
REGRA 2: Torre a usar no roque nunca foi movida
Resolvi aplicar lógica semelhante para controlar “o movimento” das torres. Primeiro os testes:
[Test]
public void KingCastleRookWasMoved_SideWithWhiteRookInH1AndNoMoveInformation_ReturnsFalse()
{
var white = new Side("E1",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("H1")),
new WhitePawns());
white.KingCastleRookWasMoved.Should().Be(false);
}
[Test]
public void KingCastleRookWasMoved_SideWithWhiteRookInH1WhereRookWasMoved_ReturnsTrue()
{
var white = new Side("E1",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("H1")),
new WhitePawns(),
kingCastleRookWasMoved: true);
white.KingCastleRookWasMoved.Should().Be(true);
}
[Test]
public void KingCastleRookWasMoved_SideWithBlackRookInH8WhereRookWasMoved_ReturnsTrue()
{
var black = new Side("E8",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("H8")),
new BlackPawns(),
kingCastleRookWasMoved: true);
black.KingCastleRookWasMoved.Should().Be(true);
}
[Test]
public void KingCastleRookWasMoved_SideWithBlackRookInH8AndNoMoveInformation_ReturnsFalse()
{
var black = new Side("E8",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("H8")),
new BlackPawns());
black.KingCastleRookWasMoved.Should().Be(false);
}
[Test]
public void KingCastleRookWasMoved_SideWithWhiteRookInH2AndNoMoveInformation_ReturnsTrue()
{
var white = new Side("E2",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("H2")),
new WhitePawns());
white.KingCastleRookWasMoved.Should().Be(true);
}
[Test]
public void KingCastleRookWasMoved_SideWithBlackRookInH7AndNoMoveInformation_ReturnsTrue()
{
var black = new Side("E7",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("H7")),
new BlackPawns());
black.KingCastleRookWasMoved.Should().Be(true);
}
Agora, a propriedade e o construtor:
public bool _kingCastleRookWasMoved;
public bool KingCastleRookWasMoved
{
get
{
if (_kingCastleRookWasMoved) return true;
if (this.IsWhite)
return !this.Rooks.Locations.Contains(new Square("H1"));
else
return !this.Rooks.Locations.Contains(new Square("H8"));
}
}
public Side(
Square kingLocation,
PieceSet<Queen> queens,
PieceSet<Bishop> bishops,
PieceSet<Knight> knights,
PieceSet<Rook> rooks,
IPawns pawns,
bool kingWasMoved = false,
bool kingCastleRookWasMoved = false
)
: this()
{
_King = new PieceSet<King>(kingLocation.AsBoard);
Queens = queens;
Rooks = rooks;
Bishops = bishops;
Knights = knights;
Pawns = pawns;
Occupation = queens.Locations | rooks.Locations | _King.Locations |
bishops.Locations | knights.Locations | pawns.Locations;
this._kingWasMoved = kingWasMoved;
this._kingCastleRookWasMoved = kingCastleRookWasMoved;
}
Perfeito! Agora, o lado da dama
[Test]
public void QueenCastleRookWasMoved_SideWithWhiteRookInA1AndNoMoveInformation_ReturnsFalse()
{
var white = new Side("E1",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A1")),
new WhitePawns());
white.QueenCastleRookWasMoved.Should().Be(false);
}
[Test]
public void QueenCastleRookWasMoved_SideWithWhiteRookInA1WhereRookWasMoved_ReturnsTrue()
{
var white = new Side("E1",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A1")),
new WhitePawns(),
queenCastleRookWasMoved: true);
white.QueenCastleRookWasMoved.Should().Be(true);
}
[Test]
public void QueenCastleRookWasMoved_SideWithBlackRookInH8WhereQueenWasMoved_ReturnsTrue()
{
var black = new Side("E8",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A8")),
new BlackPawns(),
queenCastleRookWasMoved: true);
black.QueenCastleRookWasMoved.Should().Be(true);
}
[Test]
public void QueenCastleRookWasMoved_SideWithBlackRookInA8AndNoMoveInformation_ReturnsFalse()
{
var black = new Side("E8",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A8")),
new BlackPawns());
black.QueenCastleRookWasMoved.Should().Be(false);
}
[Test]
public void QueenCastleRookWasMoved_SideWithWhiteRookInA2AndNoMoveInformation_ReturnsTrue()
{
var white = new Side("E2",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A2")),
new WhitePawns());
white.QueenCastleRookWasMoved.Should().Be(true);
}
[Test]
public void QueenCastleRookWasMoved_SideWithBlackRookInA7AndNoMoveInformation_ReturnsTrue()
{
var black = new Side("E7",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A7")),
new BlackPawns());
black.QueenCastleRookWasMoved.Should().Be(true);
}
Propriedade:
public bool _queenCastleRookWasMoved;
public bool QueenCastleRookWasMoved
{
get
{
if (_queenCastleRookWasMoved) return true;
if (this.IsWhite)
return !this.Rooks.Locations.Contains(new Square("A1"));
else
return !this.Rooks.Locations.Contains(new Square("A8"));
}
}
Feito!
REGRA 3: Rei não está em xeque
Já temos suporte em código para validar essa regra. No passado, escrevemos a lógica para verificar se uma “casa” está sendo atacada. Hoje, vamos apenas deixar as coisas mais “explícitas”. Eis o método que escrevi:
public bool IsInCheck(Side enemy)
{
return enemy.Attacks(this.KingLocation, this.Occupation);
}
Dois testes básicos, para “tranquilidade”.
[Test]
public void KingInCheck_InCheckScenario_ReturnsTrue()
{
var black = new Side("E7",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A7")),
new BlackPawns());
var white = new Side("A2",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new BlackPawns());
white.IsInCheck(black).Should().Be(true);
}
[Test]
public void KingInCheck_NotInCheckScenario_ReturnsFalse()
{
var black = new Side("E7",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(new Square("A7")),
new BlackPawns());
var white = new Side("B2",
new PieceSet<Queen>(),
new PieceSet<Bishop>(),
new PieceSet<Knight>(),
new PieceSet<Rook>(),
new BlackPawns());
white.IsInCheck(black).Should().Be(false);
}
REGRA 4: Nenhuma das casas pelas quais o Rei irá passar ou ficar está sob ataque
A lógica aqui é bem simples, também. Afinal, já temos o método para verificar se uma casa está sob ataque. Veja:
public bool IsKingCastlePathUnderAttack(Side enemy)
{
if (this.IsWhite)
{
return
enemy.Attacks("E1", this.Occupation) ||
enemy.Attacks("F1", this.Occupation) ||
enemy.Attacks("G1", this.Occupation);
}
else
{
return
enemy.Attacks("E8", this.Occupation) ||
enemy.Attacks("F8", this.Occupation) ||
enemy.Attacks("G8", this.Occupation);
}
}
public bool IsQueenCastlePathUnderAttack(Side enemy)
{
if (this.IsWhite)
{
return
enemy.Attacks("E1", this.Occupation) ||
enemy.Attacks("D1", this.Occupation) ||
enemy.Attacks("C1", this.Occupation);
}
else
{
return
enemy.Attacks("E8", this.Occupation) ||
enemy.Attacks("D8", this.Occupation) ||
enemy.Attacks("C8", this.Occupation);
}
}
Não escrevi testes para esses métodos!
REGRA 5: Casas entre o rei e a torre estão desocupadas
Enfim, a última regra: Casas desocupadas. Novamente, temos o trabalho simplificado pelo que já fizemos nos posts anteriores. Observe:
public bool IsKingCastlePathClear()
{
if (this.IsWhite)
{
return
this.Occupation.IsClear(Bitboard.With.F1.G1.Build());
}
else
{
return
this.Occupation.IsClear(Bitboard.With.F8.G8.Build());
}
}
public bool IsQueenCastlePathClear()
{
if (this.IsWhite)
{
return
this.Occupation.IsClear(Bitboard.With.D1.C1.B1.Build());
}
else
{
return
this.Occupation.IsClear(Bitboard.With.D8.C8.B8.Build());
}
}
Combinando tudo
Pronto! Agora que já temos toda lógica para cada uma das regras do roque implementadas, vamos escrever os métodos que verificam todas as regras. Observe:
public bool CanPerformKingCastle(Side enemy)
{
return
!this.KingWasMoved &&
!this.KingCastleRookWasMoved &&
!this.IsInCheck(enemy) &&
!this.IsKingCastlePathUnderAttack(enemy) &&
this.IsKingCastlePathClear();
}
public bool CanPerformQueenCastle(Side enemy)
{
return
!this.KingWasMoved &&
!this.QueenCastleRookWasMoved &&
!this.IsInCheck(enemy) &&
!this.IsQueenCastlePathUnderAttack(enemy) &&
this.IsQueenCastlePathClear();
}
Pronto!
![]()






Leandro Daniel
19/11/2011
Poxa, muito bom Elemar, obrigado!
Abs,
Leandro Daniel
Regis Araujo
19/11/2011
Parabens pelo codigos. Tenho acompanhado deste inicio o projeto, aprendi varios conceitos interessantes que foram muito iteis em meus projetos. Valew kra.
elemarjr
20/11/2011
Que bacana, man! Show de bola. Se importa em dar alguns exemplos?