/*
$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.BoardFactory.State.STARTING;
import static fr.free.jchecs.core.BoardFactory.Type.FASTEST;
import static fr.free.jchecs.core.FENUtils.toFEN;
import static fr.free.jchecs.core.SANUtils.toSAN;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* Description d'une partie en cours.
*
* @author David Cotton
*/
public final class Game
{
/** Temps (en ms) alloué à un joueur, pour une partie. */
private static final long GAME_DURATION = 15 * 60 * 1000;
/** Support des propriétés liées. */
final PropertyChangeSupport _propertyChangeSupport = new PropertyChangeSupport(this);
/** Description du joueur noir. */
private final Player _blackPlayer = new Player(false);
/** Liste des positions de l'échiquier. */
private final List<MoveGenerator> _positions = new ArrayList<MoveGenerator>();
/** Liste des mouvements éxécutés. */
private final List<Move> _moves = new ArrayList<Move>();
/** Liste des notations SAN des mouvements. */
private final List<String> _sanMoves = new ArrayList<String>();
/** Description du joueur blanc. */
private final Player _whitePlayer = new Player(true);
/** Valeur courante du compteur de temps des noirs. */
long _blackTimer;
/** Mouvement courant (>= 0). */
private int _currentMove;
/** Position courante (> 0). */
private int _currentPosition;
/** Valeur du dernier relevé du timer. */
long _lastTimerTick;
/** Timer de l'horloge. */
private Timer _timer;
/** Valeur courante du compteur de temps des blancs. */
long _whiteTimer;
/**
* Instancie une nouvelle partie.
*/
public Game()
{
resetTo(BoardFactory.valueOf(FASTEST, STARTING));
}
/**
* Ajoute un objet à l'écoute des changements de propriétés.
*
* @param pPropriete Propriété à écouter.
* @param pEcouteur Objet à ajouter à l'écoute.
*/
public void addPropertyChangeListener(final String pPropriete,
final PropertyChangeListener pEcouteur)
{
assert pPropriete != null;
assert pEcouteur != null;
_propertyChangeSupport.addPropertyChangeListener(pPropriete, pEcouteur);
}
/**
* Renvoi la description courante de l'échiquier.
*
* @return Description courante de l'échiquier.
*/
public MoveGenerator getBoard()
{
assert (_currentPosition > 0) && (_currentPosition <= _positions.size());
return _positions.get(_currentPosition - 1);
}
/**
* Renvoi l'éventuel mouvement en cours.
*
* @return Mouvement courant (ou null si aucun).
*/
public Move getCurrentMove()
{
Move res = null;
if ((_currentMove > 0) && (_currentMove <= _moves.size()))
{
res = _moves.get(getCurrentMoveIndex());
}
return res;
}
/**
* Renvoi l'indice du mouvement courant.
*
* @return Index du mouvement actuel.
*/
public int getCurrentMoveIndex()
{
assert (_currentMove >= 0) && (_currentMove <= _moves.size());
return _currentMove - 1;
}
/**
* Renvoi la chaine FEN correspondant à la position courante.
*
* @return Chaîne FEN de la position courante.
*/
public String getFENPosition()
{
assert (_currentPosition > 0) && (_currentPosition <= _positions.size());
return toFEN(getBoard());
}
/**
* Renvoi le nombre de mouvements stocké.
*
* @return Nombre de mouvements.
*/
public int getMovesCount()
{
return _moves.size();
}
/**
* Renvoi l'ensemble des mouvements juqu'au mouvement courant.
*
* @return Liste des mouvements jusqu'au mouvement courant.
*/
public Move [] getMovesToCurrent()
{
final Move [] res = new Move [ _currentMove ];
for (int i = 0; i < _currentMove; i++)
{
res[i] = _moves.get(i);
}
return res;
}
/**
* Renvoi la définition de joueur correspondant à une couleur.
*
* @param pCouleur A "true" pour les joueur blanc, "false" pour le noir.
* @return Joueur correspondant.
*/
public Player getPlayer(final boolean pCouleur)
{
if (pCouleur)
{
return _whitePlayer;
}
return _blackPlayer;
}
/**
* Renvoi la liste des chaînes SAN correspondant aux mouvements.
*
* @return Liste des chaînes SAN.
*/
public String [] getSANStrings()
{
return _sanMoves.toArray(new String [ _sanMoves.size() ]);
}
/**
* Renvoi la chaîne FEN de la position de départ.
*
* @return Chaîne FEN de la position de départ.
*/
public String getStartingPosition()
{
return toFEN(_positions.get(0));
}
/**
* Renvoi l'état de la partie en cours.
*
* @return Etat de la partie.
*/
public State getState()
{
final State res;
final MoveGenerator etat = getBoard();
final boolean trait = etat.isWhiteActive();
if (etat.getValidMoves(trait).length == 0)
{
if (etat.isInCheck(trait))
{
if (trait)
{
res = State.BLACK_MATES;
}
else
{
res = State.WHITE_MATES;
}
}
else
{
res = State.STALEMATE;
}
}
else if (etat.getHalfmoveCount() > 50)
{
res = State.DRAWN_BY_50_MOVE_RULE;
}
else
{
final MoveGenerator enCours = getBoard();
int rep = 0;
for (int i = 0; i < _currentPosition; i++)
{
if (enCours.equals(_positions.get(i)))
{
rep++;
if (rep >= 3)
{
return State.DRAWN_BY_TRIPLE_REPETITION;
}
}
}
res = State.IN_PROGRESS;
}
return res;
}
/**
* Renvoi la valeur courante d'un timer pour un joueur.
*
* @param pCouleur A "true" pour le timer des blancs, à "false" pour les noirs.
* @return Valeur courante du timer correspondant.
*/
public long getTimer(final boolean pCouleur)
{
if (pCouleur)
{
return _whiteTimer;
}
return _blackTimer;
}
/**
* Aller au premier mouvement.
*/
public void goFirst()
{
if (_currentMove > 0)
{
_currentMove = 0;
_currentPosition = 1;
_propertyChangeSupport.firePropertyChange("position", null, null);
}
}
/**
* Aller au dernier mouvement.
*/
public void goLast()
{
final int s = _moves.size();
if (_currentMove < s)
{
_currentMove = s;
_currentPosition = _positions.size();
_propertyChangeSupport.firePropertyChange("position", null, null);
}
}
/**
* Aller au mouvement suivant.
*/
public void goNext()
{
if (_currentMove < _moves.size())
{
_currentMove++;
_currentPosition++;
_propertyChangeSupport.firePropertyChange("position", null, null);
}
}
/**
* Aller au mouvement précédent.
*/
public void goPrevious()
{
if (_currentMove > 0)
{
_currentMove--;
_currentPosition--;
_propertyChangeSupport.firePropertyChange("position", null, null);
}
}
/**
* Ajoute un mouvement à partir de la position courante.
*
* @param pMouvement Mouvement à ajouter.
*/
public void moveFromCurrent(final Move pMouvement)
{
assert pMouvement != null;
while (_moves.size() > _currentMove)
{
_moves.remove(_moves.size() - 1);
_sanMoves.remove(_sanMoves.size() - 1);
}
while (_positions.size() > _currentPosition)
{
_positions.remove(_positions.size() - 1);
}
MoveGenerator etat = getBoard();
final boolean trait = etat.isWhiteActive();
final StringBuilder san = new StringBuilder();
if (trait)
{
san.append(etat.getFullmoveNumber()).append(". ");
}
san.append(toSAN(etat, pMouvement));
san.append(' ');
etat = etat.derive(pMouvement, true);
_positions.add(etat);
_currentPosition = _positions.size();
_moves.add(pMouvement);
_currentMove = _moves.size();
switch (getState())
{
case IN_PROGRESS :
break;
case WHITE_MATES :
san.append("1-0");
break;
case BLACK_MATES :
san.append("0-1");
break;
case STALEMATE :
san.append("1/2-1/2");
break;
case DRAWN_BY_50_MOVE_RULE :
case DRAWN_BY_TRIPLE_REPETITION :
san.append("1/2-1/2 {Repetition}");
break;
default :
assert false;
}
_sanMoves.add(san.toString());
assert _moves.size() == _sanMoves.size();
assert _positions.size() == (_moves.size() + 1);
_propertyChangeSupport.firePropertyChange("position", null, null);
}
/**
* Supprime un objet à l'écoute des changements de propriétés.
*
* @param pPropriete Propriété à écouter.
* @param pEcouteur Objet à ajouter à l'écoute.
*/
public void removePropertyChangeListener(final String pPropriete,
final PropertyChangeListener pEcouteur)
{
assert pPropriete != null;
assert pEcouteur != null;
_propertyChangeSupport.removePropertyChangeListener(pPropriete, pEcouteur);
}
/**
* Ré-initialise la description de la partie à la position transmise.
*
* @param pEtat Nouvel état de l'échiquier.
*/
public void resetTo(final MoveGenerator pEtat)
{
assert pEtat != null;
_moves.clear();
_sanMoves.clear();
_currentMove = _moves.size();
_positions.clear();
_positions.add(pEtat);
_currentPosition = _positions.size();
if (_timer != null)
{
_timer.cancel();
}
_blackTimer = GAME_DURATION;
_whiteTimer = GAME_DURATION;
_lastTimerTick = System.currentTimeMillis();
_timer = new Timer();
_propertyChangeSupport.firePropertyChange("position", null, null);
_timer.scheduleAtFixedRate(new TimerTask()
{
/**
* Action déclenchée périodiquement par le timer gérant l'horloge.
*/
@Override
public void run()
{
if (getState() == State.IN_PROGRESS)
{
final long time = System.currentTimeMillis();
if (getBoard().isWhiteActive())
{
_whiteTimer -= time - _lastTimerTick;
}
else
{
_blackTimer -= time - _lastTimerTick;
}
_lastTimerTick = time;
_propertyChangeSupport.firePropertyChange("timer", null, null);
}
}
}, 250, 1000);
}
/** Enumération des états possibles d'une partie. */
public static enum State
{
/** En cours. */
IN_PROGRESS,
/** Victoire des noirs. */
BLACK_MATES,
/** Victoire des blancs. */
WHITE_MATES,
/** Pat. */
STALEMATE,
/** Terminée suite à la répétition de la même position 3 fois ou plus. */
DRAWN_BY_TRIPLE_REPETITION,
/** Terminée par la règle des 50 coups. */
DRAWN_BY_50_MOVE_RULE;
}
}