001    /**
002     * A GameThread is the main loop that drives a game of Antichess. The user
003     * interacts through this thread via a GraphicUI.
004     */
005    
006    package ui;
007    
008    import java.io.File;
009    import java.net.URL;
010    
011    import javax.swing.ImageIcon;
012    import javax.swing.JOptionPane;
013    
014    import core.Color;
015    import core.Game;
016    import core.GameLoadException;
017    import core.HumanPlayer;
018    import core.Move;
019    import core.Piece;
020    import core.Player;
021    
022    import rules.King;
023    import ai.AIFactory;
024    
025    public class GameThread extends Thread implements StopwatchListener {
026            private Game game;
027            private GraphicUI gui;
028            private TimerBox timerBox;
029        private AIFactory aiFactory;
030    
031            boolean newGameRequested = false; // Has the user requested a new game?
032            File loadFile = null; // Null until the user tries to load a game
033            Move suggestedMove = null; // Move input from the user
034            Move nextAiMove = null; // Move input from the AI
035            Runnable aiRunnable; // The Runnable calculating the AI's next move
036            boolean waitingForAi = false;
037            boolean gameOverMessage = false; // Has the game-over prompt been displayed?
038            
039            public GameThread(GraphicUI gui) {
040                    game = null;
041                    this.gui = gui;
042                    aiFactory = new AIFactory();
043            }
044            
045    
046    
047            /**
048             * Sets up a default game, and then runs the game loop
049             */
050            public synchronized void run() {
051                    // Start a default game: human (white) vs. computer, 5min to a side
052                    Player whitePlayer = new HumanPlayer(Color.WHITE);
053                    int initialTime = 300000;
054                    Player blackPlayer = aiFactory.createPlayer(false, initialTime, 
055                                                                initialTime, null);
056                    timerBox = new TimerBox(this);
057                    game = new Game(whitePlayer, initialTime, blackPlayer, initialTime);
058                    setWatches();
059                    gui.updateBoard();
060                    
061                    try {
062                            while (true) {
063                                    // The user asked for a new game
064                                    if (newGameRequested) {
065                                            startNewGame();
066                                    }
067                                    
068                                    // The user tried to load a game
069                                    if (loadFile != null) {
070                                            loadGame();
071                                            loadFile = null;
072                                    }
073                                    
074                                    // No game in progress; wait for user input
075                                    else if (game == null) {
076                                            this.wait();
077                                    }
078                                    
079                                    // The current game is over; wait for user input
080                                    else if (game.getWinner() != Color.NONE) {
081                                            if (! gameOverMessage) {
082                                                    timerBox.switchClockTo(Color.NONE);
083                                                    ImageIcon icon;
084                                                    
085                                                    String imgDir = SquarePanel.getArtDirectory();
086                                                    Piece k = new King(game.getWinner(), 0);
087                                                    String imgFile = k.toString() +
088                                                    k.getColor().toString() + ".gif";
089                                                    URL url = getClass().getResource("/"+imgDir + "/" +
090                                                                    imgFile);
091                                                    if (url != null){ //run from JAR
092                                                            icon = new ImageIcon(url);
093                                                    } else {
094                                                            icon = new ImageIcon(imgDir + File.separator
095                                                                            + imgFile);
096                                                    }
097                                                    
098                                                    JOptionPane.showMessageDialog(gui, "The " +
099                                                                    game.getWinner() + " player won " +
100                                                            game.getWinReason().humanReadableString() +
101                                                            "\n\nPlease start or load a new game.",
102                                                            "Game Over", JOptionPane.PLAIN_MESSAGE, icon);
103                                                    gameOverMessage = true;
104                                                    wait();
105                                            }
106                                    }
107                                    
108                                    // There is a game in progress, and it's the human's turn
109                                    else if (game.getCurrentPlayer() instanceof HumanPlayer) {
110                                            // The human has no legal moves and must pass
111                                            if (game.getGameState().isLegal(new Move())) {
112                                                    gui.updateHistory(new Move(),
113                                                                    game.getCurrentPlayer().getColor().equals(
114                                                                                    Color.WHITE));
115                                                    game.makeMove(new Move());
116                                                    gui.updateBoard();
117                                                    
118                                            }
119                                            // The human player hasn't entered a move via the GUI
120                                            else if (suggestedMove == null) {
121                                                    this.wait();
122                                            }
123                                            else {
124                                                    nextHumanMove();
125                                            }
126                                    }
127                                    
128                                    // It's the computer's turn
129                                    // Get the next AI move
130                                    else if (nextAiMove == null) {
131                                            //Thread.sleep(200);
132                                            if (! waitingForAi) {
133                                                    getNextAiMove();
134                                            }
135                                            wait();
136                                    }
137                                    
138                                    else {
139                                            makeNextAiMove();
140                                    }
141                                    
142                                    // Give GUI commands a chance to be recognized
143                                    this.wait(1);
144                            }
145                    }
146                    catch (InterruptedException x) {}
147            }
148            
149            /**
150             * @return the Game this thread is running
151             */
152            public Game getGame() {
153                    return game;
154            }
155            
156            /**
157             * This method is called by the GUI when the user requests a new game.
158             */
159            public synchronized void requestNewGame() {
160                    newGameRequested = true;
161                    this.notifyAll();
162            }
163            
164            /**
165             * This method is called by the GUI when the user requests to load a game.
166             */
167            public synchronized void requestLoadGame(File file) {
168                    loadFile = file;
169                    this.notifyAll();
170            }
171            
172            /**
173             * This method is called by the GUI when the user tries to make a move.
174             */
175            public synchronized void suggestMove(Move m) {
176                    if (game.getCurrentPlayer() instanceof HumanPlayer) {
177                            suggestedMove = m;
178                            this.notifyAll();
179                    }
180            }
181            
182            /**
183             * Begins a new game of Antichess. The user chooses the settings for the
184             * game with a NewGameDialog.
185             */
186            private void startNewGame() {
187                    GameSettings g = NewGameDialog.showDialog();
188                    
189                    if (g == null) {
190                            newGameRequested = false;
191                            return;
192                    }
193                    
194                    Player whitePlayer;
195                    Player blackPlayer;
196                    
197                    if (g.isWhiteHuman()) {
198                            whitePlayer = new HumanPlayer(core.Color.WHITE);
199                    }
200                    else {
201                        whitePlayer = aiFactory.createPlayer(true, g.getWhiteTime(), 
202                                                       g.getBlackTime(), g.getPowerupString());
203                    }
204                    
205                    if (g.isBlackHuman()) {
206                            blackPlayer = new HumanPlayer(core.Color.BLACK);
207                    }
208                    else {
209                        blackPlayer = aiFactory.createPlayer(false, g.getWhiteTime(), 
210                                                       g.getBlackTime(), g.getPowerupString());
211                    }
212                    
213                    game = new Game(whitePlayer, g.getWhiteTime(),
214                                    blackPlayer, g.getBlackTime(), g.getPowerupString());
215                    
216                    newGameRequested = false;
217                    suggestedMove = null;
218                    nextAiMove = null;
219                    waitingForAi = false;
220                    aiRunnable = null;
221                    gameOverMessage = false;
222                    gui.updateBoard();
223                    gui.clearHistory();
224                    setWatches();
225            }
226            
227            /**
228             * Loads and starts a game from a savefile. The loaded game will have the
229             * same types of players (human/computer) as the current game.
230             */
231            private void loadGame() {
232                    // Create the players
233                    Player whitePlayer;
234                    Player blackPlayer;
235                    
236                    // No game in progress: Default to human-vs-human game
237                    if (game == null) {
238                            whitePlayer = new HumanPlayer(Color.WHITE);
239                            blackPlayer = new HumanPlayer(Color.BLACK);
240                    }
241                    // Use player types of current game
242                    else {
243                        if (game.getPlayerByColor(Color.WHITE) instanceof HumanPlayer) {
244                            whitePlayer = new HumanPlayer(Color.WHITE);
245                        }
246                        else {
247                            whitePlayer = aiFactory.createPlayer(true, 0, 0, null);
248                        }
249                            
250                            if (game.getPlayerByColor(Color.BLACK) instanceof HumanPlayer) {
251                                    blackPlayer = new HumanPlayer(Color.BLACK);
252                            }
253                            else {
254                                blackPlayer = aiFactory.createPlayer(false, 0, 0, null);
255                            }
256                    }
257    
258                    try {
259                            game = Game.loadGame(whitePlayer, blackPlayer, loadFile);
260                            
261                            // Update players
262                            whitePlayer.update(game);
263                            blackPlayer.update(game);
264                            
265                            loadFile = null;
266                            suggestedMove = null;
267                            nextAiMove = null;
268                            waitingForAi = false;
269                            aiRunnable = null;
270                            gameOverMessage = false;
271                            gui.updateBoard();
272                            gui.clearHistory();
273                            int moveNum = 1;
274                            for (Move m : game.getMoveHistory()) {
275                                    gui.updateHistory(m, moveNum % 2 != 0);
276                                    moveNum++;
277                            }
278                            setWatches();
279                    }
280                    catch (GameLoadException e) {
281                            JOptionPane.showMessageDialog(gui, loadFile.getName() +
282                                            " could not be loaded.\n" + e.getMessage(), "Error",
283                                            JOptionPane.ERROR_MESSAGE);
284                            e.printStackTrace();
285                    }
286            }
287            
288            /**
289             * Gets the AI player's next move, then updates the GUI and timers
290             * accordingly.
291             */
292            private synchronized void getNextAiMove() {
293                    waitingForAi = true;
294                    
295                    // find CPU player's next move
296                    final Player cpu = game.getCurrentPlayer();
297    
298                    final Move lastMove;
299                    if (game.getMoveHistory().size() == 0) {
300                        lastMove = new Move();
301                    }
302                    else {
303                            lastMove= game.getMoveHistory().get(game.getMoveHistory().size()
304                                            - 1);
305                    }
306                    final long thistime = game.getTimeLeftForPlayer(cpu);
307                    //XXX UGLY!
308                    final long othertime = game.getTimeLeftForPlayer(
309                                    game.getPlayerByColor(
310                                                    game.getGameState().getCurrentColor().otherColor()));
311                    
312                    aiRunnable = new Runnable() {
313                            public synchronized void run() {
314                                    Move move = cpu.askForMove(lastMove, thistime, othertime);
315                                    //
316                                    if (aiRunnable == this) {
317                                            nextAiMove = move;
318                                    }
319                                    this.notifyAll();
320                            }
321                    };
322                    Thread t = new Thread(aiRunnable);
323                    t.start();
324            }
325            
326            private void makeNextAiMove() {
327                    if (!game.getGameState().isLegal(nextAiMove)){
328                        throw new RuntimeException("AI returned invalid move");
329                    }
330                    gui.updateHistory(nextAiMove,
331                                    game.getCurrentPlayer().getColor() == Color.WHITE);
332                    if (game.getWinner() != Color.NONE) {
333                            return;
334                    }
335                    game.makeMove(nextAiMove);
336                    gui.updateBoard();
337                    timerBox.switchClockTo(game.getCurrentPlayer().getColor());
338                    nextAiMove = null;
339                    waitingForAi = false;
340            }
341            
342            /**
343             * Uses the input from the GUI to make a move for a human player.
344             */
345            private synchronized void nextHumanMove() {
346                    // The player tried to make an illegal move
347                    if (suggestedMove == null) {
348                            return;
349                    }
350                    
351                    if (!game.getGameState().isLegal(suggestedMove)){
352                        throw new RuntimeException("Human returned invalid move");
353                    }
354                    gui.updateHistory(suggestedMove,
355                                    game.getCurrentPlayer().getColor() == Color.WHITE);
356                    game.makeMove(suggestedMove);
357                    gui.updateBoard();
358                    timerBox.switchClockTo(game.getCurrentPlayer().getColor());
359                    
360                    suggestedMove = null;
361            }
362            
363            /**
364             * Triggered by Stopwatch events. Keeps the GameState and GUI updated as far
365             * as time left in the game.
366             */
367            public synchronized void timeChanged(long millis, Stopwatch s) {
368                    //System.out.println("Time changed");
369                    if (s == timerBox.getWhiteWatch()) {
370                            game.setTime(game.getPlayerByColor(Color.WHITE), (int) millis);
371                            gui.updateTime(millis, Color.WHITE);
372                    }
373                    else if (s == timerBox.getBlackWatch()) {
374                            game.setTime(game.getPlayerByColor(Color.BLACK), (int) millis);
375                            gui.updateTime(millis, Color.BLACK);
376                    }
377                    this.notifyAll();
378            }
379            
380            /**
381             * Sets up the TimerBox for a new or newly loaded game.
382             */
383            private void setWatches() {
384                    timerBox.setWatches(game.getTimeLeftForPlayer(
385                                    game.getPlayerByColor(Color.WHITE)),
386                                    game.getTimeLeftForPlayer(
387                                                    game.getPlayerByColor(Color.BLACK)));
388                    
389                    timerBox.switchClockTo(game.getCurrentPlayer().getColor());
390            }
391            
392    }