CS21 Lab 7: TextTwist Top down design

Design is due March 23 by 11:59pm

Full implementation is due March 30 by 11:59pm

Goals

The goals for this lab assignment are:

  • Practice using Top-Down Design

  • Write a complex program, with multiple functions, from scratch

Notes

Please read through the entire lab before starting!

This is a two-part lab, split over two weeks. For the first week you will work with a partner and focus on using top-down design to create the overall structure for the program. If you have a partner in mind, you can email tdd@cs.swarthmore.edu and CC your partner to let us know of your preferred pairing. It will work best if your partner is in the same lab section. If you do not have a partner when you start lab, we can help assign one to you.

Once your proposed structure has been reviewed and approved by your professor or lab instructor, you will individually use bottom-up implementation to complete the full program.

You have two weeks to complete the full program. The first week is for the design, and the second for the full program. Your initial top-down design is due this Saturday (23 March) and the full implementation the following week (30 March). It is highly recommended that you submit your top-down design as soon as is possible so that we have time to give you feedback before you begin your full implementation. If you submit your design on the last day at midnight, it might take us a few days to get to it and send you comments.

Please read the guidelines for lab partnerships for information on how to best collaborate with a partner on lab assignments.

Getting and Working on Lab07 Code

Make sure you are working in the correct directory when you edit and run lab 07 files.

Run update21 to get the starting point code of the next lab assignment. This will create a new ~/cs21/labs/07 directory for your lab work. Edit and run Lab 07 programs from within your cs21/labs/07 directory.

Make sure all programs are saved to your cs21/labs/07 directory! Files outside that directory will not be graded.

$ update21
$ cd ~/cs21/labs/07
$ pwd
/home/username/cs21/labs/07
$ ls
(should see your program files here)

Then edit and run program files in your ~/cs21/labs/07 directory:

$ code filename.py
$ python3 filename.py

1. TextTwist

The game TextTwist is a word game where the player is given a set of six letters and must create as many words as possible from those letters. In this lab, you will design and implement a text-based version of TextTwist. The file /usr/local/doc/twist_puzzles.txt contains a list of puzzles, with each line being a six letter word that you can shuffle to start the game. The file /usr/local/doc/twist_words.txt contains a list of words, one per line, that you can use to check if a guessed user word is valid.

The game begins by picking a word at random from the puzzle file. As a motivating example, suppose the word is "cherub". The game then displays the letters of the puzzle in random order and asks the user to enter as many words as they can find using those letters. For example, the user might enter "cub", "curb", "herb".

The game continues until the user either enters the string "q" to quit, or the user finds all the words. The game should display a status board showing:

  1. The number of words found

  2. The user’s score

  3. The words found so far

  4. Blanks spaces for the words that have not been found yet

Initially, a sample status may look like the output shown below. Your output can look different, but should display the same key features:

---     ---     ---     ---     ---
---     ---     ---     ---     ---
----    ----    ----    ----    ----
----    ----    ------
Score: 0	 Words found: 0

Your letters are: HUBCER
Enter a word (q to quit, t to shuffle):

After the user enters "cub", the status board may look like this:

Enter a word (q to quit, t to shuffle): cub
---     cub     ---     ---     ---
---     ---     ---     ---     ---
----    ----    ----    ----    ----
----    ----    ------
Score: 3	 Words found: 1

Your letters are: HUBCER
Enter a word (q to quit, t to shuffle):

1.1. Shuffling the initial set of letters

In the example above, the puzzle consists of the letters in the word "cherub". The game should shuffle the letters to start so the user has to unscramble the letters to find the word "cherub". You can use the shuffle function from the random module to shuffle the letters, but only if the letters are in a list. You can convert a string txt to a list of characters using list(txt). The shuffle function shuffles the list in place. You can convert the list back to a string using "".join(lst), where lst is the list of characters.

1.2. Generating the word list

The file /usr/local/doc/twist_words.txt contains a list of words, one per line, that you can use to check if a guessed user word is valid. You should read the file into a list of words at the start of the game. Once you have picked a puzzle word, e.g., "cherub", you will need to use the list of words in the word file to create a new list of possible words you can spell with the letters in the puzzle. Give a word word and puzzle puzzle, you can check if word can be spelled with the letters in puzzle if every letter in word appears at least as many times in puzzle as it does in word. For example, the word "cub" can be spelled with the letters in "cherub", because the letters "c", "u", "and "b" all appear at least once in "cherub". However, the word "cheer" cannot be spelled with the letters in "cherub", because the letter "e" appears twice in "cheer" but only once in "cherub".

Hint: you can use the count method of a string to count the number of times a letter appears in a string. For example, "butter".count("q") returns 0, and "butter".count("t") returns 2.

For each word in the word file check if you can spell the word with the letters in the puzzle. If you can spell the given word, add it to a list of possible words. In pseudocode this might look like:

# determine if a given word can be spelled with the letters in the puzzle
# word: a string representing a word
# puzzle: a string representing the puzzle
# okword: indicator/flag to indicate if the word can be spelled

set okword to yes/true

for each letter in word
    count the number of times letter appears in the word (n1)
    count the number of times letter appears in the puzzle (n2)
    if n1 is bigger than n2
        you cannot spell this word. update okword to no/false

# outside of the for loop above

if okword is yes/true
    add the word to the list of possible words

The code above only checks one word against the puzzle. You should repeat this process for each word in the word file. You can then use this list to check if the user’s guess is valid. The words in the word file are sorted first by length, then in alphabetical order.

1.3. Keeping track of words found

In addition to keeping track of all possible words you can make with a given puzzle, you should also keep track of the words the user has found so far. You can use a list to keep track of the words the user has found so far. Initially, this list should replace each word in the list of possible words with a string of dashes of the same length. For example, if the list of possible words is ["cub", "curb", "herb"], the list of words found so far should be ["---", "---", "---"]. As the user finds words, you should replace the corresponding string of dashes with the word the user found. For example, if the user finds the word "cub", the list of words found so far should be ["cub", "---", "---"].

You can use the in operator to check if a user’s guess is in the list of possible words. For example, "cub" in ["cub", "curb", "herb"] is True, but "cheer" in ["cub", "curb", "herb"] is False.

1.4. Keeping score

For each correct word the user finds, they get points equal to the length of the word. For example, if the user finds the word "cub", they get 3 points. If the user finds the word "herb", they get 4 points. The game ends when the user finds all the words or enters "q" to quit. The user’s score is the sum of the points for each word found.

1.5. Checking for valid input

Your program should check if a user response is valid. A word is valid if:

  1. It is in the list of possible words

  2. It is at least 3 letters long

  3. It has not already been found

The strings "q" and "t" are also valid. If the user enters "q", the game should end. If the user enters "t", the game should re-shuffle the puzzle letters and display the new shuffled set of letters. If the user enters anything else, the game should display an error message and ask the user to enter a new word.

1.6. Ending the game

The game ends when the user finds all the words or enters "q" to quit. If the user finds all the words, the game should display a message congratulating the user and displaying their score. If the user enters "q", the game should display the final score and also show the list of all possible words the user could have found.

2. Example Game

3. Requirements

You have some freedom in how you want your game to look. Here are our requirements for the full program:

  • As part of your top-down design, your main function should have a loop that plays the game. The body of this loop should contain more than just a call to one or two functions. Instead, it should contain multiple function calls and other code that implements various parts of a single user turn.

  • The program should print an introduction once describing the basic rules of the game.

  • The program should use the files above to choose a random puzzle and get the list of legal words.

  • The player should be prompted to take their turn with an informative prompt.

  • Players should be able to enter words in lowercase or uppercase. You can convert a string s to all uppercase letters using s.upper(). This string method creates and returns a new uppercase string since strings are immutable.

  • Legal words entered by the player should be scored properly and the score should be displayed to the player.

  • The player’s updated score should be displayed regularly.

  • If a player enters an invalid response, they should be given an informative message. Recall that illegal response are:

    • words that cannot be spelled using the letters in the puzzle at most once

    • words that contain letters that aren’t in the puzzle

    • words that are less than 3 letters long

    • words that aren’t in the dictionary

    • words that have already been guessed. The strings "q" and "t" are valid exceptions to this rule.

  • When the game ends, display a status message saying how many points the player got and if they found all the words. If the player quits, display the words they could have found.

  • All output should be clean and easy to read.

3.1. Top-Down Design Requirements

You should complete your top-down design (TDD), submit it (run handin21), and obtain feedback on it before beginning the full game implementation. Special procedures for this two-week lab:

  • create your design in design_twist.py first

  • after you have a working design (see example below), run handin21 to turn it in! Then send a short email to tdd@cs.swarthmore.edu, letting us know your design is done. We will take a look at each design and send you comments (usually within a day or two). If the design is incomplete or insufficient, you may be asked to submit a second design.

  • after you have the design done and have heard back from us, please copy the design file to twist.py and implement the full game in twist.py (i.e., leave design_twist.py as is!)

    cp design_twist.py twist.py
  • please ensure your design meets the following requirements before submitting:

    • main() should be completely written, and should perform high-level steps without focusing on details

    • main() should call the functions you create in your design, in the appropriate order, and with arguments that match parameters. Return values should be saved in main(), even if you don’t do anything with them yet.

    • all functions should be stubbed out with parameters, a block comment, and a return statement. They don’t need to actually do anything yet, except possibly call other functions.

    • if your function is meant to return something, you should return a reasonable value of the appropriate type (e.g., return 0 for an int, or [1,2,3] for a list).

    • your design should have several functions. Each function should be function worthy (i.e., not a trivial task) and also demonstrate encapsulation (one clearly defined purpose).

    • the design should run without syntax errors (although it doesn’t play the full game yet)

Your goal for the design is to completely write main(), such that it won’t need to change much as you implement and test all of the other functions.

If you are confused about how the top-down design should look, please see the following examples of a design and full program for a simple guessing game program.

4. Helpful Methods/Functions

Here are some functions/methods you might find helpful in your implementation.

4.1. Random functions

To use these functions, import them at the top of your program, before the definition of main. Only import the function you plan on using

from random import choice, shuffle, randrange
  • choice(lst) returns a random element from the list lst

  • shuffle(lst) shuffles the list lst in place. Note this only works for lists, not strings. It also shuffles the list in place, so you don’t need to assign the result to a new variable.

  • randrange(start, stop) returns a random integer in the range start to stop (exclusive)

4.2. File I/O functions

  • open(filename, "r") opens the file filename in for reading. For example, f = open("words.txt", "r") opens the file words.txt for reading and assigns the file object to the variable f.

  • f.readlines() reads all the lines in the file f and returns a list of strings, one for each line in the file. Note: each line will have a newline character at the end, so you may need to strip this off using the strip method (see below).

  • f.close() closes the file f. You should always close a file after you are done reading from it.

See catfile.py and flashcards_soln_tia.py in your inclass/w07-tdd folder for examples

4.3. String methods

  • s.upper() returns a new string with all the characters in s converted to uppercase

  • s.lower() returns a new string with all the characters in s converted to lowercase

  • s.count(c) returns the number of times the character c appears in the string s

  • sep.join(lst) returns a new string that is the concatenation of all the strings in lst, separated by the string sep. For example, " ".join(["a", "b", "c"]) returns "a b c". In this case, the separator is a space (sep = " ").

  • s.strip() returns a new string with any leading or trailing whitespace removed. For example, " hello ".strip() returns "hello".

  • s * n repeats the string s n times. For example, "-" * 5 returns "-----".

  • len(s) returns the number of characters in the string s.

See strings_and_lists.py in inclass/w08-more-tdd for examples.

4.4. List methods

  • lst.append(x) adds the element x to the end of the list lst. Note: this method modifies the list in place, so you don’t need to assign the result to a new variable.

  • lst.count(x) returns the number of times the element x appears in the list lst.

  • lst[pos] = item modifies the element at position pos in the list lst to be item.

  • len(lst) returns the number of elements in the list lst.

  • item in lst returns True if item is in the list lst, and False otherwise.

  • list(s) returns a list of all the characters in the string s.

See strings_and_lists.py in inclass/w08-more-tdd for examples.

4.5. Submitting your design

After you have a working design (see below), you and your partner should both run handin21 to turn it in! After running handin21, send an email to tdd@cs.swarthmore.edu, letting us know your design is done. We will take a look at each design and send you comments (usually within a day or two). If the design is incomplete or insufficient, you may be asked to submit a second design.

After you have the design and have heard back from us, copy the file to twist.py, e.g., cp design_twist.py twist.py, or use "Save As" in Visual Studio Code. Then implement the full program. Leave design_twist.py as it is. Work by yourself for the implementation portion of this lab.

You may copy your design to your partner using scp. First, make sure your partner has run update21. Then, with your partner sitting nearby, open a Terminal and cd to your cs21/labs/07 folder, then copy the file as follows:

cd
cd cs21/labs/07
scp design_twist.py  partner@cslab.cs.swarthmore.edu:cs21/labs/07

Replace the word partner above with your partners login ID, e.g., adanner1. You partner will be prompted for their password. Once your partner types their password, scp should copy the file from your account to your partners. Be careful that you are copying from the completed design computer to avoid overwriting a more up to date design on your partner’s account.

5. Answer the Questionnaire

After each lab, please complete the short Google Forms questionnaire. Please select the right lab number (Lab 07) from the dropdown menu on the first question.

Once you’re done with that, you should run handin21 again.

Submitting lab assignments

Remember to run handin21 to turn in your lab files! You may run handin21 as many times as you want. Each time it will turn in any new work. We recommend running handin21 after you complete each program or after you complete significant work on any one program.

Logging out

When you’re done working in the lab, you should log out of the computer you’re using.

First quit any applications you are running, including your vscode editor, the browser and the terminal. Then click on the logout icon (logout icon or other logout icon) and choose "log out".

If you plan to leave the lab for just a few minutes, you do not need to log out. It is, however, a good idea to lock your machine while you are gone. You can lock your screen by clicking on the lock xlock icon. PLEASE do not leave a session locked for a long period of time. Power may go out, someone might reboot the machine, etc. You don’t want to lose any work!