Improve the odds of winning at hangman with ai - python

I'm new (and to stack overflow, this is the first question I have ever asked) to python, I have been self-teaching myself for a couple of weeks. I was doing some beginner projects when I decided to make a hangman ai.
#importing
import random
import time
import sys
from collections import Counter
#---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#defining some variables
list_of_words = open("dictionary.txt", "r")
list_of_words = list_of_words.read().split()
SYMBOL = "abcdefghijklmnopqrstuvwxyz"
#---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#main game loop
def main():
while True:
print("\nGenerating word...")
word = list_of_words[random.randint(0, len(list_of_words) - 1)].lower()
word_guess = []
wrong_attempts = 0
wrong_letters = []
game_state = True
for symbol in word:
if symbol in SYMBOL:
word_guess.append("_")
else:
word_guess.append(symbol)
word_show = " ".join(word_guess)
word = list(word)
while game_state != False:
print("\n" + word_show)
print("\nWrong attempts [{0}/5]" .format(wrong_attempts))
if len(wrong_letters) > 0:
print("\nLetters guessed [{0}]" .format(", ".join(wrong_letters)))
letter = "-"
while letter not in SYMBOL or letter == "" or len(letter) > 1:
try:
letter = input("\nGuess a letter or enter 0 to call the ai: ")
except:
print("\nUnexpected error ocurred, try again")
if letter == "0":
correct_letters = [letter for letter in word_guess if letter in SYMBOL]
letter = ai_solver(wrong_letters, word_guess)
elif letter in wrong_letters or letter in word_guess:
print("\nYou already guessed letter [{0}]" .format(letter))
letter = ""
if letter in word:
for i in range(len(word)):
if letter == word[i]:
word_guess[i] = letter
else:
wrong_letters.append(letter)
wrong_attempts += 1
word_show = " ".join(word_guess)
if "".join(word_guess) == "".join(word):
print("\nYou won!")
game_state = False
elif wrong_attempts == 5:
print("\nYou lost!")
print("The word was [{0}]" .format("".join(word)))
game_state = False
option = input("\nWant to play again?[Y/N]: ")
if option.lower().startswith("n"):
sys.exit(0)
def ai_solver(letters_attempted, word_guess):
letters_attempted = letters_attempted if len(letters_attempted) != 0 else ""
available_words = []
for word in list_of_words:
append = False
if len(word) == len(word_guess):
append = True
for i in range(len(word_guess)):
if word[i] in letters_attempted:
append = False
break
if word_guess[i] != "_":
if word[i] != word_guess[i]:
append = False
break
if append == True:
print("[{0}]" .format(word))
available_words.append(word)
common_letters = [letter for letter in "".join(available_words) if letter not in word_guess]
common_letters = Counter("".join(common_letters)).most_common(1)
return common_letters[0][0]
main()
What I tried to do is, to filter all the possible words that have the same length as word_guess.
Then filter out any words that contained a letter that was guessed incorrectly by checking letters_attempted.
Then it would filter out all words that had letters that did not match with word_guess.
if word_guess[i] != "_":
if word[i] != word_guess[i]:
append = False
break
Although it works fine, sometimes it would lose, what can I add to increase the chances of winning?
Thank you!

Your two filter steps are a good first start. There are several different steps you could take to try to improve things. Let's call the words that fit the criteria so far the candidate words.
The first step would be to analyze all the candidate words and figure out which letter appears most frequently in the candidate words. (Not counting repeated letters multiple times.) That letter would make a good next guess.
A slightly more sophisticated approach would look at information gain from a guess. That is, it might be that half the candidate words have a 's', but all such words end in 's'. There might be slight fewer candidate words with a 't', but the 't' can appear anywhere in the word. So, when you guess 't' you actually get a lot more information about what the word could be, because you are shown the location of the 't' when you guess it correctly. Particularly when you don't have enough guesses to figure out every word, such a strategy may help you figure out more words in the guesses that you have.

Related

Hangman Python, reveal different all indexes containing user's input

Basically if the chosen word is "green" and the user types "e" then I want it to show "__ ee _".
But instead, it shows something like this: __ e __.
How can I fix this? Here is my code:
print("Welcome to hangman game!")
import random
words = ['act', 'air', 'age', 'bag', 'cap', 'map', 'acre', 'card', 'dish', 'wack', 'exam', 'god', 'boards', 'chair', 'count', 'facts', 'house']
word = random.choice(words)
list(word)
letters_guessed = []
wrong_letters = [""]
guesses_left = 8
win_confirm = 0
for i in word:
letters_guessed.append("_")
while guesses_left > 0:
print("\nThe word contains {} letters".format(len(word)))
print("You have {} guesses left".format(guesses_left))
print(*letters_guessed)
user = input("\nEnter a letter --> ")
if user in letters_guessed or user in wrong_letters:
print("You have already entered '{}', enter another letter!".format(user))
guesses_left += 1
if user in word:
letter = word.index(user)
letters_guessed[letter] = user
print(*letters_guessed)
win_confirm += 1
if win_confirm == len(word):
print("You won!")
print("The word was '{}' ".format(word))
break
continue
else:
guesses_left -= 1
wrong_letters.append(user)
if guesses_left <= 0:
print("You don't have any chances left :(")
print("The word was '{}' ".format(word))
If there is any fix please tell me, thanks.
user.index() will get only the first index of the letter.
if user in word:
letter = word.index(user)
letters_guessed[letter] = user
print(*letters_guessed)
Try this code instead:
if guess in ai_word:
for letter, num in zip(ai_word, range(len(ai_word))):
if guess == letter: #If guess is there in actual word then according to the index add it
user_letters[num] = guess
zip() basically gets a element each from two or more iterables and returns them as a tuple. So here zip(ai_word, range(len(ai_word))) gets the letter and also its index so if the letter matches we can easily change the letter with its index.
Edit - As syggested by #Oli int the comments -
You can also use:
if guess in ai_word:
for num, letter in enumerate(ai_word):
if guess == letter: #If guess is there in actual word then according to the index add it
user_letters[num] = guess

How to a terminate a while loop?

I have this code that defines a word and I am making guesses of letters that form the actual word. It works perfectly, but I am unable to terminate the loop from taking in user input after I obtain the correct formation of letters. Does any one have an idea in terminating the loop?
word = "EVAPORATE"
guessed_word = "_" * len(word)
word = list(word)
guessed_word = list(guessed_word)
new_list = []
while True:
guess_letter = input("Enter a guess: ")
for index, letter in enumerate(word):
if letter == guess_letter:
guessed_word[index] = letter
print(' '.join(guessed_word))
You can simply change while True to while word != guessed_word, then it will stop after you obtain the correct answer.
This will work
word = "EVAPORATE"
word = word.lower()
word = list(word)
guessed_word = "_" * len(word)
guessed_word = list(guessed_word)
new_list = []
while True:
guess_letter = input("Enter a guess: ")
for index, letter in enumerate(word):
if letter == guess_letter:
guessed_word[index] = letter
print(' '.join(guessed_word))
if '_' not in guessed_word:
print('Congratulation!')
break
you should get to know about flow control statements being break, continue and pass. Look at break for this particular question
You can use break.
For example...
n = 5
while n > 0:
n -= 1
if n == 2:
break
print(n)
print('Loop ended.')

How do you insert repeated values with distinct indices into an empty list in python?

I'm trying to build a basic program where the computer selects a word out of a pre-existing list (called "words") and the user must guess the appropriate letters to guess the word. This is what the main function looks like so far:
def game():
word = random.choice(words)
while ' ' or '-' in word:
word = random.choice(words)
if ' ' or '-' not in word:
break
print(f'Hint: The chosen word is {len(word)} letters long')
letters = list(word)
progress = []
while True:
guess = str(input('Guess a letter: '))
if len(guess) > 1:
print('Sorry, guess a single letter: ')
if guess in word:
print(f'The letter {guess} is in the word')
for i, j in enumerate(letters):
if progress.count(guess) >= letters.count(guess):
break
elif j == guess:
progress.insert(i, j)
print('Current progress: ' + '-'.join(progress))
if len(progress) == len(word):
if letters[:] == progress[:]:
print('Congrats! You found the word: ' + str(word))
break
elif guess not in word:
print(f'The letter {guess} is not in the word: Try Again')
continue
My issue is with the for loop where I use enumerate(y) and the respective "elif j == guess" condition. I noticed that when running the function, the code works if the letters that are repeated are successive (ex: in the word "chilly", if I type in "l", the function correctly displays the two l's and the game works as intended). However, if the letters are repeated separately (ex: the word "cologne"), the function doesn't insert the "l" between the two o's, and keeps the two o's together regardless, thus preventing the proper word from ever being guessed. Is there a different method I could use to fix this problem?
You should remember the letters already guessed and simplyfiy the printing to any letter that you remember and use - for any other letter in the word.
Your errror stems from your list and counting method to remember which letters to print or not.
I fixed your incorrect if-condition (see How to test multiple variables against a value? for more on that).
import random
# supply list of words to function to avoid globals
def game(words):
word = random.choice(words)
# fixed your check ... not sure why you do that
while ' ' in word or '-' in word:
word = random.choice(words)
# no break needed, will break if no space/- in
print(f'Hint: The chosen word is {len(word)} letters long')
# remember which letters where guessed already
guesses = set()
while True:
guess = input('Guess a letter: ') # no str needed it is already a str
if len(guess) > 1:
print('Sorry, guess a single letter: ')
continue # back to while loop
# add to guessed letters
guesses.add(guess)
# print message
if guess in word:
print(f'The letter {guess} is in the word')
else:
print(f'The letter {guess} is not in the word: Try Again')
continue # back to while loop
print('Current progress: ', end="")
# assume we have all letters guessed
done = True
for idx, letter in enumerate(word):
if letter in guesses:
# print letter if already guessed
print(letter, end="")
else:
# invalidate assumption and print -
done = False
print("-",end="")
print()
# if assumption not invalidated: done
if done:
print('Congrats! You found the word: ' + str(word))
break
game(["eye", "ear", "egg", "anvil"])
Output:
Hint: The chosen word is 3 letters long
Guess a letter: The letter e is in the word
Current progress: e-e
Guess a letter: The letter r is not in the word: Try Again
Current progress: e-e
Guess a letter: The letter y is in the word
Current progress: eye
Congrats! You found the word: eye

Working with multiple occurrences of elements in a list

I'm still a noob when it comes to coding and I'm trying to create my own hangman game. I ran into some difficulties when it comes to guessing characters of a word which occur more than once in a word.
Here's a snippet of my code:
def random_word():
#word generator
randomized = random.randint(0,(len(content_words)-1))
word_to_guess = content_words[randomized].lower()
splitted = []
word_progress = []
for character in word_to_guess:
splitted.append(character.lower())
word_progress.append("?")
counter = 0
while counter <= 5:
print(word_to_guess)
print(splitted)
print(word_progress)
#Start of the game
options = str(input("Do you want to guess the word or the characters?: ").lower())
#word
if options == "word":
guess_word = input("Please your guess of the word: ").lower()
if guess_word == word_to_guess:
print("Correct! The word was " + word_to_guess + " you only needed " + str(counter) + " tries!")
break
elif guess_word != word_to_guess:
counter += 3
print("You have entered the wrong word ! You now have " + str(5-counter) + " tries left!")
continue
#characters
elif options == "characters":
guess_character = input("Please enter the character you would like to enter!: ")
if guess_character in splitted and len(guess_character) == 1:
print("Correct! The character " + guess_character.upper() + " is in the word were looking for!" )
for char in word_to_guess:
if char == guess_character:
word_progress[word_to_guess.index(char)] = word_to_guess[word_to_guess.index(char)]
continue
....so basically in the character section only the first occurrence of the guessed character gets implemented into the word_to_guess list. What is the best way to handle this problem?
By the way this is the first question I've ever asked regarding to coding and on this platform, please excuse me if I didn't really formulate my problem in the most efficient way.
index will only return the index of the first occurence of your character.
You should use enumerate to iterate on the characters and indices at the same time:
for index, char in enumerate(word_to_guess):
if char == guess_character:
word_progress[index] = guess_character

Saving results of loop iterations in a game

I am trying to make a hangman game and I am running into trouble with the display. I have a loop that is supposed to put the correctly guessed letters in the right places, however it only shows the correct location for one letter at a time. I thought it would be helpful to save the result of the previous iteration, and then display that, but I am not sure how to do that.
import random,time
hanglist = []
answerlist = []
file_var = open("wordlist.100000")
for n in file_var:
hanglist.append(file_var.readline())
word = random.choice(hanglist)
print("word is",word)
guesses = 10
while guesses != 0:
print("guess a letter")
answer = input()
answerlist.append(answer)
if answer in word:
m = list(word)
for n in m:
if n == answer:
print(answer, end = '')
else:
print('_', end = '')
else:
print("close, but not exactly")
guesses -= 1
And here are the outputs
word is fabric
guess a letter
f
f______guess a letter
a
_a_____guess a letter
To solve your issue just replace if n==answer to if n in answer. But, from the above code, I can see code can't handle these issues:
If the user guesses the same word again and again
After 4 guesses are done and total word is guessed, then code should break out of the loop, which it is not happening.
While reading line, it need to strip the '\n' otherwise its really hard
My code addresses these issue:
import random,time
hanglist = []
answerlist = []
file_var = open("wordlist.100000")
for n in file_var:
# strips the '/n' at the end
hanglist.append(file_var.readline().rstrip())
word = random.choice(hanglist)
print("word is",word)
guesses = 10
while guesses!=0:
print("guess a letter")
answer = input()
if answer in answerlist:
continue
answerlist.append(answer)
if answer in word:
# to print entire word guessed till now- with current and previous iterations
word_print = ''
for n in word:
# to print all the last state occurences
if n in answerlist:
word_print += n
else:
word_print += '_'
print(word_print,end='')
# word is correctly guessed
if '_' not in word_print:
break
else:
print("close, but not exactly")
guesses = guesses-1
Your issue is with
if n == answer:
print(answer,end = '')
else:
print('_', end = '')
which only compares each letter with the current guess, answer. Instead, if you use
if n in answerlist:
print(n, end = '')
else:
print('_', end = '')
it will show the letter if that letter is in the list of their previous guesses.
Additionally: the previous m= list(word) is not necessary, as for n in word: is valid.

Categories