CS21 Lab 5: More functions, more fun

Due Saturday, March 20, before midnight (US/Eastern local to Swarthmore)

Programming Tips

  • Use a comment at the top of the file to describe the purpose of the program (see example).

  • All programs should have a main() function (see example).

  • Use variable names that describe the contents of the variables.

  • Write your programs incrementally and test them as you go. This is really crucial to success: don’t write lots of code and then test it all at once! Write a little code, make sure it works, then add some more and test it again.

  • Don’t assume that if your program passes the sample tests we provide that it is completely correct. Come up with your own test cases and verify that the program is producing the right output on them.

  • Avoid writing any lines of code that exceed 80 columns. In emacs, at the bottom, center of the window, there is an indication of both the line and the column of the cursor.

Are your files in the correct place?

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

$ update21
$ cd ~/cs21/labs/05
$ pwd
/home/username/cs21/labs/05
$ ls
Questions-05.txt
(should see your program files here)

Goals

The goals for this lab assignment are:

  • Work with functions and lists

  • Work with mutable and immutable function parameters

  • Work with functions that return values

  • Work with functions done only for their side effect

1. checkpoint

At the end of your lab session, be sure to run handin21.

Your lab instructor will check your files, which should reflect that you have made non-trivial progress (in python or pseudocode) towards the solution. Note that if you have not made much progress towards your solution, we expect that you would have been actively seeking help during your lab over Slack.

10% of your lab grade is dependent on this checkpoint.

If there are circumstances which prevent you from making substantial progress on this lab, please contact your lab instructor as soon as possible.

2. Lingo

In the file lingo.py you will write several functions to implement a secret word guessing game called Lingo. Lingo is a turn-based guessing game. The computer selects a random five letter word and the player tries to guess the word. Each guess must also be a valid five letter English word (this is what makes the game interesting and hard). After a guess is made, the computer provides status information about how close (or how far) the player is to arriving at the solution. The status information includes which letters are correct, but in the wrong position, and which letters are correct, and in the right position.

The rules of lingo are described on this Lingo Instructions and Example Gameplay page.

You are required to implement and use functions for this lab, as we describe below. We provide one function for you, getWords() you should import for the cs21s21 library. This function takes no input parameters and returns a list of valid five-letter English words. See this example:

$ python3
>>> from cs21s21 import getWords
>>> allwords = getWords()
>>> allwords[402]
'blobs'

You must write the following functions, and your implementation must match the specifications given here, but you are welcome to write additional helper functions if you’d like.

The instructions are a little bit long; both the rules for Lingo and the specification for your program. Make sure you read through the whole thing (and you understand it) before trying to get started!

2.1. Incrementally develop main

Start by creating a main function that will serve as the primary control center of our program. For now you’ll do two things here:

  • Write a printIntro() function and call it from within main. The printIntro() function takes in no input parameters and returns no value. It simply prints instructions about the game to the user.

  • Make a call to the getWords() function and save the return value to a variable that will be useful throughout the program.

As you add each function to your program, you will be using main to test them, so your main function will go through many iterations before you get to the final version. This is normal.

2.2. Make the Secret word

Now write a genSecretTarget(allWords) function. This should take one input parameter (the list of valid words) and return a string containing the (randomly chosen) secret target word for the current run of the game.

2.3. Getting user guesses

Now write a getGuess(allWords) function. This function gets a valid guess from the user (prompting them as many times as necessary) and returns the guessed string. A user’s guess is valid if it appears as one of the five letter words in the list of five letter words in the input parameter allWords. Alternatively, a user may type QUIT in all caps as a valid guess.

Notice that you can (and should!) test the behavior of this function right away, before you implement the rest of the program.

2.4. Updating the status

Here you’ll write three functions:

  • printStatus(guess, secret)

  • exactMatch(statusList, secretList)

  • inexactMatch(statusList, secretList)

For the printStatus(guess, secret) function: The input parameters guess and secret for this function are both strings. guess should be a valid five-letter guess and secret should be the secret five-letter target words that the player is trying to guess. This function should compute the exact and inexact matches and print the current status to the screen. Note: this function should leverage the other helper functions and should not be particularly long itself. It should not return anything.

Notice in the examples that we want to show users the status before they’ve made any actual guesses. You can do this using printStatus, but it’s a bit tricky. Instead, you can just to print "word: - - - - -" at the very start. If you’ve finished up the lab and want to come back to this, try (as optional extra fun things to do) using the printStatus function instead.

For the exactMatch(statusList, secretList) function: given two lists of five characters each, statusList and secretList, compute the exact matches between the two lists by converting exact matches in statusList to UPPERCASE and replacing the corresponding match in secretList with a .. This function will modify the mutable lists statusList and secretList, but will not return anything.

For the inexactMatch(statusList, secretList) function: given two lists of five characters each, statusList and secretList, compute the inexact matches between the two lists by replacing the corresponding match in secretList with a .. If a lowercase letter in statusList is not an inexact match, replace this letter with a -. This function will modify the mutable lists statusList and secretList, but will not return anything.

Computing Exact Matches

The exactMatch and inexactMatch functions are two of the more technical functions to write in this lab. Both take a list of characters as input parameters and modify that list to update the status. Let’s look at exactMatch(statusList, secretList) in more detail. Suppose we have a guess of cloud, and secret word ghoul. These strings need to be converted to lists statusList and secretList as shown in the table below in the before column. The exactMatch function needs to check each position in the statusList and determine if the letter in the corresponding position in secretList matches. In this example, both the o and u are exact matches. Your function, according the specification above should uppercase the o and u in statusList. You must also replace the corresponding matches in secretList with a .. By replacing the characters in secretList with dots, we are essentially removing them from the secret list (we’ll see how to more literally remove from a list later in the course), so they cannot be counted again as inexact matches later. Note in the after column in the table below, only the lists change.

variable before after

secret

"ghoul"

"ghoul"

guess

"cloud"

"cloud"

statusList

['c', 'l', 'o', 'u', 'd']

['c', 'l', 'O', 'U', 'd']

secretList

['g', 'h', 'o', 'u', 'l']

['g', 'h', '.', '.', 'l']

Computing Inexact Matches

After computing exact matches, we reuse the modified statusList and secretList to compute inexact matches as follows: We consider each character in statusList sequentially. For each character ch in statusList, we first check if ch is upper-case. If it is, it must have been an exact match and we can ignore it for inexact matching. If it is lower case, we check if ch appears anywhere in secretList. If the answer is no, we can replace the position in statusList containing ch with a - indicating this letter was neither an exact match nor an inexact match. If ch does appear in secretList however, we do not need to modify statusList at all. We do need to find some location in secretList containing a ch and replace this location with a . so that we do not attempt to match this letter in secretList with some other letter in statusList.

For our particular example of a secret word ghoul and guess cloud, this algorithm would first look at the c in cloud. There is no c in gh..l, so we replace the c with a -. The next letter l does appear in gh..l so we need to replace the l in secretList with a . to indicate that the l can no longer match other letters in the guess. The next two letters O and U are upper-case exact matches, so there is no need to update either statusList or secretList. Finally, there is no matching d, so our final statusList is ['-', 'l', 'O', 'U', '-']. We could later use the characters in this list to print the status to the user, but without all the [''] syntax.

variable before after

statusList

['c', 'l', 'O', 'U', 'd']

['-', 'l', 'O', 'U', '-']

secretList

['g', 'h', '.', '.', 'l']

['g', 'h', '.', '.', '.']

Converting Strings

At some point you will need to convert a string type into a list of characters. Just like we used int(), float(), and str() to convert from one type into another, we can use list(txt) to convert the string variable txt into a list.

txt="hello"
lst = list(txt)
print(lst)
['h', 'e', 'l', 'l', 'o']

You will also need to convert characters to upper case. You have two options here:

  • You can use some ord() and chr() tricks to write a toUpper(ch) function.

  • You can use python’s built in upper() method. We will talk about methods and objects soon, but for now, you can just copy/paste the following syntax: <var>.upper() will compute the upper case version of any string variable <var>

ch = "h"
up = ch.upper()
print(up)
H

text = "hello"
up = text.upper()
print(up)
HELLO

Beyond upper(), here are some built-in Python functions that will likely be useful to you. You are not obligated to use all (or even most!) of these, but you may find them quite helpful:

<str>.isupper()

<str>.islower()

<list>.index()

<str>.join()

<str>.upper()

<str>.lower()

<str>.find()

random.choice() but remember to import random

2.5. Putting it all together

We’re almost done now. The fully finished lingo.py program might have the primary steps in main() as shown below. Think about how to use the helper functions you’ve just implemented to accomplish each step.

  1. Print out general instructions to the user

  2. Pick a secret word for the user to guess

  3. Show the initial status of the game,- - - - -

  4. Ask the user for an initial guess

  5. Until the user guesses the secret word or types QUIT:

    • show the status of the current guess

    • keep track of the number of turns

    • get another guess

  6. Show the total number of turns needed to win, or show the secret word if the user decided to quit.

It is very important that you think carefully about how each of your functions is being used. Each of the steps in the main function that we have outlined should only need a few lines of code – the functions handle most of the complexity – thus the entirety of your main function should be relatively short (less than 25 lintes total for instance).

Additionally, it is incredibly important that you use incremental development. The outline of main is a good starting point - you should complete one step at a time, thoroughly test it to see if your program works, and only move on after getting it to work. We will ask you to backtrack and do incremental development if you do not follow this strategy. If you try to implement the whole program at once, you will waste a lot of time and energy trying to fix your program.

Two final points to help you test and debug as efficiently as possible: * It may be helpful to print out the secret word at the beginning when testing, or other sorts of messages to check the internal state of the program at different points – but remember to remove this code when you are finished and submit the lab. * Similarly, I would consider initially having the program 'hardcode' in a particular secret word so that it’s simple to try evaluating exact and inexact matches without having to think about what letters those should include. You should make sure that you’re output matches the output in the examples for instance! Just remember to remove anything you’d initially hardcoded when you are finished and ready to submit the lab.

3. Answer the questionnaire

Each lab will have a short questionnaire at the end. Please edit the Questions-05.txt file in your cs21/labs/05 directory and answer the questions in that file.

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

4. Computer Players — optional

This is an optional extra challenge. This part does not affect your grade so please only attempt this after completing the rest of your lab. It is simply an extra challenge, if you want to try it.

There are lots of fun extensions you could add to lingo.py

  • Implement a computer player who will make guesses instead of a user.

  • Modify your main function to run your newly automated lingo.py program many times and see, on average, how quickly the computer opponent can solve the same.

  • Anything else you can think of? Let us know if you come up with something interesting!

Turning in your labs…​.

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.