6
\$\begingroup\$

Preamble: I am very new to Python, and outside of Googling functions and a former work colleague telling me why I'm wrong, I have no formal training and haven't taken any "Learn Python" courses.

I have written a CLI Hangman game which selects a random word from a ~7000-line list of words (in another file called wordlist.txt), and you have to guess the word within a set number of attempts.

I would really appreciate some input into how I can follow normal Python coding practices better; my experience is in other languages so I tried my best to follow general coding standards but beyond that I just followed what PyCharm suggested I should improve - I am not looking for improvements in things like functionality; I am more looking for improvements in code style and best practices.

Below is my code. There is another file which this is called from (if you select the correct menu item), Menu.py, hence no Hangman() at the end:

import os


class Hangman(object):
    input_letter = []
    word_to_guess = []
    DIR_PATH = os.path.dirname(os.path.realpath(__file__))
    DICTIONARY = os.path.join(DIR_PATH, "wordlist.txt")
    FILE = open(DICTIONARY, "r")
    list_of_words = []
    mutable_hidden_word = []
    count = 0
    game_complete = False
    wrong_guesses = []
    number_of_guesses = 0
    HANGED_MAN = {
        0: "     \n    |      \n    |      \n    |      \n    |       \n    |      \n    |\n____|________\n",
        1: "     _______\n    |      \n    |      \n    |      \n    |       \n    |      \n    |\n____|________\n",
        2: "     _______\n    |/      \n    |      \n    |      \n    |       \n    |      \n    |\n____|________\n",
        3: "     _______\n    |/      |\n    |      \n    |      \n    |       \n    |      \n    |\n____|________\n",
        4: "     _______\n    |/      |\n    |      (_)\n    |      \n    |       \n    |      \n    |\n____|________\n",
        5: "     _______\n    |/      |\n    |      (_)\n    |       |\n    |       |\n    |      \n    |\n____|________\n",
        6: "     _______\n    |/      |\n    |      (_)\n    |      \\|\n    |       |\n    |      \n    |\n____|________\n",
        7: "     _______\n    |/      |\n    |      (_)\n    |      \\|/\n    |       |\n    |      \n    |\n____|________\n",
        8: "     _______\n    |/      |\n    |      (_)\n    |      \\|/\n    |       |\n    |      / \n    |\n____|________\n",
        9: "     _______\n    |/      |\n    |      (_)\n    |      \\|/\n    |       |\n    |      / \\\n    |\n____|________\n"
    }

    def __init__(self):
        print "Welcome to Hangman!"
        self.reset_all()
        self.add_words_to_list()
        self.get_random_word(self.list_of_words, self.read_word_list_length(self.DICTIONARY))
        self.FILE.close()
        self.create_hidden_word(self.mk_string(self.word_to_guess, ""))
        while not self.game_complete:
            os.system('clear')
            self.play_game()
        play_again = raw_input("Play again? (y/n)\n> ").lower()
        if play_again == "y":
            Hangman()


    # SETUP FUNCTIONS
    def reset_all(self):
        self.word_to_guess = []
        self.wrong_guesses = []
        self.mutable_hidden_word = []
        self.game_complete = False
        self.count = 0
        self.number_of_guesses = 0
        self.input_letter = []


    def add_words_to_list(self):
        if not self.list_of_words:
            for line in self.FILE:
                self.list_of_words.append(line)


    @staticmethod
    def read_word_list_length(fileName):
        with open(fileName) as f:
            i = -1
            for i, l in enumerate(f, 1):
                pass
        return i


    @staticmethod
    def mk_string(word, inbetween=""):
        return inbetween.join(word)


    def get_random_word(self, listOfWords, wordListLength):
        from random import randint
        randomWord = randint(0, wordListLength-1)
        word = listOfWords[randomWord].replace("\n", "")
        for char in word:
            self.word_to_guess.extend(char)


    def create_hidden_word(self, wordToHide):
        for char in range(0, len(wordToHide)):
            self.mutable_hidden_word.append("_")


    # GAMEPLAY FUNCTIONS
    def play_game(self):
        if "_" in self.mutable_hidden_word:
            self.print_hanged_man()
            self.print_hidden_word()
            self.print_wrong_guesses()
            self.take_player_guess()
            self.check_player_guess(self.input_letter[0])
            self.number_of_guesses += 1
            if self.count == 9:
                self.print_hanged_man()
                self.print_hidden_word()
                self.print_wrong_guesses()
                print "You lose!"
                print "Correct word: " + self.mk_string(self.word_to_guess)
                self.game_complete = True
        else:
            self.print_hanged_man()
            self.print_hidden_word()
            print "Congratulations, you win! Your word was \"%s\". It took you %d guesses to win."\
                  % (self.mk_string(self.word_to_guess), self.number_of_guesses)
            self.game_complete = True


    def take_player_guess(self):
        player_input = raw_input("\nInput a letter: >>  ")
        if len(player_input) > 1:
            self.take_player_guess()
        else:
            self.input_letter.insert(0, player_input)


    def check_player_guess(self, guess):
        if guess not in self.mk_string(self.word_to_guess):
            self.count += 1
            self.wrong_guesses.append(guess)
        else:
            for letter in range(0, len(self.mk_string(self.word_to_guess))):
                if self.mk_string(self.word_to_guess)[letter] == guess:
                    self.mutable_hidden_word[letter] = guess

    def print_hidden_word(self):
        print self.mk_string(self.mutable_hidden_word, " ")


    def print_wrong_guesses(self):
        if not self.wrong_guesses == []:
            print "Wrong guesses: " + self.mk_string(self.wrong_guesses, ", ")


    def print_hanged_man(self):
        try:
            print self.HANGED_MAN[self.count]
        except KeyError:
            print "Dictionary key is invalid!"
\$\endgroup\$
1
\$\begingroup\$

2 notes :

1) add more docstrings for comments from

def check_player_guess(self, guess):
    if guess not in self.mk_string(self.word_to_guess):
        ...

to

def check_player_guess(self, guess):
    """what does it do? params"""
    if guess not in self.mk_string(self.word_to_guess):
        ...

take it as a good practise though i must congratulate you on your judicious namings

2) use @classmethod will reduce some bloats

from

def play_game(self):
    if "_" in self.mutable_hidden_word:
        self.print_hanged_man()
        self.print_hidden_word()
        self.print_wrong_guesses()
        self.take_player_guess()
        self.check_player_guess(self.input_letter[0])
        self.number_of_guesses += 1

to

@classmethod
def play_game():
    ...
        print_hanged_man()
        print_hidden_word()
        print_wrong_guesses()
        take_player_guess()
        check_player_guess(self.input_letter[0])
        number_of_guesses += 1

if @classmethod was also used on them (i.e print_ ...)

\$\endgroup\$
  • 1
    \$\begingroup\$ Hey, thanks for replying! I have been reading up on @classmethod vs instance methods and while I understand their differences, I don't understand which (if either) would have any obvious benefits over the other in my scenario. Is there any real benefit of using @classmethod here over using instance methods, other than the removal of self., or is it purely an aesthetic or personal choice in this scenario? Apologies if this question has an obvious; I am still learning! \$\endgroup\$ – James Whiteley Aug 20 '18 at 9:18
  • \$\begingroup\$ @JamesWhiteley makes logic more evident \$\endgroup\$ – Abdur-Rahmaan Janhangeer Aug 21 '18 at 5:46

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.