private IEnumerator Payout(int payout)
    {
        JackpotText.Visible = false;

        if (payout > 0)
        {
            State = KtaneVideoPoker.State.Paying;

            int amountPaid = 0;

            BetText.Text = "";

            Audio.PlaySoundAtTransform("gliss2", transform);
            yield return(new WaitForSeconds(1f));

            while (amountPaid < payout)
            {
                int increment = (payout - amountPaid) > 50 ? (payout - amountPaid) / 25 : 1;
                Credits         += increment;
                amountPaid      += increment;
                BetText.Text     = string.Format("WIN {0}", amountPaid);
                CreditsText.Text = string.Format("CREDIT {0}", Credits);
                Audio.PlaySoundAtTransform("beep_25ms", transform);
                yield return(new WaitForSeconds(0.05f));
            }
        }

        if (payout >= JackpotThreshold && !IsSolved)
        {
            Streak = SolveStreakTarget;
            ProgressLightManager.SetValue(Streak);
            ModuleLog("Lucky you! You won a jackpot. Module disarmed!");
            IsSolved = true;
            BombModule.HandlePass();
        }
        else if (Streak >= SolveStreakTarget && !IsSolved)
        {
            ModuleLog("You played {0} hands correctly in a row. Module disarmed!", SolveStreakTarget);
            IsSolved = true;
            BombModule.HandlePass();
        }

        GameInfoButton.Enable();
        SpeedButton.Enable();
        BetOneButton.Enable();
        BetMaxButton.Enable();
        DealButton.Text.text = "Deal";
        DealButton.Enable();

        State = KtaneVideoPoker.State.Idle;

        NextBetOneSetsBetAmountToOne = true;
    }
    private IEnumerator Jackpot()
    {
        if (!IsSolved)
        {
            ModuleLog("JACKPOT!");
        }
        Audio.PlaySoundAtTransform("jackpot_-3dB", transform);
        State = KtaneVideoPoker.State.JackpotPending;

        DealButton.Text.text = "Claim";
        DealButton.Enable();

        // Only commaify numbers >= 10,000
        JackpotText.Text = string.Format("JACKPOT!  {0}\nCALL ATTENDANT", string.Format(JackpotValue >= 10000 ? "{0:n0}" : "{0}", JackpotValue));

        while (true)
        {
            JackpotText.Visible = !JackpotText.Visible;
            yield return(new WaitForSeconds(1));
        }
    }
    private void ResetMachine()
    {
        ModuleLog("Resetting machine...");
        for (int i = 0; i < 5; i++)
        {
            CardObjects[i].Card = null;
        }
        Streak = 0;
        ProgressLightManager.SetValue(0);
        BetAmount            = 1;
        BetText.Text         = "BET 1";
        Credits              = 1000;
        CreditsText.Text     = "CREDIT 1000";
        GameMessageText.Text = "PLAY 5 CREDITS";
        State = KtaneVideoPoker.State.Idle;
        GameInfoButton.Text.text = "Game Info";
        DealButton.Text.text     = "Deal";

        foreach (var co in CardObjects)
        {
            co.Held = false;
        }

        GameInfoButton.Enable();
        SpeedButton.Enable();
        BetOneButton.Enable();
        BetMaxButton.Enable();
        DealButton.Enable();

        while (SpeedButton.GetSpeedIndex() != 2)
        {
            SpeedButton.ChangeSpeed();
        }

        PayTable.Visible = false;
    }
    // Buttons

    private void OnPressGameInfo(KMSelectable sender)
    {
        if (State == KtaneVideoPoker.State.Idle)
        {
            State            = KtaneVideoPoker.State.ShowPayTable;
            PayTable.Visible = true;

            GameInfoButton.Text.text = "Back";
            SpeedButton.Disable();
            BetOneButton.Disable();
            BetMaxButton.Disable();
            DealButton.Disable();

            // Not the prettiest, but the KMSelectables are the top-level card objects
            foreach (var card in Raw_CardSelectables)
            {
                card.gameObject.SetActive(false);
            }
        }
        else if (State == KtaneVideoPoker.State.ShowPayTable)
        {
            State            = KtaneVideoPoker.State.Idle;
            PayTable.Visible = false;

            GameInfoButton.Text.text = "Game Info";
            SpeedButton.Enable();
            BetOneButton.Enable();
            BetMaxButton.Enable();
            DealButton.Enable();

            // Not the prettiest, but the KMSelectables are the top-level card objects
            foreach (var card in Raw_CardSelectables)
            {
                card.gameObject.SetActive(true);
            }
        }
        else if (State == KtaneVideoPoker.State.ChooseHolds)
        {
            State            = KtaneVideoPoker.State.ShowPayTableMidHand;
            PayTable.Visible = true;

            GameInfoButton.Text.text = "Back";
            SpeedButton.Disable();
            DealButton.Disable();

            // Not the prettiest, but the KMSelectables are the top-level card objects
            foreach (var card in Raw_CardSelectables)
            {
                card.gameObject.SetActive(false);
            }
        }
        else if (State == KtaneVideoPoker.State.ShowPayTableMidHand)
        {
            State            = KtaneVideoPoker.State.ChooseHolds;
            PayTable.Visible = false;

            GameInfoButton.Text.text = "Game Info";
            SpeedButton.Disable();
            DealButton.Enable();

            // Not the prettiest, but the KMSelectables are the top-level card objects
            foreach (var card in Raw_CardSelectables)
            {
                card.gameObject.SetActive(true);
            }
        }
    }
    private IEnumerator DrawCards()
    {
        var heldCards = CardObjects.Where(co => co.Held).Select(co => co.RankText.text + co.SuitText.text);

        if (!IsSolved)
        {
            ModuleLog("Attempting to {0}...", heldCards.Count() > 0 ? ("hold " + heldCards.Join(" ")) : "discard everything");
        }

        int strategyIndex = Enumerable.Range(0, 5).Where(i => CardObjects[i].Held).Sum(i => 1 << i);

        if (!IsSolved && !AcceptablePlays.Contains(strategyIndex))
        {
            ModuleLog("Strike! This isn't an optimal play. Resetting streak to 0.");
            Streak = 0;
            ProgressLightManager.SetValue(0);
            StartCoroutine(BlinkProgressLightsRed());
            BombModule.HandleStrike();
        }
        else
        {
            if (!IsSolved)
            {
                Streak++;
                ProgressLightManager.SetValue(Streak);
                ModuleLog("Yes, that's an optimal play! Current streak: {0}", Streak);
            }

            GameMessageText.Text = "";

            State = KtaneVideoPoker.State.SecondDeal;

            GameInfoButton.Disable();
            SpeedButton.Disable();
            DealButton.Disable();

            for (int i = 0; i < 5; i++)
            {
                if (!CardObjects[i].Held)
                {
                    CardObjects[i].Card = null;
                }
            }

            yield return(new WaitForSeconds(2 * SpeedButton.GetDelay()));

            for (int i = 0; i < 5; i++)
            {
                if (CardObjects[i].Card == null)
                {
                    Audio.PlayGameSoundAtTransform(KMSoundOverride.SoundEffect.TypewriterKey, transform);
                    CardObjects[i].Card = AvailableCards.Pop();
                    yield return(new WaitForSeconds(SpeedButton.GetDelay()));
                }
            }

            if (!IsSolved)
            {
                ModuleLog("Final hand: {0}", CardObjects.Select(co => co.RankText.text + co.SuitText.text).Join(" "));
            }

            // Evaluate the hand
            var hand    = new KtaneVideoPoker.Core.Hand(CardObjects.Select(co => co.Card).Where(c => c.HasValue).Select(c => c.Value));
            var variant = VariantInfo.VariantIfUsingMaxBet(BetAmount == 5);

            var finalHandType = variant.Evaluate(hand);
            int payout        = variant.PayoutForResult(finalHandType) * BetAmount;

            if (finalHandType == KtaneVideoPoker.Core.HandResult.Nothing)
            {
                if (!IsSolved)
                {
                    ModuleLog("Oh well, this hand doesn't pay. Better luck next time!");
                }
                GameMessageText.Text = "PLAY 5 CREDITS";

                StartCoroutine(Payout(0));
            }
            else
            {
                if (!IsSolved)
                {
                    ModuleLog("Hand type {0} with bet {1} pays {2}", finalHandType.ToFriendlyString(), BetAmount, payout);
                }
                GameMessageText.Text = finalHandType.ToFriendlyString();

                if (payout >= JackpotThreshold)
                {
                    JackpotValue     = payout;
                    JackpotCoroutine = StartCoroutine(Jackpot());
                    yield break;
                }
                else
                {
                    StartCoroutine(Payout(payout));
                }
            }
        }
    }
    private IEnumerator BeginDeal()
    {
        // TODO: Disable buttons

        Credits             -= BetAmount;
        CreditsText.Text     = String.Format("CREDIT {0}", Credits);
        BetText.Text         = String.Format("BET {0}", BetAmount);
        GameMessageText.Text = "GOOD LUCK";
        State = KtaneVideoPoker.State.FirstDeal;

        for (int i = 0; i < 5; i++)
        {
            CardObjects[i].Card = null;
            CardObjects[i].Held = false;
        }

        GameInfoButton.Disable();
        SpeedButton.Disable();
        BetOneButton.Disable();
        BetMaxButton.Disable();
        DealButton.Disable();

        yield return(new WaitForSeconds(2 * SpeedButton.GetDelay()));

        int deckSize = KtaneVideoPoker.Core.Util.StandardDeckSize + VariantInfo.VariantIfUsingMaxBet(BetAmount == 5).JokerCount;

        AvailableCards = new Stack <KtaneVideoPoker.Core.Card>(Enumerable.Range(0, deckSize).Select(KtaneVideoPoker.Core.Card.CreateWithId).ToList().Shuffle());

        for (int i = 0; i < 5; i++)
        {
            Audio.PlayGameSoundAtTransform(KMSoundOverride.SoundEffect.TypewriterKey, transform);
            CardObjects[i].Card = AvailableCards.Pop();
            yield return(new WaitForSeconds(SpeedButton.GetDelay()));
        }

        // Log cards
        if (!IsSolved)
        {
            ModuleLog("Dealt cards: {0}", CardObjects.Select(co => co.CardAsLogString()).Join(" "));
        }

        var hand           = new KtaneVideoPoker.Core.Hand(CardObjects.Select(co => co.Card).Where(c => c.HasValue).Select(c => c.Value));
        var strategyResult = VariantInfo.StrategyMaxBet.Evaluate(hand);

        if (BetAmount == 5)
        {
            AcceptablePlays = new HashSet <int>(strategyResult.Strategies);

            if (!IsSolved)
            {
                if (strategyResult.RuleIndex >= 0)
                {
                    ModuleLog("Use rule {0}", strategyResult.RuleIndex + 1);
                    ModuleLog("Applicable special rules: {0}", strategyResult.ExtraRules.Length > 0 ? strategyResult.ExtraRules.Join(", ") : "none");
                }
                else
                {
                    ModuleLog("Applicable exceptions: {0}", strategyResult.ExtraRules.Length > 0 ? strategyResult.ExtraRules.Join(", ") : "none");
                    ModuleLog("No valid rules found");
                }

                ModuleLog("Acceptable plays:");
                foreach (int strategy in strategyResult.Strategies)
                {
                    if (strategy == 0)
                    {
                        ModuleLog("• Discard everything");
                    }
                    else
                    {
                        ModuleLog("• Hold {0}", CardObjects.Where((co, i) => (strategy & (1 << i)) != 0).Select(co => co.RankText.text + co.SuitText.text).Join(" "));
                    }
                }
            }
        }


        State = KtaneVideoPoker.State.ChooseHolds;

        var intermediateHandType = VariantInfo.VariantIfUsingMaxBet(BetAmount == 5).Evaluate(hand);

        if (intermediateHandType == KtaneVideoPoker.Core.HandResult.Nothing)
        {
            GameMessageText.Text = "";
        }
        else
        {
            Audio.PlaySoundAtTransform("gliss1", transform);
            GameMessageText.Text = intermediateHandType.ToFriendlyString();
        }

        GameInfoButton.Text.text = "Game Info";
        GameInfoButton.Enable();

        SpeedButton.Enable();

        DealButton.Text.text = "Draw";
        DealButton.Enable();
    }