#!/usr/bin/env python3 import random class NoMoreCards(Exception): pass class InvalidCard(Exception): pass class InvalidSuit(Exception): pass class NoMatchingCard(Exception): pass class ChangeSuit(Exception): pass class IllegalMove(Exception): pass class NotSequential(Exception): pass class Game(object): def __init__(self, deck, rules): self.players = [] self.drawdeck = deck self.playdeck = Deck() self.rules = rules # Which index player's turn is it? self.player_index = 0 self.last_player = None @property def current_player(self): return self.players[self.player_index] def next_turn(self): current_player = self.players[self.player_index] print(f"Current player is {current_player}") if self.player_index == len(self.players): print(f"Reached the end of players: {self.players}, {current_player}") self.current_player = 0 else: self.current_player += 1 print(f"Incrementing current_player: {self.players}, {current_player}") def determine_start(self, first_card): """Determine who starts the game.""" card_map = {} for player in self.players: player_min = player.hand.min_of_suit_or_none(first_card.suit) if player_min is None: card_map[None] = player else: card_map[str(player_min)] = player if len(card_map) == 1 and None in card_map: return card_map[None] else: without_none = {k:v for k,v in card_map.items() if k} min_card = min(without_none.keys()) min_player = without_none[min_card] return min_player def add_players(self, *players): for player in players: print(f"Adding player: {player}") self.players.append(player) def flipcard(self): if not len(self.playdeck.cards) == 0: return False first_card = self.drawdeck.draw() print(f"First card is: {first_card}") self.playdeck.put(first_card) starter = self.determine_start(first_card) print(f"{starter} to start!") self.player_index = self.players.index(starter) print("player index", self.player_index) def deal(self): for player in self.players: num_cards = self.rules.initial_cards cards = list(self.drawdeck.draw_num(num_cards)) player.hand.put_cards(*cards) if self.rules.flipcard: self.flipcard() def play_card(self, player, card, special=True): print(f"Player {player} attempting to play {card}") last_card = self.playdeck.last_card print(f"Last card {last_card}") if player == self.last_player: print("Player is last player, setting special to False") special = False is_playable = self.rules.is_playable_on(last_card, card, special=special) print("Card is playable", is_playable) if is_playable: actual_card = player.hand.draw_card(card) self.playdeck.put(actual_card) self.last_player = player return is_playable def play_combination(self, player, *cards): print(f"Player {player} attempting to play combination {cards}") last_card = self.playdeck.last_card for index, card in enumerate(cards): print("Combination iter", index, card) is_playable = self.rules.is_playable_on(last_card, card, special=False) if not is_playable: raise IllegalMove if index != 0: is_sequential = self.rules.is_sequential(last_card, card) if not is_sequential: raise NotSequential last_card = card for card in cards: self.play_card(player, card) if card == self.rules.change_suit: raise ChangeSuit class Player(object): def __init__(self, name): self.name = name self.hand = Hand() def __str__(self): return f"@{self.name}" def __repr__(self): return self.__str__() class SetOfCards(object): def __init__(self, *cards): self.cards = [] if cards: self.cards = [*cards] def draw(self): card = self.cards.pop() return card def draw_num(self, num): for i in range(0, num): yield self.cards.pop() def put(self, card): self.cards.append(card) def put_cards(self, *cards): for card in cards: self.put(card) def draw_card(self, card): for mycard in self.cards: if card == mycard: self.cards.remove(mycard) print(f"Returned {mycard} and amended cards: {self.cards}") return mycard raise NoMatchingCard def __str__(self): return str(self.cards) def __repr__(self): return self.__str__() class Deck(SetOfCards): def shuffle(self): random.shuffle(self.cards) @property def last_card(self): if self.cards: return self.cards[-1] else: raise NoMoreCards @property def last_cards(self): if self.cards: return self.cards[:5] def fill(self): for suit_str in "SHCD": values_str_10 = [str(x) for x in range(2, 11)] for val_str in [*values_str_10, *"JQKA"]: card_str = f"{suit_str}{val_str}" print("card_str", card_str) card = Card(card_str) self.cards.append(card) print("cards after fill", self.cards) print("len after fill", len(self.cards)) class Hand(SetOfCards): def have_suit(self, suit): """Have we got this suit?""" for card in self.cards: if card.suit == suit: return True return False def min_of_suit_or_none(self, suit): if not self.have_suit(suit): return None suit_cards = [] for card in self.cards: if card.suit == suit: suit_cards.append(card) return min(suit_cards) class Rules(object): def __init__(self): self.initial_cards = 7 # Number of cards to start with self.flipcard = True # Initially flip a card self.jacks_on_twos = False # Can we play Jacks on 2s? self.jacks_on_eights = False # Can we play Jacks on 8s? self.set_power_cards() def set_power_cards(self): # Next player has to pick up two cards if they don't have a # card of the same value self.pick_up_two = Value("2") # Next player has to skip a go if they don't have another self.skip_a_turn = Value("8") # How many cards to pick up self.multiple_number = 6 pick_up_multiple_suits = [Suit("S"), Suit("C")] # Create a BlackJack filter self.pick_up_multiple = Filter(pick_up_multiple_suits, Value("J")) self.change_suit = Value("A") self.specials_all = [self.pick_up_two, self.skip_a_turn, self.pick_up_multiple, self.change_suit] self.specials_power = self.specials_all[:-1] def can_play_on_special_power(self, bottom_card, card): # If the bottom card is a two if bottom_card == self.pick_up_two: # If the proposed card is not also the same if card == self.pick_up_two: return True if self.jacks_on_twos: if card == self.pick_up_mutiple: return True # We can only play 2s or Jacks if that is enabled return False # if the bottom card is a two if bottom_card == self.skip_a_turn: # if the proposed card is not also the same if card == self.skip_a_turn: return True if self.jacks_on_eights: # If the proposed card is a blackjack if card == self.pick_up_multiple: return True # We can only play 8s or Jacks if that is enabled return False # If the bottom card is a Blackjack if bottom_card == self.pick_up_multiple: # If the card is the other Blackjack if card == self.pick_up_multiple: return True return False print("Reached final return for can play on special") return False def can_play_special_on(self, bottom_card, card): print("CARD IS ", card) print("CHANGE SUIT IS", self.change_suit) if card == self.change_suit: print("Card is change suit") if bottom_card not in self.specials_power: return True return False def is_sequential(self, last_card, card): print("Sequential", last_card, card) is_one_above = card.val.numeric_value == last_card.val.numeric_value + 1 print("is one above", is_one_above) is_one_below = card.val.numeric_value == last_card.val.numeric_value - 1 print("is one below", is_one_below) return is_one_below | is_one_above def is_playable_on(self, bottom_card, card, special): print(f"Is playable on {bottom_card} {card}") if special: if bottom_card in self.specials_power: print("Bottom card in specials all") return self.can_play_on_special_power(bottom_card, card) if card.suit == bottom_card.suit: print("Suit is bottom card suit") return True if card.val == bottom_card.val: print("Value is bottom card value") return True if special: if card in self.specials_all: print("Card is in specials_all") return self.can_play_special_on(bottom_card, card) class Suit(object): def __init__(self, suit): if suit not in "SHDC": raise InvalidSuit self.suit = suit def __str__(self): return f"{self.suit}" def __repr__(self): return self.__str__() def __eq__(self, other): if isinstance(other, Suit): return self.suit == other.suit elif isinstance(other, Card): return self.suit == other.suit.suit class Value(object): def __init__(self, val): self.hash_map = {"J": 11, "Q": 12, "K": 13, "A": 14} string_values = [str(x) for x in range(2, 11)] if val not in self.hash_map.keys() and val not in string_values: raise InvalidCard self.val = val self.numeric_value = self.get_numeric_value(val) def get_numeric_value(self, val): if val in self.hash_map: return self.hash_map[val] else: return int(val) def __gt__(self, other): return self.numeric_value > other.numeric_value def __lt__(self, other): return self.numeric_value < other.numeric_value def __eq__(self, other): if isinstance(other, Value): return self.numeric_value == other.numeric_value elif isinstance(other, Card): return self.numeric_value == other.val.numeric_value else: print(f"ERROR! Cannot compare {self} and {other}") return False def __str__(self): return f"{str(self.val)}" def __repr__(self): return self.__str__() @staticmethod def multiple(*vals): for val in vals: yield Value(val) class Filter(object): def __init__(self, suits, val): self.suits = suits self.val = val def __str__(self): return f"|{self.val} of {self.suits}|" def __repr__(self): return self.__str__() def __eq__(self, other): if other.suit in self.suits and other.val == self.val: return True else: return False class Card(object): def __init__(self, identifier): self.suit, self.val = Card.identifier_to_card(identifier) def playable_on(self, card): pass def __str__(self): return f"{self.suit}{self.val}" def __repr__(self): return self.__str__() def __gt__(self, other): return self.val.numeric_value > other.val.numeric_value def __lt__(self, other): return self.val.numeric_value < other.val.numeric_value def __eq__(self, other): if isinstance(other, Card): if self.val.numeric_value == other.val.numeric_value: if self.suit.suit == other.suit.suit: return True elif isinstance(other, Value): return self.val.numeric_value == other.numeric_value elif isinstance(other, Suit): return self.suit.suit == other.suit return False @staticmethod def identifier_to_card(identifier): suit_str = identifier[0] val_str = identifier[1:] suit = Suit(suit_str) val = Value(val_str) return suit, val @staticmethod def multiple_suit(val, suits): for suit in suits: card = Card() card.suit = suit card.val = val yield card mark = Player("Mark") john = Player("John") deck = Deck() deck.fill() #deck.shuffle() rules = Rules() game = Game(deck, rules) game.add_players(mark, john) game.deal() print("Mark's cards:", mark.hand.cards) print("John's cards:", john.hand.cards) print("--- Beginning game ---") while 1: print("=====") current_player = game.current_player print(f"Player {current_player}'s go!") player_cards = current_player.hand.cards last_player = game.last_player if last_player: print(f"Last player: {last_player}") print(f"Last card is: {game.playdeck.last_card}") print(f"Last 5 cards: {game.playdeck.last_cards}") print(f"Your cards: {player_cards}") text = input("~> ") parsed_cards = text.split(",") print("Parsed", parsed_cards) try: cards_to_play = list(map(Card, parsed_cards)) print("Cards to play", cards_to_play) played = game.play_combination(current_player, *cards_to_play) # if len(cards_to_play) == 1: # if len(card) == 0: # print("Invalid card") # continue # card_to_play = Card(card) # print("Card to play", card_to_play) # # print(f"Attempting to play {card_to_play}") # played = game.play_card(current_player, card_to_play) # if not played: # print("Illegal move!") # continue except ChangeSuit: suit = None while not suit: new_suit = input("Enter a new suit: #> ") try: suit = Suit(new_suit) except InvalidSuit: print(f"Suit {new_suit} is not valid, please select S, H, D or C") continue print(f"Changing last card of {game.playdeck.cards[-1]} to {suit}") game.playdeck.cards[-1].suit = suit except InvalidCard: print("Invalid card:", card) continue except IllegalMove: print("Illegal move!") continue except NoMatchingCard: print("You don't have this card!") continue except NotSequential: print("Combination is not sequential!") continue if len(current_player.hand.cards) == 0: print(f"Player {current_player} wins!") print("Thanks for playing!") exit()