2
\$\begingroup\$

This program is yet to become user-friendly and is still in development, but I wanted to improve the current code.

from random import choice
from os import system

clear = lambda: system('cls') # FOR WINDOWS ONLY

words = [i.strip() for i in open('words.txt').readlines()]

# Possible stages
STAGES = [
"""






    -------
""",

"""
    _______
    |   |
    |
    |
    |
    |
    -------
""",

"""
    _______
    |   |
    |   O
    |
    |
    |
    -------
""",

"""
    _______
    |   |
    |   O
    |   |
    |
    |
    -------
""",


"""
    _______
    |   |
    |   O
    |  /|
    |
    |
    -------
""",

"""
    _______
    |   |
    |   O
    |  /|\\
    |
    |
    -------
""",

"""
    _______
    |   |
    |   O
    |  /|\\
    |  /
    |
    -------
""",

"""
    _______
    |   |
    |   O
    |  /|\\
    |  / \\
    |
    -------
"""]

class HangMan:
    def __init__(self):
        self.current_stage = 0                              # The stage in which the hangman is
        self.letters = [chr(i) for i in range(97, 97 + 26)] # Possible characters
        self.word = choice(words)                           # Random word
        self.current_word = ['_'] * len(self.word)          # The letters which the player has unlocked till now

    def get_input(self) -> int:
        """ Takes the player's input and processes it """

        print(*self.letters)
        print()

        char = input('Please enter a letter from the above set: ')

        while char.lower() not in self.letters:
            print(f'Invalid letter: {char}\n')
            char = input('Please enter another letter: ')

        print()
        return self.process_char(char)

    def process_char(self, given_char: str) -> int:
        """

        Processes the input and returns 0, 1, or 2

        0 -> Win
        1 -> Neither a win nor a loss
        2 -> Lose

        """

        self.letters.remove(given_char.lower())
        unlocked = 0

        for index, char in enumerate(self.word):
            if char.lower() == given_char.lower():
                self.current_word[index] = char
                unlocked += 1

        if '_' not in self.current_word:
            print('You\'ve won!')
            return 0

        if not unlocked:
            print('Whoops! Wrong letter!')

            if not self.next_stage():
                clear()
                print(STAGES[self.current_stage])
                print('You\'ve lost!')
                return 2
        else:
            print(f'Yay! You\'ve unlocked {unlocked} letter{"" if unlocked == 1 else "s"}!')

        return 1

    def next_stage(self) -> bool:
        """ Returns if this stage is the last """

        self.current_stage += 1

        if self.current_stage < len(STAGES) - 1:
            return True

        return False

    def game(self) -> None:
        """ Starts the game """

        while True:
            clear() # Comment if you are not using a command console

            print('Currently unlocked letters:', ''.join(self.current_word))
            print()
            print(STAGES[self.current_stage])

            x = self.get_input()

            if x == 0 or x == 2:
                print(f'The word was {self.word}!')

            print()
            input('Press enter to continue...')

            if x == 0 or x == 2:
                return

man = HangMan()

man.game()

How do I improve this? (Make this look better, make it faster, etc.)

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Your code is good but it may be overly complex. The more concise the code the easier it is to read (for me anyway). In this example below I have just used some example words, this code of course is just a quick example for demonstration. There is not anything in place if you get the word or want to guess the word etc. I couldn't really find a reason to use a class for simpler games because although the functions are 'closely related' in your functions, comprehension can save a lot of time and code.

The while loop is useful because it gives a consistent input option which can be as user friendly as you make it. I think its essential for the users experience when making something like a text RPG.

import random

words = ['cheese', 'biscuits', 'hammer', 'evidence'] 
word_choice = [i.lower() for i in random.choice(words)]
lives = 3
cor_guess = []
while True:
  print('Letters found: {}\nWord length: {}'.format(cor_guess, len(word_choice)))
  if lives == 0:
      print('Your dead homie!\nThe word was: ', word_choice)
      break
  guess = str(input('Guess a letter: '))
  if guess in word_choice:
      print(guess, 'is in the word!\n')
      cor_guess.append(guess)
  else:
      print('Your dying slowly!\n')
      lives -= 1

Example of the output:

Letters found: []
Word length: 8
Guess a letter: v
v is in the word!

Letters found: ['v']
Word length: 8
Guess a letter: i
i is in the word!

Letters found: ['v', 'i']
Word length: 8
Guess a letter: r
Your dying slowly!

Letters found: ['v', 'i']
Word length: 8
Guess a letter: b
Your dying slowly!

Letters found: ['v', 'i']
Word length: 8
Guess a letter: o
Your dying slowly!

Letters found: ['v', 'i']
Word length: 8
Your dead homie!
The word was:  ['e', 'v', 'i', 'd', 'e', 'n', 'c', 'e']
\$\endgroup\$
1
\$\begingroup\$
STAGES = [
"""






    -------
""",

"""
    _______
    |   |
    |
    |
    |
    |
    -------
""",

...

I would remove the first line break at the top of each stage to condense it, then re-add it when printing out.

clear()

I would remove this as users' usually don't like having their terminal cleared unexpectedly.

    def next_stage(self) -> bool:
        """ Returns if this stage is the last """

        self.current_stage += 1

        if self.current_stage < len(STAGES) - 1:
            return True

        return False

This can be simplified to self.current_stage += 1 then return self.current_stage < len(STAGES) - 1.

.lower() is called four times; the user's input should be lower-cased before processing so that .lower() doesn't have to be called again. This would require self.letters = [chr(i) for i in range(97, 97 + 26)] to just have lowercase letters so that .lower() can be removed in the loop that checks if the characters are in the list containing capitals.

            print()
            input('Press enter to continue...')

            if x == 0 or x == 2:
                return

This can be removed and replaced with "Do you want to play again?" If the user enters yes, then they can otherwise not. If the functionality is that the user keeps playing until they either win or lose, then it could be different.

\$\endgroup\$
1
  • \$\begingroup\$ I agree with your last point, but as I said, the code is still in development. Great answer though! \$\endgroup\$
    – Sriv
    Commented Dec 5, 2019 at 4:59

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.