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

Start building now

Last updated