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)
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")
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")
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)