4
\$\begingroup\$

Introduction

As a Python neophyte, I implemented the Hangman game as a practice, which operates case-insensitively on the 26 English letters.

My biggest concern at the moment is my use of bytearray to store the word, but I might have made other beginner mistakes as well. I'd like to locate them and improve accordingly.

Code

import itertools


class Hangman:

    def __init__(self, target, guesses):
        assert target.isascii() and target.isalpha()

        self.target = bytearray(target.lower(), 'utf-8')
        self.guesses = guesses
        self.display = bytearray(itertools.repeat(ord('_'), len(target)))

    def run(self):
        while True:
            print()
            print()
            print(''.join(chr(byte) for byte in self.display))
            print()
            print(f'Remaining guesses: {self.guesses}')

            guess = input('Guess a letter: ')

            if len(guess) != 1:
                print('Invalid guess.')
                continue
            elif not guess.isascii() or not guess.isalpha():
                print('Non-letter guessed.')
                continue

            guess = ord(guess)

            if self.display.count(guess) != 0:
                print('Already guessed this letter.')
                continue

            if self.target.count(guess) == 0:
                self.guesses -= 1
            else:
                self.display = bytearray(
                    (
                        guess if target_c == guess else display_c
                    ) for display_c, target_c in zip(self.display, self.target)
                )

            if self.display == self.target:
                print()
                print()
                print(''.join(chr(byte) for byte in self.target))
                print('You won.')
                break
            elif self.guesses == 0:
                print()
                print()
                print(''.join(chr(byte) for byte in self.target))
                print()
                print('You lost.')
                break


if __name__ == '__main__':
    # Merriam-Webster Word of the Day: October 2, 2020
    hangman = Hangman('pachyderm', 6)
    hangman.run()

Example session

$ python hangman.py


_________

Remaining guesses: 6
Guess a letter: a


_a_______

Remaining guesses: 6
Guess a letter: e


_a____e__

Remaining guesses: 6
Guess a letter: i


_a____e__

Remaining guesses: 5
Guess a letter: o


_a____e__

Remaining guesses: 4
Guess a letter: u


_a____e__

Remaining guesses: 3
Guess a letter: r


_a____er_

Remaining guesses: 3
Guess a letter: y


_a__y_er_

Remaining guesses: 3
Guess a letter: c


_ac_y_er_

Remaining guesses: 3
Guess a letter: h


_achy_er_

Remaining guesses: 3
Guess a letter: p


pachy_er_

Remaining guesses: 3
Guess a letter: d


pachyder_

Remaining guesses: 3
Guess a letter: m


pachyderm
You won.
\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

Use more functions

Currently, the entirety of your hangman game lies inside the run() call. You can split certain parts of it to their own functions, such as checking for win condition, asking for character input, displaying the current state etc.

Empty prints

Use \n to get a new line in console, instead of putting in empty print statements.

Checking guessed character

For strings, you can just check if the character is in string or not, since you have no need for the count of repetitions itself.

ord, chr and bytearray

They are of no use. You do not need a bytearray of ordinals, simply splitting the word into a list would be enough (not really needed though).

Type hints

With newer python, type hinting feature has also become available. Use them to assist you with the type of values for the variables.


So, your program could also be rewritten like:

class Hangman:
    def __init__(self, target: str, guesses: int):
        assert self.is_ascii_alphabetical(
            target
        ), "Provided secret word is not ascii alphabetical string."
        self.secret_word = target
        self.guesses = guesses
        self.guessed_word = ["_"] * len(target)
        self._finish = False

    def is_ascii_alphabetical(self, value: str) -> bool:
        return value.isascii() and value.isalpha()

    def display(self, real_word: bool = False):
        if not real_word:
            print(f"\n\n{''.join(self.guessed_word)}\n")
        else:
            print(f"\n\n{self.secret_word}\n")

    def read_character(self) -> str:
        while True:
            guess = input("Guess a letter: ")
            if len(guess) != 1:
                print("Invalid guess.")
                continue
            elif not self.is_ascii_alphabetical(guess):
                print("Non-letter guessed.")
                continue
            if self.is_guess_repeat(guess):
                print("Already guessed this letter.")
                continue
        return guess.lower()

    def update_guessed_word(self, character: str) -> None:
        self.guessed_word = [
            new if new == character else old
            for old, new in zip(self.guessed_word, self.secret_word)
        ]

    def is_guess_repeat(self, guess: str) -> bool:
        return guess in self.guessed_word

    def is_guess_valid(self, guess: str) -> bool:
        if guess not in self.secret_word:
            return False
        return True

    def is_game_over(self) -> bool:
        return self.guesses <= 0

    def is_game_won(self) -> bool:
        return "_" not in self.guessed_word

    def run(self):
        while not self._finish:
            self.display()
            print(f"Remaining guesses: {self.guesses}")
            guess = self.read_character()
            if self.is_guess_valid(guess):
                self.update_guessed_word(guess)
            else:
                self.guesses -= 1
            if self.is_game_won():
                self.display(True)
                print("You won.")
                self._finish = True
            elif self.is_game_over():
                self.display(True)
                print("You lost.")
                self._finish = True


if __name__ == "__main__":
    # Merriam-Webster Word of the Day: October 2, 2020
    hangman = Hangman("pachyderm", 6)
    hangman.run()
\$\endgroup\$
1
  • \$\begingroup\$ Thanks. I guess I am still not used to thinking in Python, since I must have subconsciously rejected using a list of strings as somehow inefficient. \$\endgroup\$
    – L. F.
    Oct 4, 2020 at 1:26

Your Answer

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

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