I have to write a code in Python where it is about playing the game hangman. Currently my code cannot replace the "_" with the guessed letters properly, where it only replaces one and doesn't show all of the guessed letters.

My code is below:

import random

word = random.choice(["bread", "clouds", "plane"])

guess = 0
correct_guess = 0
play_again = True

print(word)
name = input("Hello, what's your name? ")
print('Hello', name)
status = input('Do you wish to play hangman? Yes/No ')
hyphens = print('_ ' * (len(word)))

if status == 'No' 'no' 'N' 'n':
  exit()

while play_again is True:

  letter = input('Guess a letter: ')

  if letter in word:
    guess = guess + 1
    correct_guess = correct_guess + 1
    print('You guessed a letter correctly!')
    position = word.find(letter)
    print("The letter is in position", position + 1, "out of", len(word), "in the word. Guess Count:", guess)
    if position == 0:
      print(letter, "_ _ _ _")
    if position == 1:
      print("_", letter, "_ _ _ ")
    if position == 2:
      print("_ _", letter, "_ _ ")
    if position == 3:
      print("_ _ _", letter, "_ ")
    if position == 4:
      print("_ _ _ _", letter)
  else:
    guess = guess + 1
    print("That letter isn't in the word. Guess Count:", guess)
  if guess == 10:
    print('Bad luck. You lost.')
    play = input('Do you want to play again? ')
    if play == 'No' 'no' 'N' 'n':
      exit()
  if correct_guess == 5:
    print('Congratulations! You have guessed the word! The word was', word + '.')
    play = input('Do you want to play again? ')
    if play == 'No' 'no' 'N' 'n':
      exit()

Your help is very much appreciated. Also, I don't know a lot about programming and I am fairly new to it.

James

3 Answers

1
ncica On Best Solutions

you can't write expression like :

if status == 'No' 'no' 'N' 'n':

the correct way is:

if play == 'No' or play == 'no' or play == 'N' or play == 'n':

or:

if play in ['No' ,'no', 'N' ,'n']

one solution:

import random

word = random.choice(["bread", "clouds", "plane"])


print(word)
name = input("Hello, what's your name? ")
print('Hello', name)
status = input('Do you wish to play hangman? Yes/No ')


def play():
    if status == 'No' or status == 'no' or status == 'N' or status == 'n':
        print ("Goodbye")
        return # exit 
    else:
        print('_ ' * (len(word))) # _ _ _ _ _ 

    guess = 0
    correct_guess = 0
    play_again = True

    pos_list = ['_' for x in range(len(word))] # define list where you will put your guessed letters ['_', '_', '_', '_', '_']
    while play_again is True:
      letter = input('Guess a letter: ')

      if letter in word:
        guess = guess + 1
        correct_guess = correct_guess + 1
        print('You guessed a letter correctly!')
        position = word.find(letter)
        print("The letter is in position", position + 1, "out of", len(word), "in the word. Guess Count:", guess)
        pos_list[position] = letter # save letter at position # ['_', '_', '_', 'a', '_']
        print (' '.join(pos_list)) # _ _ _ a _

      else:
        guess = guess + 1
        print("That letter isn't in the word. Guess Count:", guess)
      if guess == 10:
        print('Bad luck. You lost.')
        play = input('Do you want to play again? ')
        if play == 'No' or play == 'no' or play == 'N' or play == 'n':
            print("Goodbye")
            return
        else:
             print('_ ' * (len(word)))
      if correct_guess == len(word):
        print('Congratulations! You have guessed the word! The word was', word + '.')
        play = input('Do you want to play again? ')
        if play == 'No' or play == 'no' or play == 'N' or play == 'n':
            print("Goodbye")
            return
        else:
            print('_ ' * (len(word)))

play()
0
Community On

Cleaned it up a bit, removing duplicated code. Also that problematic condition:

if status == 'No' 'no' 'N' 'n':

Changed it to:

if status.lower().startswith("n"):

Also important to lower the letter input by the user. Here a full working code.

import random
import re

def start():
    status = input('Do you wish to play hangman? Yes/No ')
    if status.lower().startswith("n"):
        return True
    # Computer chooses word
    word = random.choice(["bread", "clouds", "plane"])
    # Setup game vars
    guess = []
    game_status = ["_"] * len(word)

    while True:
        print(" ".join(game_status))
        letter = input('Guess a letter: ').lower()
        if letter in guess:
            print("You already tried that one.")
            continue
        guess.append(letter)
        if letter in word:
            print('You guessed a letter correctly!')
            positions = [m.start() for m in re.finditer(letter, word)]
            for pos in positions:
                game_status[pos] = letter
        else:
            print("That letter isn't in the word.")
        if len(guess) == 10:
            print(f'Bad luck {name}. You lost. The word was: {word}.')
            return
        if "_" not in game_status:
            print(f'Congratulations {name}! You have guessed the word! The word was {word}.')
            return

# Welcome
name = input("Hello, what's your name? ")
print('Hello', name)
while True:
    stop = start()
    if stop:
        break
print("Goodbye!")
0
Andrew Allen On

TL;DR: ' '.join(l if l in guessed_letters else '_' for l in word) is a one liner but for OP I've gone into detail.

I wanted to expand on my comment a little.

One way to write great code is to flip writing and testing code upside down and test first for code you have not written. This is call test driven development.

There are many tools for testing code such as unittest but one I'll demo here is pytest.

First we identify a small piece of functionality we'd like to implement. In this case returning a string in hangman form. This takes two inputs, the word we want to guess and the letters guessed. I want to implement this as a function so I'll write the function but put pass for now.

Now is a good time to comment the function with the inputs and expected outputs. Now is better than never.

def hangman_word(word, guessed_letters):
    """Returns a string in hangman form where letters are hyphens
    or guessed letters. Letters or hyphens are space separated.

    `word` is a string. This is the word to be printed in hangman form.

    `guessed_letters` is a list of the letters guessed that should be shown.

    For example::

        >>> hangman_word('bordereau', ['o', 'd', 'e'])
        '_ o _ d e _ e _ _'

    """
    pass

We then write tests but we do so in a deliberate manner. We want to cover different aspects that may go wrong...no guessed letters, letters that repeat, letters in a different order than the word. Each test focuses on each aspect. Pytest will run tests on functions starting with test_.

def test_hangman_word_no_guessed_letters():
    assert hangman_word('zonule', []) == '_ _ _ _ _ _'
def test_hangman_word_one_letter():
    assert hangman_word('zonule', ['n']) == '_ _ n _ _ _'
def test_hangman_word_multiple_letters_ordered():
    assert hangman_word('zonule', ['n', 'u']) == '_ _ n u _ _'
def test_hangman_word_multiple_letters_unordered():
    assert hangman_word('zonule', ['u', 'n']) == '_ _ n u _ _'
def test_hangman_word_repeated_letters():
    assert hangman_word('bordereau', ['o', 'd', 'e']) == '_ o _ d e _ e _ _'
def test_hangman_word_guessed_letter_wrong():
    assert hangman_word('hypostrophe', ['o', 'd', 'e']) == '_ _ _ o _ _ _ o _ _ e'
def test_hangman_word_multiple_repeats():
    assert hangman_word('intempestive', ['t', 'd', 'e']) == '_ _ t e _ _ e _ t _ _ e'
def test_hangman_word_all_letters():
    assert hangman_word('intempestive', list('intempestive')) == 'i n t e m p e s t i v e'

if __name__ == '__main__':
    print('main has run')

With the function hangman_word and these tests together in a file named hang_man.py if I run pytest

C:\Users\Andrew\Desktop>pytest hang_man.py

I'll get a detailed report

C:\Users\Andrew\Desktop>pytest hang_man.py
============================= test session starts =============================
platform win32 -- Python 3.4.0, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: C:\Users\Andrew\Desktop, inifile:
collected 8 items

hang_man.py FFFFFFFF                                                     [100%]

================================== FAILURES ===================================
____________________ test_hangman_word_no_guessed_letters _____________________

    def test_hangman_word_no_guessed_letters():
>       assert hangman_word('zonule', []) == '_ _ _ _ _ _'
E       AssertionError: assert None == '_ _ _ _ _ _'
E        +  where None = hangman_word('zonule', [])

hang_man.py:21: AssertionError
________________________ test_hangman_word_one_letter _________________________

    def test_hangman_word_one_letter():
>       assert hangman_word('zonule', ['n']) == '_ _ n _ _ _'
E       AssertionError: assert None == '_ _ n _ _ _'
E        +  where None = hangman_word('zonule', ['n'])

<<REMOVED>>
========================== 8 failed in 0.21 seconds ===========================

So all 8 tests fails. Good. Now we can write the code.

Now I know enough python to write a one liner so I replace pass with

return ' '.join(l if l in guessed_letters else '_' for l in word)

And when I run pytest again...

C:\Users\Andrew\Desktop>pytest hang_man.py
=========================================================================================================== test session starts ============================================================================================================
platform win32 -- Python 3.4.0, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: C:\Users\Andrew\Desktop, inifile:
collected 8 items

hang_man.py ........                                                                                                                                                                                                                  [100%]
=================== 8 passed in 0.13 seconds ===================

The point here is I have reusable code I know works because I've tested it and now I can move on to other aspects such as counting incorrect guesses and the consle input aspects.

A full code solution

I decided to implement this using an object oriented State Pattern design (for self learning purposes) so thought I'd also stick this in here. While the code is bigger than a functional style the advantage is that the code is extremely testable and nicely separates the mechanics of the game and taking input.

Running a game is simply a matter of running:

game = Context('represents', lives=15)
game.startGame()
class Context(object):
    """A class that keeps track of the hangman game context and current state.
    This follows a State Design Pattern.

    `word` is string. This is the word to guess.
    `guessed_letters` is a list containing letters guessed
    `lives` is a number that is the number of incorrect guesses allowed.
    `state` is class representing the state of the game.
    `message` is a string message from processing the letter guess.

    The :meth:`.process` method processes guessed letters, updating the
    lives, guessed_letters and message for the gameplay.
    """
    def __init__(self, word, lives=10):
        self.word = word
        self.guessed_letters = []
        self.lives = lives
        self.state = HoldingState()
        self.message = ''

    def won(self):
        """Tests if all letters of in word are in guessed_letters"""
        return all(l in self.guessed_letters for l in self.word)

    def lost(self):
        """Tests if we have run out of lives"""
        return self.lives <= 0

    def hangman_word(self):
        """Returns a string in hangman form where letters are hyphens
        or guessed letters. Letters or hyphens are space separated.

        `word` is a string. This is the word to be printed in hangman form.

        `guessed_letters` is a list of the letters guessed that should be shown.

        For example::

            >>> hangman_word('bordereau', ['o', 'd', 'e'])
            '_ o _ d e _ e _ _'

        """
        return ' '.join(l if l in self.guessed_letters else '_' for l in self.word)

    def process(self, letter):
        """Requests the state to process the guessed letter.

        The State takes care of updating the lives, guessed_letters and providing a
        message for the gameplay"""
        self.state.process(letter, self)

    def startGame(self):
        """Runs the game.

        Checks for final states (WonState / DeadState) else shows the hangman
        word, letters guessed and takes as input the letter guessed and 
        processes this.

        """
        while True:
            if self.state == WonState():
                print(f'You win! The word was indeed {self.word}')
                return
            if self.state == DeadState():
                print(f'You lose! The word was {self.word}')
                return
            print('Hangman Word:', self.hangman_word() + '\n')
            print('Letters guessed: ' + ' '.join(sorted(self.guessed_letters)) + '\n')
            letter = input('Guess a letter: ')
            self.process(letter)
            print('\n' + self.message + '\n')


class BaseState(object):
    """Represents a state of the context.

    This is an abstract base class. Subclasses must override and
    implement the :meth:`.process` method.

    The :meth:`.process` method updates the context variables and changes the
    context state.

    """
    def process(self, letter, context):
        """Updates the context variables and changes the context state..

        **This method is not implemented in this base class; subclasses
        must override this method.**

        """
        raise NotImplementedError

    def __eq__(self, other):
        """Overrides the default implementation

        Tests equality of states by testing if there are the same class

        """
        if isinstance(other, self.__class__):
            return True
        return False

class HoldingState(BaseState):
    """The default state."""
    def process(self, letter, context):
        if letter in context.guessed_letters:
            context.state = RepeatedGuessState()
        elif letter in context.word:
            context.state = CorrectGuessState()
        else:
            context.state = IncorrectGuessState()
        # Calls process again to return to HoldingState / WonState / DeadState
        context.state.process(letter, context)

class RepeatedGuessState(BaseState):
    """An intermediate state."""
    def process(self, letter, context):
        context.message = "You've repeated a guess!"
        context.state = HoldingState()

class CorrectGuessState(BaseState):
    """An intermediate state."""
    def process(self, letter, context):
        context.message = "Correct guess!"
        context.guessed_letters.append(letter)
        if context.won():
            context.state = WonState()
        else:
            context.state = HoldingState()

class IncorrectGuessState(BaseState):
    """An intermediate state."""
    def process(self, letter, context):
        context.lives -= 1
        context.message = f"Incorrect guess! You've lost a life. {context.lives} lives remaining"
        context.guessed_letters.append(letter)
        if context.lost():
            context.state = DeadState()
        else:
            context.state = HoldingState()

class DeadState(BaseState):
    """A final state representing a lost game."""
    def process(self, letter, context):
        pass

class WonState(BaseState):
    """A final state representing a game won."""
    def process(self, letter, context):
        pass

if __name__ == '__main__':
    game = Context('represents')
    game.startGame()

This passes the follwing tests (pytest):

def test_start_to_correct():
    game = Context('represents')
    game.process('r')
    assert game.state == HoldingState()
    assert game.lives == 10

def test_start_to_incorrect():
    game = Context('represents')
    game.process('i')
    assert game.state == HoldingState()
    assert game.lives == 9

def test_repeated_guess():
    game = Context('represents')
    game.process('i')
    game.process('i')
    assert game.state == HoldingState()
    assert game.lives == 9

def test_guesses():
    game = Context('word', lives=4)
    game.process('a')
    assert game.lives == 3

def test_win():
    game = Context('word', lives=4)
    game.process('w')
    game.process('o')
    game.process('r')
    game.process('d')
    assert game.state == WonState()

def test_lost():
    game = Context('wort', lives=4)
    game.process('a')
    assert game.lives == 3
    game.process('b')
    assert game.lives == 2
    game.process('c')
    assert game.lives == 1
    game.process('d')
    assert game.lives == 0
    assert game.state == DeadState()