public void HandEvaluator()
        {
            Assert.AreEqual("High Card (A)", HoldemGame.HandClass(hand("2C 3C 4C TD AC")));
            Assert.AreEqual("Pair (2)", HoldemGame.HandClass(hand("2C 2D 4C TD AC")));
            Assert.AreEqual("Two Pairs (3/2)", HoldemGame.HandClass(hand("2C 2D 3C 3D AC")));
            Assert.AreEqual("Three of a Kind (2)", HoldemGame.HandClass(hand("2C 2D 2S 3D AC")));
            Assert.AreEqual("Straight (Wheel)", HoldemGame.HandClass(hand("2C 3S 4S 5D AC")));
            Assert.AreEqual("Straight", HoldemGame.HandClass(hand("3C 4S 5S 6D 7C")));
            Assert.AreEqual("Flush", HoldemGame.HandClass(hand("3S TS 5S 6S 7S")));
            Assert.AreEqual("Full House (10/3)", HoldemGame.HandClass(hand("TS TC TC 3S 3S")));
            Assert.AreEqual("Four of a Kind (10)", HoldemGame.HandClass(hand("TS TC TC TS 4S")));
            Assert.AreEqual("Straight Flush", HoldemGame.HandClass(hand("9S TS JS QS KS")));
            Assert.AreEqual("Royal Flush", HoldemGame.HandClass(hand("TS JS QS KS AS")));

            // high card
            Assert.Greater(
                hand("2C 3C 4C TD AC"),
                hand("2H 3H 4H TH KD"));
            // straight flushes
            Assert.Greater(
                HoldemGame.EvaluateHand(1, 2, 3, 4, 5),
                HoldemGame.EvaluateHand(0, 1, 2, 3, 4));
        }
Example #2
0
    private void Update()
    {
        // XXX apparently udon will run some Update()s before Starts() so
        // HoldemGame's start might not initialize this
        if (game == null)
        {
            return;
        }
        // don't bother if game isn't initialized
        if (game.tableState == TABLE_UNINITIALIZED)
        {
            return;
        }

        var locallySeated = isLocallySeated();
        var seated        = IsSeated();

        uiReady.interactable = locallySeated;
        goReadyToggle.SetActive(locallySeated || !seated);

        if (locallySeated)
        {
            // one ui read
            playerReady = uiReady.isOn;

            uiJoinLeaveButton.interactable = !playerReady;
            if (playerReady)
            {
                uiJoinLeaveText.text = "Ready";
            }
            else
            {
                uiJoinLeaveText.text = "Leave";
            }
        }
        else
        {
            if (seated)
            {
                uiJoinLeaveButton.interactable = false;
                uiJoinLeaveText.text           = $"{OwnerName()}";
            }
            else
            {
                uiJoinLeaveButton.interactable = true;
                uiJoinLeaveText.text           = "Join";
            }
        }

        var playerState = game.playerState[seatIdx];
        var inPlay      = playerState != PLAYER_DEAD;

        uiDealer.enabled = game.tableState == TABLE_PLAYING && game.dealerSeat == seatIdx;

        var  winner   = game.tableState == TABLE_WINNER;
        bool showdown = false;

        if (winner)
        {
            int challengers = 0;
            for (int i = 0; i < 10; ++i)
            {
                if (game.playerState[i] == PLAYER_COMMITED)
                {
                    challengers++;
                }
            }
            if (challengers > 1)
            {
                showdown = true;
            }
        }

        // display at least blank cards if in play
        goHoleCards.SetActive(inPlay);

        var holes = inPlay && (locallySeated || game.headsUp || showdown);

        uiHole0.enabled    = holes;
        uiHole1.enabled    = holes;
        uiBestHand.enabled = holes && game.bettingRound >= FLOP;

        var acting = playerState == PLAYER_ACTING;
        var valid  = game.IsValidBet(bet, seatIdx);

        uiStatusColor.enabled = seated;
        uiStatusColor.color   =
            playerState == PLAYER_DEAD ? Color.black :
            playerState == PLAYER_PENDING ? Color.white :
            playerState == PLAYER_ACTING ? Color.green :
            playerState == PLAYER_COMMITED ? Color.blue : Color.red;

        goBetUi.SetActive(acting);

        uiBet.text = GetBet();

        goChipDisplay.SetActive(seated);
        var stack = game.stacks[seatIdx];

        if (acting && bet > 0)
        {
            var left = stack - bet;
            uiChips.text = $"{left} (total {stack})";
        }
        else
        {
            uiChips.text = $"{stack}";
        }

        uiCallCheck.interactable = locallySeated && acting && valid;
        uiFold.interactable      = locallySeated && acting;

        uiConfirm.interactable = uiPending && (valid || bet == -1);
        var uiConfirmColors = uiConfirm.colors;

        uiConfirmColors.normalColor   = committedEpoch == game.epoch ? Color.yellow : Color.white;
        uiConfirmColors.disabledColor = playerState == PLAYER_COMMITED ? Color.blue : Color.grey;
        uiConfirm.colors = uiConfirmColors;

        var uiFoldColors = uiFold.colors;

        uiFoldColors.normalColor = (bet == -1 && uiPending) ? Color.red : Color.white;
        uiFold.colors            = uiFoldColors;

        var uiCallCheckColors = uiCallCheck.colors;

        uiCallCheckColors.normalColor = bet >= 0 && uiPending ? Color.green : Color.white;
        uiCallCheck.colors            = uiCallCheckColors;

        uiCallCheckText.text =
            bet == 0 ? "Check" :
            bet == game.stacks[seatIdx] ? "All-in" :
            bet == -1 ? "(fold)" :
            !valid ? "(invalid bet)" :
            bet > game.currentBet ?
            (game.currentBet > 0 ?  "Re-Raise" : "Raise") :
            game.currentBet > 0 ? "Call" : "Check";

        uiActTimer.enabled = acting;
        if (acting)
        {
            var now       = Networking.LocalPlayer == null ? (int)(Time.time * 1000) : Networking.GetServerTimeInMilliseconds();
            var timeout   = game.lastTransitionMillis;
            var remaining = game.actionTimeoutSecs - (now - timeout) / 1000;
            uiActTimer.text = $"{remaining / 60}:{remaining % 60} to act";
        }

        // transition check
        if (updateSeenEpoch != game.epoch)
        {
            updateSeenEpoch = game.epoch;
            // edge-triggered
            if (locallySeated)
            {
                if (acting)
                {
                    // start off with a call
                    bet = Mathf.Min(stack, game.currentBet - game.roundContribution[seatIdx]);
                    // require the confirm again
                    uiPending = false;
                }
                else
                {
                    // once it comes around to us again, make sure we aren't
                    // accidentally already commited from the past
                    bet            = 0;
                    committedEpoch = -1;
                }
            }
            if (game.tableState == TABLE_PLAYING)
            {
                uiHole0.text = game.unicard(game.holeCards0[seatIdx]);
                uiHole1.text = game.unicard(game.holeCards1[seatIdx]);
                var bestHand =
                    game.bettingRound == RIVER?game.BestPlayerHandSeat(seatIdx) :
                        game.bettingRound == TURN?game.BestPlayerHandTurnSeat(seatIdx) :
                            game.bettingRound == FLOP?game.BestPlayerHandFlop(seatIdx) : 0;

                if (bestHand > 0UL)
                {
#if !COMPILER_UDONSHARP
                    uiBestHand.text = $"{HoldemGame.HandClass(bestHand)}";
#else
                    uiBestHand.text = $"{game.HandClass(bestHand)}";
#endif
                }
                else
                {
                    uiBestHand.text = "";
                }
            }
        }
    }