Skip to content

Instantly share code, notes, and snippets.

@conqp
Last active September 1, 2022 09:38
Show Gist options
  • Save conqp/b35d8a67769e405126ee1d3276d9a4b5 to your computer and use it in GitHub Desktop.
Save conqp/b35d8a67769e405126ee1d3276d9a4b5 to your computer and use it in GitHub Desktop.
Simple hangman game
#! /usr/bin/env python3
"""Hangman game."""
from os import linesep
from string import ascii_letters
from sys import exit, stderr
from typing import Iterator
class GameOver(Exception):
"""Indicates that the game is over."""
def __init__(self, *, won: bool):
super().__init__()
self.won = won
class Hangman:
"""A Hangman game."""
def __init__(self, text: str, max_tries: int):
self.text = text
self.max_tries = max_tries
self.hits = set()
self.misses = set()
def __str__(self):
return f'{self.player_text}{linesep}Tries left: {self.tries_left}'
@property
def guesses(self) -> set[str]:
"""Return a set of guessed letters."""
return self.hits | self.misses
@property
def letters(self) -> set[str]:
"""Return a set of letters in the text."""
return {char for char in self.text.casefold() if char in ascii_letters}
@property
def tries_left(self) -> int:
"""Return the amount of tried left."""
return self.max_tries - len(self.misses)
@property
def player_text(self) -> str:
"""Return the text to display for the player."""
return ''.join(self.player_chars)
@property
def player_chars(self) -> Iterator[str]:
"""Yield chars to display to the player."""
for char in self.text:
if char in ascii_letters and char.casefold() not in self.hits:
yield '_'
else:
yield char
def guess(self, letter: str) -> bool:
"""Guess a letter."""
if len(letter) != 1:
raise ValueError(f'Not a single letter: {letter}')
if letter not in ascii_letters:
raise ValueError(f'Invalid letter: {letter}')
if (casefold_letter := letter.casefold()) in self.guesses:
raise ValueError(f'You already guessed this letter: {letter}')
if hit := casefold_letter in self.letters:
self.hits.add(casefold_letter)
else:
self.misses.add(casefold_letter)
if self.tries_left <= 0:
raise GameOver(won=False)
if all(letter in self.hits for letter in self.letters):
raise GameOver(won=True)
return hit
def play(game: Hangman) -> bool:
"""Play a game."""
while True:
print(game)
letter = input('Guess a letter: ')
try:
hit = game.guess(letter)
except ValueError as error:
print(error, file=stderr)
continue
except GameOver as game_over:
print('The text was:', game.text)
if game_over.won:
print("Congratulations, you've won!")
return True
print("You lose!", file=stderr)
return False
print('Good guess!' if hit else 'Sorry, wrong letter!')
def main() -> int:
"""Play example game."""
phrase = input('Input phrase to guess: ')
if play(Hangman(phrase, max_tries=11)):
return 0
return 1
if __name__ == '__main__':
exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment