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    }