Olá pessoal, como estamos?
Se você está chegando agora, estamos desenvolvendo um engine de Xadrez que será realmente forte. O código-fonte está disponível em https://github.com/elemarjr/StrongChess. Os posts anteriores estão disponíveis aqui.
Depois de algum tempo sem encostar nesse projeto, resolvi dar sequência aos trabalhos. A pausa foi intencional. Queria um refactoring no código feito pelo juanplopes que, infelizmente, não ocorreu (o cara está sem tempo).
Hoje, apresento algumas implementações importantes. No centro, está o início de implementação para Board: um tipo para representar uma posição completa no tabuleiro.
Que peça está em E4 (e outras questões desse tipo)?
Nos décimo post, apresentei Side (uma representação para a colocação das peças de um dos jogadores). Com os dados desse tipo, conseguimos responder a pergunta acima. A resposta ocorre por um enum. Observe:
[Flags]
public enum ChessPieces
{
None = 0,
King = 1,
Queen = 2,
Bishop = 4,
Knight = 8,
Rook = 16,
Pawn = 32
}
Para fazer testes abrangentes, optei por usar algum T4. Observe:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StrongChess.Model.Tests.Sets
{
using NUnit.Framework;
using SharpTestsEx;
using StrongChess.Model.Sets;
using StrongChess.Model.Pieces;
[TestFixture]
class SideTests_GetPieceAt
{
<#
var pieces = new [] { "Rook", "Knight", "Bishop",
"Queen", "King",
"Bishop", "Knight", "Rook" };
var files = new [] {"A", "B", "C", "D", "E", "F", "G", "H"};
for (int i = 0; i < 8; i++)
{
#>
[Test]
public void
GetPieceAt_<#= files[i] #>2InWhiteInitialPosition_ReturnsPawn()
{
// arrange
var side = Side.WhiteInitialPosition;
// act
var piece = side.GetPieceAt("<#= files[i] #>2");
// assert
piece.Should().Be(ChessPieces.Pawn);
}
[Test]
public void
GetPieceAt_<#= files[i] #>7InWhiteInitialPosition_ReturnsPawn()
{
// arrange
var side = Side.BlackInitialPosition;
// act
var piece = side.GetPieceAt("<#= files[i] #>7");
// assert
piece.Should().Be(ChessPieces.Pawn);
}
[Test]
public void
GetPieceAt_<#= files[i] #>1InWhiteInitialPosition_Returns<#= pieces[i] #>()
{
// arrange
var side = Side.WhiteInitialPosition;
// act
var piece = side.GetPieceAt("<#= files[i] #>1");
// assert
piece.Should().Be(ChessPieces.<#= pieces[i] #>);
}
[Test]
public void
GetPieceAt_<#= files[i] #>8InBlackInitialPosition_Returns<#= pieces[i] #>()
{
// arrange
var side = Side.BlackInitialPosition;
// act
var piece = side.GetPieceAt("<#= files[i] #>8");
// assert
piece.Should().Be(ChessPieces.<#= pieces[i] #>);
}
<# } #>
[Test]
public void GetPieceAt_E4InWhiteInitialPosition_ReturnsNone()
{
// arrange
var side = Side.WhiteInitialPosition;
// act
var piece = side.GetPieceAt("E4");
// assert
piece.Should().Be(ChessPieces.None);
}
}
}
Como você pode ver, nos testes, utilizou um novo método da classe Side. Trata-se de GetPieceAt, que recebe um Square como argumento. Eis sua implementação:
public ChessPieces GetPieceAt(Square sq)
{
if (!this.Occupation.Contains(sq))
return ChessPieces.None;
else if (this.Pawns.Locations.Contains(sq))
return ChessPieces.Pawn;
else if (this.Queens.Locations.Contains(sq))
return ChessPieces.Queen;
else if (this.Bishops.Locations.Contains(sq))
return ChessPieces.Bishop;
else if (this.Knights.Locations.Contains(sq))
return ChessPieces.Knight;
else if (this.Rooks.Locations.Contains(sq))
return ChessPieces.Rook;
return ChessPieces.King;
}
Como pode ver, trata-se de uma implementação muito simples. Basicamente, verifico qual Bitboard (conceito fundamental explicado na parte 1) possui o bit correspondente a casa ligada. Bacana, não?
Side – representando uma posição do jogo
Até aqui, temos tipos para representar regras de movimentação, para representar ocupações em um tabuleiro, mas não de uma posição propriamente dita.. esse é o propósito da classe Board. Observe:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StrongChess.Model
{
using StrongChess.Model.Sets;
using StrongChess.Model.Pieces;
using StrongChess.Model.Exceptions;
public struct Board
{
public Side White { get; private set; }
public Side Black { get; private set; }
public int NoPawnMovesCount { get; private set; }
public bool IsWhiteTurn { get; private set; }
public Square? Enpassant { get; private set; }
public Bitboard Occupation
{
get
{
return White.Occupation | Black.Occupation;
}
}
public Board(Side white, Side black,
int noPawnMovesCount = 0,
bool isWhiteTurn = true,
Square? enpassant = null)
: this()
{
this.White = white;
this.Black = black;
this.NoPawnMovesCount = noPawnMovesCount;
this.IsWhiteTurn = isWhiteTurn;
this.Enpassant = enpassant;
}
public static Board NewGame()
{
//...
}
public Board MakeMove(Move move)
{
//..
}
}
}
Esse tipo é bem simples … Temos
- White e Black – para representar cada um dos lados;
- NoPownMovesCount - para contar quantos lances foram realizados sem que um peão tenha sido movimentado (regra dos cinquenta lances);
- IsWhiteTurn – uma que indica quem deve jogar;
- Enpassant – correspondência com a casa do Enpassant (caso exista uma).
Há dois métodos:
- NewGame – configura a posição inicial para um jogo novo;
- MakeMove – que altera o estado “do tabuleiro”, criando uma nova posição.
Implementando NewGame
Antes de escrever código, escrevemos testes. Observe os testes desenvolvidos durante durante a escrita de NewGame:
[Test]
public void NewGame_WhiteInInitialPosition()
{
// arrange
var b = Board.NewGame();
//act
// assert
b.White.Should().Be(Side.WhiteInitialPosition);
}
[Test]
public void NewGame_BlackInInitialPosition()
{
// arrange
var b = Board.NewGame();
//act
// assert
b.Black.Should().Be(Side.BlackInitialPosition);
}
[Test]
public void NewGame_IsWhiteTurn_ReturnsTrue()
{
// arrange
var b = Board.NewGame();
//act
// assert
b.IsWhiteTurn.Should().Be(true);
}
[Test]
public void NewGame_NoPawnMovesCount_Returns0()
{
// arrange
var b = Board.NewGame();
//act
// assert
b.NoPawnMovesCount.Should().Be(0);
}
[Test]
public void Occupation_InitialPostion_ReturnsBlackAndWhiteOccupation()
{
// arrange
var b = Board.NewGame();
// act
// assert
b.Occupation.Should().Be((Bitboard)
(b.White.Occupation | b.Black.Occupation));
}
Bonito .. agora a implementação:
public static Board NewGame()
{
var result = new Board();
result.White = Side.WhiteInitialPosition;
result.Black = Side.BlackInitialPosition;
result.IsWhiteTurn = true;
result.NoPawnMovesCount = 0;
return result;
}
Bacana!
Implementando (os testes para) movimentos simples de peões
Movimentar peões deve ser muito fácil. Além de atualizar a posição, devemos atualizar a posição do enpassant.
[Test]
public void MakeMove_InitialPositionE2E4()
{
// arrange
var board = Board.NewGame();
// act
var result = board.MakeMove(new Move("E2", "E4"));
// assert
result.White.Pawns.Locations.Should().Be(
Bitboard.With.A2.B2.C2.D2.E4.F2.G2.H2
.Build()
);
}
[Test]
public void MakeMove_InitialPositionE2E4_EnpassantShouldBeE3()
{
// arrange
var board = Board.NewGame();
Square enpassant = new Square("E3");
// act
var result = board.MakeMove(new Move("E2", "E4"));
// assert
result.Enpassant.Value.Should().Be(enpassant);
//Assert.Fail(result.Enpassant.ToString());
}
[Test]
public void MakeMove_InitialPositionE2E4AndE7E5_EnpassantShouldBeE6()
{
// arrange
var board = Board.NewGame();
Square enpassant = new Square("E6");
// act
var result = board
.MakeMove(new Move("E2", "E4"))
.MakeMove(new Move("E7", "E5"));
// assert
result.Enpassant.Value.Should().Be(enpassant);
//Assert.Fail(result.Enpassant.ToString());
}
[Test]
public void MakeMove_InitialPositionE2E4AndE7E6_EnpassantShouldBeNull()
{
// arrange
var board = Board.NewGame();
// act
var result = board
.MakeMove(new Move("E2", "E4"))
.MakeMove(new Move("E7", "E6"));
// assert
Assert.IsTrue(result.Enpassant == null);
//Assert.Fail(result.Enpassant.ToString());
}
Acredito que o código se exmplica sozinho.
Implementando (os testes) para movimentos de captura por peões
Capturas por pões são bacanas (na verdade, movimentos simples). Alguns testes evidentes são indicados abaixo:
[Test]
public void MakeMove_InitialPositionE2E4AndD7D5_E4D5ShouldBePossible()
{
// arrange
var board = Board.NewGame();
Move m = new Move("E4", "D5");
// act
var result = board
.MakeMove(new Move("E2", "E4"))
.MakeMove(new Move("D7", "D5"));
var c = result.White.Pawns.GetAllMoves(~result.Occupation,
result.Black.Occupation, "D6")
.Where(move => move.From == m.From && move.To == m.To);
c.Count().Should().Be(1);
}
[Test]
public void MakeMove_InitialPositionE2E4AndD7D5AndE4D5_WhitePawnOccupiesD5()
{
// arrange
var board = Board.NewGame();
Square s = "D5";
// act
var result = board
.MakeMove(new Move("E2", "E4"))
.MakeMove(new Move("D7", "D5"))
.MakeMove(new Move("E4", "D5"));
// assert
result.White.Pawns.Locations.Contains(s).Should().Be(true);
}
[Test]
public void MakeMove_InitialPositionE2E4AndD7D5AndE4D5_BlackPawnLeavesTheBoard()
{
// arrange
var board = Board.NewGame();
// act
var result = board
.MakeMove(new Move("E2", "E4"))
.MakeMove(new Move("D7", "D5"))
.MakeMove(new Move("E4", "D5"));
// assert
result.Black.Pawns.Locations.Should().Be(
Bitboard.With.H7.G7.F7.E7.C7.B7.A7.Build());
}
Implementando (os testes) para movimentos de enpassant
Enpassant é uma forma de captura diferente no Xadrez. Precisa de testes diferentes também. Observe:
[Test]
public void MakeMove_InitialPositionE2E4AndD7D5AndE4D5AndF7F5AndE5F6_BlackPawnLeavesTheBoard()
{
// arrange
var board = Board.NewGame();
// act
var result = board
.MakeMove(new Move("E2", "E4"))
.MakeMove(new Move("D7", "D5"))
.MakeMove(new Move("E4", "E5"))
.MakeMove(new Move("F7", "F5"))
.MakeMove(new Move("E5", "F6"));
// assert
result.Black.Pawns.Locations.Should().Be(
Bitboard.With.H7.G7.E7.D5.C7.B7.A7.Build());
}
Movimentos inválidos… Exception…
Se um movimento inválido for executado, uma exception deverá ser disparada. Observe:
[Test]
public void MakeMove_InitialPositionE2E5_ShouldThrowInvalidMoveException()
{
// arrange
var board = Board.NewGame();
var move = new Move("E2", "E5");
// act
board.Executing((b) => b.MakeMove(move))
.Throws<InvalidMoveException>();
}
Uma implementação suficiente para passar nos testes
Inicialmente, não estou tomando cuidado com a solução. Desejo apenas passar nos testes. Observe:
public Board MakeMove(Move move)
{
Square? enpassant = null;
var moving = (this.IsWhiteTurn ? White : Black);
var notmoving = (this.IsWhiteTurn ? Black : White);
if (moving.GetPieceAt(move.From) == ChessPieces.Pawn)
{
var isvalid = moving.Pawns
.GetAllMoves(~Occupation, notmoving.Occupation,
this.Enpassant)
.Count(m => m.From == move.From && m.To == move.To)
> 0;
if (!isvalid)
throw new InvalidMoveException(move, this);
var locations = moving.Pawns.Locations
& (~move.From.AsBoard)
| move.To.AsBoard;
IPawns pawns = IsWhiteTurn ?
(IPawns) new WhitePawns(locations) :
(IPawns) new BlackPawns(locations);
if (move.From.File == move.To.File &&
Math.Abs(move.From.Rank - move.To.Rank) > 1)
enpassant = new Square(
(move.From.Rank + move.To.Rank) / 2,
move.From.File
);
moving = new Side(
moving.KingLocation,
moving.Queens,
moving.Bishops,
moving.Knights,
moving.Rooks,
pawns
);
Bitboard negative;
if (move.To != Enpassant)
negative = ~move.To.AsBoard;
else if (move.To.Rank == 6)
negative = ~(new Square(5, move.To.File).AsBoard);
else
negative = ~(new Square(4, move.To.File).AsBoard);
notmoving = new Side(
notmoving.KingLocation,
new PieceSet<Queen>(notmoving.Queens.Locations & negative),
new PieceSet<Bishop>(notmoving.Bishops.Locations & negative),
new PieceSet<Knight>(notmoving.Knights.Locations & negative),
new PieceSet<Rook>(notmoving.Rooks.Locations & negative),
(IsWhiteTurn ?
(IPawns)new BlackPawns(notmoving.Pawns.Locations & negative)
:
(IPawns)new WhitePawns(notmoving.Pawns.Locations & negative)
)
);
}
else
{
throw new NotImplementedException(
string.Format("There is no support to {0} moves", moving.GetPieceAt(move.From))
);
}
var white = (this.IsWhiteTurn ? moving : notmoving);
var black = (this.IsWhiteTurn ? notmoving : moving);
return new Board(white, black, 0, !IsWhiteTurn, enpassant);
}
Grande e feio! Mas funciona… Nos próximos posts partimos para um refactoring.
Por hoje, era isso.
![]()






maio 18th, 2011 → 23:32
[...] post anterior, começamos o desenvolvimento do modelo que representa uma posição de jogo: a classe Board. Mais [...]