Show Game.java syntax highlighted
package geronimo.hoshigo.model.game;
import geronimo.hoshigo.model.goban.GoColor;
import geronimo.hoshigo.model.goban.GoColorTools;
import geronimo.hoshigo.model.goban.IllegalGobanSize;
import geronimo.hoshigo.model.goban.IllegalVertexException;
import geronimo.hoshigo.model.goban.TakenVertexException;
import geronimo.hoshigo.model.goban.Vertex;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
public class Game implements Runnable
{
private enum InternalState { WAITING_FOR_MOVE,
WAITING_FOR_LEGAL_MOVES,
WAITING_FOR_SCORE,
WAITING_NOTHING,
ENDED };
// joueur qui est en train de jouer ou qui vient de jouer
private GoColor playingColor;
// coups legaux générés
private Set<Move> legalMoves;
// nombre de pierres d'handicap pour noir
public final int handicap;
// valeur du komi
public final float komi;
// joueurs et arbitre
private Player white;
private Player black;
private Referee referee;
// état origine de la partie
private GameState rootGameState;
// état courrant de la partie
public GameState currentGameState; // TODO changer la visibilité en provate - test en cours
// état interne du jeu
private InternalState internalState;
// score de la partie lorsque elle est terminée
private Score score;
// thread associé au jeu
private Thread gameThread;
// écouteurs de changement d'état du jeu
private final Set<GameListener> gameListeners;
/**
* Constructeur
* @param gobanSize taille du goban
* @param handicap nombre de pierres de handicap
* @param komi valeur du komi
* @param freeHandicap definit si l'handicap est libre ou non
* @param black joueur noir
* @param white joueur blanc
* @param referee arbitre
* @throws InvalidHandicapException si les paramètre d'andicap sont invalides
* @throws IllegalGobanSize si la taille du goban est invalide
*/
public Game(int gobanSize, int handicap, float komi, boolean freeHandicap,
Player black, Player white, Referee referee)
throws InvalidHandicapException, IllegalGobanSize
{
super();
// initialisation des champs simples
this.black = null;
this.white = null;
this.referee = null;
this.score = null;
this.handicap = handicap;
this.komi = komi;
this.rootGameState = new GameState(gobanSize);
this.currentGameState = this.rootGameState;
this.gameListeners =
Collections.synchronizedSet(new HashSet<GameListener>());
this.internalState = InternalState.WAITING_NOTHING;
this.gameThread = null;
this.playingColor = GoColor.BLACK;
this.legalMoves = null;
// verification de la valeur du handicap
if (this.handicap < 0 && this.handicap > gobanSize * gobanSize)
{
throw new InvalidHandicapException(handicap);
}
if (handicap != 0 && ! freeHandicap)
{
// si l'handicap n'est pas free on verifie qu'il coresponde à un
// placement fixé normal
if ( ! FixedHandicapGameGenerator.
isValidFixedHandicap(gobanSize,handicap))
{
throw new InvalidHandicapException(handicap);
}
else
{
this.currentGameState =
FixedHandicapGameGenerator.
getHandicapGameState(this.rootGameState,handicap);
// change de joueur courrant après avoir placé les pierres
// de handicap
this.switchCurrentPlayer();
this.rootGameState = this.currentGameState;
}
}
this.setPlayer(GoColor.BLACK,black);
this.setPlayer(GoColor.WHITE, white);
this.setReferee(referee);
}
public synchronized void start()
{
if (this.gameThread == null)
{
// demarre les acteurs du jeu
this.black.start();
this.white.start();
this.referee.start();
// lance le jeu
this.gameThread = new Thread(this);
this.internalState = InternalState.WAITING_NOTHING;
this.gameThread.start();
}
}
private void switchCurrentPlayer()
{
if (this.hasToPlaceHandicap())
{
this.playingColor = GoColor.BLACK;
}
else
{
if (this.currentGameState != this.rootGameState)
{
this.playingColor =
GoColorTools.opposite(this.currentGameState.lastMove.color);
}
}
}
/**
* Décide de l'action à effectuer en fonction de l'état interne et de
* l'état courrant du jeu
*/
private void performActions()
{
// si on attends une réponse alors on n'a rien à faire dans cette
// méthode
if (this.internalState != InternalState.WAITING_NOTHING)
{
return;
}
// si on doit placer des pierres d'handicap
if (this.isEnded())
{
this.queryScore();
}
else if (this.hasToPlaceHandicap())
{
this.requestHandicapMove();
}
else if (! this.hasLegalMoves())
{
this.requestLegalMoves();
}
else if (this.hasLegalMoves())
{
// demande les coups légaux au
this.requestMove();
}
}
/**
* @return <tt>true</tt> si les légal moves ont été calculées
* <tt>false</tt> sinon
*/
private boolean hasLegalMoves()
{
return this.legalMoves != null;
}
/**
* Demande les coups légaux
*/
private void requestLegalMoves()
{
// si on attends une réponse ou qu'on a déjà reçu les coups légaux
// alors on n'a rien à faire dans cette méthode
if (this.internalState != InternalState.WAITING_NOTHING ||
this.hasLegalMoves())
{
return;
}
this.internalState = InternalState.WAITING_FOR_LEGAL_MOVES;
this.referee.legalMoveNeeded();
}
/**
* Demande au joueur suivant de générer sont coup
*/
private void requestMove()
{
// si on attends une réponse alors on n'a rien à faire dans cette
// méthode
if (this.internalState != InternalState.WAITING_NOTHING ||
! this.hasLegalMoves())
{
return;
}
this.internalState = InternalState.WAITING_FOR_MOVE;
this.getPlayer(this.playingColor).moveNeeded(this.legalMoves);
}
/**
* Demande au joueur handicap de donner ses coups
*/
private void requestHandicapMove()
{
// si on attends une réponse alors on n'a rien à faire dans cette
// méthode
if (this.internalState != InternalState.WAITING_NOTHING)
{
return;
}
this.internalState = InternalState.WAITING_FOR_MOVE;
// génération de tout les coups possibles
Set<Move> legalMoves = new HashSet<Move>();
for (Vertex v : this.currentGameState.getGoban().getEmptyCoords())
{
legalMoves.add(new StoneMove(v,this.playingColor));
}
// envoi du message au joueur noir
this.black.moveNeeded(legalMoves);
}
/**
* @return <tt>true</tt> si on a encore besoin de placer des pierres
* de handicap, <tt>false</tt> sinon
*/
private boolean hasToPlaceHandicap()
{
return this.currentGameState.depth < this.handicap;
}
/**
* Retourne le joueur d'une certaine couleur
* @param color couleur du joueur voulu
* @return le joueur d'une certaine couleur
*/
public Player getPlayer(GoColor color)
{
if (color == GoColor.BLACK)
{
return this.black;
}
else
{
return this.white;
}
}
/**
* Met à jour le joueur d'une certaine couleur
* @param color couleur du joueur voulu
* @param player player
* @return le joueur d'une certaine couleur
*/
public synchronized void setPlayer(GoColor color, Player player)
{
Player p = this.getPlayer(color);
if (p != null)
{
this.removeGameListener(p);
p.setGame(null);
}
if (player != null)
{
if (color == GoColor.BLACK)
{
this.black = player;
this.black.setGame(this);
this.addGameListener(player);
}
else
{
this.white = player;
this.white.setGame(this);
this.addGameListener(player);
}
}
// notifie les écouteurs
this.notifyGameListeners();
}
/**
* Change l'arbitre du jeu
* @param referee
*/
public synchronized void setReferee(Referee referee)
{
if (this.referee != null)
{
this.removeGameListener(this.referee);
this.referee.setGame(null);
}
if (referee != null)
{
this.referee = referee;
this.referee.setGame(this);
this.addGameListener(this.referee);
// notifie les écouteurs
this.notifyGameListeners();
}
}
/**
* @return l'arbitre de la partie
*/
public Referee getReferee()
{
return this.referee;
}
/**
* Fonction appelée par un joueur en réponse au
* genMove appelé par le jeu
* @param move coup joué par le joueur
* @param player joueur qui a répondu
*/
public synchronized void moveGenerated(Move move, Player player)
{
// verifie que la méthode est appelée au bon moment
if (this.internalState == InternalState.WAITING_FOR_MOVE &&
this.getPlayer(this.playingColor) == player)
{
try
{
this.currentGameState = new GameState(this.currentGameState, move);
// on met à jour le joueur qui doit jouer
this.switchCurrentPlayer();
}
catch (TakenVertexException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalVertexException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
this.internalState = InternalState.WAITING_NOTHING;
// efface les coups légaux
this.legalMoves = null;
this.notifyGameListeners();
// continue la boucle évenementielle
this.notifyAll();
}
}
}
/**
* Demande le score à l'arbitre
*/
private void queryScore()
{
if (this.internalState == InternalState.WAITING_NOTHING)
{
this.internalState = InternalState.WAITING_FOR_SCORE;
this.referee.scoreNeeded();
}
}
/**
* Notifie tous le écouteurs que l'etat courrant du jeu à changé
*/
private void notifyGameListeners()
{
for (GameListener l : this.gameListeners)
{
l.gameChanged(this);
}
}
/**
* Methode appelée en réponse au scoreNeeded
* @param scrore score calculé
* @param referee référence vers l'arbitre qui réponds
*/
public synchronized void scoreCalculated(Score score, Referee referee)
{
if (this.internalState == InternalState.WAITING_FOR_SCORE
&& this.referee == referee)
{
if (this.isEnded())
{
this.internalState = InternalState.ENDED;
this.score = score;
this.notifyGameEnded();
}
else
{
this.internalState = InternalState.WAITING_NOTHING;
this.score = score;
}
// continue la boucle évènementielle
this.notifyAll();
}
}
/**
* notifie à tous les écouteurs que la partie est terminée
*/
private void notifyGameEnded()
{
for (GameListener l : this.gameListeners)
{
l.gameEnded(this,this.score);
}
}
/**
* Methode appelée en réponse au legalMoveNeeded
* @param legalMoves coups légaux calculés
* @param referee référence vers l'arbitre qui réponds
*/
public synchronized void legalMovesCalculated(Set<Move> legalMoves, Referee referee)
{
if (this.internalState == InternalState.WAITING_FOR_LEGAL_MOVES
&& this.referee == referee)
{
this.legalMoves = legalMoves;
this.internalState = InternalState.WAITING_NOTHING;
this.notifyAll();
}
}
/**
* @return Returns the currentGameState.
*/
public GameState getCurrentGameState()
{
return this.currentGameState;
}
/**
* @return Returns the rootGameState.
*/
public GameState getRootGameState()
{
return this.rootGameState;
}
/**
* le légendaire toString
*/
public String toString()
{
if (this.isEnded() && this.score != null)
{
return this.black.getName() + " vs " + this.white.getName() + " : "
+ this.score;
}
else
{
return this.black.getName() + " vs " + this.white.getName();
}
}
/**
* Détermine si la partie est terminée, en fonction des deux derniers coups
* des joueurs
* @return <tt>true</tt> si la partie est terminnée, <tt>false</tt> sinon
*/
public boolean isEnded()
{
if (this.currentGameState.depth > 1)
{
return this.currentGameState.lastMove.isPass()
&& this.currentGameState.getParent().lastMove.isPass();
}
else
{
return false;
}
}
/**
* Ajoute un écouteur de changement d'état du jeu
* @param listener ecouteur à rajouter
*/
public void addGameListener(GameListener listener)
{
this.gameListeners.add(listener);
}
/**
* Enleve un écouteur de changement d'état du jeu
* @param listener écouteur à enlever
*/
public void removeGameListener(GameListener listener)
{
this.gameListeners.remove(listener);
}
/**
* Boucle principale du thread associé au jeu
*/
public synchronized void run()
{
// lance la première action
this.performActions();
while (this.internalState != InternalState.ENDED)
{
try
{
this.wait();
this.performActions();
}
catch (InterruptedException e)
{
// c'est normal si le programme est destroyé
}
}
// déréférence le thread qui va mourrir
this.gameThread = null;
}
/**
* Annule le dernierCoup et notifie les observeurs
* du nouvel l'état du jeu
* @TODO probleme de synchronisation si un joueur programme est en train de
* jouer, voir abstractplayer.gameChanged()
*/
public synchronized void undo()
{
if (this.currentGameState.depth > 0)
{
// change l'état courrant
this.currentGameState = this.currentGameState.getParent();
this.internalState = InternalState.WAITING_NOTHING;
this.legalMoves = null;
// notifie du changement d'état du jeu
this.notifyGameListeners();
}
}
/**
* Détermine si un état de jeu fait parti d'une partie
* @param gs état à rechercher
* @return <tt>true</tt> si gs est contenu dans la partie,
* <tt>false</tt> sinon
*/
public boolean contains(GameState gs)
{
return this.rootGameState.equals(gs) || this.rootGameState.getAllChilds().contains(gs);
}
/**
* Permet d'avoir la liste des états entre un état de
* départ à un état cible dans l'arborescence du jeu et dans l'ordre
* (de l'état de départ à l'état d'arrivée)
* note : la liste contient aussi l'état de départ et l'état d'arrivée
* @param startState état de départ
* @param endState état cible
* @return la liste des coups à jouer
*/
public List<GameState> getStatesSequenceBeetween(GameState startState,
GameState targetState)
{
List<GameState> startParents = startState.getAllParents();
List<GameState> targetParents = targetState.getAllParents();
// liste des états qui permettent d'aller d'un état à l'autre
List<GameState> interStates = new Vector<GameState>();
// si les deux états sont les memes alors il n'y a pas de séquence
// de coups entre eux
if( startState.equals(targetState) ||
(startParents.isEmpty() && targetParents.isEmpty()) )
{
return interStates;
}
// si les deux états sont sur la meme branche
// et que l'état de départ est plus récent que l'état cible
else if (startParents.contains(targetState))
{
// on ajoute l'état de départ et on enleve les parents de l'état
// cible
interStates.add(startState); // d'abort l'état de départ
interStates.addAll(startParents); // puis les états entre
interStates.removeAll(targetParents); // et enfin on enleve les états supéflus
}
// si les deux états sont sur la meme branche et que l'état de départ
// est moins récent que l'état cible
else if (targetParents.contains(startState))
{
// on ajoute l'état de départ et on enleve les parents de l'état
// cible
interStates.add(targetState); // d'abort l'état de départ
interStates.addAll(targetParents); // puis les états entre
interStates.removeAll(startParents); // enleve les états supéflus
Collections.reverse(interStates); // renverse la liste des états
}
// si les deux états sont sur des branches différentes
else
{
// cree la branche allant du départ au pere commun
List<GameState> startBranch = new Vector<GameState>(startParents);
startBranch.add(0,startState);
startBranch.removeAll(targetParents);
// cree la branche allant du pere commun à la cible
List<GameState> targetBranch = new Vector<GameState>(targetParents);
targetBranch.add(0,targetState);
targetBranch.removeAll(startParents);
Collections.reverse(targetBranch);
// recupere le parent commun
GameState commonFather =targetBranch.get(0).getParent();
// cree la liste des coups
interStates.addAll(startBranch);
interStates.add(commonFather);
interStates.addAll(targetBranch);
}
return interStates;
}
/**
* @return le joueur en train de jouer
*/
public Player getCurrentPlayer()
{
return this.getPlayer(this.playingColor);
}
/**
* @return Retourne la couleur du joueur qui doit jouer
*/
public GoColor getPlayingColor()
{
return this.playingColor;
}
/**
* Méthode appelée lorsque l'on veut détruire la partie
*/
public void destroy()
{
this.gameListeners.clear();
this.internalState = InternalState.ENDED;
if (this.gameThread != null)
{
this.gameThread.interrupt();
}
this.black.destroy();
this.white.destroy();
this.referee.destroy();
}
/**
* @return Returns the score.
*/
public Score getScore()
{
return score;
}
}
See more files for this project here