예제 #1
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)
예제 #2
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")
예제 #3
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")
예제 #4
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)