Source code for thekraf.gamemodels

"""
thekraf.gamemodels
==================

Models for use by the game controller
"""
import thekraf.config as cfg
import thekraf.diceutils as diceutils
from thekraf.score import Score


[docs]class Cycle(object): """A cycle or round of the game Each player gets one turn per cycle Attributes: pids (tuple[int]): IDs of players turns(list[Turn]): Turns in the cycle """ def __init__(self, **kwargs): self.pids = tuple(kwargs.get('pids', ())) self.turns = list(kwargs.get('turns', [])) if self.turns and not isinstance(self.turn, Turn): self.turns = [Turn(**d) for d in kwargs['turns']] @property def turn(self): """The current :class:`.Turn`""" if self.turns: return self.turns[-1] else: return None @property def complete(self): """True if all players have completed their turn for this round""" if len(self.pids) == len(self.turns) and self.turn.complete: return True else: return False
[docs]class Game(object): """Model for a game Attributes: mode (str): Game mode. Possible modes are: * 'rounds' -- The most points after a certain number of rounds * 'points' -- First player to reach a certain number of points goal (int): Value associated withe the game mode to determine the end of the game min_first_bank (int): The minimum running total of a turn required to bank the first time min_bank (int): The minimum running total of a turn required to bank after already being on the board pids (tuple[int]): IDs of players in the game cycles(list[Cycle]): Rounds of the game """ MODES = cfg.GAME_MODES def __init__(self, **kwargs): # Config game mode self.is_anonymous = kwargs.get('is_anonymous', False) self.mode = kwargs.get('mode', 'rounds') assert self.mode in cfg.GAME_MODES self.goal = self.MODES[self.mode]['goal'] self.min_first_bank = self.MODES[self.mode]['min_first_bank'] self.min_bank = self.MODES[self.mode]['min_bank'] self._players = () self.users = [] if self.is_anonymous: self.player_names = kwargs.get('users', ()) else: self.users = list(kwargs.get('users', ())) # If item not Cycle, create cycle from dictionary representation self.cycles = list(Cycle(**item) for item in kwargs.get('cycles', []) if not isinstance(item, Cycle)) self.opts = kwargs.get('opts') self._description = '' @property def description(self): chunks = [] if self.mode == 'rounds': chunks.append('Score the most points in {goal} rounds.') elif self.mode == 'points': chunks.append('Score {goal} points first.') if self.min_first_bank > 50: chunks.append('The minimum score to bank') if self.min_bank == self.min_first_bank: chunks.append('is {min_bank} points.') else: chunks.append('the first time is {min_first_bank}') if self.min_bank > 50: chunks.append( 'points, and {min_bank} points thereafter.' ) else: chunks.append('points.') d = {k: getattr(self, k) for k in self.MODES['rounds'].keys()} return ' '.join(chunks).format(**d) @property def players(self): if self.is_anonymous: return self._players else: return self.users @property def player(self): turn = self.turn if turn: for player in self.players: if player.id == turn.pid: return player return None @property def pids(self): return tuple(player.id for player in self.players) @property def cycle(self): """Cycle: The current cycle""" if self.cycles: return self.cycles[-1] else: return None @property def turn(self): """Turn: The current turn Returns: thekraf.gamemodels.Turn: """ if self.cycles: return self.cycles[-1].turn else: return None @property def player_names(self): return tuple(player.nickname for player in self.players) @player_names.setter def player_names(self, nicknames): self._players = tuple(Player(id=i, nickname=nickname) for i, nickname in enumerate(nicknames, start=-len(nicknames))) @property def turns(self): """collections.Generator[Turn]: All turns of all rounds""" return sum((cycle.turns for cycle in self.cycles), []) @property def score_per_turns(self): """dict[int, int]: Player ID -> total for each turn""" return {pid: tuple(turn.total for turn in self.turns if turn.pid == pid) for pid in self.pids} @property def totals(self): """dict[int, int]: Player ID -> running total for game""" return {pid: sum(turn.total for turn in self.turns if turn.pid == pid and turn.complete) for pid in self.pids} @property def complete(self): """bool: True if the goal of the game has been reached""" # At least, the round must be complete if not self.cycle.complete: return False if self.mode == 'rounds': if self.goal == len(self.cycles): return True elif self.mode == 'points': if any(total >= self.goal for total in self.totals.values()): return True return False
[docs]class Player(object): """A player of a game Attributes: id (int): Unique player ID name(str): Name of player """ def __init__(self, **kwargs): self.id = kwargs.get('id', 0) default_name = 'player-{}'.format(self.id) self.nickname = kwargs.get('nickname', default_name)
[docs]class Turn(object): """Series of rolls and scores until bank or bust Each player will have one Turn per Round. A turn consists of a series of rolls and scores. After scoring, the player may choose to continue rolling the remaining dice (or all dice if all dice have been scored), or bank the running total for the turn. After a roll, the player must score with at least one die. If the player is unable to score with any of the dice, the turn is busted. A busted turn results in the turned being scored as 0. Rolls and scores will usually be balanced; The dice are rolled, and then a portion of the rolled dice a scored. The exception is a busted turn. Since there is no way to score, the final roll, the number of rolls will be one more than the number of scores. Attributes: complete (bool): True of the turn is finished (either banked or busted) pid (int): Indicates the player playing the turn dice_score_pairs (list[tuple[tuple[int], int]]): Items in this list are tuples of the dice that were scored after each roll and the corresponding score roll_hist (list[tuple[int]]): History of rolls. Scored dice must be a subset of rolled dice. min_bank (int): Minimum running total for a turn required to bank """ FIRST_DICE_COUNT = 6 """int: Number of dice to roll at the beginning of a turn""" def __init__(self, **kwargs): self.complete = False self.pid = kwargs.get('pid', -1) # If reconstructing with a dict, make sure everything is the right type self.dice_score_pairs = [ (tuple(dice), score) for dice, score in kwargs.get('dice_score_pairs', []) ] self.roll_hist = list(map(tuple, kwargs.get('roll_hist', []))) self.min_bank = kwargs.get('min_bank', -1) self.opts_name = kwargs.get('opts_name') @property def busted(self): """bool: True if there is no way to score with the last roll""" if self.rolled and not Score.subscores(cfg.GAME_OPTS[self.opts_name], self.rolled): return True else: return False @property def subscores(self): """dict[str, int]: Scores for sub-combos of rolled""" return Score.subscores(cfg.GAME_OPTS[self.opts_name], self.rolled) @property def min_scorable(self): """tuple[tuple[int], int]: Best score with the least number of dice""" d = self.subscores if d: # Sort by the secondary key first (score) sorted_by_score = sorted(self.subscores.items(), key=lambda p: p[1], reverse=True) # Then, sort by num of dice dice_str, score = sorted(sorted_by_score, key=lambda p: len(p[0]))[0] return diceutils.DiceUtils.create_dice(dice_str), score else: return None @property def total(self): """int: Total of the scored dice, or 0 if busted""" if self.busted: return 0 else: return sum(self.scores) @property def rolled(self): """tuple[int]: The last roll of the dice""" if self.roll_hist: return self.roll_hist[-1] else: return None @rolled.setter @diceutils.norm_dice(1) def rolled(self, dice): if self.roll_hist: del self.roll_hist[-1] self.roll_hist.append(dice) @property def score(self): """int: The score for the last scored dice""" if self.dice_score_pairs: return self.dice_score_pairs[-1][1] else: return None @property def scored_dice(self): """int: The score for the last scored dice""" if self.dice_score_pairs: return self.dice_score_pairs[-1][0] else: return None @property def scores(self): """int: The score for the last scored dice""" return tuple(p[1] for p in self.dice_score_pairs)