In this lab, you will use tries to do implement the family-fun game of Boggle.
The purpose of this lab is to:
The game Boggle is played with 16 dice that have letters on all faces. The dice are randomly deposited into a four-by-four grid so that the players see the 16 letters on the top faces. For example, the board may look like this:
Each player has a limited amount of time to identify as many words as can be located on the board. Words can be formed using adjacent letters in any direction (including diagonals) but cannot reuse letters or wrap around the board. For example, the board below contains the words dent, hit, tine, tide, hub, bun, hide, raid, rain, etc. The location of "tide" is highlighted in blue. Players accumulate points based on the number of words found.
In this lab, you are going to write a program that lets the user play a one-person version of Boggle. The GUI is provided for you; your job is to write the program that rolls the dice, reads and stores a dictionary, finds all dictionary words on the board, and checks whether given guesses are on the board. There's plenty to do, so let's get on with it!
The lab09.jar file contains words_ospd.txt (the official scrabble dictionary), lexicon.txt (a smaller but still extensive dictionary), small.txt (a much smaller dictionary), dice.txt (a list of the dice), Square.java (to be explained later), ExpandableList.java and BoggleFrame.java (the Boggle GUI). You should not have to edit any of these files in this lab.
In today's lab you'll implement your own Trie class called MyTrie, which should be a subclass of AbstractSet<String>. If you need it, you can read these lecture notes for details on the use and implementation of Tries.
Each MyTrie instance should contain the following data members:
boolean isWord; // whether this trie node is the end of a word int size; // the number of words represented by this trie MyTrie children; // the child tries of this node
The no-argument constructor for a Trie should initialize an empty Trie. The result should be a single Trie node whose isWord flag is false and whose children array is an array of null pointers. (You need to instantiate the array, but not its individual entries.) Recall that the size of the array is determined by the number of characters in the alphabet; in our case, that will be 26. (This is a good place for the use of a "static final" variable to define a constant, so that you don't need to hard-code the number 26 in more than one place in your program.)
Remember: you should be testing these methods as you construct them, one at a time. Make jUnit tests for this purpose, and this will save you time down the road.
prefix, false otherwise. Note that this is not checking
isWordvalues, just existence of the appropriate children. As soon as one of the expected children is not present, return false.
In searching a trie for a string, the individual characters of the string must be used to index into each trie node's array of children. To accomplish this, you will need to convert each character to a numeric index. In particular, you will need to convert 'a' to 0, 'b' to 1, 'c' to 2, ..., and 'z' to 25.
This is easy to do in Java, because characters are considered to be a numeric type compatible with int, so it is possible to perform arithmetic operations on them. When Java performs arithmetic on characters, each character is interpreted as the number used in its Unicode representation. Because the letters of the alphabet are assigned consecutive Unicode values, a letter can be converted to a number in the range 0..25 by simply subtracting the letter 'a' from it. For example, 'b' - 'a' is equal to 1, 'c' - 'a' is equal to 2, 'd' - 'a' is equal to 3, etc.
Note: Use actual character literals. Use of numeric equivalents will result in a loss of points. For example, use 'z' instead of 122 or 0x7a or 0172.
You should test your Trie class before proceeding. For example, a good test may create a new empty Trie, then add the words "hello", "hellos", "hella", "apples", "bolivia", and "bologna". You could then check whether your trie contains "hello" (it should), "h" (it should not), and "hellos" (it should). If you print out your tree, the words should appear in alphabetical order. Of course, you'll want more tests than just this, but it's a start.
Another good test is as follows: Build a trie from a lexicon file (some are provided for you), and display it using toString. Then use the contains method to test a bunch of the search words.
As discussed, a Boggle board consists of a four-by-four grid of dice. Therefore, there is a Square class provided to you that represent a square on the boggle board; that is, the "showing" side of one of the dice. You can read through the class to see how it works---it's not very complicated. Don't worry about "marking" the Square yet; that will become clear in the next portion of the lab.
Your next task is to write the class Boggle that represents a Boggle board.
Each Boggle board should contain the following data members:
MyTrie lex; // The dictionary, stored in a Trie Square board; // The 4x4 board MyTrie foundWords; // The dictionary words on the current board MyTrie guesses; // The valid guesses made so far by our one player String dice; // An array of dice -- explained later!
You should write a Boggle constructor that takes a single String parameter that represents the file name of your lexicon (i.e. dictionary), and creates a new Trie out of the words in that lexicon. You should also "initialize" the dice (see the private
If you try to implement all the methods below before testing them with the GUI, you will probably have a tough time debugging your code. To alleviate some pain and suffering, I recommend that you implement all the methods (or, the non-trivial ones) with "bogus" default code (the minimum amount needed to make the compiler happy), then add the implementations one at a time, running your Boggle program as you go along.
wordand false otherwise.
containsmethod on the
guessto the list of guesses, if it is in the dictionary (or better, foundWords).
wstarting from each square
sqin the board, and return any one list it may find.
dicefrom the file dice.txt.
ito show a random face from
prefixand can be completed in a valid way on the board starting at square
foundWordsto contain all words on the board that are in the dictionary.
sqin the board, and call
search(sq,"")from each one, and adding the Strings of the resulting Trie into the foundWords Trie.
sqthat form the word
w(or, return an empty list if no such path exists)
searchmethod (although it's not exactly the same).
For this lab, whenever you see a "q" on the board, you should treat is as "qu".
Here is a nice search algorithm to find all words on the board. It is based on the idea of "recursive backtracking", which we've already seen in our maze solver lab.
for each square sq on the board search for words starting at sq in lex via the helper method
The search helper method uses the following logic:
search(Square sq, String prefix) // check to see if we have found a word on the current path if the current path represents a word in the dictionary add the word to the wordlist // continue searching on all possible paths from this square let l = the letter in sq for each unmarked square s adjacent to sq mark it recursively search for words starting at square s with prefix prefix+sq.letter, and add these to wordlist unmark it
Your Boggle class should have a main method that takes a single command-line argument that is the file name of the lexicon. Your program should then construct a new Boggle instance from this argument, and create a new BoggleFrame instance as follows:
Boggle boggle = new Boggle( args ); BoggleFrame bFrame = new BoggleFrame( boggle ); bFrame.pack(); bFrame.setLocationRelativeTo(null); bFrame.setVisible(true);
And now you should be able to run your program with the command
% java Boggle words_ospd.txtor
% java Boggle lexicon.txtand play Boggle to your heart's content. The dictionaries take a few seconds to load, and this happens in between the GUI showing up and the dice letters loading, so be patient, but not too patient. (You can always trie out Serializable and write the Trie to disk to speed things up.) Enjoy!
Use handin to submit the following files: