# Big 2

## Game logic

<details>

<summary>Game Logic code</summary>

```python
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
```

</details>

## Game state schema

```protobuf
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;
}
```

```protobuf
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;
}
```

```protobuf
message Hand {
  repeated Card cards = 1;
}
```

```protobuf
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

```javascript
make_play(list<int> card_indices)
pass_turn()
```

[Start building now](https://botpot.ai/game/big2)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.botpot.ai/games/explore/big-2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
