How to replace characters in a string using iteration and variables? (Python) - python

I am writing a hangman game in python as part of a college project, and I am trying to use string.replace(old, new) to substitute the blanks (_) with letters. Instead of using actual string characters though, I am trying to use variables for 'old' and 'new'. Here's what I've got so far for this bit:
if validGuess == 'true':
if guess in word:
for letter in word:
if letter == guess:
word.replace(letter, guess)
else:
missNum = (missNum + 1)
else:
tryNum = (tryNum - 1)
However, it isn't working. I don't get any errors, it simply will not replace the blanks.
What am I doing wrong here? Is there a better way to achieve what I am doing?
-EDIT-
I tried to implement #Peter Westlake's solution (which seemed to me the most elegant) but I have run into an issue. I have a section of code which converts a randomly selected word into underscores:
#converting word to underscores
wordLength = len(word)
wordLength = (wordLength - 1)
print(wordLength) #testing
for i in range(0,wordLength):
wordGuess = (wordGuess + '_')
print(wordGuess)
And this seems to work fine. Here is the code for letter substitution:
if validGuess == 'true':
wordGuess = ''.join([letter if guess == letter else wordGuess[pos]
for pos, letter in enumerate(word)])
if guess not in word:
tryNum = (tryNum - 1)
print(wordGuess)
However, here is the output:
Guess a letter: a
test
Traceback (most recent call last):
File "G:\Python\Hangman\hangman.py", line 60, in <module>
for pos, letter in enumerate(word)])
File "G:\Python\Hangman\hangman.py", line 60, in <listcomp>
for pos, letter in enumerate(word)])
IndexError: string index out of range
String index out of range? What does that mean?

str.replace() returns the new string, store the new value:
word = word.replace(letter, guess)
Python strings are immutable and cannot be altered in-place.
However, you are replacing letter with the exact same value; letter == guess is only True if both are the same character.
I'd keep a separate set of correctly guessed letters instead, and rebuild the displayed underscores and correct guesses each time:
correct_guesses = set()
incorrect_guesses = set()
if guess in correct_guesses & incorrect_guesses:
print('You already guessed that letter')
elif guess in word:
# correct guess!
correct_guesses.add(guess)
display_word = ''.join(char if char in correct_guesses else '_' for char in word)
else:
# incorrect guess!
incorrect_guesses.add(guess)
print('Oops, incorrect guess!')
missNum += 1

I think I understand what you're getting at here.
I would probably rebuild the word-so-far on the spot instead of having a persistent string for it, keeping the tested letters separately. When the user tries a new character, make two checks:
See if the guess character has been guessed already: if guess in tried. If so, proceed however you like (penalize or ignore), but don't add the character to the tried-characters list.
If not, see if the character is in the target word: elif guess in word. If not, assess some penalty and add the guess to the tried-characters list.
For any other result: else. Add the guess to the tried-characters list.
To display the user's progress, make a blank string. Go through the target word character-at-a-time: for char in word, like you have been. But instead of trying to modify an extant string, just add the character to the end of the blank string if it's in the tried-characters string, or an underscore if not: show += char if char in tried else "_". Once that for loop is exhausted, display what you've got!
Alternatively, use .join with a slightly different iterator: show = "".join(char if char in tried else '_' for char in word). It'll iterate through word, keeping each letter if it's in your tried-characters string, or substituting an underscore if not, putting whatever is in "" between them (or nothing, if you leave it as ""). It looks like you already know that, though.
At the hazard of completely rewriting your code, this is what it might look like:
## init
word = "mauritius" # the word you're looking for. I chose this one.
tried = str() # initialize a list of tested characters
tryNum = 3 # however many wrong guesses the user gets
...
## in your run loop...
if tryNum: # evaluates 0 as Fasle, if you didn't already know
guess = UserInput() # some means to get the guess from the user; a single-character string.
if guess in tried:
print "Tried that letter already!"
elif guess not in word: # the letter hasn't been tested yet, but isn't in the word, either.
print "Wrong! %d guesses left!" % tryNum
tryNum -= 1
tried += guess
else: # the guess is new (not tried) and it's valid (in the word)
tried += guess
show = str() # this is the string you will display. make a blank one each time.
for char in word:
show += char if char in tried else "_" # if the character has been tried, add it to the output. Otherwise, an underscore.
print show # display the word so far
if show == word:
print "You win!" # Congratulations! You hung a man.
else: # Out of tries; the convict lives another day.
print "Game Over!" # I am not sure how committed to this vocabulary-based execution you really are...
You can swap if tryNum: with while tryNum: and it should work all by itself, after initialization. If you do, there are fun things you can do with continues and breaks, but that goes a bit beyond the scope of your question.
You can swap show = str() and the for char in word: block out with the .join singleton in this next example, too. Change ''.join(..) to ' '.join(..) to add a space between characters/underscores!
This compressed version is probably a bit less Pythonic:
# post-init...
if tryNum:
guess = UserInput()
if guess in tried: pass
elif guess not in word:
print "Wrong! %d guesses left!" % tryNum
tryNum -= 1
tried += guess
else: tried += guess
show = ''.join(char if char in tried else '_' for char in word)
if show == word: print "You win!"
else: print "Game Over!"
This does not answer your first question of "What am I doing wrong?" but I think it might be a better way of going about what you intend? It might be a bit easier to maintain and expand for you, too.
Note: Go ahead and replace UserInput() with something like str(raw_input("Guess a letter!"))[0] if you want to try this thing out on its own.

Replacing a letter with an identical guess isn't going to do anything! I think you want to find the position in the word where the guessed letter appears, and replace the _ in that position with the letter. For that you will need to find every position where the letter occurs, e.g. using the index() method.
For instance, to replace the first occurrence of the guess:
# Illustration of the principle, not the complete answer.
word = 'faq'
display = '___'
# Put in a loop, one iteration for each guess input.
guess = 'a'
display = ''.join([letter if guess == letter else display[pos]
for pos, letter in enumerate(word)])
print display
Which will print _a_.

Related

Issue with analyzing strings in Python for Wordle-esque progam

I've created a function that takes a user-inputted guess, compared it to a hidden word taken randomly from a word doc, and returns a string that indicates if any letters match or are in the word at all. Here is the function:
def wordResults(guess, testGuess):
#guess = user inputted guess
#testGuess = secret word
results = ""
for i in range(5):
#Check if letters at given position match
#in each word, append capital letter if so
if guess[i] == testGuess[i]:
results += guess[i].upper()
#Check if letter at given position is in
#the secret word at all, append lowercase
#letter if so
elif testGuess.find(guess[i]) != -1:
results += guess[i]
#Append underscore if neither condition is met
else:
results += "_"
return results
My issue lies with the elif-statement. I would like it to print a lowercase only if that letter appears in the word, but not if the letter is already in the correct spot. Here is the program running to show what I'm referring to:
(Note: the hidden word is also user-inputted until I get the program working as intended)
For Guess #2, I would like it so that the first 'h' does not show up, since it is indicating the 5th letter in 'conch' that is already confirmed with a capital 'H'. Hope that makes sense.
It's a lot easier to work with a list and then make it a string at the end:
guess = guess.lower()
testGuess = testGuess.lower()
result = []
for i, letter in enumerate(guess):
if letter in testGuess:
if letter == testGuess[i]:
result.append(letter)
else:
result.append(letter.upper())
else:
result.append('_')
for i, letter in enumerate(result):
if letter.upper() in result and result[i] != letter:
result[i] = '_'
return ''.join(result)
For guess two, then the second loop checks if each letter is already in the loop and placed correctly and if it's not in the correct spot, makes it back into an _.

How to replace the specified dash with the letter

I wish to write a hangman program and in order to do so, I have to replace the hash ('-') letter(s) with the user's guessed letter (guess). But when I run the code, it replaces all the hashes with the user's guess letter.
The code seems okay but I don't get the desired result.
words is a list of words I have written before the function.
def word_guess():
random.shuffle(words)
word = words[0]
words.pop(0)
print(word)
l_count = 0
for letter in word:
l_count += 1
# the hidden words are shown a '-'
blank = '-' * l_count
print(blank)
guess = input("please guess a letter ")
if guess in word:
# a list of the position of all the specified letters in the word
a = [i for i, letter in enumerate(word) if letter == guess]
for num in a:
blank_reformed = blank.replace(blank[num], guess)
print(blank_reformed)
word_guess()
e.g: when the word is 'funny', and guess is 'n', the output is 'nnnnn'.
How should I replace the desired hash string with guess letter?
it replaces all the hashes
This is exactly what blank.replace is supposed to do, though.
What you should do is replace that single character of the string. Since strings are immutable, you can't really do this. However, lists of strings are mutable, so you could do blank = ['-'] * l_count, which would be a list of dashes, and then modify blank[num]:
for num in a:
blank[num] = guess
print(blank)
A couple things to note:
inefficient/un-pythonic pop operation (see this)
l_count is just len(word)
un-pythonic, unreadable replacement
Instead, here's a better implementation:
def word_guess() -> str:
random.shuffle(words)
word = words.pop()
guess = input()
out = ''
for char in word:
if char == guess:
out.append(char)
else:
out.append('-')
return out
If you don't plan to use the locations of the correct guess later on, then you can simplify the last section of code:
word = 'hangman'
blank = '-------'
guess = 'a'
if guess in word:
blank_reformed = ''.join(guess if word[i] == guess else blank[i] for i in range(len(word)))
blank_reformed
'-a---a-'
(You still have some work to do make the overall game work...)

How to break in this situation

So I am almost done making a Hangman Program (just the framework), but I am having trouble with breaking a loop. This is because I am supposed to allow the user to have unlimited trials, so I couldn't use something like:
x = 10
while x != 0
do something
x -= 1
However, I figured out another way - to make a guess_list that contains every single letter that is tried and say:
if final_letter in guess_list:
break
I thought this would work fine (and it did for a couple trials), but I just checked the program again and it didn't work (I'll insert a picture here to show the details).
The thing I've noticed here is that I need to type everything in order (if 'friend'; 'f' then 'r' then 'i'...) to get the while loop break. How can I possibly get it to break when there are no more asterisks left??
My code is:
fr = input("What file would you like to open: ")
f = open(fr, 'r')
text = f.read()
x = 0
textsplit = text.split()
guess_list = ''
import random
rand_letter = random.choice(textsplit)
for letter in range(len(textsplit)):
final_letter = rand_letter.lower()
final_letter = ''.join(x for x in final_letter if x.isalpha())
for i in range(len(final_letter)):
print ('*', end = '')
print ()
while x == 0:
guess = input("Guess a character: ")
guess_list += guess
if guess not in final_letter:
for char in final_letter:
if char in guess_list:
print (char, end = ''),
if char not in guess_list:
print('*', end = '')
print('')
print("Incorrect. Try again!")
else:
for char in final_letter:
if char in guess_list:
print (char, end = ''),
if char not in guess_list:
print('*', end = '')
if final_letter in guess_list:
print('')
print('YOU GOT IT!!!! NIICEE')
break
Any help/comment/advice/thoughts would be greatly appreciated!!
Firstly, reading a file in for sample words is hard to replicate. Here, I've just used a predetermined list and split it into words.
Next, I've used sets here, as sets are useful for checking if something's already been guessed or if a character is in the goal word. Also, variable names are changed for clarity's sake, final_letter being a prime example.
If someone guesses a word which has characters that are only partially in the goal word (guessing "if" for "fond"), this will add to current progress but report incorrect. If you want to make sure that characters are in order for words ("niw" not being a valid guess for "win"), this will require some changes*.
s.update(iterable) adds all the things in an iterable (list, set, generator, etc) to a set s. s.issubset(other_set) checks that all elements of s are in other_set.
import random
textsplit = "friend win lose hangman".split()
guesses = set()
goal_word = random.choice(textsplit)
goal_set = set(goal_word)
print("".join(["*" for _ in goal_word]))
while True:
guess = input("Guess a character: ")
set_guess = set(guess)
if set_guess.issubset(guesses):
print("Please try again; you already guessed", guess)
continue
guesses.update(list(guess))
if goal_set.issubset(guesses):
print('YOU GOT IT!!!! NIICEE')
break
progress = ""
for c in goal_word:
if c in guesses:
progress += c
else:
progress += "*"
print(progress)
if not all((c in goal_set for c in set_guess)):
print("Incorrect. Try again!")
*Sets are unordered, so s.issubset(other_set) only cares that all the elements of s are in other_set, as s and other_set both don't know their own order. To support ordering, consider checking for equality between the guessed word and the goal word.

Implementing a hangman function in Python

Here is the problematic piece of my function:
def hangman1(word):
global guessesMade
global guessesLeft
currentGuess = '_ ' * len(word)
let = print(input('Please guess a letter: '))
for i in range(len(word)):
if word[i] == let:
print('{} is contained in the word.'.format(let))
if i == 0:
currentGuess = word[0] + currentGuess[1:]
else:
currentGuess = currentGuess[:i] + word[i] + currentGuess[i + 1:]
print(currentGuess)
The user enters a letter at the prompt and it checks if the letter is in the randomWord that was generated outside of the function from a list of words. I can get it to print the blanks correctly, but if the user enters a letter that is in the word it prints out a line of the correct letter instead of the blanks with the correct letter mixed in between.
Any help is appreciated.
The main problem you're having right now is two-fold - one, that the replace() method replaces all instances of any given input within a string, not the first one, and two, that you don't currently have any way of telling which letters you've already uncovered. Calling replace("_", let) will always replace every single instance of "_", and given that you're applying that to a string that is only composed of underscores, it'll always overwrite the entire string. It seems like you're also regenerating hidden_let every time hangman() is called with a guess letter, meaning that best-case with your design now you're only going to ever show every letter the user just guessed and a bunch of underscores otherwise.
What you'd want to do is have two values, correct_word and current_guess. correct_word will be the word the player has to guess, and current_guess will be their progress in guessing the word, starting with a string of only underscores of the same length as correct_word.
Here's a short example. I've taken the liberty of removing your global references - globals are generally frowned upon - and encapsulated the behavior in a small class. You'd want to replace the value in hangmanner.play_hangman() with whatever your random word is.
class Hangmanner:
correct_word = ''
current_guess = ''
def play_hangman(self, word):
self.correct_word = word
self.current_guess = '_' * len(self.correct_word)
while self.current_guess != self.correct_word:
self.guess_letter(input("Please guess a letter: "))
def guess_letter(self, guessed_letter):
for i in range(len(self.correct_word)):
if self.correct_word[i] == guessed_letter:
if i == 0:
self.current_guess = self.correct_word[i] + self.current_guess[1:]
else:
self.current_guess = self.current_guess[:i] + self.correct_word[i] + self.current_guess[i + 1:]
print(self.current_guess)
if __name__ == "__main__":
hangmanner = Hangmanner()
hangmanner.play_hangman("test")
This uses the slicing function in python, where you can use the brackets and the [first:last] syntax to access an arbitrary range of any given collection. If either first or last is missing, the slice continues to the beginning or end of the collection, respectively. Above, current_guess[1:] returns current_guess from the second index to the last. current_guess[:i] returns current_guess from the first index up to the index preceding i, given that last is the exclusive end bound.
hiddenLet.replace('_',let) replaces all occurrences of _ with whatever let represents.
newWordList = [x if x==let else '_' for x in randWord]
newWord = ''.join(newWordList)

string issue regarding python, basic introduction level

I have a person calling a function with three inputs:
puzzle - so a random word, for example. 'john'
view - their view of the puzzle (they guess letters and they become revealed scoring points, etc) so lets say they only see j^h^ (^ represents hidden characters).
letter_guessed - they guess a letter, so if someone guessed 'o', the view would come back as 'joh^'
But my code just doesn't seem work and I can't seem to understand why, and please if you could do it using my bit of code below, I understand there are many ways to solve it but I'm interested in what I had to do if I wanted to solve this question using a for statement with nested if statements.
What it doesnt do: it simply displays the view again, the line of incorrect code is result = result + letter because i dont know how to make python scan for the hidden variable and replace the ^ with set found alphabetic letter.
def update_view(puzzle,view,letter_guessed):
result = ""
for index in range(0,len(puzzle)):
if puzzle[index] == letter_guessed:
result = result + letter_guessed
else:
result = view
return result
If the letter is currently "^" and the letter was guessed correctly, you want to add the guessed letter to result. Else, you want to add whatever was on the view earlier
def guess(word, view, letter) :
result = ""
for i in range(0,len(word)) :
if view[i] == "^" and word[i] == letter:
result += word[i]
else :
result += view[i]
return result
Demo
The above if-else condition can be further shortened using Python's true if condition else false construct
def guess(word, view, letter) :
result = ""
for i in range(0,len(word)) :
result += word[i] if view[i] == "^" and word[i] == letter else view[i]
return result

Categories

Resources