Example #1
0
def leave_table(table_name):
    data = request.get_json()
    table = Table.query.filter_by(name=table_name).first()
    user = User.query.get(get_jwt_identity())

    if not user:
        return jsonify({"success": False, "msg": "No user found"}), 401
    if not user.active_player:
        return jsonify({"success": False, "msg": "No active player"})

    try:
        user.new_transaction(user.active_player.balance)
        player_data = user.active_player.serialize()
        table.leave(user.active_player)
        sse.publish(player_data, type="playerLeft", channel=table.name)
        return jsonify({"success": True})
    except PlayerNotAtTableError:
        return jsonify({
            "success": False,
            "msg": "Player is not at table {}".format(table.id)
        })
    except Exception as e:
        return jsonify({
            "success": False,
            "msg": "Unknown exception",
            "exception": e
        })
Example #2
0
def new_hand(table_name):
    table = Table.query.filter_by(name=table_name).first()
    hand = table.new_hand(TexasHoldemHand)

    sse.publish(
        {
            "id": hand.id,
            "next": hand.next_to_act.id,
            "dealer": hand.dealer.id,
            "start_utc": hand.created_utc
        },
        type="newHand",
        channel=table.name)

    return jsonify({"success": True, "hand": hand.id})
Example #3
0
def join_table(table_name):
    data = request.get_json()
    table = Table.query.filter_by(name=table_name).first()
    user = User.query.get(get_jwt_identity())
    position = data.get("position")
    raw_amount = data.get("amount", "0")

    if not user:
        return jsonify({"success": False, "msg": "No user found"}), 401
    if user.active_player:
        return jsonify({
            "success": False,
            "msg": "Can only occupy one seat at a time"
        })

    # TODO - This converts the decimal input to an int and rounds down to the
    #  nearest small blind buy in. Is that what I should be doing? Will
    #  probably need some way to tell the players what happened.
    pennies = int(float(raw_amount) * 100)
    rounded_amount = (pennies // table.stakes.small) * table.stakes.small

    try:
        player = table.join(user, position)
        player.update(balance=rounded_amount)
        user.new_transaction(-rounded_amount)

        player_data = player.serialize()
        player_data["name"] = user.name
        sse.publish(player_data, type="newPlayer", channel=table.name)

        return jsonify({"success": True})
    except SeatOccupiedError:
        return jsonify({
            "success": False,
            "msg": "Seat {} is occupied".format(position)
        })
    except TableFullError:
        return jsonify({
            "success": False,
            "msg": "Table {} is full".format(table.id)
        })
    except Exception as e:
        return jsonify({
            "success": False,
            "msg": "Unknown exception",
            "exception": e
        })
Example #4
0
def deal(table_name):
    table = Table.query.filter_by(name=table_name).first()

    if not table.active_hand:
        return jsonify({"success": False, "msg": "No active hand to deal"})
    elif table.active_hand.antes_owed or table.active_hand.blinds_owed:
        return jsonify({
            "success": False,
            "msg": "Can't deal before pre-hand bets complete"
        })
    elif table.active_hand.dealt:
        return jsonify({"success": False, "msg": "Hand already dealt"})

    hand = table.active_hand
    hand.deal(TexasHoldemHand,
              table.ready_players)  # TODO - get hand type from json

    sse.publish(
        {
            "id": hand.id,
            "next": hand.next_to_act.id,
            "dealer": hand.dealer.id,
            "numPlayers": len(hand.players)
        },
        type="deal",
        channel=table.name)

    if EMULATOR_ENABLED:
        sse.publish([{
            "playerId": holding.player_id,
            "holdings": [card.name for card in holding.cards]
        } for holding in hand.player_holdings],
                    type="emulateDeal",
                    channel=table.name)

    for holding in hand.player_holdings:
        sse.publish(
            {
                "id": hand.id,
                "playerId": holding.player_id,
                "holdings": [card.name for card in holding.cards]
            },
            type="deal",
            channel=holding.player.user_id)

    return jsonify({"success": True, "hand": hand.id})
Example #5
0
def heartbeat():
    """Send data to every channel regularly to keep alive"""
    with app.app_context():
        for channel in sse.redis.pubsub_channels():
            sse.publish({"alive": True}, type="heartbeat", channel=channel)
Example #6
0
def action(table_name):
    table = Table.query.filter_by(name=table_name).first()
    hand = table.active_hand
    if not hand:
        # TODO - Handle no current hand state
        return jsonify({"success": False, "msg": "No current hand"})

    data = request.get_json()
    user = User.query.get(get_jwt_identity())
    if not user:
        return jsonify({"success": False, "msg": "No user found"}), 401

    player = user.active_player
    if EMULATOR_ENABLED:
        player = hand.next_to_act
    if not player:
        return jsonify({"success": False, "msg": "No player found"})
    elif player not in table.active_players:
        return jsonify({
            "success": False,
            "msg": "Can't act while sitting at a different table"
        })
    elif player not in table.ready_players:
        return jsonify({
            "success": False,
            "msg": "Can't act while sitting out"
        })

    if player != hand.next_to_act:
        # TODO - Handle action by wrong user state
        return jsonify({"success": False, "msg": "Player acting out of turn"})

    try:
        action_type = ActionType[data.get("actionType")]
    except KeyError:
        return jsonify({"success": False, "msg": "Invalid action type"})

    current_bet = data.get("betAmt", 0)
    prev_bet = hand.active_betting_round.sum_player_bets(player)
    total_bet = prev_bet + current_bet

    if hand.antes_owed:
        if action_type != ActionType.BLIND:  # TODO - Create ANTE type?
            return jsonify({"success": False, "msg": "Expected ante"})
        elif current_bet != hand.stakes.ante and current_bet != player.balance:
            return jsonify({"success": False, "msg": "Incorrect ante amount"})
    elif hand.blinds_owed:
        if action_type != ActionType.BLIND:
            return jsonify({"success": False, "msg": "Expected blind"})

        expected_blind = hand.stakes.small if hand.active_betting_round.sum == 0 else hand.stakes.big
        if current_bet != expected_blind and current_bet != player.balance:
            # TODO - Solve for all-in on big blind
            #  This should allow players to bet amounts other than SB or BB
            #  but it will fail to set the expected bet to the BB if the player
            #  who should have paid the full BB went all-in for less.
            return jsonify({"success": False, "msg": "Incorrect blind amount"})
    else:
        if not hand.dealt:
            return jsonify({
                "success": False,
                "msg": "Can't take action before hand dealt"
            })
        if action_type == ActionType.BLIND:
            return jsonify({
                "success": False,
                "msg": "Cannot post blind after hand dealt"
            })
        elif action_type == ActionType.CHECK:
            current_bet = 0
            if hand.active_betting_round.bet and not prev_bet == hand.active_betting_round.bet:
                return jsonify({
                    "success": False,
                    "msg": "Cannot check if bet > 0"
                })
        elif action_type == ActionType.BET:
            if current_bet > player.balance:
                return jsonify({"success": False, "msg": "Bet > balance"})
            elif hand.active_betting_round.bet and total_bet < hand.active_betting_round.bet and total_bet != player.balance:
                return jsonify({"success": False, "msg": "Bet < current bet"})
            elif total_bet < determine_min_raise(
                    hand.stakes.big, hand.active_betting_round.bet, prev_bet,
                    hand.active_betting_round.raise_amt, player.balance
            ) and total_bet != hand.active_betting_round.bet:
                return jsonify({"success": False, "msg": "Bet < minimum bet"})

    prev_num_rounds = len(hand.betting_rounds)

    try:
        act = Action.create(holding_id=player.active_holding.id,
                            type=data.get("actionType"))
        resolution = hand.resolve_action(act, current_bet, total_bet)
    except IntegrityError:
        return jsonify({"success": False, "msg": "Database error"})
    except Exception as e:
        return jsonify({
            "success": False,
            "msg": "Unknown error",
            "error": str(e)
        })

    # TODO - This is such a hack. Fix it.
    # Number of cards showing at each round num
    try:
        num_up_cards = [0, 3, 4, 5]
        num_up_cards_round = num_up_cards[hand.active_betting_round.round_num]
        up_cards = [card.name
                    for card in hand.board.cards][:num_up_cards_round]
    except AttributeError:
        # Hand has ended, so hand.active_betting_round is None
        up_cards = []

    sse_data = {
        "id":
        act.id,
        "handId":
        hand.id,
        "playerId":
        act.player.id,
        "playerBalance":
        act.player.balance,
        "actionType":
        act.type,
        "pot":
        hand.active_pot.amount,
        "next":
        hand.next_to_act.id,
        "bet":
        current_bet,
        "roundBet":
        hand.active_betting_round.bet if hand.active_betting_round else 0,
        "roundRaise":
        hand.active_betting_round.raise_amt
        if hand.active_betting_round else 0,
        "playerRoundBet":
        total_bet,  # Sum of player's bets for this betting round (THIS WILL FAIL ON ALL-IN SIDE POTS)
        "roundComplete":
        resolution.get("round_complete", False)
    }

    if resolution.get("new_round"):
        sse_data["newRound"] = True
        sse_data["board"] = up_cards

    if resolution.get("hand_complete"):
        sse_data["handComplete"] = True
        sse_data["winners"] = [{
            "id": pot.winner.id,
            "amount": pot.amount,
            "balance": pot.winner.balance
        } for pot in hand.pots_paid]

        # Include holding data for showdown if necessary
        if len(hand.live_holdings) > 1:
            sse_data["showdown"] = [{
                "playerId":
                holding.player_id,
                "holdings": [card.name for card in holding.cards],
            } for holding in hand.live_holdings]

    sse.publish(sse_data, type="action", channel=table.name)

    return jsonify({"success": True})