예제 #1
0
def new_standard_deck():
    deck = Hand()

    # Add two more agreements to the list of ranks for a total of 3.
    ranks = [AGREEMENT, AGREEMENT]
    ranks.extend(RANKS)

    for r in ranks:
        for s in DEFAULT_SUITS:
            deck.add(ExpeditionsCard(r, s))
    return deck
예제 #2
0
파일: whist.py 프로젝트: Cloudxtreme/giles
    def clear_trick(self):

        # Set the current trick to an empty hand...
        self.trick = Hand()
        self.led_suit = None

        # ...and set everyone's played card to None.
        for seat in self.seats:
            seat.data.card = None

        # Clear the layout as well.
        self.layout.clear()
예제 #3
0
파일: trick.py 프로젝트: Cloudxtreme/giles
def sorted_hand(hand, trump_suit=None):
    """Sorts a hand of cards.  Puts trumps first, if any, then sorts the
    remaining suits arbitrarily.  Returns this newly-sorted hand.
    """

    trump_cards = Hand()
    other_suits = {}
    for card in hand:
        this_suit = card.suit
        if card.suit == trump_suit:
            hand_to_add_to = trump_cards
        else:
            if this_suit not in other_suits:
                other_suits[this_suit] = Hand()
            hand_to_add_to = other_suits[this_suit]
        hand_to_add_to.add(card)

    # Now that they're sorted out, combine them back together.
    s_hand = Hand()
    trump_cards.sort()
    for card in trump_cards:
        s_hand.add(card)
    for suit in sorted(other_suits.keys()):
        suit_hand = other_suits[suit]
        suit_hand.sort()
        for card in suit_hand:
            s_hand.add(card)

    return s_hand
예제 #4
0
    def init_hand(self):

        # Depending on the number of suits requested for play, we build
        # structures.
        suit_list = DEFAULT_SUITS[:]
        if self.suit_count >= 6:
            suit_list.append(CYAN)
        if self.suit_count == 7:
            suit_list.append(MAGENTA)

        # If, on the other hand, the number of suits is /less/ than five,
        # we use a subset of the default suits.
        if self.suit_count < 5:
            suit_list = DEFAULT_SUITS[:self.suit_count]

        # All right, we have a list of suits involved in this game.  Let's
        # build the various piles that are based on those suits.
        self.left.data.expeditions = []
        self.right.data.expeditions = []
        self.discards = []
        for suit in suit_list:
            discard_pile = Struct()
            left_expedition = Struct()
            right_expedition = Struct()
            for pile in (discard_pile, left_expedition, right_expedition):
                pile.suit = suit
                pile.hand = Hand()
                pile.value = 0
            self.left.data.expeditions.append(left_expedition)
            self.right.data.expeditions.append(right_expedition)
            self.discards.append(discard_pile)

        # We'll do a separate loop for generating the deck to minimize
        # confusion.
        self.draw_pile = Hand()
        for suit in suit_list:
            for rank in NUMERICAL_RANKS:
                self.draw_pile.add(ExpeditionsCard(rank, suit))

            # Add as many agreements as requested.
            for agreement in range(self.agreement_count):
                self.draw_pile.add(ExpeditionsCard(AGREEMENT, suit))

        # Lastly, shuffle the draw deck and initialize hands.
        self.draw_pile.shuffle()
        self.left.data.hand = Hand()
        self.right.data.hand = Hand()
예제 #5
0
파일: hokm.py 프로젝트: Cloudxtreme/giles
    def clear_trick(self):

        # Set the current trick to an empty hand...
        self.trick = Hand()
        self.led_suit = None

        # ...and set everyone's played card to None.
        for seat in self.seats:
            seat.data.card = None

        # Clear the layout as well.
        self.layout.clear()
예제 #6
0
파일: hokm.py 프로젝트: Cloudxtreme/giles
    def new_deck(self):

        # In 4-player mode, it's a standard 52-card pack.
        if self.mode == 4:
            self.deck = new_deck()
        else:

            # If it's a short deck, 7-A are full; if a long deck, 3-A are.
            full_ranks = [ACE, KING, QUEEN, JACK, '10', '9', '8', '7', '6']
            if self.short:
                short_rank = '5'
            else:
                full_ranks.extend(['5', '4', '3'])
                short_rank = '2'

            # Build the deck, full ranks first.
            self.deck = Hand()
            for suit in (CLUBS, DIAMONDS, HEARTS, SPADES):
                for rank in full_ranks:
                    self.deck.add(PlayingCard(rank, suit))

            # We only want three of the short rank.  No hearts, because.
            for suit in (CLUBS, DIAMONDS, SPADES):
                self.deck.add(PlayingCard(short_rank, suit))
예제 #7
0
    def init_hand(self):

        # Depending on the number of suits requested for play, we build
        # structures.
        suit_list = DEFAULT_SUITS[:]
        if self.suit_count >= 6:
            suit_list.append(CYAN)
        if self.suit_count == 7:
            suit_list.append(MAGENTA)

        # If, on the other hand, the number of suits is /less/ than five,
        # we use a subset of the default suits.
        if self.suit_count < 5:
            suit_list = DEFAULT_SUITS[:self.suit_count]

        # All right, we have a list of suits involved in this game.  Let's
        # build the various piles that are based on those suits.
        self.left.data.expeditions = []
        self.right.data.expeditions = []
        self.discards = []
        for suit in suit_list:
            discard_pile = Struct()
            left_expedition = Struct()
            right_expedition = Struct()
            for pile in (discard_pile, left_expedition, right_expedition):
                pile.suit = suit
                pile.hand = Hand()
                pile.value = 0
            self.left.data.expeditions.append(left_expedition)
            self.right.data.expeditions.append(right_expedition)
            self.discards.append(discard_pile)

        # We'll do a separate loop for generating the deck to minimize
        # confusion.
        self.draw_pile = Hand()
        for suit in suit_list:
            for rank in NUMERICAL_RANKS:
                self.draw_pile.add(ExpeditionsCard(rank, suit))

            # Add as many agreements as requested.
            for agreement in range(self.agreement_count):
                self.draw_pile.add(ExpeditionsCard(AGREEMENT, suit))

        # Lastly, shuffle the draw deck and initialize hands.
        self.draw_pile.shuffle()
        self.left.data.hand = Hand()
        self.right.data.hand = Hand()
예제 #8
0
파일: hokm.py 프로젝트: Cloudxtreme/giles
    def start_deal(self):

        # Set the trick counts to zero, appropriately for the mode.
        if self.mode == 4:
            self.ns.tricks = 0
            self.ew.tricks = 0
        else:
            self.west.data.tricks = 0
            self.south.data.tricks = 0
            self.east.data.tricks = 0

        dealer_name = self.dealer.player_name

        self.bc_pre(
            "^R%s^~ (%s%s^~) gives the cards a good shuffle...\n" %
            (dealer_name, self.get_color_code(self.dealer), self.dealer))
        self.new_deck()
        self.deck.shuffle()

        # Deal out five cards each.
        self.bc_pre("^R%s^~ deals five cards out to each of the players.\n" %
                    dealer_name)
        for seat in self.seats:
            seat.data.hand = Hand()
        for i in range(5):
            for seat in self.seats:
                seat.data.hand.add(self.deck.discard())

        # Clear the internal metadata about trumps.
        self.trump_suit = None

        # Sort the hakem's hand.
        self.hakem.data.hand = sorted_hand(self.hakem.data.hand)

        # Show the hakem their hand.
        if self.hakem.player:
            self.tell_pre(self.hakem.player,
                          "Please choose a trump suit for this hand.\n")
            self.show_hand(self.hakem.player)

        # The hakem both chooses and, eventually, leads.
        self.turn = self.hakem
        self.layout.change_turn(self.hakem.data.who)

        # Shift into "choosing" mode.
        self.state.set("choosing")
예제 #9
0
파일: hokm.py 프로젝트: Cloudxtreme/giles
    def new_deck(self):

        # In 4-player mode, it's a standard 52-card pack.
        if self.mode == 4:
            self.deck = new_deck()
        else:

            # If it's a short deck, 7-A are full; if a long deck, 3-A are.
            full_ranks = [ACE, KING, QUEEN, JACK, '10', '9', '8', '7', '6']
            if self.short:
                short_rank = '5'
            else:
                full_ranks.extend(['5', '4', '3'])
                short_rank = '2'

            # Build the deck, full ranks first.
            self.deck = Hand()
            for suit in (CLUBS, DIAMONDS, HEARTS, SPADES):
                for rank in full_ranks:
                    self.deck.add(PlayingCard(rank, suit))

            # We only want three of the short rank.  No hearts, because.
            for suit in (CLUBS, DIAMONDS, SPADES):
                self.deck.add(PlayingCard(short_rank, suit))
예제 #10
0
파일: whist.py 프로젝트: Cloudxtreme/giles
    def new_deal(self):

        dealer_name = self.dealer.player_name

        self.bc_pre(
            "^R%s^~ (%s%s^~) gives the cards a good shuffle...\n" %
            (dealer_name, self.get_color_code(self.dealer), self.dealer))
        deck = new_deck()
        deck.shuffle()

        # Deal out all of the cards.  We'll flip the last one; that determines
        # the trump suit for the hand.
        self.bc_pre("^R%s^~ deals the cards out to all the players.\n" %
                    dealer_name)
        for seat in self.seats:
            seat.data.hand = Hand()
        for i in range(13):
            for seat in self.seats:
                seat.data.hand.add(deck.discard())

        # Flip the dealer's last card; it determines the trump suit.
        last_card = self.dealer.data.hand[-1]
        self.bc_pre("^R%s^~ flips their last card; it is ^C%s^~.\n" %
                    (dealer_name, card_to_str(last_card, LONG)))
        self.trump_suit = last_card.suit

        # Sort everyone's hands.
        for seat in self.seats:
            seat.data.hand = sorted_hand(seat.data.hand, self.trump_suit)

        # Show everyone their hands.
        self.show_hands()

        # Set the trick counts to zero.
        self.ns.tricks = 0
        self.ew.tricks = 0
예제 #11
0
파일: hokm.py 프로젝트: Cloudxtreme/giles
class Hokm(SeatedGame):
    """A Hokm game table implementation.  Hokm is a Persian trick-taking
    card game of unknown provenance.  This implementation doesn't
    currently rearrange the seats at the start, but does support both the
    standard 4p partnership game and the quirky 3p mode.  In addition, 3p
    mode can use both a short deck (13 cards per hand) or a long deck (17).
    """
    def __init__(self, server, table_name):

        super(Hokm, self).__init__(server, table_name)

        self.game_display_name = "Hokm"
        self.game_name = "hokm"

        self.state = State("need_players")
        self.prefix = "(^RHokm^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name,
                                       self.game_display_name)

        # Hokm-specific stuff.
        self.goal = 7
        self.trick = None
        self.trump_suit = None
        self.led_suit = None
        self.turn = None
        self.dealer = None
        self.hakem = None
        self.winner = None

        # Default to four-player mode.
        self.mode = 4
        self.short = True
        self.setup_mode()

    def setup_mode(self):

        # Sets up all of the structures that depend on the mode of Hokm
        # we're playing: seats, layouts, and (in 4p mode) partnerships.
        # Remember that seats in Hokm go the opposite direction of the
        # American/European standard.

        if self.mode == 4:

            self.seats = [
                Seat("North"),
                Seat("West"),
                Seat("South"),
                Seat("East"),
            ]

            self.seats[0].data.who = NORTH
            self.seats[1].data.who = WEST
            self.seats[2].data.who = SOUTH
            self.seats[3].data.who = EAST

            self.min_players = 4
            self.max_players = 4
            self.layout = FourPlayerCardGameLayout()

            # Set up the partnership structures.
            self.ns = Struct()
            self.ew = Struct()
            self.ns.score = 0
            self.ew.score = 0

        elif self.mode == 3:

            self.seats = [
                Seat("West"),
                Seat("South"),
                Seat("East"),
            ]

            self.seats[0].data.who = WEST
            self.seats[1].data.who = SOUTH
            self.seats[2].data.who = EAST

            self.west = self.seats[0]
            self.south = self.seats[1]
            self.east = self.seats[2]
            self.west.data.score = 0
            self.south.data.score = 0
            self.east.data.score = 0

            self.min_players = 3
            self.max_players = 3
            self.layout = ThreePlayerCardGameLayout()

        else:
            self.log_pre(
                "MAJOR ERROR: Hokm initialization with invalid mode %s!" %
                self.mode)

    def show_help(self, player):

        super(Hokm, self).show_help(player)
        player.tell_cc("\nHOKM SETUP PHASE:\n\n")
        player.tell_cc(
            "          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n"
        )
        player.tell_cc(
            "            ^!goal^. <num>, ^!score^.     Set the goal score to <num>.\n"
        )
        player.tell_cc(
            "              ^!players^. 3|4, ^!pl^.     Set the number of players.\n"
        )
        player.tell_cc(
            "             ^!short^. on|off, ^!sh^.     Use a short deck (3p only).\n"
        )
        player.tell_cc(
            "            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n"
        )
        player.tell_cc("\nHOKM PLAY:\n\n")
        player.tell_cc(
            "            ^!choose^. <suit>, ^!ch^.     Declare <suit> as trumps.  Hakem only.\n"
        )
        player.tell_cc(
            "              ^!play^. <card>, ^!pl^.     Play <card> from your hand.\n"
        )
        player.tell_cc(
            "                 ^!hand^., ^!inv^., ^!i^.     Look at the cards in your hand.\n"
        )

    def display(self, player):

        player.tell_cc("%s" % self.layout)

    def get_color_code(self, seat):
        if self.mode == 4:
            if seat == self.seats[0] or seat == self.seats[2]:
                return "^R"
            else:
                return "^M"
        else:
            if seat == self.west:
                return "^M"
            elif seat == self.south:
                return "^R"
            else:
                return "^B"

    def get_sp_str(self, seat):

        return "^G%s^~ (%s%s^~)" % (seat.player_name,
                                    self.get_color_code(seat), seat)

    def get_score_str(self):
        if self.mode == 4:
            return "          ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (
                self.ns.score, self.ew.score)
        else:
            return "          ^M%s^~: %d    ^R%s^~: %d    ^B%s^~: %d\n" % (
                self.west.player_name, self.west.data.score,
                self.south.player_name, self.south.data.score,
                self.east.player_name, self.east.data.score)

    def get_metadata(self):

        to_return = "\n\n"
        if self.turn:
            seat_color = self.get_color_code(self.turn)
            to_return += "%s is the hakem.\n" % (self.get_sp_str(self.hakem))
            if self.trump_suit:
                trump_str = "^C%s^~" % self.trump_suit
            else:
                trump_str = "^cwaiting to be chosen^~"

            to_return += "It is ^Y%s^~'s turn (%s%s^~).  Trumps are ^C%s^~.\n" % (
                self.turn.player_name, seat_color, self.turn, trump_str)
            if self.mode == 4:
                to_return += "Tricks:   ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (
                    self.ns.tricks, self.ew.tricks)
            else:
                to_return += "Tricks:   ^M%s^~: %d    ^R%s^~: %d    ^B%s^~: %d\n" % (
                    self.west.player_name, self.west.data.tricks,
                    self.south.player_name, self.south.data.tricks,
                    self.east.player_name, self.east.data.tricks)
        to_return += "The goal score for this game is ^C%s^~.\n" % get_plural_str(
            self.goal, "point")
        to_return += self.get_score_str()

        return to_return

    def show(self, player):
        self.display(player)
        player.tell_cc(self.get_metadata())

    def set_goal(self, player, goal_str):

        if not goal_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_goal = int(goal_str)
        if new_goal < 1:
            self.tell_pre(player, "The goal must be at least one point.\n")
            return False

        # Got a valid goal.
        self.goal = new_goal
        self.bc_pre("^M%s^~ has changed the goal to ^G%s^~.\n" %
                    (player, get_plural_str(new_goal, "point")))

    def set_short(self, player, short_bits):

        if self.mode != 3:
            self.tell_pre(
                player, "Cannot set short mode when not in 3-player mode.\n")
            return False

        short_bool = booleanize(short_bits)
        if short_bool:
            if short_bool > 0:
                self.short = True
                display_str = "^Con^~"
            elif short_bool < 0:
                self.short = False
                display_str = "^coff^~"
            self.bc_pre("^R%s^~ has turned short suits %s.\n" %
                        (player, display_str))
        else:
            self.tell_pre(player, "Not a valid boolean!\n")

    def set_players(self, player, player_str):

        if not player_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_mode = int(player_str)

        if new_mode == self.mode:
            self.tell_pre(player, "That is the current player count.\n")
            return False

        elif new_mode != 3 and new_mode != 4:
            self.tell_pre(player,
                          "Only 3-player and 4-player Hokm is supported.\n")
            return False

        # Got a valid mode.
        self.mode = new_mode
        self.bc_pre("^M%s^~ has changed the number of players to ^G%s^~.\n" %
                    (player, new_mode))
        self.setup_mode()

    def clear_trick(self):

        # Set the current trick to an empty hand...
        self.trick = Hand()
        self.led_suit = None

        # ...and set everyone's played card to None.
        for seat in self.seats:
            seat.data.card = None

        # Clear the layout as well.
        self.layout.clear()

    def new_deck(self):

        # In 4-player mode, it's a standard 52-card pack.
        if self.mode == 4:
            self.deck = new_deck()
        else:

            # If it's a short deck, 7-A are full; if a long deck, 3-A are.
            full_ranks = [ACE, KING, QUEEN, JACK, '10', '9', '8', '7', '6']
            if self.short:
                short_rank = '5'
            else:
                full_ranks.extend(['5', '4', '3'])
                short_rank = '2'

            # Build the deck, full ranks first.
            self.deck = Hand()
            for suit in (CLUBS, DIAMONDS, HEARTS, SPADES):
                for rank in full_ranks:
                    self.deck.add(PlayingCard(rank, suit))

            # We only want three of the short rank.  No hearts, because.
            for suit in (CLUBS, DIAMONDS, SPADES):
                self.deck.add(PlayingCard(short_rank, suit))

    def start_deal(self):

        # Set the trick counts to zero, appropriately for the mode.
        if self.mode == 4:
            self.ns.tricks = 0
            self.ew.tricks = 0
        else:
            self.west.data.tricks = 0
            self.south.data.tricks = 0
            self.east.data.tricks = 0

        dealer_name = self.dealer.player_name

        self.bc_pre(
            "^R%s^~ (%s%s^~) gives the cards a good shuffle...\n" %
            (dealer_name, self.get_color_code(self.dealer), self.dealer))
        self.new_deck()
        self.deck.shuffle()

        # Deal out five cards each.
        self.bc_pre("^R%s^~ deals five cards out to each of the players.\n" %
                    dealer_name)
        for seat in self.seats:
            seat.data.hand = Hand()
        for i in range(5):
            for seat in self.seats:
                seat.data.hand.add(self.deck.discard())

        # Clear the internal metadata about trumps.
        self.trump_suit = None

        # Sort the hakem's hand.
        self.hakem.data.hand = sorted_hand(self.hakem.data.hand)

        # Show the hakem their hand.
        if self.hakem.player:
            self.tell_pre(self.hakem.player,
                          "Please choose a trump suit for this hand.\n")
            self.show_hand(self.hakem.player)

        # The hakem both chooses and, eventually, leads.
        self.turn = self.hakem
        self.layout.change_turn(self.hakem.data.who)

        # Shift into "choosing" mode.
        self.state.set("choosing")

    def finish_deal(self):

        self.bc_pre("^R%s^~ finishes dealing the cards out.\n" %
                    self.dealer.player_name)
        while len(self.deck):
            for seat in self.seats:
                seat.data.hand.add(self.deck.discard())

        # Sort everyone's hands now that we have a trump suit.
        for seat in self.seats:
            seat.data.hand = sorted_hand(seat.data.hand, self.trump_suit)

        # Show everyone their completed hands.
        self.show_hands()

        # We're playing now.
        self.state.set("playing")

    def show_hand(self, player):

        seat = self.get_seat_of_player(player)

        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return

        print_str = "Your current hand:\n   "
        print_str += hand_to_str(seat.data.hand, self.trump_suit)
        print_str += "\n"
        self.tell_pre(player, print_str)

    def show_hands(self):

        for seat in self.seats:
            if seat.player:
                self.show_hand(seat.player)

    def play(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # Okay, it's a card in their hand.  First, let's do the "follow the
        # led suit" business.
        action_str = "^Wplays^~"
        if self.led_suit:

            this_suit = potential_card.suit
            if (this_suit != self.led_suit
                    and hand_has_suit(seat.data.hand, self.led_suit)):

                # You can't play off-suit if you can match the led suit.
                self.tell_pre(player,
                              "You can't throw off; you have the led suit.\n")
                return False

        else:

            # No led suit; they're the leader.
            action_str = "^Yleads^~ with"
            self.led_suit = potential_card.suit

        # They either matched the led suit, didn't have any of it, or they
        # are themselves the leader.  Nevertheless, their play is valid.
        seat.data.card = potential_card
        self.trick.add(seat.data.hand.discard_specific(potential_card))
        trump_str = ""
        if potential_card.suit == self.trump_suit:
            trump_str = ", a ^Rtrump^~"
        self.bc_pre("%s %s ^C%s^~%s.\n" %
                    (self.get_sp_str(seat), action_str,
                     card_to_str(potential_card, LONG), trump_str))
        self.layout.place(seat.data.who, potential_card)
        return potential_card

    def tick(self):

        # If all seats are full and active, autostart.
        active_seats = [x for x in self.seats if x.player]
        if (self.state.get() == "need_players"
                and len(active_seats) == self.mode and self.active):
            self.state.set("playing")
            self.bc_pre("The game has begun.\n")

            # Initialize everything by clearing the (non-existent) trick.
            self.clear_trick()

            # Pick a hakem at random.
            self.hakem = random.choice(self.seats)
            self.bc_pre("Fate has spoken, and the starting hakem is %s!\n" %
                        self.get_sp_str(self.hakem))

            # The dealer is always the player before the hakem.
            self.dealer = self.prev_seat(self.hakem)
            self.start_deal()

    def choose(self, player, choose_str):

        choose_str = choose_str.lower()

        if choose_str in (
                "clubs",
                "c",
        ):
            self.trump_suit = CLUBS
        elif choose_str in (
                "diamonds",
                "d",
        ):
            self.trump_suit = DIAMONDS
        elif choose_str in (
                "hearts",
                "h",
        ):
            self.trump_suit = HEARTS
        elif choose_str in (
                "spades",
                "s",
        ):
            self.trump_suit = SPADES
        else:
            self.tell_pre(player, "That's not a valid suit!\n")
            return

        # Success.  Declare it and finish the deal.
        self.bc_pre("^Y%s^~ has picked ^R%s^~ as trumps.\n" %
                    (player, self.trump_suit))
        self.finish_deal()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()

            command_bits = command_str.split()
            primary = command_bits[0].lower()

            if state == "setup":

                if primary in (
                        "goal",
                        "score",
                        "sc",
                        "g",
                ):
                    if len(command_bits) == 2:
                        self.set_goal(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid goal command.\n")
                    handled = True

                elif primary in (
                        "players",
                        "pl",
                ):
                    if len(command_bits) == 2:
                        self.set_players(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid players command.\n")
                    handled = True

                elif primary in (
                        "short",
                        "sh",
                ):
                    if len(command_bits) == 2:
                        self.set_short(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid short command.\n")
                    handled = True

                elif primary in (
                        "done",
                        "ready",
                        "d",
                        "r",
                ):
                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in (
                        "config",
                        "setup",
                        "conf",
                ):
                    self.state.set("setup")
                    self.bc_pre(
                        "^R%s^~ has switched the game to setup mode.\n" %
                        player)
                    handled = True

            elif state == "choosing":

                if primary in (
                        "hand",
                        "inventory",
                        "inv",
                        "i",
                ):
                    if player == self.hakem.player:
                        self.show_hand(player)
                    else:
                        self.tell_pre(player,
                                      "You can't look at your cards yet!\n")
                    handled = True

                elif primary in (
                        "choose",
                        "trump",
                        "ch",
                        "tr",
                ):
                    if player == self.hakem.player:
                        if len(command_bits) == 2:
                            self.choose(player, command_bits[1])
                        else:
                            self.tell_pre(player, "Invalid choose command.\n")
                    else:
                        self.tell_pre(player, "You're not hakem!\n")
                    handled = True

            elif state == "playing":

                card_played = False
                if primary in (
                        "hand",
                        "inventory",
                        "inv",
                        "i",
                ):
                    self.show_hand(player)
                    handled = True

                elif primary in (
                        "play",
                        "move",
                        "pl",
                        "mv",
                ):
                    if len(command_bits) == 2:
                        card_played = self.play(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid play command.\n")
                    handled = True

                if card_played:

                    # A card hit the table.  We need to do stuff.
                    if len(self.trick) == self.mode:

                        # Finish the trick up.
                        self.finish_trick()

                        # Did that end the hand?
                        winner = self.find_hand_winner()

                        if winner:

                            # Yup.  Resolve the hand...
                            self.resolve_hand(winner)

                            # And look for a winner.
                            winner = self.find_winner()
                            if winner:

                                # Found a winner.  Finish.
                                self.resolve(winner)
                                self.finish()

                            else:

                                # No winner.  Redeal.
                                self.start_deal()

                    else:

                        # Trick not over.  Rotate.
                        self.turn = self.next_seat(self.turn)
                        self.layout.change_turn(self.turn.data.who)
                        if self.turn.player:
                            self.show_hand(self.turn.player)

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def finish_trick(self):

        # Okay, we have a trick with four cards.  Which card won?
        winner = handle_trick(self.trick, self.trump_suit)

        # This /should/ just return one seat...
        winning_seat_list = [x for x in self.seats if x.data.card == winner]

        if len(winning_seat_list) != 1:
            self.server.log.log(
                self.log_prefix +
                "Something went horribly awry; trick ended without a finish.")
            self.bc_pre(
                "Something went horribly wrong; no one won the trick!  Tell the admin.\n"
            )
            return

        winning_seat = winning_seat_list[0]

        # Print information about the winning card.
        self.bc_pre("%s wins the trick with ^C%s^~.\n" %
                    (self.get_sp_str(winning_seat), card_to_str(winner, LONG)))

        # If there are four players, give the trick to the correct partnership.
        if self.mode == 4:
            if winning_seat == self.seats[0] or winning_seat == self.seats[2]:
                self.ns.tricks += 1
            else:
                self.ew.tricks += 1
        else:
            winning_seat.data.tricks += 1

        # Clear the trick.
        self.clear_trick()

        # Set the next leader to the player who won.
        self.turn = winning_seat
        self.layout.change_turn(self.turn.data.who)
        if self.turn.player:
            self.show_hand(self.turn.player)

    def find_hand_winner(self):

        # In four-player mode, this is actually really simple; winning only
        # occurs when one side has more than 6 tricks.
        if self.mode == 4:
            if self.ns.tricks > 6:
                return self.ns
            elif self.ew.tricks > 6:
                return self.ew
        else:

            # In three-player mode, this is considerably less simple.  If
            # one player has more tricks than either other player can possibly
            # get, they win...
            tricks_remaining = len(self.west.data.hand)
            for seat in self.seats:
                our_tricks = seat.data.tricks
                prev_tricks = self.prev_seat(seat).data.tricks
                next_tricks = self.next_seat(seat).data.tricks
                if ((our_tricks > prev_tricks + tricks_remaining)
                        and (our_tricks > next_tricks + tricks_remaining)):
                    return seat

                # ...orrr if there are no tricks left and the other two players
                # tied for the number of tricks, we win as well.  3p Hokm, you
                # so crazy.
                if (not tricks_remaining) and prev_tricks == next_tricks:
                    return seat

                # There's also the case where one player gets the first seven;
                # this is handled already for the short deck by the first check
                # above, but has to have a specific check for the long-deck
                # game.
                if our_tricks == 7 and not prev_tricks and not next_tricks:
                    return seat

        # No winner yet.
        return None

    def resolve_hand(self, winner):

        # Assume the hakem won and there was no sweep; we'll adjust later.
        hakem_won = True
        swept = False

        # 4p mode shenanigans first.
        if self.mode == 4:

            if winner == self.ns:
                winning_str = "^RNorth/South^~"
                if self.hakem != self.seats[0] and self.hakem != self.seats[2]:
                    hakem_won = False
                loser = self.ew
            else:
                winning_str = "^MEast/West^~"
                if self.hakem != self.seats[1] and self.hakem != self.seats[3]:
                    hakem_won = False
                loser = self.ns

            # Did the loser get no tricks?  If so, the winner swept!
            if loser.tricks == 0:
                swept = True

        else:

            # 3P mode.  Check whether the hakem really won...
            if winner != self.hakem:
                hakem_won = False

            # ...and whether the winner swept.
            prev_tricks = self.prev_seat(winner).data.tricks
            next_tricks = self.next_seat(winner).data.tricks
            if not prev_tricks and not next_tricks:
                swept = True

            winning_str = self.get_sp_str(winner)

        if swept:
            action_str = "^Yswept^~"

            # 2 points if the hakem won, 3 if others did.
            if hakem_won:
                addend = 2
            else:
                addend = 3
        else:

            # Standard win.  One point.
            action_str = "^Wwon^~"
            addend = 1

        # Let everyone know.
        self.bc_pre("%s %s the hand and gains ^C%s^~.\n" %
                    (winning_str, action_str, get_plural_str(addend, "point")))

        # Apply the score.
        if self.mode == 4:
            winner.score += addend
        else:
            winner.data.score += addend

        # Show everyone's scores.
        self.bc_pre(self.get_score_str())

        # Did the hakem not win?  If so, we need to have a new hakem and dealer.
        if not hakem_won:

            # In 4p mode, it just rotates...
            if self.mode == 4:
                self.dealer = self.hakem
                self.hakem = self.next_seat(self.hakem)
            else:

                # In 3p mode, the winner becomes hakem.
                self.hakem = winner
                self.dealer = self.prev_seat(self.hakem)

            self.bc_pre(
                "The ^Yhakem^~ has been unseated!  The new hakem is %s.\n" %
                self.get_sp_str(self.hakem))
        else:
            self.bc_pre("%s remains the hakem.\n" %
                        self.get_sp_str(self.hakem))

    def find_winner(self):

        if self.mode == 4:

            # Easy: has one of the sides reached a winning score?
            if self.ns.score >= self.goal:
                return self.ns
            elif self.ew.score >= self.goal:
                return self.ew

        else:

            # Have any of the players reached a winning score?
            for seat in self.seats:
                if seat.data.score >= self.goal:
                    return seat

        return None

    def resolve(self, winner):

        if self.mode == 4:
            if self.ns == winner:
                name_one = self.seats[0].player_name
                name_two = self.seats[2].player_name
            else:
                name_one = self.seats[1].player_name
                name_two = self.seats[3].player_name
            self.bc_pre("^G%s^~ and ^G%s^~ win!\n" % (name_one, name_two))
        else:
            self.bc_pre("^G%s^~ wins!\n" % winner.player_name)
예제 #12
0
파일: whist.py 프로젝트: sunfall/giles
class Whist(SeatedGame):
    """A Whist game table implementation.  Whist came about sometime in the
    18th century.  This implementation does not (currently) score honours,
    because honours are boring.
    """

    def __init__(self, server, table_name):

        super(Whist, self).__init__(server, table_name)

        self.game_display_name = "Whist"
        self.game_name = "whist"
        self.seats = [
            Seat("North"),
            Seat("East"),
            Seat("South"),
            Seat("West"),
        ]

        self.min_players = 4
        self.max_players = 4
        self.state = State("need_players")
        self.prefix = "(^RWhist^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Whist-specific guff.
        self.ns = Struct()
        self.ns.score = 0
        self.ew = Struct()
        self.ew.score = 0
        self.seats[0].data.who = NORTH
        self.seats[1].data.who = EAST
        self.seats[2].data.who = SOUTH
        self.seats[3].data.who = WEST

        self.goal = 5
        self.trick = None
        self.trump_suit = None
        self.led_suit = None
        self.turn = None
        self.dealer = None
        self.winner = None

        self.layout = FourPlayerCardGameLayout()

    def show_help(self, player):

        super(Whist, self).show_help(player)
        player.tell_cc("\nWHIST SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("            ^!goal^. <num>, ^!score^.     Set the goal score to <num>.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nWHIST PLAY:\n\n")
        player.tell_cc("              ^!play^. <card>, ^!pl^.     Play <card> from your hand.\n")
        player.tell_cc("                 ^!hand^., ^!inv^., ^!i^.     Look at the cards in your hand.\n")

    def display(self, player):

        player.tell_cc("%s" % self.layout)

    def get_score_str(self):
        return "          ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (self.ns.score, self.ew.score)

    def get_color_code(self, seat):

        if seat == self.seats[0] or seat == self.seats[2]:
            return "^R"
        else:
            return "^M"

    def get_sp_str(self, seat):

        return "^G%s^~ (%s%s^~)" % (seat.player_name, self.get_color_code(seat), seat)

    def get_metadata(self):

        to_return = "\n\n"
        if self.turn:
            to_return += "It is ^Y%s^~'s turn (%s%s^~).  Trumps are ^C%s^~.\n" % (self.turn.player_name, self.get_color_code(self.turn), self.turn, self.trump_suit)
            to_return += "Tricks:   ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (self.ns.tricks, self.ew.tricks)
        to_return += "The goal score for this game is ^C%s^~.\n" % get_plural_str(self.goal, "point")
        to_return += self.get_score_str()

        return to_return

    def show(self, player):
        self.display(player)
        player.tell_cc(self.get_metadata())

    def set_goal(self, player, goal_str):

        if not goal_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_goal = int(goal_str)
        if new_goal < 1:
            self.tell_pre(player, "The goal must be at least one point.\n")
            return False

        # Got a valid goal.
        self.goal = new_goal
        self.bc_pre("^M%s^~ has changed the goal to ^G%s^~.\n" % (player, get_plural_str(new_goal, "point")))

    def clear_trick(self):

        # Set the current trick to an empty hand...
        self.trick = Hand()
        self.led_suit = None

        # ...and set everyone's played card to None.
        for seat in self.seats:
            seat.data.card = None

        # Clear the layout as well.
        self.layout.clear()

    def new_deal(self):

        dealer_name = self.dealer.player_name

        self.bc_pre("^R%s^~ (%s%s^~) gives the cards a good shuffle...\n" % (dealer_name, self.get_color_code(self.dealer), self.dealer))
        deck = new_deck()
        deck.shuffle()

        # Deal out all of the cards.  We'll flip the last one; that determines
        # the trump suit for the hand.
        self.bc_pre("^R%s^~ deals the cards out to all the players.\n" % dealer_name)
        for seat in self.seats:
            seat.data.hand = Hand()
        for i in range(13):
            for seat in self.seats:
                seat.data.hand.add(deck.discard())

        # Flip the dealer's last card; it determines the trump suit.
        last_card = self.dealer.data.hand[-1]
        self.bc_pre("^R%s^~ flips their last card; it is ^C%s^~.\n" % (dealer_name,
           card_to_str(last_card, LONG)))
        self.trump_suit = last_card.suit

        # Sort everyone's hands.
        for seat in self.seats:
            seat.data.hand = sorted_hand(seat.data.hand, self.trump_suit)

        # Show everyone their hands.
        self.show_hands()

        # Set the trick counts to zero.
        self.ns.tricks = 0
        self.ew.tricks = 0

    def show_hand(self, player):

        seat = self.get_seat_of_player(player)

        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return

        print_str = "Your current hand:\n   "
        print_str += hand_to_str(seat.data.hand, self.trump_suit)
        print_str += "\n"
        self.tell_pre(player, print_str)

    def show_hands(self):

        for seat in self.seats:
            if seat.player:
                self.show_hand(seat.player)

    def play(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # Okay, it's a card in their hand.  First, let's do the "follow the
        # led suit" business.
        action_str = "^Wplays^~"
        if self.led_suit:

            this_suit = potential_card.suit
            if (this_suit != self.led_suit and
               hand_has_suit(seat.data.hand, self.led_suit)):

                # You can't play off-suit if you can match the led suit.
                self.tell_pre(player, "You can't throw off; you have the led suit.\n")
                return False

        else:

            # No led suit; they're the leader.
            action_str = "^Yleads^~ with"
            self.led_suit = potential_card.suit

        # They either matched the led suit, didn't have any of it, or they
        # are themselves the leader.  Nevertheless, their play is valid.
        seat.data.card = potential_card
        self.trick.add(seat.data.hand.discard_specific(potential_card))
        trump_str = ""
        if potential_card.suit == self.trump_suit:
            trump_str = ", a ^Rtrump^~"
        self.bc_pre("%s %s ^C%s^~%s.\n" % (self.get_sp_str(seat), action_str, card_to_str(potential_card, LONG), trump_str))
        self.layout.place(seat.data.who, potential_card)
        return potential_card

    def tick(self):

        # If all seats are full and active, autostart.
        if (self.state.get() == "need_players" and self.seats[0].player and
           self.seats[1].player and self.seats[2].player and
           self.seats[3].player and self.active):
            self.state.set("playing")
            self.bc_pre("The game has begun.\n")

            # Initialize everything by clearing the (non-existent) trick.
            self.clear_trick()

            # Make a new deal.
            self.dealer = self.seats[0]
            self.new_deal()

            # Eldest leads to the first trick.
            self.turn = self.next_seat(self.dealer)
            self.layout.change_turn(self.turn.data.who)

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()

            command_bits = command_str.split()
            primary = command_bits[0].lower()

            if state == "setup":

                if primary in ("goal", "score", "sc", "g",):
                    if len(command_bits) == 2:
                        self.set_goal(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid goal command.\n")
                    handled = True

                elif primary in ("done", "ready", "d", "r",):
                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):
                    self.state.set("setup")
                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    handled = True

            elif state == "playing":

                card_played = False
                if primary in ("hand", "inventory", "inv", "i",):
                    self.show_hand(player)
                    handled = True

                elif primary in ("play", "move", "pl", "mv",):
                    if len(command_bits) == 2:
                        card_played = self.play(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid play command.\n")
                    handled = True

                if card_played:

                    # A card hit the table.  We need to do stuff.
                    if len(self.trick) == 4:

                        # Finish the trick up.
                        self.finish_trick()

                        # Is that the last trick of this hand?
                        if self.ns.tricks + self.ew.tricks == 13:

                            # Yup.  Finish the hand up.
                            self.finish_hand()

                            # Did someone win the overall game?
                            winner = self.find_winner()
                            if winner:

                                # Yup.  Finish.
                                self.resolve(winner)
                                self.finish()

                            else:

                                # Nope.  Pass the deal to the next dealer...
                                self.dealer = self.next_seat(self.dealer)

                                # Deal and set up the first player.
                                self.new_deal()
                                self.turn = self.next_seat(self.dealer)
                                self.layout.change_turn(self.turn.data.who)

                    else:

                        # Trick not over.  Rotate.
                        self.turn = self.next_seat(self.turn)
                        self.layout.change_turn(self.turn.data.who)
                        if self.turn.player:
                            self.show_hand(self.turn.player)

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def finish_trick(self):

        # Okay, we have a trick with four cards.  Which card won?
        winner = handle_trick(self.trick, self.trump_suit)

        # This /should/ just return one seat...
        winning_seat_list = [x for x in self.seats if x.data.card == winner]

        if len(winning_seat_list) != 1:
            self.server.log.log(self.log_prefix + "Something went horribly awry; trick ended without a finish.")
            self.bc_pre("Something went horribly wrong; no one won the trick!  Tell the admin.\n")
            return

        winning_seat = winning_seat_list[0]

        # Print information about the winning card.
        self.bc_pre("%s wins the trick with ^C%s^~.\n" % (self.get_sp_str(winning_seat), card_to_str(winner, LONG)))

        # Give the trick to the correct partnership.
        if winning_seat == self.seats[0] or winning_seat == self.seats[2]:
            self.ns.tricks += 1
        else:
            self.ew.tricks += 1

        # Clear the trick.
        self.clear_trick()

        # Set the next leader to the player who won.
        self.turn = winning_seat
        self.layout.change_turn(self.turn.data.who)
        if self.turn.player:
            self.show_hand(self.turn.player)

    def finish_hand(self):

        # Which side won more than 6 tricks?
        if self.ns.tricks > 6:
            winning_side = "^RNorth/South^~"
            addend = self.ns.tricks - 6
            self.ns.score += addend
        else:
            winning_side = "^MEast/West^~"
            addend = self.ew.tricks - 6
            self.ew.score += addend

        # Let everyone know.
        self.bc_pre("%s wins the hand and gains ^C%s^~.\n" % (winning_side, get_plural_str(addend, "point")))
        self.bc_pre(self.get_score_str())

    def find_winner(self):

        # Easy: has one of the sides reached a winning score?
        if self.ns.score >= self.goal:
            return self.ns
        elif self.ew.score >= self.goal:
            return self.ew

        return None

    def resolve(self, winning_partnership):

        if self.ns == winning_partnership:
            name_one = self.seats[0].player_name
            name_two = self.seats[2].player_name
        else:
            name_one = self.seats[1].player_name
            name_two = self.seats[3].player_name
        self.bc_pre("^G%s^~ and ^G%s^~ win!\n" % (name_one, name_two))
예제 #13
0
def new_deck(ace_high=True):
    deck = Hand()
    for r in RANKS:
        for s in SUITS:
            deck.add(PlayingCard(r, s, ace_high))
    return deck
예제 #14
0
class Expeditions(SeatedGame):
    """A Expeditions game table implementation.  Based on a game invented in
    1999 by Reiner Knizia.
    """

    def __init__(self, server, table_name):

        super(Expeditions, self).__init__(server, table_name)

        self.game_display_name = "Expeditions"
        self.game_name = "expeditions"
        self.seats = [
            Seat("Left"),
            Seat("Right")
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RExpeditions^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Expeditions-specific stuff.
        self.suit_count = 5
        self.agreement_count = 3
        self.penalty = 20
        self.bonus = True
        self.bonus_length = 8
        self.bonus_points = 20
        self.hand_size = 8
        self.goal = 1

        self.turn = None
        self.draw_pile = None
        self.discards = []
        self.left = self.seats[0]
        self.right = self.seats[1]
        self.left.data.side = LEFT
        self.left.data.curr_score = 0
        self.left.data.overall_score = 0
        self.left.data.hand = None
        self.left.data.expeditions = []
        self.right.data.side = RIGHT
        self.right.data.curr_score = 0
        self.right.data.overall_score = 0
        self.right.data.hand = None
        self.right.data.expeditions = []
        self.resigner = None
        self.first_player = None
        self.just_discarded_to = None

        self.printable_layout = None
        self.init_hand()

    def init_hand(self):

        # Depending on the number of suits requested for play, we build
        # structures.
        suit_list = DEFAULT_SUITS[:]
        if self.suit_count >= 6:
            suit_list.append(CYAN)
        if self.suit_count == 7:
            suit_list.append(MAGENTA)

        # If, on the other hand, the number of suits is /less/ than five,
        # we use a subset of the default suits.
        if self.suit_count < 5:
            suit_list = DEFAULT_SUITS[:self.suit_count]

        # All right, we have a list of suits involved in this game.  Let's
        # build the various piles that are based on those suits.
        self.left.data.expeditions = []
        self.right.data.expeditions = []
        self.discards = []
        for suit in suit_list:
            discard_pile = Struct()
            left_expedition = Struct()
            right_expedition = Struct()
            for pile in (discard_pile, left_expedition, right_expedition):
                pile.suit = suit
                pile.hand = Hand()
                pile.value = 0
            self.left.data.expeditions.append(left_expedition)
            self.right.data.expeditions.append(right_expedition)
            self.discards.append(discard_pile)

        # We'll do a separate loop for generating the deck to minimize
        # confusion.
        self.draw_pile = Hand()
        for suit in suit_list:
            for rank in NUMERICAL_RANKS:
                self.draw_pile.add(ExpeditionsCard(rank, suit))

            # Add as many agreements as requested.
            for agreement in range(self.agreement_count):
                self.draw_pile.add(ExpeditionsCard(AGREEMENT, suit))

        # Lastly, shuffle the draw deck and initialize hands.
        self.draw_pile.shuffle()
        self.left.data.hand = Hand()
        self.right.data.hand = Hand()

    def get_discard_str(self, pos):

        discard_pile = self.discards[pos]
        if len(discard_pile.hand):
            return(value_to_str(discard_pile.hand[-1].value()))
        return "."

    def get_expedition_str(self, expedition):

        to_return = ""
        for card in expedition:
            to_return += value_to_str(card.value())

        return to_return

    def get_sp_str(self, seat):

        if seat == self.left:
            return "^C%s^~" % self.left.player_name
        else:
            return "^M%s^~" % self.right.player_name

    def update_printable_layout(self):

        self.printable_layout = []
        self.printable_layout.append("                   .---.\n")

        # Loop through all table rows.
        for row in range(self.suit_count):
            left = self.left.data.expeditions[row]
            right = self.right.data.expeditions[row]
            suit_char = left.suit[0].upper()
            left_suit_char = suit_char
            right_suit_char = suit_char
            expedition_str = get_color_code(left.suit)
            expedition_str += self.get_expedition_str(left.hand.reversed()).rjust(18)
            if self.bonus and len(left.hand) >= self.bonus_length:
                left_suit_char = "*"
            if self.bonus and len(right.hand) >= self.bonus_length:
                right_suit_char = "*"
            expedition_str += " %s %s %s " % (left_suit_char, self.get_discard_str(row), right_suit_char)
            expedition_str += self.get_expedition_str(right.hand)
            expedition_str += "^~\n"
            self.printable_layout.append(expedition_str)
            self.printable_layout.append("                   |   |\n")

        # Replace the last unnecessary separator row with the end of the board.
        self.printable_layout[-1] = "                   `---'\n"

    def get_metadata_str(self):

        to_return = "^Y%s^~ remain in the draw pile.\n" % get_plural_str(len(self.draw_pile), "card")
        if not self.turn:
            to_return += "The game has not started yet.\n"
        else:
            to_return += "It is %s's turn to " % self.get_sp_str(self.turn)
            sub = self.state.get_sub()
            if sub == "play":
                to_return += "^cplay a card^~.\n"
            else:
                to_return += "^cdraw a card^~.\n"
        to_return += "The goal score for this game is ^Y%s^~.\n" % get_plural_str(self.goal, "point")
        to_return += "Overall:      %s: %s     %s: %s\n" % (self.get_sp_str(self.left), self.left.data.overall_score, self.get_sp_str(self.right), self.right.data.overall_score)

        return to_return

    def show(self, player, show_metadata=True):

        if not self.printable_layout:
            self.update_printable_layout()
        player.tell_cc("%s         %s\n" % (self.get_sp_str(self.left).rjust(21), self.get_sp_str(self.right)))
        for line in self.printable_layout:
            player.tell_cc(line)
        if show_metadata:
            player.tell_cc("\n" + self.get_metadata_str())

    def show_hand(self, player):

        seat = self.get_seat_of_player(player)

        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return

        print_str = "Your current hand:\n   "
        print_str += hand_to_str(seat.data.hand)
        print_str += "\n"
        self.tell_pre(player, print_str)

    def send_layout(self, show_metadata=True):

        for player in self.channel.listeners:
            self.show(player, show_metadata)
        for seat in self.seats:
            if seat.player:
                self.show_hand(seat.player)

    def deal(self):

        # Deal cards until each player has hand_size cards.
        self.bc_pre("A fresh hand is dealt to both players.\n")
        for i in range(self.hand_size):
            self.left.data.hand.add(self.draw_pile.discard())
            self.right.data.hand.add(self.draw_pile.discard())

        # Sort hands.
        self.left.data.hand = sorted_hand(self.left.data.hand)
        self.right.data.hand = sorted_hand(self.right.data.hand)

        # Clear scores.
        self.left.data.curr_score = 0
        self.right.data.curr_score = 0

    def tick(self):

        # If both seats are full and the game is active, autostart.
        if (self.state.get() == "need_players" and self.seats[0].player
           and self.seats[1].player and self.active):
            self.state.set("playing")
            self.state.set_sub("play")
            self.bc_pre("^CLeft^~: ^Y%s^~; ^MRight^~: ^Y%s^~\n" %
               (self.left.player_name, self.right.player_name))
            self.turn = self.left
            self.first_player = self.left
            self.deal()
            self.send_layout()

    def calculate_deck_size(self, suits, agrees):

        # Eventually this will depend on just what cards are in the deck,
        # but for now there are 9 point cards per suit plus the agreements.
        return (9 + agrees) * suits

    def set_suits(self, player, suit_str):

        if not suit_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_suit_count = int(suit_str)
        if new_suit_count < MIN_SUITS or new_suit_count > MAX_SUITS:
            self.tell_pre(player, "The number of suits must be between %d and %d inclusive.\n" % (MIN_SUITS, MAX_SUITS))
            return False

        # Does this give too few cards for the hand size?
        if self.calculate_deck_size(new_suit_count, self.agreement_count) <= self.hand_size * 2:
            self.tell_pre(player, "That number of suits is too small for the hand size.\n")
            return False

        # Valid.
        self.suit_count = new_suit_count
        self.bc_pre("^M%s^~ has changed the suit count to ^G%s^~.\n" % (player, new_suit_count))
        self.init_hand()
        self.update_printable_layout()

    def set_agreements(self, player, agree_str):

        if not agree_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_agree_count = int(agree_str)
        if new_agree_count < MIN_AGREEMENTS or new_agree_count > MAX_AGREEMENTS:
            self.tell_pre(player, "The number of agreements must be between %d and %d inclusive.\n" % (MIN_AGREEMENTS, MAX_AGREEMENTS))
            return False

        # Does this give too few cards for the hand size?
        if self.calculate_deck_size(self.suit_count, new_agree_count) <= self.hand_size * 2:
            self.tell_pre(player, "That number of agreements is too small for the hand size.\n")
            return False

        # Valid.
        self.agreement_count = new_agree_count
        self.bc_pre("^M%s^~ has changed the agreement count to ^G%s^~.\n" % (player, new_agree_count))
        self.init_hand()
        self.update_printable_layout()

    def set_hand(self, player, hand_str):

        if not hand_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_hand_size = int(hand_str)
        if new_hand_size < MIN_HAND_SIZE or new_hand_size > MAX_HAND_SIZE:
            self.tell_pre(player, "The hand size must be between %d and %d inclusive.\n" % (MIN_HAND_SIZE, MAX_HAND_SIZE))
            return False

        # If the drawn hands are greater than or equal to the actual card
        # count, that doesn't work either.
        if (new_hand_size * 2) >= len(self.draw_pile):
            self.tell_pre(player, "The hand size is too large for the number of cards in play.\n")
            return False

        # Valid.
        self.hand_size = new_hand_size
        self.bc_pre("^M%s^~ has changed the hand size to ^G%s^~.\n" % (player, new_hand_size))

    def set_penalty(self, player, penalty_str):

        if not penalty_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_penalty = int(penalty_str)
        if new_penalty < MIN_PENALTY or new_penalty > MAX_PENALTY:
            self.tell_pre(player, "The penalty must be between %d and %d inclusive.\n" % (MIN_PENALTY, MAX_PENALTY))
            return False

        # Valid.
        self.penalty = new_penalty
        self.bc_pre("^M%s^~ has changed the penalty to ^G%s^~.\n" % (player, new_penalty))

    def set_bonus(self, player, bonus_bits):

        if len(bonus_bits) == 1:

            bonus = bonus_bits[0]
            # Gotta be 'none' or 0.
            if bonus in ("none", "n", "0",):
                self.bonus = False
                self.bc_pre("^M%s^~ has disabled the expedition bonuses.\n" % player)
                return True
            else:
                self.tell_pre(player, "Invalid bonus command.\n")
                return False

        elif len(bonus_bits) == 2:

            points, length = bonus_bits

            if not points.isdigit() or not length.isdigit():
                self.tell_pre(player, "Invalid bonus command.\n")
                return False

            points = int(points)
            length = int(length)

            if not points or not length:
                self.bonus = False
                self.bc_pre("^M%s^~ has disabled the expedition bonuses.\n" % player)
                return True
            else:
                self.bonus = True
                self.bonus_points = points
                self.bonus_length = length
                self.bc_pre("^M%s^~ has set the expedition bonuses to ^C%s^~ at length ^R%s^~.\n" % (player, get_plural_str(points, "point"), length))
                return True

        else:
            self.tell_pre(player, "Invalid bonus command.\n")
            return False

    def set_goal(self, player, goal_str):

        if not goal_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_goal = int(goal_str)
        if new_goal < 1:
            self.tell_pre(player, "The goal must be at least one point.\n")
            return False

        # Got a valid goal.
        self.goal = new_goal
        self.bc_pre("^M%s^~ has changed the goal to ^G%s^~.\n" % (player, get_plural_str(new_goal, "point")))

    def suit_to_loc(self, suit):

        if suit in DEFAULT_SUITS:
            return DEFAULT_SUITS.index(suit)
        elif suit == CYAN:
            return 5
        elif suit == MAGENTA:
            return 6

        return None

    def evaluate(self, player):

        for seat in self.seats:
            score_str = "%s: " % seat.player_name
            score_str += " + ".join(["%s%s^~" % (get_color_code(x.suit), x.value) for x in seat.data.expeditions])
            score_str += " = %s\n" % (get_plural_str(seat.data.curr_score, "point"))
            self.tell_pre(player, score_str)

    def play(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "play":
            self.tell_pre(player, "You should be drawing or retrieving, not playing!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # All right.  Grab the hand for that expedition.
        exp_hand = seat.data.expeditions[self.suit_to_loc(potential_card.suit)].hand

        # If this card is a lower value than the top card of the hand, nope.
        if len(exp_hand) and potential_card < exp_hand[-1]:
            self.tell_pre(player, "You can no longer play this card on this expedition.\n")
            return False

        # If it's the same value and not an agreement, nope.
        elif (len(exp_hand) and potential_card == exp_hand[-1] and
           potential_card.rank != AGREEMENT):
            self.tell_pre(player, "You cannot play same-valued point cards on an expedition.\n")
            return False

        # Passed the tests.  Play it and clear the discard tracker.
        exp_hand.add(seat.data.hand.discard_specific(potential_card))
        self.just_discarded_to = None

        self.bc_pre("%s played %s.\n" % (self.get_sp_str(seat),
                       card_to_str(potential_card, mode=LONG)))
        return True

    def discard(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "play":
            self.tell_pre(player, "You should be drawing or retrieving, not discarding!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # All right, they can discard it.  Get the appropriate discard pile...
        discard_pile = self.discards[self.suit_to_loc(potential_card.suit)].hand

        discard_pile.add(seat.data.hand.discard_specific(potential_card))

        # Note the pile we just discarded to, so the player can't just pick it
        # back up as their next play.
        self.just_discarded_to = potential_card.suit

        self.bc_pre("%s discarded %s.\n" % (self.get_sp_str(seat),
                          card_to_str(potential_card, mode=LONG)))
        return True

    def draw(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "draw":
            self.tell_pre(player, "You should be playing or discarding, not drawing!\n")
            return False

        # Draw a card.  This one's easy!
        draw_card = self.draw_pile.discard()
        seat.data.hand.add(draw_card)

        # Resort the hand.
        seat.data.hand = sorted_hand(seat.data.hand)

        self.bc_pre("%s drew a card.\n" % (self.get_sp_str(seat)))
        self.tell_pre(player, "You drew %s.\n" % card_to_str(draw_card, mode=LONG))
        return True

    def retrieve(self, player, retrieve_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "draw":
            self.tell_pre(player, "You should be playing or discarding, not retrieving!\n")
            return False

        # Turn the retrieve string into an actual suit.
        suit = str_to_suit(retrieve_str)
        if not suit:
            self.tell_pre(player, "That's not a valid suit!\n")
            return False

        # Is that a valid location in this game?
        loc = self.suit_to_loc(suit)
        if loc >= self.suit_count:
            self.tell_pre(player, "That suit isn't in play this game.\n")
            return False

        # Is there actually a card there /to/ draw?
        discard_pile = self.discards[loc].hand
        if not len(discard_pile):
            self.tell_pre(player, "There are no discards of that suit.\n")
            return False

        # Is it the card they just discarded?
        if suit == self.just_discarded_to:
            self.tell_pre(player, "You just discarded that card!\n")
            return False

        # Phew.  All tests passed.  Give them the card.
        dis_card = discard_pile.discard()
        seat.data.hand.add(dis_card)
        seat.data.hand = sorted_hand(seat.data.hand)

        self.bc_pre("%s retrieved %s from the discards.\n" % (self.get_sp_str(seat),
                                                  card_to_str(dis_card, mode=LONG)))
        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if self.turn != seat:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("suits",):

                    if len(command_bits) == 2:
                        self.set_suits(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid suits command.\n")
                    handled = True

                elif primary in ("agreements", "agree",):

                    if len(command_bits) == 2:
                        self.set_agreements(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid agree command.\n")
                    handled = True

                elif primary in ("hand",):

                    if len(command_bits) == 2:
                        self.set_hand(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid hand command.\n")
                    handled = True

                elif primary in ("penalty",):

                    if len(command_bits) == 2:
                        self.set_penalty(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid penalty command.\n")
                    handled = True

                elif primary in ("bonus",):

                    if len(command_bits) >= 2:
                        self.set_bonus(player, command_bits[1:])
                    else:
                        self.tell_pre(player, "Invalid bonus command.\n")
                    handled = True

                elif primary in ("goal", "score",):

                    if len(command_bits) == 2:
                        self.set_goal(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid goal command.\n")
                    handled = True

                elif primary in ("done", "ready", "d", "r",):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):

                    self.state.set("setup")
                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in ("hand", "inventory", "inv", "i",):
                    self.show_hand(player)
                    handled = True

                elif primary in ("evaluate", "eval", "score", "e", "s",):
                    self.evaluate(player)
                    handled = True

                elif primary in ("move", "play", "mv", "pl",):

                    if len(command_bits) == 2:
                        made_move = self.play(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid play command.\n")
                    handled = True

                elif primary in ("discard", "toss", "dc", "di", "to",):
                    if len(command_bits) == 2:
                        made_move = self.discard(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid discard command.\n")
                    handled = True

                elif primary in ("draw", "dr",):
                    made_move = self.draw(player)
                    handled = True

                elif primary in ("retrieve", "re",):
                    if len(command_bits) == 2:
                        made_move = self.retrieve(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid retrieve command.\n")
                    handled = True

                elif primary in ("resign",):

                    if self.resign(player):
                        made_move = True
                    handled = True

                if made_move:

                    substate = self.state.get_sub()

                    # Okay, something happened on the layout.  Update scores
                    # and the layout.
                    self.update_scores()
                    self.update_printable_layout()

                    # Is the game over?
                    if not len(self.draw_pile) or self.resigner:

                        # Yup.  Resolve the game.
                        self.resolve_hand()

                        # Is there an overall winner?
                        winner = self.find_winner()

                        if winner:
                            self.resolve(winner)
                            self.finish()

                        else:

                            # Hand over, but not the game itself.  New deal.
                            self.bc_pre("The cards are collected for another hand.\n")
                            self.init_hand()

                            # Switch dealers.
                            self.first_player = self.next_seat(self.first_player)
                            self.turn = self.first_player
                            self.state.set("playing")
                            self.state.set_sub("play")
                            self.deal()
                            self.update_printable_layout()
                            self.send_layout()

                    else:

                        # If we're in the play substate, switch to the draw.
                        if substate == "play":
                            self.state.set_sub("draw")

                        else:

                            # After draw, switch turns and resend the board.
                            self.state.set_sub("play")
                            self.turn = self.next_seat(self.turn)
                            self.send_layout(show_metadata=False)

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def update_scores(self):

        for seat in self.left, self.right:

            # Start at 0.
            total = 0

            # For each expedition, if they're even on it...
            for exp in seat.data.expeditions:

                curr = 0
                multiplier = 1

                if len(exp.hand):

                    # Immediately assign the penalty.
                    curr -= self.penalty

                    # Now loop through the cards.
                    for card in exp.hand:

                        value = card.value()
                        if value == 1:

                            # Agreement; adjust multiplier.
                            multiplier += 1

                        else:

                            # Scoring card; increase current score.
                            curr += value

                    # Adjust the current score by the multiplier.
                    curr *= multiplier

                    # If bonuses are active, and this meets it, add it.
                    if self.bonus and len(exp.hand) >= self.bonus_length:
                        curr += self.bonus_points

                # No matter what, add curr to total and set it on the
                # pile.
                total += curr
                exp.value = curr

            # Set the current score for the seat.
            seat.data.curr_score = total

    def resolve_hand(self):

        for seat in self.left, self.right:

            addend = seat.data.curr_score
            if addend > 0:
                adj_str = "^Ygains ^C%s^~" % get_plural_str(addend, "point")
            elif addend < 0:
                adj_str = "^yloses ^c%s^~" % get_plural_str(-addend, "point")
            else:
                adj_str = "^Wsomehow manages to score precisely zero points^~"

            # Actually adjust the scores by the proper amounts, and inform
            # everyone of the result.
            seat.data.overall_score += addend

            # If someone resigned, scores don't matter, so don't show them.
            if not self.resigner:
                self.bc_pre("%s %s, giving them ^G%s^~.\n" % (self.get_sp_str(seat), adj_str, seat.data.overall_score))

    def find_winner(self):

        # If someone resigned, this is the easiest thing ever.
        if self.resigner == self.left:
            return self.right
        elif self.resigner == self.right:
            return self.left

        # If one player has a higher score than the other and that score
        # is higher than the goal, they win.
        if (self.left.data.overall_score > self.right.data.overall_score and
           self.left.data.overall_score >= self.goal):
            return self.left
        elif (self.right.data.overall_score > self.left.data.overall_score and
           self.right.data.overall_score >= self.goal):
            return self.right

        # Either we haven't reached the goal or there's a tie.  We'll print a
        # special message if there's a tie, because that's kinda crazy.
        if self.left.data.overall_score == self.right.data.overall_score:
            self.bc_pre("The players are tied!\n")

        # No matter what, there's no winner.
        return None

    def resolve(self, winner):
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Expeditions, self).show_help(player)
        player.tell_cc("\nEXPEDITIONS SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("                  ^!suits^. <num>     Play with <num> suits.\n")
        player.tell_cc("                  ^!agree^. <num>     Suits have <num> agreements.\n")
        player.tell_cc("                   ^!hand^. <num>     Hands have <num> cards.\n")
        player.tell_cc("                ^!penalty^. <num>     Expeditions start down <num> points.\n")
        player.tell_cc("     ^!bonus^. <pts> <len> | none     Bonus is <pts> at length <len>/none.\n")
        player.tell_cc("            ^!goal^. <num>, ^!score^.     Play until <num> points.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nEXPEDITIONS PLAY:\n\n")
        player.tell_cc("              ^!play^. <card>, ^!pl^.     Play <card> from your hand.\n")
        player.tell_cc("         ^!discard^. <card>, ^!toss^.     Discard <card> from your hand.\n")
        player.tell_cc("                     ^!draw^., ^!dr^.     Draw from the draw pile.\n")
        player.tell_cc("          ^!retrieve^. <suit>, ^!re^.     Retrieve top discard of <suit>.\n")
        player.tell_cc("                       ^!resign^.     Resign.\n")
        player.tell_cc("                 ^!hand^., ^!inv^., ^!i^.     Look at the cards in your hand.\n")
        player.tell_cc("               ^!evaluate^., ^!eval^.     Evaluate the current scores.\n")
예제 #15
0
class Expeditions(SeatedGame):
    """A Expeditions game table implementation.  Based on a game invented in
    1999 by Reiner Knizia.
    """

    def __init__(self, server, table_name):

        super(Expeditions, self).__init__(server, table_name)

        self.game_display_name = "Expeditions"
        self.game_name = "expeditions"
        self.seats = [
            Seat("Left"),
            Seat("Right")
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RExpeditions^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Expeditions-specific stuff.
        self.suit_count = 5
        self.agreement_count = 3
        self.penalty = 20
        self.bonus = True
        self.bonus_length = 8
        self.bonus_points = 20
        self.hand_size = 8
        self.goal = 1

        self.turn = None
        self.draw_pile = None
        self.discards = []
        self.left = self.seats[0]
        self.right = self.seats[1]
        self.left.data.side = LEFT
        self.left.data.curr_score = 0
        self.left.data.overall_score = 0
        self.left.data.hand = None
        self.left.data.expeditions = []
        self.right.data.side = RIGHT
        self.right.data.curr_score = 0
        self.right.data.overall_score = 0
        self.right.data.hand = None
        self.right.data.expeditions = []
        self.resigner = None
        self.first_player = None
        self.just_discarded_to = None

        self.printable_layout = None
        self.init_hand()

    def init_hand(self):

        # Depending on the number of suits requested for play, we build
        # structures.
        suit_list = DEFAULT_SUITS[:]
        if self.suit_count >= 6:
            suit_list.append(CYAN)
        if self.suit_count == 7:
            suit_list.append(MAGENTA)

        # If, on the other hand, the number of suits is /less/ than five,
        # we use a subset of the default suits.
        if self.suit_count < 5:
            suit_list = DEFAULT_SUITS[:self.suit_count]

        # All right, we have a list of suits involved in this game.  Let's
        # build the various piles that are based on those suits.
        self.left.data.expeditions = []
        self.right.data.expeditions = []
        self.discards = []
        for suit in suit_list:
            discard_pile = Struct()
            left_expedition = Struct()
            right_expedition = Struct()
            for pile in (discard_pile, left_expedition, right_expedition):
                pile.suit = suit
                pile.hand = Hand()
                pile.value = 0
            self.left.data.expeditions.append(left_expedition)
            self.right.data.expeditions.append(right_expedition)
            self.discards.append(discard_pile)

        # We'll do a separate loop for generating the deck to minimize
        # confusion.
        self.draw_pile = Hand()
        for suit in suit_list:
            for rank in NUMERICAL_RANKS:
                self.draw_pile.add(ExpeditionsCard(rank, suit))

            # Add as many agreements as requested.
            for agreement in range(self.agreement_count):
                self.draw_pile.add(ExpeditionsCard(AGREEMENT, suit))

        # Lastly, shuffle the draw deck and initialize hands.
        self.draw_pile.shuffle()
        self.left.data.hand = Hand()
        self.right.data.hand = Hand()

    def get_discard_str(self, pos):

        discard_pile = self.discards[pos]
        if len(discard_pile.hand):
            return(value_to_str(discard_pile.hand[-1].value()))
        return "."

    def get_expedition_str(self, expedition):

        to_return = ""
        for card in expedition:
            to_return += value_to_str(card.value())

        return to_return

    def get_sp_str(self, seat):

        if seat == self.left:
            return "^C%s^~" % self.left.player_name
        else:
            return "^M%s^~" % self.right.player_name

    def update_printable_layout(self):

        self.printable_layout = []
        self.printable_layout.append("                   .---.\n")

        # Loop through all table rows.
        for row in range(self.suit_count):
            left = self.left.data.expeditions[row]
            right = self.right.data.expeditions[row]
            suit_char = left.suit[0].upper()
            left_suit_char = suit_char
            right_suit_char = suit_char
            expedition_str = get_color_code(left.suit)
            expedition_str += self.get_expedition_str(left.hand.reversed()).rjust(18)
            if self.bonus and len(left.hand) >= self.bonus_length:
                left_suit_char = "*"
            if self.bonus and len(right.hand) >= self.bonus_length:
                right_suit_char = "*"
            expedition_str += " %s %s %s " % (left_suit_char, self.get_discard_str(row), right_suit_char)
            expedition_str += self.get_expedition_str(right.hand)
            expedition_str += "^~\n"
            self.printable_layout.append(expedition_str)
            self.printable_layout.append("                   |   |\n")

        # Replace the last unnecessary separator row with the end of the board.
        self.printable_layout[-1] = "                   `---'\n"

    def get_metadata_str(self):

        to_return = "^Y%s^~ remain in the draw pile.\n" % get_plural_str(len(self.draw_pile), "card")
        if not self.turn:
            to_return += "The game has not started yet.\n"
        else:
            to_return += "It is %s's turn to " % self.get_sp_str(self.turn)
            sub = self.state.get_sub()
            if sub == "play":
                to_return += "^cplay a card^~.\n"
            else:
                to_return += "^cdraw a card^~.\n"
        to_return += "The goal score for this game is ^Y%s^~.\n" % get_plural_str(self.goal, "point")
        to_return += "Overall:      %s: %s     %s: %s\n" % (self.get_sp_str(self.left), self.left.data.overall_score, self.get_sp_str(self.right), self.right.data.overall_score)

        return to_return

    def show(self, player, show_metadata=True):

        if not self.printable_layout:
            self.update_printable_layout()
        player.tell_cc("%s         %s\n" % (self.get_sp_str(self.left).rjust(21), self.get_sp_str(self.right)))
        for line in self.printable_layout:
            player.tell_cc(line)
        if show_metadata:
            player.tell_cc("\n" + self.get_metadata_str())

    def show_hand(self, player):

        seat = self.get_seat_of_player(player)

        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return

        print_str = "Your current hand:\n   "
        print_str += hand_to_str(seat.data.hand)
        print_str += "\n"
        self.tell_pre(player, print_str)

    def send_layout(self, show_metadata=True):

        for player in self.channel.listeners:
            self.show(player, show_metadata)
        for seat in self.seats:
            if seat.player:
                self.show_hand(seat.player)

    def deal(self):

        # Deal cards until each player has hand_size cards.
        self.bc_pre("A fresh hand is dealt to both players.\n")
        for i in range(self.hand_size):
            self.left.data.hand.add(self.draw_pile.discard())
            self.right.data.hand.add(self.draw_pile.discard())

        # Sort hands.
        self.left.data.hand = sorted_hand(self.left.data.hand)
        self.right.data.hand = sorted_hand(self.right.data.hand)

        # Clear scores.
        self.left.data.curr_score = 0
        self.right.data.curr_score = 0

    def tick(self):

        # If both seats are full and the game is active, autostart.
        if (self.state.get() == "need_players" and self.seats[0].player
           and self.seats[1].player and self.active):
            self.state.set("playing")
            self.state.set_sub("play")
            self.bc_pre("^CLeft^~: ^Y%s^~; ^MRight^~: ^Y%s^~\n" %
               (self.left.player_name, self.right.player_name))
            self.turn = self.left
            self.first_player = self.left
            self.deal()
            self.send_layout()

    def calculate_deck_size(self, suits, agrees):

        # Eventually this will depend on just what cards are in the deck,
        # but for now there are 9 point cards per suit plus the agreements.
        return (9 + agrees) * suits

    def set_suits(self, player, suit_str):

        if not suit_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_suit_count = int(suit_str)
        if new_suit_count < MIN_SUITS or new_suit_count > MAX_SUITS:
            self.tell_pre(player, "The number of suits must be between %d and %d inclusive.\n" % (MIN_SUITS, MAX_SUITS))
            return False

        # Does this give too few cards for the hand size?
        if self.calculate_deck_size(new_suit_count, self.agreement_count) <= self.hand_size * 2:
            self.tell_pre(player, "That number of suits is too small for the hand size.\n")
            return False

        # Valid.
        self.suit_count = new_suit_count
        self.bc_pre("^M%s^~ has changed the suit count to ^G%s^~.\n" % (player, new_suit_count))
        self.init_hand()
        self.update_printable_layout()

    def set_agreements(self, player, agree_str):

        if not agree_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_agree_count = int(agree_str)
        if new_agree_count < MIN_AGREEMENTS or new_agree_count > MAX_AGREEMENTS:
            self.tell_pre(player, "The number of agreements must be between %d and %d inclusive.\n" % (MIN_AGREEMENTS, MAX_AGREEMENTS))
            return False

        # Does this give too few cards for the hand size?
        if self.calculate_deck_size(self.suit_count, new_agree_count) <= self.hand_size * 2:
            self.tell_pre(player, "That number of agreements is too small for the hand size.\n")
            return False

        # Valid.
        self.agreement_count = new_agree_count
        self.bc_pre("^M%s^~ has changed the agreement count to ^G%s^~.\n" % (player, new_agree_count))
        self.init_hand()
        self.update_printable_layout()

    def set_hand(self, player, hand_str):

        if not hand_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_hand_size = int(hand_str)
        if new_hand_size < MIN_HAND_SIZE or new_hand_size > MAX_HAND_SIZE:
            self.tell_pre(player, "The hand size must be between %d and %d inclusive.\n" % (MIN_HAND_SIZE, MAX_HAND_SIZE))
            return False

        # If the drawn hands are greater than or equal to the actual card
        # count, that doesn't work either.
        if (new_hand_size * 2) >= len(self.draw_pile):
            self.tell_pre(player, "The hand size is too large for the number of cards in play.\n")
            return False

        # Valid.
        self.hand_size = new_hand_size
        self.bc_pre("^M%s^~ has changed the hand size to ^G%s^~.\n" % (player, new_hand_size))

    def set_penalty(self, player, penalty_str):

        if not penalty_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_penalty = int(penalty_str)
        if new_penalty < MIN_PENALTY or new_penalty > MAX_PENALTY:
            self.tell_pre(player, "The penalty must be between %d and %d inclusive.\n" % (MIN_PENALTY, MAX_PENALTY))
            return False

        # Valid.
        self.penalty = new_penalty
        self.bc_pre("^M%s^~ has changed the penalty to ^G%s^~.\n" % (player, new_penalty))

    def set_bonus(self, player, bonus_bits):

        if len(bonus_bits) == 1:

            bonus = bonus_bits[0]
            # Gotta be 'none' or 0.
            if bonus in ("none", "n", "0",):
                self.bonus = False
                self.bc_pre("^M%s^~ has disabled the expedition bonuses.\n" % player)
                return True
            else:
                self.tell_pre(player, "Invalid bonus command.\n")
                return False

        elif len(bonus_bits) == 2:

            points, length = bonus_bits

            if not points.isdigit() or not length.isdigit():
                self.tell_pre(player, "Invalid bonus command.\n")
                return False

            points = int(points)
            length = int(length)

            if not points or not length:
                self.bonus = False
                self.bc_pre("^M%s^~ has disabled the expedition bonuses.\n" % player)
                return True
            else:
                self.bonus = True
                self.bonus_points = points
                self.bonus_length = length
                self.bc_pre("^M%s^~ has set the expedition bonuses to ^C%s^~ at length ^R%s^~.\n" % (player, get_plural_str(points, "point"), length))
                return True

        else:
            self.tell_pre(player, "Invalid bonus command.\n")
            return False

    def set_goal(self, player, goal_str):

        if not goal_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_goal = int(goal_str)
        if new_goal < 1:
            self.tell_pre(player, "The goal must be at least one point.\n")
            return False

        # Got a valid goal.
        self.goal = new_goal
        self.bc_pre("^M%s^~ has changed the goal to ^G%s^~.\n" % (player, get_plural_str(new_goal, "point")))

    def suit_to_loc(self, suit):

        if suit in DEFAULT_SUITS:
            return DEFAULT_SUITS.index(suit)
        elif suit == CYAN:
            return 5
        elif suit == MAGENTA:
            return 6

        return None

    def evaluate(self, player):

        for seat in self.seats:
            score_str = "%s: " % seat.player_name
            score_str += " + ".join(["%s%s^~" % (get_color_code(x.suit), x.value) for x in seat.data.expeditions])
            score_str += " = %s\n" % (get_plural_str(seat.data.curr_score, "point"))
            self.tell_pre(player, score_str)

    def play(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "play":
            self.tell_pre(player, "You should be drawing or retrieving, not playing!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # All right.  Grab the hand for that expedition.
        exp_hand = seat.data.expeditions[self.suit_to_loc(potential_card.suit)].hand

        # If this card is a lower value than the top card of the hand, nope.
        if len(exp_hand) and potential_card < exp_hand[-1]:
            self.tell_pre(player, "You can no longer play this card on this expedition.\n")
            return False

        # If it's the same value and not an agreement, nope.
        elif (len(exp_hand) and potential_card == exp_hand[-1] and
           potential_card.rank != AGREEMENT):
            self.tell_pre(player, "You cannot play same-valued point cards on an expedition.\n")
            return False

        # Passed the tests.  Play it and clear the discard tracker.
        exp_hand.add(seat.data.hand.discard_specific(potential_card))
        self.just_discarded_to = None

        self.bc_pre("%s played %s.\n" % (self.get_sp_str(seat),
                       card_to_str(potential_card, mode=LONG)))
        return True

    def discard(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "play":
            self.tell_pre(player, "You should be drawing or retrieving, not discarding!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # All right, they can discard it.  Get the appropriate discard pile...
        discard_pile = self.discards[self.suit_to_loc(potential_card.suit)].hand

        discard_pile.add(seat.data.hand.discard_specific(potential_card))

        # Note the pile we just discarded to, so the player can't just pick it
        # back up as their next play.
        self.just_discarded_to = potential_card.suit

        self.bc_pre("%s discarded %s.\n" % (self.get_sp_str(seat),
                          card_to_str(potential_card, mode=LONG)))
        return True

    def draw(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "draw":
            self.tell_pre(player, "You should be playing or discarding, not drawing!\n")
            return False

        # Draw a card.  This one's easy!
        draw_card = self.draw_pile.discard()
        seat.data.hand.add(draw_card)

        # Resort the hand.
        seat.data.hand = sorted_hand(seat.data.hand)

        self.bc_pre("%s drew a card.\n" % (self.get_sp_str(seat)))
        self.tell_pre(player, "You drew %s.\n" % card_to_str(draw_card, mode=LONG))
        return True

    def retrieve(self, player, retrieve_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        substate = self.state.get_sub()
        if substate != "draw":
            self.tell_pre(player, "You should be playing or discarding, not retrieving!\n")
            return False

        # Turn the retrieve string into an actual suit.
        suit = str_to_suit(retrieve_str)
        if not suit:
            self.tell_pre(player, "That's not a valid suit!\n")
            return False

        # Is that a valid location in this game?
        loc = self.suit_to_loc(suit)
        if loc >= self.suit_count:
            self.tell_pre(player, "That suit isn't in play this game.\n")
            return False

        # Is there actually a card there /to/ draw?
        discard_pile = self.discards[loc].hand
        if not len(discard_pile):
            self.tell_pre(player, "There are no discards of that suit.\n")
            return False

        # Is it the card they just discarded?
        if suit == self.just_discarded_to:
            self.tell_pre(player, "You just discarded that card!\n")
            return False

        # Phew.  All tests passed.  Give them the card.
        dis_card = discard_pile.discard()
        seat.data.hand.add(dis_card)
        seat.data.hand = sorted_hand(seat.data.hand)

        self.bc_pre("%s retrieved %s from the discards.\n" % (self.get_sp_str(seat),
                                                  card_to_str(dis_card, mode=LONG)))
        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if self.turn != seat:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("suits",):

                    if len(command_bits) == 2:
                        self.set_suits(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid suits command.\n")
                    handled = True

                elif primary in ("agreements", "agree",):

                    if len(command_bits) == 2:
                        self.set_agreements(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid agree command.\n")
                    handled = True

                elif primary in ("hand",):

                    if len(command_bits) == 2:
                        self.set_hand(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid hand command.\n")
                    handled = True

                elif primary in ("penalty",):

                    if len(command_bits) == 2:
                        self.set_penalty(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid penalty command.\n")
                    handled = True

                elif primary in ("bonus",):

                    if len(command_bits) >= 2:
                        self.set_bonus(player, command_bits[1:])
                    else:
                        self.tell_pre(player, "Invalid bonus command.\n")
                    handled = True

                elif primary in ("goal", "score",):

                    if len(command_bits) == 2:
                        self.set_goal(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid goal command.\n")
                    handled = True

                elif primary in ("done", "ready", "d", "r",):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):

                    self.state.set("setup")
                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in ("hand", "inventory", "inv", "i",):
                    self.show_hand(player)
                    handled = True

                elif primary in ("evaluate", "eval", "score", "e", "s",):
                    self.evaluate(player)
                    handled = True

                elif primary in ("move", "play", "mv", "pl",):

                    if len(command_bits) == 2:
                        made_move = self.play(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid play command.\n")
                    handled = True

                elif primary in ("discard", "toss", "di", "to",):
                    if len(command_bits) == 2:
                        made_move = self.discard(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid discard command.\n")
                    handled = True

                elif primary in ("draw", "dr",):
                    made_move = self.draw(player)
                    handled = True

                elif primary in ("retrieve", "re",):
                    if len(command_bits) == 2:
                        made_move = self.retrieve(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid retrieve command.\n")
                    handled = True

                elif primary in ("resign",):

                    if self.resign(player):
                        made_move = True
                    handled = True

                if made_move:

                    substate = self.state.get_sub()

                    # Okay, something happened on the layout.  Update scores
                    # and the layout.
                    self.update_scores()
                    self.update_printable_layout()

                    # Is the game over?
                    if not len(self.draw_pile) or self.resigner:

                        # Yup.  Resolve the game.
                        self.resolve_hand()

                        # Is there an overall winner?
                        winner = self.find_winner()

                        if winner:
                            self.resolve(winner)
                            self.finish()

                        else:

                            # Hand over, but not the game itself.  New deal.
                            self.bc_pre("The cards are collected for another hand.\n")
                            self.init_hand()

                            # Switch dealers.
                            self.first_player = self.next_seat(self.first_player)
                            self.turn = self.first_player
                            self.state.set("playing")
                            self.state.set_sub("play")
                            self.deal()
                            self.update_printable_layout()
                            self.send_layout()

                    else:

                        # If we're in the play substate, switch to the draw.
                        if substate == "play":
                            self.state.set_sub("draw")

                        else:

                            # After draw, switch turns and resend the board.
                            self.state.set_sub("play")
                            self.turn = self.next_seat(self.turn)
                            self.send_layout(show_metadata=False)

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def update_scores(self):

        for seat in self.left, self.right:

            # Start at 0.
            total = 0

            # For each expedition, if they're even on it...
            for exp in seat.data.expeditions:

                curr = 0
                multiplier = 1

                if len(exp.hand):

                    # Immediately assign the penalty.
                    curr -= self.penalty

                    # Now loop through the cards.
                    for card in exp.hand:

                        value = card.value()
                        if value == 1:

                            # Agreement; adjust multiplier.
                            multiplier += 1

                        else:

                            # Scoring card; increase current score.
                            curr += value

                    # Adjust the current score by the multiplier.
                    curr *= multiplier

                    # If bonuses are active, and this meets it, add it.
                    if self.bonus and len(exp.hand) >= self.bonus_length:
                        curr += self.bonus_points

                # No matter what, add curr to total and set it on the
                # pile.
                total += curr
                exp.value = curr

            # Set the current score for the seat.
            seat.data.curr_score = total

    def resolve_hand(self):

        for seat in self.left, self.right:

            addend = seat.data.curr_score
            if addend > 0:
                adj_str = "^Ygains ^C%s^~" % get_plural_str(addend, "point")
            elif addend < 0:
                adj_str = "^yloses ^c%s^~" % get_plural_str(-addend, "point")
            else:
                adj_str = "^Wsomehow manages to score precisely zero points^~"

            # Actually adjust the scores by the proper amounts, and inform
            # everyone of the result.
            seat.data.overall_score += addend

            # If someone resigned, scores don't matter, so don't show them.
            if not self.resigner:
                self.bc_pre("%s %s, giving them ^G%s^~.\n" % (self.get_sp_str(seat), adj_str, seat.data.overall_score))

    def find_winner(self):

        # If someone resigned, this is the easiest thing ever.
        if self.resigner == self.left:
            return self.right
        elif self.resigner == self.right:
            return self.left

        # If one player has a higher score than the other and that score
        # is higher than the goal, they win.
        if (self.left.data.overall_score > self.right.data.overall_score and
           self.left.data.overall_score >= self.goal):
            return self.left
        elif (self.right.data.overall_score > self.left.data.overall_score and
           self.right.data.overall_score >= self.goal):
            return self.right

        # Either we haven't reached the goal or there's a tie.  We'll print a
        # special message if there's a tie, because that's kinda crazy.
        if self.left.data.overall_score == self.right.data.overall_score:
            self.bc_pre("The players are tied!\n")

        # No matter what, there's no winner.
        return None

    def resolve(self, winner):
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Expeditions, self).show_help(player)
        player.tell_cc("\nEXPEDITIONS SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("                  ^!suits^. <num>     Play with <num> suits.\n")
        player.tell_cc("                  ^!agree^. <num>     Suits have <num> agreements.\n")
        player.tell_cc("                   ^!hand^. <num>     Hands have <num> cards.\n")
        player.tell_cc("                ^!penalty^. <num>     Expeditions start down <num> points.\n")
        player.tell_cc("     ^!bonus^. <pts> <len> | none     Bonus is <pts> at length <len>/none.\n")
        player.tell_cc("            ^!goal^. <num>, ^!score^.     Play until <num> points.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nEXPEDITIONS PLAY:\n\n")
        player.tell_cc("              ^!play^. <card>, ^!pl^.     Play <card> from your hand.\n")
        player.tell_cc("         ^!discard^. <card>, ^!toss^.     Discard <card> from your hand.\n")
        player.tell_cc("                     ^!draw^., ^!dr^.     Draw from the draw pile.\n")
        player.tell_cc("          ^!retrieve^. <suit>, ^!re^.     Retrieve top discard of <suit>.\n")
        player.tell_cc("                       ^!resign^.     Resign.\n")
        player.tell_cc("                 ^!hand^., ^!inv^., ^!i^.     Look at the cards in your hand.\n")
        player.tell_cc("               ^!evaluate^., ^!eval^.     Evaluate the current scores.\n")
예제 #16
0
파일: forty_one.py 프로젝트: sunfall/giles
class FortyOne(SeatedGame):
    """An implementation of Forty-One, a quirky combination of solo and
    partnership trick-taking that is apparently popular in Syria.  The only
    real differences from the rules I've seen online are that this version
    supports a 'whist' mode rather than always having Hearts as trump and it
    forces play to continue if winning scores are tied.
    """

    def __init__(self, server, table_name):

        super(FortyOne, self).__init__(server, table_name)

        self.game_display_name = "Forty-One"
        self.game_name = "fortyone"

        # Seat ordering is "Persian."
        self.seats = [
            Seat("North"),
            Seat("West"),
            Seat("South"),
            Seat("East"),
        ]

        self.min_players = 4
        self.max_players = 4
        self.state = State("need_players")
        self.prefix = "(^RForty-One^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # 41-specific stuff.
        self.goal = 41
        self.double = 7
        self.minimum = 11
        self.whist = False
        self.positive = True
        self.trick = None
        self.trump_suit = None
        self.led_suit = None
        self.turn = None
        self.dealer = None
        self.winner = None

        self.north = self.seats[0]
        self.west = self.seats[1]
        self.south = self.seats[2]
        self.east = self.seats[3]

        self.north.data.who = NORTH
        self.west.data.who = WEST
        self.south.data.who = SOUTH
        self.east.data.who = EAST

        self.north.data.partner = self.south
        self.west.data.partner = self.east
        self.south.data.partner = self.north
        self.east.data.partner = self.west

        self.layout = FourPlayerCardGameLayout()

        # Set everyone's score to zero.
        for seat in self.seats:
            seat.data.score = 0

    def show_help(self, player):

        super(FortyOne, self).show_help(player)
        player.tell_cc("\nFORTY-ONE SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("            ^!goal^. <num>, ^!score^.     Set the goal score to <num>.\n")
        player.tell_cc("           ^!double^. <num>, ^!doub^.     Set the lowest doubling to <num>.\n")
        player.tell_cc("           ^!minimum^. <num>, ^!min^.     Set the minimum deal bid to <num>.\n")
        player.tell_cc("         ^!positive^. on|off, ^!pos^.     Require positive partners for wins.\n")
        player.tell_cc("             ^!whist^. on|off, ^!wh^.     Enable whist mode for trumps.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nFORTY-ONE PLAY:\n\n")
        player.tell_cc("            ^!choose^. <suit>, ^!ch^.     Declare <suit> as trumps.  Hakem only.\n")
        player.tell_cc("                 ^!bid^. <num>, ^!b^.     Bid to win <num> tricks.\n")
        player.tell_cc("              ^!play^. <card>, ^!pl^.     Play <card> from your hand.\n")
        player.tell_cc("                 ^!hand^., ^!inv^., ^!i^.     Look at the cards in your hand.\n")

    def display(self, player):

        player.tell_cc("%s" % self.layout)

    def get_color_code(self, seat):
        if seat == self.north or seat == self.south:
            return "^R"
        else:
            return "^M"

    def get_sp_str(self, seat):

        return "^G%s^~ (%s%s^~)" % (seat.player_name, self.get_color_code(seat), seat)

    def get_player_num_str(self, seat, num):

        return "%s%s^~: %d" % (self.get_color_code(seat), seat.player_name, num)

    def get_score_str(self):
        return_str = "       "
        for seat in self.seats:
            return_str += "   %s" % self.get_player_num_str(seat, seat.data.score)
        return_str += "\n"

        return return_str

    def get_trick_str(self):
        return_str = "^CTricks^~:"
        for seat in self.seats:
            return_str += "   %s" % self.get_player_num_str(seat, seat.data.tricks)
        return_str += "\n"

        return return_str

    def get_bid_str(self):
        return_str = "  ^GBids^~:"
        for seat in self.seats:
            return_str += "   %s" % self.get_player_num_str(seat, seat.data.bid)
        return_str += "\n"

        return return_str

    def get_metadata(self):

        to_return = "\n\n"
        if self.turn:
            seat_color = self.get_color_code(self.turn)

            to_return += "It is ^Y%s^~'s turn (%s%s^~)." % (self.turn.player_name, seat_color, self.turn)
            if self.whist:
                to_return += "  Trumps are ^C%s^~." % self.trump_suit
            to_return += "\n" + self.get_trick_str()
            to_return += self.get_bid_str()
        to_return += "\nThe goal score for this game is ^C%s^~.\n" % get_plural_str(self.goal, "point")
        to_return += self.get_score_str()
        if self.positive:
            partner_str = "^Ymust have"
        else:
            partner_str = "^ydo not need to have"
        to_return += "(Both partners %s^~ a positive score to win.)\n" % partner_str
        if self.double != 7:
            if self.double:
                to_return += "Bids double value at ^C%s^~.\n" % get_plural_str(self.double, "trick")
            else:
                to_return += "Bids ^Rnever^~ double their value.\n"
        if self.minimum != 11:
            to_return += "The minimum bid for a deal is ^G%s^~.\n" % get_plural_str(self.minimum, "point")

        return to_return

    def show(self, player):
        self.display(player)
        player.tell_cc(self.get_metadata())

    def set_goal(self, player, goal_str):

        if not goal_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_goal = int(goal_str)
        if new_goal < 1:
            self.tell_pre(player, "The goal must be at least one point.\n")
            return False

        # Got a valid goal.
        self.goal = new_goal
        self.bc_pre("^M%s^~ has changed the goal to ^G%s^~.\n" % (player, get_plural_str(new_goal, "point")))

    def set_double(self, player, double_str):

        if not double_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_double = int(double_str)

        if new_double > 13:
            self.tell_pre(player, "The doubling value must be at most 13 tricks.\n")
            return False

        # Got a valid double value.
        self.double = new_double
        if self.double:
            self.bc_pre("^M%s^~ has changed the doubling value to ^G%s^~.\n" % (player, get_plural_str(new_double, "trick")))
        else:
            self.bc_pre("^M%s^~ has ^Rdisabled^~ doubling values.\n" % (player,))

    def set_minimum(self, player, minimum_str):

        if not minimum_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_minimum = int(minimum_str)

        # It doesn't make sense for the minimum to be below 4, as that's the
        # lowest possible bid anyhow.  13 is a plausible maximum.
        if new_minimum < 4:
            self.tell_pre(player, "The minimum must be at least 4 points.\n")
            return False

        if new_minimum > 13:
            self.tell_pre(player, "The minimum must be at most 13 points.\n")
            return False

        # Got a valid minimum.
        self.minimum = new_minimum
        self.bc_pre("^M%s^~ has changed the minimum to ^G%s^~.\n" % (player, get_plural_str(new_minimum, "point")))

    def set_positive(self, player, positive_bits):

        positive_bool = booleanize(positive_bits)
        if positive_bool:
            if positive_bool > 0:
                self.positive = True
                display_str = "^Crequires^~"
            elif positive_bool < 0:
                self.positive = False
                display_str = "^cdoes not require^~"
            self.bc_pre("^R%s^~ sets it so that winning %s positive scores.\n" % (player, display_str))
        else:
            self.tell_pre(player, "Not a valid boolean!\n")

    def set_whist(self, player, whist_bits):

        whist_bool = booleanize(whist_bits)
        if whist_bool:
            if whist_bool > 0:
                self.whist = True
                display_str = "^Con^~"
            elif whist_bool < 0:
                self.whist = False
                display_str = "^coff^~"
            self.bc_pre("^R%s^~ has turned whist-style trumps %s.\n" % (player, display_str))
        else:
            self.tell_pre(player, "Not a valid boolean!\n")

    def clear_trick(self):

        # Set the current trick to an empty hand...
        self.trick = Hand()
        self.led_suit = None

        # ...and set everyone's played card to None.
        for seat in self.seats:
            seat.data.card = None

        # Clear the layout as well.
        self.layout.clear()

    def new_deal(self):

        # Set tricks and bids to zero.
        for seat in self.seats:
            seat.data.tricks = 0
            seat.data.bid = 0

        dealer_name = self.dealer.player_name

        self.bc_pre("^R%s^~ (%s%s^~) gives the cards a good shuffle...\n" % (dealer_name, self.get_color_code(self.dealer), self.dealer))
        deck = new_deck()
        deck.shuffle()

        # Deal out all of the cards.
        self.bc_pre("^R%s^~ deals the cards out to all of the players.\n" % dealer_name)
        for seat in self.seats:
            seat.data.hand = Hand()
        for _ in range(13):
            for seat in self.seats:
                seat.data.hand.add(deck.discard())

        # If we're in whist mode, flip the dealer's last card to determine
        # trumps.
        if self.whist:
            last_card = self.dealer.data.hand[-1]
            self.bc_pre("^R%s^~ flips their last card; it is ^C%s^~.\n" % (dealer_name, card_to_str(last_card, LONG)))
            self.trump_suit = last_card.suit
        else:

            # Hearts forever.
            self.trump_suit = HEARTS

        # Sort everyone's hands and show them to everyone.
        for seat in self.seats:
            seat.data.hand = sorted_hand(seat.data.hand, self.trump_suit)
        self.show_hands()

    def show_hand(self, player):

        seat = self.get_seat_of_player(player)

        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return

        print_str = "Your current hand:\n   "
        print_str += hand_to_str(seat.data.hand, self.trump_suit)
        print_str += "\n"
        self.tell_pre(player, print_str)

    def show_hands(self):

        for seat in self.seats:
            if seat.player:
                self.show_hand(seat.player)

    def bid(self, player, bid_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        if not bid_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        bid = int(bid_str)

        if bid < 1:
            self.tell_pre(player, "You must bid at least one point.\n")
            return False

        if bid > 13:
            self.tell_pre(player, "You can't bid more tricks than there are!\n")
            return False

        # Got a valid bid.
        seat.data.bid = bid
        self.bc_pre("%s has bid ^G%s^~.\n" % (self.get_sp_str(seat), get_plural_str(bid, "trick")))
        return True

    def play(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # Okay, it's a card in their hand.  First, let's do the "follow the
        # led suit" business.
        action_str = "^Wplays^~"
        if self.led_suit:

            this_suit = potential_card.suit
            if this_suit != self.led_suit and hand_has_suit(seat.data.hand, self.led_suit):

                # You can't play off-suit if you can match the led suit.
                self.tell_pre(player, "You can't throw off; you have the led suit.\n")
                return False

        else:

            # No led suit; they're the leader.
            action_str = "^Yleads^~ with"
            self.led_suit = potential_card.suit

        # They either matched the led suit, didn't have any of it, or they
        # are themselves the leader.  Nevertheless, their play is valid.
        seat.data.card = potential_card
        self.trick.add(seat.data.hand.discard_specific(potential_card))
        trump_str = ""
        if potential_card.suit == self.trump_suit:
            trump_str = ", a ^Rtrump^~"
        self.bc_pre("%s %s ^C%s^~%s.\n" % (self.get_sp_str(seat), action_str, card_to_str(potential_card, LONG), trump_str))
        self.layout.place(seat.data.who, potential_card)
        return potential_card

    def tick(self):

        # If all seats are full and active, autostart.
        active_seats = [x for x in self.seats if x.player]
        if self.state.get() == "need_players" and len(active_seats) == 4 and self.active:
            self.state.set("bidding")
            self.bc_pre("The game has begun.\n")

            # Initialize everything by clearing the (non-existent) trick.
            self.clear_trick()

            # Pick a starting dealer at random.
            self.dealer = random.choice(self.seats)
            self.bc_pre("Fate has spoken, and the starting dealer is %s!\n" % self.get_sp_str(self.dealer))
            self.new_deal()

            # Eldest opens bidding, per usual.
            self.turn = self.next_seat(self.dealer)
            self.layout.change_turn(self.turn.data.who)
            if self.turn.player:
                self.tell_pre(self.turn.player, "It is your turn to bid.\n")

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()

            command_bits = command_str.split()
            primary = command_bits[0].lower()

            if state == "setup":

                if primary in ("goal", "score", "sc", "g",):
                    if len(command_bits) == 2:
                        self.set_goal(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid goal command.\n")
                    handled = True

                elif primary in ("positive", "pos", "po", "p",):
                    if len(command_bits) == 2:
                        self.set_positive(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid positive command.\n")
                    handled = True

                elif primary in ("double", "doub",):
                    if len(command_bits) == 2:
                        self.set_double(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid double command.\n")
                    handled = True

                elif primary in ("minimum", "min",):
                    if len(command_bits) == 2:
                        self.set_minimum(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid minimum command.\n")
                    handled = True

                elif primary in ("whist", "wh",):
                    if len(command_bits) == 2:
                        self.set_whist(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid whist command.\n")
                    handled = True

                elif primary in ("done", "ready", "d", "r",):
                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):
                    self.state.set("setup")
                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    handled = True

            elif state == "bidding":

                if primary in ("hand", "inventory", "inv", "i",):
                    self.show_hand(player)
                    handled = True

                elif primary in ("bid", "b",):
                    if len(command_bits) == 2:
                        bid_made = self.bid(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid bid command.\n")
                    handled = True

                if bid_made:

                    bid_list = [x for x in self.seats if x.data.bid]
                    if len(bid_list) == 4:

                        # Bidding is complete.  Are enough tricks bid?
                        bid_total = 0
                        point_total = 0
                        for seat in self.seats:
                            bid = seat.data.bid
                            bid_total += bid
                            point_total += bid

                            # Bids of self.double or more count double, if set.
                            if self.double:
                                if bid >= self.double:
                                    point_total += bid

                        bid_str = get_plural_str(bid_total, "trick")
                        point_str = get_plural_str(point_total, "point")
                        if point_total >= self.minimum:

                            # Enough indeed.  Start the game proper.
                            self.bc_pre("With ^W%s^~ bid for a total of ^C%s^~, play begins!\n" % (bid_str, point_str))
                            self.state.set("playing")
                            self.turn = self.next_seat(self.turn)
                            if self.turn.player:
                                self.show_hand(self.turn.player)

                        else:

                            # Not enough.  Throw hands in and deal fresh.
                            self.bc_pre("With only ^R%s^~ bid, everyone throws in their hand.\n" % bid_str)
                            self.dealer = self.next_seat(self.dealer)

                            # Deal and set up the first player to bid.
                            self.new_deal()
                            self.turn = self.next_seat(self.dealer)
                            if self.turn.player:
                                self.tell_pre(self.turn.player, "It is your turn to bid.\n")

                    else:

                        # Still need more bids.
                        self.turn = self.next_seat(self.turn)
                        if self.turn.player:
                            self.tell_pre(self.turn.player, "It is your turn to bid.\n")
                            self.show_hand(self.turn.player)

                    # No matter what happened, update the layout.
                    self.layout.change_turn(self.turn.data.who)

            elif state == "playing":

                card_played = False
                if primary in ("hand", "inventory", "inv", "i",):
                    self.show_hand(player)
                    handled = True

                elif primary in ("play", "move", "pl", "mv",):
                    if len(command_bits) == 2:
                        card_played = self.play(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid play command.\n")
                    handled = True

                if card_played:

                    # A card hit the table.  We need to do stuff.
                    if len(self.trick) == 4:

                        # Finish the trick up.
                        self.finish_trick()

                        # Is that the last trick?
                        if self.north.data.tricks + self.west.data.tricks + self.south.data.tricks + self.east.data.tricks == 13:

                            # Resolve the hand...
                            self.resolve_hand()

                            # And look for a winner.
                            winner = self.find_winner()
                            if winner:

                                # Found a winner.  Finish.
                                self.resolve(winner)
                                self.finish()

                            else:

                                # No winner.  Pass the deal to the next player...
                                self.dealer = self.next_seat(self.dealer)

                                # Deal and set up the first player to bid.
                                self.new_deal()
                                self.turn = self.next_seat(self.dealer)
                                self.layout.change_turn(self.turn.data.who)
                                self.state.set("bidding")
                                if self.turn.player:
                                    self.tell_pre(self.turn.player, "It is your turn to bid.\n")

                    else:

                        # Trick not over.  Rotate.
                        self.turn = self.next_seat(self.turn)
                        self.layout.change_turn(self.turn.data.who)
                        if self.turn.player:
                            self.show_hand(self.turn.player)

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def finish_trick(self):

        # Okay, we have a trick with four cards.  Which card won?
        winner = handle_trick(self.trick, self.trump_suit)

        # This /should/ just return one seat...
        winning_seat_list = [x for x in self.seats if x.data.card == winner]

        if len(winning_seat_list) != 1:
            self.server.log.log(self.log_prefix + "Something went horribly awry; trick ended without a finish.")
            self.bc_pre("Something went horribly wrong; no one won the trick!  Tell the admin.\n")
            return

        winning_seat = winning_seat_list[0]

        # Print information about the winning card.
        self.bc_pre("%s wins the trick with ^C%s^~.\n" % (self.get_sp_str(winning_seat), card_to_str(winner, LONG)))

        # Give the trick to the correct seat.
        winning_seat.data.tricks += 1

        # Clear the trick.
        self.clear_trick()

        # Set the next leader to the player who won.
        self.turn = winning_seat
        self.layout.change_turn(self.turn.data.who)
        if self.turn.player:
            self.show_hand(self.turn.player)

    def resolve_hand(self):

        for seat in self.seats:

            # Determine the score delta; if the bid is < self.double (or
            # doubling is disabled) it's just the bid, otherwise it's double.
            bid = seat.data.bid
            if bid < self.double or not self.double:
                score_delta = bid
            else:
                score_delta = bid * 2
            score_delta_str = get_plural_str(score_delta, "point")

            result_str = "%s bid ^G%s^~ and " % (self.get_sp_str(seat), get_plural_str(bid, "trick"))

            if bid <= seat.data.tricks:

                # Woot, gain points!
                seat.data.score += score_delta
                result_str += "took ^W%d^~, gaining ^G%s^~.\n" % (seat.data.tricks, score_delta_str)

            else:

                # Aw, lost points.
                seat.data.score -= score_delta
                result_str += "only took ^W%d^~, losing ^R%s^~.\n" % (seat.data.tricks, score_delta_str)

            self.bc_pre(result_str)

        self.bc_pre(self.get_score_str())

    def find_winner(self):

        # The rules don't define what happens if both sides win in the same
        # deal.  I've arbitrarily decided that highest score wins, and if
        # both sides have equivalent high scores, there isn't a winner yet.
        high_count = -1
        high_list = None
        for seat in self.seats:
            if seat.data.score >= self.goal:
                if seat.data.score > high_count:
                    high_count = seat.data.score
                    high_list = [seat]
                elif seat.data.score == high_count:
                    high_list.append(seat)

        # If we're in positive mode, strip entries from the list if their
        # partners don't have a positive score.
        if self.positive:
            high_list = [x for x in high_list if x.data.partner.data.score > 0]

        # If the list is empty, we can flat-out bail.
        if not high_list:
            return None

        # If the list has multiple entries, and they're from different teams,
        # we also don't have a winner.
        if (self.north in high_list or self.south in high_list) and (self.west in high_list or self.east in high_list):

            # This is at least worth reporting.
            self.bc_pre("Both sides are ^Ctied for the win^~!  The game continues...\n")
            return None

        # It's fine if both members of a team won at the same time, but we
        # don't care which, since a partnership wins.
        return high_list[0]

    def resolve(self, winning_seat):

        if self.north == winning_seat or self.south == winning_seat:
            name_one = self.north.player_name
            name_two = self.south.player_name
        else:
            name_one = self.west.player_name
            name_two = self.east.player_name

        self.bc_pre("^G%s^~ and ^G%s^~ win!\n" % (name_one, name_two))
예제 #17
0
def sorted_hand(hand, trump_suit=None):
    """Sorts a hand of cards.  Puts trumps first, if any, then sorts the
    remaining suits arbitrarily.  Returns this newly-sorted hand.
    """

    trump_cards = Hand()
    other_suits = {}
    for card in hand:
        this_suit = card.suit
        if card.suit == trump_suit:
            hand_to_add_to = trump_cards
        else:
            if this_suit not in other_suits:
                other_suits[this_suit] = Hand()
            hand_to_add_to = other_suits[this_suit]
        hand_to_add_to.add(card)

    # Now that they're sorted out, combine them back together.
    s_hand = Hand()
    trump_cards.sort()
    for card in trump_cards:
        s_hand.add(card)
    for suit in sorted(other_suits.keys()):
        suit_hand = other_suits[suit]
        suit_hand.sort()
        for card in suit_hand:
            s_hand.add(card)

    return s_hand
예제 #18
0
파일: hokm.py 프로젝트: Cloudxtreme/giles
class Hokm(SeatedGame):
    """A Hokm game table implementation.  Hokm is a Persian trick-taking
    card game of unknown provenance.  This implementation doesn't
    currently rearrange the seats at the start, but does support both the
    standard 4p partnership game and the quirky 3p mode.  In addition, 3p
    mode can use both a short deck (13 cards per hand) or a long deck (17).
    """

    def __init__(self, server, table_name):

        super(Hokm, self).__init__(server, table_name)

        self.game_display_name = "Hokm"
        self.game_name = "hokm"

        self.state = State("need_players")
        self.prefix = "(^RHokm^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Hokm-specific stuff.
        self.goal = 7
        self.trick = None
        self.trump_suit = None
        self.led_suit = None
        self.turn = None
        self.dealer = None
        self.hakem = None
        self.winner = None

        # Default to four-player mode.
        self.mode = 4
        self.short = True
        self.setup_mode()

    def setup_mode(self):

        # Sets up all of the structures that depend on the mode of Hokm
        # we're playing: seats, layouts, and (in 4p mode) partnerships.
        # Remember that seats in Hokm go the opposite direction of the
        # American/European standard.

        if self.mode == 4:

            self.seats = [
                Seat("North"),
                Seat("West"),
                Seat("South"),
                Seat("East"),
            ]

            self.seats[0].data.who = NORTH
            self.seats[1].data.who = WEST
            self.seats[2].data.who = SOUTH
            self.seats[3].data.who = EAST

            self.min_players = 4
            self.max_players = 4
            self.layout = FourPlayerCardGameLayout()

            # Set up the partnership structures.
            self.ns = Struct()
            self.ew = Struct()
            self.ns.score = 0
            self.ew.score = 0

        elif self.mode == 3:

            self.seats = [
                Seat("West"),
                Seat("South"),
                Seat("East"),
            ]

            self.seats[0].data.who = WEST
            self.seats[1].data.who = SOUTH
            self.seats[2].data.who = EAST

            self.west = self.seats[0]
            self.south = self.seats[1]
            self.east = self.seats[2]
            self.west.data.score = 0
            self.south.data.score = 0
            self.east.data.score = 0

            self.min_players = 3
            self.max_players = 3
            self.layout = ThreePlayerCardGameLayout()

        else:
            self.log_pre("MAJOR ERROR: Hokm initialization with invalid mode %s!" % self.mode)

    def show_help(self, player):

        super(Hokm, self).show_help(player)
        player.tell_cc("\nHOKM SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("            ^!goal^. <num>, ^!score^.     Set the goal score to <num>.\n")
        player.tell_cc("              ^!players^. 3|4, ^!pl^.     Set the number of players.\n")
        player.tell_cc("             ^!short^. on|off, ^!sh^.     Use a short deck (3p only).\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nHOKM PLAY:\n\n")
        player.tell_cc("            ^!choose^. <suit>, ^!ch^.     Declare <suit> as trumps.  Hakem only.\n")
        player.tell_cc("              ^!play^. <card>, ^!pl^.     Play <card> from your hand.\n")
        player.tell_cc("                 ^!hand^., ^!inv^., ^!i^.     Look at the cards in your hand.\n")

    def display(self, player):

        player.tell_cc("%s" % self.layout)

    def get_color_code(self, seat):
        if self.mode == 4:
            if seat == self.seats[0] or seat == self.seats[2]:
                return "^R"
            else:
                return "^M"
        else:
            if seat == self.west:
                return "^M"
            elif seat == self.south:
                return "^R"
            else:
                return "^B"

    def get_sp_str(self, seat):

        return "^G%s^~ (%s%s^~)" % (seat.player_name, self.get_color_code(seat), seat)

    def get_score_str(self):
        if self.mode == 4:
            return "          ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (self.ns.score, self.ew.score)
        else:
            return "          ^M%s^~: %d    ^R%s^~: %d    ^B%s^~: %d\n" % (self.west.player_name, self.west.data.score, self.south.player_name, self.south.data.score, self.east.player_name, self.east.data.score)

    def get_metadata(self):

        to_return = "\n\n"
        if self.turn:
            seat_color = self.get_color_code(self.turn)
            to_return += "%s is the hakem.\n" % (self.get_sp_str(self.hakem))
            if self.trump_suit:
                trump_str = "^C%s^~" % self.trump_suit
            else:
                trump_str = "^cwaiting to be chosen^~"

            to_return += "It is ^Y%s^~'s turn (%s%s^~).  Trumps are ^C%s^~.\n" % (self.turn.player_name, seat_color, self.turn, trump_str)
            if self.mode == 4:
                to_return += "Tricks:   ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (self.ns.tricks, self.ew.tricks)
            else:
                to_return += "Tricks:   ^M%s^~: %d    ^R%s^~: %d    ^B%s^~: %d\n" % (self.west.player_name, self.west.data.tricks, self.south.player_name, self.south.data.tricks, self.east.player_name, self.east.data.tricks)
        to_return += "The goal score for this game is ^C%s^~.\n" % get_plural_str(self.goal, "point")
        to_return += self.get_score_str()

        return to_return

    def show(self, player):
        self.display(player)
        player.tell_cc(self.get_metadata())

    def set_goal(self, player, goal_str):

        if not goal_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_goal = int(goal_str)
        if new_goal < 1:
            self.tell_pre(player, "The goal must be at least one point.\n")
            return False

        # Got a valid goal.
        self.goal = new_goal
        self.bc_pre("^M%s^~ has changed the goal to ^G%s^~.\n" % (player, get_plural_str(new_goal, "point")))

    def set_short(self, player, short_bits):

        if self.mode != 3:
            self.tell_pre(player, "Cannot set short mode when not in 3-player mode.\n")
            return False

        short_bool = booleanize(short_bits)
        if short_bool:
            if short_bool > 0:
                self.short = True
                display_str = "^Con^~"
            elif short_bool < 0:
                self.short = False
                display_str = "^coff^~"
            self.bc_pre("^R%s^~ has turned short suits %s.\n" % (player, display_str))
        else:
            self.tell_pre(player, "Not a valid boolean!\n")

    def set_players(self, player, player_str):

        if not player_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_mode = int(player_str)

        if new_mode == self.mode:
            self.tell_pre(player, "That is the current player count.\n")
            return False

        elif new_mode != 3 and new_mode != 4:
            self.tell_pre(player, "Only 3-player and 4-player Hokm is supported.\n")
            return False

        # Got a valid mode.
        self.mode = new_mode
        self.bc_pre("^M%s^~ has changed the number of players to ^G%s^~.\n" % (player, new_mode))
        self.setup_mode()

    def clear_trick(self):

        # Set the current trick to an empty hand...
        self.trick = Hand()
        self.led_suit = None

        # ...and set everyone's played card to None.
        for seat in self.seats:
            seat.data.card = None

        # Clear the layout as well.
        self.layout.clear()

    def new_deck(self):

        # In 4-player mode, it's a standard 52-card pack.
        if self.mode == 4:
            self.deck = new_deck()
        else:

            # If it's a short deck, 7-A are full; if a long deck, 3-A are.
            full_ranks = [ACE, KING, QUEEN, JACK, '10', '9', '8', '7', '6']
            if self.short:
                short_rank = '5'
            else:
                full_ranks.extend(['5', '4', '3'])
                short_rank = '2'

            # Build the deck, full ranks first.
            self.deck = Hand()
            for suit in (CLUBS, DIAMONDS, HEARTS, SPADES):
                for rank in full_ranks:
                    self.deck.add(PlayingCard(rank, suit))

            # We only want three of the short rank.  No hearts, because.
            for suit in (CLUBS, DIAMONDS, SPADES):
                self.deck.add(PlayingCard(short_rank, suit))

    def start_deal(self):

        # Set the trick counts to zero, appropriately for the mode.
        if self.mode == 4:
            self.ns.tricks = 0
            self.ew.tricks = 0
        else:
            self.west.data.tricks = 0
            self.south.data.tricks = 0
            self.east.data.tricks = 0

        dealer_name = self.dealer.player_name

        self.bc_pre("^R%s^~ (%s%s^~) gives the cards a good shuffle...\n" % (dealer_name, self.get_color_code(self.dealer), self.dealer))
        self.new_deck()
        self.deck.shuffle()

        # Deal out five cards each.
        self.bc_pre("^R%s^~ deals five cards out to each of the players.\n" % dealer_name)
        for seat in self.seats:
            seat.data.hand = Hand()
        for i in range(5):
            for seat in self.seats:
                seat.data.hand.add(self.deck.discard())

        # Clear the internal metadata about trumps.
        self.trump_suit = None

        # Sort the hakem's hand.
        self.hakem.data.hand = sorted_hand(self.hakem.data.hand)

        # Show the hakem their hand.
        if self.hakem.player:
            self.tell_pre(self.hakem.player, "Please choose a trump suit for this hand.\n")
            self.show_hand(self.hakem.player)

        # The hakem both chooses and, eventually, leads.
        self.turn = self.hakem
        self.layout.change_turn(self.hakem.data.who)

        # Shift into "choosing" mode.
        self.state.set("choosing")

    def finish_deal(self):

        self.bc_pre("^R%s^~ finishes dealing the cards out.\n" % self.dealer.player_name)
        while len(self.deck):
            for seat in self.seats:
                seat.data.hand.add(self.deck.discard())

        # Sort everyone's hands now that we have a trump suit.
        for seat in self.seats:
            seat.data.hand = sorted_hand(seat.data.hand, self.trump_suit)

        # Show everyone their completed hands.
        self.show_hands()

        # We're playing now.
        self.state.set("playing")

    def show_hand(self, player):

        seat = self.get_seat_of_player(player)

        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return

        print_str = "Your current hand:\n   "
        print_str += hand_to_str(seat.data.hand, self.trump_suit)
        print_str += "\n"
        self.tell_pre(player, print_str)

    def show_hands(self):

        for seat in self.seats:
            if seat.player:
                self.show_hand(seat.player)

    def play(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # Okay, it's a card in their hand.  First, let's do the "follow the
        # led suit" business.
        action_str = "^Wplays^~"
        if self.led_suit:

            this_suit = potential_card.suit
            if (this_suit != self.led_suit and
               hand_has_suit(seat.data.hand, self.led_suit)):

                # You can't play off-suit if you can match the led suit.
                self.tell_pre(player, "You can't throw off; you have the led suit.\n")
                return False

        else:

            # No led suit; they're the leader.
            action_str = "^Yleads^~ with"
            self.led_suit = potential_card.suit

        # They either matched the led suit, didn't have any of it, or they
        # are themselves the leader.  Nevertheless, their play is valid.
        seat.data.card = potential_card
        self.trick.add(seat.data.hand.discard_specific(potential_card))
        trump_str = ""
        if potential_card.suit == self.trump_suit:
            trump_str = ", a ^Rtrump^~"
        self.bc_pre("%s %s ^C%s^~%s.\n" % (self.get_sp_str(seat), action_str, card_to_str(potential_card, LONG), trump_str))
        self.layout.place(seat.data.who, potential_card)
        return potential_card

    def tick(self):

        # If all seats are full and active, autostart.
        active_seats = [x for x in self.seats if x.player]
        if (self.state.get() == "need_players" and
           len(active_seats) == self.mode and self.active):
            self.state.set("playing")
            self.bc_pre("The game has begun.\n")

            # Initialize everything by clearing the (non-existent) trick.
            self.clear_trick()

            # Pick a hakem at random.
            self.hakem = random.choice(self.seats)
            self.bc_pre("Fate has spoken, and the starting hakem is %s!\n" % self.get_sp_str(self.hakem))

            # The dealer is always the player before the hakem.
            self.dealer = self.prev_seat(self.hakem)
            self.start_deal()

    def choose(self, player, choose_str):

        choose_str = choose_str.lower()

        if choose_str in ("clubs", "c",):
            self.trump_suit = CLUBS
        elif choose_str in ("diamonds", "d",):
            self.trump_suit = DIAMONDS
        elif choose_str in ("hearts", "h",):
            self.trump_suit = HEARTS
        elif choose_str in ("spades", "s",):
            self.trump_suit = SPADES
        else:
            self.tell_pre(player, "That's not a valid suit!\n")
            return

        # Success.  Declare it and finish the deal.
        self.bc_pre("^Y%s^~ has picked ^R%s^~ as trumps.\n" % (player, self.trump_suit))
        self.finish_deal()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()

            command_bits = command_str.split()
            primary = command_bits[0].lower()

            if state == "setup":

                if primary in ("goal", "score", "sc", "g",):
                    if len(command_bits) == 2:
                        self.set_goal(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid goal command.\n")
                    handled = True

                elif primary in ("players", "pl",):
                    if len(command_bits) == 2:
                        self.set_players(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid players command.\n")
                    handled = True

                elif primary in ("short", "sh",):
                    if len(command_bits) == 2:
                        self.set_short(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid short command.\n")
                    handled = True

                elif primary in ("done", "ready", "d", "r",):
                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):
                    self.state.set("setup")
                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    handled = True

            elif state == "choosing":

                if primary in ("hand", "inventory", "inv", "i",):
                    if player == self.hakem.player:
                        self.show_hand(player)
                    else:
                        self.tell_pre(player, "You can't look at your cards yet!\n")
                    handled = True

                elif primary in ("choose", "trump", "ch", "tr",):
                    if player == self.hakem.player:
                        if len(command_bits) == 2:
                            self.choose(player, command_bits[1])
                        else:
                            self.tell_pre(player, "Invalid choose command.\n")
                    else:
                        self.tell_pre(player, "You're not hakem!\n")
                    handled = True

            elif state == "playing":

                card_played = False
                if primary in ("hand", "inventory", "inv", "i",):
                    self.show_hand(player)
                    handled = True

                elif primary in ("play", "move", "pl", "mv",):
                    if len(command_bits) == 2:
                        card_played = self.play(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid play command.\n")
                    handled = True

                if card_played:

                    # A card hit the table.  We need to do stuff.
                    if len(self.trick) == self.mode:

                        # Finish the trick up.
                        self.finish_trick()

                        # Did that end the hand?
                        winner = self.find_hand_winner()

                        if winner:

                            # Yup.  Resolve the hand...
                            self.resolve_hand(winner)

                            # And look for a winner.
                            winner = self.find_winner()
                            if winner:

                                # Found a winner.  Finish.
                                self.resolve(winner)
                                self.finish()

                            else:

                                # No winner.  Redeal.
                                self.start_deal()

                    else:

                        # Trick not over.  Rotate.
                        self.turn = self.next_seat(self.turn)
                        self.layout.change_turn(self.turn.data.who)
                        if self.turn.player:
                            self.show_hand(self.turn.player)

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def finish_trick(self):

        # Okay, we have a trick with four cards.  Which card won?
        winner = handle_trick(self.trick, self.trump_suit)

        # This /should/ just return one seat...
        winning_seat_list = [x for x in self.seats if x.data.card == winner]

        if len(winning_seat_list) != 1:
            self.server.log.log(self.log_prefix + "Something went horribly awry; trick ended without a finish.")
            self.bc_pre("Something went horribly wrong; no one won the trick!  Tell the admin.\n")
            return

        winning_seat = winning_seat_list[0]

        # Print information about the winning card.
        self.bc_pre("%s wins the trick with ^C%s^~.\n" % (self.get_sp_str(winning_seat), card_to_str(winner, LONG)))

        # If there are four players, give the trick to the correct partnership.
        if self.mode == 4:
            if winning_seat == self.seats[0] or winning_seat == self.seats[2]:
                self.ns.tricks += 1
            else:
                self.ew.tricks += 1
        else:
            winning_seat.data.tricks += 1

        # Clear the trick.
        self.clear_trick()

        # Set the next leader to the player who won.
        self.turn = winning_seat
        self.layout.change_turn(self.turn.data.who)
        if self.turn.player:
            self.show_hand(self.turn.player)

    def find_hand_winner(self):

        # In four-player mode, this is actually really simple; winning only
        # occurs when one side has more than 6 tricks.
        if self.mode == 4:
            if self.ns.tricks > 6:
                return self.ns
            elif self.ew.tricks > 6:
                return self.ew
        else:

            # In three-player mode, this is considerably less simple.  If
            # one player has more tricks than either other player can possibly
            # get, they win...
            tricks_remaining = len(self.west.data.hand)
            for seat in self.seats:
                our_tricks = seat.data.tricks
                prev_tricks = self.prev_seat(seat).data.tricks
                next_tricks = self.next_seat(seat).data.tricks
                if ((our_tricks > prev_tricks + tricks_remaining) and
                   (our_tricks > next_tricks + tricks_remaining)):
                    return seat

                # ...orrr if there are no tricks left and the other two players
                # tied for the number of tricks, we win as well.  3p Hokm, you
                # so crazy.
                if (not tricks_remaining) and prev_tricks == next_tricks:
                    return seat

                # There's also the case where one player gets the first seven;
                # this is handled already for the short deck by the first check
                # above, but has to have a specific check for the long-deck
                # game.
                if our_tricks == 7 and not prev_tricks and not next_tricks:
                    return seat

        # No winner yet.
        return None

    def resolve_hand(self, winner):

        # Assume the hakem won and there was no sweep; we'll adjust later.
        hakem_won = True
        swept = False

        # 4p mode shenanigans first.
        if self.mode == 4:

            if winner == self.ns:
                winning_str = "^RNorth/South^~"
                if self.hakem != self.seats[0] and self.hakem != self.seats[2]:
                    hakem_won = False
                loser = self.ew
            else:
                winning_str = "^MEast/West^~"
                if self.hakem != self.seats[1] and self.hakem != self.seats[3]:
                    hakem_won = False
                loser = self.ns

            # Did the loser get no tricks?  If so, the winner swept!
            if loser.tricks == 0:
                swept = True

        else:

            # 3P mode.  Check whether the hakem really won...
            if winner != self.hakem:
                hakem_won = False

            # ...and whether the winner swept.
            prev_tricks = self.prev_seat(winner).data.tricks
            next_tricks = self.next_seat(winner).data.tricks
            if not prev_tricks and not next_tricks:
                swept = True

            winning_str = self.get_sp_str(winner)

        if swept:
            action_str = "^Yswept^~"

            # 2 points if the hakem won, 3 if others did.
            if hakem_won:
                addend = 2
            else:
                addend = 3
        else:

            # Standard win.  One point.
            action_str = "^Wwon^~"
            addend = 1

        # Let everyone know.
        self.bc_pre("%s %s the hand and gains ^C%s^~.\n" % (winning_str, action_str, get_plural_str(addend, "point")))

        # Apply the score.
        if self.mode == 4:
            winner.score += addend
        else:
            winner.data.score += addend

        # Show everyone's scores.
        self.bc_pre(self.get_score_str())

        # Did the hakem not win?  If so, we need to have a new hakem and dealer.
        if not hakem_won:

            # In 4p mode, it just rotates...
            if self.mode == 4:
                self.dealer = self.hakem
                self.hakem = self.next_seat(self.hakem)
            else:

                # In 3p mode, the winner becomes hakem.
                self.hakem = winner
                self.dealer = self.prev_seat(self.hakem)

            self.bc_pre("The ^Yhakem^~ has been unseated!  The new hakem is %s.\n" % self.get_sp_str(self.hakem))
        else:
            self.bc_pre("%s remains the hakem.\n" % self.get_sp_str(self.hakem))

    def find_winner(self):

        if self.mode == 4:

            # Easy: has one of the sides reached a winning score?
            if self.ns.score >= self.goal:
                return self.ns
            elif self.ew.score >= self.goal:
                return self.ew

        else:

            # Have any of the players reached a winning score?
            for seat in self.seats:
                if seat.data.score >= self.goal:
                    return seat

        return None

    def resolve(self, winner):

        if self.mode == 4:
            if self.ns == winner:
                name_one = self.seats[0].player_name
                name_two = self.seats[2].player_name
            else:
                name_one = self.seats[1].player_name
                name_two = self.seats[3].player_name
            self.bc_pre("^G%s^~ and ^G%s^~ win!\n" % (name_one, name_two))
        else:
            self.bc_pre("^G%s^~ wins!\n" % winner.player_name)
예제 #19
0
def new_deck(ace_high=True):
    deck = Hand()
    for r in RANKS:
        for s in SUITS:
            deck.add(PlayingCard(r, s, ace_high))
    return deck
예제 #20
0
파일: whist.py 프로젝트: Cloudxtreme/giles
class Whist(SeatedGame):
    """A Whist game table implementation.  Whist came about sometime in the
    18th century.  This implementation does not (currently) score honours,
    because honours are boring.
    """
    def __init__(self, server, table_name):

        super(Whist, self).__init__(server, table_name)

        self.game_display_name = "Whist"
        self.game_name = "whist"
        self.seats = [
            Seat("North"),
            Seat("East"),
            Seat("South"),
            Seat("West"),
        ]

        self.min_players = 4
        self.max_players = 4
        self.state = State("need_players")
        self.prefix = "(^RWhist^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name,
                                       self.game_display_name)

        # Whist-specific guff.
        self.ns = Struct()
        self.ns.score = 0
        self.ew = Struct()
        self.ew.score = 0
        self.seats[0].data.who = NORTH
        self.seats[1].data.who = EAST
        self.seats[2].data.who = SOUTH
        self.seats[3].data.who = WEST

        self.goal = 5
        self.trick = None
        self.trump_suit = None
        self.led_suit = None
        self.turn = None
        self.dealer = None
        self.winner = None

        self.layout = FourPlayerCardGameLayout()

    def show_help(self, player):

        super(Whist, self).show_help(player)
        player.tell_cc("\nWHIST SETUP PHASE:\n\n")
        player.tell_cc(
            "          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n"
        )
        player.tell_cc(
            "            ^!goal^. <num>, ^!score^.     Set the goal score to <num>.\n"
        )
        player.tell_cc(
            "            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n"
        )
        player.tell_cc("\nWHIST PLAY:\n\n")
        player.tell_cc(
            "              ^!play^. <card>, ^!pl^.     Play <card> from your hand.\n"
        )
        player.tell_cc(
            "                 ^!hand^., ^!inv^., ^!i^.     Look at the cards in your hand.\n"
        )

    def display(self, player):

        player.tell_cc("%s" % self.layout)

    def get_score_str(self):
        return "          ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (
            self.ns.score, self.ew.score)

    def get_color_code(self, seat):

        if seat == self.seats[0] or seat == self.seats[2]:
            return "^R"
        else:
            return "^M"

    def get_sp_str(self, seat):

        return "^G%s^~ (%s%s^~)" % (seat.player_name,
                                    self.get_color_code(seat), seat)

    def get_metadata(self):

        to_return = "\n\n"
        if self.turn:
            to_return += "It is ^Y%s^~'s turn (%s%s^~).  Trumps are ^C%s^~.\n" % (
                self.turn.player_name, self.get_color_code(
                    self.turn), self.turn, self.trump_suit)
            to_return += "Tricks:   ^RNorth/South^~: %d    ^MEast/West^~: %d\n" % (
                self.ns.tricks, self.ew.tricks)
        to_return += "The goal score for this game is ^C%s^~.\n" % get_plural_str(
            self.goal, "point")
        to_return += self.get_score_str()

        return to_return

    def show(self, player):
        self.display(player)
        player.tell_cc(self.get_metadata())

    def set_goal(self, player, goal_str):

        if not goal_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        new_goal = int(goal_str)
        if new_goal < 1:
            self.tell_pre(player, "The goal must be at least one point.\n")
            return False

        # Got a valid goal.
        self.goal = new_goal
        self.bc_pre("^M%s^~ has changed the goal to ^G%s^~.\n" %
                    (player, get_plural_str(new_goal, "point")))

    def clear_trick(self):

        # Set the current trick to an empty hand...
        self.trick = Hand()
        self.led_suit = None

        # ...and set everyone's played card to None.
        for seat in self.seats:
            seat.data.card = None

        # Clear the layout as well.
        self.layout.clear()

    def new_deal(self):

        dealer_name = self.dealer.player_name

        self.bc_pre(
            "^R%s^~ (%s%s^~) gives the cards a good shuffle...\n" %
            (dealer_name, self.get_color_code(self.dealer), self.dealer))
        deck = new_deck()
        deck.shuffle()

        # Deal out all of the cards.  We'll flip the last one; that determines
        # the trump suit for the hand.
        self.bc_pre("^R%s^~ deals the cards out to all the players.\n" %
                    dealer_name)
        for seat in self.seats:
            seat.data.hand = Hand()
        for i in range(13):
            for seat in self.seats:
                seat.data.hand.add(deck.discard())

        # Flip the dealer's last card; it determines the trump suit.
        last_card = self.dealer.data.hand[-1]
        self.bc_pre("^R%s^~ flips their last card; it is ^C%s^~.\n" %
                    (dealer_name, card_to_str(last_card, LONG)))
        self.trump_suit = last_card.suit

        # Sort everyone's hands.
        for seat in self.seats:
            seat.data.hand = sorted_hand(seat.data.hand, self.trump_suit)

        # Show everyone their hands.
        self.show_hands()

        # Set the trick counts to zero.
        self.ns.tricks = 0
        self.ew.tricks = 0

    def show_hand(self, player):

        seat = self.get_seat_of_player(player)

        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return

        print_str = "Your current hand:\n   "
        print_str += hand_to_str(seat.data.hand, self.trump_suit)
        print_str += "\n"
        self.tell_pre(player, print_str)

    def show_hands(self):

        for seat in self.seats:
            if seat.player:
                self.show_hand(seat.player)

    def play(self, player, play_str):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You're not playing!\n")
            return False

        elif seat != self.turn:
            self.tell_pre(player, "It's not your turn!\n")
            return False

        # Translate the play string into an actual card.
        potential_card = str_to_card(play_str)

        if not potential_card:
            self.tell_pre(player, "That's not a valid card!\n")
            return False

        # Do they even have this card?
        if potential_card not in seat.data.hand:
            self.tell_pre(player, "You don't have that card!\n")
            return False

        # Okay, it's a card in their hand.  First, let's do the "follow the
        # led suit" business.
        action_str = "^Wplays^~"
        if self.led_suit:

            this_suit = potential_card.suit
            if (this_suit != self.led_suit
                    and hand_has_suit(seat.data.hand, self.led_suit)):

                # You can't play off-suit if you can match the led suit.
                self.tell_pre(player,
                              "You can't throw off; you have the led suit.\n")
                return False

        else:

            # No led suit; they're the leader.
            action_str = "^Yleads^~ with"
            self.led_suit = potential_card.suit

        # They either matched the led suit, didn't have any of it, or they
        # are themselves the leader.  Nevertheless, their play is valid.
        seat.data.card = potential_card
        self.trick.add(seat.data.hand.discard_specific(potential_card))
        trump_str = ""
        if potential_card.suit == self.trump_suit:
            trump_str = ", a ^Rtrump^~"
        self.bc_pre("%s %s ^C%s^~%s.\n" %
                    (self.get_sp_str(seat), action_str,
                     card_to_str(potential_card, LONG), trump_str))
        self.layout.place(seat.data.who, potential_card)
        return potential_card

    def tick(self):

        # If all seats are full and active, autostart.
        if (self.state.get() == "need_players" and self.seats[0].player
                and self.seats[1].player and self.seats[2].player
                and self.seats[3].player and self.active):
            self.state.set("playing")
            self.bc_pre("The game has begun.\n")

            # Initialize everything by clearing the (non-existent) trick.
            self.clear_trick()

            # Make a new deal.
            self.dealer = self.seats[0]
            self.new_deal()

            # Eldest leads to the first trick.
            self.turn = self.next_seat(self.dealer)
            self.layout.change_turn(self.turn.data.who)

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()

            command_bits = command_str.split()
            primary = command_bits[0].lower()

            if state == "setup":

                if primary in (
                        "goal",
                        "score",
                        "sc",
                        "g",
                ):
                    if len(command_bits) == 2:
                        self.set_goal(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid goal command.\n")
                    handled = True

                elif primary in (
                        "done",
                        "ready",
                        "d",
                        "r",
                ):
                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in (
                        "config",
                        "setup",
                        "conf",
                ):
                    self.state.set("setup")
                    self.bc_pre(
                        "^R%s^~ has switched the game to setup mode.\n" %
                        player)
                    handled = True

            elif state == "playing":

                card_played = False
                if primary in (
                        "hand",
                        "inventory",
                        "inv",
                        "i",
                ):
                    self.show_hand(player)
                    handled = True

                elif primary in (
                        "play",
                        "move",
                        "pl",
                        "mv",
                ):
                    if len(command_bits) == 2:
                        card_played = self.play(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid play command.\n")
                    handled = True

                if card_played:

                    # A card hit the table.  We need to do stuff.
                    if len(self.trick) == 4:

                        # Finish the trick up.
                        self.finish_trick()

                        # Is that the last trick of this hand?
                        if self.ns.tricks + self.ew.tricks == 13:

                            # Yup.  Finish the hand up.
                            self.finish_hand()

                            # Did someone win the overall game?
                            winner = self.find_winner()
                            if winner:

                                # Yup.  Finish.
                                self.resolve(winner)
                                self.finish()

                            else:

                                # Nope.  Pass the deal to the next dealer...
                                self.dealer = self.next_seat(self.dealer)

                                # Deal and set up the first player.
                                self.new_deal()
                                self.turn = self.next_seat(self.dealer)
                                self.layout.change_turn(self.turn.data.who)

                    else:

                        # Trick not over.  Rotate.
                        self.turn = self.next_seat(self.turn)
                        self.layout.change_turn(self.turn.data.who)
                        if self.turn.player:
                            self.show_hand(self.turn.player)

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def finish_trick(self):

        # Okay, we have a trick with four cards.  Which card won?
        winner = handle_trick(self.trick, self.trump_suit)

        # This /should/ just return one seat...
        winning_seat_list = [x for x in self.seats if x.data.card == winner]

        if len(winning_seat_list) != 1:
            self.server.log.log(
                self.log_prefix +
                "Something went horribly awry; trick ended without a finish.")
            self.bc_pre(
                "Something went horribly wrong; no one won the trick!  Tell the admin.\n"
            )
            return

        winning_seat = winning_seat_list[0]

        # Print information about the winning card.
        self.bc_pre("%s wins the trick with ^C%s^~.\n" %
                    (self.get_sp_str(winning_seat), card_to_str(winner, LONG)))

        # Give the trick to the correct partnership.
        if winning_seat == self.seats[0] or winning_seat == self.seats[2]:
            self.ns.tricks += 1
        else:
            self.ew.tricks += 1

        # Clear the trick.
        self.clear_trick()

        # Set the next leader to the player who won.
        self.turn = winning_seat
        self.layout.change_turn(self.turn.data.who)
        if self.turn.player:
            self.show_hand(self.turn.player)

    def finish_hand(self):

        # Which side won more than 6 tricks?
        if self.ns.tricks > 6:
            winning_side = "^RNorth/South^~"
            addend = self.ns.tricks - 6
            self.ns.score += addend
        else:
            winning_side = "^MEast/West^~"
            addend = self.ew.tricks - 6
            self.ew.score += addend

        # Let everyone know.
        self.bc_pre("%s wins the hand and gains ^C%s^~.\n" %
                    (winning_side, get_plural_str(addend, "point")))
        self.bc_pre(self.get_score_str())

    def find_winner(self):

        # Easy: has one of the sides reached a winning score?
        if self.ns.score >= self.goal:
            return self.ns
        elif self.ew.score >= self.goal:
            return self.ew

        return None

    def resolve(self, winning_partnership):

        if self.ns == winning_partnership:
            name_one = self.seats[0].player_name
            name_two = self.seats[2].player_name
        else:
            name_one = self.seats[1].player_name
            name_two = self.seats[3].player_name
        self.bc_pre("^G%s^~ and ^G%s^~ win!\n" % (name_one, name_two))