Tuesday, August 31, 2010

UCT Python Course - Part 3

What I really wanted to discuss about the Python course when I started these blog posts was a really cool experience I had when I set a couple of rowdy students a challenging but fun task. On the last day of the course I noticed two groups of students who were throwing paper around and looking generally bored. So I decided to be a bit proactive and manage the situation so that they weren't a distraction to the other students.

In order to assess the progress of the one group I asked them to implement a simple email spam proofing scheme where they have to replace "@" with " AT " and "." with " DOT ". Of course the students responded that they didn't want to do it now, however after a bit of coaxing I got them to do it. I fully expected them to struggle a bit with this task but I was pleasantly surprised to find they had no real trouble completing it and easy produced something like:
print 'email_address@domain.co.za'.replace(
  '@', ' AT ').replace('.', ' DOT ')

So this is where things get interesting. I had been joking with the other tutors about giving some of the advanced students hangman to do as a challenge (I assume everyone is familiar with the game). So I decided to get this group of students to do it instead. Now I was a bit worried that it was too difficult a problem for them - but then the gears in my head started turning and I began thinking of how I could break the problem down into a set of simpler problems or steps if you prefer.

So to start off I asked them to create the ASCII art of the final "hanged man". After much grumbling (the students thought I was punishing them for being noisy for some reason) I got them to start creating the man in their python file - this is when they started to get excited! It ended up looking something like this: 
|   |
|   O
|  /|\
|  / \
|
---------

Once they had the man drawn in the file without any code I asked them to run it. At first they were a bit confused why the program didn't work but soon figured out they had to add print statements and with a bit of help they got the character escaping working as well (Yes I know we should have used raw strings but they didn't work properly in the Wing IDE). So their program now looked something like this:

print '------   '
print '|   |    '
print '|   O    '
print '|  /|\\  '
print '|  / \\  '
print '|        '
print '---------'

Now that the students could draw the final version I asked them to create all the intermediate diagrams from gallows to the final diagram. After that I pointed out that we only want do display one diagram and asked them if there was any way to decide which version of the diagram to display. After a bit of discussion they suggested using if statements. I suggested they use a variable called incorrectGuess which they could update to choose which diagram to display. I also showed them how to turn it into a function called drawMan, since they were a bit vague on how functions worked I thought this would be a good chance to re-enforce the concept. I illustrated how raw_input worked and explained that it would be nice not to have to rewrite all those if statements everytime we wanted to draw the man - we could instead create a special program that did just that. After a bit of help the produced the following drawMan function:

def drawMan(incorrectGuess):
  if incorrectGuess == 0:
    print '------   '
    print '|        '
    print '|        '
    print '|        '
    print '|        '
    print '|        '
    print '---------'
  elif incorrectGuess == 1:
    print '------   '
    print '|   |    '
    print '|        '
    print '|        '
    print '|        '
    print '|        '
    print '---------'
#--------------------------- SNIP -------------------------

  elif incorrectGuess == 6:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|  /|\\  '
    print '|  /     '
    print '|        '
    print '---------'

  elif incorrectGuess == 7:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|  /|\\  '
    print '|  / \\  '
    print '|        '
    print '---------'
I ask them to test the function to see if it produced the correct diagrams when called with different incorrectGuess values and I think this really served to tie together the whole concept of a function as I think they finally understood why we use them.
drawMan(0)
drawMan(3)
drawMan(7)
Now that they had a function to do the drawing we had to start working on implementing the game logic - since we were running low on time I decided we could take a couple of shortcuts and hard code some values.

Firstly they created a variable to hold the word that had to be guessed. Next I explained how we could use lists (tried to use all the tools they were taught in this example) to keep track of all the letters that had been guessed, as well as, all the correct letters. I also explained how strings were a special type of list were the items were letters. With a bit of help and a reminder about the len function they were able to produce the following code to keep track of how well the player was doing:

word = 'introduction'
guessedLetters = []
correctLetters = []
incorrectLetters = 0
while incorrectLetters < 8 and len(correctLetters) < 8:
  incorrectLetters = len(guessedLetters) - len(correctLetters)
  drawMan(incorrectLetters)
  guess = raw_input('Enter a letter: ')
  if guess not in guessedLetters:
    guessedLetters.append(guess)
  if guess in word and guess not in correctLetters:
    correctLetters.append(guess)
    print 'Yes'
  else:
    print 'No'
  print correctLetters

Along the way I made some suggestions about adding print statements so they could see what was going on and after a couple of attempts the above was produced. Unfortunately we were now quite low on time - about 3 minutes left in the session - so I let them get away with hardcoding the 'len(correctLetters) < 8' condition instead of calculating the number of unique letters in the word (Yes in hindsight this is not the easiest way to do things I should have just made them keep track of the missing letters but it was a long weekend, I was under a bit of time pressure and was making it up as I went along).

So for all intents they had a working hangman game - the only thing that was really missing was drawing the word that they had to guess with underscores for the hidden characters. I debated whether to let them complete that at home but decided I wanted them to have something they could take away, show to their friends and get excited about. So I decided to help them write the last method and this is what I managed to come up with in the minute I had left:
def drawGuess(word, correctLetters):
  for i in word:
    if i in correctLetters:
      print i,
    else:
      print '_',
  print ''
Putting everything together we get a very simple but COOL hangman game:
def drawMan(incorrectGuess):
  if incorrectGuess == 0:
    print '------   '
    print '|        '
    print '|        '
    print '|        '
    print '|        ' 
    print '|        '
    print '---------'
  elif incorrectGuess == 1:
    print '------   '
    print '|   |    '
    print '|        '
    print '|        '
    print '|        ' 
    print '|        '
    print '---------'
  elif incorrectGuess == 2:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|        '
    print '|        ' 
    print '|        '
    print '---------'
  elif incorrectGuess == 3:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|   |    '
    print '|        ' 
    print '|        '
    print '---------'
  elif incorrectGuess == 4:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|  /|    '
    print '|        ' 
    print '|        '
    print '---------'
  elif incorrectGuess == 5:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|  /|\\  '
    print '|        ' 
    print '|        '
    print '---------'
  elif incorrectGuess == 6:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|  /|\\  '
    print '|  /     ' 
    print '|        '
    print '---------'
  elif incorrectGuess == 7:
    print '------   '
    print '|   |    '
    print '|   O    '
    print '|  /|\\  '
    print '|  / \\  ' 
    print '|        '
    print '---------'

def drawGuess(word, correctLetters):
  for i in word:
    if i in correctLetters:
      print i,
    else:
      print '_',
  print ''

word = 'introduction'
guessedLetters = []
correctLetters = []
incorrectLetters = 0
while incorrectLetters < 8 and len(correctLetters) < 8:
  incorrectLetters = len(guessedLetters) - len(correctLetters)
  drawMan(incorrectLetters)
  drawGuess(word, correctLetters )
  guess = raw_input('Enter a letter: ')
  if guess not in guessedLetters:
    guessedLetters.append(guess)
  if guess in word and guess not in correctLetters:
    correctLetters.append(guess)

Now of course there are far better ways to code the above but I think it was an amazing effort for kids who had never programmed before this weekend. Unfortunately I didn't have time to set them any extensions but I think I would have liked to suggest adding in a list of words and picking one at random instead of hardcoding 'introduction'. This would also mean fixing up all the other hardcoded values (looping condition for example). Another good idea would be to print out all the letters which had not been tried yet, I also think adding some sort of score table, perhaps with a multiplayer round-robin style format would be a great challenge question. I just hope the students feel confident enough to mess around with the example and have a bit of fun because in the end that is all this is really about.

Till next time ...

2 comments:

  1. unique_letters = len(set(word))

    ReplyDelete
  2. Yip that works, but I didn't want to fry their brains :)

    ReplyDelete