/* $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 java.util.Random; /** * Squelette de l'implémentation d'une classe représentant un état de la partie. * * @author David Cotton */ @SuppressWarnings("serial") abstract class AbstractBoard implements Board { /** Constantes de pièce / position pour le calcul de clés de hachage "Zobrist". */ protected static final int [][] ZOBRIST_PIECE_POSITION; /** Constante de prise en passant pour le calcul de clés de hachage "Zobrist". */ protected static final int [] ZOBRIST_EN_PASSANT; /** Constante de petit roque pour les noirs pour le calcul de clés de hachage "Zobrist". */ protected static final int ZOBRIST_BLACK_CASTLE_LONG; /** Constante de grand roque pour les noirs pour le calcul de clés de hachage "Zobrist". */ protected static final int ZOBRIST_BLACK_CASTLE_SHORT; /** Constante de trait aux blancs pour le calcul de clés de hachage "Zobrist". */ protected static final int ZOBRIST_WHITE_ACTIVE; /** Constante de petit roque pour les blancs pour le calcul de clés de hachage "Zobrist". */ protected static final int ZOBRIST_WHITE_CASTLE_LONG; /** Constante de grand roque pour les blancs pour le calcul de clés de hachage "Zobrist". */ protected static final int ZOBRIST_WHITE_CASTLE_SHORT; static { final Random rnd = new Random(123456789L); final int nbPieces = Piece.values().length; ZOBRIST_PIECE_POSITION = new int [ nbPieces ] [ FILE_COUNT * RANK_COUNT ]; for (int i = nbPieces; --i >= 0; /* Pré-décrémenté */) { for (int j = FILE_COUNT * RANK_COUNT; --j >= 0; /* Pré-décrémenté */) { ZOBRIST_PIECE_POSITION[i][j] = rnd.nextInt(); } } ZOBRIST_EN_PASSANT = new int [ FILE_COUNT ]; for (int i = FILE_COUNT; --i >= 0; /* Pré-décrémenté */) { ZOBRIST_EN_PASSANT[i] = rnd.nextInt(); } ZOBRIST_BLACK_CASTLE_LONG = rnd.nextInt(); ZOBRIST_BLACK_CASTLE_SHORT = rnd.nextInt(); ZOBRIST_WHITE_ACTIVE = rnd.nextInt(); ZOBRIST_WHITE_CASTLE_LONG = rnd.nextInt(); ZOBRIST_WHITE_CASTLE_SHORT = rnd.nextInt(); } /** Drapeau indiquant le droit de roquer côté roi (petit roque) pour les noirs. */ private boolean _blackCastleShort = true; /** Drapeau indiquant le droit de roquer côté reine (grand roque) pour les noirs. */ private boolean _blackCastleLong = true; /** Case cible de l'éventuelle 'une prise "en passant" disponible (peut être à null). */ private Square _enPassant; /** Numéro du coup. */ private int _fullmoveNumber = 1; /** Compteur des demi-coups depuis la dernière prise ou le dernier mouvement de pion. */ private int _halfmoveCount; /** Drapeau positionné à vrai si le trait est aux blancs. */ private boolean _whiteActive = true; /** Drapeau indiquant le droit de roquer côté roi (petit roque) pour les blancs. */ private boolean _whiteCastleShort = true; /** Drapeau indiquant le droit de roquer côté reine (grand roque) pour les blancs. */ private boolean _whiteCastleLong = true; /** * Crée une nouvelle instance. */ protected AbstractBoard() { // Rien de spécifique... } /** * Crée une nouvelle instance, initialisée à partir de l'état reçu. * * @param pEtat Instance initiale. */ protected AbstractBoard(final Board pEtat) { assert pEtat != null; _blackCastleShort = pEtat.canCastleShort(false); _blackCastleLong = pEtat.canCastleLong(false); _enPassant = pEtat.getEnPassant(); _fullmoveNumber = pEtat.getFullmoveNumber(); _halfmoveCount = pEtat.getHalfmoveCount(); _whiteActive = pEtat.isWhiteActive(); _whiteCastleShort = pEtat.canCastleShort(true); _whiteCastleLong = pEtat.canCastleLong(true); } /** * Crée une nouvelle instance, copiée à partir de l'instance reçue. * * @param pEtat Instance à copier. */ protected AbstractBoard(final AbstractBoard pEtat) { assert pEtat != null; _blackCastleShort = pEtat._blackCastleShort; _blackCastleLong = pEtat._blackCastleLong; _enPassant = pEtat._enPassant; _fullmoveNumber = pEtat._fullmoveNumber; _halfmoveCount = pEtat._halfmoveCount; _whiteActive = pEtat._whiteActive; _whiteCastleShort = pEtat._whiteCastleShort; _whiteCastleLong = pEtat._whiteCastleLong; } /** * Renvoi l'état du droit de roquer côté roi (petit roque) pour une couleur. * * @param pBlanc Positionné à "true" pour obtenir l'état des blancs. * @return Etat du droit de roquer côté roi pour la couleur. */ public final boolean canCastleLong(final boolean pBlanc) { if (pBlanc) { return _whiteCastleLong; } return _blackCastleLong; } /** * Renvoi l'état du droit de roquer côté reine (grand roque) pour une couleur. * * @param pBlanc Positionné à "true" pour obtenir l'état des blancs. * @return Etat du droit de roquer côté reine pour la couleur. */ public final boolean canCastleShort(final boolean pBlanc) { if (pBlanc) { return _whiteCastleShort; } return _blackCastleShort; } /** * Fonction de comparaison entre échiquiers, pour permettre un tri. * * @param pEchiquier Echiquier avec lequel comparer. * @return -1 si l'échiquier est plus "petit", 0 s'ils sont égaux, 1 s'il est plus "grand". * @see Comparable#compareTo(Object) */ public final int compareTo(final Board pEchiquier) { int res = 0; for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */) { for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */) { final Piece p1 = getPieceAt(x, y); final Piece p2 = pEchiquier.getPieceAt(x, y); if ((p1 != null) || (p2 != null)) { if (p1 == null) { return -1; } else if (p2 == null) { return 1; } else { res = p1.compareTo(p2); if (res != 0) { return res; } } } } } final Square ep = pEchiquier.getEnPassant(); if ((_enPassant != null) || (ep != null)) { if (_enPassant == null) { return -1; } else if (ep == null) { return 1; } else { res = _enPassant.compareTo(ep); } } if (res == 0) { res = Boolean.valueOf(_whiteActive).compareTo(Boolean.valueOf(pEchiquier.isWhiteActive())); } if (res == 0) { res = Boolean.valueOf(_blackCastleLong).compareTo( Boolean.valueOf(pEchiquier.canCastleLong(false))); } if (res == 0) { res = Boolean.valueOf(_blackCastleShort).compareTo( Boolean.valueOf(pEchiquier.canCastleShort(false))); } if (res == 0) { res = Boolean.valueOf(_whiteCastleLong).compareTo( Boolean.valueOf(pEchiquier.canCastleLong(true))); } if (res == 0) { res = Boolean.valueOf(_whiteCastleShort).compareTo( Boolean.valueOf(pEchiquier.canCastleShort(true))); } return res; } /** * Teste l'égalité entre deux descriptions de partie. * <p> * Attention : le nombre de demi-coups depuis la dernière prise ainsi que le numéro du tour ne * sont pas pris en compte dans la comparaison. * </p> * <p> * Pour des raisons de performance, il est préférable que les implémentations concrètes de cette * classe prévoient une version spécialisée du test d'égalité. De plus, pour conserver * l'interopérabilité, les implémentations doivent rester compatibles sur ce test. * </p> * * @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 Board)) { return false; } if (hashCode() != pObjet.hashCode()) { return false; } final Board o = (Board) pObjet; for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */) { for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */) { if (getPieceAt(x, y) != o.getPieceAt(x, y)) { return false; } } } return equalsInternal(o); } /** * Teste l'égalité sur les données internes de cette classe. * * @param pEtat Etat avec lequel comparer. * @return Vrai si les données internes sont égales. */ protected final boolean equalsInternal(final Board pEtat) { assert pEtat != null; boolean res = true; if (_whiteActive != pEtat.isWhiteActive()) { res = false; } else if (_enPassant != pEtat.getEnPassant()) { res = false; } else if (_blackCastleLong != pEtat.canCastleLong(false)) { res = false; } else if (_blackCastleShort != pEtat.canCastleShort(false)) { res = false; } else if (_whiteCastleLong != pEtat.canCastleLong(true)) { res = false; } else if (_whiteCastleShort != pEtat.canCastleShort(true)) { res = false; } return res; } /** * Renvoi l'éventuelle case cible de la prise "en passant" en cours. * * @return Case cible de la price "en passant" (peut être à null). */ public final Square getEnPassant() { return _enPassant; } /** * Renvoi le numéro du coup. * * @return Numéro de coup (> 0). */ public final int getFullmoveNumber() { assert _fullmoveNumber > 0; return _fullmoveNumber; } /** * Renvoi la valeur du compteur de demi-coups depuis la dernière prise ou le dernier mouvement de * pion. * * @return Nombre de demi-coups (>= 0). */ public final int getHalfmoveCount() { assert _halfmoveCount >= 0; return _halfmoveCount; } /** * Implémentation par défaut du calcul des clés de hachage, suivant la méthode "Zobrist". * <p> * Les implémentations concrètes devraient surcharger cette méthode pour des raisons de * performance. Attention : les résultats produits par les différentes implémentations doivent * rester compatibles si l'on souhaite l'interopérabilité. * </p> * * @return Clé de hachage. */ @Override public int hashCode() { int res = zobristRoot(); for (final Square s : Square.values()) { final Piece p = getPieceAt(s); if (p != null) { res ^= ZOBRIST_PIECE_POSITION[p.ordinal()][s.getIndex()]; } } return res; } /** * Indique si le trait est aux blancs. * * @return "true" si le trait est aux blancs, "false" s'il est aux noirs. */ public final boolean isWhiteActive() { return _whiteActive; } /** * Alimente l'état du droit de roquer côté roi (petit roque) pour une couleur. * <p> * Potentiellement dangeureux : attention à ne pas casser le contrat qui veut que les * implémentations se comportent comme des objets immuables. * </p> * * @param pBlanc Positionné à "true" pour alimenter l'état des blancs. * @param pEtat Etat du droit de roquer côté roi pour la couleur. */ protected void setCastleLong(final boolean pBlanc, final boolean pEtat) { if (pBlanc) { _whiteCastleLong = pEtat; } else { _blackCastleLong = pEtat; } } /** * Alimente l'état du droit de roquer côté reine (grand roque) pour une couleur. * <p> * Potentiellement dangeureux : attention à ne pas casser le contrat qui veut que les * implémentations se comportent comme des objets immuables. * </p> * * @param pBlanc Positionné à "true" pour alimenter l'état des blancs. * @param pEtat Etat du droit de roquer côté reine pour la couleur. */ protected void setCastleShort(final boolean pBlanc, final boolean pEtat) { if (pBlanc) { _whiteCastleShort = pEtat; } else { _blackCastleShort = pEtat; } } /** * Alimente l'éventuelle case cible de la prise "en passant". * <p> * Potentiellement dangeureux : attention à ne pas casser le contrat qui veut que les * implémentations se comportent comme des objets immuables. * </p> * * @param pCase Case cible de la price "en passant" (peut être à null). */ protected void setEnPassant(final Square pCase) { _enPassant = pCase; } /** * Alimente le numéro du coup. * <p> * Potentiellement dangeureux : attention à ne pas casser le contrat qui veut que les * implémentations se comportent comme des objets immuables. * </p> * * @param pNumero Numéro de coup (> 0). */ protected void setFullmoveNumber(final int pNumero) { assert pNumero > 0; _fullmoveNumber = pNumero; } /** * Alimente la valeur du compteur de demi-coups depuis la dernière prise ou le dernier mouvement * de pion. * <p> * Potentiellement dangeureux : attention à ne pas casser le contrat qui veut que les * implémentations se comportent comme des objets immuables. * </p> * * @param pNombre Nombre de demi-coups (>= 0). */ protected void setHalfmoveCount(final int pNombre) { assert pNombre >= 0; _halfmoveCount = pNombre; } /** * Alimente le drapeau du trait. * * @param pTrait "true" si le trait va aux blancs, "false" s'il va aux noirs. */ protected void setWhiteActive(final boolean pTrait) { _whiteActive = pTrait; } /** * Renvoi une chaine représentant le plateau. * * @return Chaine représentant le plateau. */ @Override public final String toString() { final StringBuilder res = new StringBuilder(); for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */) { for (int x = 0; x < FILE_COUNT; x++) { final Piece p = getPieceAt(x, y); if (p == null) { res.append('+'); } else { res.append(p.getFENLetter()); } } res.append('\n'); } return res.toString(); } /** * Calcule le début de la clé de hachage "Zobrist". * * @return Partie de la clé correspondant aux données internes de cette classe. */ protected final int zobristRoot() { int res = 0; if (_blackCastleLong) { res ^= ZOBRIST_BLACK_CASTLE_LONG; } if (_blackCastleShort) { res ^= ZOBRIST_BLACK_CASTLE_SHORT; } if (_enPassant != null) { res ^= ZOBRIST_EN_PASSANT[_enPassant.getFile()]; } if (_whiteActive) { res ^= ZOBRIST_WHITE_ACTIVE; } if (_whiteCastleLong) { res ^= ZOBRIST_WHITE_CASTLE_LONG; } if (_whiteCastleShort) { res ^= ZOBRIST_WHITE_CASTLE_SHORT; } return res; } }