import randomdefinit_game(game_settings): deck =__initalize_deck()# set up hands for players hands = []for player_idx inrange(4): hand = []for x inrange(13): card = deck.pop()# player with 3 of diamonds goes firstif card =={'suit':'DIAMOND','card_rank':3}: first_player_index = player_idx hand.append(card) hands.append({'cards': __sort_cards(hand) }) state ={'player_turn_idx': first_player_index,'players_hands': hands,'passes':0,'discard_pile': [],'last_play':{'type':'NONE'}}return statedef__initalize_deck(): suits = ['DIAMOND','CLUB','HEART','SPADE'] deck = []for card_suit in suits:for rank inrange(13): deck.append({'suit': card_suit, 'card_rank': rank +1 }) random.shuffle(deck)return deckdef__can_be_played(state,play): PLAY_VALUE ={'SINGLE':1,'DOUBLE':2,'TRIPLE':3,'STRAIGHT':4,'FLUSH':5,'FULLHOUSE':6,'BOMB':7,'STRAIGHTFLUSH':8}iflen(state['discard_pile'])==0:for c in play['cards']:if c =={'suit':'DIAMOND','card_rank':3}:returnTrueraiseInvalidActionError("First player has to play down a play containing three of diamonds")iflen(state['last_play']['cards'])==0:returnTrue last_play_type = state['last_play']['type']# 5 card hands can beat other 5 card hands, single double triple needs to be same type to playif PLAY_VALUE[last_play_type]<=3:if last_play_type != play['type']:returnFalseelif__compare(play['cards'][0], state['last_play']['cards'][0])>0:returnTrueelse:returnFalseelse:if PLAY_VALUE[play['type']]< PLAY_VALUE[last_play_type]:returnFalseelif PLAY_VALUE[play.type]> PLAY_VALUE[last_play_type]:returnTrueelif PLAY_VALUE[play.type]==5and PLAY_VALUE[last_play_type]==5:if__compare_suit(play['cards'][0]['suit'], state['last_play']['cards'][0]['suit'])>0:returnTrueelif__compare(play['cards'][0], state['last_play']['cards'][0])>0:returnTrueelif__compare(play['cards'][0], state['last_play']['cards'][0])>0:returnTrueelse:returnFalsedef__get_suit_value_dict():return{'DIAMOND':1,'CLUB':2,'HEART':3,'SPADE':4}def__compare_suit(suit1,suit2): suit_dict =__get_suit_value_dict() compare = suit_dict[suit1]- suit_dict[suit2]return comparedef__compare_rank(rank1,rank2):if rank1 == rank2:return0elif rank1 ==2:return1elif rank2 ==2:return-1elif rank1 ==1:return1elif rank2 ==1:return-1else: compare = rank1 - rank2return comparedef__compare(card1,card2): compare =__compare_rank(card1['card_rank'], card2['card_rank'])if compare ==0:return__compare_suit(card1['suit'], card2['suit'])else:return compare# insertion sort because will be dealing with small lists (at most length 13 (rare), usually length 5)def__sort_cards(cards): cards_array = [c for c in cards] n =len(cards_array)for i inrange(n):for j inrange(i, 0, -1):if__compare(cards_array[j], cards_array[j-1])<0: cards_array[j], cards_array[j-1]= cards_array[j-1], cards_array[j]return cards_arraydef__straight_helper(num):if num ==1:return14else:return numdef__determine_play(cards): cards =__sort_cards(cards)iflen(cards)==1: play_type ='SINGLE' top_card = cards[0]eliflen(cards)==2and cards[0]['card_rank'] == cards[1]['card_rank']: play_type ='DOUBLE' top_card = cards[1]eliflen(cards)==3and cards[0]['card_rank'] == cards[1]['card_rank'] and cards[0]['card_rank'] == cards[2]['card_rank']: play_type ='TRIPLE' top_card = cards[2]eliflen(cards)==5: straight =True flush =True card1 =None count1 =0 card2 =None count2 =0 suit = cards[0]['suit']for i inrange(5):if cards[i]['suit'] != suit: flush =Falseif (cards[i]['card_rank'] ==2) or\ (i <4and__straight_helper(cards[i+1]['card_rank'])- cards[i]['card_rank'] !=1): straight =Falseif card1 isNone: card1 = cards[i] count1 +=1elif cards[i]['card_rank'] == card1['card_rank']: count1 +=1elif card2 isNone: card2 = cards[i] count2 +=1elif cards[i]['card_rank'] == card2['card_rank']: count2 +=1if straight and flush: play_type ='STRAIGHTFLUSH' top_card = cards[4]elif straight: play_type ='STRAIGHT' top_card = cards[4]elif flush: play_type ='FLUSH' top_card = cards[4]elif count1 ==3and count2 ==2: play_type ='FULLHOUSE' top_card = card1elif count2 ==3and count1 ==2: play_type ='FULLHOUSE' top_card = card2elif count1 ==4: play_type ='BOMB' top_card = card1elif count2 ==4: play_type ='BOMB' top_card = card2else:raiseInvalidActionError("Invalid play")else:raiseInvalidActionError("Invalid play") idx_of_top_card = cards.index(top_card)# ordered_cards is just cards with the top_card being the first one in the list ordered_cards = [cards[idx_of_top_card]] ordered_cards.extend(cards[0:idx_of_top_card]) ordered_cards.extend(cards[idx_of_top_card +1:])return{'type': play_type,'cards': ordered_cards}defmake_play(state,card_indices): player_idx = state['player_turn_idx']iflen(card_indices)!=len(set(card_indices)):raiseInvalidActionError("Invalid play") hand = state['players_hands'][player_idx]['cards'] cards_played = []for i in card_indices:if i <0or i >=len(hand):raiseInvalidActionError("Not a valid play in the player's hand") cards_played.append(hand[i]) play =__determine_play(cards_played)if__can_be_played(state, play): state['last_play']= play new_hand = []for i inrange(len(hand)):if i notin card_indices: new_hand.append(hand[i]) state['players_hands'][player_idx]['cards'] = new_handfor card in cards_played: state['discard_pile'].append(card) state['player_turn_idx']= (player_idx +1) %4 state['passes']=0else:raiseInvalidActionError("Invalid play")return statedefpass_turn(state): player_idx = state['player_turn_idx']if state['passes']<2: state['player_turn_idx']= (player_idx +1) %4 state['passes']+=1elif state['passes']==2: state['passes']=0 state['last_play']['cards'] = [] state['player_turn_idx']= (player_idx +1) %4else:raiseInvalidActionError("Everyone else has passed. You can play any card")return statedefget_game_result(state):for i inrange(len(state['players_hands'])):iflen(state['players_hands'][i]['cards'])==0:return{"game_result":"Winner","winner_idx": i}return{"game_result":"NoWinnerYet"}defget_player_states(state): hand_count = [len(state['players_hands'][i]['cards'])for i inrange(4)] player_states = []for i inrange(4): player_states.append({'player_turn_idx': state['player_turn_idx'],'player_hand': state['players_hands'][i],'players_hand_count': hand_count,'passes': state['passes'],'discard_pile': state['discard_pile'],'last_play': state['last_play'] })return player_states
Game state schema
message State {// Required field to indicate the player who should be making the next move// Values = 0 or 1int32 player_turn_idx =1;// Each player can only see their own hands. They can see the number of cards// in the other player's hands, but not their card type.Hand player_hand =2;// An array of the number of cards in each player's hands, including yourself.repeatedint32 players_hand_count =3;// The number of consecutive players who passed. If everyone else passed, // you can make whatever Play you want next.int32 passes =4;// All the cards that have been played goes into the discard pile.repeatedCard discard_pile =5;// The last play. What you play is based on this, and it should match normally// match in Play Type, but larger, or be Bomb or Straight FLush. If everyone// else passed, the cards in last_play will be empty, and you can play whatever.Play last_play =6;}
message Play {
enum Type {
SINGLE = 0;
DOUBLE = 1;
TRIPLE = 2;
STRAIGHT = 3;
FLUSH = 4;
FULLHOUSE = 5;
BOMB = 6;
STRAIGHTFLUSH = 7;
}
// Subsequent plays must match in type except for BOMB
// and STRAIGHTFLUSH
Type type = 1;
// Array of cards in that play.
repeated Card cards = 2;
}
message Hand {
repeated Card cards = 1;
}
message Card {
enum Suit {
DIAMOND = 0;
CLUB = 1;
HEART = 2;
SPADE = 3;
}
Suit suit = 1;
// Values go from 1 through 13 inclusive. J, Q, K, A have ranks of
// 11, 12, 13, 1, respectively.
int32 card_rank = 2;
}