Source code for hangman.model

# coding=utf-8
"""
hangman.model
~~~~~~~~~~~~~
"""
from __future__ import absolute_import

import re
from collections import namedtuple

from .utils import WordBank, GameLost, GameWon


[docs]class Hangman(object): """ The 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): if not answer: # Populate 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() # PUBLIC API # -------------------------------------------------------------------
[docs] def guess(self, letter): """Add letter to hits or misses.""" # 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
# INSTANCE PROPERTIES # ------------------------------------------------------------------- @property def misses(self): """List of misses.""" return sorted(list(self._misses)) @misses.setter def misses(self, letters): for letter in letters: self._add_miss(letter) @property def hits(self): """List of hits.""" return sorted(list(self._hits)) @hits.setter def hits(self, letters): for letter in letters: self._add_hit(letter) @property def remaining_turns(self): """Calculate number of turns remaining.""" return self.MAX_TURNS - len(self.misses) @property def status(self): """Build a string representation of status.""" hits = self.hits # calculated property def fill_in(letter): """Replace non-hits with `_`.""" return letter if letter in hits else '_' return ''.join(fill_in(letter) for letter in self.answer) # UTILITIES # ------------------------------------------------------------------- def _add_miss(self, value): """Add a letter to misses. Check for game over.""" self._misses.add(value.upper()) if self.remaining_turns <= 0: raise GameLost def _add_hit(self, value): """Add a letter to hits. Check for game won""" 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""" 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""" letter = str(letter).upper() return not not self._re_guess_rules.search(letter)
def __repr__(self): return repr(self._repr(self.status, self.misses, self.remaining_turns))