001 /** 002 * A GraphicUI is an interactive graphical interface to play Antichess 003 */ 004 005 package ui; 006 007 import java.awt.Dimension; 008 import java.awt.Font; 009 import java.awt.GridLayout; 010 import java.awt.event.ActionEvent; 011 import java.awt.event.ActionListener; 012 import java.awt.event.MouseEvent; 013 import java.awt.event.MouseListener; 014 import java.io.File; 015 import java.io.FileWriter; 016 import java.io.IOException; 017 import java.io.PrintWriter; 018 019 import javax.swing.Box; 020 import javax.swing.BoxLayout; 021 import javax.swing.ButtonGroup; 022 import javax.swing.JButton; 023 import javax.swing.JFileChooser; 024 import javax.swing.JFrame; 025 import javax.swing.JLabel; 026 import javax.swing.JOptionPane; 027 import javax.swing.JPanel; 028 import javax.swing.JRadioButton; 029 import javax.swing.JScrollPane; 030 import javax.swing.JTextArea; 031 import javax.swing.WindowConstants; 032 033 import rules.GameState; 034 035 import core.Color; 036 import core.Move; 037 import core.Position; 038 039 040 public class GraphicUI extends JFrame 041 implements ActionListener, MouseListener { 042 043 private static final long serialVersionUID = 85253; 044 045 // GUI components 046 private JButton newButton; 047 private JButton loadButton; 048 private JButton saveButton; 049 050 private JRadioButton standardButton; 051 private JRadioButton fantasyButton; 052 053 private BoardPanel boardPanel; 054 055 private JLabel wLabel; 056 private JLabel bLabel; 057 private JLabel wTimeLabel; 058 private JLabel bTimeLabel; 059 060 private JTextArea historyArea; 061 062 // Thread that runs the game loop 063 private GameThread gameThread; 064 065 // The position on the BoardPanel selected by the user 066 private Position selectedPosition = null; 067 068 public static void main(String[] args) { 069 GraphicUI gui = new GraphicUI(); 070 GameThread t = new GameThread(gui); 071 gui.setGameThread(t); 072 t.start(); 073 } 074 075 /** 076 * Constructor. Builds the GUI, sets up listeners, and makes it visible. 077 */ 078 public GraphicUI() { 079 080 // Build the GUI 081 JPanel mainPanel; 082 JPanel leftPanel; 083 JPanel buttonPanel; 084 JPanel rightPanel; 085 JPanel timePanel; 086 JScrollPane historyPane; 087 088 newButton = new JButton("New..."); 089 loadButton = new JButton("Load..."); 090 saveButton = new JButton("Save..."); 091 standardButton = new JRadioButton("Standard"); 092 fantasyButton = new JRadioButton("Fantasy"); 093 ButtonGroup bGroup = new ButtonGroup(); 094 bGroup.add(standardButton); 095 bGroup.add(fantasyButton); 096 standardButton.setSelected(true); 097 098 buttonPanel = new JPanel(); 099 buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); 100 buttonPanel.add(newButton); 101 buttonPanel.add(loadButton); 102 buttonPanel.add(saveButton); 103 buttonPanel.add(Box.createRigidArea(new Dimension(20, 5))); 104 buttonPanel.add(standardButton); 105 buttonPanel.add(Box.createRigidArea(new Dimension(5, 5))); 106 buttonPanel.add(fantasyButton); 107 buttonPanel.add(Box.createGlue()); 108 109 boardPanel = new BoardPanel(); 110 111 leftPanel = new JPanel(); 112 leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); 113 leftPanel.add(buttonPanel); 114 leftPanel.add(Box.createRigidArea(new Dimension(0, 10))); 115 leftPanel.add(boardPanel); 116 117 wLabel = new JLabel("White:"); 118 bLabel = new JLabel("Black:"); 119 wTimeLabel = new JLabel(); 120 bTimeLabel = new JLabel(); 121 Font labelFont = new Font(wLabel.getFont().getName(), Font.PLAIN, 18); 122 wLabel.setFont(labelFont); 123 bLabel.setFont(labelFont); 124 wTimeLabel.setFont(labelFont); 125 bTimeLabel.setFont(labelFont); 126 127 timePanel = new JPanel(); 128 timePanel.setLayout(new GridLayout(2, 2)); 129 timePanel.add(wLabel); 130 timePanel.add(wTimeLabel); 131 timePanel.add(bLabel); 132 timePanel.add(bTimeLabel); 133 134 historyArea = new JTextArea(30, 16); 135 historyArea.setFont(new Font("monospaced", Font.PLAIN, 14)); 136 historyArea.setText("White Black"); 137 historyArea.setEditable(false); 138 historyPane = new JScrollPane(historyArea, 139 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 140 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 141 142 rightPanel = new JPanel(); 143 rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS)); 144 rightPanel.add(timePanel); 145 rightPanel.add(Box.createGlue()); 146 rightPanel.add(Box.createRigidArea(new Dimension(0, 15))); 147 rightPanel.add(historyPane); 148 149 mainPanel = new JPanel(); 150 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS)); 151 mainPanel.add(leftPanel); 152 mainPanel.add(Box.createRigidArea(new Dimension(50, 0))); 153 mainPanel.add(rightPanel); 154 155 this.add(mainPanel); 156 157 // Set the close operation 158 this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 159 160 // Add listeners 161 newButton.addActionListener(this); 162 loadButton.addActionListener(this); 163 saveButton.addActionListener(this); 164 standardButton.addActionListener(this); 165 fantasyButton.addActionListener(this); 166 boardPanel.addMouseListener(this); 167 168 // Show the window 169 this.setTitle("Antichess"); 170 this.setSize(this.getPreferredSize()); 171 this.setVisible(true); 172 this.setSize(this.getPreferredSize()); 173 } 174 175 /** 176 * Called when a user presses one of the buttons. 177 */ 178 public void actionPerformed(ActionEvent e) { 179 if (e.getSource() == newButton) { 180 newButtonAction(); 181 } 182 183 else if (e.getSource() == loadButton) { 184 loadButtonAction(); 185 } 186 187 else if (e.getSource() == saveButton) { 188 saveButtonAction(); 189 } 190 191 else if (e.getSource() == standardButton) { 192 standardButtonAction(); 193 } 194 195 else if (e.getSource() == fantasyButton) { 196 fantasyButtonAction(); 197 } 198 } 199 200 /** 201 * Sends a request to the GameThread to start a new game. 202 */ 203 private void newButtonAction() { 204 gameThread.requestNewGame(); 205 } 206 207 /** 208 * Lets the user choose a file via a FileChooser, then sends a request to 209 * the GameThread to load that game file. 210 */ 211 private void loadButtonAction() { 212 JFileChooser fc = new JFileChooser(); 213 fc.showOpenDialog(this); 214 File file = fc.getSelectedFile(); 215 gameThread.requestLoadGame(file); 216 } 217 218 /** 219 * Lets the user choose a file via a FileChooser, then saves the current 220 * game in XML format. 221 */ 222 private void saveButtonAction() { 223 // If no game is in progress, the button does nothing 224 if (gameThread.getGame() == null) { 225 return; 226 } 227 JFileChooser fc = new JFileChooser(); 228 fc.showSaveDialog(this); 229 File file = fc.getSelectedFile(); 230 String xml = gameThread.getGame().getXML(); 231 try { 232 if (file != null) { 233 PrintWriter output = new PrintWriter(new FileWriter(file)); 234 output.print(xml); 235 output.close(); 236 } 237 } 238 catch (IOException x) { 239 JOptionPane.showMessageDialog(this, 240 "Error: Could not save " + file.getName(), 241 "Error", 242 JOptionPane.ERROR_MESSAGE); 243 } 244 } 245 246 private void standardButtonAction() { 247 SquarePanel.setArtDirectory("resources"); 248 this.repaint(); 249 } 250 251 private void fantasyButtonAction() { 252 SquarePanel.setArtDirectory("resources/fantasy"); 253 this.repaint(); 254 } 255 256 /** 257 * Sets the GUI's GameThread. 258 */ 259 private void setGameThread(GameThread t) { 260 this.gameThread = t; 261 } 262 263 // Called by the main thread when it changes the board 264 /** 265 * Looks up the GameState and updates the display accordingly. 266 */ 267 public void updateBoard() { 268 GameState state = 269 gameThread.getGame().getGameState(); 270 boardPanel.updateBoard(state); 271 this.repaint(); 272 } 273 274 /** 275 * Called by the main thread to clear move history 276 */ 277 public void clearHistory() { 278 historyArea.setText("White Black"); 279 historyArea.append("\n"); 280 this.repaint(); 281 } 282 283 /** 284 * Called by the main thread when it makes a move. Updates the TextArea that 285 * lists all past moves. 286 */ 287 public void updateHistory(Move move, boolean whiteMove) { 288 String moveString = move.toString(); 289 if (move.isPass()) { 290 moveString = "pass "; 291 } 292 293 // White just moved 294 if (whiteMove) { 295 historyArea.append("\n" + moveString); 296 } 297 // Black just moved 298 else { 299 historyArea.append(" " + moveString); 300 } 301 302 historyArea.setCaretPosition(historyArea.getText().length()); 303 this.repaint(); 304 } 305 306 /** 307 * Called by the main thread. Updates one of the labels showing how much 308 * time the player has left in the game. 309 * 310 * @param millis is the time in milliseconds left on the player's clock 311 * @param color is the color the player whose clock needs to be updated 312 */ 313 public void updateTime(long millis, Color color) { 314 long seconds = millis / 1000; 315 long minutes = seconds / 60; 316 seconds = seconds % 60; 317 String secondString = String.valueOf(seconds); 318 if (seconds < 10) { 319 secondString = "0" + secondString; 320 } 321 String time = String.valueOf(minutes) + ":" + secondString; 322 if (color == Color.WHITE) { 323 wTimeLabel.setText(time); 324 wTimeLabel.repaint(); 325 } 326 else { 327 bTimeLabel.setText(time); 328 bTimeLabel.repaint(); 329 } 330 } 331 332 // MouseListener methods to listen to the BoardPanel 333 // I only need mouseClicked() 334 335 private Position dragStart = null; 336 337 /** 338 * Called when the user clicks on the BoardPanel. The first click selects a 339 * square; the second click creates a move from those two clicks and sends 340 * it to the GameThread, which will actually make the move happen. 341 */ 342 public void mouseClicked(MouseEvent e) { 343 344 } 345 346 public void mousePressed(MouseEvent e) { 347 // Don't do anything if there is no game, or the game is over 348 if (gameThread.getGame() == null || 349 gameThread.getGame().getWinner() != Color.NONE) { 350 return; 351 } 352 353 // Store the pressed square. 354 dragStart = positionFromMouseEvent(e); 355 } 356 357 public void mouseReleased(MouseEvent e) { 358 // Don't do anything if there is no game, or the game is over 359 if (gameThread.getGame() == null || 360 gameThread.getGame().getWinner() != Color.NONE) { 361 return; 362 } 363 364 // Determine which square was clicked 365 // If the mouse was pressed/released on different squares, do nothing 366 Position dragEnd = positionFromMouseEvent(e); 367 if (dragStart != dragEnd) { 368 dragStart = null; 369 return; 370 } 371 372 // Update the BoardPanel's selected square 373 374 // No square is currently selected 375 if (selectedPosition == null) { 376 if (boardPanel.setSelectedPanel(dragEnd)) { 377 selectedPosition = dragEnd; 378 } 379 } 380 // There is a selected square 381 else { 382 // Only suggest a move if the user clicked a different square 383 if (! selectedPosition.equals(dragEnd)) { 384 // Construct a move and suggest it to the main thread 385 Move move = new Move(selectedPosition, dragEnd); 386 if (gameThread.getGame().getGameState().isLegal(move)) { 387 gameThread.suggestMove(move); 388 } 389 } 390 391 boardPanel.setSelectedPanel(null); 392 selectedPosition = null; 393 } 394 395 this.paint(this.getGraphics()); 396 } 397 398 public void mouseEntered(MouseEvent e) { 399 400 } 401 402 /** 403 * Leaving the BoardPanel means cancelling a "click" in progress. 404 */ 405 public void mouseExited(MouseEvent e) { 406 dragStart = null; 407 } 408 409 /** 410 * @param e is a MouseEvent occuring on a BoardPanel 411 * @return the Position on the board where the MouseEvent took place 412 */ 413 private Position positionFromMouseEvent(MouseEvent e) { 414 int x = (e.getX() * 8) / boardPanel.getWidth(); 415 int y = 7 - ((e.getY() * 8) / boardPanel.getHeight()); 416 return Position.get(x, y); 417 } 418 }