Package driftingdroids.ui

Source Code of driftingdroids.ui.SwingGUI

/*  DriftingDroids - yet another Ricochet Robots solver program.
    Copyright (C) 2011  Michael Henke

    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 of the License, 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, see <http://www.gnu.org/licenses/>.
*/

package driftingdroids.ui;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.LinearGradientPaint;
import java.awt.MultipleGradientPaint;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CancellationException;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;

import net.java.dev.designgridlayout.DesignGridLayout;
import net.java.dev.designgridlayout.RowGroup;
import driftingdroids.model.Board;
import driftingdroids.model.Move;
import driftingdroids.model.Solution;
import driftingdroids.model.SolverBFS;



public class SwingGUI implements ActionListener {
    private static final Color COL_BACKGROUND = new Color(190, 190, 190);
    private static final Color COL_CELL1 = new Color(210, 210, 210);
    private static final Color COL_CELL2 = new Color(245, 245, 245);
    private static final Color COL_WALL = new Color(85, 85, 85);
    private static final Color[] COL_ROBOT = {
        new Color(245, 99, 99), //Color.RED,
        new Color(99, 245, 99), //Color.GREEN,
        new Color(99, 99, 245), //Color.BLUE,
        new Color(245, 245, 99),//Color.YELLOW,
        Color.WHITE
    };
   
    private static final String AC_BOARD_QUADRANTS= "quadrants";
    private static final String AC_PLACE_ROBOT    = "placerobot";
    private static final String AC_PLACE_GOAL     = "placegoal";
    private static final String AC_GAME_ID        = "gameid";
    private static final String AC_SELECT_SOLUTION= "selectsolution";
    private static final String AC_SHOW_COLOR_NAMES = "showcolornames";
   
    private Board board = null;
    private final BoardCell[] boardCells;
    private int[] currentPosition;
   
    private SolverTask solverTask = null;   //only set while SolverTask is working
    private List<Solution> computedSolutionList = null;       //result of SolverTask -> solver.execute()
    private int computedSolutionIndex = 0;
    private final List<Move> moves;
    private int hintCounter = 0;
    private int placeRobot = -1;    //default: false
    private boolean placeGoal = false;
   
    private final JComboBox[] jcomboQuadrants = { new JComboBox(), new JComboBox(), new JComboBox(), new JComboBox() };
    private final JComboBox jcomboRobots = new JComboBox();
    private final JComboBox jcomboOptSolutionMode = new JComboBox();
    private final JCheckBox jcheckOptAllowRebounds = new JCheckBox();
    private final JCheckBox jcheckOptShowColorNames = new JCheckBox();
    private final JTabbedPane jtabPreparePlay = new JTabbedPane();
    private final JComboBox jcomboPlaceRobot = new JComboBox();
    private final JButton jbutPlaceGoal = new JButton("Place goal");
    private final JComboBox jcomboGameIDs = new JComboBox();
    private final JComboBox jcomboSelectSolution = new JComboBox();
    private final JButton jbutSolutionHint = new JButton("Hint");
    private final JButton jbutNextMove = new JButton(">");
    private final JButton jbutAllMoves = new JButton(">>|");
    private final JButton jbutPrevMove = new JButton("<");
    private final JButton jbutNoMoves = new JButton("|<<");
    private final JTextPane jtextSolution = new JTextPane();
   
    public SwingGUI(final String windowTitle) throws InterruptedException, InvocationTargetException {
        this.board = Board.createBoardGameID("0765+41+2E21BD0F+1C");   //1A 4B 3B 2B
        this.moves = new ArrayList<Move>();
       
        this.boardCells = new BoardCell[this.board.size];
        for (int i = 0;  i < this.board.size;  ++i) {
            boardCells[i] = new BoardCell(i);
        }
       
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                createAndShowGUI(windowTitle);
            }
        });
    }
   
    private void makeBoardQuadrants() {
        this.board = Board.createBoardQuadrants(
                this.jcomboQuadrants[0].getSelectedIndex(),
                this.jcomboQuadrants[1].getSelectedIndex(),
                this.jcomboQuadrants[2].getSelectedIndex(),
                this.jcomboQuadrants[3].getSelectedIndex(),
                this.jcomboRobots.getSelectedIndex() + 1 );
        this.refreshBoard();
    }
   
    private void makeRandomBoardQuadrants() {
        this.board = Board.createBoardRandom(this.jcomboRobots.getSelectedIndex() + 1);
        this.refreshBoard();
    }
   
    private void updateBoardRandomRobots() {
        this.board.setRobotsRandom();
        this.updateBoardGetRobots();
    }
   
    private void updateBoardRandomGoal() {
        this.board.setRobots(this.currentPosition);
        this.board.setGoalRandom();
        this.updateBoardGetRobots();
    }
   
    private void updateBoardGetRobots() {
        this.currentPosition = this.board.getRobotPositions().clone();
        this.runSolverTask();
    }
   
    private void removeSolution() {
        if (null != this.solverTask) {
            this.solverTask.cancel(true);
        }
        this.computedSolutionList = null;
        this.computedSolutionIndex = 0;
        this.moves.clear();
        this.jcomboSelectSolution.setSelectedIndex(0);
        while (this.jcomboSelectSolution.getItemCount() > 1) {
            this.jcomboSelectSolution.removeItemAt(1);
        }
        this.refreshButtons();
    }
   
    private void appendSolutionText(String str, Color bgCol) {
        StyledDocument doc = this.jtextSolution.getStyledDocument();
        Style style = null;
        if (null != bgCol) {
            style = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
            StyleConstants.setForeground(style, Color.BLACK);
            StyleConstants.setBackground(style, bgCol);
        }
        try {
            doc.insertString(doc.getLength(), str, style);
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
    }
   
    private void setSolution(final SolverBFS solver) {
        this.computedSolutionList = solver.get();
        this.computedSolutionIndex = 0;
        for (int i = 0;  i < this.computedSolutionList.size();  ++i) {
            this.jcomboSelectSolution.addItem((i+1) + ")  " + this.computedSolutionList.get(i).toString());
        }
        this.hintCounter = 0;
        this.jtextSolution.setText(null);
        this.appendSolutionText("options:\n" + solver.getOptionsAsString2() + "\n", null);
        if (this.computedSolutionList.get(this.computedSolutionIndex).size() > 0) {
            final long seconds = (solver.getSolutionMilliSeconds() + 999) / 1000;
            final int solutions = this.computedSolutionList.size();
            this.appendSolutionText("found " + solutions + " solution" + (1==solutions ? "" : "s") +
                    " in " + seconds + " second" + (1==seconds ? "" : "s") + ".\n\n", null);
        } else {
            this.appendSolutionText("no solution found!\n", null);
        }
        System.out.println(this.computedSolutionList.get(this.computedSolutionIndex).toMovelistString() + "  (" + solver.toString() + ")");
        this.refreshButtons();
    }
   
    private void selectSolution(final int solutionIndex, final String solutionString) {
        if ( (null != this.computedSolutionList) &&
                (solutionIndex -1 != this.computedSolutionIndex) &&
                (solutionIndex > 0) &&
                (solutionIndex <= this.computedSolutionList.size()) ) {
            //reset moves
            final int oldMovesSize = this.moves.size();
            while (this.jbutPrevMove.isEnabled()) {
                this.showPrevMove(false);
            }
            //set new solution
            this.computedSolutionIndex = solutionIndex - 1;
            this.appendSolutionText("\nselected solution " + solutionString + "\n", null);
            this.computedSolutionList.get(this.computedSolutionIndex).resetMoves();
            this.hintCounter = 3;
            //show moves
            for (int i = 0;  i < oldMovesSize;  ++i) {
                this.showNextMove(true);
            }
        }
    }
   
    private void showHint() {
        final int moves = this.computedSolutionList.get(this.computedSolutionIndex).size();
        final String movesStr = Integer.toString(moves) + " move" + (1==moves ? "" : "s");
        final Set<Integer> robotsMoved = this.computedSolutionList.get(this.computedSolutionIndex).getRobotsMoved();
        final String robotsMovedStr = Integer.toString(robotsMoved.size()) + " robot" + (1==robotsMoved.size() ? "" : "s");
        if (0 == this.hintCounter) {
            //first hint: number of moves
            this.appendSolutionText("hint: solution contains " + movesStr + ".\n", null);
        } else if (1 == this.hintCounter) {
            //second hint: number of moves + number of robots moved
            this.appendSolutionText("hint: " + movesStr + ", " + robotsMovedStr + " moved.\n", null);
        } else {
            //last hint: number of moves + number of robots moved + which robots moved
            this.appendSolutionText("last hint: " + movesStr + ", " + robotsMovedStr + ": ", null);
            for (Integer robot : robotsMoved) {
                this.appendSolutionText(Board.ROBOT_COLOR_NAMES_SHORT[robot.intValue()], COL_ROBOT[robot.intValue()]);
            }
            this.appendSolutionText(".\n", null);
        }
        ++this.hintCounter;
    }
   
    private void runSolverTask() {
        this.refreshBoard();
        this.removeSolution();
        this.solverTask = new SolverTask();
        this.solverTask.execute();
    }
   
    private class SolverTask extends SwingWorker<SolverBFS, Object> {
        @Override
        protected SolverBFS doInBackground() throws Exception {
            final SolverBFS solver = new SolverBFS(board);
            solver.setOptionSolutionMode((SolverBFS.SOLUTION_MODE)jcomboOptSolutionMode.getSelectedItem());
            solver.setOptionAllowRebounds(jcheckOptAllowRebounds.isSelected());
            jtextSolution.setText(null);
            appendSolutionText("options:\n" + solver.getOptionsAsString2() + "\n", null);
            appendSolutionText("computing solutions...\n\n", null);
            solver.execute();
            return solver;
        }
        @Override
        protected void done() {
            String errorMsg = "";
            solverTask = null;
            try {
                setSolution(this.get());
            }
            catch (CancellationException e) {
                System.err.println(e.toString());
            }
            catch (Exception e) {
                errorMsg = e.toString();
                e.printStackTrace();
            }
            if ( ! errorMsg.isEmpty()) {
                appendSolutionText("error:\n" + errorMsg + "\n", null);
            }
        }
    }
   
    private class ShowHideAction implements ItemListener     {
        final private RowGroup group;
        public ShowHideAction(RowGroup group) {
            this.group = group;
        }
        @Override
        public void itemStateChanged(ItemEvent event) {
            if (event.getStateChange() == ItemEvent.SELECTED) {
                this.group.show();
            } else {
                this.group.hide();
            }
        }
    }
   
    private class TabListener implements ChangeListener {
        private Board oldBoard = null;
        @Override
        public void stateChanged(ChangeEvent e) {
            refreshBoard(); //repaint the entire board
            if (isModePlay()) {
                if (this.oldBoard != board) {
                    updateBoardRandomGoal();    //set random goal and start solver thread
                } else {
                    updateBoardGetRobots();     //start solver thread
                }
            } else {
                removeSolution();   //stop solver thread
                this.oldBoard = board;
            }
        }
    }
   
   
    private void addKeyBindingTooltip(AbstractButton button, int keyCode, String tooltip, Action action) {
        final KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
        final String actionMapKey = KeyEvent.getKeyText(keyCode) + "_action_key";
        button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, actionMapKey);
        button.getActionMap().put(actionMapKey, action);
        button.addActionListener(action);
       
        String acceleratorDelimiter = UIManager.getString("MenuItem.acceleratorDelimiter");
        if (null == acceleratorDelimiter) { acceleratorDelimiter = "-"; }
        String acceleratorText = "";
        int modifiers = keyStroke.getModifiers();
        if (modifiers > 0) {
            acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
            acceleratorText += acceleratorDelimiter;
        }
        acceleratorText += KeyEvent.getKeyText(keyCode);
        button.setToolTipText("<html>" + tooltip + " &nbsp; <small><strong>" + acceleratorText + "</strong></small></html>");
    }
   
   
    @SuppressWarnings("serial")
    private void createAndShowGUI(String windowTitle) {
        final JFrame frame = new JFrame(windowTitle);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       
        final JButton jbutRandomLayout = new JButton("Random layout");
        this.addKeyBindingTooltip(jbutRandomLayout, KeyEvent.VK_L,
                "place the board tiles at random positions",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        makeRandomBoardQuadrants();
                        refreshJComboPlaceRobot();
                        refreshJcomboQuadrants();
                    }
                }
        );
       
        for (int i = 0;  i < 4;  ++i) {
            this.jcomboQuadrants[i].setModel(new DefaultComboBoxModel(Board.QUADRANT_NAMES));
            this.jcomboQuadrants[i].setEditable(false);
            this.jcomboQuadrants[i].setSelectedIndex(this.board.getQuadrantNum(i));
            this.jcomboQuadrants[i].setActionCommand(AC_BOARD_QUADRANTS);
            this.jcomboQuadrants[i].addActionListener(this);
            this.setJComboCenterAlignment(this.jcomboQuadrants[i]);
        }
       
        final String[] strRobots = { "1", "2", "3", "4", "5" };
        this.jcomboRobots.setModel(new DefaultComboBoxModel(strRobots));
        this.jcomboRobots.setEditable(false);
        this.jcomboRobots.setActionCommand(AC_BOARD_QUADRANTS);
        this.jcomboRobots.addActionListener(this);
        this.setJComboCenterAlignment(this.jcomboRobots);
        this.refreshJcomboRobots();
       
        final SolverBFS.SOLUTION_MODE[] solModes = SolverBFS.SOLUTION_MODE.values();
        this.jcomboOptSolutionMode.setModel(new DefaultComboBoxModel(solModes));
        this.jcomboOptSolutionMode.setSelectedItem(SolverBFS.SOLUTION_MODE.MINIMUM);
        this.setJComboCenterAlignment(this.jcomboOptSolutionMode);
       
        this.jcheckOptAllowRebounds.setText("allow rebound moves");
        this.jcheckOptAllowRebounds.setSelected(true);

        this.jcheckOptShowColorNames.setText("show color names (robots and goals)");
        this.jcheckOptShowColorNames.setSelected(true);
        this.jcheckOptShowColorNames.setActionCommand(AC_SHOW_COLOR_NAMES);
        this.jcheckOptShowColorNames.addActionListener(this);

        final JPanel preparePanel = new JPanel();
        final DesignGridLayout prepareLayout = new DesignGridLayout(preparePanel);
        prepareLayout.row().grid().add(new JSeparator());
        prepareLayout.row().grid().add(new JLabel("board tiles")).add(jbutRandomLayout);
        prepareLayout.row().grid().add(this.jcomboQuadrants[0]).add(this.jcomboQuadrants[1]);
        prepareLayout.row().grid().add(this.jcomboQuadrants[3]).add(this.jcomboQuadrants[2]);
        prepareLayout.row().grid().add(new JLabel(" "));
        prepareLayout.row().grid().add(new JLabel("number of robots")).add(this.jcomboRobots);
        prepareLayout.row().grid().add(new JLabel(" "));
        prepareLayout.row().grid().add(new JSeparator());
        prepareLayout.row().grid().add(new JLabel("solver options"));
        prepareLayout.row().grid().add(new JLabel(" "));
        prepareLayout.row().grid().add(new JLabel("prefer solution with")).add(this.jcomboOptSolutionMode);
        prepareLayout.row().grid().add(new JLabel("number of robots moved"));
        prepareLayout.row().grid().add(new JLabel(" "));
        prepareLayout.row().grid().add(this.jcheckOptAllowRebounds);
        prepareLayout.row().grid().add(new JLabel(" "));
        prepareLayout.row().grid().add(new JSeparator());
        prepareLayout.row().grid().add(new JLabel("GUI options"));
        prepareLayout.row().grid().add(new JLabel(" "));
        prepareLayout.row().grid().add(this.jcheckOptShowColorNames);
       
        final JButton jbutRandomRobots = new JButton("Random robots");
        this.addKeyBindingTooltip(jbutRandomRobots, KeyEvent.VK_R,
                "place the robots randomly on the board",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        placeRobot = -1;
                        refreshJComboPlaceRobot();
                        updateBoardRandomRobots();
                    }
                }
        );
       
        final JButton jbutRandomGoal = new JButton("Random goal");
        this.addKeyBindingTooltip(jbutRandomGoal, KeyEvent.VK_G,
                "pick a goal at random",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        placeGoal = false;
                        updateBoardRandomGoal();
                    }
                }
        );
       
        this.refreshJComboPlaceRobot();   //this.jcomboPlaceRobot
        this.jcomboPlaceRobot.setEditable(false);
        this.jcomboPlaceRobot.setActionCommand(AC_PLACE_ROBOT);
        this.jcomboPlaceRobot.addActionListener(this);
        this.jcomboPlaceRobot.setToolTipText("first select a robot here and then click on its new board position");
        this.setJComboCenterAlignment(this.jcomboPlaceRobot);
       
        this.jbutPlaceGoal.setActionCommand(AC_PLACE_GOAL);
        this.jbutPlaceGoal.addActionListener(this);
        this.jbutPlaceGoal.setToolTipText("first click here and then select a goal on the board");
       
        this.jcomboGameIDs.setModel(new DefaultComboBoxModel());
        this.jcomboGameIDs.setEditable(true);
        this.jcomboGameIDs.setActionCommand(AC_GAME_ID);
        this.jcomboGameIDs.addActionListener(this);
       
        this.jcomboSelectSolution.setModel(new DefaultComboBoxModel());
        this.jcomboSelectSolution.setPrototypeDisplayValue("99)  99/9/#####")//longest string possible here
        this.jcomboSelectSolution.addItem("Select solution");
        this.jcomboSelectSolution.setEditable(false);
        this.jcomboSelectSolution.setActionCommand(AC_SELECT_SOLUTION);
        this.jcomboSelectSolution.addActionListener(this);
        this.jcomboSelectSolution.setToolTipText("SPOILER WARNING. clicking this reveals hints about the solutions");
        this.setJComboCenterAlignment(this.jcomboSelectSolution);
       
        this.addKeyBindingTooltip(this.jbutSolutionHint, KeyEvent.VK_H,
                "click 3 times to get more detailed hints",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        showHint();
                    }
                }
        );
       
        this.addKeyBindingTooltip(this.jbutNextMove, KeyEvent.VK_N,
                "show next move",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        showNextMove(true);
                    }
                }
        );
       
        this.addKeyBindingTooltip(this.jbutAllMoves, KeyEvent.VK_M,
                "show all moves",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        while (jbutNextMove.isEnabled()) {
                            showNextMove(true);
                        }
                    }
                }
        );
       
        this.addKeyBindingTooltip(this.jbutPrevMove, KeyEvent.VK_B,
                "undo last move",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        showPrevMove(true);
                    }
                }
        );
       
        this.addKeyBindingTooltip(this.jbutNoMoves, KeyEvent.VK_V,
                "undo all moves",
                new AbstractAction() {
                    public void actionPerformed(ActionEvent e) {
                        while (jbutPrevMove.isEnabled()) {
                            showPrevMove(true);
                        }
                    }
                }
        );
       
        this.jtextSolution.setEditable(false);
        final JPanel panelSolutionText = new JPanel(new BorderLayout());
        panelSolutionText.add(this.jtextSolution, BorderLayout.CENTER);
        final JScrollPane scrollSolutionText = new JScrollPane(this.jtextSolution, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollSolutionText.setPreferredSize(new Dimension(10, 10)); //workaround for layout problem ?!?!
        scrollSolutionText.setMinimumSize(new Dimension(10, 10));   //workaround for layout problem ?!?!
       
        final RowGroup playSolutionGroup = new RowGroup();
        final JCheckBox groupBox = new JCheckBox("show computed solutions");
        groupBox.addItemListener(new ShowHideAction(playSolutionGroup));
       
        final JPanel playPanel = new JPanel();
        final DesignGridLayout playLayout = new DesignGridLayout(playPanel);
        playLayout.row().grid().add(new JSeparator());
        playLayout.row().grid().add(new JLabel("set starting position"));
        playLayout.row().grid().add(jbutRandomRobots).add(jbutRandomGoal);
        playLayout.row().grid().add(this.jcomboPlaceRobot).add(this.jbutPlaceGoal);
        playLayout.row().grid().add(new JLabel("game ID")).add(this.jcomboGameIDs, 3);
        playLayout.row().grid().add(new JLabel(" "));
        playLayout.row().grid().add(new JSeparator());
        playLayout.row().grid().add(groupBox);
        playLayout.row().group(playSolutionGroup).grid().add(this.jcomboSelectSolution).add(this.jbutSolutionHint);
        playLayout.row().group(playSolutionGroup).grid().add(this.jbutNoMoves).add(this.jbutPrevMove).add(this.jbutNextMove).add(this.jbutAllMoves);
        playLayout.row().group(playSolutionGroup).grid().add(scrollSolutionText);
        groupBox.setSelected(true); //false
        //playSolutionGroup.hide();
       
        this.jtabPreparePlay.addTab("Prepare board / Options", preparePanel);
        this.jtabPreparePlay.addTab("Play game", playPanel);
        this.jtabPreparePlay.setSelectedIndex(1);   //Play
        this.jtabPreparePlay.addChangeListener(new TabListener());
       
        final JPanel boardPanel = new JPanel(new GridLayout(this.board.height, this.board.width));
        for (int i = 0; i < this.board.size; ++i) {
            boardPanel.add(this.boardCells[i]);
        }
       
        frame.getContentPane().add(boardPanel, BorderLayout.CENTER);
        frame.getContentPane().add(this.jtabPreparePlay, BorderLayout.EAST);
        frame.pack();
        frame.setVisible(true);
       
        this.updateBoardGetRobots();
    }
   
    private void setJComboCenterAlignment(final JComboBox jcombo) {
        final DefaultListCellRenderer renderer = new DefaultListCellRenderer();
        renderer.setHorizontalAlignment(SwingConstants.CENTER);
        jcombo.setRenderer(renderer);
    }
   
    private void refreshJComboPlaceRobot() {
        final Vector<String> vec = new Vector<String>();
        vec.add("Place robot");
        final int numRobots = this.board.getRobotPositions().length;
        for (int i = 0;  i < numRobots;  ++i) {
            vec.add(Board.ROBOT_COLOR_NAMES_LONG[i]);
        }
        this.jcomboPlaceRobot.setModel(new DefaultComboBoxModel(vec));
        this.jcomboPlaceRobot.setSelectedIndex(0);
    }
   
    private boolean isModePlay() {
        return (this.jtabPreparePlay.getSelectedIndex() == 1);
    }
   
    private void refreshJcomboRobots() {
        final String tmp = this.jcomboRobots.getActionCommand();
        this.jcomboRobots.setActionCommand("");
        this.jcomboRobots.setSelectedIndex(this.board.getRobotPositions().length - 1);
        this.jcomboRobots.setActionCommand(tmp);
    }

    private void refreshJcomboQuadrants() {
        for (int i = 0;  i < 4;  ++i) {
            final String tmp = this.jcomboQuadrants[i].getActionCommand();
            this.jcomboQuadrants[i].setActionCommand("");
            this.jcomboQuadrants[i].setSelectedIndex(this.board.getQuadrantNum(i));
            this.jcomboQuadrants[i].setActionCommand(tmp);
        }
    }

   
    private void refreshBoard() {
        //repaint board
        for (JComponent comp : this.boardCells) {
            comp.repaint();
        }
        //manipulate combobox of game IDs
        final String ac = this.jcomboGameIDs.getActionCommand();
        this.jcomboGameIDs.setActionCommand("");
        String newGameID = this.board.getGameID();
        if (this.jcomboGameIDs.getSelectedIndex() < 0) {    //not a list item
            Object item = this.jcomboGameIDs.getSelectedItem();
            if (null != item) {
                if (null != Board.createBoardGameID(item.toString())) {
                    newGameID = item.toString();
                }
            }
        }
        final int itemCount = this.jcomboGameIDs.getItemCount();
        boolean itemInList = false;
        for (int i = 0;  i < itemCount;  ++i) {
            final String itemStr =  this.jcomboGameIDs.getItemAt(i).toString();
            if (itemStr.equals(newGameID)) {
                itemInList = true;
                break;
            }
        }
        if (false == itemInList) {
            this.jcomboGameIDs.addItem(newGameID);
        }
        this.jcomboGameIDs.setSelectedItem(newGameID);
        this.jcomboGameIDs.setActionCommand(ac);
    }
   
   
    private void refreshBoard(final Move step) {
        for (int pos : step.pathMap.keySet()) {
            this.boardCells[pos].repaint();
        }
    }
   
    private void refreshButtons() {
        if (null == this.computedSolutionList) {
            this.jcomboSelectSolution.setEnabled(false);
            this.jbutSolutionHint.setEnabled(false);
            this.jbutNextMove.setEnabled(false);
            this.jbutPrevMove.setEnabled(false);
            this.jbutAllMoves.setEnabled(false);
            this.jbutNoMoves.setEnabled(false);
        } else {
            if (this.computedSolutionList.get(this.computedSolutionIndex).size() > 0) {
                this.jcomboSelectSolution.setEnabled(true);
                this.jbutSolutionHint.setEnabled(true);
            }
            this.jbutNextMove.setEnabled(true);
            this.jbutPrevMove.setEnabled(true);
            this.jbutAllMoves.setEnabled(true);
            this.jbutNoMoves.setEnabled(true);
            if (this.board.getGoal().robotNumber < 0) {
                for (int pos : this.currentPosition) {
                    if (pos == this.board.getGoal().position) {
                        this.jbutNextMove.setEnabled(false);
                        this.jbutAllMoves.setEnabled(false);
                        break;
                    }
                }
            } else if ((this.currentPosition[this.board.getGoal().robotNumber] == this.board.getGoal().position)
                    || (this.computedSolutionList.get(this.computedSolutionIndex).size() < 1)){
                this.jbutNextMove.setEnabled(false);
                this.jbutAllMoves.setEnabled(false);
            }
            if (this.moves.isEmpty()) {
                this.jbutPrevMove.setEnabled(false);
                this.jbutNoMoves.setEnabled(false);
            }
        }
    }
   
    private void showNextMove(final boolean doPrint) {
        final Move step = this.computedSolutionList.get(this.computedSolutionIndex).getNextMove();
        if (null != step) {
            this.moves.add(step);
            this.currentPosition[step.robotNumber] = step.newPosition;
            this.showMove(step, doPrint);
        }
    }
    private void showPrevMove(final boolean doPrint) {
        final Move step = this.computedSolutionList.get(this.computedSolutionIndex).getPrevMove();
        if (null != step) {
            this.moves.remove(step);
            this.currentPosition[step.robotNumber] = step.oldPosition;
            this.showMove(step, doPrint);
        }
    }
    private void showMove(final Move step, final boolean doPrint) {
        if (doPrint) {
            this.appendSolutionText((step.stepNumber + 1) + ": ", null);
            this.appendSolutionText(step.strRobotDirection(), COL_ROBOT[step.robotNumber]);
            this.appendSolutionText(" " + step.strOldNewPosition() + (this.computedSolutionList.get(this.computedSolutionIndex).isRebound(step) ? " rebound" : "") + "\n", null);
            //System.out.println(step.toString());
        }
        this.refreshButtons();
        this.refreshBoard(step);
    }
   
   
    private void handleGameID() {
        final String myGameID = this.board.getGameID();
        final String newGameID = this.jcomboGameIDs.getSelectedItem().toString();
        if (!newGameID.equals(myGameID)) {
            final Board newBoard = Board.createBoardGameID(newGameID);
            if (null != newBoard) {
                this.board = newBoard;
                this.refreshJComboPlaceRobot();
                this.refreshJcomboRobots();
                this.refreshJcomboQuadrants();
                this.updateBoardGetRobots();
            } else {
                appendSolutionText("error: this game ID '" + newGameID + "' is not valid.\n", null);
            }
        }
    }
   
   
    /* (non-Javadoc)
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if (AC_BOARD_QUADRANTS.equals(e.getActionCommand())) {
            this.makeBoardQuadrants();
            this.refreshJComboPlaceRobot();
        } else if (AC_SELECT_SOLUTION.equals(e.getActionCommand())) {
            this.selectSolution(this.jcomboSelectSolution.getSelectedIndex(), this.jcomboSelectSolution.getSelectedItem().toString());
        } else if (AC_PLACE_ROBOT.equals(e.getActionCommand())) {
            this.placeRobot = this.jcomboPlaceRobot.getSelectedIndex() - 1;     //item #0 is "Place robot"
        } else if (AC_PLACE_GOAL.equals(e.getActionCommand())) {
            this.placeGoal = !this.placeGoal;
            this.refreshBoard();
        } else if (AC_GAME_ID.equals(e.getActionCommand())) {
            this.handleGameID();
        } else if (AC_SHOW_COLOR_NAMES.equals(e.getActionCommand())) {
            this.refreshBoard();
        }
    }
   
    private class BoardCell extends JPanel implements MouseListener {
        private static final long serialVersionUID = 1L;
       
        private static final int PREF_WIDTH = 33;       // preferred width
        private static final int PREF_HEIGHT = 33;      // preferred height
        private static final int H_WALL_DIVISOR = 12;   // horizontal walls: height / H_WALL_DIVISOR
        private static final int V_WALL_DIVISOR = 12;   // vertical walls: width / vWallDivisor
       
       
        private final int boardPosition;
       
        public BoardCell(int boardPosition) {
            super();
            this.boardPosition = boardPosition;
            this.setPreferredSize(new Dimension(PREF_WIDTH, PREF_HEIGHT));
            this.setMinimumSize(new Dimension(10, 10));
            this.addMouseListener(this);
        }
       
        @Override
        protected void paintComponent(Graphics graphics) {
            final Graphics2D g2d = (Graphics2D) graphics.create();
            final int height = this.getSize().height;
            final int width  = this.getSize().width;
            final int hWallWidth = height / H_WALL_DIVISOR;
            final int vWallWidth = width / V_WALL_DIVISOR;
           
            this.setToolTipText(null);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
           
            // fill background (walls and center)
            g2d.setColor(COL_BACKGROUND);
            g2d.fillRect(0, 0, width, height);
           
            // fill center
            g2d.setPaint(new GradientPaint(0, height-1, COL_CELL1, width-1, 0, COL_CELL2));
            g2d.fillRect(vWallWidth, hWallWidth, width - vWallWidth * 2, height - hWallWidth * 2);
           
            // fill the 4 walls
            final byte[] walls = board.getWalls(this.boardPosition);
            g2d.setColor(COL_WALL);
            if (walls[Board.NORTH] != 0) {
                g2d.fillRect(0, 0, width, hWallWidth);
            }
            if (walls[Board.EAST] != 0) {
                g2d.fillRect(width - vWallWidth, 0, width, height);
            }
            if (walls[Board.SOUTH] != 0) {
                g2d.fillRect(0, height - hWallWidth, width, height);
            }
            if (walls[Board.WEST] != 0) {
                g2d.fillRect(0, 0, vWallWidth, height);
            }
           
            // paint the goal
            if (!isModePlay() || placeGoal || (board.getGoal().position == this.boardPosition)) {
                final Board.Goal goal;
                if (isModePlay() && !placeGoal) {
                    goal = board.getGoal();
                } else {
                    goal = board.getGoalAt(this.boardPosition);
                }
                if (null != goal) {
                    final Paint thePaint;
                    if (goal.robotNumber < 0) {
                        final float fStep = 1.0f / (COL_ROBOT.length-1);
                        final float[] fractions = new float[COL_ROBOT.length];
                        for (int i = 1; i < fractions.length; ++i) {
                            fractions[i] = fractions[i - 1] + fStep;
                        }
                        thePaint = new LinearGradientPaint(vWallWidth*2, hWallWidth*2, vWallWidth*2, height-1-hWallWidth*2, fractions, COL_ROBOT, MultipleGradientPaint.CycleMethod.REPEAT);
                    } else {
                        thePaint = new GradientPaint(0, 0, COL_ROBOT[goal.robotNumber], 0, height-1, Color.DARK_GRAY);
                    }
                    g2d.setPaint(thePaint);
                    final Shape outerShape = new Rectangle2D.Double(
                            vWallWidth, hWallWidth,
                            width-vWallWidth-vWallWidth, height-hWallWidth-hWallWidth);
                    final Shape innerShape;
                    switch (goal.shape) {
                    case Board.GOAL_SQUARE:
                        innerShape = new Rectangle2D.Double(
                                Math.round(width * (1.0d/4.0d)), Math.round(height * (1.0d/4.0d)),
                                Math.round(width * (2.0d/4.0d) - 1), Math.round(height * (2.0d/4.0d) - 1) );
                        break;
                    case Board.GOAL_TRIANGLE:
                        final Polygon triangle = new Polygon();
                        triangle.addPoint(width     / 5, height * 4 / 5);
                        triangle.addPoint(width * 4 / 5, height * 4 / 5);
                        triangle.addPoint(width    >> 1, height     / 5);
                        innerShape = triangle;
                        break;
                    case Board.GOAL_HEXAGON:
                        final Polygon hexagon = new Polygon();
                        hexagon.addPoint(width     / 6, height >> 1);
                        hexagon.addPoint(width * 2 / 6, height     / 5);
                        hexagon.addPoint(width * 4 / 6, height     / 5);
                        hexagon.addPoint(width * 5 / 6, height >> 1);
                        hexagon.addPoint(width * 4 / 6, height * 4 / 5);
                        hexagon.addPoint(width * 2 / 6, height * 4 / 5);
                        innerShape = hexagon;
                        break;
                    default:    //case Board.GOAL_CIRCLE:
                        innerShape = new Ellipse2D.Double(
                                width * (1.0d/5.0d), height * (1.0d/5.0d),
                                width * (3.0d/5.0d), height * (3.0d/5.0d) );
                        break;
                    }
                    final Area area = new Area(outerShape);
                    area.subtract(new Area(innerShape));
                    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                    g2d.fill(area);
                    if (jcheckOptShowColorNames.isSelected()) {
                        final String goalColorShort = ((goal.robotNumber < 0) ? "*" : Board.ROBOT_COLOR_NAMES_SHORT[goal.robotNumber]);
                        g2d.setColor(Color.BLACK);
                        g2d.drawChars(goalColorShort.toCharArray(), 0, 1, width / 2 - 3, height / 2 + 3);
                        final String goalColorLong = ((goal.robotNumber < 0) ? "wildcard" : Board.ROBOT_COLOR_NAMES_LONG[goal.robotNumber]);
                        this.setToolTipText(goalColorLong + " " + Board.GOAL_SHAPE_NAMES[goal.shape] + " goal");
                    }
                }
            }
           
            // paint the robot paths
            if (isModePlay()) {
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
                final Stroke oldStroke = g2d.getStroke();
                final int pathWidth = Math.min(hWallWidth, vWallWidth);
                g2d.setStroke(new BasicStroke(pathWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
                for (Move step : moves) {
                    final Integer path = step.pathMap.get(Integer.valueOf(this.boardPosition));
                    if (null != path) {
                        g2d.setColor(COL_ROBOT[step.robotNumber]);
                        final int halfHeight = (height >> 1) + ((step.robotNumber - 2) * pathWidth);
                        final int halfWidth = (width >> 1) + ((step.robotNumber - 2) * pathWidth);
                        if ((path.intValue() & Move.PATH_NORTH) != 0) {
                            g2d.drawLine(halfWidth, 0, halfWidth, halfHeight);
                        }
                        if ((path.intValue() & Move.PATH_SOUTH) != 0) {
                            g2d.drawLine(halfWidth, halfHeight, halfWidth, height-1);
                        }
                        if ((path.intValue() & Move.PATH_EAST) != 0) {
                            g2d.drawLine(halfWidth, halfHeight, width-1, halfHeight);
                        }
                        if ((path.intValue() & Move.PATH_WEST) != 0) {
                            g2d.drawLine(0, halfHeight, halfWidth, halfHeight);
                        }
                    }
                }
                g2d.setStroke(oldStroke);
            }
           
            // paint the robots
            if (isModePlay()) {
                for (int i = 0; i < currentPosition.length; ++i) {
                    if (currentPosition[i] == this.boardPosition) {
                        final Paint fillPaint = new GradientPaint(0, 0, COL_ROBOT[i], 0, height-1, Color.DARK_GRAY);
                        final Color outlineColor = Color.BLACK;
                        Polygon shapeFoot = new Polygon();
                        shapeFoot.addPoint(width / 2 - 1, height * 3 / 4 - 1);
                        shapeFoot.addPoint(vWallWidth, height - 1 - hWallWidth);
                        shapeFoot.addPoint(width - 1 - vWallWidth, height - 1 - hWallWidth);
                        final Ellipse2D.Double shapeBody = new Ellipse2D.Double(
                                vWallWidth * 3, hWallWidth,
                                width - 1 - vWallWidth * 6,
                                height - 1 - hWallWidth * 2
                                );
                        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                        g2d.setPaint(fillPaint);    g2d.fill(shapeFoot);
                        g2d.setColor(outlineColor); g2d.draw(shapeFoot);
                        g2d.setPaint(fillPaint);    g2d.fill(shapeBody);
                        g2d.setColor(outlineColor); g2d.draw(shapeBody);
                        if (jcheckOptShowColorNames.isSelected()) {
                            g2d.setColor(Color.WHITE);
                            g2d.drawChars(Board.ROBOT_COLOR_NAMES_SHORT[i].toCharArray(), 0, 1, width / 2 - 3, height / 2 + 3);
                            this.setToolTipText(Board.ROBOT_COLOR_NAMES_LONG[i] + " robot");
                        }
                        break;
                    }
                }
            }
        }
       
        //implements MouseListener
        @Override
        public void mouseClicked(MouseEvent e) {
            if (placeRobot >= 0) {
                if (board.setRobot(placeRobot, this.boardPosition)) {
                    updateBoardGetRobots();
                }
                placeRobot = -1;
                refreshJComboPlaceRobot();
            }
            if (placeGoal) {
                if (board.setGoal(this.boardPosition)) {
                    placeGoal = false;
                    updateBoardGetRobots();
                }
            }
        }
        @Override
        public void mouseEntered(MouseEvent e) { /* NO-OP */ }
        @Override
        public void mouseExited(MouseEvent e) { /* NO-OP */ }
        @Override
        public void mousePressed(MouseEvent e) { /* NO-OP */ }
        @Override
        public void mouseReleased(MouseEvent e) { /* NO-OP */ }
    }

}
TOP

Related Classes of driftingdroids.ui.SwingGUI

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.