2

I'm creating a simple hangman game program where the secret word is selected from a large dictionary (300,000+ words). I'm testing the code with a much smaller dictionary, and one of the things that I want to filter out are any words that contain uppercase letters. I've been trying to do that, but currently my code will only filter out some words that contain or are fully uppercase.

Here's the code:

class Hangman(object):
    def __init__(self, level=5, non_ascii=False, dictionary='ezdict.txt', allow_uppercase=False, non_alpha=False):

        global os
        import os
        import random

        self.level = level
        self.score = 0
        self.loss = 6
        self.wordbank = []
        self.used_letters = []
        self.game_dict = []

        self.empty_gallows = " _____\n |   |\n |\n |\n |\n |\n |\n |\n_|_________\n|/       \|\n|         |"
        self.first_gallows = " _____\n |   |\n |   O\n |\n |\n |\n |\n |\n_|_________\n|/       \|\n|         |"
        self.second_gallows = " _____\n |   |\n |   O\n |   |\n |\n |\n |\n |\n_|_________\n|/       \|\n|         |"
        self.third_gallows = " _____\n |   |\n |   O\n |  /|\n |\n |\n |\n |\n_|_________\n|/       \|\n|         |"
        self.fourth_gallows = " _____\n |   |\n |   O\n |  /|\\\n |\n |\n |\n |\n_|_________\n|/       \|\n|         |"
        self.fifth_gallows = " _____\n |   |\n |   O\n |  /|\\\n |  /\n |\n |\n |\n_|_________\n|/       \|\n|         |"
        self.sixth_gallows = " _____\n |   |\n |   O\n |  /|\\\n |  / \\\n |\n |\n |\n_|_________\n|/       \|\n|         |"
        self.gal_list = [self.empty_gallows, self.first_gallows, self.second_gallows, self.third_gallows, self.fourth_gallows, self.fifth_gallows, self.sixth_gallows]

        with open(dictionary, 'r') as file:
            self.words = file.read().split('\n')

        for i in self.words:
            if (len(i) <= self.level) and (len(i) > 0):
                self.game_dict.append(i)

        if non_ascii == False:
            for i in self.game_dict:
                try:
                    i.encode('ascii')
                except UnicodeEncodeError:
                    self.game_dict.remove(i)

        if non_alpha == False:
            for i in self.game_dict:
                if i.isalpha() != True:
                    self.game_dict.remove(i)

        if allow_uppercase == False:
            for i in self.game_dict:
                if any(letter.isupper() for letter in i):
                    self.game_dict.remove(i)

        self.secret_word = random.choice(self.game_dict)

        for i in list(self.secret_word):
            self.wordbank.append('_')

        os.system('clear')

    def __play_again(self):
        play_again = input("Would you like to play again? Type 'yes' or 'no': ")
        if play_again == 'yes':
            Hangman().play()
        elif play_again == 'no':
            os.system('clear')
            exit
        else:
            print('Please check your input and try again.')
            self.__play_again()


    def play(self):  # Think about implementing an option to choose the word
        print(self.game_dict)  # For Testing
        print(self.gal_list[self.score])
        print('\n', ' '.join(self.wordbank))
        print('\nPREVIOUSLY GUESSED LETTERS:', ' '.join(self.used_letters))

        self.__user_input()

    def __user_input(self):
        print(self.secret_word)  # For testing
        self.guess = ''
        while self.guess == '':
            self.guess = input('Guess a letter: ')
            if len(self.guess) != 1:
                os.system('clear')
                print('Please guess a single letter!')
                self.play()
            elif self.guess in self.used_letters:
                os.system('clear')
                print('This letter has already been guessed.')
                self.play()
            elif len(self.guess) == 1:
                if self.guess in self.secret_word:
                    for i in range(len(self.secret_word)):
                        if self.guess == self.secret_word[i]:
                             self.wordbank[i] = self.guess
                            #print(self.wordbank)  # For testing
                else:
                    self.score += 1
            self.used_letters += self.guess
            win_check = ''
            for i in self.wordbank:
                win_check += i
            if win_check == self.secret_word:
                os.system('clear')
                print("You've won the game!")
                self.__play_again()
            elif self.score == self.loss:
                os.system('clear')
                print("Sorry, you've lost this round...")
                print('The secret word was ' + str(self.secret_word) + '.')
                self.__play_again()
            else:
                os.system('clear')
                self.play()


h = Hangman()
h.play()

The part that is causing issues is this:

if allow_uppercase == False:
    for i in self.game_dict:
        if any(letter.isupper() for letter in i):
            self.game_dict.remove(i)

The text file contains these words, contained in a file called 'exdict.txt':

twinkle
little
star
how
wonder
what
you
are
up
above
the
world
so
high
like
diamond
in
sky
ABC
DEF
XYZ
HIK
bourrées
günter
contraction's
bob's
o'connell

For the uppercase letters, it seems to only check every other word in the list, but I am completely stumped. It should filter out all words with uppercase characters in them, but half of them always remain. This can be seen when it prints out the list of usable words (self.game_dict) upon starting the game.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
bpryan
  • 51
  • 1
  • 10

1 Answers1

2

You are modifying the data structure while iterating over which leads to a completely incorrect loop:

Never alter the container you're looping on, because iterators on that container are not going to be informed of your alterations and, as you've noticed, that's quite likely to produce a very different loop and/or an incorrect one.

Instead, re-define the game_dict filtering out non-desired items:

if allow_uppercase == False:
    self.game_dict = [item for item in self.game_dict
                      if not any(letter.isupper() for letter in item)]
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • Thank you, this makes sense! However, I'm still a bit confused by the syntax. Where in my code would I re-define 'game_dict'? – bpryan Dec 13 '18 at 03:39
  • @bpryan sure, just added one more line - should make it a bit more clear :) – alecxe Dec 13 '18 at 03:40
  • This worked, but now the 'if non_alpha == False' is not working properly, aka game_dict suddenly contains words with non-alpha numeric characters that were previously being filtered out. Sorry to be so clueless... – bpryan Dec 13 '18 at 03:47
  • @bpryan yeah, you basically have the same exact problem in other loops and need to apply the same idea for them as well. – alecxe Dec 13 '18 at 03:48
  • When I try to do this, it throws out 'IndexError: Cannot choose from an empty sequence' as if game_dict is empty. I am also not sure how to take care of the 'non_ascii' loop so that it doesn't alter it's own container. – bpryan Dec 13 '18 at 03:55