A5.2: Ghost
Announcements and Clarifications
7/27 Due Date Changed to Sunday 5/30 @ 5:00pm
7/29 Updated common_words.txt to remove the word "and/or", please download the new file here
Brief Description
Your task in this assigment is to implement the game Ghost using a prefix tree. You will also implement a simple AI algorithm to construct a computer player for Ghost.
Goals and Requirements
- Implement a prefix tree
- Implement the rules of Ghost
- Use your prefix tree to construct a computer player for Ghost.
Rules
Ghost is a simple two player word game. Each turn a player must pick a letter. The sequence of letters selected since the start of the game must form the beginning of an English word. If a player chooses a letter that completes an English word, that player loses. If a player chooses a letter that prevents any word from being formed, that player loses. See below for several example plays.
Implementation
All of the design decisions are up to you. You may implement your prefix tree however you choose, however before starting consider the important operations in the problem space. Remember that a prefix tree is a multiway tree where each node stores a character. Paths from the root to a leaf is a word stored in the tree. Paths from the root to an internal node is a prefix of a word stored in the tree.
The game will consist of two players, a human player (entering letters on the console) and a computer player. The computer player will operate in one of two possible modes. In the first mode the computer player will simply select some random letter between A and Z for each of its moves. In the second mode, the computer player will select moves that cause it to win. More information on how to implement an intelligent algorithm for the computer player see the next section.
The initial configuration of the game will be set using command line arguments:
java Ghost dictionary_file first_player comp_mode
The first argument is the dictionary file which is a list containing all the "valid" English words (this file need not contain all English words, but a large subset of them). A sample file is available here (source). The second argument describes who goes first, if 1 is passed the human, if 0 is passed the computer plays first. The final argument is the computer's AI mode. If 0 is passed the computer chooses moves randomly, if 1 is passed the computer uses the algorithm we describe later.
A several sample runs of the game are shown below:
wolf(15)%java Ghost common_words.txt 0 1 Computer goes first Computer's choice: N Current prefix: N Enter letter choice: i Computer's choice: A Current prefix: NIA Enter letter choice: c Computer's choice: I Current prefix: NIACI Enter letter choice: n Completed word NIACIN, you lose.
wolf(15)% java Ghost common_words.txt 1 1 Player goes first Current prefix: Enter letter choice: J Computer's choice: O Current prefix: JO Enter letter choice: g Computer's choice: G Current prefix: JOGG Enter letter choice: L Computer's choice: E Completed word JOGGLE, computer loses.
wolf(16)% java Ghost common_words.txt 1 1 Player goes first Current prefix: Enter letter choice: K Computer's choice: H Current prefix: KH Enter letter choice: e Computer's choice: D Current prefix: KHED Enter letter choice: d No word begins with the letters KHEDD, you lose.
AI Algorithm
The algorithm that you will be implementing is from a family of algorithms called min-max algorithms. The idea behind min-max algorithms is to make your moves based on considering the moves an optimal opponent (one that always makes the right move) would make in response. The algorithm classifies moves as winning moves or losing moves. The description of the algorithm to classify moves is recursive in nature:
- A move is considered a winning move for player A if it is player A's turn and there exists a move such that all choices for player B's next move are losing moves for player B.
- A move is considered a losing move for player A if it is the player A's turn and every move allow player B to make at least one move that is winning move for player B.
- If a player's move completes a word or produces a prefix that begins no words in the dictionary, that move is a losing move.
- A winning move for player A is a
losing move for player B and vice versa.
This algorithm is naturally applicable to prefix trees because each node in the prefix tree represents a choice in the game. Your algorithm will recurse through your data structure alternating turns. Each time the computer must make a choice, you should run the algorithm on the 26 possible choices for the computer's move. If any of the choices is a winning move, the computer is guaranteed to win the game and should make that move. If the computer has no winning moves, the computer should randomly select a possible move. If the computer has multiple winning moves it should select one of them at random.
For example, consider the following dictionary of three words:
AND ANTLER COW
Suppose the computer was playing first, the computer needs to decide whether to choose A or C first. If the computer choose C the human must choose O, then the computer will choose W and lose. So choosing C is a losing move. Suppose the computer choose A, the human player must choose N. Now the computer has a choice between D and T. If the computer selects D it loses, if the computer selects T eventually the human player will select R and lose, this means that T is a winning move for the computer. This means A is a winning move for the computer. The computer chooses A for its first move.
Commenting and Style
- Your program should be written in a style that makes it easy to read and understand.
- At the beginning of each .java file you should include a description of the class and how it interacts with the other parts of your program.
- You are not required to use javadoc style comments.
- If your code is doing something complex or non-standard please comment that portion heavily.
Handin
Please hand all necessary files into your handin directory in a subdirectory named Ghost. Your application class should be called Ghost.java. There should be exactly one class declared within each .java file. If your program does anything strange (bugs), awesome(extra features) or has a non-intuitive interface please include a file called README.txt which explain them. If there are bugs in your program but you do not describe them in your README you will lose more credit than if you had described them.
Hints
- Develop incremently
- Implement prefix tree
- Implement game with two humans
- Implement random computer player
- Implement min-max algorithm
- The data set is fairly large (~50k words), your program should run in a reasonable amount of time considering the data set. If it takes more than 5 seconds to load the prefix tree or to determine computer moves, you are probably doing something inefficiently. Remember bubble sort from A4 took a long time to run on just 30k words. O(n^2) algorithms are too slow for this application.
- Make a small dictionary file of nonsense words to test the functionality of your prefix tree. You might want to apply the visualization procedure from A5.1 to check correctness of your tree's construction.
- If you get the program working correctly on the sample dictionary, the computer should always win if it plays first (and often if plays second).
- The sample dictionary may have some strange words in it though it should not effect whether your program works or not. All words will only contain the characters A through Z.
- Some of the words in the dictionary file may be prefixes of other words in the dictionary file. For example "shore" is a prefix of "shoreline". In the game Ghost, "shoreline" will never be a valid word because the game would have been over once "shore" was spelled. This means data need only be stored at the leaves of your prefix tree.
- There are a number of choices for how you implement the multi-way branching of your prefix tree. You can store references to children in a linked list, an array, an ArrayList or a digital tree. Feel free to use Java collection classes to assist in this aspect.