/*
$Id$
Copyright (C) 2006-2007 by David Cotton
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package fr.free.jchecs.core;
import static fr.free.jchecs.core.Constants.FILE_COUNT;
import static fr.free.jchecs.core.Constants.RANK_COUNT;
import static fr.free.jchecs.core.Piece.BLACK_BISHOP;
import static fr.free.jchecs.core.Piece.BLACK_KING;
import static fr.free.jchecs.core.Piece.BLACK_KNIGHT;
import static fr.free.jchecs.core.Piece.BLACK_PAWN;
import static fr.free.jchecs.core.Piece.BLACK_QUEEN;
import static fr.free.jchecs.core.Piece.BLACK_ROOK;
import static fr.free.jchecs.core.Piece.WHITE_BISHOP;
import static fr.free.jchecs.core.Piece.WHITE_KING;
import static fr.free.jchecs.core.Piece.WHITE_KNIGHT;
import static fr.free.jchecs.core.Piece.WHITE_PAWN;
import static fr.free.jchecs.core.Piece.WHITE_QUEEN;
import static fr.free.jchecs.core.Piece.WHITE_ROOK;
import static fr.free.jchecs.core.PieceType.BISHOP;
import static fr.free.jchecs.core.PieceType.KING;
import static fr.free.jchecs.core.PieceType.KNIGHT;
import static fr.free.jchecs.core.PieceType.PAWN;
import static fr.free.jchecs.core.PieceType.QUEEN;
import static fr.free.jchecs.core.PieceType.ROOK;
/**
* Représentation d'un état de la partie basée sur un tableau à deux dimensions pour stocker les
* positions.
* <p>
* Cette façon de coder un plateau de jeu est plus naturelle et simple à valider mais offre de
* faibles performances : elle est à éviter dans un moteur de génération des coups, mais est idéale
* pour les tests unitaires de représentations plus complexes.
* </p>
*
* @author David Cotton
*/
final class ArrayBoard extends AbstractMoveGenerator
{
/** Instance correspondant à un état initial, sans pièces. */
static final MoveGenerator EMPTY = new ArrayBoard();
/** Instance correspondant à l'état initial standard. */
static final MoveGenerator STARTING;
static
{
final ArrayBoard etat = new ArrayBoard();
for (int x = 0; x < FILE_COUNT; x++)
{
etat._pieces[x][1] = WHITE_PAWN;
etat._pieces[x][RANK_COUNT - 2] = BLACK_PAWN;
}
etat._pieces[0][0] = WHITE_ROOK;
etat._pieces[1][0] = WHITE_KNIGHT;
etat._pieces[2][0] = WHITE_BISHOP;
etat._pieces[3][0] = WHITE_QUEEN;
etat._pieces[FILE_COUNT - 4][0] = WHITE_KING;
etat._pieces[FILE_COUNT - 3][0] = WHITE_BISHOP;
etat._pieces[FILE_COUNT - 2][0] = WHITE_KNIGHT;
etat._pieces[FILE_COUNT - 1][0] = WHITE_ROOK;
etat._pieces[0][RANK_COUNT - 1] = BLACK_ROOK;
etat._pieces[1][RANK_COUNT - 1] = BLACK_KNIGHT;
etat._pieces[2][RANK_COUNT - 1] = BLACK_BISHOP;
etat._pieces[3][RANK_COUNT - 1] = BLACK_QUEEN;
etat._pieces[FILE_COUNT - 4][RANK_COUNT - 1] = BLACK_KING;
etat._pieces[FILE_COUNT - 3][RANK_COUNT - 1] = BLACK_BISHOP;
etat._pieces[FILE_COUNT - 2][RANK_COUNT - 1] = BLACK_KNIGHT;
etat._pieces[FILE_COUNT - 1][RANK_COUNT - 1] = BLACK_ROOK;
etat.setKingSquare(false, Square.valueOf(4, 7));
etat.setKingSquare(true, Square.valueOf(4, 0));
STARTING = etat;
}
/** Identifiant de la classe pour la sérialisation. */
private static final long serialVersionUID = -7691142320420490263L;
/** Liste des modificateurs pour les mouvements d'un roi. */
private static final int [] KING_MOVES = { -1, 0, 1, 1, 1, 0, -1, -1 };
/** Liste des modificateurs pour les mouvements d'un cavalier. */
private static final int [] KNIGHT_MOVES = { -1, 1, 2, 2, 1, -1, -2, -2, };
/** Description du plateau. */
private final Piece [][] _pieces = new Piece [ FILE_COUNT ] [ RANK_COUNT ];
/** Buffer de la clé de hachage (peut être à null). */
private Integer _hashCode;
/**
* Crée une nouvelle instance interne.
*/
private ArrayBoard()
{
// Rien de spécifique...
}
/**
* Crée une nouvelle instance, initialisée à partir de l'état reçu en paramètre.
*
* @param pEtat Instance initial.
*/
private ArrayBoard(final Board pEtat)
{
super(pEtat);
for (final Square s : Square.values())
{
_pieces[s.getFile()][s.getRank()] = pEtat.getPieceAt(s);
}
}
/**
* Crée une nouvelle instance, copie conforme de l'instance reçue.
*
* @param pEtat Instance à copier.
*/
private ArrayBoard(final ArrayBoard pEtat)
{
super(pEtat);
for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */)
{
System.arraycopy(pEtat._pieces[x], 0, _pieces[x], 0, RANK_COUNT);
}
}
/**
* Renvoi une nouvelle instance, initialisée à partir d'un état quelconque.
*
* @param pEtat Etat de départ.
* @return Copie de l'état.
*/
public MoveGenerator derive(final Board pEtat)
{
assert pEtat != null;
return new ArrayBoard(pEtat);
}
/**
* Renvoi une nouvelle instance décrivant l'état du jeu après application d'un mouvement.
*
* @param pMouvement Description de mouvement.
* @param pSuivant Drapeau positionné si l'on souhaite que le jeu passe au demi-coups suivant.
* @return Instance dérivée.
*/
public MoveGenerator derive(final Move pMouvement, final boolean pSuivant)
{
assert pMouvement != null;
final ArrayBoard res = new ArrayBoard(this);
// Ajuste les compteurs...
if (pSuivant)
{
final boolean t = !isWhiteActive();
res.setWhiteActive(t);
if (t)
{
res.setFullmoveNumber(getFullmoveNumber() + 1);
}
if (pMouvement.getCaptured() == null)
{
res.setHalfmoveCount(getHalfmoveCount() + 1);
}
else
{
res.setHalfmoveCount(0);
}
}
// Déplace la pièce...
final Piece piece = pMouvement.getPiece();
final PieceType typePiece = piece.getType();
final boolean trait = piece.isWhite();
final Square src = pMouvement.getFrom();
final int xSrc = src.getFile();
final int ySrc = src.getRank();
assert res._pieces[xSrc][ySrc] == piece;
res._pieces[xSrc][ySrc] = null;
final Square dst = pMouvement.getTo();
final int xDst = dst.getFile();
final int yDst = dst.getRank();
res._pieces[xDst][yDst] = piece;
// ... éxécute un mouvement spécifique de type "roque" et gère le suivi des rois ...
if (typePiece == KING)
{
res.setKingSquare(trait, dst);
if (xSrc == 4)
{
if (xDst == 2)
{
// ... côté reine...
final Piece tour = res._pieces[0][yDst];
assert tour != null;
assert tour.getType() == ROOK;
res._pieces[0][yDst] = null;
res._pieces[3][yDst] = tour;
res.setCastled(trait, true);
}
else if (xDst == 6)
{
// ... côté roi...
final Piece tour = res._pieces[FILE_COUNT - 1][yDst];
assert tour != null;
assert tour.getType() == ROOK;
res._pieces[FILE_COUNT - 1][yDst] = null;
res._pieces[5][yDst] = tour;
res.setCastled(trait, true);
}
}
}
// ... éxécute un mouvement spécifique du type "en passant" ...
if ((typePiece == PAWN) && (dst == getEnPassant()))
{
if (trait)
{
res._pieces[xDst][yDst - 1] = null;
}
else
{
res._pieces[xDst][yDst + 1] = null;
}
}
// Gére la promotion des pions...
if (typePiece == PAWN)
{
if (trait)
{
assert yDst > ySrc;
if (yDst == RANK_COUNT - 1)
{
res._pieces[xDst][yDst] = WHITE_QUEEN;
}
}
else
{
assert yDst < ySrc;
if (yDst == 0)
{
res._pieces[xDst][yDst] = BLACK_QUEEN;
}
}
}
// Tient compte des interdictions de roquer que le mouvement peut provoquer...
if (canCastleShort(trait))
{
if ((typePiece == KING) || ((typePiece == ROOK) && (xSrc == FILE_COUNT - 1)))
{
res.setCastleShort(trait, false);
}
}
if (canCastleLong(trait))
{
if ((typePiece == KING) || ((typePiece == ROOK) && (xSrc == 0)))
{
res.setCastleLong(trait, false);
}
}
// Détecte si une possibilité de prise "en passant" doit être signalée...
res.setEnPassant(null);
if (typePiece == PAWN)
{
// En profite pour aussi gérer le compteur de demis coups...
if (pSuivant)
{
res.setHalfmoveCount(0);
}
if (trait)
{
if ((ySrc == 1) && (yDst == 3))
{
res.setEnPassant(Square.valueOf(xDst, 2));
}
}
else
{
if ((ySrc == RANK_COUNT - 2) && (yDst == RANK_COUNT - 4))
{
res.setEnPassant(Square.valueOf(xDst, RANK_COUNT - 3));
}
}
}
return res;
}
/**
* Méthode spécialisée pour tester l'égalité entre deux descriptions de ce type.
*
* @param pObjet Objet avec lequel comparer.
* @return Vrai si les deux objets sont égaux.
*/
@Override
public boolean equals(final Object pObjet)
{
if (pObjet == this)
{
return true;
}
if (pObjet instanceof ArrayBoard)
{
if (hashCode() != pObjet.hashCode())
{
return false;
}
final ArrayBoard o = (ArrayBoard) pObjet;
for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */)
{
for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */)
{
if (_pieces[x][y] != o._pieces[x][y])
{
return false;
}
}
}
return equalsInternal(o);
}
return super.equals(pObjet);
}
/**
* Renvoi toutes les cases cibles des mouvements possibles (y compris ceux mettant le roi en
* échec) pour la pièce contenue par une case.
*
* @param pOrigine Case à l'origine du mouvement.
* @return Liste des cases cibles (y compris celles conduisant à un échec).
*/
public Square [] getAllTargets(final Square pOrigine)
{
assert pOrigine != null;
Square [] res = null;
final Piece piece = _pieces[pOrigine.getFile()][pOrigine.getRank()];
if (piece != null)
{
final boolean trait = piece.isWhite();
switch (piece.getType())
{
case BISHOP :
res = getBishopTargets(pOrigine, trait);
break;
case KING :
res = getKingTargets(pOrigine, trait);
break;
case KNIGHT :
res = getKnightTargets(pOrigine, trait);
break;
case PAWN :
res = getPawnTargets(pOrigine, trait);
break;
case QUEEN :
res = getQueenTargets(pOrigine, trait);
break;
case ROOK :
res = getRookTargets(pOrigine, trait);
break;
default :
assert false;
}
}
else
{
res = new Square [ 0 ];
}
return res;
}
/**
* Renvoi toutes les cases cibles possibles d'un mouvement de type "fou" d'une certaine couleur (y
* compris ceux mettant le roi en échec) à partir d'une case.
*
* @param pOrigine Case à l'origine du mouvement.
* @param pBlanc Positionné à vrai si la recherche concerne les blancs.
* @return Liste des cases cibles (y compris celles conduisant à un échec).
*/
public Square [] getBishopTargets(final Square pOrigine, final boolean pBlanc)
{
assert pOrigine != null;
final Square [] tmp = new Square [ 13 ];
int nb = 0;
final int xSrc = pOrigine.getFile();
final int ySrc = pOrigine.getRank();
// Mouvements / prise vers le haut/gauche...
int xDst = xSrc;
int yDst = ySrc;
while ((--xDst >= 0) && (++yDst < RANK_COUNT))
{
final Piece p = _pieces[xDst][yDst];
if (p == null)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
break;
}
}
// Mouvements / prise vers le haut/droit...
xDst = xSrc;
yDst = ySrc;
while ((++xDst < FILE_COUNT) && (++yDst < RANK_COUNT))
{
final Piece p = _pieces[xDst][yDst];
if (p == null)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
break;
}
}
// Mouvements / prise vers le bas/gauche...
xDst = xSrc;
yDst = ySrc;
while ((--xDst >= 0) && (--yDst >= 0))
{
final Piece p = _pieces[xDst][yDst];
if (p == null)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
break;
}
}
// Mouvements / prise vers le bas/droit...
xDst = xSrc;
yDst = ySrc;
while ((++xDst < FILE_COUNT) && (--yDst >= 0))
{
final Piece p = _pieces[xDst][yDst];
if (p == null)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
break;
}
}
final Square [] res = new Square [ nb ];
System.arraycopy(tmp, 0, res, 0, nb);
return res;
}
/**
* Renvoi la liste des cases pouvant être atteintes par un mouvement de type roi.
*
* @param pOrigine Case à l'origine du mouvement.
* @param pBlanc A vrai pour indiquer une recherche sur les blancs.
* @return Liste des cases cibles (y compris celles conduisant à un échec).
*/
public Square [] getKingTargets(final Square pOrigine, final boolean pBlanc)
{
assert pOrigine != null;
final Square [] tmp = new Square [ 8 ];
int nb = 0;
final int xSrc = pOrigine.getFile();
final int ySrc = pOrigine.getRank();
final int kLength = KING_MOVES.length;
for (int i = kLength; --i >= 0; /* Pré-décrémenté */)
{
final int xDst = xSrc + KING_MOVES[i];
final int yDst = ySrc + KING_MOVES[(i + 2) % kLength];
if ((xDst >= 0) && (yDst >= 0) && (xDst < FILE_COUNT) && (yDst < RANK_COUNT))
{
final Piece p = _pieces[xDst][yDst];
if ((p == null) || (p.isWhite() != pBlanc))
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
}
}
if ((nb > 0) && (xSrc == 4))
{
if (canCastleShort(pBlanc) && (_pieces[5][ySrc] == null) && (_pieces[6][ySrc] == null))
{
final Piece t = _pieces[FILE_COUNT - 1][ySrc];
if ((t != null) && (t.getType() == ROOK) && (t.isWhite() == pBlanc))
{
tmp[nb++] = Square.valueOf(6, ySrc);
}
}
if (canCastleLong(pBlanc) && (_pieces[3][ySrc] == null) && (_pieces[2][ySrc] == null)
&& (_pieces[1][ySrc] == null))
{
final Piece t = _pieces[0][ySrc];
if ((t != null) && (t.getType() == ROOK) && (t.isWhite() == pBlanc))
{
tmp[nb++] = Square.valueOf(2, ySrc);
}
}
}
final Square [] res = new Square [ nb ];
System.arraycopy(tmp, 0, res, 0, nb);
return res;
}
/**
* Renvoi la liste des cases pouvant être atteintes par un mouvement de type cavalier.
*
* @param pOrigine Case à l'origine du mouvement.
* @param pBlanc A vrai pour indiquer une recherche sur les blancs.
* @return Liste des cases cibles (y compris celles conduisant à un échec).
*/
public Square [] getKnightTargets(final Square pOrigine, final boolean pBlanc)
{
assert pOrigine != null;
final Square [] tmp = new Square [ 8 ];
int nb = 0;
final int oX = pOrigine.getFile();
final int oY = pOrigine.getRank();
final int kLength = KNIGHT_MOVES.length;
for (int i = kLength; --i >= 0; /* Pré-décrémenté */)
{
final int xDst = oX + KNIGHT_MOVES[i];
final int yDst = oY + KNIGHT_MOVES[(i + 2) % kLength];
if ((xDst >= 0) && (yDst >= 0) && (xDst < FILE_COUNT) && (yDst < RANK_COUNT))
{
final Piece p = _pieces[xDst][yDst];
if ((p == null) || (p.isWhite() != pBlanc))
{
tmp[nb++] = Square.valueOf(xDst, yDst);
}
}
}
final Square [] res = new Square [ nb ];
System.arraycopy(tmp, 0, res, 0, nb);
return res;
}
/**
* Renvoi la liste des cases pouvant être atteintes par un mouvement de type pion.
*
* @param pOrigine Case à l'origine du mouvement.
* @param pBlanc A vrai pour indiquer une recherche sur les blancs.
* @return Liste des cases cibles (y compris celles conduisant à un échec).
*/
public Square [] getPawnTargets(final Square pOrigine, final boolean pBlanc)
{
assert pOrigine != null;
final Square [] tmp = new Square [ 4 ];
int nb = 0;
final int ySrc = pOrigine.getRank();
if (pBlanc)
{
if (ySrc < RANK_COUNT - 1)
{
final int xSrc = pOrigine.getFile();
// Mouvement de 1...
if (_pieces[xSrc][ySrc + 1] == null)
{
tmp[nb++] = Square.valueOf(xSrc, ySrc + 1);
// Mouvement initial de 2
if ((ySrc == 1) && (_pieces[xSrc][3] == null))
{
tmp[nb++] = Square.valueOf(xSrc, 3);
}
}
if (xSrc > 0)
{
// Prise à gauche (y compris en passant)...
final Square cDest = Square.valueOf(xSrc - 1, ySrc + 1);
final Piece pDest = _pieces[xSrc - 1][ySrc + 1];
if (((pDest != null) && (!pDest.isWhite())) || (cDest == getEnPassant()))
{
tmp[nb++] = cDest;
}
}
if (xSrc < FILE_COUNT - 1)
{
// Prise à droite (y compris en passant)...
final Square cDest = Square.valueOf(xSrc + 1, ySrc + 1);
final Piece pDest = _pieces[xSrc + 1][ySrc + 1];
if (((pDest != null) && (!pDest.isWhite())) || (cDest == getEnPassant()))
{
tmp[nb++] = cDest;
}
}
}
}
else
{
if (ySrc > 0)
{
final int xSrc = pOrigine.getFile();
// Mouvement de 1...
if (_pieces[xSrc][ySrc - 1] == null)
{
tmp[nb++] = Square.valueOf(xSrc, ySrc - 1);
// Mouvement initial de 2
if ((ySrc == RANK_COUNT - 2) && (_pieces[xSrc][RANK_COUNT - 4] == null))
{
tmp[nb++] = Square.valueOf(xSrc, RANK_COUNT - 4);
}
}
if (xSrc > 0)
{
// Prise à gauche (y compris en passant)...
final Square cDest = Square.valueOf(xSrc - 1, ySrc - 1);
final Piece pDest = _pieces[xSrc - 1][ySrc - 1];
if (((pDest != null) && pDest.isWhite()) || (cDest == getEnPassant()))
{
tmp[nb++] = cDest;
}
}
if (xSrc < FILE_COUNT - 1)
{
// Prise à droite (y compris en passant)...
final Square cDest = Square.valueOf(xSrc + 1, ySrc - 1);
final Piece pDest = _pieces[xSrc + 1][ySrc - 1];
if (((pDest != null) && pDest.isWhite()) || (cDest == getEnPassant()))
{
tmp[nb++] = cDest;
}
}
}
}
final Square [] res = new Square [ nb ];
System.arraycopy(tmp, 0, res, 0, nb);
return res;
}
/**
* Renvoi l'éventuelle pièce présente sur la case indiquée.
*
* @param pCase Case à tester.
* @return Pièce présente sur la case (ou null si aucune).
*/
public Piece getPieceAt(final Square pCase)
{
assert pCase != null;
return getPieceAt(pCase.getFile(), pCase.getRank());
}
/**
* Renvoi l'éventuelle pièce présente sur la case dont les coordonnées sont indiquées.
*
* @param pColonne Colonne de la case à tester (de 0 à 7).
* @param pLigne Ligne de la case à tester (de 0 à 7).
* @return Pièce présente sur la case (ou null).
*/
public Piece getPieceAt(final int pColonne, final int pLigne)
{
assert (pColonne >= 0) && (pColonne < FILE_COUNT);
assert (pLigne >= 0) && (pLigne < RANK_COUNT);
return _pieces[pColonne][pLigne];
}
/**
* Renvoi toutes les cases cibles possibles d'un mouvement de type "dame" d'une certaine couleur
* (y compris ceux mettant le roi en échec) à partir d'une case.
*
* @param pOrigine Case à l'origine du mouvement.
* @param pBlanc Mis à vrai pour rechercher pour les blancs.
* @return Liste des cases cibles (y compris celles conduisant à un échec).
*/
public Square [] getQueenTargets(final Square pOrigine, final boolean pBlanc)
{
assert pOrigine != null;
final Square [] tour = getRookTargets(pOrigine, pBlanc);
final int tl = tour.length;
final Square [] fou = getBishopTargets(pOrigine, pBlanc);
final int fl = fou.length;
final Square [] res = new Square [ tl + fl ];
System.arraycopy(tour, 0, res, 0, tl);
System.arraycopy(fou, 0, res, tl, fl);
return res;
}
/**
* Renvoi toutes les cases cibles possibles d'un mouvement de type "tour" d'une certaine couleur
* (y compris ceux mettant le roi en échec) à partir d'une case.
*
* @param pOrigine Case à l'origine du mouvement.
* @param pBlanc Mis à vrai pour rechercher pour les blancs.
* @return Liste des cases cibles (y compris celles conduisant à un échec).
*/
public Square [] getRookTargets(final Square pOrigine, final boolean pBlanc)
{
assert pOrigine != null;
final Square [] tmp = new Square [ 14 ];
int nb = 0;
final int xSrc = pOrigine.getFile();
final int ySrc = pOrigine.getRank();
// Mouvements / prise vers la gauche...
for (int x = xSrc - 1; x >= 0; x--)
{
final Piece p = _pieces[x][ySrc];
if (p == null)
{
tmp[nb++] = Square.valueOf(x, ySrc);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(x, ySrc);
}
break;
}
}
// Mouvements / prise vers la droite...
for (int x = xSrc + 1; x < FILE_COUNT; x++)
{
final Piece p = _pieces[x][ySrc];
if (p == null)
{
tmp[nb++] = Square.valueOf(x, ySrc);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(x, ySrc);
}
break;
}
}
// Mouvements / prise vers le haut...
for (int y = ySrc + 1; y < RANK_COUNT; y++)
{
final Piece p = _pieces[xSrc][y];
if (p == null)
{
tmp[nb++] = Square.valueOf(xSrc, y);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(xSrc, y);
}
break;
}
}
// Mouvements / prise vers le bas...
for (int y = ySrc - 1; y >= 0; y--)
{
final Piece p = _pieces[xSrc][y];
if (p == null)
{
tmp[nb++] = Square.valueOf(xSrc, y);
}
else
{
if (p.isWhite() != pBlanc)
{
tmp[nb++] = Square.valueOf(xSrc, y);
}
break;
}
}
final Square [] res = new Square [ nb ];
System.arraycopy(tmp, 0, res, 0, nb);
return res;
}
/**
* Renvoi tous les mouvements valides pour une couleur.
*
* @param pTrait Positionné à "true" pour indiquer une recherche pour les blancs.
* @return Liste des mouvements valides.
*/
public Move [] getValidMoves(final boolean pTrait)
{
final Move [] tmp = new Move [ 164 ];
int nb = 0;
for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */)
{
for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */)
{
final Piece p = _pieces[x][y];
if ((p != null) && (p.isWhite() == pTrait))
{
final Square orig = Square.valueOf(x, y);
for (final Square dst : getValidTargets(orig))
{
final Piece prise;
if ((p.getType() != PAWN) || (dst != getEnPassant()))
{
prise = _pieces[dst.getFile()][dst.getRank()];
}
else
{
if (pTrait)
{
prise = _pieces[dst.getFile()][dst.getRank() - 1];
}
else
{
prise = _pieces[dst.getFile()][dst.getRank() + 1];
}
}
tmp[nb++] = new Move(p, orig, dst, prise);
}
}
}
}
final Move [] res = new Move [ nb ];
System.arraycopy(tmp, 0, res, 0, nb);
return res;
}
/**
* Renvoi toutes les cases cibles des mouvements valides à partir d'une case.
*
* @param pOrigine Case à l'origine du mouvement.
* @return Liste des cases cibles.
*/
public Square [] getValidTargets(final Square pOrigine)
{
assert pOrigine != null;
final Piece piece = _pieces[pOrigine.getFile()][pOrigine.getRank()];
if (piece != null)
{
final Square [] total = getAllTargets(pOrigine);
final int tLength = total.length;
final Square [] tmp = new Square [ tLength ];
int nb = 0;
final boolean trait = piece.isWhite();
for (int t = tLength; --t >= 0; /* Pré-décrémenté */)
{
final Square cible = total[t];
final int xDst = cible.getFile();
final int yDst = cible.getRank();
final Piece prise = _pieces[xDst][yDst];
if (!derive(new Move(piece, pOrigine, cible, prise), false).isInCheck(trait))
{
if ((piece.getType() == KING) && (pOrigine.getFile() == 4))
{
final int delta = 4 - cible.getFile();
if ((delta == 2) || (delta == -2))
{
// Elimine le roque si le roi est en échec ou s'il le serait sur la case
// intermédiaire...
if (isInCheck(trait)
|| derive(
new Move(piece, pOrigine, Square.valueOf(4 - (delta / 2), cible.getRank())),
false).isInCheck(trait))
{
continue;
}
}
}
tmp[nb++] = cible;
}
}
final Square [] res = new Square [ nb ];
System.arraycopy(tmp, 0, res, 0, nb);
return res;
}
return new Square [ 0 ];
}
/**
* Surcharge du calcul des clés de hachage, pour optimisation.
*
* @return Clé de hachage.
*/
@Override
public synchronized int hashCode()
{
if (_hashCode == null)
{
int h = zobristRoot();
for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */)
{
for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */)
{
final Piece p = _pieces[x][y];
if (p != null)
{
h ^= ZOBRIST_PIECE_POSITION[p.ordinal()][x + y * FILE_COUNT];
}
}
}
assert h == super.hashCode();
_hashCode = Integer.valueOf(h);
}
return _hashCode.intValue();
}
/**
* Indique si une case est attaquée par une couleur.
*
* @param pCible Case cible.
* @param pCouleur Positionné à "true" pour tester l'attaque par les blancs.
* @return Vrai si la case est attaquée.
*/
public boolean isAttacked(final Square pCible, final boolean pCouleur)
{
assert pCible != null;
final int xSrc = pCible.getFile();
final int ySrc = pCible.getRank();
Piece p = null;
int x = xSrc - 1;
// Gauche
while ((x >= 0) && (p == null))
{
p = _pieces[x--][ySrc];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == ROOK) || (t == QUEEN))
{
return true;
}
}
p = null;
x = xSrc + 1;
// Droite
while ((x < FILE_COUNT) && (p == null))
{
p = _pieces[x++][ySrc];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == ROOK) || (t == QUEEN))
{
return true;
}
}
p = null;
int y = ySrc - 1;
// Bas
while ((y >= 0) && (p == null))
{
p = _pieces[xSrc][y--];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == ROOK) || (t == QUEEN))
{
return true;
}
}
p = null;
y = ySrc + 1;
// Haut
while ((y < RANK_COUNT) && (p == null))
{
p = _pieces[xSrc][y++];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == ROOK) || (t == QUEEN))
{
return true;
}
}
p = null;
x = xSrc - 1;
y = ySrc - 1;
// Bas / Gauche
while ((x >= 0) && (y >= 0) && (p == null))
{
p = _pieces[x--][y--];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == BISHOP) || (t == QUEEN))
{
return true;
}
}
p = null;
x = xSrc - 1;
y = ySrc + 1;
// Haut / Gauche
while ((x >= 0) && (y < RANK_COUNT) && (p == null))
{
p = _pieces[x--][y++];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == BISHOP) || (t == QUEEN))
{
return true;
}
}
p = null;
x = xSrc + 1;
y = ySrc + 1;
// Haut / Droit
while ((x < FILE_COUNT) && (y < RANK_COUNT) && (p == null))
{
p = _pieces[x++][y++];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == BISHOP) || (t == QUEEN))
{
return true;
}
}
p = null;
x = xSrc + 1;
y = ySrc - 1;
// Bas / Droit
while ((x < FILE_COUNT) && (y >= 0) && (p == null))
{
p = _pieces[x++][y--];
}
if ((p != null) && (p.isWhite() == pCouleur))
{
final PieceType t = p.getType();
if ((t == BISHOP) || (t == QUEEN))
{
return true;
}
}
// Cavalier
int kLength = KNIGHT_MOVES.length;
for (int i = kLength; --i >= 0; /* Pré-décrémenté */)
{
x = xSrc + KNIGHT_MOVES[i];
y = ySrc + KNIGHT_MOVES[(i + 2) % kLength];
if ((x >= 0) && (y >= 0) && (x < FILE_COUNT) && (y < RANK_COUNT))
{
p = _pieces[x][y];
if ((p != null) && (p.isWhite() == pCouleur) && (p.getType() == KNIGHT))
{
return true;
}
}
}
// Roi
kLength = KING_MOVES.length;
for (int i = kLength; --i >= 0; /* Pré-décrémenté */)
{
x = xSrc + KING_MOVES[i];
y = ySrc + KING_MOVES[(i + 2) % kLength];
if ((x >= 0) && (y >= 0) && (x < FILE_COUNT) && (y < RANK_COUNT))
{
p = _pieces[x][y];
if ((p != null) && (p.isWhite() == pCouleur) && (p.getType() == KING))
{
return true;
}
}
}
// Pions...
if (pCouleur)
{
if (ySrc > 1)
{
if (((xSrc > 0) && (_pieces[xSrc - 1][ySrc - 1] == WHITE_PAWN))
|| ((xSrc < FILE_COUNT - 1) && (_pieces[xSrc + 1][ySrc - 1] == WHITE_PAWN)))
{
return true;
}
}
}
else
{
if (ySrc < RANK_COUNT - 2)
{
if (((xSrc > 0) && (_pieces[xSrc - 1][ySrc + 1] == BLACK_PAWN))
|| ((xSrc < FILE_COUNT - 1) && (_pieces[xSrc + 1][ySrc + 1] == BLACK_PAWN)))
{
return true;
}
}
}
return false;
}
/**
* Indique si le roi d'une couleur est en échec.
*
* @param pCouleur Positionné à "true" pour tester l'échec sur les blancs, à "false" sinon.
* @return Vrai si le roi est en échec.
*/
public boolean isInCheck(final boolean pCouleur)
{
final Square posRoi = getKingSquare(pCouleur);
for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */)
{
for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */)
{
final Piece p = _pieces[x][y];
if ((p != null) && (p.isWhite() != pCouleur))
{
final Square test = Square.valueOf(x, y);
if (test != posRoi)
{
for (final Square s : getAllTargets(test))
{
if (s == posRoi)
{
return true;
}
}
}
}
}
}
return false;
}
}