CS 2110 Summer 2011: Assignment 2 — Boggle

[Pages:8]CS 2110 Summer 2011: Assignment 2 -- Boggle

Due July 12 at 5pm

This assignment is to be done in pairs. Information about partners will be provided separately.

1 Playing Boggle1

In this assignment, we continue the theme of games. Unlike the last assignment, where the computer relied on deception to outplay its human opponent, in this assignment the computer will try to win using nothing more than clever algorithms and brute force computation.

You will design software to play a computer version of the board game Boggle. If you are unfamiliar with the game, here is a brief description. The board is a 4 x 4 grid onto which 16 dice are randomly placed. On the face of each die is a single letter (except for one face, which has the two letters "Qu"). The objective is to form words from sequentially adjacent cubes. (Two cubes are adjacent, or neighboring, if they lie next to each other horizontally, vertically, or diagonally. Cubes in the middle of the board have 8 neighbors; a cube at the corner of the board has only 3 neighbors.) In forming a word, each cube can be used only once. The objective to find as many words as possible in a given amount of time. In the traditional board game version, all players start at the same time, writing down as many words as they can find, and then comparing word lists when time is up. A player is awarded points for finding a word that no one else found, and more points are given for longer words. Additional details of the game are described on Wikipedia ().

Our computerized version has only a few modifications. We have two players: a human word finder and a computer word finder. The human goes first, and has 60 seconds to find as many words as possible. Then it is the computer's turn. As the screenshot suggests, the computer often trounces the human. In addition, in our version the size of the square grid is variable, and you can play either a 4 x 4 or a 5 x 5 board. On the smaller board, the computer is restricted to finding words of three or more letters; on the larger board, 4 or more letters.

1This assignment was adapted with permission from Owen Astrachan of Duke. His assignment is the result of a combination of efforts from computer science educators at Duke, Stanford, and UCSD.

1

Figure 1: A screenshot of the computer version of Boggle. Highlighted on the board is one of the 84 words that the computer found: "clothe." The primary goal of this assignment is to design and implement recursive algorithms. So that you can concentrate on these aspects, we provide code that manages the details of the game and displays the board graphically. (You will have a chance to code graphical user interfaces later this summer!)

Primary Learning Objectives ? Master recursion, particularly recursive backtracking algorithms (2(a) on syllabus). ? Practice the methodology of write unit tests (4(b) on syllabus). ? Use inheritance appropriately in software design (3(a,c) on syllabus).

2

2 The Assignment

You are asked to (a) implement three classes; (b) write unit tests for one of those classes; and (c) answer a few questions.

Classes to Implement ? Define and implement a new class called WordOnBoardFinder that implements the IWordOnBoardFinder interface. When the human player enters a word, this class can be used to verify that the word does in fact occur on the board. (Hint: it may also have other uses.) ? Define and implement two versions of the computer player. Each version of your computer player must implement the IAutoPlayer interface. Conceptually, the computer player is given a Boggle board and a lexicon (a list of words) and its job is to find every word that can be formed on the board under the rules of Boggle. In addition to code that manages the Boggle board, we also provide a Java class that manages the lexicon, called SimpleLexicon. ? The first version should be in a new class called LexiconFirstAutoPlayer. The strategy behind this version is to start with the lexicon: for each word that occurs in the lexicon, see if it occurs on the board. ? The second version should be in a new class called BoardFirstAutoPlayer. The strategy behind this version is to start with the board and try to form words by trying all possible paths on the board. For each path, check whether the word formed by the path occurs in the lexicon. As explained in Section 4.4, it should avoid "dead end" paths. The SimpleLexicon class offers useful features for recognizing dead ends.

Unit Tests In class called TestWordFinder, you should write at least 3 test cases that test your WordOnBoardFinder implementation. Each test case should test a distinct aspect of the class' functionality. (If you implement your WordOnBoardFinder class using test-driven development, each test will naturally examine a distinct aspect of your implementation.) Give each test a descriptive name and include a comment that describes what each particular test is testing. (Note: an example test is provided for you, you must implement 3 in addition to this one.) You are not required to submit tests for your IAutoPlayer implementations. Nevertheless we recommend that you test this code to make sure it works properly.

3

Questions 1. In BoardFirstAutoPlayer, record the number of times that your recursive helper method is called. The specific number may vary from game to game, but roughly how many times is it called? If the answer is around 12 million, then you may not be avoiding dead ends (see 4.4). 2. Describe how you dealt with the special case of the "Qu." 3. Briefly describe your experience working with a partner. For each method, who wrote the body of the method? When you were not the one typing, what were you doing to help your partner?

2.1 Grading guidelines

Carefully review the comments you received on assignment 1 and avoid similar mistakes this time. While for the first assignment, we did not deduct points for some mistakes such as poor style and incorrect .jar files, we will be deducting points for these mistakes on assignment 2. As with assignment 1, your code must adhere to the interfaces. Finally, be sure to include answers to the above questions.

2.2 Submission

Submissions should be handed in via CMS . You should submit a jar file called assignment2.jar containing:

? WordOnBoardFinder.java ? BoardFirstAutoPlayer.java ? LexiconFirstAutoPlayer.java ? TestWordFinder.java ? "answers.txt" containing the answers to the questions above. ? (Optional) BinarySearchLexicon.java Instructions for creating a .jar file were provided in assignment 1. Following the convention from the first assignment, your Java code should be in a package having the name id.assignment2 where netid is either your netid or your partners' netid (either is fine).

4

3 Extension (Optional)

Completing the following extensions will earn you karma points, as explained in the syllabus.

Good Test Cases Exceptionally good test cases will earn karma.

BinarySearchLexicon While conceptually a lexicon is just a set of words, the Java class that we provide for managing the lexicon, SimpleLexicon, does more than just maintain a set of words. In particular, it is possible to check not just whether a word is contained in the lexicon but also whether it is a prefix of a word in the lexicon. The SimpleLexicon implements the ILexicon interface. To complete this extension, write an alternative implementation of the ILexicon interface. Your implementation should be written in a class called BinarySearchLexicon. This class should store words in a sorted ArrayList. Sort the list after adding all the words to it using the Java built-in method Collections.sort. To determine the status of a word, implement a recursive binary search. We will discuss binary search in class, but perhaps not until near the deadline for this assignment. You can read about it in the book on p. 423. The key idea is to use binary search to find whether a given String is a word in the sorted lexicon. Or if the word is not there, a binary search can tell you where the word would be. Once you've found this location, checking for a prefix is fairly straightforward. You might find it helpful to use the startsWith method defined in the String class. When checking for a prefix, make sure you don't go off the end of the array of words in the lexicon. Write some test cases that demonstrate the correctness of your ILexicon implementation.

4 Tips

4.1 Getting Started

From CMS, download assignment2.jar and the two lists of words, bogwords.txt and ospd3.txt. The second list is a large list derived from the Official Scrabble Player's Dictionary and contains some rather unusual words (good for impressing friends). Following the same procedure as in assignment 1, import the jar into Eclipse. You should not modify any of these files except BoggleMain and AutoPlayerMain.

5

We are providing you with a lot of code. Much of it deals with the graphical interface and mechanics of the game. That stuff you can ignore. But you will need to review some of the interfaces and classes. I suggest using the built-in feature of Eclipse to generate javadocs which you can then browse in Eclipse or in a browser.

Play the game by compiling the code and running the main method of BoggleMain. Before running, be sure to update the variable filename with the appropriate location of one of the two word lists. Since the game is being played with BadWordOnBoardFinder and BadAutoPlayer, the user's guesses are not found on the board and the auto player finds no words. That's no fun. Once your code is working, replace these bad implementations with your code and play the game!

You may need to install JUnit to run the testing code. Instructions will be available on the course website, on the Assignments page.

4.2 Testing

You are encouraged to think about testing first. You may even want to try test-driven development.

The class ExampleTest gives some hints on how to get started: it illustrates how to create a test board and run a single unit test. Do not modify this code. Create a new class, TestWordFinder, that tests your implementation and include at least 3 test methods in this class. You are free to copy code from ExampleTest into your class TestWordFinder as you see fit.

Before thinking about the recursive aspects of WordOnBoardFinder, I suggest that you set up the test code and get your WordOnBoardFinder to pass the test that checks for one-letter words.

4.3 WordOnBoardFinder

Your WordOnBoardFinder class must implement the cellsForWord method of the IWordOnBoardFinder interface. This method is responsible for finding whether and where a given word occurs on the board. I recommend that you write a private "helper" method that is called within cellsForWord. This helper method should be recursive. It will search for the word beginning at a specified (row, column) cell on the board--the code in method in cellsForWord calls the helper method with every possible (row, column) as a starting point as follows:

public List cellsForWord(BoggleBoard board, String word) { List list = new ArrayList(); for(int r=0; r < board.size(); r++){

6

for(int c=0; c < board.size(); c++){ if (helper(board,r,c,list, ....)){ // not shown

You're free to write the helper method as you see fit. Here are some suggestions. The goal of the helper method is to recursively "grow" a match to the word, one letter/cell at a time. At any given point in time, it will have a partial match to the word (say, the first k characters) and it will be trying to extend that match by matching the next character in the word with its current position (row, column) on the board. You may want the helper method to have an int parameter which can act as an index indicating which character in the string is the one being tentatively matched to the current board cell. The first time the helper method is called this parameter has the value zero indicating the first character of the string should be matched. There are several things to keep in mind:

? When should you stop recursing? There are two cases: you have found the word (how do you check this?) or, given the tentative match you have so far, it is not possible to extend the match (why not?).

? Which direction can you move to extend the match? A board cell has between 3 and 8 neighbors, depending on its location on the board. It might be helpful to have a separate method that takes care of the messiness of figuring out where you can move next. Depending on how you do it, this method might return a boolean (valid/invalid move) or a list of neighboring cells.

? A board cell can be matched only once. If a word contains more than one occurrence of a given letter, how will you prevent a cell from being accidentally re-used?

? When you don't find the word being looked for, you'll have to "backtrack" and undo the work done so far. As with typical, recursive, backtracking code, this often involves undoing the one step made before the recursive invocation(s). What changes are you making at each step? What do you need to undo?

? Be careful when finding a word that contains "qu" because in this case, two letters in the word are matched to a single cube on the Boggle board (the "Qu" cube). My suggestion is to first get your program working for non-Q words and then deal with this special case at the end.

4.4 AutoPlayers

Start working on these classes only after completing WordOnBoardFinder.

We recommend that your auto players subclass AbstractAutoPlayer. If you do, there are three important details. First, after creating a new auto player, be sure to call the inherited method initialize to initialize various internal data structures. Second, the first line of your findAllValidWords method should be a call the inherited

7

method clear, which will set the autoplayer's score to zero and then clear any words found in a previous game. Third, when you find a word, call the inherited add method to add it your auto player's word list. You may find the class AutoPlayerMain very helpful when debugging. It creates a small board, runs an autoplayer on it, and then prints out the words that are found. The board is small enough that with pencil and paper, you should be able to simulate your code running on this example. The auto players will decide whether a word is a valid English word by consulting a lexicon. We provide SimpleLexicon, which takes care of managing the lexicon. SimpleLexicon is an implementation of the ILexicon interface. You should review the documentation of this interface so that you can use it effectively in your code.

Lexicon First The idea behind this strategy is quite simple: for every word in the lexicon, see if it occurs on the board. The ILexicon interface has an iterator method, allowing you to iterate over the words in the lexicon. Given the hard work you put into WordOnBoardFinder, writing this class should be easy.

Board First Rather than iterating over every every word in the dictionary you can use the board to generate potential words. For example, taking the board in the example screenshot, you might start with "I" in the upper left corner and try to generate all possible words starting from that position, trying "IR", "IRS", "IRSR", etc. Clearly, no word begins with "IRSR" and you can use the features of the ILexicon interface to eliminate exploring such dead-ends. In particular, look closely at the method wordStatus. Rather than a boolean return value, it returns a LexStatus, which indicates whether the string is either (a) a word, (b) a prefix of a word, or (c) neither a word, nor a prefix. (The LexStatus class is an Enumeration, which is explained in the textbook, Appendix A, p. 872.) The approach you take here should somewhat similar to WordOnBoardFinder and you will likely want to have a similar recursive helper method. The key difference is that instead of "growing" a single word, the goal here is to grow all possible sequences of letters and for each sequence, check to see if it's a word in the lexicon. Because of this difference, the helper method may have different parameters than the one in WordOnBoardFinder.

8

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download