6
\$\begingroup\$

I am teaching a Python crash course and thought a Hangman rendition would be fruitful.

I have an end product that I'll work towards with the hour that I have for the course and I was wondering what could be refactored or implemented to help the exchange of information from myself, intermediate level, to absolute beginners.

from random import choice
import os


def clear():
    os.system('clear')


def play_again():
    play_again = input("\nPlay again? Y/n ").lower()

    if play_again != 'n':
        clear()
        game()

    else:
        print("\n\n\n\nCatch ya later!\n\n\n\n")


def get_secret_word():
    words = ['angry', 'beautiful', 'brave', 'careful', 'charismatic', 'clever',
             'creative', 'cute', 'dangerous', 'exciting', 'famous', 'friendly',
             'happy', 'interesting', 'lucky', 'profound', 'popular', 'rich',
             'thin', 'young']

    secret_word = choice(words)
    return secret_word


def get_guess(bad_guesses, good_guesses, secret_word):
    while True:
        guess = input("Guess a letter: ").lower()

        if guess == '':
            print("What was that?")
            board(bad_guesses, good_guesses, secret_word)

        elif len(guess) != 1:
            input("You can only guess a single letter!")
            board(bad_guesses, good_guesses, secret_word)

        elif guess in bad_guesses or guess in good_guesses:
            input("You've already guessed that letter!")
            board(bad_guesses, good_guesses, secret_word)

        elif not guess.isalpha():
            input("You can only guess LETTERS!")
            board(bad_guesses, good_guesses, secret_word)

        else:
            return guess


def board(bad_guesses, good_guesses, secret_word):
    clear()
    print('** Strikes: {}/8 **'.format(len(bad_guesses)))
    print('')

    for guess in bad_guesses:
        print(guess, end=" ")
    print('\n\n')

    for guess in secret_word:
        if guess in good_guesses:
            print(guess, end=" ")
        else:
            print('_ ', end=" ")

    print('')


def game():
    secret_word = get_secret_word()
    good_guesses = []
    bad_guesses = []
    game_done = False

    while not game_done:
        board(bad_guesses, good_guesses, secret_word)
        guess = get_guess(bad_guesses, good_guesses, secret_word)
        if guess in secret_word:
            good_guesses.append(guess)
            found = True
            for guess in secret_word:
                if guess not in good_guesses:
                    found = False
            if found:
                print("\nYou win!\n")
                print("The secret word was {}.".format(secret_word.upper()))
                game_done = True

        elif guess not in secret_word:
            bad_guesses.append(guess)
            if len(bad_guesses) == 8:
                clear()
                print("\n **  ENGHH!  ** ")
                print("\nStrike ! You lost!")
                print("\nThe secret word was {}".format(secret_word.upper()))
                game_done = True

        if game_done:
            play_again()

        else:
            board(bad_guesses, good_guesses, secret_word)


game()
\$\endgroup\$
  • 2
    \$\begingroup\$ "I am teaching a Python crash course ..." Aaaaw! \$\endgroup\$ – πάντα ῥεῖ Apr 30 '17 at 22:48
8
\$\begingroup\$

I could come up with a laundry list of improvements. However, I would like to focus on just one issue, which makes your code an unforgivably bad example for your students: you are misusing functions as goto labels.

Note that game() can call play_again(), which in turn may call game(). This mutual recursion is inappropriate. If you play many games, then hit CtrlC, you will see that the deepening stack is keeping track of all the previous calls, uselessly preparing for resuming execution after a return.

If you want to do something repeatedly in Python, use a loop. A reasonable outline would look like this:

def play_again():
    """
    Ask the player whether to play again.
    """
    return 'n' != input("\nPlay again? Y/n ").lower()

def game():
    """
    Play a single game of Hangman.  Return True if the player wins,
    or False if the player loses.
    """
    secret_word = get_secret_word()
    good_guesses = []
    bad_guesses = []

    while True:
        board(bad_guesses, good_guesses, secret_word)
        guess = get_guess(bad_guesses, good_guesses, secret_word)
        if guess in secret_word:
            good_guesses.append(guess)
            if all(letter in good_guesses for letter in secret_word):
                print("\nYou win!\n")
                print("The secret word was {}.".format(secret_word.upper()))
                return True
        else:
            bad_guesses.append(guess)
            if len(bad_guesses) == 8:
                clear()
                print("\n **  ENGHH!  ** ")
                print("\nStrike ! You lost!")
                print("\nThe secret word was {}".format(secret_word.upper()))
                return False

def main():
    """
    Play games of Hangman until the user decides to quit.
    """
    while True:
        game()
        if not play_again():
            print("\n\n\n\nCatch ya later!\n\n\n\n")
            break
        clear()

Additional remarks:

  • Flag variables, like game_done and found, are almost always a bad idea. Here, game_done can be eliminated by using proper looping technique, and found can be determined using the all() function.
  • if guess not in secret_word is redundant. You can just say else.

Oh, and one more thing, since you should be setting a good example for your students: write a docstring for every function.

\$\endgroup\$
6
\$\begingroup\$

First of all, congrats for teaching Python! I wish there were more crash curses related to it.

Now, about your code:

  • you have loops (while / for) - ok
  • you introduce the students a data structure(lists) - ok
  • you have functions - ok
  • you have some external modules - ok
  • you have some conditionals - ok

It looks fine at the first sight. What I didn't like about your code (I supposed I don't know anything about the language):

  1. You have a function called play_again() and a variable with the same name. That might be misleading.
  2. os.system('clear') is platform specific and only works on OSX / Linux. On windows you'd use os.system('cls'). It'd be nice to comment this somewhere in your code.
  3. You didn't document any of your functions. If a student isn't paying attention (it happens a lot) and tries to repeat at home, it might not remember everything you said. Try to add docstrings to your functions.
\$\endgroup\$
5
\$\begingroup\$

Your code can have a lot of improvements... Here are what i think you could improve.

  1. You would not want to use os if it were to be a crash course. In fact i don't think os makes much of a difference in your program tbh.

  2. Nested if's. Try not to use them as much as possible as they make the code look much harder to understand and read!

  3. The validation you used for the guesses are written well however looking at it from a beginners perspective, validation may be difficult to understand don't you think? Especially in this case where there are so many things going on in your code...

  4. Ideally having more lists can be hard to track, and for such a simple game i think it is unnecessary to have a separate list for 'good_guesses' and 'bad_guesses'. You could instead maybe make a single list called 'used' and a variable called 'wrong'. 'wrong' will be the counter for how many guesses they got wrong and 'used' will store all the valid letters that they have used.

You said this was a crash course for absolute beginners... Then your ideal aim should be to 'Keep it as simple as possible!' for the student(s). Tbh i feel like you have done the opposite, BUT you can still improve on it.

PS: If you want to know good code(easy to understand and simple) for beginners you should check out a couple of Beginners Python books. They usually have a section where they talk about hangman.

I AM NOT TELLING YOU TO COPY THEM! INSTEAD GET AN IDEA OF HOW THEY TACKLE THE PROBLEM!


I may have missed out on some improvements so i apologize if i have not talked about them!

I respect that you are teaching beginners and i hope you do well! Good Luck! :)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.