Course Home | Assignments | Class Photo | Computing Resources | Lab Hours/Tutoring | Schedule | Submit

Overview

Our goal is to design a program to simulate a game of Boggle. The program will display a randomly chosen board, allow the user to enter as many words as he or she can find. The software will check that each entered word is legitimate. When the user is done entering words, the software should also produce a list of all other words that could have been found on that board.

Newly Introduced Techniques

• Significant use of Recursion.
• Two-dimensional array structure (i.e., list of lists).

Collaboration Policy

For this assignment, you are allowed to work with one other student if you wish (in fact, we suggest that you do so). If any student wishes to have a partner but has not been able to locate one, please let the instructor know so that we can match up partners.

Files You Will Need

We are providing you with three files for the project. The easiest way to get them, if you are working on our computer system, is by typing the following from a terminal window:
```cp -R /Public/goldwasser/150/programs/boggle .
```

boggleUtil.py is a module with some utilities to generate new boards and to display them (details below). The other two entries are word lists: bogshort.txt contains 19,912 words (most common words, but not plurals of nouns, or varying tenses of verbs); boglong.txt contains 117,661 words and is much more complete (this is purported to be the official scrabble dictionary).

Rules

A Boggle board is comprised of 16 dice. Each side of a die is typically marked with a single letter from the English alphabet, though one dice has a side marked with 'Qu' (more about this later). To start the game, the dice are shaken up and then placed in a 4x4 grid. An example of such a game board is shown here.

A player is then asked to find as many words as possible on the game board. A word can be find by stepping from die to neighboring die, where this step can either be horizontal, vertical or diagonal. Such a path must be found so as to spell the word from beginning to end. But you may not use the same die twice when spelling a single word. For example, the word WALK can be found as shown in the following figure.

The word CRIER could also be formed, by starting at the top-right corner. Note that the letter 'R' is used twice, yet this is legal so long as we use two different dice (thus using the top-left corner of the board for the final 'R' in this example). Notice that we cannot form the word DAD in this puzzle, as that would require us to use the same die twice.

The goal of the player is to list as many words as possible that can be found on the board. More formally, for a player to get credit for a word, the following conditions must be satisfied:

• The word must be in the official dictionary.
• The word must be at least three characters long (note that in the case of using 'Qu' this means it may perhaps be only two dice).
• It must be possible to form the word on the given board.
• It must not be a word already used by the player. That is, you cannot get credit for the same English word twice, even if it were possible to form it in different paths on the board (e.g., TIER in this example).

In the multiplayer version of Boggle, players only get credit for words that they find that are not found by any other players. In our one-player version, we will let the human player take the first turn, getting credit for all words he or she finds; then we let the computer take the second turn, taking credit for all remaining words not used by the player. The scoring for the official game gives more points for longer words, based upon the following formula:

 Number of letters Points awarded 3 4 5 6 7 8 or more 1 1 2 3 5 11

Sample Round

Here is a sample run of our program. Note that your program does not need to match the precise formatting shown here, but this should demonstrate the spirit of the game and the expected interactions.

```Welcome to Boggle.
What is the name of the wordlist file? [default: bogshort.txt]
What board number? [default: random] 180
R  Qu I  C
U  E  R  O
I  W  Y  K
T  D  A  L

Please enter words, one per line.  When you are done enter a blank line.

walk
tie
die
or
-- Words must use at least three letters.
ore
core
rice
-- Sorry.  That word cannot be formed on the gameboard.
york
-- Sorry.  That word is not in the wordfile.
cork
quirk
rock
-- Sorry.  That word cannot be formed on the gameboard.
lawyer
law
yore
ore
-- You already used that word.
year
-- Sorry.  That word cannot be formed on the gameboard.
-- Sorry.  That word cannot be formed on the gameboard.
day
way
tie
-- You already used that word.
wit

------------------------------------------
Player used 598.168277 seconds.
You found 14 words worth a total of 17 points.
core
cork
day
die
law
lawyer
ore
quirk
tie
walk
way
wit
yore

Now it is the computer's turn...
------------------------------------------
Computer used 0.487780 seconds.
The computer found 32 additional words for 39 points.
awe
awry
aye
coy
crew
cry
dye
dyer
ire
irk
kayo
lay
lye
lyric
okay
query
quirky
royal
rue
rye
tid
tidal
tidy
tier
weir
wry
yak
yaw

------------------------------------------
Would you like to play again? [default: Y] n
Okay. Thanks for playing!
```

Data Structures

We will be starting you off with some utilities for generating and representing a game board. The game board is viewed as a two-dimensional structure. This can be naturally modeled in Python as a list of lists. That is, the board can be thought of as a list of columns, where each column is then a list of dice. For example, on the board diagramed earlier in this description, the first column can be viewed as a list ['R','U','I','T'], the next column as ['Qu','E','W','D'], and so on. The overall board is then a list comprised of the four columns.

Syntactically, if we have a variable board representing such a list of lists, then we can refer to an individual die using a syntax such as board[2][3] which is the die 'A' in our example. Namely, the simpler expression, board[2] would represent the column ['I', 'R', 'Y', 'A'] (as the columns are indexed starting with zero). Given that board[2] evaluates to this list, we can then index into that list, so that board[2][3] results in the 'A'.

Given such a representation of a board, I have provided three utility functions for you to use:

• newBoard()
By default, will randomly generate a new board structure and return it to the caller. However it first prompts the user for a 'game number' and if provided, will give the user a particular game board rather than a new randomly generated one (for example board 180 is the one displayed above).

• drawBoard(board)
In this form, the board is drawn in pure text on the standard output screen.

• drawBoard(board, canvas)
In this form, a picture of the board is displayed on the given Canvas.

• clearHighlights(board)
(See next blurb about the representation of the dice.) This simply clears the highlights for all of the dice.

Rather than representing the board directly as such a two-dimensional list of strings, we have created a class Die to represent each of the dice. The operations supported by a die are:

• getValue()
returns a string representing the visible side of the die. Note that the text will be all lower-case (even though our figures show them as upper case).

• setHighlight()
Requests that this die be displayed in 'highlighted' form when the board is subsequently drawn.

• clearHighlight()
Requests that this die no longer be displayed in 'highlighted' form when the board is subsequently drawn.

The major challenges are:

• Basic straightforward code to control the flow of a game.

• Checking aspects of validity of a player's word, such as the length, whether it is in the dictionary, and whether it has been used already.

• Checking the validity of a player's word as to whether it can legally be formed on a game board. Moreso, to be able to highlight the location of a valid word visually, you will need to keep track of the locations of the dice used for that word.

This task can be accomplished with the careful use of recursion. The key is to get the correct signature for modeling the problem. The goal might be to have a routine such as find(word), but the problem is that for the general recursion, you need to be able to find a partial word and to avoid using dice that have already been used. Therefore, we recommend the following signature:

```def find(suffix, board, used, lastspot):
"""
Determines whether or not the suffix can be found on the board,
while avoiding the used dice, and starting the remaining path with
a neighbor of the lastspot die.

If successful, return the overall list of dice used.
If unsuccessful, returns None and ensures that 'used' is unchanged.
"""
```

• The ability to report all words that the player missed (and to do so efficiently, even on the large dictionary).

There are really two ways to find all such words. The first way is a word-based search. Presuming that you have a working function to check whether a given word appears on the game board, you can simply loop through all the words in the official dictionary and check whether each appears on the board. This takes minimal effort in terms of coding, but unfortunately you will find that it is quite slow, even on the shorter dictionary.

A completely different approach is one we will term path-based. That is discussed as an extra credit challenge.

Please submit your sourcecode, boggle.py as well as a separate 'readme' file. If you worked as a pair, please make this clear and briefly describe the contributions of each person in the effort. Please see details regarding the submission process from the general programming web page, as well as a discussion of the late policy.

Extra Credit (1 point)

The efficiency of the automated player can be greatly improved by changing the high-level approach. Rather than testing every word in the dictionary indivdiually, we use the following path-based search.

We can try to form paths on the board and then check whether such a path leads you to a word in the dictionary. To make this efficient, the idea is to build short paths and then before going on, check whether the prefix you have actually occurs in the dictionary. That is, if you start tracing a path starting with "TRG" and you find that there are no words anywhere in the official dictionary starting with "TRG" then there is no need to continue spending time exploring this path.

Therefore, you will need to write a separate routine to be able to effectively check whether a given prefix occurs in the official dictionary. Since the words are kept alphabetized in a list, we can use binary search to accomplish this task. Checking whether a prefix occurs is very similar to checking whether an actual word occurs. With some minor adjustments, the binary search code we have already seen can be adapted to suit this task (see Chapter 11).

Michael Goldwasser