Skip to content

Instantly share code, notes, and snippets.

@nitobuendia
Created July 15, 2019 11:38
Show Gist options
  • Save nitobuendia/5a0517f0d72f7043a440c4bd67a9d599 to your computer and use it in GitHub Desktop.
Save nitobuendia/5a0517f0d72f7043a440c4bd67a9d599 to your computer and use it in GitHub Desktop.
Hangman game - implemented in Python
"""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