1
\$\begingroup\$

Q1. Can this code be simplified/ made more efficient?

Q2. Why does the following code have to be indented in the way that it is? used.append(guess) and so_far = new

Q3. Out of interest, is there a way to print the hangman figure once and just replace the previous figure, to avoid mass text/ reloading in the Python Shell?

The Following code has been slightly adapted from Python Programming for the absolute beginner - Hangman Game

#Python 3.4.3, MAC OSX (Latest)
#Hangman Game

import random
from time import sleep

HANGMAN = (
"""
 ------
 |    |
 |
 |
 |
 |
 |
 |
 |
----------
""",
"""
 ------
 |    |
 |    O
 |
 |
 |
 |
 |
 |
----------
""",
"""
 ------
 |    |
 |    O
 |   -+-
 | 
 |   
 |   
 |   
 |   
----------
""",
"""
 ------
 |    |
 |    O
 |  /-+-
 |   
 |   
 |   
 |   
 |   
----------
""",
"""
 ------
 |    |
 |    O
 |  /-+-/
 |   
 |   
 |   
 |   
 |   
----------
""",
"""
 ------
 |    |
 |    O
 |  /-+-/
 |    |
 |   
 |   
 |   
 |   
----------
""",
"""
 ------
 |    |
 |    O
 |  /-+-/
 |    |
 |    |
 |   | 
 |   | 
 |   
----------
""",
"""
 ------
 |    |
 |    O
 |  /-+-/
 |    |
 |    |
 |   | |
 |   | |
 |  
----------
""")

WORDS = ("APPLE", "ORACLE", "MIMO", "TESLA")
word = random.choice(WORDS)
POSITIVE_SAYINGS = ("Well done!", "Awesome!", "You Legend!")
MAX_WRONG = len(word) - 1
so_far = ("-") * len(word)
used = []
wrong = 0

print("\t \t Welcome to Hangman!")
print()
input("Press Enter to START: ")

while wrong < MAX_WRONG and so_far != word:
    print()
    print(HANGMAN[wrong])
    print("Word so far: ", so_far)
    print("Letters used: ", used)

    guess = input("Guess a letter: ").upper()
    sleep(1) # Time delay - allows userfriendly reading
    print()

    while guess in used:
        print("Try again... You've already used this letter")
        guess = input("Guess a letter: ").upper()
        sleep(1)
        print()
    used.append(guess)

    if guess in word:
        print(random.choice(POSITIVE_SAYINGS),"...Updating word so far...")

        new = ""
        for i in range(len(word)):
            if guess == word[i]:
                new += guess

            else:
                new += so_far[i]
        so_far = new 

    else:
        print("INCORRECT! Try again!")
        wrong += 1

print("Calculating result...")
sleep(1)
if wrong == MAX_WRONG:
    print("UNLUCKY! Better luck next time!")

else:
    print("WINNER! Congratulations!")

print()
print()
input("Press Enter to Leave: ")
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

This sure looks like a fun game!

So far, it's pretty well constructed, so I just have some suggestions:

To answer your questions...

Q1. Can this code be simplified/ made more efficient?

Yes. I recommend putting it in a class for simplicity. As far as efficiency goes, the largest running time is O(n), so it is already efficient. It might be possible to simplify it to allow the max running time to be O(log n), but I don't know how.

Class version:

# Whenever possible, only import specific functions from the library
from random import choice
from time import sleep


class Hangman:
    """
    A Hangman class.
    """
    _HANGMAN = (
        """
         ------
         |    |
         |
         |
         |
         |
         |
         |
         |
        ----------
        """,
        """
         ------
         |    |
         |    O
         |
         |
         |
         |
         |
         |
        ----------
        """,
        """
         ------
         |    |
         |    O
         |   -+-
         | 
         |   
         |   
         |   
         |   
        ----------
        """,
        """
         ------
         |    |
         |    O
         |  /-+-
         |   
         |   
         |   
         |   
         |   
        ----------
        """,
        """
         ------
         |    |
         |    O
         |  /-+-/
         |   
         |   
         |   
         |   
         |   
        ----------
        """,
        """
         ------
         |    |
         |    O
         |  /-+-/
         |    |
         |   
         |   
         |   
         |   
        ----------
        """,
        """
         ------
         |    |
         |    O
         |  /-+-/
         |    |
         |    |
         |   | 
         |   | 
         |   
        ----------
        """,
        """
         ------
         |    |
         |    O
         |  /-+-/
         |    |
         |    |
         |   | |
         |   | |
         |  
        ----------
        """)

    _WORDS = ("APPLE", "ORACLE", "MIMO", "TESLA")
    _POSITIVE_SAYINGS = ("Well done!", "Awesome!", "You Legend!")

    def __init__(self):
        """
        The Python constructor for this class.
        """
        self._word = choice(self._WORDS)
        self._so_far = "-" * len(self._word)
        self._used = []
        self._wrong_answers = 0

    def play(self):
        """
        This is the main driver of the game.
        Plays the game.
        """
        self._reset_game()
        self._start_game()

        # The amount of incorrect answers should be no greater than the length
        # of HANGMAN.
        #
        # Use the length of HANGMAN to ensure there's no index
        # overflow error when printing current progress.
        while self._wrong_answers < len(self._HANGMAN) and self._so_far != self._word:
            self._print_current_progress()
            guess = self._user_guess()
            self._check_answer(guess)

        self._print_result()
        self._play_again()

    # ---------------------------------
    # "Private" methods

    def _check_answer(self, guess):
        """
        Checks to see if the user's guess is correct.
        :param guess: User's guess
        """
        if guess in self._word:
            print(choice(self._POSITIVE_SAYINGS), "...Updating word so far...")

            for i in range(len(self._word)):
                if guess == self._word[i]:
                    # so_far is spliced this way:
                    # so_far [from the start : up until, but not including the
                    #     position of the correctly guessed letter]
                    # + guessed letter
                    # + so_far [from the position next to the
                    #     correctly guessed letter : to the end]
                    self._so_far = self._so_far[:i] + guess + self._so_far[i+1:]

        else:
            print("INCORRECT! Try again!")
            self._wrong_answers += 1

    def _play_again(self):
        """
        Asks the user if he or she would like to play again.
        If the user wants to play again, calls play().
        Otherwise, thanks the user for playing.
        """

        print("Would you like to play again?")
        user_input = input("Enter Y for yes or N for no: ").upper()

        if user_input == "Y":
            self.play()

        else:
            print()
            print("Thank you for playing!")

    def _print_current_progress(self):
        """
        Prints the current progress of the game.
        """
        print()
        print(self._HANGMAN[self._wrong_answers])
        print("Word so far: ", self._so_far)
        print("Letters used: ", sorted(self._used))

    def _print_result(self):
        """
        Prints the result (win or lose).
        """
        sleep(1)
        print()
        print("Calculating result...")
        sleep(1)
        print()
        if self._wrong_answers == len(self._HANGMAN):
            print("UNLUCKY! Better luck next time!")
        else:
            print("WINNER! Congratulations!")

    def _reset_game(self):
        """
        Resets the game by calling the constructor.
        """
        self.__init__()

    def _start_game(self):
        """
        Starts the game by printing an introduction
        and asks the user to hit the ENTER key to continue.
        """
        print()
        print("\t\tWelcome to Hangman!")
        print()
        input("Press Enter to START:")

    def _user_guess(self):
        """
        Asks for the user to guess a letter.
        :returns: User's guessed letter.
        """
        guess = input("Guess a letter: ").upper()
        sleep(1)  # Time delay - allows user friendly reading
        print()

        while guess in self._used:
            print("Try again... You've already used this letter")
            guess = input("Guess a letter: ").upper()
            sleep(1)
            print()

        self._used.append(guess)

        return guess

if __name__ == '__main__':
    game = Hangman()

    game.play()

This is also available on secret Gist


If you want to just focus on the method part, you can see the method version by clicking on my Github Gist link


Q2. Why does the following code have to be indented in the way that it is?
used.append(guess) and
so_far = new

From the code in your question, used.append(guess) needs to be outside of the while loop or the guessed letter will be added to the used list repeatedly as long as the while condition is true.

so_far = new needs to be outside of the for loop for the same reason. In my suggested code, you will have no need to use the new variable because string splicing is used instead.


Q3. Out of interest, is there a way to print the hangman figure once and just replace the previous figure, to avoid mass text/ reloading in the Python Shell?

Currently, I don't know. I'll keep it in my mind and let you know if I find a way.


Update:

In one of your comments below, you asked if the code above was OOP. The answer is kind-of, but no. I didn't define a model or the logic in a separate entity. However, I did create a Gist link that is the OOP version of the code.

You can click on this Gist link to see the OOP version.

\$\endgroup\$
16
  • \$\begingroup\$ Excellent answer. I'd like to add that Q3. Yes. There is a way by printing ANSI codes for cursor control. \$\endgroup\$
    – omgimanerd
    Commented Jul 26, 2017 at 9:32
  • \$\begingroup\$ @sometowngeek Thank you for this excellent and thorough answer. I am yet to learn about def functions and classes in my 'Python journey' thus far. These kind of answers is what makes this platform great. Once I've got my laptop I'm going to study your code and learn what it's doing! \$\endgroup\$
    – Greg
    Commented Jul 26, 2017 at 18:57
  • \$\begingroup\$ @Greg There are upsides and downsides of using Python. One of the downsides is strong-typing is not possible, so you have to do the extra work in order to ensure the correct data type is used. \$\endgroup\$ Commented Jul 26, 2017 at 20:23
  • \$\begingroup\$ No worries. I'm more than happy to help. :) \$\endgroup\$ Commented Jul 26, 2017 at 20:24
  • 1
    \$\begingroup\$ @Greg This link has an alternate version of my answer which is more geared towards OOP. I can update my original answer to reflect that if you'd like. \$\endgroup\$ Commented Aug 18, 2017 at 1:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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