Last active
September 1, 2022 09:38
-
-
Save conqp/b35d8a67769e405126ee1d3276d9a4b5 to your computer and use it in GitHub Desktop.
Simple hangman game
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
#! /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