My first Hangman game in PythonPangrams CodeEval challengeFirst Hangman gamePython Hangman GameSimple Python hangman game“Hangman” game in PythonHackerrank Gemstones SolutionThe BFS approach to the SmartWordToy challengeSimple Hangman game - first Python projectA first “Hangman” game in PythonCodewars - Highest Scoring Word
A cotton-y connection
Garage door sticks on a bolt
I reverse the source code, you reverse the input!
How to change products sort order in magento 2?
Where to find the Arxiv endorsement code?
Convert a string of digits from words to an integer
Why is the population of post-Soviet states declining?
Why is STARTTLS still used?
How to prevent pickpocketing in busy bars?
A delve into extraordinary chess problems: Selfmate 2
What does `idem` mean in the VIM docs?
What is the climate impact of planting one tree?
Why would an airline put 15 passengers at once on standby?
When did Unix stop storing passwords in clear text?
Would a 737 pilot use flaps in nose dive?
My first Hangman game in Python
Is there an in-universe explanation of how Frodo's arrival in Valinor was recorded in the Red Book?
what organs or modifications would be needed to have hairy fish?
Vilna Gaon's gematria for the number of kosher & non-kosher sukkot in Masechet Sukkah
Why are the wings of some modern gliders tadpole shaped?
Speed and Velocity in Russian
A word that refers to saying something in an attempt to anger or embarrass someone into doing something that they don’t want to do?
Sci-fi movie with one survivor and an organism(?) recreating his memories
Why aren't faces sharp in my f/1.8 portraits even though I'm carefully using center-point autofocus?
My first Hangman game in Python
Pangrams CodeEval challengeFirst Hangman gamePython Hangman GameSimple Python hangman game“Hangman” game in PythonHackerrank Gemstones SolutionThe BFS approach to the SmartWordToy challengeSimple Hangman game - first Python projectA first “Hangman” game in PythonCodewars - Highest Scoring Word
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
$begingroup$
This is my first game and I'm really proud of it, but I think it's horribly messy. Is there any way to improve it?
#HANGMAN
# Valid Word Checker
def valid_word(word):
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
for i in word:
if i in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'i' is not allowedn")
return False
if len(word) > 12:
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
word_list = []
spaces = []
guessed_letters = []
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
head =" O"
armL = " /"
torso = "|"
armR = "\"
legL = "/"
legR = " \"
hangman_parts = [head, armL, torso, armR, legL, legR]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
wordisValid = False
while True:
word = input("nnnChoose any wordnnn").lower()
if valid_word(word) == True:
wordisValid = True
break
else:
continue
# Add to list
for i in word:
word_list.append(i)
spaces.append("_ ")
# Main Game Loop
bad_letter = 0
while wordisValid == True:
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
# Winning Loop
if "".join(spaces) == word:
print(f"YOU WIN! The word was: word")
break
# Choose Letters
player_guess = input("nnPlease choose a letter: nnnn").lower()
guessed_letters.append(" " + player_guess)
if player_guess in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'player_guess' is not allowedn")
elif len(player_guess) > 1:
print("nError: You must use one letter.n")
elif player_guess == "":
print("nError: No input provided.n")
# Wrong Letter
elif player_guess not in word_list:
bad_letter += 1
if bad_letter == 1:
hangman_final.append(hangman_progress[1] + head)
elif bad_letter == 2:
hangman_final.append(hangman_progress[2] + " " + torso)
elif bad_letter == 3:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso)
elif bad_letter == 4:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso + armR)
elif bad_letter == 5:
hangman_final.append(hangman_progress[3] + " " + legL)
elif bad_letter == 6:
hangman_final.pop(3)
hangman_final.append(hangman_progress[3] + " " + legL + legR)
print("nnThe word was: " + word)
print("nnnnnnn" + "".join(hangman_final))
print(" YOU GOT HUNG ")
break
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
print(f"nnnplayer_guess is not in the word. Try again.nn")
# END GAME
if bad_letter == 6:
break
# Add letters guessed to a list
counter = 0
for i in word:
if player_guess == i:
spaces[counter] = player_guess
counter += 1
Even though it's working, I'm not sure that I have the right idea when it comes to making this type of project.
python beginner python-3.x game hangman
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
add a comment
|
$begingroup$
This is my first game and I'm really proud of it, but I think it's horribly messy. Is there any way to improve it?
#HANGMAN
# Valid Word Checker
def valid_word(word):
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
for i in word:
if i in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'i' is not allowedn")
return False
if len(word) > 12:
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
word_list = []
spaces = []
guessed_letters = []
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
head =" O"
armL = " /"
torso = "|"
armR = "\"
legL = "/"
legR = " \"
hangman_parts = [head, armL, torso, armR, legL, legR]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
wordisValid = False
while True:
word = input("nnnChoose any wordnnn").lower()
if valid_word(word) == True:
wordisValid = True
break
else:
continue
# Add to list
for i in word:
word_list.append(i)
spaces.append("_ ")
# Main Game Loop
bad_letter = 0
while wordisValid == True:
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
# Winning Loop
if "".join(spaces) == word:
print(f"YOU WIN! The word was: word")
break
# Choose Letters
player_guess = input("nnPlease choose a letter: nnnn").lower()
guessed_letters.append(" " + player_guess)
if player_guess in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'player_guess' is not allowedn")
elif len(player_guess) > 1:
print("nError: You must use one letter.n")
elif player_guess == "":
print("nError: No input provided.n")
# Wrong Letter
elif player_guess not in word_list:
bad_letter += 1
if bad_letter == 1:
hangman_final.append(hangman_progress[1] + head)
elif bad_letter == 2:
hangman_final.append(hangman_progress[2] + " " + torso)
elif bad_letter == 3:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso)
elif bad_letter == 4:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso + armR)
elif bad_letter == 5:
hangman_final.append(hangman_progress[3] + " " + legL)
elif bad_letter == 6:
hangman_final.pop(3)
hangman_final.append(hangman_progress[3] + " " + legL + legR)
print("nnThe word was: " + word)
print("nnnnnnn" + "".join(hangman_final))
print(" YOU GOT HUNG ")
break
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
print(f"nnnplayer_guess is not in the word. Try again.nn")
# END GAME
if bad_letter == 6:
break
# Add letters guessed to a list
counter = 0
for i in word:
if player_guess == i:
spaces[counter] = player_guess
counter += 1
Even though it's working, I'm not sure that I have the right idea when it comes to making this type of project.
python beginner python-3.x game hangman
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
$begingroup$
Welcome to Code Review! You can take the tour for a quick overview of the site.
$endgroup$
– L. F.
5 hours ago
add a comment
|
$begingroup$
This is my first game and I'm really proud of it, but I think it's horribly messy. Is there any way to improve it?
#HANGMAN
# Valid Word Checker
def valid_word(word):
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
for i in word:
if i in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'i' is not allowedn")
return False
if len(word) > 12:
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
word_list = []
spaces = []
guessed_letters = []
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
head =" O"
armL = " /"
torso = "|"
armR = "\"
legL = "/"
legR = " \"
hangman_parts = [head, armL, torso, armR, legL, legR]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
wordisValid = False
while True:
word = input("nnnChoose any wordnnn").lower()
if valid_word(word) == True:
wordisValid = True
break
else:
continue
# Add to list
for i in word:
word_list.append(i)
spaces.append("_ ")
# Main Game Loop
bad_letter = 0
while wordisValid == True:
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
# Winning Loop
if "".join(spaces) == word:
print(f"YOU WIN! The word was: word")
break
# Choose Letters
player_guess = input("nnPlease choose a letter: nnnn").lower()
guessed_letters.append(" " + player_guess)
if player_guess in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'player_guess' is not allowedn")
elif len(player_guess) > 1:
print("nError: You must use one letter.n")
elif player_guess == "":
print("nError: No input provided.n")
# Wrong Letter
elif player_guess not in word_list:
bad_letter += 1
if bad_letter == 1:
hangman_final.append(hangman_progress[1] + head)
elif bad_letter == 2:
hangman_final.append(hangman_progress[2] + " " + torso)
elif bad_letter == 3:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso)
elif bad_letter == 4:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso + armR)
elif bad_letter == 5:
hangman_final.append(hangman_progress[3] + " " + legL)
elif bad_letter == 6:
hangman_final.pop(3)
hangman_final.append(hangman_progress[3] + " " + legL + legR)
print("nnThe word was: " + word)
print("nnnnnnn" + "".join(hangman_final))
print(" YOU GOT HUNG ")
break
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
print(f"nnnplayer_guess is not in the word. Try again.nn")
# END GAME
if bad_letter == 6:
break
# Add letters guessed to a list
counter = 0
for i in word:
if player_guess == i:
spaces[counter] = player_guess
counter += 1
Even though it's working, I'm not sure that I have the right idea when it comes to making this type of project.
python beginner python-3.x game hangman
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
This is my first game and I'm really proud of it, but I think it's horribly messy. Is there any way to improve it?
#HANGMAN
# Valid Word Checker
def valid_word(word):
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
for i in word:
if i in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'i' is not allowedn")
return False
if len(word) > 12:
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
word_list = []
spaces = []
guessed_letters = []
ill_chars = ["!","@","#","$","%","^","&","*","(",")",",",".", " ","1","2","3","4","5","6","7","8","9","0"]
head =" O"
armL = " /"
torso = "|"
armR = "\"
legL = "/"
legR = " \"
hangman_parts = [head, armL, torso, armR, legL, legR]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
wordisValid = False
while True:
word = input("nnnChoose any wordnnn").lower()
if valid_word(word) == True:
wordisValid = True
break
else:
continue
# Add to list
for i in word:
word_list.append(i)
spaces.append("_ ")
# Main Game Loop
bad_letter = 0
while wordisValid == True:
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
# Winning Loop
if "".join(spaces) == word:
print(f"YOU WIN! The word was: word")
break
# Choose Letters
player_guess = input("nnPlease choose a letter: nnnn").lower()
guessed_letters.append(" " + player_guess)
if player_guess in ill_chars:
print(f"nError: It must be a letter. No symbols, numbers or spaces allowed. nn 'player_guess' is not allowedn")
elif len(player_guess) > 1:
print("nError: You must use one letter.n")
elif player_guess == "":
print("nError: No input provided.n")
# Wrong Letter
elif player_guess not in word_list:
bad_letter += 1
if bad_letter == 1:
hangman_final.append(hangman_progress[1] + head)
elif bad_letter == 2:
hangman_final.append(hangman_progress[2] + " " + torso)
elif bad_letter == 3:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso)
elif bad_letter == 4:
hangman_final.pop(2)
hangman_final.append(hangman_progress[2] + armL + torso + armR)
elif bad_letter == 5:
hangman_final.append(hangman_progress[3] + " " + legL)
elif bad_letter == 6:
hangman_final.pop(3)
hangman_final.append(hangman_progress[3] + " " + legL + legR)
print("nnThe word was: " + word)
print("nnnnnnn" + "".join(hangman_final))
print(" YOU GOT HUNG ")
break
print("".join(hangman_final))
print("nnnn")
print("".join(spaces))
print("nnThe word has: " + str(len(word)) + " letters.")
print("nYou've tried the following letters: " + "nn" + "".join(guessed_letters) + "nn")
print(f"nnnplayer_guess is not in the word. Try again.nn")
# END GAME
if bad_letter == 6:
break
# Add letters guessed to a list
counter = 0
for i in word:
if player_guess == i:
spaces[counter] = player_guess
counter += 1
Even though it's working, I'm not sure that I have the right idea when it comes to making this type of project.
python beginner python-3.x game hangman
python beginner python-3.x game hangman
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
edited 7 hours ago
Confettimaker
5385 silver badges17 bronze badges
5385 silver badges17 bronze badges
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
asked 9 hours ago
Python NovicePython Novice
292 bronze badges
292 bronze badges
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Python Novice is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$begingroup$
Welcome to Code Review! You can take the tour for a quick overview of the site.
$endgroup$
– L. F.
5 hours ago
add a comment
|
$begingroup$
Welcome to Code Review! You can take the tour for a quick overview of the site.
$endgroup$
– L. F.
5 hours ago
$begingroup$
Welcome to Code Review! You can take the tour for a quick overview of the site.
$endgroup$
– L. F.
5 hours ago
$begingroup$
Welcome to Code Review! You can take the tour for a quick overview of the site.
$endgroup$
– L. F.
5 hours ago
add a comment
|
1 Answer
1
active
oldest
votes
$begingroup$
While it's clear that you're new to python, it's still pretty good that you got it to run first time. Good job!
Input Validation
Currently, you have a list of forbidden characters. While that can work, by default python allows unicode input. That means there's literally thousands of letters someone can input. I suggest instead using a whitelist of valid inputs. Like this:
VALID_LETTERS = "abcdefghijklmnopqrstuvwxyz" # This won't ever change, and typically this sort
# of things is a module-level variable.
def valid_word(word):
for letter in word:
if letter not in VALID_LETTERS:
print(f"Letter letter is not a valid letter. Please use just a-z.")
return
if len(word) > 8: # This was 12 in your script. Little mistake? And you might want a
# module-level constant for this as well.
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
main() and game()
Generally, in python we never execute code at the moment the module is loaded. Read here for details, and how we guard against it. I'll come back to this later. For now, it means we should put as much as possible into functions - lets call them main() and game().
main() will be the main menu function. Since we don't have a true menu, this will basically start a game, and perhaps ask if the player wants to play again.
game() will be a function that lets us play a single game. Most of this will be a loop that gets player input and calculates the game state. Basically everything in your script that isn't another function should be in the game() function.
I'll cut some variables we don't need later on. I'll explain them when we get to where we use them.
Like this:
def game():
# Variable naming is important. This is an example of a good name.
guessed_letters = []
# ill_chars = [...] We already have this variable before here!
# Lets assign the parts directly. We can index them as in "3rd part".
# And for ourselves, we comment the meaning to make sure we still know what it means a year
# from now.
hangman_parts = [
" O", # Head
" /", # Left Arm
"|" , # Torse
"\", # Right Arm
"/", # Left Leg
" \" # Right Leg
]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
# Naming: should be word_is_valid, to be consistent with your other variables.
word_is_valid = False
while not word_is_valid:
word = input("nnnChoose any wordnnn").lower()
word_is_valid = valid_word(word)
# With this loop condition, it'll keep asking until valid_word returns True. So we can just
# assign that value to word_is_valid directly.
# Since this is the word we're trying to guess, lets just push it upwards in
# the console a lot, so we won't see it all the time:
print("n" * 100) # Prints 100 newlines.
# We don't need word_list, just word will do. We don't need spaces either.
while True: # Main game loop. We don't need to check for anything, we'll just use break or
# return to get out.
Now we're going to use a list comprehension. It's almost the same as a generator expression, but it gives us a true list, so we can ask python how long it is:
# Calculate how many bad letters we have with a list comprehension:
bad_letter = len([letter for letter in guessed_letters if letter not in word])
draw_hangman(bad_letter)
if bad_letter > 5:
print("nnnnYOU GOT HUNG")
return
print("nnnn")
You used to draw your hangman at two different places, while it's more practical to do it in one place. It also ensures that if you want to change something, you'll only have to change it once.
I've also changed how to draw it. It's much more useful to have a function for this drawing operation, and have it calculate how to draw from the number of bad letters so far. This makes the drawing independent of the current game state. Here's the version I came up with, based on your modifications of hangman_final:
def draw_hangman(bad_letter):
print(hangman_final, end="")
if bad_letter > 0: # Head
print(hangman_progress[1] + hangman_parts[0], end="")
if bad_letter == 2: # Torso and arms
print(hangman_progress[2] + " " + hangman_parts[2], end="")
elif bad_letter == 3:
print(hangman_progress[2] + "".join(hangman_parts[1:3]), end="")
elif bad_letter > 3:
print(hangman_progress[2] + "".join(hangman_parts[1:4]), end="")
if bad_letter > 4: # Legs
print(hangman_progress[3] + " " + hangman_parts[4], end="")
if bad_letter > 5:
print(hangman_parts[5], end="")
Here we need to print the letters we guessed, and underscores otherwise. We can calculate and print it in one line:
print(" ".join(letter for letter in word if letter in guessed_letters else "_"))
For every letter, this will print it if it's contained in guessed_letters, and otherwise print an underscore. This is a generator expression. This one is almost equal to the function:
def make_word(word, guessed_letters):
result = []
for letter in word:
if letter in guessed_letters:
result.append(letter)
else:
result.append("_")
return result
I say almost, because it's not exactly a list that comes out of it, even if it acts the same if you use it in a for loop or feed it to a function that does, like "".join(). It also acts more like a loop than a function, but that's not really important right now.
We feed the result to the join. And note that we feed it to " ".join(), with a space in between, which will put a space between every letter like you did before.
print(f"nnThe word has: len(word) letters.") # f-strings are shorter, and easy to use.
print(f"nYou've tried the following letters:nn''.join(guessed_letters) nn")
# For a string literal inside an f-string, use the other type of quotes
# This is another generator expression - this one returns a not-quite-list of booleans, and
# the all() builtin function returns True if and only if all results of the generator
# expression are True.
if all(letter in guessed_letters for letter in word):
print(f"You WIN!nThe word was: word")
return
input_valid = False
while not input_valid:
new_letter = input("nnPlease choose a letter: nnnn").lower()
# VERY GOOD that you lowercase it! Prevents tons of weird behaviour.
if len(new_letter) > 1:
print("Please enter a single letter only.")
elif new_letter not in VALID_LETTERS:
print(f"new_letter is not a valid letter. Please enter a letter a-z")
elif not new_letter: # Empty strings evaluate to False
print("Please enter a letter before pressing enter.")
elif new_letter in guessed_letters:
print("You already guessed new_letter before!")
else:
guessed_letters.append(new_letter):
input_valid = True
This is pretty close to your input. But I put it in a loop, so if we have an invalid input, we don't have to traverse the entire game loop again. I use a pretty simple signalling boolean to keep it going, but perhaps there's more elegant ways to do this. Never wrong to keep it simple, though.
Right now, to play the game, all you have to do is call the game() function. But perhaps we want to play multiple games in series? Lets make our main() function:
def main():
game()
while "y" in input("Play again? [Y/n]").lower():
game()
Why is this in a separate function? Again, for the sake of extensiblility. Perhaps, we want to import our game from another module. If so, we don't want it to run when we import, but when we call a function. So we end the script with this guard:
if __name__ == "__main__":
main()
If we execute this file directly, this will run our main() function and let us play the game. But if we import it, it won't, so we can call the function when we want it.
$endgroup$
2
$begingroup$
Instead of definingVALID_LETTERSmanually:from string import ascii_lowercase as VALID_LETTERS?
$endgroup$
– Mathias Ettinger
3 hours ago
$begingroup$
Would work just as well, but personally I wouldn't import for something like this. Unless you need localization for countries with different alphabets, but in that case you'd need to go a lot more complicated than that.
$endgroup$
– Gloweye
3 hours ago
$begingroup$
If the whitelist is just lower case letters, one can also check the entire input string usingword.islower() and word.isascii().
$endgroup$
– GZ0
2 hours ago
$begingroup$
Using both, you mean? Yeah, it could work, but I don't think the current is a bad solution. I wanted to keep it someone similar to the question, and I also reused it to check the letters again. And if it was performance limited, I'd useord()and check it's value instead. However, This is both readable and generalizable while not being slow or an antipattern. So that's why I think this is the better way.
$endgroup$
– Gloweye
2 hours ago
add a comment
|
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/4.0/"u003ecc by-sa 4.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Python Novice is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f229552%2fmy-first-hangman-game-in-python%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
While it's clear that you're new to python, it's still pretty good that you got it to run first time. Good job!
Input Validation
Currently, you have a list of forbidden characters. While that can work, by default python allows unicode input. That means there's literally thousands of letters someone can input. I suggest instead using a whitelist of valid inputs. Like this:
VALID_LETTERS = "abcdefghijklmnopqrstuvwxyz" # This won't ever change, and typically this sort
# of things is a module-level variable.
def valid_word(word):
for letter in word:
if letter not in VALID_LETTERS:
print(f"Letter letter is not a valid letter. Please use just a-z.")
return
if len(word) > 8: # This was 12 in your script. Little mistake? And you might want a
# module-level constant for this as well.
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
main() and game()
Generally, in python we never execute code at the moment the module is loaded. Read here for details, and how we guard against it. I'll come back to this later. For now, it means we should put as much as possible into functions - lets call them main() and game().
main() will be the main menu function. Since we don't have a true menu, this will basically start a game, and perhaps ask if the player wants to play again.
game() will be a function that lets us play a single game. Most of this will be a loop that gets player input and calculates the game state. Basically everything in your script that isn't another function should be in the game() function.
I'll cut some variables we don't need later on. I'll explain them when we get to where we use them.
Like this:
def game():
# Variable naming is important. This is an example of a good name.
guessed_letters = []
# ill_chars = [...] We already have this variable before here!
# Lets assign the parts directly. We can index them as in "3rd part".
# And for ourselves, we comment the meaning to make sure we still know what it means a year
# from now.
hangman_parts = [
" O", # Head
" /", # Left Arm
"|" , # Torse
"\", # Right Arm
"/", # Left Leg
" \" # Right Leg
]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
# Naming: should be word_is_valid, to be consistent with your other variables.
word_is_valid = False
while not word_is_valid:
word = input("nnnChoose any wordnnn").lower()
word_is_valid = valid_word(word)
# With this loop condition, it'll keep asking until valid_word returns True. So we can just
# assign that value to word_is_valid directly.
# Since this is the word we're trying to guess, lets just push it upwards in
# the console a lot, so we won't see it all the time:
print("n" * 100) # Prints 100 newlines.
# We don't need word_list, just word will do. We don't need spaces either.
while True: # Main game loop. We don't need to check for anything, we'll just use break or
# return to get out.
Now we're going to use a list comprehension. It's almost the same as a generator expression, but it gives us a true list, so we can ask python how long it is:
# Calculate how many bad letters we have with a list comprehension:
bad_letter = len([letter for letter in guessed_letters if letter not in word])
draw_hangman(bad_letter)
if bad_letter > 5:
print("nnnnYOU GOT HUNG")
return
print("nnnn")
You used to draw your hangman at two different places, while it's more practical to do it in one place. It also ensures that if you want to change something, you'll only have to change it once.
I've also changed how to draw it. It's much more useful to have a function for this drawing operation, and have it calculate how to draw from the number of bad letters so far. This makes the drawing independent of the current game state. Here's the version I came up with, based on your modifications of hangman_final:
def draw_hangman(bad_letter):
print(hangman_final, end="")
if bad_letter > 0: # Head
print(hangman_progress[1] + hangman_parts[0], end="")
if bad_letter == 2: # Torso and arms
print(hangman_progress[2] + " " + hangman_parts[2], end="")
elif bad_letter == 3:
print(hangman_progress[2] + "".join(hangman_parts[1:3]), end="")
elif bad_letter > 3:
print(hangman_progress[2] + "".join(hangman_parts[1:4]), end="")
if bad_letter > 4: # Legs
print(hangman_progress[3] + " " + hangman_parts[4], end="")
if bad_letter > 5:
print(hangman_parts[5], end="")
Here we need to print the letters we guessed, and underscores otherwise. We can calculate and print it in one line:
print(" ".join(letter for letter in word if letter in guessed_letters else "_"))
For every letter, this will print it if it's contained in guessed_letters, and otherwise print an underscore. This is a generator expression. This one is almost equal to the function:
def make_word(word, guessed_letters):
result = []
for letter in word:
if letter in guessed_letters:
result.append(letter)
else:
result.append("_")
return result
I say almost, because it's not exactly a list that comes out of it, even if it acts the same if you use it in a for loop or feed it to a function that does, like "".join(). It also acts more like a loop than a function, but that's not really important right now.
We feed the result to the join. And note that we feed it to " ".join(), with a space in between, which will put a space between every letter like you did before.
print(f"nnThe word has: len(word) letters.") # f-strings are shorter, and easy to use.
print(f"nYou've tried the following letters:nn''.join(guessed_letters) nn")
# For a string literal inside an f-string, use the other type of quotes
# This is another generator expression - this one returns a not-quite-list of booleans, and
# the all() builtin function returns True if and only if all results of the generator
# expression are True.
if all(letter in guessed_letters for letter in word):
print(f"You WIN!nThe word was: word")
return
input_valid = False
while not input_valid:
new_letter = input("nnPlease choose a letter: nnnn").lower()
# VERY GOOD that you lowercase it! Prevents tons of weird behaviour.
if len(new_letter) > 1:
print("Please enter a single letter only.")
elif new_letter not in VALID_LETTERS:
print(f"new_letter is not a valid letter. Please enter a letter a-z")
elif not new_letter: # Empty strings evaluate to False
print("Please enter a letter before pressing enter.")
elif new_letter in guessed_letters:
print("You already guessed new_letter before!")
else:
guessed_letters.append(new_letter):
input_valid = True
This is pretty close to your input. But I put it in a loop, so if we have an invalid input, we don't have to traverse the entire game loop again. I use a pretty simple signalling boolean to keep it going, but perhaps there's more elegant ways to do this. Never wrong to keep it simple, though.
Right now, to play the game, all you have to do is call the game() function. But perhaps we want to play multiple games in series? Lets make our main() function:
def main():
game()
while "y" in input("Play again? [Y/n]").lower():
game()
Why is this in a separate function? Again, for the sake of extensiblility. Perhaps, we want to import our game from another module. If so, we don't want it to run when we import, but when we call a function. So we end the script with this guard:
if __name__ == "__main__":
main()
If we execute this file directly, this will run our main() function and let us play the game. But if we import it, it won't, so we can call the function when we want it.
$endgroup$
2
$begingroup$
Instead of definingVALID_LETTERSmanually:from string import ascii_lowercase as VALID_LETTERS?
$endgroup$
– Mathias Ettinger
3 hours ago
$begingroup$
Would work just as well, but personally I wouldn't import for something like this. Unless you need localization for countries with different alphabets, but in that case you'd need to go a lot more complicated than that.
$endgroup$
– Gloweye
3 hours ago
$begingroup$
If the whitelist is just lower case letters, one can also check the entire input string usingword.islower() and word.isascii().
$endgroup$
– GZ0
2 hours ago
$begingroup$
Using both, you mean? Yeah, it could work, but I don't think the current is a bad solution. I wanted to keep it someone similar to the question, and I also reused it to check the letters again. And if it was performance limited, I'd useord()and check it's value instead. However, This is both readable and generalizable while not being slow or an antipattern. So that's why I think this is the better way.
$endgroup$
– Gloweye
2 hours ago
add a comment
|
$begingroup$
While it's clear that you're new to python, it's still pretty good that you got it to run first time. Good job!
Input Validation
Currently, you have a list of forbidden characters. While that can work, by default python allows unicode input. That means there's literally thousands of letters someone can input. I suggest instead using a whitelist of valid inputs. Like this:
VALID_LETTERS = "abcdefghijklmnopqrstuvwxyz" # This won't ever change, and typically this sort
# of things is a module-level variable.
def valid_word(word):
for letter in word:
if letter not in VALID_LETTERS:
print(f"Letter letter is not a valid letter. Please use just a-z.")
return
if len(word) > 8: # This was 12 in your script. Little mistake? And you might want a
# module-level constant for this as well.
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
main() and game()
Generally, in python we never execute code at the moment the module is loaded. Read here for details, and how we guard against it. I'll come back to this later. For now, it means we should put as much as possible into functions - lets call them main() and game().
main() will be the main menu function. Since we don't have a true menu, this will basically start a game, and perhaps ask if the player wants to play again.
game() will be a function that lets us play a single game. Most of this will be a loop that gets player input and calculates the game state. Basically everything in your script that isn't another function should be in the game() function.
I'll cut some variables we don't need later on. I'll explain them when we get to where we use them.
Like this:
def game():
# Variable naming is important. This is an example of a good name.
guessed_letters = []
# ill_chars = [...] We already have this variable before here!
# Lets assign the parts directly. We can index them as in "3rd part".
# And for ourselves, we comment the meaning to make sure we still know what it means a year
# from now.
hangman_parts = [
" O", # Head
" /", # Left Arm
"|" , # Torse
"\", # Right Arm
"/", # Left Leg
" \" # Right Leg
]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
# Naming: should be word_is_valid, to be consistent with your other variables.
word_is_valid = False
while not word_is_valid:
word = input("nnnChoose any wordnnn").lower()
word_is_valid = valid_word(word)
# With this loop condition, it'll keep asking until valid_word returns True. So we can just
# assign that value to word_is_valid directly.
# Since this is the word we're trying to guess, lets just push it upwards in
# the console a lot, so we won't see it all the time:
print("n" * 100) # Prints 100 newlines.
# We don't need word_list, just word will do. We don't need spaces either.
while True: # Main game loop. We don't need to check for anything, we'll just use break or
# return to get out.
Now we're going to use a list comprehension. It's almost the same as a generator expression, but it gives us a true list, so we can ask python how long it is:
# Calculate how many bad letters we have with a list comprehension:
bad_letter = len([letter for letter in guessed_letters if letter not in word])
draw_hangman(bad_letter)
if bad_letter > 5:
print("nnnnYOU GOT HUNG")
return
print("nnnn")
You used to draw your hangman at two different places, while it's more practical to do it in one place. It also ensures that if you want to change something, you'll only have to change it once.
I've also changed how to draw it. It's much more useful to have a function for this drawing operation, and have it calculate how to draw from the number of bad letters so far. This makes the drawing independent of the current game state. Here's the version I came up with, based on your modifications of hangman_final:
def draw_hangman(bad_letter):
print(hangman_final, end="")
if bad_letter > 0: # Head
print(hangman_progress[1] + hangman_parts[0], end="")
if bad_letter == 2: # Torso and arms
print(hangman_progress[2] + " " + hangman_parts[2], end="")
elif bad_letter == 3:
print(hangman_progress[2] + "".join(hangman_parts[1:3]), end="")
elif bad_letter > 3:
print(hangman_progress[2] + "".join(hangman_parts[1:4]), end="")
if bad_letter > 4: # Legs
print(hangman_progress[3] + " " + hangman_parts[4], end="")
if bad_letter > 5:
print(hangman_parts[5], end="")
Here we need to print the letters we guessed, and underscores otherwise. We can calculate and print it in one line:
print(" ".join(letter for letter in word if letter in guessed_letters else "_"))
For every letter, this will print it if it's contained in guessed_letters, and otherwise print an underscore. This is a generator expression. This one is almost equal to the function:
def make_word(word, guessed_letters):
result = []
for letter in word:
if letter in guessed_letters:
result.append(letter)
else:
result.append("_")
return result
I say almost, because it's not exactly a list that comes out of it, even if it acts the same if you use it in a for loop or feed it to a function that does, like "".join(). It also acts more like a loop than a function, but that's not really important right now.
We feed the result to the join. And note that we feed it to " ".join(), with a space in between, which will put a space between every letter like you did before.
print(f"nnThe word has: len(word) letters.") # f-strings are shorter, and easy to use.
print(f"nYou've tried the following letters:nn''.join(guessed_letters) nn")
# For a string literal inside an f-string, use the other type of quotes
# This is another generator expression - this one returns a not-quite-list of booleans, and
# the all() builtin function returns True if and only if all results of the generator
# expression are True.
if all(letter in guessed_letters for letter in word):
print(f"You WIN!nThe word was: word")
return
input_valid = False
while not input_valid:
new_letter = input("nnPlease choose a letter: nnnn").lower()
# VERY GOOD that you lowercase it! Prevents tons of weird behaviour.
if len(new_letter) > 1:
print("Please enter a single letter only.")
elif new_letter not in VALID_LETTERS:
print(f"new_letter is not a valid letter. Please enter a letter a-z")
elif not new_letter: # Empty strings evaluate to False
print("Please enter a letter before pressing enter.")
elif new_letter in guessed_letters:
print("You already guessed new_letter before!")
else:
guessed_letters.append(new_letter):
input_valid = True
This is pretty close to your input. But I put it in a loop, so if we have an invalid input, we don't have to traverse the entire game loop again. I use a pretty simple signalling boolean to keep it going, but perhaps there's more elegant ways to do this. Never wrong to keep it simple, though.
Right now, to play the game, all you have to do is call the game() function. But perhaps we want to play multiple games in series? Lets make our main() function:
def main():
game()
while "y" in input("Play again? [Y/n]").lower():
game()
Why is this in a separate function? Again, for the sake of extensiblility. Perhaps, we want to import our game from another module. If so, we don't want it to run when we import, but when we call a function. So we end the script with this guard:
if __name__ == "__main__":
main()
If we execute this file directly, this will run our main() function and let us play the game. But if we import it, it won't, so we can call the function when we want it.
$endgroup$
2
$begingroup$
Instead of definingVALID_LETTERSmanually:from string import ascii_lowercase as VALID_LETTERS?
$endgroup$
– Mathias Ettinger
3 hours ago
$begingroup$
Would work just as well, but personally I wouldn't import for something like this. Unless you need localization for countries with different alphabets, but in that case you'd need to go a lot more complicated than that.
$endgroup$
– Gloweye
3 hours ago
$begingroup$
If the whitelist is just lower case letters, one can also check the entire input string usingword.islower() and word.isascii().
$endgroup$
– GZ0
2 hours ago
$begingroup$
Using both, you mean? Yeah, it could work, but I don't think the current is a bad solution. I wanted to keep it someone similar to the question, and I also reused it to check the letters again. And if it was performance limited, I'd useord()and check it's value instead. However, This is both readable and generalizable while not being slow or an antipattern. So that's why I think this is the better way.
$endgroup$
– Gloweye
2 hours ago
add a comment
|
$begingroup$
While it's clear that you're new to python, it's still pretty good that you got it to run first time. Good job!
Input Validation
Currently, you have a list of forbidden characters. While that can work, by default python allows unicode input. That means there's literally thousands of letters someone can input. I suggest instead using a whitelist of valid inputs. Like this:
VALID_LETTERS = "abcdefghijklmnopqrstuvwxyz" # This won't ever change, and typically this sort
# of things is a module-level variable.
def valid_word(word):
for letter in word:
if letter not in VALID_LETTERS:
print(f"Letter letter is not a valid letter. Please use just a-z.")
return
if len(word) > 8: # This was 12 in your script. Little mistake? And you might want a
# module-level constant for this as well.
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
main() and game()
Generally, in python we never execute code at the moment the module is loaded. Read here for details, and how we guard against it. I'll come back to this later. For now, it means we should put as much as possible into functions - lets call them main() and game().
main() will be the main menu function. Since we don't have a true menu, this will basically start a game, and perhaps ask if the player wants to play again.
game() will be a function that lets us play a single game. Most of this will be a loop that gets player input and calculates the game state. Basically everything in your script that isn't another function should be in the game() function.
I'll cut some variables we don't need later on. I'll explain them when we get to where we use them.
Like this:
def game():
# Variable naming is important. This is an example of a good name.
guessed_letters = []
# ill_chars = [...] We already have this variable before here!
# Lets assign the parts directly. We can index them as in "3rd part".
# And for ourselves, we comment the meaning to make sure we still know what it means a year
# from now.
hangman_parts = [
" O", # Head
" /", # Left Arm
"|" , # Torse
"\", # Right Arm
"/", # Left Leg
" \" # Right Leg
]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
# Naming: should be word_is_valid, to be consistent with your other variables.
word_is_valid = False
while not word_is_valid:
word = input("nnnChoose any wordnnn").lower()
word_is_valid = valid_word(word)
# With this loop condition, it'll keep asking until valid_word returns True. So we can just
# assign that value to word_is_valid directly.
# Since this is the word we're trying to guess, lets just push it upwards in
# the console a lot, so we won't see it all the time:
print("n" * 100) # Prints 100 newlines.
# We don't need word_list, just word will do. We don't need spaces either.
while True: # Main game loop. We don't need to check for anything, we'll just use break or
# return to get out.
Now we're going to use a list comprehension. It's almost the same as a generator expression, but it gives us a true list, so we can ask python how long it is:
# Calculate how many bad letters we have with a list comprehension:
bad_letter = len([letter for letter in guessed_letters if letter not in word])
draw_hangman(bad_letter)
if bad_letter > 5:
print("nnnnYOU GOT HUNG")
return
print("nnnn")
You used to draw your hangman at two different places, while it's more practical to do it in one place. It also ensures that if you want to change something, you'll only have to change it once.
I've also changed how to draw it. It's much more useful to have a function for this drawing operation, and have it calculate how to draw from the number of bad letters so far. This makes the drawing independent of the current game state. Here's the version I came up with, based on your modifications of hangman_final:
def draw_hangman(bad_letter):
print(hangman_final, end="")
if bad_letter > 0: # Head
print(hangman_progress[1] + hangman_parts[0], end="")
if bad_letter == 2: # Torso and arms
print(hangman_progress[2] + " " + hangman_parts[2], end="")
elif bad_letter == 3:
print(hangman_progress[2] + "".join(hangman_parts[1:3]), end="")
elif bad_letter > 3:
print(hangman_progress[2] + "".join(hangman_parts[1:4]), end="")
if bad_letter > 4: # Legs
print(hangman_progress[3] + " " + hangman_parts[4], end="")
if bad_letter > 5:
print(hangman_parts[5], end="")
Here we need to print the letters we guessed, and underscores otherwise. We can calculate and print it in one line:
print(" ".join(letter for letter in word if letter in guessed_letters else "_"))
For every letter, this will print it if it's contained in guessed_letters, and otherwise print an underscore. This is a generator expression. This one is almost equal to the function:
def make_word(word, guessed_letters):
result = []
for letter in word:
if letter in guessed_letters:
result.append(letter)
else:
result.append("_")
return result
I say almost, because it's not exactly a list that comes out of it, even if it acts the same if you use it in a for loop or feed it to a function that does, like "".join(). It also acts more like a loop than a function, but that's not really important right now.
We feed the result to the join. And note that we feed it to " ".join(), with a space in between, which will put a space between every letter like you did before.
print(f"nnThe word has: len(word) letters.") # f-strings are shorter, and easy to use.
print(f"nYou've tried the following letters:nn''.join(guessed_letters) nn")
# For a string literal inside an f-string, use the other type of quotes
# This is another generator expression - this one returns a not-quite-list of booleans, and
# the all() builtin function returns True if and only if all results of the generator
# expression are True.
if all(letter in guessed_letters for letter in word):
print(f"You WIN!nThe word was: word")
return
input_valid = False
while not input_valid:
new_letter = input("nnPlease choose a letter: nnnn").lower()
# VERY GOOD that you lowercase it! Prevents tons of weird behaviour.
if len(new_letter) > 1:
print("Please enter a single letter only.")
elif new_letter not in VALID_LETTERS:
print(f"new_letter is not a valid letter. Please enter a letter a-z")
elif not new_letter: # Empty strings evaluate to False
print("Please enter a letter before pressing enter.")
elif new_letter in guessed_letters:
print("You already guessed new_letter before!")
else:
guessed_letters.append(new_letter):
input_valid = True
This is pretty close to your input. But I put it in a loop, so if we have an invalid input, we don't have to traverse the entire game loop again. I use a pretty simple signalling boolean to keep it going, but perhaps there's more elegant ways to do this. Never wrong to keep it simple, though.
Right now, to play the game, all you have to do is call the game() function. But perhaps we want to play multiple games in series? Lets make our main() function:
def main():
game()
while "y" in input("Play again? [Y/n]").lower():
game()
Why is this in a separate function? Again, for the sake of extensiblility. Perhaps, we want to import our game from another module. If so, we don't want it to run when we import, but when we call a function. So we end the script with this guard:
if __name__ == "__main__":
main()
If we execute this file directly, this will run our main() function and let us play the game. But if we import it, it won't, so we can call the function when we want it.
$endgroup$
While it's clear that you're new to python, it's still pretty good that you got it to run first time. Good job!
Input Validation
Currently, you have a list of forbidden characters. While that can work, by default python allows unicode input. That means there's literally thousands of letters someone can input. I suggest instead using a whitelist of valid inputs. Like this:
VALID_LETTERS = "abcdefghijklmnopqrstuvwxyz" # This won't ever change, and typically this sort
# of things is a module-level variable.
def valid_word(word):
for letter in word:
if letter not in VALID_LETTERS:
print(f"Letter letter is not a valid letter. Please use just a-z.")
return
if len(word) > 8: # This was 12 in your script. Little mistake? And you might want a
# module-level constant for this as well.
print("nnError: Word is too long. Use a word with eight characters or less.")
return False
return True
main() and game()
Generally, in python we never execute code at the moment the module is loaded. Read here for details, and how we guard against it. I'll come back to this later. For now, it means we should put as much as possible into functions - lets call them main() and game().
main() will be the main menu function. Since we don't have a true menu, this will basically start a game, and perhaps ask if the player wants to play again.
game() will be a function that lets us play a single game. Most of this will be a loop that gets player input and calculates the game state. Basically everything in your script that isn't another function should be in the game() function.
I'll cut some variables we don't need later on. I'll explain them when we get to where we use them.
Like this:
def game():
# Variable naming is important. This is an example of a good name.
guessed_letters = []
# ill_chars = [...] We already have this variable before here!
# Lets assign the parts directly. We can index them as in "3rd part".
# And for ourselves, we comment the meaning to make sure we still know what it means a year
# from now.
hangman_parts = [
" O", # Head
" /", # Left Arm
"|" , # Torse
"\", # Right Arm
"/", # Left Leg
" \" # Right Leg
]
hangman_progress = ["",
"|",
"n|",
"n|"]
hangman_final = ["|~~~~|n"]
# Check if word is valid
# Naming: should be word_is_valid, to be consistent with your other variables.
word_is_valid = False
while not word_is_valid:
word = input("nnnChoose any wordnnn").lower()
word_is_valid = valid_word(word)
# With this loop condition, it'll keep asking until valid_word returns True. So we can just
# assign that value to word_is_valid directly.
# Since this is the word we're trying to guess, lets just push it upwards in
# the console a lot, so we won't see it all the time:
print("n" * 100) # Prints 100 newlines.
# We don't need word_list, just word will do. We don't need spaces either.
while True: # Main game loop. We don't need to check for anything, we'll just use break or
# return to get out.
Now we're going to use a list comprehension. It's almost the same as a generator expression, but it gives us a true list, so we can ask python how long it is:
# Calculate how many bad letters we have with a list comprehension:
bad_letter = len([letter for letter in guessed_letters if letter not in word])
draw_hangman(bad_letter)
if bad_letter > 5:
print("nnnnYOU GOT HUNG")
return
print("nnnn")
You used to draw your hangman at two different places, while it's more practical to do it in one place. It also ensures that if you want to change something, you'll only have to change it once.
I've also changed how to draw it. It's much more useful to have a function for this drawing operation, and have it calculate how to draw from the number of bad letters so far. This makes the drawing independent of the current game state. Here's the version I came up with, based on your modifications of hangman_final:
def draw_hangman(bad_letter):
print(hangman_final, end="")
if bad_letter > 0: # Head
print(hangman_progress[1] + hangman_parts[0], end="")
if bad_letter == 2: # Torso and arms
print(hangman_progress[2] + " " + hangman_parts[2], end="")
elif bad_letter == 3:
print(hangman_progress[2] + "".join(hangman_parts[1:3]), end="")
elif bad_letter > 3:
print(hangman_progress[2] + "".join(hangman_parts[1:4]), end="")
if bad_letter > 4: # Legs
print(hangman_progress[3] + " " + hangman_parts[4], end="")
if bad_letter > 5:
print(hangman_parts[5], end="")
Here we need to print the letters we guessed, and underscores otherwise. We can calculate and print it in one line:
print(" ".join(letter for letter in word if letter in guessed_letters else "_"))
For every letter, this will print it if it's contained in guessed_letters, and otherwise print an underscore. This is a generator expression. This one is almost equal to the function:
def make_word(word, guessed_letters):
result = []
for letter in word:
if letter in guessed_letters:
result.append(letter)
else:
result.append("_")
return result
I say almost, because it's not exactly a list that comes out of it, even if it acts the same if you use it in a for loop or feed it to a function that does, like "".join(). It also acts more like a loop than a function, but that's not really important right now.
We feed the result to the join. And note that we feed it to " ".join(), with a space in between, which will put a space between every letter like you did before.
print(f"nnThe word has: len(word) letters.") # f-strings are shorter, and easy to use.
print(f"nYou've tried the following letters:nn''.join(guessed_letters) nn")
# For a string literal inside an f-string, use the other type of quotes
# This is another generator expression - this one returns a not-quite-list of booleans, and
# the all() builtin function returns True if and only if all results of the generator
# expression are True.
if all(letter in guessed_letters for letter in word):
print(f"You WIN!nThe word was: word")
return
input_valid = False
while not input_valid:
new_letter = input("nnPlease choose a letter: nnnn").lower()
# VERY GOOD that you lowercase it! Prevents tons of weird behaviour.
if len(new_letter) > 1:
print("Please enter a single letter only.")
elif new_letter not in VALID_LETTERS:
print(f"new_letter is not a valid letter. Please enter a letter a-z")
elif not new_letter: # Empty strings evaluate to False
print("Please enter a letter before pressing enter.")
elif new_letter in guessed_letters:
print("You already guessed new_letter before!")
else:
guessed_letters.append(new_letter):
input_valid = True
This is pretty close to your input. But I put it in a loop, so if we have an invalid input, we don't have to traverse the entire game loop again. I use a pretty simple signalling boolean to keep it going, but perhaps there's more elegant ways to do this. Never wrong to keep it simple, though.
Right now, to play the game, all you have to do is call the game() function. But perhaps we want to play multiple games in series? Lets make our main() function:
def main():
game()
while "y" in input("Play again? [Y/n]").lower():
game()
Why is this in a separate function? Again, for the sake of extensiblility. Perhaps, we want to import our game from another module. If so, we don't want it to run when we import, but when we call a function. So we end the script with this guard:
if __name__ == "__main__":
main()
If we execute this file directly, this will run our main() function and let us play the game. But if we import it, it won't, so we can call the function when we want it.
edited 3 hours ago
answered 7 hours ago
GloweyeGloweye
6261 silver badge6 bronze badges
6261 silver badge6 bronze badges
2
$begingroup$
Instead of definingVALID_LETTERSmanually:from string import ascii_lowercase as VALID_LETTERS?
$endgroup$
– Mathias Ettinger
3 hours ago
$begingroup$
Would work just as well, but personally I wouldn't import for something like this. Unless you need localization for countries with different alphabets, but in that case you'd need to go a lot more complicated than that.
$endgroup$
– Gloweye
3 hours ago
$begingroup$
If the whitelist is just lower case letters, one can also check the entire input string usingword.islower() and word.isascii().
$endgroup$
– GZ0
2 hours ago
$begingroup$
Using both, you mean? Yeah, it could work, but I don't think the current is a bad solution. I wanted to keep it someone similar to the question, and I also reused it to check the letters again. And if it was performance limited, I'd useord()and check it's value instead. However, This is both readable and generalizable while not being slow or an antipattern. So that's why I think this is the better way.
$endgroup$
– Gloweye
2 hours ago
add a comment
|
2
$begingroup$
Instead of definingVALID_LETTERSmanually:from string import ascii_lowercase as VALID_LETTERS?
$endgroup$
– Mathias Ettinger
3 hours ago
$begingroup$
Would work just as well, but personally I wouldn't import for something like this. Unless you need localization for countries with different alphabets, but in that case you'd need to go a lot more complicated than that.
$endgroup$
– Gloweye
3 hours ago
$begingroup$
If the whitelist is just lower case letters, one can also check the entire input string usingword.islower() and word.isascii().
$endgroup$
– GZ0
2 hours ago
$begingroup$
Using both, you mean? Yeah, it could work, but I don't think the current is a bad solution. I wanted to keep it someone similar to the question, and I also reused it to check the letters again. And if it was performance limited, I'd useord()and check it's value instead. However, This is both readable and generalizable while not being slow or an antipattern. So that's why I think this is the better way.
$endgroup$
– Gloweye
2 hours ago
2
2
$begingroup$
Instead of defining
VALID_LETTERS manually: from string import ascii_lowercase as VALID_LETTERS?$endgroup$
– Mathias Ettinger
3 hours ago
$begingroup$
Instead of defining
VALID_LETTERS manually: from string import ascii_lowercase as VALID_LETTERS?$endgroup$
– Mathias Ettinger
3 hours ago
$begingroup$
Would work just as well, but personally I wouldn't import for something like this. Unless you need localization for countries with different alphabets, but in that case you'd need to go a lot more complicated than that.
$endgroup$
– Gloweye
3 hours ago
$begingroup$
Would work just as well, but personally I wouldn't import for something like this. Unless you need localization for countries with different alphabets, but in that case you'd need to go a lot more complicated than that.
$endgroup$
– Gloweye
3 hours ago
$begingroup$
If the whitelist is just lower case letters, one can also check the entire input string using
word.islower() and word.isascii().$endgroup$
– GZ0
2 hours ago
$begingroup$
If the whitelist is just lower case letters, one can also check the entire input string using
word.islower() and word.isascii().$endgroup$
– GZ0
2 hours ago
$begingroup$
Using both, you mean? Yeah, it could work, but I don't think the current is a bad solution. I wanted to keep it someone similar to the question, and I also reused it to check the letters again. And if it was performance limited, I'd use
ord() and check it's value instead. However, This is both readable and generalizable while not being slow or an antipattern. So that's why I think this is the better way.$endgroup$
– Gloweye
2 hours ago
$begingroup$
Using both, you mean? Yeah, it could work, but I don't think the current is a bad solution. I wanted to keep it someone similar to the question, and I also reused it to check the letters again. And if it was performance limited, I'd use
ord() and check it's value instead. However, This is both readable and generalizable while not being slow or an antipattern. So that's why I think this is the better way.$endgroup$
– Gloweye
2 hours ago
add a comment
|
Python Novice is a new contributor. Be nice, and check out our Code of Conduct.
Python Novice is a new contributor. Be nice, and check out our Code of Conduct.
Python Novice is a new contributor. Be nice, and check out our Code of Conduct.
Python Novice is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f229552%2fmy-first-hangman-game-in-python%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
$begingroup$
Welcome to Code Review! You can take the tour for a quick overview of the site.
$endgroup$
– L. F.
5 hours ago