Artículos anteriores:
Juego de Ajedrez (I). Introducción a la Programación Orientada a Objetos
Juego de Ajedrez (II). Creando la estructura del proyecto
El código completo tanto en C# como en Visual Basic .NET está disponible en:
Códigos de muestra - Ejemplos MSDN. Juego de Ajedrez
Una vez creada la estructura de clases del proyecto voy a definir la funcionalidad de cada una de las piezas del juego. Para ello crearé una clase para cada tipo de pieza que herede de la clase abstracta
ChessPiece.
En primer lugar crearé una serie de elementos que nos serán de utilidad a la hora de definir el comportamiento de la pieza.
En el archivo
Chess.cs/Chess.vb voy a crear una estructura
Movement para definir un movimiento de una pieza. La estructura tendrá dos campos enteros:
Forward que indica el número de celdas que la pieza se moverá hacia adelante (en caso de moverse hacia atrás se indicarán números negativos) y
ToRight que indica el numeró de celdas que la pieza se moverá hacia la derecha (en caso de moverse hacia la izquierda se indicarán números negativos).
struct Movement
{
public int Forward;
public int ToRight;
}
Structure Movement
Public Forward As Integer
Public ToRight As Integer
End Structure
En la clase
Chessboard voy a crear una nueva sobrecarga para el método
GetSquare que recibirá como parámetros una celda origen y un movimiento y que devolverá la celda a la que se movería la pieza ubicada en la celda origen tras realizar el movimiento.
public Square GetSquare(Square fromSquare, Movement move)
{
ChessPiece piece = fromSquare.Piece;
if (piece == null) return null;
return GetSquare(fromSquare.Row
+ (piece.Color == PlayerOnTop ? move.Forward : -move.Forward)
, fromSquare.Column + move.ToRight);
}
Public Function GetSquare(fromSquare As Square, move As Movement) As Square
Dim piece As ChessPiece = fromSquare.Piece
If piece Is Nothing Then Return Nothing
Return GetSquare(fromSquare.Row _
+ IIf(piece.Color = PlayerOnTop, move.Forward, -move.Forward) _
, fromSquare.Column + move.ToRight)
End Function
Si en la celda origen no hay ninguna pieza el método no devuelve nada. Si la hay calcula la celda destino teniendo en cuenta el color de la pieza, ya que un movimiento hacia adelante de una pieza blanca no va en la misma dirección que un movimiento hacia adelante de una pieza negra.
En la clase abstracta
ChessPiece definiré un método
CanBeDestination que determinará si una celda puede ser destino del movimiento de la pieza. Básicamente una pieza se puede mover a una celda si la celda está vacía o si está ocupada por una pieza de otro color (en este caso se "comería" a la otra pieza).
protected virtual bool CanBeDestination(Square destinationSquare)
{
if (destinationSquare == null) return false;
return (destinationSquare.Piece == null || destinationSquare.Piece.Color != Color);
}
Public Function CanBeDestination(destinationSquare As Square) As Boolean
If destinationSquare Is Nothing Then Return False
Return (destinationSquare.Piece Is Nothing OrElse destinationSquare.Piece.Color <> Color)
End Function
También crearé un método
DestinationSquaresForMoves que recibirá como parámetro una serie de movimientos y devolverá las celdas a las que podría moverse la pieza realizando esos movimientos. Para ello obtengo para cada movimiento cual sería la celda destino utilizando el método
GetSquare y compruebo que la celda sea un destino válido utilizando el método
CanBeDestination.
protected IEnumerable<Square> DestinationSquaresForMoves(IEnumerable<Movement> moves)
{
Chessboard board = BoardSquare.Board;
List<Square> squares = new List<Square>();
foreach (Movement move in moves)
{
Square destination = board.GetSquare(BoardSquare, move);
if (CanBeDestination(destination))
squares.Add(destination);
}
return squares;
}
Protected Function DestinationSquaresForMoves(
moves As IEnumerable(Of Movement)) As IEnumerable(Of Square)
Dim board As Chessboard = BoardSquare.Board
Dim squares As New List(Of Square)
For Each move As Movement In moves
Dim destination As Square = board.GetSquare(BoardSquare, move)
If CanBeDestination(destination) Then
squares.Add(destination)
End If
Next
Return squares
End Function
Por último crearé un método, similar al anterior, que devolverá los posibles celdas destino al moverse la pieza en una dirección. Este método nos será útil para obtener los posibles destinos de los movimientos de torres, alfiles y reinas. La dirección en la que se moverá se determinará por dos valores enteros: un incremento hacia adelante (si es negativo indicará una dirección hacia atrás) y un incremento hacia la derecha (si es negativo indicará una dirección hacia la izquierda).
protected IEnumerable<Square> DestinationSquaresOnOneDirection(
int forwardIncrement, int rightIncrement)
{
Chessboard board = BoardSquare.Board;
List<Square> squares = new List<Square>();
int forward = forwardIncrement;
int right = rightIncrement;
bool pieceOrEndFounded = false;
while (!pieceOrEndFounded)
{
Square destination = board.GetSquare(BoardSquare, new Movement { Forward = forward, ToRight = right });
if (CanBeDestination(destination))
squares.Add(destination);
pieceOrEndFounded = (destination == null || destination.Piece != null);
forward += forwardIncrement;
right += rightIncrement;
}
return squares;
}
Protected Function DestinationSquaresOnOneDirection(
forwardIncrement As Integer, rightIncrement As Integer) As IEnumerable(Of Square)
Dim board As Chessboard = BoardSquare.Board
Dim squares As New List(Of Square)
Dim forward As Integer = forwardIncrement
Dim right As Integer = rightIncrement
Dim pieceOrEndFounded As Boolean = False
Do While Not pieceOrEndFounded
Dim destination As Square = board.GetSquare(BoardSquare,
New Movement() With {.Forward = forward, .ToRight = right})
If CanBeDestination(destination) Then
squares.Add(destination)
End If
pieceOrEndFounded = (destination Is Nothing OrElse destination.Piece IsNot Nothing)
forward += forwardIncrement
right += rightIncrement
Loop
Return squares
End Function
Básicamente el método va recorriendo las celdas en la dirección especificada y añadiéndolas a la lista a devolver hasta que llega al final del tablero o a una celda ocupada por otra pieza.
Bien, con todas estas "armas" ya estamos en condiciones de empezar a implementar las clases para los diferentes tipos de piezas:
El rey
Para implementar la funcionalidad del rey voy a crear una clase King que heredará de la clase ChessPiece y en la que deberemos sobrescribir el método GetDestinationSquares que definimos como abstracto en la clase base.
Este método debe devolver las celdas a las que puede moverse la pieza para lo que utilizaré el método DestinationSquareForMoves pasándole los diferentes movimientos que puede realizar el rey.
De esta forma la clase
King quedaría:
class King : ChessPiece
{
public King(PlayerColor color) : base(color) { }
private Movement[] moves =
{
new Movement() {Forward=1, ToRight=-1 },
new Movement() {Forward=1, ToRight=0 },
new Movement() {Forward=1, ToRight=1 },
new Movement() {Forward=0, ToRight=-1 },
new Movement() {Forward=0, ToRight=1 },
new Movement() {Forward=-1, ToRight=-1 },
new Movement() {Forward=-1, ToRight=0 },
new Movement() {Forward=-1, ToRight=1 }
};
public override IEnumerable<Square> GetDestinationSquares()
{
if (BoardSquare == null) return null;
return DestinationSquaresForMoves(moves);
}
}
Class King
Inherits ChessPiece
Public Sub New(pieceColor As PlayerColor)
MyBase.New(pieceColor)
End Sub
Private moves As Movement() = {
New Movement() With {.Forward = 1, .ToRight = -1},
New Movement() With {.Forward = 1, .ToRight = 0},
New Movement() With {.Forward = 1, .ToRight = 1},
New Movement() With {.Forward = 0, .ToRight = -1},
New Movement() With {.Forward = 0, .ToRight = 1},
New Movement() With {.Forward = -1, .ToRight = -1},
New Movement() With {.Forward = -1, .ToRight = 0},
New Movement() With {.Forward = -1, .ToRight = 1}
}
Public Overrides Function GetDestinationSquares() As IEnumerable(Of Square)
If BoardSquare Is Nothing Then Return Nothing
Return DestinationSquaresForMoves(moves)
End Function
End Class
La Torre
De forma similar definiré la clase Rook que definirá el comportamiento del la torre. La única diferencia es que para obtener las posibles celdas destino utilizaré el método DestinationSquaresOnOneDirection con las cuatro direcciones en las que se puede mover la torre: hacia la izquierda (Forward: 0, ToRight: -1), hacia la derecha (Forward: 0, ToRight: 1), hacia adelante (Forward: 1, ToRight: 0) y hacia atrás (Forward: -1, ToRight: 0).
class Rook : ChessPiece
{
public Rook(PlayerColor color) : base(color) { }
public override IEnumerable<Square> GetDestinationSquares()
{
if (BoardSquare == null) return null;
List<Square> posibleSquares = new List<Square>();
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, -1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, 1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 0));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 0));
return posibleSquares;
}
}
Class Rook
Inherits ChessPiece
Public Sub New(pieceColor As PlayerColor)
MyBase.New(pieceColor)
End Sub
Public Overrides Function GetDestinationSquares() As IEnumerable(Of Square)
If BoardSquare Is Nothing Then Return Nothing
Dim posibleSquares As New List(Of Square)
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, -1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, 1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 0))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 0))
Return posibleSquares
End Function
End Class
El Alfil, la Reina y el Caballo
Siguiendo el ejemplo del rey y la torre podemos definir las clases Bishop para el alfil, Queen para la reina y Knight para el caballo.
class Bishop : ChessPiece
{
public Bishop(PlayerColor color) : base(color) { }
public override IEnumerable<Square> GetDestinationSquares()
{
if (BoardSquare == null) return null;
List<Square> posibleSquares = new List<Square>();
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, -1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, -1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 1));
return posibleSquares;
}
}
class Queen : ChessPiece
{
public Queen(PlayerColor color) : base(color) { }
public override IEnumerable<Square> GetDestinationSquares()
{
if (BoardSquare == null) return null;
List<Square> posibleSquares = new List<Square>();
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, -1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 0));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, -1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, 1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, -1));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 0));
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 1));
return posibleSquares;
}
}
class Knight : ChessPiece
{
public Knight(PlayerColor color) : base(color) { }
private Movement[] moves =
{
new Movement {Forward=1, ToRight=-2 },
new Movement {Forward=2, ToRight=-1 },
new Movement {Forward=2, ToRight=1 },
new Movement {Forward=1, ToRight=2 },
new Movement {Forward=-1, ToRight=-2 },
new Movement {Forward=-2, ToRight=-1 },
new Movement {Forward=-2, ToRight=1 },
new Movement {Forward=-1, ToRight=2 }
};
public override IEnumerable<Square> GetDestinationSquares()
{
if (BoardSquare == null) return null;
return DestinationSquaresForMoves(moves);
}
}
Class Bishop
Inherits ChessPiece
Public Sub New(pieceColor As PlayerColor)
MyBase.New(pieceColor)
End Sub
Public Overrides Function GetDestinationSquares() As IEnumerable(Of Square)
If BoardSquare Is Nothing Then Return Nothing
Dim posibleSquares As New List(Of Square)
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, -1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, -1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 1))
Return posibleSquares
End Function
End Class
Class Queen
Inherits ChessPiece
Public Sub New(pieceColor As PlayerColor)
MyBase.New(pieceColor)
End Sub
Public Overrides Function GetDestinationSquares() As IEnumerable(Of Square)
If BoardSquare Is Nothing Then Return Nothing
Dim posibleSquares As New List(Of Square)
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, -1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 0))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(1, 1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, -1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(0, 1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, -1))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 0))
posibleSquares.AddRange(DestinationSquaresOnOneDirection(-1, 1))
Return posibleSquares
End Function
End Class
Class Knight
Inherits ChessPiece
Public Sub New(pieceColor As PlayerColor)
MyBase.New(pieceColor)
End Sub
Private moves As Movement() = {
New Movement() With {.Forward = 1, .ToRight = -2},
New Movement() With {.Forward = 2, .ToRight = -1},
New Movement() With {.Forward = 2, .ToRight = 1},
New Movement() With {.Forward = 1, .ToRight = 2},
New Movement() With {.Forward = -1, .ToRight = -2},
New Movement() With {.Forward = -2, .ToRight = -1},
New Movement() With {.Forward = -2, .ToRight = 1},
New Movement() With {.Forward = -1, .ToRight = 2}
}
Public Overrides Function GetDestinationSquares() As IEnumerable(Of Square)
If BoardSquare Is Nothing Then Return Nothing
Return DestinationSquaresForMoves(moves)
End Function
End Class
El peón
El caso del peón es un caso especial, sus movimientos dependen de diferentes variables que las del resto de piezas:
- Puede realizar un movimiento de dos celdas hacia adelante siempre que se encuentre en la posición inicial y las dos celdas de delante estén vacías
- Puede realizar un movimiento de una celda hacia adelante si ésta está vacía
- Puede realizar un movimiento de una posición hacia adelante y otra hacia izquierda o derecha siempre que la celda destino esté ocupada por una pieza de otro color
Así que en el caso de la clase Pawn que definirá el comportamiento del peón deberemos añadir algo más de lógica al método GetDestinationSquares.
class Pawn : ChessPiece
{
public Pawn(PlayerColor color) : base(color) { }
public override IEnumerable<Square> GetDestinationSquares()
{
if (BoardSquare == null) return null;
Chessboard board = BoardSquare.Board;
bool isInStartPosition = (board.GetSquare(BoardSquare, new Movement { Forward = -2, ToRight = 0 }) == null);
List<Square> posibleSquares = new List<Square>();
Square destinationSquare = board.GetSquare(BoardSquare, new Movement { Forward = 1, ToRight = 0 });
if (destinationSquare != null && destinationSquare.Piece == null)
{
posibleSquares.Add(destinationSquare);
if (isInStartPosition)
{
destinationSquare = board.GetSquare(BoardSquare, new Movement { Forward = 2, ToRight = 0 });
if (destinationSquare != null && destinationSquare.Piece == null)
posibleSquares.Add(destinationSquare);
}
}
destinationSquare = board.GetSquare(BoardSquare, new Movement { Forward = 1, ToRight = -1 });
if (destinationSquare != null && destinationSquare.Piece != null && destinationSquare.Piece.Color != Color)
posibleSquares.Add(destinationSquare);
destinationSquare = board.GetSquare(BoardSquare, new Movement { Forward = 1, ToRight = 1 });
if (destinationSquare != null && destinationSquare.Piece != null && destinationSquare.Piece.Color != Color)
posibleSquares.Add(destinationSquare);
return posibleSquares;
}
}
Class Pawn
Inherits ChessPiece
Public Sub New(pieceColor As PlayerColor)
MyBase.New(pieceColor)
End Sub
Public Overrides Function GetDestinationSquares() As IEnumerable(Of Square)
If BoardSquare Is Nothing Then Return Nothing
Dim board As Chessboard = BoardSquare.Board
Dim isInStartPosition =
(board.GetSquare(BoardSquare, New Movement With {.Forward = -2, .ToRight = 0}) Is Nothing)
Dim posibleSquares As New List(Of Square)
Dim destinationSquare =
board.GetSquare(BoardSquare, New Movement With {.Forward = 1, .ToRight = 0})
If (destinationSquare IsNot Nothing AndAlso destinationSquare.Piece Is Nothing) Then
posibleSquares.Add(destinationSquare)
If isInStartPosition Then
destinationSquare =
board.GetSquare(BoardSquare, New Movement With {.Forward = 2, .ToRight = 0})
If (destinationSquare IsNot Nothing AndAlso destinationSquare.Piece Is Nothing) Then
posibleSquares.Add(destinationSquare)
End If
End If
End If
destinationSquare =
board.GetSquare(BoardSquare, New Movement With {.Forward = 1, .ToRight = -1})
If (destinationSquare IsNot Nothing AndAlso destinationSquare.Piece IsNot Nothing _
AndAlso destinationSquare.Piece.Color <> Color) Then
posibleSquares.Add(destinationSquare)
End If
destinationSquare =
board.GetSquare(BoardSquare, New Movement With {.Forward = 1, .ToRight = 1})
If destinationSquare IsNot Nothing AndAlso destinationSquare.Piece IsNot Nothing _
AndAlso destinationSquare.Piece.Color <> Color Then
posibleSquares.Add(destinationSquare)
End If
Return posibleSquares
End Function
End Class
De esta forma tenemos definidas todas las piezas del juego. En la próxima entrada continuaré añadiendo funcionalidad a las celdas y el tablero de juego.
Gracias, Asier, lo pruebo.......Abrazos...
ResponderEliminarBuenas tardes, lo terminaste?
ResponderEliminarSaludos
Siempre he dicho que si no vas a terminar lo que empiezas, será mejor no empezar.
ResponderEliminar