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 }