diff options
Diffstat (limited to 'Algorithms/Part-II/4-Boggle/BoggleGame.java')
-rw-r--r-- | Algorithms/Part-II/4-Boggle/BoggleGame.java | 1006 |
1 files changed, 1006 insertions, 0 deletions
diff --git a/Algorithms/Part-II/4-Boggle/BoggleGame.java b/Algorithms/Part-II/4-Boggle/BoggleGame.java new file mode 100644 index 0000000..71d5477 --- /dev/null +++ b/Algorithms/Part-II/4-Boggle/BoggleGame.java @@ -0,0 +1,1006 @@ +/************************************************************************* + * Author: Matthew Drabick + * Compilation: javac BoggleGame.java + * Execution: java BoggleGame [M N] + * Dependencies: BoggleSolver.java BoggleBoard.java + * + * GUI for the boggle solver. Pits the user against a computer opponent + * of various difficulties. Can be launched from the command line, where + * the default size of the board for that game must be specified. + * + * To add: Way to change the size of the board from inside the game + * + * % javac BoggleGame.java + * + * % java BoggleGame + * + * % java -Xmx300m BoggleGame 3 7 + * + * Report bugs to: wayne@princeton.edu, CC mdrabick@princeton.edu + * + * Note: expect some compiler warning with Java 7 because + * javax.swing.JList is a parameterized type in Java 7 but not + * in Java 6. + * + *************************************************************************/ + +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Component; +import java.awt.GridLayout; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.TreeSet; +import java.util.Timer; +import java.util.TimerTask; +import java.util.Vector; +import javax.swing.*; + + +public class BoggleGame extends JFrame { + private final static int GAME_TIME = 180; // in seconds + private final static int SECONDS_PER_MINUTE = 60; // number of seconds in one minute + private final static int FOUND_WORDS_DISPLAY_COUNT = 17; // how many rows to display for the two side columns + private final static int ALL_WORDS_DISPLAY_COUNT = 7; // how many rows to display for the middle all-words list + + // sizes of GUI elements, in pixels + private final static int DEF_HEIGHT = 550; + private final static int DEF_WIDTH = 700; + private final static int WORD_PANEL_WIDTH = 205; + private final static int WORD_PANEL_HEIGHT = 325; + + // colors for displaying words found only by player, opponent, and both + private final static Color PLAYER_POINT_WORD = new Color(0xBFBFBF); + private final static Color OPP_POINT_WORD = new Color(0xBFBFBF); + private final static Color NONPOINT_WORD = new Color(0xBFBFBF); + + // game levels + // keep these in sync - should be a text description for each level! + // if making adjustments to levels, endGame (~line 400) contains hard-coded elements + // menu items will be adjusted automatically + private final static int NUMBER_OF_LEVELS = 5; + private final static String[] LEVEL_DESCRIPTION = { + "Nursery", + "Shakespeare", + "Algorithms 4/e", + "Hard", + "Impossible" + }; + private final static int NURSERY = 0; + private final static int SHAKESPEARE = 1; + private final static int ALGORITHMS = 2; + private final static int HARD = 3; + private final static int IMPOSSIBLE = 4; + + // keep these two values in sync! + // used to force the JTextfield and the JList to be the same length + private final static int DEF_COLUMNS = 10; + private final static String MAX_WORD_SIZE = "INCONSEQUENTIALLY"; + + + // keeps track of the level + private int gameDifficulty = 0; + + // number and rows and columns of board + private int BOARD_ROWS; + private int BOARD_COLS; + + // game values + private boolean inGame = true; + private int elapsedTime = 0; // elapsed time (in seconds) + private int points = 0; // current number of points + private Timer timer = new Timer(); + + private String[] emptyList = new String[0]; + + private LinkedHashSet<String> foundWords; // to keep words in same order as entered + private TreeSet<String> validWords; + private TreeSet<String> opponentFoundWords; + private JList foundWordsList; + private JList validWordsList; + private JList opponentFoundWordsList; + private int oppCurScore; + private BoggleBoard board; + + // dictionaries + // (words that appear in Shakespeare, nursery rhymes, common words, and Algorithms 4/e) + private SET<String> shakespeareDictionary; + private SET<String> nurseryDictionary; + private SET<String> commonDictionary; + private SET<String> algs4Dictionary; + + // GUI elements + private JMenuBar menuBar; + private JMenu gameMenu; + private JRadioButtonMenuItem[] difficultySelection; + private BoggleSolver solver; + private JLabel clock; + private BoardPanel bp; + private final JTextField entryField; + private JLabel scoreLabel; + private JLabel possiblePointsLabel; + private JLabel oppScoreLabel; + + /** + * Construct the GUI for the Boggle game + */ + public BoggleGame(int rows, int cols) { + // set the number of rows and columns + BOARD_ROWS = rows; + BOARD_COLS = cols; + + // this.setPreferredSize(new Dimension(DEF_WIDTH, DEF_HEIGHT)); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setTitle("Boggle"); + setLocationRelativeTo(null); + this.makeMenuBar(); + + // timer panel + JPanel timerPanel = new JPanel(); + JLabel timerLabel = new JLabel("Timer:"); + String seconds = String.format("%02d", (GAME_TIME - elapsedTime) % SECONDS_PER_MINUTE); + String minutes = String.format("%02d", (GAME_TIME - elapsedTime) / SECONDS_PER_MINUTE); + String time = minutes + ":" + seconds; + clock = new JLabel(time); + timerPanel.add(timerLabel); + timerPanel.add(clock); + + // text entry field + entryField = new JTextField(DEF_COLUMNS); + entryField.setMaximumSize(new Dimension(entryField.getPreferredSize().width, + entryField.getPreferredSize().height)); + entryField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + checkWord(); + } + }); + entryField.addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent e) { } + @Override + public void keyReleased(KeyEvent e) { + JTextField txtSrc = (JTextField) e.getSource(); + String text = txtSrc.getText().toUpperCase(); + bp.matchWord(text); + } + @Override + public void keyTyped(KeyEvent e) { } + }); + + // list of typed words + foundWordsList = new JList(); + foundWordsList.setPrototypeCellValue(MAX_WORD_SIZE); + foundWordsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + foundWordsList.setListData(emptyList); + foundWordsList.setVisibleRowCount(FOUND_WORDS_DISPLAY_COUNT); + foundWordsList.setLayoutOrientation(JList.VERTICAL_WRAP); + foundWordsList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component comp = super.getListCellRendererComponent(list, value, index, false, false); + JComponent jc = (JComponent) comp; + String word = (String)value; + if (!inGame && inGame) { + if (foundWords.contains(word) && !opponentFoundWords.contains(word)) { + comp.setBackground(PLAYER_POINT_WORD); + } + else if (foundWords.contains(word) && opponentFoundWords.contains(word)) { + comp.setBackground(NONPOINT_WORD); + } + } + comp.setForeground(Color.black); + return comp; + } + }); + JScrollPane foundWordsScrollPane = new JScrollPane(foundWordsList); + foundWordsScrollPane.setPreferredSize(new Dimension(WORD_PANEL_WIDTH, WORD_PANEL_HEIGHT)); + foundWordsScrollPane.setMinimumSize(foundWordsScrollPane.getPreferredSize()); + foundWordsScrollPane.setMaximumSize(foundWordsScrollPane.getPreferredSize()); + JPanel scoreLabelPanel = new JPanel(); + scoreLabel = new JLabel("My Points:"); + scoreLabelPanel.add(scoreLabel); + JPanel controlPanel = new JPanel(); + + // layout for the left panel, with controls and found word display + GroupLayout controlLayout = new GroupLayout(controlPanel); + controlPanel.setLayout(controlLayout); + controlLayout.setAutoCreateGaps(true); + controlLayout.setAutoCreateContainerGaps(true); + controlLayout.setHorizontalGroup( + controlLayout.createSequentialGroup() + .addGroup(controlLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(timerPanel) + .addComponent(entryField) + .addComponent(foundWordsScrollPane) + .addComponent(scoreLabelPanel)) + ); + controlLayout.setVerticalGroup( + controlLayout.createSequentialGroup() + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(timerPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(entryField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(foundWordsScrollPane, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(scoreLabelPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + + // creates the board and the list that will show all the available words at the end of a game + bp = new BoardPanel(); + validWordsList = new JList(); + validWordsList.setVisible(true); + validWordsList.setVisibleRowCount(ALL_WORDS_DISPLAY_COUNT); + validWordsList.setPrototypeCellValue(MAX_WORD_SIZE); + validWordsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + validWordsList.setLayoutOrientation(JList.VERTICAL_WRAP); + validWordsList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component comp = super.getListCellRendererComponent(list, value, index, false, false); + String word = (String) value; + if (!inGame) { + if (foundWords.contains(word)) { + comp.setBackground(OPP_POINT_WORD); + } + } + comp.setForeground(Color.black); + return comp; + } + }); + JScrollPane validWordsScrollPane = new JScrollPane(validWordsList); + validWordsScrollPane.setPreferredSize(new Dimension(300, 145)); + validWordsScrollPane.setMinimumSize(validWordsScrollPane.getPreferredSize()); + validWordsScrollPane.setMaximumSize(validWordsScrollPane.getPreferredSize()); + JPanel possiblePointsPanel = new JPanel(); + possiblePointsLabel = new JLabel(); + possiblePointsPanel.add(possiblePointsLabel); + JPanel gamePanel = new JPanel(); + + // layout for that panel + GroupLayout gameLayout = new GroupLayout(gamePanel); + gamePanel.setLayout(gameLayout); + gameLayout.setAutoCreateGaps(true); + gameLayout.setAutoCreateContainerGaps(true); + gameLayout.setHorizontalGroup( + gameLayout.createSequentialGroup() + .addGroup(gameLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(bp) + .addComponent(validWordsScrollPane) + .addComponent(possiblePointsPanel)) + ); + gameLayout.setVerticalGroup( + gameLayout.createSequentialGroup() + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bp, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(validWordsScrollPane, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(possiblePointsPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + + // Opponent game panel + JLabel opponentLabel = new JLabel("Opponent's Words:"); + JPanel opponentLabelPanel = new JPanel(); + opponentLabelPanel.add(opponentLabel); + oppScoreLabel = new JLabel("Opponent's Points: "); + JPanel oppScoreLabelPanel = new JPanel(); + oppScoreLabelPanel.add(oppScoreLabel); + opponentFoundWordsList = new JList(); + opponentFoundWordsList.setPrototypeCellValue(MAX_WORD_SIZE); + opponentFoundWordsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + opponentFoundWordsList.setListData(emptyList); + opponentFoundWordsList.setVisibleRowCount(FOUND_WORDS_DISPLAY_COUNT); + opponentFoundWordsList.setLayoutOrientation(JList.VERTICAL_WRAP); + opponentFoundWordsList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component comp = super.getListCellRendererComponent(list, value, index, false, false); + String word = (String) value; + if (!inGame && inGame) { + if (!foundWords.contains(word) && opponentFoundWords.contains(word)) { + comp.setBackground(OPP_POINT_WORD); + } + else if (foundWords.contains(word) && opponentFoundWords.contains(word)) { + comp.setBackground(NONPOINT_WORD); + } + } + comp.setForeground(Color.black); + return comp; + } + }); + + JScrollPane opponentWordsScrollPane = new JScrollPane(opponentFoundWordsList); + opponentWordsScrollPane.setPreferredSize(new Dimension(WORD_PANEL_WIDTH, WORD_PANEL_HEIGHT)); + opponentWordsScrollPane.setMinimumSize(opponentWordsScrollPane.getPreferredSize()); + opponentWordsScrollPane.setMaximumSize(opponentWordsScrollPane.getPreferredSize()); + JPanel spacingPanel = new JPanel(); + spacingPanel.setPreferredSize(new Dimension(WORD_PANEL_WIDTH, 22)); + JPanel opponentPanel = new JPanel(); + GroupLayout buttonsLayout = new GroupLayout(opponentPanel); + opponentPanel.setLayout(buttonsLayout); + buttonsLayout.setAutoCreateContainerGaps(true); + buttonsLayout.setAutoCreateGaps(true); + buttonsLayout.setHorizontalGroup( + buttonsLayout.createSequentialGroup() + .addGroup(buttonsLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(spacingPanel) + .addComponent(opponentLabelPanel) + .addComponent(opponentWordsScrollPane) + .addComponent(oppScoreLabelPanel)) + //.addComponent(winnerLabel)) + ); + buttonsLayout.setVerticalGroup( + buttonsLayout.createSequentialGroup() + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spacingPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(opponentLabelPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(opponentWordsScrollPane, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(oppScoreLabelPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + //.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + //.addComponent(winnerLabel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + + // layout for the left and right panels + Container content = getContentPane(); + GroupLayout layout = new GroupLayout(content); + content.setLayout(layout); + layout.setAutoCreateContainerGaps(true); + layout.setAutoCreateGaps(true); + layout.setHorizontalGroup( + layout.createSequentialGroup() + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(controlPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(gamePanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) + .addComponent(opponentPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER, false) + .addComponent(controlPanel) + .addComponent(gamePanel) + .addComponent(opponentPanel)) + ); + + // all words in shakespeare + In in1 = new In(new File("dictionary-shakespeare.txt")); + shakespeareDictionary = new SET<String>(); + for (String s : in1.readAllStrings()) + shakespeareDictionary.add(s); + + // all words in shakespeare + In in2 = new In(new File("dictionary-nursery.txt")); + nurseryDictionary = new SET<String>(); + for (String s : in2.readAllStrings()) + nurseryDictionary.add(s); + + // about 20K common words + In in3 = new In(new File("dictionary-common.txt")); + commonDictionary = new SET<String>(); + for (String s : in3.readAllStrings()) + commonDictionary.add(s); + + // all words in Algorithms 4/e + In in4 = new In(new File("dictionary-algs4.txt")); + algs4Dictionary = new SET<String>(); + for (String s : in4.readAllStrings()) + algs4Dictionary.add(s); + + // dictionary + In in = new In(new File("dictionary-yawl.txt")); + String[] dictionary = in.readAllStrings(); + + // create the Boggle solver with the given dictionary + solver = new BoggleSolver(dictionary); + + newGame(); + this.pack(); + } + + /** + * Start a new game, can be called via the menu selection, the button, or CMD+N (CRTL+N) + */ + private void newGame() { + if (BOARD_ROWS == 4 && BOARD_COLS == 4) { + board = new BoggleBoard(); + } + else { + board = new BoggleBoard(BOARD_ROWS, BOARD_COLS); + } + clock.setForeground(Color.BLACK); + entryField.requestFocus(); + inGame = true; + points = 0; + scoreLabel.setText("Current Points:" + points); + entryField.setEnabled(true); + + foundWords = new LinkedHashSet<String>(); + + // set display of word lists to be empty + foundWordsList.setListData(emptyList); + validWordsList.setListData(emptyList); + opponentFoundWordsList.setListData(emptyList); + + bp.setBoard(); + bp.unhighlightCubes(); + + // all valid words + Iterable<String> words = solver.getAllValidWords(board); + validWords = new TreeSet<String>(); + int possiblePoints = 0; + for(String s : words) { + validWords.add(s); + possiblePoints += scoreWord(s); + } + possiblePointsLabel.setText("Possible Points: " + possiblePoints); + + // opponent's words + opponentFoundWords = new TreeSet<String>(); + if (gameDifficulty == NURSERY) { + for (String word : validWords) + if (nurseryDictionary.contains(word)) + opponentFoundWords.add(word); + } + + else if (gameDifficulty == SHAKESPEARE) { + for (String word : validWords) + if (shakespeareDictionary.contains(word) && StdRandom.uniform(3) != 0) + opponentFoundWords.add(word); + } + + else if (gameDifficulty == ALGORITHMS) { + for (String word : validWords) + if (algs4Dictionary.contains(word)) + opponentFoundWords.add(word); + } + + else if (gameDifficulty == HARD) { + for (String word : validWords) + if (commonDictionary.contains(word) && StdRandom.bernoulli()) + opponentFoundWords.add(word); + } + + else if (gameDifficulty == IMPOSSIBLE) { + for (String word : validWords) + if (StdRandom.uniform(4) != 0) + opponentFoundWords.add(word); + } + + // opponent's score + oppCurScore = 0; + for (String word : opponentFoundWords) + oppCurScore += scoreWord(word); + + oppScoreLabel.setText("Opponent's Points: " + oppCurScore); + timer.cancel(); + elapsedTime = -1; + timer = new Timer(); + timer.schedule(new Countdown(), 0, 1000); + + } + + /** + * End the current game, can be called via the menu selection, the button, or CMD+E (CRTL+E) + */ + private void endGame() { + + clock.setText("00:00"); + clock.setForeground(Color.RED); + timer.cancel(); + entryField.setText(""); + entryField.setEnabled(false); + + // display list of all valid words + validWordsList.setListData(validWords.toArray()); + + // highlight found words by specifying indices of found words + int[] indices = new int[foundWords.size()]; + int i = 0; + int n = 0; + for (String s : validWords) { + if (foundWords.contains(s)) + indices[i++] = n; + n++; + } + validWordsList.setSelectedIndices(indices); + //validWordsList.setEnabled(false); + + inGame = false; + + // compute score, discounting words that both players found + int playerScore = points; + int opponentScore = oppCurScore; + for (String s : foundWords) { + if (opponentFoundWords.contains(s)) { + playerScore -= scoreWord(s); + opponentScore -= scoreWord(s); + } + } + + // strike out words in user's list that opponent found + Object[] list1 = foundWords.toArray(); + for (int j = 0; j < list1.length; j++) { + if (opponentFoundWords.contains(list1[j])) { + list1[j] = "<html><strike>" + list1[j] + "</strike></html>"; + } + } + foundWordsList.setListData(list1); + + // strike out words in opponent's list that user found + Object[] list2 = opponentFoundWords.toArray(); + for (int j = 0; j < list2.length; j++) { + if (foundWords.contains(list2[j])) { + list2[j] = "<html><strike>" + list2[j] + "</strike></html>"; + } + } + opponentFoundWordsList.setListData(list2); + + // display dialog indicating which player won + String winnerMessage = ""; + if (playerScore > opponentScore) winnerMessage = " You win!\n\n"; + else if (playerScore < opponentScore) winnerMessage = " The computer wins!\n\n"; + else winnerMessage = " Tie!\n\n"; + String scoreMessage = " Final score:\n You: " + playerScore + " - Computer: " + opponentScore; + JOptionPane.showMessageDialog(this, winnerMessage + scoreMessage, "Game finished", JOptionPane.PLAIN_MESSAGE); + } + + /** + * Timer that runs to keep track of the game time + */ + private class Countdown extends TimerTask { + @Override + public void run() { + if (elapsedTime < GAME_TIME - 1) { + elapsedTime++; + String seconds = String.format("%02d", (GAME_TIME - elapsedTime) % SECONDS_PER_MINUTE); + String minutes = String.format("%02d", (GAME_TIME - elapsedTime) / SECONDS_PER_MINUTE); + String time = minutes + ":" + seconds; + clock.setText(time); + } + else { + endGame(); + } + } + } + + /** + * Check the word entered in the text field or selected by clicks on the board + * Pressing ENTER or clicking the Check Word button will call this + */ + private void checkWord() { + String s; + // decide to which to use, take the longer + if (entryField.getText().length() >= bp.getCurrentPath().length()) + s = entryField.getText().toUpperCase(); + else + s = bp.getCurrentPath().toUpperCase(); + s = s.trim(); + if (s.equals("")) return; + + // search for word + if (validWords.contains(s) && !foundWords.contains(s)) { + foundWords.add(s); + foundWordsList.setListData(foundWords.toArray()); + points += scoreWord(s); + scoreLabel.setText("Current Points: " + points); + entryField.setText(""); + } + + // used for testing (gets 100% of all valid words) + else if (s.equals("GODMODE")) { + for (String str : solver.getAllValidWords(board)) { + entryField.setText(str); + checkWord(); + } + } + + // used for testing (gets 25% of all valid words) + else if (s.equals("GODMODE4")) { + for (String str : solver.getAllValidWords(board)) { + if (StdRandom.uniform(4) == 0) { + entryField.setText(str); + checkWord(); + } + } + } + + // beep if invalid word + else { + Toolkit.getDefaultToolkit().beep(); + entryField.setText(""); + } + } + + /** + * Score a word based off typical Boggle scoring + * @param s Word to score + * @return Score of the word passed in + */ + private int scoreWord(String s) { + int pointValue; + int length = s.length(); + if (length < 5) pointValue = 1; + else if (length == 5) pointValue = 2; + else if (length == 6) pointValue = 3; + else if (length == 7) pointValue = 5; + else pointValue = 11; + return pointValue; + } + + /** + * Class that displays the board for the user to interact with + * @author mdrabick + */ + private class BoardPanel extends JPanel { + private int NUM_OF_CUBES = BOARD_ROWS * BOARD_COLS; + private JLabel[] cubes = new JLabel[NUM_OF_CUBES]; + private int CUBE_DIM = 60; + private int[] path; + private boolean foundWord; + + /** + * Constructor for the board which the user interacts with in order to play Boggle + */ + public BoardPanel() { + GridLayout cubeLayout = new GridLayout(BOARD_ROWS, BOARD_COLS); + this.setPreferredSize(new Dimension(CUBE_DIM*BOARD_COLS, CUBE_DIM*BOARD_ROWS)); + this.setMinimumSize(this.getPreferredSize()); + this.setMaximumSize(this.getPreferredSize()); + this.setLayout(cubeLayout); + for (int i = 0; i < NUM_OF_CUBES; i++) { + final int cur = i; + cubes[i] = new JLabel("", JLabel.CENTER); + cubes[i].setFont(new Font("SansSerif", Font.PLAIN, 28)); + cubes[i].setPreferredSize(new Dimension(CUBE_DIM, CUBE_DIM)); + cubes[i].setMinimumSize(cubes[i].getPreferredSize()); + cubes[i].setMaximumSize(cubes[i].getPreferredSize()); + cubes[i].setBorder(BorderFactory.createRaisedBevelBorder()); + cubes[i].setOpaque(true); + cubes[i].setBackground(new Color(146, 183, 219)); + cubes[i].addMouseListener(new MouseListener() { + @Override + public void mouseClicked(MouseEvent arg0) { + if (inGame) { + if (path == null) { + path = new int[NUM_OF_CUBES]; + for (int n = 0; n < path.length; n++) { + path[n] = -1; + } + path[0] = cur; + highlightCubes(); + return; + } + for (int j = 0; j < path.length; j++) { + // if it is the first cube clicked + if (j == 0 && path[j] == -1) { + path[j] = cur; + break; + } + // if the cube clicked is in the path + else if (path[j] == cur ) { + // check if it is the last cube or the last one in the current path + //if so un-highlight it + if (j == path.length-1 || path[j+1] == -1) { + cubes[cur].setBackground(new Color(146, 183, 219)); + path[j] = -1; + } + break; + } + // check for adjacency to the last cube in the path + else if (path[j] == -1) { + // row above + if (path[j-1] >= cur-BOARD_COLS-1 && path[j-1] <= cur-BOARD_COLS+1) + path[j] = cur; + // next to (same row) + else if (path[j-1] == cur-1 || path[j-1] == cur+1) + path[j] = cur; + // row below + else if (path[j-1] >= cur+BOARD_COLS-1 && path[j-1] <= cur+BOARD_COLS+1) + path[j] = cur; + + break; + } + } + highlightCubes(); + } + } + @Override + public void mouseEntered(MouseEvent arg0) { } + @Override + public void mouseExited(MouseEvent arg0) { } + @Override + public void mousePressed(MouseEvent arg0) { } + @Override + public void mouseReleased(MouseEvent arg0) { } + }); + cubes[i].addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent arg0) { } + @Override + public void keyReleased(KeyEvent arg0) { } + @Override + public void keyTyped(KeyEvent arg0) { + int keyCode = arg0.getKeyCode(); + if (keyCode == KeyEvent.VK_ENTER) { + checkWord(); + } + } + }); + this.add(cubes[i]); + } + } + /** + * Clear the selected blocks (change from highlighted to unhighlighted) + */ + public void clearSelection() { + for (int i = 0; i < path.length; i++) { + path[i] = -1; + cubes[i].setBackground(new Color(146, 183, 219)); + } + } + + /** + * Get the word spelled by the selected path. + * @return the word spelled out + */ + public String getCurrentPath() { + StringBuilder selectedWord = new StringBuilder(8); + for (int s : path) { + if (s < 0) break; + selectedWord.append(cubes[s].getText().charAt(0)); + if (cubes[s].getText().charAt(0) == 'Q') selectedWord.append('U'); + } + return selectedWord.toString(); + } + + /** + * Set the board with a String array + * + */ + public void setBoard() { + String[] letters = new String[BOARD_ROWS * BOARD_COLS]; + for (int i = 0; i < BOARD_ROWS; i++) { + for (int j = 0; j < BOARD_COLS; j++) { + char letter = board.getLetter(i, j); + if (letter == 'Q') + cubes[i*BOARD_COLS + j].setText("Qu"); + else + cubes[i*BOARD_COLS + j].setText("" + letter); + } + } + } + + /** + * Highlight all the cubes in the path array + */ + public void highlightCubes() { + for (int i = 0; i < path.length; i++) { + if (path[i] == -1) break; + cubes[path[i]].setBackground(new Color(232, 237, 76)); + } + } + + /** + * Un-highlight all the cubes in the path array + */ + public void unhighlightCubes() { + if (path == null) return; + for (int i = 0; i < path.length; i++) { + if (path[i] == -1) break; + cubes[path[i]].setBackground(new Color(146, 183, 219)); + } + } + + /** + * Highlight the correct cubes when typing + * @param s String to match on the board + */ + public void matchWord(String s) { + if (path != null) unhighlightCubes(); + path = new int[NUM_OF_CUBES]; + for (int i = 0; i < path.length; i++) { + path[i] = -1; + } + foundWord = false; + s = s.toUpperCase(); + for (int i = 0; i < cubes.length; i++) { + if (s.startsWith(cubes[i].getText().toUpperCase())) { + dfs(s, 0, 0, i / BOARD_COLS, i % BOARD_COLS); + } + if (foundWord) break; + } + if (foundWord) { + highlightCubes(); + } + } + + /** + * Recursive helper method to search for a particular string on the board + * @param s String that is being searched + * @param curChar Current char that is being sought + * @param pathIndex Current number of cubes searched (only differs from curChar if there is a q in string) + * @param i Row of the board + * @param j Column of the board + */ + private void dfs(String s, int curChar, int pathIndex, int i, int j) { + // if the word has already been found + // if (foundWord) return; + // out of bounds + if (i < 0 || j < 0 || i >= BOARD_ROWS || j >= BOARD_COLS) return; + // return if entire word is found + if (curChar >= s.length()) { + foundWord = true; + return; + } + // can't use a cell more than once + for (int n = 0; n < path.length; n++) { + if (path[n] == (i*BOARD_COLS)+j) return; + } + // ignore if character if there is a 'Q' with no 'U' + if (curChar != 0 && s.charAt(curChar-1) == 'Q' && s.charAt(curChar) != 'U') + return; + // increment character count if it is a 'U' after a 'Q' and keep searching + if (curChar != 0 && s.charAt(curChar-1) == 'Q' && s.charAt(curChar) == 'U') + curChar += 1; + if (curChar >= s.length()) { + foundWord = true; + return; + } + // if it doesn't have the right character + if (cubes[(i*BOARD_COLS)+j].getText().charAt(0) != s.charAt(curChar)) { + return; + } + // mark spot and save location; + path[pathIndex] = (i*BOARD_COLS)+j; + //visited[i][j] = true; + // consider all neighbors + for (int ii = -1; ii <= 1; ii++) + for (int jj = -1; jj <= 1; jj++) + if (!foundWord) dfs(s, curChar+1, pathIndex+1, i + ii, j + jj); + + if (!foundWord) path[curChar] = -1; + } + } + + /** + * Create the menu bar + */ + private void makeMenuBar() { + menuBar = new JMenuBar(); + gameMenu = new JMenu("Game"); + gameMenu.setMnemonic(KeyEvent.VK_G); + gameMenu.getAccessibleContext().setAccessibleDescription("This menu contains game options"); + menuBar.add(gameMenu); + JMenuItem newGameMenuItem = new JMenuItem("New...", KeyEvent.VK_N); + newGameMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + newGameMenuItem.getAccessibleContext().setAccessibleDescription("Starts a new game"); + newGameMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + newGame(); + } + }); + JMenuItem endGameMenuItem = new JMenuItem("End Game", KeyEvent.VK_E); + endGameMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + endGameMenuItem.getAccessibleContext().setAccessibleDescription("Ends the current game"); + endGameMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + endGame(); + } + }); + gameMenu.add(newGameMenuItem); + gameMenu.add(endGameMenuItem); + gameMenu.addSeparator(); + ButtonGroup difficultyGroup = new ButtonGroup(); + difficultySelection = new JRadioButtonMenuItem[NUMBER_OF_LEVELS]; + for (int i = 0; i < NUMBER_OF_LEVELS; i++) { + difficultySelection[i] = new JRadioButtonMenuItem(LEVEL_DESCRIPTION[i % LEVEL_DESCRIPTION.length]); // mod as a check against mismatched sizes + if (i == 0) difficultySelection[i].setSelected(true); + difficultySelection[i].setActionCommand(LEVEL_DESCRIPTION[i % LEVEL_DESCRIPTION.length]); + difficultySelection[i].addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent ae) { + for (int i = 0; i < LEVEL_DESCRIPTION.length; i++) { + if (ae.getActionCommand().equals(LEVEL_DESCRIPTION[i])) { + gameDifficulty = i; + //endGame(); + newGame(); + break; + } + } + } + }); + difficultyGroup.add(difficultySelection[i]); + gameMenu.add(difficultySelection[i]); + } + JMenuItem quitMenuItem = new JMenuItem("Quit", KeyEvent.VK_Q); + quitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + quitMenuItem.getAccessibleContext().setAccessibleDescription("Quits the program"); + quitMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + timer.cancel(); + System.exit(0); + } + }); + gameMenu.addSeparator(); + gameMenu.add(quitMenuItem); + setJMenuBar(menuBar); + } + + + /** + * @param args + */ + public static void main(final String[] args) { + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + int rows = 0; + int cols = 0; + if (args.length == 0) { + rows = 4; + cols = 4; + } + else if (args.length == 1) { + try { + rows = Integer.parseInt(args[0]); + cols = rows; + } catch (NumberFormatException e) { + System.err.println("Usage: java BoggleGame " + + "\nor: java BoggleGame [rows]" + + "\nor: java BoggleGame [rows] [cols]"); + System.exit(1); + } + } + else if (args.length == 2) { + try { + rows = Integer.parseInt(args[0]); + cols = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + System.err.println("Usage: java BoggleGame " + + "\nor: java BoggleGame [rows]" + + "\nor: java BoggleGame [rows] [cols]"); + System.exit(1); + } + } + else { + System.err.println("Usage: java BoggleGame " + + "\nor: java BoggleGame [rows]" + + "\nor: java BoggleGame [rows] [cols]"); + System.exit(1); + } + + if (rows <= 0 || cols <= 0) { + throw new java.lang.IllegalArgumentException("Rows and columns must be positive" + + "\nUsage: java BoggleGame " + + "\nor: java BoggleGame [rows]" + + "\nor: java BoggleGame [rows] [cols]"); + } + new BoggleGame(rows, cols).setVisible(true); + } + }); + } + +} |