January 30, 2021
BoggleSolver.java
to Lab 4.In this lab you will implement a method to find words on a Boggle game board. Boggle is a word game designed by Allan Turoff and distributed by Hasbro. It involves a board made up of 16 cubic dice, where each die has a letter printed on each of its 6 sides. At the beginning of the game, the 16 dice are shaken and randomly distributed into a 4-by-4 tray, with only the top sides of the dice visible. The players compete to accumulate points by building valid words from the dice, according to these rules:
Here are some examples of valid and invalid words:
PINS (valid) |
PINES (valid) |
DATES (invalid—dice not adjacent) |
PINT (invalid—path not sequential) |
TEPEE (invalid—die used more than once) |
SID (invalid—word not in dictionary) |
The goals for this lab are
getAllValidWords
?BoggleBoard
object? (It is likely you will need to use all of those listed Discussion).java
files for the lab, the starter project contains a number of text files (.txt
) for Boggle boards and dictionaries. You shouldn’t need to do anything with these (some are used by the tests).BoggleGame.java
to make sure the provided components of the game work. You should see a Boggle board, a timer, and a place to enter words. You won’t be able to enter any words yet because your task is to implement the method that finds the legal words.All the components of the Boggle game except the method to find valid words are provided to you. Your task is to implement the
public Set<String> getAllValidWords(BoggleBoard board)
method in BoggleSolver.java
. To know which sequences of letters are valid words, you need a dictionary. This is set up by the provided BoggleSolver
constructor. Specifically, the class has a field dictionary
that is a Set
of the words in the dictionary.
A Set
is a Java interface for a collection that contains no duplicate elements. For your purposes in this lab, you only need to use two of its methods:
add(String s)
: adds s
to the set, doing nothing if the string is already in the setcontains(String s)
: returns true
if s
is present in the setThe getAllValidWords
method you’re implementing returns a Set
of strings containing all the words that can be made using board
following the rules given in Overview. The starter code creates a HashSet
object for you add words to and return. A HashSet
implements the Set
interface using the hashing technique we’re learning about this week.
BoggleBoard
You are provided with BoggleBoard.java
for representing Boggle boards. Importantly for your purposes, it provides methods for getting the size of the board and the letter at each location and for keeping track of what locations have been used as part of a word. Here is the relevant API:
/**
* Returns the number of rows.
* @return number of rows
*/
public int rows()
/**
* Returns the number of columns.
* @return number of columns
*/
public int cols()
/**
* Returns the letter in row i and column j.
* @param i the row
* @param j the column
* @return a String version of the letter in row i and column j,
* returning "Qu" when the letter is 'Q'
*/
public String getLetter(int i, int j)
/**
* Marks the location at row i and column j as visited.
* @param i the row
* @param j the column
*/
public void visit(int i, int j)
/**
* Marks the location at row i and column j as not visited.
* @param i the row
* @param j the column
*/
public void unvisit(int i, int j)
/**
* Checks if the location at row i and column j is visited.
* @param i the row
* @param j the column
* @return true if the location is marked as visited
*/
public boolean isVisited(int i, int j)
/**
* Checks if all locations are visited.
* @return true if all locations are marked as visited
*/
public boolean allVisited()
/**
* Checks if the location at row i and column j is a valid board location.
* @param i the row
* @param j the column
* @return true is the location is a valid board location
*/
public boolean isValidLocation(int i, int j)
You are free to implement finding valid words in any recursive way you see fit. That said, I strongly recommend using a recursive backtracking approach. I outline one possible such approach below.
Searching for a word on a Boggle board follows this basic process:
To perform this search there are a some of things you need to keep track of:
Since getAllValidWords
only takes a board as a parameter, it won’t be able to recursively keep track of all of these. So you might implement a recursive helper method that does:
private void getAllValidWordsHelper(BoggleBoard board, Set<String> words,
String wordSoFar, int row, int col)
row
and col
are the current board location.wordSoFar
is a string of the letters you’ve encountered.words
is the set of words created in getAllValidWords
(our helper method needs to add words to it, so we pass it as a parameter).board
itself can keep track of where you’ve visited using the visit(int i, int j)
, isVisited(int i, int j)
, and unvisit(int i, int j)
methods.This helper method will search for words starting at location (row
, col
), adding on letters it encounters to wordSoFar
. Each recursive call is exploring the possible choices of next location to visit. To find all the words on a board, you need to start a search from each board location. This you can handle in getAllValidWords
: loop over all board locations (use board.rows()
and board.cols()
to get the size of the board) and call getAllValidWordsHelper
to start a search at each one.
How will you know if a word is in the dictionary? Use the contains
method of the dictionary
field: dictionary.contains(wordSoFar)
.
How will you “trace out paths through adjacent locations”? If you are at location (1
, 1
) of a 4-by-4 board, there are eight possible adjacent locations:
Note that the coordinates of the adjacent locations involve adding or subtracing 1 from the current row and/or column. Thus, we can nicely express these using nested for loops from row - 1
to row + 1
and from col - 1
to col + 1
where (row
, col
) is the current location. Of course, this will sometimes result in locations that are off the sides of the board. You can use board.isValidLocation
to skip these.
An important part of recursive backtracking is stopping the search when you reach a dead end. In Boggle, a search has reached a dead end if the letters you’ve encountered so far (wordSoFar
) don’t lead to any word in the dictionary. For example, if wordSoFar
is "TX"
, there’s no point to exploring further because no word in the dictionary begins with "TX"
. One term for letters at the start of a word is prefix. BoggleSolver
has a Set<String>
field prefixes
that contains all the prefixes to every word in the dictionary. For example, if the dictionary contains the word "DATA"
, prefixes
will contain "D"
, "DA"
, and "DAT"
. You should only make a recursive call if prefixes.contains(wordSoFar)
.
Like other examples of recursive backtracking we’ve seen, the code you need to write doesn’t take very many lines (likely no more than 15 or 20 in any one method). That doesn’t mean it’s easy or obvious, however. Planning out ahead of time (step 1 of Preparatory Exercises) will be very helpful.
Of the 11 JUnit tests provided with the starter project, only 2 (findsSomeWordsTest
and findsAllWordsTest
) are part of your grade (see Grading). The others are intended to help you debug if the graded ones are failing. I have tried to give these tests useful error messages when they fail.
There is one testing situation to be aware of. If you do not use prefixes
to avoid searching dead ends, as described in Procedure, your code will make an enormous amount of recursive calls. All the tests have a 2 second time limit, which should prevent running the tests from freezing VS Code in most cases. I have observed, however, that if I run all the tests at once with a print statement in my recursive method that doesn’t stop at dead ends, it causes VS Code to freeze. If you are debugging with print statements, take care to run just one test at a time.
You are expected to submit a BoggleSolver.java
that contains no checkstyle
errors. It is ok to have checkstyle
warnings in your submitted code, though I encourage you to fix them if you have time. Avoiding these warnings will become part of your grade on future labs.
You should ignore all Java warnings in BoggleBoard.java
and BoggleGame.java
.
This lab will be graded out of 30 points as shown below. While most of the points for this lab are associated with specific test cases, partial credit can be earned for test cases that don’t pass. It it possible to earn a passing graded even if your submission does not pass any tests. Partial credit will be awarded based on evidence of a good-faith effort to implement the related features. Comments explaining your approach can help earn partial credit.
Requirement | Points |
---|---|
findsSomeWordsTest passes |
12 points |
findsAllWordsTest passes |
6 points |
BoggleSolver.java implements a recursive method |
6 points |
BoggleSolver.java does not have any checkstyle errors |
3 points |
Check-in post | 1.5 points |
BoggleSolver.java compiles without warnings |
1 points |
Correct submission format (a file named BoggleSolver.java ) |
0.5 points |
Adapted from Matthew Drabick and Kevin Wayne: https://coursera.cs.princeton.edu/algs4/assignments/boggle/specification.php↩︎