/* $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_PAWN; import static fr.free.jchecs.core.Piece.BLACK_QUEEN; import static fr.free.jchecs.core.Piece.WHITE_PAWN; import static fr.free.jchecs.core.Piece.WHITE_QUEEN; 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 à une dimension accédé à travers le * masque hexadécimal x88. * <p> * Cette représentation est plus performante que la représentation naturelle avec un tableau à deux * dimensions, tout en restant moins complexe que les BitBoards. * </p> * * @author David Cotton */ final class X88Board extends AbstractMoveGenerator { /** Indice du dernier élément valide dans les buffer de travail des cases. */ private static int S_nbBufferedSquares; /** Identifiant de la classe pour la sérialisation. */ private static final long serialVersionUID = 7427708516011286821L; /** Filtre appliqué sur les indices du tableau. */ private static final int X88 = 0x88; /** Equivalences entre les indices x88 et les indices normaux des cases. */ private static final int [] FROM_X88 = { 0, 1, 2, 3, 4, 5, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, 8, 9, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, 16, 17, 18, 19, 20, 21, 22, 23, -1, -1, -1, -1, -1, -1, -1, -1, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, -1, -1, -1, -1, -1, -1, -1, -1, 40, 41, 42, 43, 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, -1, -1, -1, -1, -1, -1, -1, -1, 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, }; /** Liste des modificateurs pour les mouvements d'un roi. */ private static final int [] KING_MOVES = { -17, -16, -15, -1, 1, 15, 16, 17 }; /** Liste des modificateurs pour les mouvements d'un cavalier. */ private static final int [] KNIGHT_MOVES = { -33, -31, -18, -14, 14, 18, 31, 33, }; /** Liste de cases cibles vides. */ private static final Square [] NO_SQUARE = new Square [ 0 ]; /** * Buffer de travail pour optimiser la recherche des cibles de mouvements. * <p> * L'utilisation d'un buffer statique optimise les performances mais nécessite de faire très * attention à la synchronisation pour que la classe reste sûre vis-à-vis des threads. * </p> */ // 27 est le nombre maximum de cases cibles pour une pièce (une dame, dans le meilleur des cas). private static final int [] SQUARES_BUFFER = new int [ 27 ]; /** Description du plateau. */ private final Piece [] _pieces = new Piece [ FILE_COUNT * RANK_COUNT * 2 - FILE_COUNT ]; /** Clé de hachage. */ private int _hashCode; /** * Crée une nouvelle instance, initialisée à partir de l'état reçu en paramètre. * * @param pEtat Instance initial. */ X88Board(final Board pEtat) { super(pEtat); for (final Square s : Square.values()) { _pieces[s.getRank() * 16 + s.getFile()] = pEtat.getPieceAt(s); } _hashCode = super.hashCode(); } /** * Crée une nouvelle instance, copie conforme de l'instance reçue. * * @param pEtat Instance à copier. */ private X88Board(final X88Board pEtat) { super(pEtat); System.arraycopy(pEtat._pieces, 0, _pieces, 0, _pieces.length); _hashCode = pEtat._hashCode; } /** * Ajoute au buffer interne 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 Indice de la case à l'origine du mouvement. */ private void addAllTargets(final int pOrigine) { assert (pOrigine & X88) == 0; final Piece piece = _pieces[pOrigine]; if (piece != null) { final boolean trait = piece.isWhite(); switch (piece.getType()) { case BISHOP : addBishopTargets(pOrigine, trait); break; case KING : addKingTargets(pOrigine, trait); break; case KNIGHT : addKnightTargets(pOrigine, trait); break; case PAWN : addPawnTargets(pOrigine, trait); break; case QUEEN : addBishopTargets(pOrigine, trait); addRookTargets(pOrigine, trait); break; case ROOK : addRookTargets(pOrigine, trait); break; default : assert false; } } } /** * Ajoute au buffer interne 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 Indice de la case à l'origine du mouvement. * @param pBlanc Positionné à vrai si la recherche concerne les blancs. */ private void addBishopTargets(final int pOrigine, final boolean pBlanc) { assert (pOrigine & X88) == 0; // Mouvements / prise vers le haut/gauche... int dst = pOrigine + 15; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst += 15; } // Mouvements / prise vers le haut/droit... dst = pOrigine + 17; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst += 17; } // Mouvements / prise vers le bas/gauche... dst = pOrigine - 17; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst -= 17; } // Mouvements / prise vers le bas/droit... dst = pOrigine - 15; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst -= 15; } } /** * Ajoute au buffer interne la liste des cases pouvant être atteintes par un mouvement de type * roi. * * @param pOrigine Indice de la case à l'origine du mouvement. * @param pBlanc A vrai pour indiquer une recherche sur les blancs. */ private void addKingTargets(final int pOrigine, final boolean pBlanc) { assert (pOrigine & X88) == 0; boolean testerRoque = false; for (final int km : KING_MOVES) { final int dst = pOrigine + km; if ((dst & X88) == 0) { final Piece p = _pieces[dst]; if ((p == null) || (p.isWhite() != pBlanc)) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; testerRoque = true; } } } if (testerRoque && ((pOrigine & 0xF) == 4)) { if (canCastleShort(pBlanc) && (_pieces[pOrigine + 1] == null) && (_pieces[pOrigine + 2] == null)) { final Piece t = _pieces[pOrigine + 3]; if ((t != null) && (t.getType() == ROOK) && (t.isWhite() == pBlanc)) { SQUARES_BUFFER[S_nbBufferedSquares++] = pOrigine + 2; } } if (canCastleLong(pBlanc) && (_pieces[pOrigine - 1] == null) && (_pieces[pOrigine - 2] == null) && (_pieces[pOrigine - 3] == null)) { final Piece t = _pieces[pOrigine - 4]; if ((t != null) && (t.getType() == ROOK) && (t.isWhite() == pBlanc)) { SQUARES_BUFFER[S_nbBufferedSquares++] = pOrigine - 2; } } } } /** * Ajoute au buffer interne la liste des cases pouvant être atteintes par un mouvement de type * cavalier. * * @param pOrigine Indice de la case à l'origine du mouvement. * @param pBlanc A vrai pour indiquer une recherche sur les blancs. */ private void addKnightTargets(final int pOrigine, final boolean pBlanc) { assert (pOrigine & X88) == 0; for (final int km : KNIGHT_MOVES) { final int dst = pOrigine + km; if ((dst & X88) == 0) { final Piece p = _pieces[dst]; if ((p == null) || (p.isWhite() != pBlanc)) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } } } } /** * Ajoute au buffer interne la liste des cases pouvant être atteintes par un mouvement de type * pion. * * @param pOrigine Indice de la case à l'origine du mouvement. * @param pBlanc A vrai pour indiquer une recherche sur les blancs. */ private void addPawnTargets(final int pOrigine, final boolean pBlanc) { assert (pOrigine & X88) == 0; final int ySrc = pOrigine >>> 4; if (pBlanc) { if (ySrc < RANK_COUNT - 1) { // Mouvement de 1... if (_pieces[pOrigine + 16] == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = pOrigine + 16; // Mouvement initial de 2 if ((ySrc == 1) && (_pieces[pOrigine + 32] == null)) { SQUARES_BUFFER[S_nbBufferedSquares++] = pOrigine + 32; } } final int xSrc = pOrigine & 0xF; if (xSrc > 0) { // Prise à gauche (y compris en passant)... final int iDest = pOrigine + 15; final Piece pDest = _pieces[iDest]; if (((pDest != null) && (!pDest.isWhite())) || (Square.valueOf(FROM_X88[iDest]) == getEnPassant())) { SQUARES_BUFFER[S_nbBufferedSquares++] = iDest; } } if (xSrc < FILE_COUNT - 1) { // Prise à droite (y compris en passant)... final int iDest = pOrigine + 17; final Piece pDest = _pieces[iDest]; if (((pDest != null) && (!pDest.isWhite())) || (Square.valueOf(FROM_X88[iDest]) == getEnPassant())) { SQUARES_BUFFER[S_nbBufferedSquares++] = iDest; } } } } else { if (ySrc > 0) { // Mouvement de 1... if (_pieces[pOrigine - 16] == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = pOrigine - 16; // Mouvement initial de 2 if ((ySrc == RANK_COUNT - 2) && (_pieces[pOrigine - 32] == null)) { SQUARES_BUFFER[S_nbBufferedSquares++] = pOrigine - 32; } } final int xSrc = pOrigine & 0xF; if (xSrc > 0) { // Prise à gauche (y compris en passant)... final int iDest = pOrigine - 17; final Piece pDest = _pieces[iDest]; if (((pDest != null) && pDest.isWhite()) || (Square.valueOf(FROM_X88[iDest]) == getEnPassant())) { SQUARES_BUFFER[S_nbBufferedSquares++] = iDest; } } if (xSrc < FILE_COUNT - 1) { // Prise à droite (y compris en passant)... final int iDest = pOrigine - 15; final Piece pDest = _pieces[iDest]; if (((pDest != null) && pDest.isWhite()) || (Square.valueOf(FROM_X88[iDest]) == getEnPassant())) { SQUARES_BUFFER[S_nbBufferedSquares++] = iDest; } } } } } /** * Ajoute au buffer interne 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 Indice de la case à l'origine du mouvement. * @param pBlanc Mis à vrai pour rechercher pour les blancs. */ private void addRookTargets(final int pOrigine, final boolean pBlanc) { assert (pOrigine & X88) == 0; // Mouvements / prise vers la gauche... int dst = pOrigine - 1; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst--; } // Mouvements / prise vers la droite... dst = pOrigine + 1; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst++; } // Mouvements / prise vers le haut... dst = pOrigine + 16; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst += 16; } // Mouvements / prise vers le bas... dst = pOrigine - 16; while ((dst & X88) == 0) { final Piece p = _pieces[dst]; if (p == null) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } else { if (p.isWhite() != pBlanc) { SQUARES_BUFFER[S_nbBufferedSquares++] = dst; } break; } dst -= 16; } } /** * 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 X88Board(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 trait soit modifié. * @return Instance dérivée. */ public MoveGenerator derive(final Move pMouvement, final boolean pSuivant) { assert pMouvement != null; final X88Board res = new X88Board(this); // Ajuste les compteurs... if (pSuivant) { final boolean t = !isWhiteActive(); res.setWhiteActive(t); res._hashCode ^= ZOBRIST_WHITE_ACTIVE; 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 iSrc = src.getIndex(); final int xSrc = src.getFile(); final int i88Src = src.getRank() * 16 + xSrc; assert res._pieces[i88Src] == piece; res._pieces[i88Src] = null; final int pieceOrdinal = piece.ordinal(); res._hashCode ^= ZOBRIST_PIECE_POSITION[pieceOrdinal][iSrc]; final Square dst = pMouvement.getTo(); final int iDst = dst.getIndex(); final int xDst = dst.getFile(); final int yDst = dst.getRank(); final int i88Dst = yDst * 16 + xDst; final Piece pieceDst = _pieces[i88Dst]; if (pieceDst != null) { res._hashCode ^= ZOBRIST_PIECE_POSITION[pieceDst.ordinal()][iDst]; } res._pieces[i88Dst] = piece; res._hashCode ^= ZOBRIST_PIECE_POSITION[pieceOrdinal][iDst]; // ... é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 int i = yDst * FILE_COUNT; final int i88 = i * 2; final Piece tour = res._pieces[i88]; assert tour != null; assert tour.getType() == ROOK; res._pieces[i88] = null; final int tourOrdinal = tour.ordinal(); res._hashCode ^= ZOBRIST_PIECE_POSITION[tourOrdinal][i]; res._pieces[i88 + 3] = tour; res._hashCode ^= ZOBRIST_PIECE_POSITION[tourOrdinal][i + 3]; res.setCastled(trait, true); } else if (xDst == 6) { // ... côté roi... final int i = FILE_COUNT - 1 + yDst * FILE_COUNT; final int i88 = i + yDst * FILE_COUNT; final Piece tour = res._pieces[i88]; assert tour != null; assert tour.getType() == ROOK; res._pieces[i88] = null; final int tourOrdinal = tour.ordinal(); res._hashCode ^= ZOBRIST_PIECE_POSITION[tourOrdinal][i]; res._pieces[i88 - 2] = tour; res._hashCode ^= ZOBRIST_PIECE_POSITION[tourOrdinal][i - 2]; res.setCastled(trait, true); } } } // 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 (trait) { res._hashCode ^= ZOBRIST_WHITE_CASTLE_SHORT; } else { res._hashCode ^= ZOBRIST_BLACK_CASTLE_SHORT; } } } if (canCastleLong(trait)) { if ((typePiece == KING) || ((typePiece == ROOK) && (xSrc == 0))) { res.setCastleLong(trait, false); if (trait) { res._hashCode ^= ZOBRIST_WHITE_CASTLE_LONG; } else { res._hashCode ^= ZOBRIST_BLACK_CASTLE_LONG; } } } // Détecte si une prise "en passant" doit être effectuée ou signalée et gère la promotion... final Square epOrig = getEnPassant(); res.setEnPassant(null); if (typePiece == PAWN) { final int ySrc = src.getRank(); // En profite pour aussi gérer le compteur de demis coups... if (pSuivant) { res.setHalfmoveCount(0); } if (trait) { assert yDst > ySrc; if (yDst == RANK_COUNT - 1) { res._pieces[i88Dst] = WHITE_QUEEN; res._hashCode ^= ZOBRIST_PIECE_POSITION[pieceOrdinal][iDst]; res._hashCode ^= ZOBRIST_PIECE_POSITION[WHITE_QUEEN.ordinal()][iDst]; } else if ((ySrc == 1) && (yDst == 3)) { res.setEnPassant(Square.valueOf(xDst, 2)); } else if (dst == epOrig) { final int epDst = iDst - FILE_COUNT; final int ep88Dst = i88Dst - 16; res._pieces[ep88Dst] = null; res._hashCode ^= ZOBRIST_PIECE_POSITION[_pieces[ep88Dst].ordinal()][epDst]; } } else { assert yDst < ySrc; if (yDst == 0) { res._pieces[i88Dst] = BLACK_QUEEN; res._hashCode ^= ZOBRIST_PIECE_POSITION[pieceOrdinal][iDst]; res._hashCode ^= ZOBRIST_PIECE_POSITION[BLACK_QUEEN.ordinal()][iDst]; } else if ((ySrc == RANK_COUNT - 2) && (yDst == RANK_COUNT - 4)) { res.setEnPassant(Square.valueOf(xDst, RANK_COUNT - 3)); } else if (dst == epOrig) { final int epDst = iDst + FILE_COUNT; final int ep88Dst = i88Dst + 16; res._pieces[ep88Dst] = null; res._hashCode ^= ZOBRIST_PIECE_POSITION[_pieces[ep88Dst].ordinal()][epDst]; } } } final Square epFinal = res.getEnPassant(); if ((epOrig != null) && ((epFinal == null) || (!epOrig.equals(epFinal)))) { res._hashCode ^= ZOBRIST_EN_PASSANT[epOrig.getFile()]; } if ((epFinal != null) && ((epOrig == null) || (!epFinal.equals(epOrig)))) { res._hashCode ^= ZOBRIST_EN_PASSANT[epFinal.getFile()]; } 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 X88Board) { if (hashCode() != pObjet.hashCode()) { return false; } final X88Board o = (X88Board) pObjet; int i = 0; for (int y = 0; y < RANK_COUNT; y++) { for (int x = 0; x < FILE_COUNT; x++) { if (_pieces[i] != o._pieces[i++]) { return false; } } i += 8; } 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; final int idx = pOrigine.getRank() * 16 + pOrigine.getFile(); final Piece piece = _pieces[idx]; if (piece != null) { final Square [] res; synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; addAllTargets(idx); res = new Square [ S_nbBufferedSquares ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { res[t] = Square.valueOf(FROM_X88[SQUARES_BUFFER[t]]); } } return res; } return NO_SQUARE; } /** * 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 [] res; synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; addBishopTargets(pOrigine.getRank() * 16 + pOrigine.getFile(), pBlanc); res = new Square [ S_nbBufferedSquares ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { res[t] = Square.valueOf(FROM_X88[SQUARES_BUFFER[t]]); } } 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 [] res; synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; addKingTargets(pOrigine.getRank() * 16 + pOrigine.getFile(), pBlanc); res = new Square [ S_nbBufferedSquares ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { res[t] = Square.valueOf(FROM_X88[SQUARES_BUFFER[t]]); } } 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 [] res; synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; addKnightTargets(pOrigine.getRank() * 16 + pOrigine.getFile(), pBlanc); res = new Square [ S_nbBufferedSquares ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { res[t] = Square.valueOf(FROM_X88[SQUARES_BUFFER[t]]); } } 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 [] res; synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; addPawnTargets(pOrigine.getRank() * 16 + pOrigine.getFile(), pBlanc); res = new Square [ S_nbBufferedSquares ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { res[t] = Square.valueOf(FROM_X88[SQUARES_BUFFER[t]]); } } 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 _pieces[pCase.getRank() * 16 + pCase.getFile()]; } /** * 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[pLigne * 16 + pColonne]; } /** * 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 [] res; synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; final int idx = pOrigine.getRank() * 16 + pOrigine.getFile(); addBishopTargets(idx, pBlanc); addRookTargets(idx, pBlanc); res = new Square [ S_nbBufferedSquares ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { res[t] = Square.valueOf(FROM_X88[SQUARES_BUFFER[t]]); } } 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 [] res; synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; addRookTargets(pOrigine.getRank() * 16 + pOrigine.getFile(), pBlanc); res = new Square [ S_nbBufferedSquares ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { res[t] = Square.valueOf(FROM_X88[SQUARES_BUFFER[t]]); } } 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) { Move [] tmp = new Move [ 45 ]; int nb = 0; int lTmp = tmp.length; int i88 = 0; for (int y = 0; y < RANK_COUNT; y++) { for (int x = 0; x < FILE_COUNT; x++) { final Piece p = _pieces[i88]; if ((p != null) && (p.isWhite() == pTrait)) { final Square orig = Square.valueOf(FROM_X88[i88]); for (final Square dst : getValidTargets(orig)) { final Piece prise; if ((p.getType() != PAWN) || (dst != getEnPassant())) { prise = _pieces[dst.getRank() * 16 + dst.getFile()]; } else { if (pTrait) { prise = _pieces[dst.getRank() * 16 + dst.getFile() - 16]; } else { prise = _pieces[dst.getRank() * 16 + dst.getFile() + 16]; } } tmp[nb++] = new Move(p, orig, dst, prise); if (nb >= lTmp) { final Move [] extension = new Move [ lTmp + 15 ]; System.arraycopy(tmp, 0, extension, 0, lTmp); tmp = extension; lTmp = tmp.length; } } } i88++; } i88 += 8; } 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 int iSrc = pOrigine.getRank() * 16 + pOrigine.getFile(); final Piece piece = _pieces[iSrc]; if (piece != null) { synchronized (SQUARES_BUFFER) { S_nbBufferedSquares = 0; addAllTargets(iSrc); int nbFinal = S_nbBufferedSquares; final boolean trait = piece.isWhite(); for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { final int idxCible = SQUARES_BUFFER[t]; final Square cible = Square.valueOf(FROM_X88[idxCible]); final Piece prise = _pieces[idxCible]; if (derive(new Move(piece, pOrigine, cible, prise), false).isInCheck(trait)) { SQUARES_BUFFER[t] = -1; nbFinal--; } else 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)) { SQUARES_BUFFER[t] = -1; nbFinal--; } } } } assert (nbFinal >= 0) && (nbFinal <= S_nbBufferedSquares); if (nbFinal == 0) { return NO_SQUARE; } final Square [] res = new Square [ nbFinal ]; for (int t = S_nbBufferedSquares; --t >= 0; /* Pré-décrémenté */) { final int idx = SQUARES_BUFFER[t]; if (idx >= 0) { res[--nbFinal] = Square.valueOf(FROM_X88[idx]); } } return res; } } return NO_SQUARE; } /** * Surcharge du calcul des clés de hachage, pour optimisation. * * @return Clé de hachage. */ @Override public int hashCode() { assert _hashCode == super.hashCode(); return _hashCode; } /** * 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 iSrc = pCible.getRank() * 16 + pCible.getFile(); Piece p = null; int dst = iSrc - 1; // Gauche while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst--; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == ROOK) || (t == QUEEN)) { return true; } } p = null; dst = iSrc + 1; // Droite while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst++; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == ROOK) || (t == QUEEN)) { return true; } } p = null; dst = iSrc - 16; // Bas while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst -= 16; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == ROOK) || (t == QUEEN)) { return true; } } p = null; dst = iSrc + 16; // Haut while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst += 16; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == ROOK) || (t == QUEEN)) { return true; } } p = null; dst = iSrc - 17; // Bas / Gauche while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst -= 17; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == BISHOP) || (t == QUEEN)) { return true; } } p = null; dst = iSrc + 15; // Haut / Gauche while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst += 15; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == BISHOP) || (t == QUEEN)) { return true; } } p = null; dst = iSrc + 17; // Haut / Droit while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst += 17; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == BISHOP) || (t == QUEEN)) { return true; } } p = null; dst = iSrc - 15; // Bas / Droit while ((p == null) && ((dst & X88) == 0)) { p = _pieces[dst]; dst -= 15; } if ((p != null) && (p.isWhite() == pCouleur)) { final PieceType t = p.getType(); if ((t == BISHOP) || (t == QUEEN)) { return true; } } // Cavalier for (final int km : KNIGHT_MOVES) { dst = iSrc + km; if ((dst & X88) == 0) { p = _pieces[dst]; if ((p != null) && (p.isWhite() == pCouleur) && (p.getType() == KNIGHT)) { return true; } } } // Roi for (final int km : KING_MOVES) { dst = iSrc + km; if ((dst & X88) == 0) { p = _pieces[dst]; if ((p != null) && (p.isWhite() == pCouleur) && (p.getType() == KING)) { return true; } } } // Pions... if (pCouleur) { final int ySrc = pCible.getRank(); if (ySrc > 1) { final int xSrc = pCible.getFile(); if (((xSrc > 0) && (_pieces[iSrc - 17] == WHITE_PAWN)) || ((xSrc < FILE_COUNT - 1) && (_pieces[iSrc - 15] == WHITE_PAWN))) { return true; } } } else { final int ySrc = pCible.getRank(); if (ySrc < RANK_COUNT - 2) { final int xSrc = pCible.getFile(); if (((xSrc > 0) && (_pieces[iSrc + 15] == BLACK_PAWN)) || ((xSrc < FILE_COUNT - 1) && (_pieces[iSrc + 17] == 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) { return isAttacked(getKingSquare(pCouleur), !pCouleur); } }