Подсчет количества способов из двух 6-карточных рук и кроя, эффективно - PullRequest
0 голосов
/ 04 августа 2020

Я хотел бы провести исчерпывающий анализ правильной игры для Криббиджа, у которого есть две руки из шести карт и сокращенная карта. https://en.wikipedia.org/wiki/Cribbage

В Криббидже в конце игры масти могут стать совершенно неактуальными.

Ключевые особенности криббиджа:

  1. Шесть карт раздаются каждому игроку, который сбрасывает 2, то есть 6C4 = 15 возможных сбросов на игрока.
  2. Затем сокращенная карта.
  3. 4! 4! = 576 возможных способов (некоторые способы могут быть незаконными для определенных рук) для двух игроков разыграть четыре сохраненные таким образом карты во вспомогательной игре, называемой «привязка»
  4. Решения дилера и недилера не совпадают. все симметрично в том, что сброс недилера формирует вторую руку дилера, так что дилер хочет сбросить там хорошие карты, а недилер хочет сбросить плохие карты. Кроме того, структура игры с привязкой такова, что разные карты будут хороши для дилера и не дилера. дилеру соответственно, чтобы вернуть лучшую комбинацию из четырех карт, которую следует держать, при заданном счете (игра до 121, поэтому игра будет отличаться при разных счетах, учитывая, что это пошаговая игра, и первая, набравшая 121, забирает все ). Затем из каждой руки с четырьмя картами для трех известных мертвых карт (вырезка и наши сбросы), лучший ход, вторая [согласно данному ответу], третья, четвертая и т.д.

    Я не совершенно уверен, как структурировать эту проблему, но мы видим, что с 52C6 * 46C6 * 40 = 7,63 * 10 ^ 15 возможных начальных позиций, а затем 6C4 * 6C4 * 4! 4! = 129 600 возможных путей решения для каждого, которые могут стать слишком большой проблемой для решения.

    Для упрощения я рассматриваю случай, когда масти не имеют значения. Здесь их не больше 18! / 12! / 6! = 18 564 руки для первого игрока, а не 52! / 6! / 46! = 20 358 520, если подходят масти, или меньшее число, если мы попытаемся сравнить эквивалентные руки, похожие на Генерация всех 5-карточных покерных рук .

    Алгоритм, который я придумал, заключается в повторении ряды карт, с повторением, для шестикарточной, шестикарточной и затем сокращенной карты. Это генерирует уникальные паттерны, однако не исключает таких последовательностей 10,10,10,10,10,10,10,10, которые невозможны, поскольку в колоде всего четыре карты каждого ранга.

    Это не так сложно исправить, поскольку обычно существует 4C4 = 1 способа выбрать все четыре карты данного ранга, 4C3 = 4, 4C2 = 6 и 4C1 = 4 способа выбрать 3, 2, 1, карты разной масти. Это означает, что, например, 13 карт 0,1,2,3,4,5,6,7,8,9,10,11,12 могут быть сформированы 4 ^ 13 способами. В любом случае, когда имеется более 4 игроков данного ранга, то есть 0 способов добиться этого, и нам не нужно оценивать выбор игрока для этого набора карт.

    Я написал несколько код, который доходит до подсчета способов достижения каждого набора, но он очень медленный и тратит более 90% своего времени на расчет весов.

    Сначала я попробовал Linq groupBy, который был ужасно медленным, поэтому Я переключился на словарь, а теперь на массив.

    Однако что-то все же кажется неправильным в том, что на подсчет уходит очень много времени.

        readonly byte[] ranks = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    
        // main method, pass any parameter as they're not implemented yet
        bool PlayCribbage(byte poneScore, byte dealerScore)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            var dealerHands = GetCombinations<byte>(ranks, 6);
            long totalCombos = 0;
            long validCombos = 0;
            foreach (var dealerHand in dealerHands)
            {
                var playerHands = GetCombinations<byte>(ranks, 6);
                foreach (var playerHand in playerHands)
                {
                    foreach (var cut in ranks)
                    {
                        totalCombos++;
                        long weight = this.CountWeight(dealerHand, playerHand, cut);
                        if (weight > 0) validCombos++;
    
                    }
                }
            }
            sw.Stop();
            MessageBox.Show(sw.Elapsed.TotalSeconds.ToString());
            return true;
    
        }
    
        // this is where the program is spending all its time
        long CountWeight(IEnumerable<byte> dealer, IEnumerable<byte> player, byte cut)
        {
            long weight = 1;
            byte[] counts = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            // the program spends most of its time iterating over these next two lines of code
            foreach (byte card in dealer) counts[card]++;
            foreach (byte card in player) counts[card]++;
            counts[cut]++;
            foreach (byte count in counts)
            {
                switch (count)
                {
                    case 1:
                        weight *= 4;
                        break;
                    case 2:
                        weight *= 6;
                        break;
                    case 3:
                        weight *= 4;
                        break;
                    case 4:
                    case 0:
                        break;
                    default:
                        return 0;
                }
            }
            return weight;
        }
    
        IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> items, int count)
        {
            int i = 0;
            foreach (var item in items)
            {
                if (count == 1)
                    yield return new T[] { item };
                else
                {
                    foreach (var result in GetCombinations(items.Skip(i), count - 1))
                        yield return new T[] { item }.Concat(result);
                }
                ++i;
            }
        }
    

    Возможно, я как-то лает не на то дерево. Или, может быть, я просто ожидаю, что что-то произойдет быстрее, чем на однопоточном i5-7300U, учитывая 18 564 внешних l oop элементов, 18 564 средних l oop элементов, 13 внутренних l oop элементов, а затем повторение через IEnumerable.

    Что-то не так с моим подходом или с использованием IEnumerable здесь?

    Я могу продолжить как есть, но я еще не сделал какую-либо полезную работу и это уже занимает слишком много времени (час!).

    Изменить: кажется, что-то не так с IEnumerable или, по крайней мере, с тем, как это разрешено в al oop здесь. Я добавил . ToArray () вызывает внутри метода dellierHand / playerHand foreach в файле diverHands / playerHands, и это делает его намного быстрее. Кажется, что из-за того, что перечислитель происходит много раз, и итерация через IEnumerable медленнее, чем byte [] или даже List

...