Big 2
Game logic
Game Logic code
import random
def init_game(game_settings):
deck = __initalize_deck()
# set up hands for players
hands = []
for player_idx in range(4):
hand = []
for x in range(13):
card = deck.pop()
# player with 3 of diamonds goes first
if 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 state
def __initalize_deck():
suits = ['DIAMOND', 'CLUB', 'HEART', 'SPADE']
deck = []
for card_suit in suits:
for rank in range(13):
deck.append({
'suit': card_suit,
'card_rank': rank + 1
})
random.shuffle(deck)
return deck
def __can_be_played(state, play):
PLAY_VALUE = {
'SINGLE': 1,
'DOUBLE': 2,
'TRIPLE': 3,
'STRAIGHT': 4,
'FLUSH': 5,
'FULLHOUSE': 6,
'BOMB': 7,
'STRAIGHTFLUSH': 8
}
if len(state['discard_pile']) == 0:
for c in play['cards']:
if c == {
'suit': 'DIAMOND',
'card_rank': 3
}:
return True
raise InvalidActionError("First player has to play down a play containing three of diamonds")
if len(state['last_play']['cards']) == 0:
return True
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 play
if PLAY_VALUE[last_play_type] <= 3:
if last_play_type != play['type']:
return False
elif __compare(play['cards'][0], state['last_play']['cards'][0]) > 0:
return True
else:
return False
else:
if PLAY_VALUE[play['type']] < PLAY_VALUE[last_play_type]:
return False
elif PLAY_VALUE[play.type] > PLAY_VALUE[last_play_type]:
return True
elif PLAY_VALUE[play.type] == 5 and PLAY_VALUE[last_play_type] == 5:
if __compare_suit(play['cards'][0]['suit'], state['last_play']['cards'][0]['suit']) > 0:
return True
elif __compare(play['cards'][0], state['last_play']['cards'][0]) > 0:
return True
elif __compare(play['cards'][0], state['last_play']['cards'][0]) > 0:
return True
else:
return False
def __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 compare
def __compare_rank(rank1, rank2):
if rank1 == rank2:
return 0
elif rank1 == 2:
return 1
elif rank2 == 2:
return -1
elif rank1 == 1:
return 1
elif rank2 == 1:
return -1
else:
compare = rank1 - rank2
return compare
def __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 in range(n):
for j in range(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_array
def __straight_helper(num):
if num == 1:
return 14
else:
return num
def __determine_play(cards):
cards = __sort_cards(cards)
if len(cards) == 1:
play_type = 'SINGLE'
top_card = cards[0]
elif len(cards) == 2 and cards[0]['card_rank'] == cards[1]['card_rank']:
play_type = 'DOUBLE'
top_card = cards[1]
elif len(cards) == 3 and cards[0]['card_rank'] == cards[1]['card_rank'] and cards[0]['card_rank'] == cards[2]['card_rank']:
play_type = 'TRIPLE'
top_card = cards[2]
elif len(cards) == 5:
straight = True
flush = True
card1 = None
count1 = 0
card2 = None
count2 = 0
suit = cards[0]['suit']
for i in range(5):
if cards[i]['suit'] != suit:
flush = False
if (cards[i]['card_rank'] == 2) or \
(i < 4 and __straight_helper(cards[i+1]['card_rank']) - cards[i]['card_rank'] != 1):
straight = False
if card1 is None:
card1 = cards[i]
count1 += 1
elif cards[i]['card_rank'] == card1['card_rank']:
count1 += 1
elif card2 is None:
card2 = cards[i]
count2 += 1
elif cards[i]['card_rank'] == card2['card_rank']:
count2 += 1
if 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 == 3 and count2 == 2:
play_type = 'FULLHOUSE'
top_card = card1
elif count2 == 3 and count1 == 2:
play_type = 'FULLHOUSE'
top_card = card2
elif count1 == 4:
play_type = 'BOMB'
top_card = card1
elif count2 == 4:
play_type = 'BOMB'
top_card = card2
else:
raise InvalidActionError("Invalid play")
else:
raise InvalidActionError("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
}
def make_play(state, card_indices):
player_idx = state['player_turn_idx']
if len(card_indices) != len(set(card_indices)):
raise InvalidActionError("Invalid play")
hand = state['players_hands'][player_idx]['cards']
cards_played = []
for i in card_indices:
if i < 0 or i >= len(hand):
raise InvalidActionError("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 in range(len(hand)):
if i not in card_indices:
new_hand.append(hand[i])
state['players_hands'][player_idx]['cards'] = new_hand
for card in cards_played:
state['discard_pile'].append(card)
state['player_turn_idx'] = (player_idx + 1) % 4
state['passes'] = 0
else:
raise InvalidActionError("Invalid play")
return state
def pass_turn(state):
player_idx = state['player_turn_idx']
if state['passes'] < 2:
state['player_turn_idx'] = (player_idx + 1) % 4
state['passes'] += 1
elif state['passes'] == 2:
state['passes'] = 0
state['last_play']['cards'] = []
state['player_turn_idx'] = (player_idx + 1) % 4
else:
raise InvalidActionError("Everyone else has passed. You can play any card")
return state
def get_game_result(state):
for i in range(len(state['players_hands'])):
if len(state['players_hands'][i]['cards']) == 0:
return {
"game_result": "Winner",
"winner_idx": i
}
return {
"game_result": "NoWinnerYet"
}
def get_player_states(state):
hand_count = [len(state['players_hands'][i]['cards']) for i in range(4)]
player_states = []
for i in range(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 1
int32 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.
repeated int32 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.
repeated Card 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;
}
Action schema
make_play(list<int> card_indices)
pass_turn()
Last updated