/** * Board.java * Handle the mouse clicks and subsequent actions for the Boggle program. * This is a program that displays a Boggle board on the screen, comprised * of a 4x4 grid of squares, where each contains a letter. * The user builds words by successively clicking on displayed squares, resulting in the * successive letters being remembered to form a word. There are also on-screen * squares that can be clicked to exit the program or accept a word. The * originally selected letters are selected at random according to their * distribution in the English language. * * Class: CS 107, Spring 2009 * System: BlueJ 2.5, jsdk 1.6, Windows XP * Program #2, Boggle * * @author Dale Reed */ import java.util.Scanner; // allows user input from the keyBoard2 import java.util.Random; // allows getting a random number import java.util.Date; // used for timer public class Board { // Create the random number generator. "Seed" this with the value 1. // Removing the value from the parenthesis will give a different result every // time, which isn't what we want when we are debugging. Random randomNumberGenerator = new Random(1); // Constants used in the program static final int SQUARES_PER_SIDE = 4; // number of squares per side static final int NUMBER_OF_SQUARES = SQUARES_PER_SIDE * SQUARES_PER_SIDE; // number of squares static final int SQUARE_SIZE = 60; // Square size static final int OFFSET = 5; // OFFSETs to use in boundaries around playing squares static final int TIME_LIMIT = 60; // time limit in seconds // declare the array of Square used to represent the Board, using the default constructor // for each square as it is created. Note that a set of extra "boundary" squares are created // to make it easier later on to generate all possible words on the board without having to // write special code to make sure we don't "look" off the "edge" of the board. Square[] theBoard = new Square[ SQUARES_PER_SIDE * SQUARES_PER_SIDE]; // declare the dictionary, to be used to look up words Dictionary theDictionary; // declare buttons used to give output to the user, and an "Exit" button Square displayLabel; // displays the text "Word Letters: " on screen Square ResultLabel; // displays the selected letters for a word Square AcceptWordButton; // users presses this to accept the word Square ExitButton; // users presses this to exit program Square ClearButton; // user presses this to restart the current word Square RestartButton; // user presses this to restart game Square ScoreTitleLabel; // Displays the text "Score" on screen Square ScoreLabel; // Displays actual score on screen // variables to store the previous x,y mouse location of a letter clicked. This is used // to ensure only adjacent letters can update the displayed word. Initialize to an off-screen // value so that we can tell when we update these values for the first time. int oldX = -1; int oldY = -1; // store score, which is updated as game progresses int score = 0; long startTime; // used to keep track of when program starts, in milliseconds //--------------------------------------------------------------------------------- // Constructor to create the Board Board() { // Create a dictionary, to be used in looking up words theDictionary = new Dictionary(); /* // Debugging code to verify that dictionary lookup is working // The code below is just a sample of how to look up a word System.out.print("Enter a word to lookup: "); String userInput = "work"; // lookup word and print message telling if it is found if( theDictionary.wordExists( userInput.toLowerCase() )) { System.out.println("Yes, \"" + userInput + "\" is in the dictionary. "); } else { System.out.println("No, \"" + userInput + "\" is NOT in the dictionary. "); } */ // variables used for drawing int x = 65; int y = 30; // first make a large black background square, that will end up being the border Square background = new Square( x-OFFSET, y-OFFSET, (SQUARE_SIZE+OFFSET)*SQUARES_PER_SIDE + OFFSET, "black", true, ""); // for each Board square position, make the actual square that goes there for( int i=0; i TIME_LIMIT) { return true; } else { return false; } } //--------------------------------------------------------------------------------- // Handle each mouse click void handleMouseClick(int mouseX, int mouseY) { String idString = ""; // set default for display Square theSquare = null; // will point to which ever Square was clicked, if any // keep track of the middle of the clicked square, to be used in determining square adjacency int selectedSquareMiddleX = -1; // middle X value for selected square int selectedSquareMiddleY = -1; // middle Y value for selected square // get the clicked square. theSquare = getClickedSquare( mouseX, mouseY); // if theSquare has been set (it is not null) so a valid Square was clicked. Store its // middle position to be used in adjacency checking. if( theSquare != null) { // Found the square, so retrieve it's label // trim is used to eliminate any leading or trailing spaces, such as // the spaces on the "Exit" button used to align the text in the button idString = theSquare.getLabel().trim(); } // Now check idString to see what button, if any, was clicked. if( idString.equals("Restart")) { // The Restart button was clicked. Zero out values and select new board letters // Display all words that could have been created with the old board displayAllPossibleWords(); ResultLabel.setLabel( ""); score = 0; ScoreLabel.setLabel("0"); displayLabel.setLabel("Word Letters:"); oldX = oldY = -1; //reset the old square x,y positions used for adjacency checks System.out.println("\n" + " *** Board was reset. ***" + "\n"); resetBoardLetters(); // reset the start time for the timer startTime = new Date().getTime(); // get the time in milliseconds } else if( idString.equals("Exit")) { // Display all words that could have been created with this board displayAllPossibleWords(); System.out.println("Final Score was: " + score); System.out.println("Exiting program..."); System.exit( 0); } else if( timeHasRunOut()) { // time has run out if( timeHasRunOut()) { ResultLabel.setLabel( ""); System.out.println("Time has run out. Press Restart or Exit" + "\n"); ResultLabel.setLabel( ""); displayLabel.setLabel(" TIME is UP. Restart or Exit"); } System.out.println("Final Score was: " + score); } else if( idString.equals("Accept")) { // The Accept button was clicked. Display the word and update score String theWord = ResultLabel.getLabel(); // ensure it is a valid word. Trim off any extra spaces and convert to lower case if( theDictionary.wordExists( theWord.trim().toLowerCase()) ) { // update score variable based on length of this word, and update displayed score int points = 0; switch( theWord.length()) { case 1: case 2: case 3: points = 1; break; case 4: points = 2; break; case 5: points = 4; break; default: points = 3 + theWord.length()/2; break; } score = score + points; System.out.print( theWord + " for " + points + " point"); if( points > 1) { // print the 's' on "points" if it is more than 1 System.out.print("s"); } System.out.print(", for " + score + " total. "); ScoreLabel.setLabel("" + score); // get the current time long currentTime = new Date().getTime(); long secondsRemaining = TIME_LIMIT - (currentTime - startTime)/1000; System.out.println( secondsRemaining + " seconds remaining."); }//end if( theDictionary.wordExists( ...) else { System.out.println( "Sorry, \"" + theWord + "\" is not a valid word."); } // clear the word from the screen ResultLabel.setLabel(""); oldX = oldY = -1; //reset the old square x,y positions used for adjacency checks } else if( idString.equals("Clear")) { // The Clear button was clicked. Erase the word and leave score unchanged ResultLabel.setLabel( ""); oldX = oldY = -1; //reset the old square x,y positions used for adjacency checks } else if( !idString.equals("")) { // idString is not empty ("") and was not "Exit" or "Accept" so a board letter // Square must have been clicked. // Set the upper-left corner coordinates for the selected square selectedSquareMiddleX = theSquare.getX() + theSquare.getSize()/2; selectedSquareMiddleY = theSquare.getY() + theSquare.getSize()/2; // Update the answer box by adding the latest letter clicked to what is there so far. // First ensure squares are adjacent by verifying that the distance between the two // clicks in both the x and y directions is less than the size of a square + the square OFFSET // Only do this check when a previous letter has already been clicked (oldX!=-1 and oldY!=-1) // If this is the first letter clicked, store mouse position for adjacency test next turn if( (oldX == -1) && (oldY == -1)) { ResultLabel.setLabel( ResultLabel.getLabel() + idString); // record the x,y position of the selected square for future adjacency // comparison oldX = selectedSquareMiddleX; oldY = selectedSquareMiddleY; } else { // Ensure letter is adjacent to previous letter before updating display string if( (Math.abs( selectedSquareMiddleX - oldX) <= (SQUARE_SIZE + OFFSET)) && (Math.abs( selectedSquareMiddleY - oldY) <= (SQUARE_SIZE + OFFSET)) ) { ResultLabel.setLabel( ResultLabel.getLabel() + idString); oldX = selectedSquareMiddleX; oldY = selectedSquareMiddleY; }//end if }//end else }//end else if( !idString.equals("")) }//end handleMouseClick() //--------------------------------------------------------------------------------- // Return the clicked Square, if there is one // Square getClickedSquare( int mouseX, int mouseY) { Square theSquare = null; // will point to selected Square if there is one // Check each board square to see if it overlaps with mouse click position. // If it does, return that Square. Intersection should only be true for a single // square as long as the squares don't overlap. for( int i=0; i= SquareXPosition) && (mouseX <= (SquareXPosition + squareSize)) && (mouseY >= SquareYPosition) && (mouseY <= (SquareYPosition + squareSize)) ) { return true; } else { return false; } }//end mouseIntersectsSquare() //--------------------------------------------------------------------------------- // Get a random letter to display on the Board, verifying that it is not already // on the board. Use the following table: /* letter freq cummulative freq. a 8.167 8.167 b 1.492 9.659 c 2.782 12.441 d 4.253 16.694 e 12.702 29.396 f 2.228 31.624 g 2.015 33.639 h 6.094 39.733 i 6.966 46.699 j 0.153 46.852 k 0.772 47.624 l 4.025 51.649 m 2.406 54.055 n 6.749 60.804 o 7.507 68.311 p 1.929 70.24 q 0.095 70.335 r 5.987 76.322 s 6.327 82.649 t 9.056 91.705 u 2.758 94.463 v 0.978 95.441 w 2.36 97.801 x 0.15 97.951 y 1.974 99.925 z 0.074 99.999 */ String getLetter() { String theLetter = ""; do { // get a random number, which will be used to determine which of 26 letters to choose int theRandomInt = randomNumberGenerator.nextInt(); // Get a possibly large random int // ensure it is a positive value theRandomInt = Math.abs( theRandomInt); // put it in the range 0..100000 theRandomInt = theRandomInt % 100000; // Remember that %100 gives the remainder after dividing by 100 // put it in the range 1..100000 theRandomInt += 1; // add 1 to it float theRandomNumber = theRandomInt / 1000; // put it in the range 0.000 .. 99.999 // use the random number to choose a letter if( theRandomNumber <= 8.167) theLetter = "A"; else if( theRandomNumber <= 9.659) theLetter = "B"; else if( theRandomNumber <= 12.441) theLetter = "C"; else if( theRandomNumber <= 16.694) theLetter = "D"; else if( theRandomNumber <= 29.396) theLetter = "E"; else if( theRandomNumber <= 31.624) theLetter = "F"; else if( theRandomNumber <= 33.639) theLetter = "G"; else if( theRandomNumber <= 39.733) theLetter = "H"; else if( theRandomNumber <= 46.699) theLetter = "I"; else if( theRandomNumber <= 46.852) theLetter = "J"; else if( theRandomNumber <= 47.624) theLetter = "K"; else if( theRandomNumber <= 51.649) theLetter = "L"; else if( theRandomNumber <= 54.055) theLetter = "M"; else if( theRandomNumber <= 60.804) theLetter = "N"; else if( theRandomNumber <= 68.311) theLetter = "O"; else if( theRandomNumber <= 70.24) theLetter = "P"; else if( theRandomNumber <= 70.335) theLetter = "Q"; else if( theRandomNumber <= 76.322) theLetter = "R"; else if( theRandomNumber <= 82.649) theLetter = "S"; else if( theRandomNumber <= 91.705) theLetter = "T"; else if( theRandomNumber <= 94.463) theLetter = "U"; else if( theRandomNumber <= 95.441) theLetter = "V"; else if( theRandomNumber <= 97.801) theLetter = "W"; else if( theRandomNumber <= 97.951) theLetter = "X"; else if( theRandomNumber <= 99.925) theLetter = "Y"; else if( theRandomNumber < 100.000) theLetter = "Z"; } while ( theLetterAlreadyOnBoard( theLetter) ); return theLetter; }//end getLetter() //--------------------------------------------------------------------------------- // See if the letter is already on the board. Return true if so, false otherwise. // boolean theLetterAlreadyOnBoard( String theLetter) { boolean returnValue = false; // assume it is not on board // go through each board square and verify that this proposed letter // is not already there. for( int i=0; i 2) && wordFoundOnBoard( theWord, boardLetters) ) { resultsString = resultsString + theWord + " "; } // After ~75 characters of output, go to a new line if( resultsString.length() >= 70) { System.out.println( resultsString); resultsString = ""; } } System.out.println(); } //--------------------------------------------------------------------------------- // Make an array of characters similar to the board, except with an // extra "border" on all sides, to simplify word lookup. void initializeBoardLettersArray( char[] boardLetters) { int charactersPerSide = SQUARES_PER_SIDE + 2; int boardSquareIndex = 0; for( int i=0; i (charactersPerSide * charactersPerSide - charactersPerSide) )) // bottom row { // it is one of the "blank" squares boardLetters[ i] = ' '; } else { // copy over the character from the board Squares boardLetters[ i] = theBoard[ boardSquareIndex].getLabel().toLowerCase().charAt( 0); boardSquareIndex++; // advance the pointer to the next board Square index } }//end for( int i... // Show the boardLettersArray before showing the words that can be formed System.out.print("\n" + "Board Letters:"); for( int i=0; i