Source code for hangman.model

# coding=utf-8
"""
hangman.model
~~~~~~~~~~~~~

This module contains all of the game logic.
"""
from __future__ import absolute_import
import re
from collections import namedtuple
from hangman.utils import WordBank, GameOver, GameWon


[docs]class Hangman(object): """ The hangman game object contains the logic for managing the status of the game and raising key game related events. >>> from hangman.model import Hangman >>> game = Hangman(answer='hangman') >>> game.guess('a') hangman(status='_A___A_', misses=[], remaining_turns=10) >>> game.guess('n').guess('z').guess('e') hangman(status='_AN__AN', misses=['E', 'Z'], remaining_turns=8) >>> game.status '_AN__AN' >>> game.misses ['E', 'Z'] >>> game.remaining_turns 8 """ # CLASS PROPERTIES # ------------------------------------------------------------------- MAX_TURNS = 10 _re_answer_rules = re.compile('^[A-Z]{1,16}$') _re_guess_rules = re.compile('^[A-Z]$') _repr = namedtuple('hangman', ['status', 'misses', 'remaining_turns']) # CONSTRUCTOR # ------------------------------------------------------------------- def __init__(self, answer=None): """ Instantiate a new game. Populate answer if necessary. :param str answer: answer to game instance :raises: ValueError """ if not answer: answer = WordBank.get() # Validate answer. if not self.is_valid_answer(answer): raise ValueError("Word must be letters A-Z") self.answer = answer.upper() self._misses = set() self._hits = set() # INSTANCE PROPERTIES # ------------------------------------------------------------------- @property def misses(self): """ Get list of misses. :rtype: [str] """ return sorted(list(self._misses)) @misses.setter def misses(self, value): """ `self.misses` setter. Check for game over. :param value: A single letter. :raises: GameOver """ self._misses = set(value) if self.remaining_turns <= 0: raise GameOver @property def hits(self): """ Get list of hits. :rtype: [str] """ return sorted(list(self._hits)) @hits.setter def hits(self, value): """ `self.hits` setter. Check for game won. :param value: A single letter. :raises: GameWon """ self._hits = set(value) if self._hits == set(self.answer): raise GameWon @property def remaining_turns(self): """ Calculate number of turns remaining. :return: Number of turns remaining. :rtype: int """ return self.MAX_TURNS - len(self.misses) @property def status(self): """ Build a string representation of status with letters for hits and _ for unknowns. :return: game status as string :rtype: str """ hits = self.hits def fill_in(letter): return letter if letter in hits else '_' return ''.join(fill_in(letter) for letter in self.answer) # PUBLIC API # -------------------------------------------------------------------
[docs] def guess(self, letter): """ Check if guess is a hit or miss. :param str letter: Letter to check :return: self :rtype: Hangman :raises: ValueError """ # validate input if not self.is_valid_guess(letter): raise ValueError('Must be a letter A-Z') # add to hits or misses is_miss = letter.upper() not in self.answer if is_miss: self.add_miss(letter) else: self.add_hit(letter) return self # UTILITIES # -------------------------------------------------------------------
[docs] def add_miss(self, value): """ Add a miss to the model. Check for game over. :param value: A single letter. :raises: GameOver """ self._misses.add(value.upper()) if self.remaining_turns <= 0: raise GameOver
[docs] def add_hit(self, value): """ Add a hit to the model. Check for game won. :param value: A single letter. :raises: GameWon """ self._hits.add(value.upper()) if self._hits == set(self.answer): raise GameWon
[docs] def is_valid_answer(self, word): """ Validate answer. Letters only. Max:16 :param str word: Word to validate. :return: :rtype: bool """ word = str(word).upper() return not not self._re_answer_rules.search(word)
[docs] def is_valid_guess(self, letter): """ Validate guess. Letters only. Max:1 :param str letter: Letter to validate :return: :rtype: bool """ letter = str(letter).upper() return not not self._re_guess_rules.search(letter)
def __repr__(self): """ Build a human readable representation of self. :return: namedtuple with status, misses, and remaining_turns :rtype: namedtuple """ return repr(self._repr(self.status, self.misses, self.remaining_turns))