Created
July 15, 2019 11:38
-
-
Save nitobuendia/5a0517f0d72f7043a440c4bd67a9d599 to your computer and use it in GitHub Desktop.
Hangman game - implemented in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Runs a Hangman game that can be played via terminal. | |
Prerequisites: | |
1. Download the code as hangman.py into any folder. | |
2. Get a .txt file called 'words.txt' with all the words that you want to include in the game. | |
>> Example: https://github.com/dwyl/english-words | |
To play: | |
- Run `python hangman` | |
This Hangman game features: | |
1. A simple interactive system that asks user for input for each letter and game. | |
2. A simple visual interfece that draws the hangman as the game progresses. | |
3. A simple scoring system that keeps track of the wins and losses. | |
4. Capability to change the words for any desired ones. | |
5. Capability of choosing level of difficulty based on a custom algorithm. | |
6. A custom algorithm that classifies words based on the probability of the letters and the repetition of them. | |
""" | |
import csv | |
import random | |
# Available letters. Selected English alphabet. | |
# Modify based on your language. | |
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
# Frequency of letters on a word in English. | |
# Obtained from: https://en.wikipedia.org/wiki/Letter_frequency | |
# Modify based on your language. | |
LETTER_PROBABILITY = { | |
'A': 8.167, | |
'B': 1.492, | |
'C': 2.782, | |
'D': 4.253, | |
'E': 12.70, | |
'F': 2.228, | |
'G': 2.015, | |
'H': 6.094, | |
'I': 6.966, | |
'J': 0.153, | |
'K': 0.772, | |
'L': 4.025, | |
'M': 2.406, | |
'N': 6.749, | |
'O': 7.507, | |
'P': 1.929, | |
'Q': 0.095, | |
'R': 5.987, | |
'S': 6.327, | |
'T': 9.056, | |
'U': 2.758, | |
'V': 0.978, | |
'W': 2.360, | |
'X': 0.150, | |
'Y': 1.974, | |
'Z': 0.074, | |
} | |
class Hangman(object): | |
"""Hangman game.""" | |
def __init__(self): | |
"""Initializes hangman game. | |
Loads words into memory, restarts scores and kicks off game by | |
allowing user to select difficulty level. | |
""" | |
self._game_words = self._load_words() | |
self._selected_words = set() | |
self._wins = 0 | |
self._loses = 0 | |
self._select_difficulty() | |
def _get_difficulty_input(self): | |
"""Requests and gets difficulty level from user. | |
Returns: | |
Input from user to difficulty prompt. | |
""" | |
difficulty = input('Select your difficulty: [E]asy, [M]edium, [H]ard\n') | |
return difficulty.upper() | |
def _ask_difficulty(self): | |
"""Gets difficulty level from user, and validates user input. | |
Returns: | |
Input from user to difficulty prompt. | |
Validated input must be: E, M, H. | |
""" | |
difficulty = self._get_difficulty_input() | |
while difficulty not in ('E', 'M', 'H'): | |
print( | |
'Type E for Easy words. ' | |
'Type M for Medium words. ' | |
'Type H for Hard words. ' | |
) | |
difficulty = self._get_difficulty_input() | |
return difficulty | |
def _select_difficulty(self): | |
"""Aks difficulty from user, and selects the set of words for the given difficulty. | |
After selecting difficulty, starts the game. | |
""" | |
difficulty = self._ask_difficulty() | |
if difficulty == 'H': | |
self._words = self._game_words['hard'] | |
elif difficulty == 'M': | |
self._words = self._game_words['medium'] | |
else: | |
self._words = self._game_words['easy'] | |
self._print_game_words() | |
self._start_game() | |
def _get_word_difficulty(self, word): | |
"""Gets the difficulty of a word. | |
This implements a custom algorithm to determine | |
the difficulty of a word based on: | |
- its length. | |
- the frequency of each letter. | |
- the ratio of repeated letters. | |
If word is too short or a plural, word gets excluded. | |
Args: | |
word: Word for which to obtain difficulty. | |
Returns: | |
Difficulty of the word: easy, medium, hard. | |
""" | |
word_length = len(word) | |
if word_length < 5: | |
return None | |
if word[-1] == 'S': | |
return None | |
word_unique_characters = set(list(word)) | |
unique_character_count = len(word_unique_characters) | |
repeated_letters = word_length - unique_character_count | |
repeated_ratio = (repeated_letters / word_length) + 1 | |
unique_ratio = unique_character_count / word_length | |
probability = 0 | |
for unique_character in word_unique_characters: | |
if unique_character not in LETTERS: | |
return None | |
count_letters = word.count(unique_character) | |
letter_probability = LETTER_PROBABILITY.get(unique_character, 0) / 100 | |
probability += letter_probability | |
probability = probability | |
difficulty = probability / unique_ratio | |
if difficulty < 0.25: | |
return 'hard' | |
elif difficulty < 0.4: | |
return 'medium' | |
else: | |
return 'easy' | |
def _load_words(self): | |
"""Load words from words.txt into memory. | |
Classifies each word into several difficulties. | |
Returns: | |
Dictionary containing a list of words for each difficulty. | |
""" | |
with open('words.txt', 'rt') as dictionary: | |
words = csv.reader(dictionary, delimiter=',') | |
game_words = { | |
'easy': [], | |
'medium': [], | |
'hard': [], | |
} | |
for word_line in words: | |
word = word_line[0].upper() | |
difficulty = self._get_word_difficulty(word) | |
if difficulty: | |
game_words[difficulty].append(word) | |
return game_words | |
def _print_game_words(self): | |
"""Prints length and sample words for each difficulty. | |
Used for debugging purposes only. | |
""" | |
print( | |
'E', len(self._game_words['easy']), self._game_words['easy'][0:5], | |
'\n\n\n' | |
'M', len(self._game_words['medium']), self._game_words['medium'][0:5], | |
'\n\n\n' | |
'H', len(self._game_words['hard']), self._game_words['hard'][0:5], | |
'' | |
) | |
def _choose_random_word(self): | |
"""Chooses a random word from the available set. | |
Returns: | |
Randomly selected word. | |
""" | |
selected_index = random.randint(0, len(self._words) - 1) | |
return self._words[selected_index] | |
def _select_word(self): | |
"""Selects a random word that was not previously selected. | |
Returns: | |
Randomly selected word. | |
Validation ensures not repeated within current game. | |
""" | |
word = self._choose_random_word() | |
while word in self._selected_words: | |
word = self._choose_random_word() | |
return word | |
def _get_letter_input(self): | |
"""Gets next letter for game from user. | |
Returns: | |
Input from user upon letter prompt. | |
""" | |
letter = input('Choose a letter: ') | |
return letter.upper() | |
def _ask_for_letter(self): | |
"""Asks and validates next letter from user. | |
Rejects input if: | |
- not just one letter. | |
- letter was already selected. | |
- invalid letter, not in selected set. | |
Returns: | |
Letter selected by user for current round. | |
Validation guarantees requisites. | |
""" | |
letter = self._get_letter_input() | |
while len(letter) > 1: | |
print('Must select one letter only.') | |
letter = self._get_letter_input() | |
while letter not in self._available_letters: | |
print('Letter {} was already chosen.'.format(letter)) | |
letter = self._get_letter_input() | |
return letter | |
def _update_available_letters(self, letter): | |
"""Updates available letters to select for current round. | |
Args: | |
letter: Selected letter that will be taking off the available letters. | |
""" | |
letter_position = self._available_letters.index(letter) | |
if letter_position > -1: | |
del self._available_letters[letter_position] | |
def _get_word_progress(self): | |
"""Generates a text showing the current round word progress. | |
Returns: | |
String showing the word progress. | |
Example: | |
p _ t a t _ | |
""" | |
word_display = '' | |
for character in self._selected_word: | |
if character in self._selected_letters: | |
word_display += character.upper() + ' ' | |
else: | |
word_display += '_ ' | |
return word_display | |
def _get_hangman(self): | |
""" | |
Generates a text showing the hangman progress of the current round. | |
Returns: | |
String showing the hangman progress. | |
Example (full hangman): | |
|‾‾‾‾‾| | |
| O | |
| /|\ | |
| / \ | |
| | |
‾‾‾‾‾‾‾‾‾‾ | |
""" | |
hangman = """ | |
|‾‾‾‾‾| | |
| {head} | |
| {left_arm}{body}{right_arm} | |
| {left_leg} {right_leg} | |
| | |
‾‾‾‾‾‾‾‾‾‾ | |
""".format( | |
head='O' if self._mistakes > 0 else ' ', | |
left_arm='/' if self._mistakes > 1 else ' ', | |
body='|' if self._mistakes > 2 else ' ', | |
right_arm='\\' if self._mistakes > 3 else ' ', | |
left_leg='/' if self._mistakes > 4 else ' ', | |
right_leg='\\' if self._mistakes > 5 else ' ', | |
) | |
return hangman | |
def _update_game_status(self, letter): | |
"""Updates game status for current game. | |
Adds one extra round. | |
Updates word progress. | |
Updates hangman progress. | |
Updates selected and available letters for round. | |
Updates win/lose game status. | |
Args: | |
letter: Currently selected letter. | |
""" | |
self._rounds += 1 | |
if letter not in self._selected_word: | |
self._mistakes += 1 | |
else: | |
self._correct_letters += self._selected_word.count(letter) | |
self._selected_letters.append(letter) | |
self._update_available_letters(letter) | |
self._is_lose = self._mistakes >= 6 | |
self._is_win = self._correct_letters >= len(self._selected_word) | |
def _print_game_status(self): | |
"""Prints current game status to terminal. | |
Prints: | |
- hangman progress. | |
- word progress. | |
- number of rounds played on current word. | |
- chosen letters. | |
- available letters. | |
""" | |
hangman = self._get_hangman() | |
word_display = self._get_word_progress() | |
print('\n\n\n') | |
print(hangman) | |
print('Round: {}'.format(self._rounds)) | |
print('Word: ', word_display) | |
print('Chosen letters:', ' '.join(self._selected_letters)) | |
print('Available letters: ', ' '.join(self._available_letters)) | |
def _get_new_game_input(self): | |
"""Asks user whether they want to continue playing. | |
Returns: | |
User input on whether to continue playing. | |
""" | |
play_again = input('Do you want to play again? [Y]es, [N]o\n') | |
return play_again.upper() | |
def _ask_new_game_input(self): | |
"""Asks user and validates whether they want to continue playing. | |
Returns: | |
User input on whether to continue playing. | |
Validation ensures output to be Y or N. | |
""" | |
play_again = self._get_new_game_input() | |
while play_again not in ('Y', 'N'): | |
print( | |
'Type Y if you want to play again. ' | |
'Type N if you do not want to play again. ' | |
) | |
play_again = self._get_new_game_input() | |
return play_again | |
def _ask_for_new_game(self): | |
"""Prints game results and asks for new game. | |
Prints: | |
- Current round result (win or loss). | |
- Total wins and total losses. | |
""" | |
print('\n\n\n') | |
if self._is_win: | |
self._wins += 1 | |
print('CONGRATULATIONS! You guessed: {}'.format(self._selected_word)) | |
elif self._is_lose: | |
self._loses += 1 | |
print('GAME OVER! The word was: {}'.format(self._selected_word)) | |
print('Wins: {} / Loses: {}'.format( | |
self._wins, self._loses | |
)) | |
play_again = self._ask_new_game_input() | |
if play_again == 'Y': | |
self._start_game() | |
def _set_up_new_game(self): | |
"""Set ups a new word round. | |
- Selects a new word. Adds word to selected words. | |
- Resets selected and available letters. | |
- Resets counters of rounds and mistakes. | |
- Resets end status (is_win / is_lose). | |
""" | |
self._selected_word = self._select_word() | |
self._selected_words.add(self._selected_word) | |
self._selected_letters = [] | |
self._available_letters = list(LETTERS) | |
self._correct_letters = 0 | |
self._rounds = 0 | |
self._mistakes = 0 | |
self._is_win = False | |
self._is_lose = False | |
def _play_round(self): | |
"""Executes one letter round. | |
- Asks user for new letter. | |
- Updates game progress. | |
- Prints game progress. | |
""" | |
letter = self._ask_for_letter() | |
self._update_game_status(letter) | |
self._print_game_status() | |
def _start_game(self): | |
"""Manages hangman game rounds. | |
- If round is completed: asks for new game. | |
- If round is not completed: asks for new letter. | |
""" | |
self._set_up_new_game() | |
self._print_game_status() | |
while not self._is_win and not self._is_lose: | |
self._play_round() | |
else: | |
self._ask_for_new_game() | |
game = Hangman() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment