rcrr.reversi.ui.ReversiUI.java Source code

Java tutorial

Introduction

Here is the source code for rcrr.reversi.ui.ReversiUI.java

Source

/*
 *  ReversiUI.java
 *
 *  Copyright (c) 2010, 2011, 2012 Roberto Corradini. All rights reserved.
 *
 *  This file is part of the reversi program
 *  http://github.com/rcrr/reversi
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3, or (at your option) any
 *  later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *  or visit the site <http://www.gnu.org/licenses/>.
 */

package rcrr.reversi.ui;

import java.lang.reflect.InvocationTargetException;

import java.util.EnumMap;
import java.util.Map;

import java.util.concurrent.ExecutionException;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

import javax.swing.border.TitledBorder;

import org.joda.time.Period;

import rcrr.reversi.Game;
import rcrr.reversi.Actor;
import rcrr.reversi.Move;
import rcrr.reversi.GameSnapshot;
import rcrr.reversi.Strategy;
import rcrr.reversi.IagoStrategy;

import rcrr.reversi.board.Board;
import rcrr.reversi.board.Column;
import rcrr.reversi.board.Player;
import rcrr.reversi.board.Row;
import rcrr.reversi.board.Square;
import rcrr.reversi.board.SquareState;

public class ReversiUI {

    private static final class AboutFrame extends JFrame implements ActionListener {
        AboutFrame() {
            super("About Reversi");
            setLayout(new FlowLayout());
            add(new JLabel(Constants.LOGO_128X128_ICON));
            JLabel programAndVersion = new JLabel("Reversi 2.0.0");
            programAndVersion.setFont(new Font("Courier", Font.BOLD, 24));
            add(programAndVersion);
            JLabel info = new JLabel("http://github.com/rcrr/reversi");
            info.setFont(new Font("Courier", Font.PLAIN, 10));
            add(info);
            add(Box.createRigidArea(new Dimension(0, 30)));
            JLabel copyright = new JLabel("Copyright (c) 2010, 2011, 2012");
            copyright.setFont(new Font("Courier", Font.PLAIN, 10));
            add(copyright);
            JLabel copyright2 = new JLabel("Roberto Corradini, rob_corradini@yahoo.it");
            copyright2.setFont(new Font("Courier", Font.PLAIN, 10));
            add(copyright2);
            add(Box.createRigidArea(new Dimension(0, 30)));
            JButton closeButton = new JButton("Close");
            add(closeButton);
            closeButton.addActionListener(this);
            setSize(280, 320);
            setResizable(false);
            setLocationRelativeTo(null);
            setVisible(true);
        }

        public void actionPerformed(ActionEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    dispose();
                }
            });
        }
    }

    private static final class PreferencesFrame extends JFrame implements ActionListener {

        private static final int MIN_SEARCH_DEPTH = 1;
        private static final int MAX_SEARCH_DEPTH = 10;
        private static final int SEARCH_DEPTH_STEP = 1;

        private static final int MIN_GAME_DURATION = 5;
        private static final int MAX_GAME_DURATION = 60;
        private static final int GAME_DURATION_STEP = 5;

        private final ReversiUI ui;
        private final JSpinner searchDepthSpinner;
        private final JComboBox<Player> humanPlayerColor;
        private final JSpinner gameDurationSpinner;

        PreferencesFrame(final ReversiUI ui) {
            super("Preferences");
            this.ui = ui;

            JPanel p = new JPanel(new GridLayout(3, 2, 10, 10));
            p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

            JLabel humanPlayerColorLabel = new JLabel("Human player color");
            p.add(humanPlayerColorLabel);
            Player[] colors = { Player.BLACK, Player.WHITE };
            humanPlayerColor = new JComboBox<Player>(colors);
            humanPlayerColor.setSelectedItem(ui.humanPlayer);
            p.add(humanPlayerColor);

            JLabel gameDurationLabel = new JLabel("Game duration in minutes");
            p.add(gameDurationLabel);
            SpinnerNumberModel gameDurationModel = new SpinnerNumberModel(ui.gameDuration, MIN_GAME_DURATION,
                    MAX_GAME_DURATION, GAME_DURATION_STEP);
            gameDurationSpinner = new JSpinner(gameDurationModel);
            ((JSpinner.DefaultEditor) gameDurationSpinner.getEditor()).getTextField().setEditable(false);
            p.add(gameDurationSpinner);

            JLabel searchDepthLabel = new JLabel("AI search depth");
            p.add(searchDepthLabel);
            SpinnerNumberModel searchDepthModel = new SpinnerNumberModel(ui.searchDepth, MIN_SEARCH_DEPTH,
                    MAX_SEARCH_DEPTH, SEARCH_DEPTH_STEP);
            searchDepthSpinner = new JSpinner(searchDepthModel);
            ((JSpinner.DefaultEditor) searchDepthSpinner.getEditor()).getTextField().setEditable(false);
            p.add(searchDepthSpinner);

            JButton discardButton = new JButton("Discard");
            discardButton.addActionListener(this);

            JButton applyButton = new JButton("Apply");
            applyButton.addActionListener(this);

            //Lay out the buttons from left to right.
            JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
            buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
            buttonPane.add(Box.createHorizontalGlue());
            buttonPane.add(discardButton);
            buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
            buttonPane.add(applyButton);

            Container contentPane = getContentPane();
            contentPane.add(p, BorderLayout.CENTER);
            contentPane.add(buttonPane, BorderLayout.PAGE_END);

            setResizable(false);
            setLocationRelativeTo(null);
            pack();
            setVisible(true);
        }

        public void actionPerformed(final ActionEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (e.getActionCommand().equals("Discard")) {
                        dispose();
                    } else if (e.getActionCommand().equals("Apply")) {
                        ui.searchDepth = (Integer) searchDepthSpinner.getValue();
                        ui.humanPlayer = (Player) humanPlayerColor.getSelectedItem();
                        ui.gameDuration = (Integer) gameDurationSpinner.getValue();
                        dispose();
                    } else {
                        throw new RuntimeException("Unknown action command.");
                    }
                }
            });
        }

    }

    private enum State {

        NO_GAME("No game present"), INITIALIZING("Initializing") {
            @Override
            public void reach(final ReversiUI ui) {
                super.reach(ui);
                ui.deactivateMenuItem(ui.jmiNewGame);
            }

            @Override
            public void leave(final ReversiUI ui) {
                super.leave(ui);
                ui.activateMenuItem(ui.jmiNewGame);
                ui.activateMenuItem(ui.jmiTranscript);
                ui.setMessageLabel("Begin to play activating the Play menu.");
            }
        },
        GAME_PAUSED("Game paused") {
            @Override
            public void reach(final ReversiUI ui) {
                super.reach(ui);
                ui.activateMenuItem(ui.jmiPlay);
            }
        },
        PLAYER_MOVING("Moving") {
            @Override
            public String displayName(final ReversiUI ui) {
                return ui.game.player() + "'s turn.";
            }

            @Override
            public void reach(final ReversiUI ui) {
                super.reach(ui);
                ui.deactivateMenuItem(ui.jmiPlay);
                ui.activateMenuItem(ui.jmiPause);
            }

            @Override
            public void leave(final ReversiUI ui) {
                super.leave(ui);
                ui.deactivateMenuItem(ui.jmiPause);
                ui.activateMenuItem(ui.jmiPlay);
                int value = IagoStrategy.IAGO_EVAL_FUNCTION.eval(ui.game.position());
                ui.setMessageLabel("AI one ply board evaluation is: " + value + ".");
            }
        },
        GAME_OVER("Game over") {
            @Override
            public void reach(final ReversiUI ui) {
                super.reach(ui);
                ui.setMessageLabel(ui.gameOverMessage());
            }
        };

        private boolean verbose = false;

        private String displayName;

        private State(final String displayName) {
            this.displayName = displayName;
        }

        public String displayName(final ReversiUI ui) {
            return this.displayName;
        }

        public void leave(final ReversiUI ui) {
            if (verbose)
                System.out.println("Leaving state " + this + "....");
        }

        public void reach(final ReversiUI ui) {
            if (verbose)
                System.out.println("Reaching state " + this + "....");
        }

        public static void transition(final State from, final State to) {
        }
    }

    private static final class TextAreaOutputStream extends OutputStream {
        private final JTextArea textArea;

        public TextAreaOutputStream(final JTextArea textArea) {
            this.textArea = textArea;
        }

        @Override
        public void flush() {
        }

        @Override
        public void close() {
        }

        @Override
        public void write(final int b) throws IOException {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    textArea.append(Character.toString((char) b));
                    textArea.setCaretPosition(textArea.getText().length());
                }
            });
        }
    }

    private static class ThreadEvent {
        private final Object lock = new Object();

        public void signal() {
            synchronized (lock) {
                lock.notify();
            }
        }

        public void await() throws InterruptedException {
            synchronized (lock) {
                lock.wait();
            }
        }
    }

    private final class HumanStrategyUI implements Strategy {
        public HumanStrategyUI() {
        }

        public final Move move(final GameSnapshot gameSnapshot) {
            activateCommand();
            try {
                resultsReady.await();
            } catch (InterruptedException ie) {
                throw new RuntimeException(ie);
            }
            return move;
        }
    }

    private final class GameCreator extends SwingWorker<Void, Void> {
        @Override
        public Void doInBackground() {
            setState(State.INITIALIZING);
            deactivateCommand();
            clearConsole();
            newGame();
            setState(State.GAME_PAUSED);
            return null;
        }

        @Override
        protected void done() {
            drawGame(game);
        }
    }

    private final class MoveFinder extends SwingWorker<Void, Void> {
        @Override
        public Void doInBackground() {
            setState(State.PLAYER_MOVING);
            Move move = game.move();
            drawGame(game);
            setState(State.GAME_PAUSED);
            return null;
        }

        @Override
        protected void done() {
        }
    }

    private final class GamePlayer extends SwingWorker<Void, Void> {
        @Override
        public Void doInBackground() {
            while (!pauseRequested && game.areThereAvailableMoves()) {
                MoveFinder mf = new MoveFinder();
                mf.execute();
                try {
                    mf.get();
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                } catch (ExecutionException ee) {
                    throw new RuntimeException(ee);
                }
            }
            if (!game.areThereAvailableMoves()) {
                setState(State.GAME_OVER);
            }
            return null;
        }
    }

    private boolean pauseRequested;
    private State state;

    private static final int DEFAULT_GAME_DURATION_IN_MINUTES = 30;

    private Map<Square, SquarePanel> squares = new EnumMap<Square, SquarePanel>(Square.class);
    private JFrame mainFrame;
    private JLayeredPane boardPane;
    private JPanel gridPanel;
    private JPanel dotsPanel;
    private JPanel labelsPanel;
    private JPanel labelsCornerPanel;
    private JPanel rowLabelsPanel;
    private JPanel colLabelsPanel;
    private JPanel commandPanel;
    private JPanel consolePanel;
    private JPanel statePanel;
    private JPanel messagePanel;

    private JLabel stateLabel;
    private JLabel messageLabel;

    private JTextArea textArea;

    private JMenuItem jmiPlay;
    private JMenuItem jmiNewGame;
    private JMenuItem jmiPause;
    private JMenuItem jmiTranscript;

    /** The command text field. */
    private JTextField commandTextField;

    private Game game;
    private Move move;
    private int searchDepth;
    private int gameDuration;
    private Player humanPlayer;

    private PrintStream consolePrintStream;
    private ThreadEvent resultsReady;

    public ReversiUI() {

        pauseRequested = false;
        state = State.NO_GAME;
        game = null;
        move = null;
        gameDuration = DEFAULT_GAME_DURATION_IN_MINUTES;
        searchDepth = IagoStrategy.DEFAULT_DEPTH;
        humanPlayer = Player.BLACK;
        resultsReady = new ThreadEvent();

        mainFrame = new JFrame("Reversi");
        mainFrame.getContentPane().setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.HORIZONTAL;
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel boardPanel = new JPanel();
        boardPanel.setBackground(Constants.BACKGROUND_COLOR);
        TitledBorder boardPanelTitle = BorderFactory.createTitledBorder("Board");
        boardPanel.setBorder(boardPanelTitle);

        final int gsd = 8 * Constants.SQUARE_SIZE;
        final int lsd = gsd + (Constants.LABELS_HEIGHT + (2 * Constants.LABELS_GAP) + Constants.SQUARES_GAP);
        final Dimension gridSize = new Dimension(gsd, gsd);
        final Dimension labelsSize = new Dimension(lsd, lsd);
        final int gs = Constants.LABELS_HEIGHT + Constants.LABELS_GAP + Constants.SQUARES_GAP;

        // Use a Layered Pane for this application.
        boardPane = new JLayeredPane();
        c.gridx = 0;
        c.gridy = 0;
        boardPanel.add(boardPane);
        mainFrame.getContentPane().add(boardPanel, c);
        boardPane.setPreferredSize(labelsSize);

        // Add the labels panel to the Layered Pane.
        labelsPanel = new JPanel();
        boardPane.add(labelsPanel, new Integer(10));
        labelsPanel.setLayout(null);
        labelsPanel.setPreferredSize(labelsSize);
        labelsPanel.setBounds(0, 0, labelsSize.width, labelsSize.height);
        labelsPanel.setBackground(Constants.BACKGROUND_COLOR);
        labelsPanel.setOpaque(true);

        // Add the North-West corner to the Labels Panel.
        labelsCornerPanel = new JPanel();
        labelsPanel.add(labelsCornerPanel);
        labelsCornerPanel.setLayout(new GridLayout(1, 1, 0, 0));
        labelsCornerPanel.setBackground(Color.white);
        labelsCornerPanel.setBounds(Constants.LABELS_GAP, Constants.LABELS_GAP, Constants.LABELS_HEIGHT,
                Constants.LABELS_HEIGHT);
        JPanel jplc = new JPanel(new BorderLayout());
        labelsCornerPanel.add(jplc);
        jplc.setBackground(Constants.BASE_COLOR);

        // Add the Row-List to the Label Panel.
        rowLabelsPanel = new JPanel();
        labelsPanel.add(rowLabelsPanel);
        rowLabelsPanel.setLayout(new GridLayout(8, 1, 0, Constants.SQUARES_GAP));
        rowLabelsPanel.setBounds(Constants.LABELS_GAP, gs, Constants.LABELS_HEIGHT, gsd);
        rowLabelsPanel.setBackground(Constants.BACKGROUND_COLOR);
        for (Row row : Row.values()) {
            JPanel jp = new JPanel(new BorderLayout());
            jp.setBackground(Constants.BASE_COLOR);
            rowLabelsPanel.add(jp);
            JLabel jl = new JLabel(row.toString().substring(1, 2), JLabel.CENTER);
            jl.setFont(Constants.LABELS_FONT);
            jl.setForeground(Constants.LABEL_TEXT_COLOR);
            jp.add(jl);
        }

        // Add the Column-List to the Label Panel.
        colLabelsPanel = new JPanel();
        labelsPanel.add(colLabelsPanel);
        colLabelsPanel.setLayout(new GridLayout(1, 8, Constants.SQUARES_GAP, 0));
        colLabelsPanel.setBounds(gs, Constants.LABELS_GAP, gsd, Constants.LABELS_HEIGHT);
        colLabelsPanel.setBackground(Constants.BACKGROUND_COLOR);
        for (Column column : Column.values()) {
            JPanel jp = new JPanel(new BorderLayout());
            jp.setBackground(Constants.BASE_COLOR);
            colLabelsPanel.add(jp);
            JLabel jl = new JLabel(column.toString(), JLabel.CENTER);
            jl.setFont(Constants.LABELS_FONT);
            jl.setForeground(Constants.LABEL_TEXT_COLOR);
            jp.add(jl);
        }

        // Add the grid panel to the Layered Pane. 
        gridPanel = new JPanel();
        boardPane.add(gridPanel, new Integer(20));
        gridPanel.setLayout(new GridLayout(8, 8, Constants.SQUARES_GAP, Constants.SQUARES_GAP));
        gridPanel.setPreferredSize(gridSize);
        gridPanel.setBounds(gs, gs, gridSize.width, gridSize.height);
        gridPanel.setBackground(Constants.BACKGROUND_COLOR);

        // Ad the dots panel to the Layered Pane.
        dotsPanel = new JPanel();
        boardPane.add(dotsPanel, new Integer(30));
        dotsPanel.setLayout(null);
        dotsPanel.setPreferredSize(gridSize);
        dotsPanel.setBounds(gs, gs, gridSize.width, gridSize.height);
        dotsPanel.setBackground(Constants.BACKGROUND_COLOR);
        dotsPanel.setOpaque(false);
        setDot(2, 2);
        setDot(6, 2);
        setDot(6, 6);
        setDot(2, 6);

        for (Square square : Square.values()) {
            SquarePanel squarePanel = new SquarePanel(square);
            gridPanel.add(squarePanel);
            squares.put(square, squarePanel);
        }

        JPanel bottomPanel = new JPanel(new GridBagLayout());
        bottomPanel.setBackground(Constants.BACKGROUND_COLOR);
        GridBagConstraints cBottomPanel = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 1;
        mainFrame.getContentPane().add(bottomPanel, c);

        // Add the state JPanel
        TitledBorder statePanelTitle = BorderFactory.createTitledBorder("State");
        statePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 8));
        statePanel.setBorder(statePanelTitle);
        statePanel.setBackground(Constants.BACKGROUND_COLOR);
        cBottomPanel.fill = GridBagConstraints.VERTICAL;
        cBottomPanel.gridx = 0;
        cBottomPanel.gridy = 0;
        cBottomPanel.anchor = GridBagConstraints.PAGE_START;
        bottomPanel.add(statePanel, cBottomPanel);
        stateLabel = new JLabel(state.displayName(this));
        Dimension d = stateLabel.getPreferredSize();
        stateLabel.setPreferredSize(new Dimension(d.width + 20, d.height));
        stateLabel.setFont(new Font("Monospaced", Font.PLAIN, 12));
        statePanel.add(stateLabel);

        // Add the command data entry JTextField
        TitledBorder commandPanelTitle = BorderFactory.createTitledBorder("Move");
        commandPanel = new JPanel();
        commandPanel.setBorder(commandPanelTitle);
        commandPanel.setBackground(Constants.BACKGROUND_COLOR);
        cBottomPanel.fill = GridBagConstraints.VERTICAL;
        cBottomPanel.gridx = 1;
        cBottomPanel.gridy = 0;
        bottomPanel.add(commandPanel, cBottomPanel);
        commandTextField = new JTextField(8);
        commandTextField.setFont(new Font("Monospaced", Font.PLAIN, 12));
        commandTextField.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                move(commandTextField.getText());
            }
        });
        deactivateCommand();
        commandPanel.add(commandTextField);

        // Add the message JPanel
        TitledBorder messagePanelTitle = BorderFactory.createTitledBorder("Message");
        messagePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 8));
        messagePanel.setBorder(messagePanelTitle);
        messagePanel.setBackground(Constants.BACKGROUND_COLOR);
        cBottomPanel.fill = GridBagConstraints.BOTH;
        cBottomPanel.gridx = 2;
        cBottomPanel.gridy = 0;
        cBottomPanel.weightx = 1;
        bottomPanel.add(messagePanel, cBottomPanel);
        messageLabel = new JLabel("Welcome to the Reversi board, start a new game.");
        messageLabel.setFont(new Font("Monospaced", Font.PLAIN, 12));
        messagePanel.add(messageLabel);

        TitledBorder consolePanelTitle = BorderFactory.createTitledBorder("Console");
        consolePanel = new JPanel(new GridBagLayout());
        consolePanel.setBorder(consolePanelTitle);
        consolePanel.setBackground(Constants.BACKGROUND_COLOR);
        c.gridx = 1;
        c.gridy = 0;
        c.gridheight = 2;
        c.fill = GridBagConstraints.BOTH;
        mainFrame.getContentPane().add(consolePanel, c);
        textArea = new JTextArea(40, 60);
        textArea.setEditable(false);
        textArea.setFont(new Font("Monospaced", Font.PLAIN, 10));
        textArea.setBackground(Color.BLACK);
        textArea.setForeground(Color.LIGHT_GRAY);
        JScrollPane scrollPane = new JScrollPane(textArea);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        //Add Components to this panel.
        GridBagConstraints c2 = new GridBagConstraints();
        c2.fill = GridBagConstraints.BOTH;
        c2.weightx = 1.0;
        c2.weighty = 1.0;
        consolePanel.add(scrollPane, c2);

        consolePrintStream = new PrintStream(new TextAreaOutputStream(textArea));

        /* Create the menu bar. */
        JMenuBar jmb = new JMenuBar();

        /* Add the menu bar tothe main frame. */
        mainFrame.setJMenuBar(jmb);

        /* Create the Game menu, with the Exit commnad. */
        JMenu jmGame = new JMenu("Game");
        jmb.add(jmGame);

        /* Add the New game commnad to the Game menu. */
        jmiNewGame = new JMenuItem("New game");
        jmGame.add(jmiNewGame);

        /* Add the action listener to the New game command. */
        jmiNewGame.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                new GameCreator().execute();
            }
        });

        /* Add the Play commnad to the Game menu. */
        jmiPlay = new JMenuItem("Play");
        jmiPlay.setEnabled(false);
        jmGame.add(jmiPlay);

        /* Add the action listener to the Play command. */
        jmiPlay.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                pauseRequested = false;
                new GamePlayer().execute();
            }
        });

        /* Add the Pause commnad to the Game menu. */
        jmiPause = new JMenuItem("Pause");
        jmiPause.setToolTipText("Pause will be entered at the end of this turn.");
        jmiPause.setEnabled(false);
        jmGame.add(jmiPause);

        /* Add the action listener to the Pause command. */
        jmiPause.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                pauseRequested = true;
            }
        });

        /* Add the Exit commnad to the Game menu. */
        JMenuItem jmiExit = new JMenuItem("Exit");
        jmGame.add(new JSeparator());
        jmGame.add(jmiExit);

        /* Add the action listener to the Exit command. */
        jmiExit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                System.exit(0);
            }
        });

        /* Create the Settings menu, with the Preferences commnad. */
        JMenu jmSettings = new JMenu("Settings");
        jmb.add(jmSettings);

        /* Add the Prefernces commnad to the Settings menu. */
        JMenuItem jmiPreferences = new JMenuItem("Preferences");
        jmSettings.add(jmiPreferences);

        /* Add the action listener to the Preferences command. */
        jmiPreferences.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                new PreferencesFrame(ReversiUI.this);
            }
        });

        /* Create the Data menu, with the Transcript commnad. */
        JMenu jmData = new JMenu("Data");
        jmb.add(jmData);

        /* Add the Transcript commnad to the Data menu. */
        jmiTranscript = new JMenuItem("Transcript");
        jmiTranscript.setEnabled(false);
        jmData.add(jmiTranscript);

        /* Add the action listener to the Transcript command. */
        jmiTranscript.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                new TranscriptFrame(ReversiUI.this);
            }
        });

        /* Create the Help menu, with the About commnad. */
        JMenu jmHelp = new JMenu("Help");
        jmb.add(jmHelp);

        /* Add the About commnad to the Help menu. */
        JMenuItem jmiAbout = new JMenuItem("About");
        jmHelp.add(jmiAbout);

        /* Add the action listener to the About command. */
        jmiAbout.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                new AboutFrame();
            }
        });

        mainFrame.pack();
        mainFrame.setResizable(false);
        mainFrame.setLocationRelativeTo(null);
        mainFrame.setVisible(true);

    }

    Game game() {
        return this.game;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ReversiUI();
            }
        });
    }

    private void newGame() {

        this.pauseRequested = false;
        this.move = null;
        this.resultsReady = new ThreadEvent();

        Actor black, white;

        final Actor ai = new Actor.Builder().withName("Iago").withStrategy(new IagoStrategy(searchDepth)).build();

        final Actor human = new Actor.Builder().withName("Human").withStrategy(new HumanStrategyUI()).build();

        if (humanPlayer == Player.BLACK) {
            black = human;
            white = ai;
        } else {
            black = ai;
            white = human;
        }
        ;

        game = Game.initialGame(black, white, Period.minutes(gameDuration).toStandardDuration(),
                consolePrintStream);
    }

    private void setSquareState(final Square square, final SquareState state) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                SquarePanel sp = squares.get(square);
                sp.setSquareState(state);
            }
        });
    }

    private void setMessageLabel(final String message) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                messageLabel.setText(message);
            }
        });
    }

    private void setState(final State newState) {
        final State to = newState;
        final State from = state;
        from.leave(this);
        to.reach(this);
        State.transition(from, to);
        ReversiUI.this.state = newState;
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                stateLabel.setText(ReversiUI.this.state.displayName(ReversiUI.this));
            }
        });
    }

    private State getState() {
        return this.state;
    }

    private void drawGame(final Game game) {
        invokeInDispatchThreadIfNeeded(new Runnable() {
            public void run() {
                drawBoard(game.board());
                consolePrintStream.print(game.lastGameSnapshot().printGameSnapshot());
            }
        }, WAIT);
    }

    private void drawBoard(final Board board) {
        for (Square square : Square.values()) {
            setSquareState(square, board.get(square));
        }
    }

    private void setDot(int x, int y) {
        JLabel dot = new JLabel(Constants.GRID_DOT_ICON);
        dotsPanel.add(dot);
        int xDotCenter = (Constants.SQUARE_SIZE * x) - Constants.DOT_SIZE / 2;
        int yDotCenter = (Constants.SQUARE_SIZE * y) - Constants.DOT_SIZE / 2;
        dot.setBounds(xDotCenter, yDotCenter, Constants.DOT_SIZE, Constants.DOT_SIZE);
    }

    private void clearConsole() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                textArea.setText(null);
            }
        });
    }

    private void activateMenuItem(final JMenuItem item) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                item.setEnabled(true);
            }
        });
    }

    private void deactivateMenuItem(final JMenuItem item) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                item.setEnabled(false);
            }
        });
    }

    private void activateCommand() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                commandTextField.setBackground(Color.WHITE);
                commandTextField.setForeground(Color.BLACK);
                commandTextField.setEditable(true);
                commandTextField.setEnabled(true);
                commandTextField.setText(null);
                commandTextField.requestFocusInWindow();
            }
        });
    }

    private void deactivateCommand() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                commandTextField.setEnabled(false);
                commandTextField.setEditable(false);
                commandTextField.setBackground(Color.BLACK);
                commandTextField.setForeground(Color.LIGHT_GRAY);
            }
        });
    }

    private String gameOverMessage() {
        final int discCount = game.countDiscDifference();
        if (discCount == 0)
            return "Game is a draw.";
        if (discCount > 0) {
            return "Black wins by a disc difference of " + discCount + " pieces.";
        } else {
            return "White wins by a disc difference of " + (-discCount) + " pieces.";
        }
    }

    private void move(final String command) {
        Move newMove = null;
        if (command != null) {
            if (command.equalsIgnoreCase("resign")) {
                newMove = Move.valueOf(Move.Action.RESIGN);
            } else if (command.equalsIgnoreCase("pass")) {
                newMove = Move.valueOf(Move.Action.PASS);
            } else {
                try {
                    newMove = Move.valueOf(Square.getInstance(command));
                } catch (IllegalArgumentException iae) {
                    consolePrintStream.println(command + " is not a move.");
                }
            }
        }
        if (newMove != null && game.validateMove(newMove)) {
            this.move = newMove;
            deactivateCommand();
            resultsReady.signal();
        } else {
            consolePrintStream.println(command + " is not a legal move.");
        }
    }

    private final static boolean WAIT = true;
    private final static boolean NO_WAIT = false;

    public static void invokeInDispatchThreadIfNeeded(final Runnable runnable, final boolean wait) {
        if (SwingUtilities.isEventDispatchThread()) {
            runnable.run();
        } else {
            if (wait) {
                try {
                    SwingUtilities.invokeAndWait(runnable);
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                } catch (InvocationTargetException ite) {
                    throw new RuntimeException(ite);
                }
            } else {
                SwingUtilities.invokeLater(runnable);
            }
        }
    }

}