CS21 Lab 6: Typing Tutor

Due 11:59pm Thursday, 25 October

Run update21, if you haven't already, to create the cs21/labs/06 directory. Then cd into your cs21/labs/06 directory and create the python program for lab 6 in this directory (handin21 looks for your lab 6 assignments in your cs21/labs/06 directory):

$ update21
$ cd ~/cs21/labs/06
$ pwd

Your programs are graded on both correctness and style. Please review the comments regarding programming style on the main page.

For this lab we will continue using the Zelle graphics library. We will also introduce the while loop and continue to practice writing functions.

Typing Tutor

In this lab you will write a graphical typing tutor, in the file typingTutor.py, in which a player attempts to stop falling letters (all lowercase) from hitting the bottom of the screen by typing those letters on the keyboard. The goal of this lab is to give you more practice using functions, as well as give practice using an indefinite loop.

As in Lab 05, the key data in your program will be a list. However this time the list will be of Text objects, with one Text object for each letter currently on the screen. You will use an indefinite loop to create Text objects (with each Text object representing one letter) at random locations near the top of the screen, and animate the letters as they gradually drop towards the bottom of the screen. The player earns points by typing letters on the keyboard; when the player types a letter on the screen, you should undraw the letter and stop animating the letter.

Here are some screen shots of my sample solution as I am playing:

The start:A few seconds later:The letters are falling:
Ooooh, I typed a letter:The letters are getting low:Game over:

Below we suggest several functions that you should write to help implement your game. As usual, you should write these functions and then extensively test them in main. After you know that they work, you can remove your test code and write the main function of your program.

1. Create a letter

Write a function createLetter(window) that creates a new letter. It should take the following parameter:

  1. window: A GraphWin object.
Your createLetter function should create a new letter (which is a Text object) at a random location near the top of the window and draw it in the window. You should set the string for the Text object to be the single character you wish to show on the screen.

This function should return the letter (Text object) it creates.

Test your function by creating a window and an (initially) empty list of letters in main, use createLetter to create new letters, and append those returned letters/Text objects to the list.

Hint: You can use the randrange function from the random library to get a random integer from a given range. To use this, you can put the statement:

    from random import randrange
near the top of your program. To get a random character, you can use the choice function from the random library. To use this, you can put the statement:
    from random import choice
near the top of your program. Your code to get a random character could look something like this:
ch = choice("abcdefghijklmnopqrstuvwxyz")

2. Moving Letters

Text objects have a move method which works just like the move method for Circle objects. That is, the move method takes two arguments, a dx and dy, which will move the letter dx pixels in the x direction and dy units in the y direction. We will have our letters drop straight down towards the bottom of the screen so we will not have to modify the x coordinate of the letters once they have been created. Once you are able to create and move letters, use a long for loop in main and Python's sleep function (in main) to animate the letters so they fall gradually toward the bottom of the screen.

At this point you should have some code in main that looks something like this:

   long for loop:
      create a letter
      add it to list of letters
      move all letters just a pixel or two down

To use the sleep function you will need to import it from the time library:

    from time import sleep

3. Testing for the end of the game

The game ends when any of the letters hit the bottom of the screen. To help determine this, write a function nonePastBottom(window, letters) that takes two parameters:

  1. window: A GraphWin object.
  2. letters: A list of Text objects representing untyped letters on the screen.
The nonePastBottom function should return True if all letters are still above the bottom of the screen but return False if any letters reach (or go beyond) the bottom of the screen. To get the position of a Text object, you can call the getAnchor() method on the object. It returns a Point object that you can use to test whether the letter has gone past the bottom of the screen.

Once you've written nonePastBottom, test it in main by using it as the condition in an indefinite (while) loop, ending the game when any letters reach the bottom of the screen (i.e., replace the long for loop with an indefinite loop).

4. Typing Letters

When a user types a letter, you should undraw that letter and remove it from the list of visible letters so that it is no longer animated and no longer ends the game if it hits the bottom of the screen.

To help determine if a letter has been typed, write a function matchLetters(letters, character) that takes two parameters:

  1. letters: the list of letters (as Text objects) currently on the screen.
  2. character: a one character string representing a letter that the user has typed.
The function matchLetters should return an integer that is an index of the letters list and is the index of first letter that matches the character argument. In other words, if the letters list has 3 letters in it, we would return a "0" if the first letter matched, a "1" if the second letter matched, and "2" if the last letter in the list matched. If there is no match, the function should return a -1 which represents the fact that none of the letters in the list matched the character.

As usual, test your matchLetters function inside main before continuing your implementation. To test this, you might consider using <window>.getKey() or <window>.checkKey(). The method getKey is similar to getMouse but instead of returning the Point the user clicked, getKey will wait until the user presses a key on the keyboard and returns the key the user pressed as a one character string. checkKey will also return a string of the last key pressed but not wait for the user to press a key. If the user hasn't pressed a key since the last call, this function will return None.

5. Putting it all together

Once you've implemented and tested the above functions, you should have some list of letters that are animated down the screen in an indefinite loop, with the game ending when any letters hit the bottom of the screen. You can turn this into a game as follows:

  1. Near the top of the loop, occasionally use the createLetter function to add a letter to the screen. You can do this by using an accumulator variable to count the number of times the loop has run and adding a call to createLetter, e.g., every 100 times through the loop.
  2. Use the <window>.checkKey() function as described in section 4 to learn about key presses from the user. If a key has been pressed since the last check, checkKey returns a string of the key pressed. If there has not been a key pressed, checkKey returns None.
  3. If a user presses a key, use the matchLetter method to check if the character matches any of the visible letters on the screen. Remember, matchLetter gives you the index into the letters list if there is a match. If matchLetter matches a letter, undraw the letter and remove it from the letters list in the game. Hint: you can remove an item from a list by using its index into the list with the <list>.pop() method. <list>.pop(i) deletes the ith element of the list and returns its value. Below is an example of removing an item from a list.
  4.   >>> list = ['one', 'two', 'three']
      >>> list
      ['one', 'two', 'three']
      >>> list.pop(1)
      >>> list
      ['one', 'three']
  5. Add a Text object that displays the score of the game. To keep track of the score, you'll need an additional accumulator variable that you increment when the player successfully types a letter, and use the accumulator to help update the Text output of the score.
Here are a couple of suggestions for ways to extended this game.
  1. Deduct a number of points from the user's score if they press the wrong key (i.e., they press a key for a letter that is not visible on the screen).
  2. Making the game harder as the user's score increases. There are a number of ways to make the game harder. You can increase the rate of how often letters appear on the screen, increase the speed of the letters traveling down the screen, reduce the size of the font, shift the color of the letters closer to the background color, or any combination of the above.
  3. Upper case letters are more distinct and easier to see on the screen. Make the game display upper case letters, but still accept lower case letters as input.
  4. You can warn the user they are about to lose by changing the color of the letters to red when they get below a certain point, say the lower quarter of the screen.
  5. Add a pause key to the game. You can have your game pause and unpause by pressing the space bar.
Once you are satisfied with your programs, hand them in by typing handin21 at the Linux prompt.

You may run handin21 as many times as you like, and only the most recent submission will be recorded. This is useful if you realize, after handing in some programs, that you'd like to make a few more changes to them.