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 }