class Player(object): def __init__(self, game, name): self.game = game self.name = name self.cards = Hand() def get_card(self): self.cards.append(self.game.deck.pop())
class Player(object): """ Base class for player objects. Encapsulates the core game mechanics of drawing and playing cards. """ def __init__(self, game, name): self.cards = Hand(style=self.__class__.style, name=name) self.game = game self.name = name self.message = self.game.message def take_card(self): """ Take a card from the deck. If the deck is empty, take the bottom n-1 cards from the central stack and shuffle them to get a new deck. """ if not self.game.deck: self.game.deck = self.game.central_stack[:-1] self.game.central_stack = [self.game.central_stack[-1]] shuffle(self.game.deck) self.cards.append(self.game.deck.pop()) def handle_sevens(self): """ Take 2n cards from the deck if there are n "active" sevens in the middle. """ for _ in range(self.game.sevens): self.take_card() self.take_card() self.message.push("{} has to draw {} cards!".format( self.name, 2 * self.game.sevens)) self.game.sevens = 0 print(self.game) def matches(self, top_card): matching_suite = [c for c in self.cards if c.suite == top_card.suite] matching_rank = [c for c in self.cards if c.rank == top_card.rank] return matching_suite, matching_rank def move(self): """ Automates the players choices as far as possible regardless of if it is a human or AI player. Returns True, if we have to skip one round, False, if the player has to pass, and None otherwise. """ top_card = self.game.central_stack[-1] # If the top card is a 8, we have to skip this round if the 8 is still # effective, that is, if no other player before us has skipped because # of this card. if self.game.eights: self.message.push("{} has to skip one round.".format(self.name)) self.game.eights = 0 return True # If the top card is a 7, we can avoid having to draw 2(n) cards if we play # a 7 of our own. However, we leave this choice (if there is one) to the # player, hence, we call handle_sevens only if there is no other possibility elif top_card.rank == "7" and self.game.sevens: sevens = [c for c in self.cards if c.rank == "7"] if not sevens: self.handle_sevens() # Now look if we have matching cards. If not, draw a card from the deck. matching_suite, matching_rank = self.matches(top_card) if not matching_suite and not matching_rank: self.take_card() self.message.push("{} has to draw a card.".format(self.name)) print(self.game) # Look (possibly) again for a match. If there is none, we have to pass. matching_suite, matching_rank = self.matches(top_card) if not matching_suite and not matching_rank: self.message.push("{} has to pass.".format(self.name)) return False # At this point, there is (possibly) a choice left to the player, which # may differ for AI and human players. Therefore, we return None. return None def play(self, card): """ Play out a card, announce that we have won by raising MauMau, increment and decrement the counters of unhandeled sevens and eights """ self.cards.remove(card) self.game.central_stack.append(card) self.message.push("{} plays {} of {}.".format(self.name, card.rank, card.suite)) if card.rank == "7": self.game.sevens += 1 else: self.game.sevens = 0 if card.rank == "8": self.game.eights = 1 else: self.game.eights = 0 if not self.cards: raise MauMau def __repr__(self): return self.cards.repr(style=self.style)
class Game(object): """ Game is the class encapsulating the game logic of (counterclockwise) rounds of the players in Game().player_list, breaking out of the endless loop in Game().play() only if either MauMau or GameAbort is raised. """ def __init__(self, demo=False): """ Sets the stage: Shuffles the deck, hands out 7 cards to each player and places a card in the middle. demo = True creates a game with 3 AI players. """ self.message = MessageHandler() self.deck = Deck() self.central_stack = Hand(style="top") if not demo: self.horst = HumanPlayer(self, "Horst") else: # Patch "Horst" to be an AIPlayer with the same __repr__ as a human player self.horst = AIPlayer(self, "Horst") self.horst.cards.style = "horizontal" self.player_list = [ AIPlayer(self, "Fritz"), AIPlayer(self, "Franz"), self.horst ] self.players = cycle(self.player_list) # When simulating 3 random players a million times, it turns out that the # last player has a 2% handicap compared to the others. Hence: Random beginner. for _ in range(randint(0, 2)): next(self.players) # Distribute cards. for _ in range(7): for _ in range(3): self.current_player = next(self.players) self.current_player.take_card() self.central_stack.append(self.deck.pop()) # Set up central counters for the number of unhandeled sevens and eights. if self.central_stack[-1].rank == "7": self.sevens = 1 else: self.sevens = 0 if self.central_stack[-1].rank == "8": self.eights = 1 else: self.eights = 0 def is_legal(self, card): top_card = self.central_stack[-1] return top_card.suite == card.suite or top_card.rank == card.rank def play(self): skipped = False length = 0 while True: length += 1 # Print the game print(self) # Next player self.current_player = next(self.players) time.sleep(1) try: self.current_player.move() except MauMau: # Winner, winner, chicken dinner! print(self) self.message.push("{} has won!".format( self.current_player.name)) if self.current_player != self.horst: self.message.user_message( "Sorry, you have lost against {}!".format( self.current_player.name)) else: self.message.user_message( "Congratulations {}, you have won this game!".format( self.current_player.name)) break except GameAbort: self.message.user_message("Thank you for playing!") break return self.current_player.name, length def __repr__(self): """ Representing unicode string for the game: First row is Fritz, then Franz, then Horst (the human player). """ anchor = "\x1b7\x1b[1;1f" # ANSI escape sequence to start at row 1, columm 1 cards = "\n".join([str(player) for player in self.player_list ]) # the players' cards center = [""] * 4 + [ (" " * 7) + line for line in str(self.central_stack).splitlines() ] + [""] * 12 # the central stack gets printed in the middle # Join everything up. all = "\n".join([anchor] + [c + l for c, l in zip(cards.splitlines(), center)] + [ "{:<59}".format(" ".join([ "{:>2}".format(Hand.alphabet[i].upper()) for i in range(len(self.horst.cards)) ])) ] + ["\x1b8"]) return all