Beispiel #1
0
 public PositionInfo(PretzelPosition position, LinkedListNode <PositionInfo> parent, short score)
 {
     Position      = position;
     ParentIndexes = new List <LinkedListNode <PositionInfo> > {
         parent
     };
     Score = score;
 }
 public PretzelPosition(PretzelPosition previousPosition)
 {
     Tableau = new ushort[previousPosition.Tableau.Length];
     Array.Copy(previousPosition.Tableau, Tableau, previousPosition.Tableau.Length);
     HoleIndices = new short[previousPosition.HoleIndices.Length];
     Array.Copy(previousPosition.HoleIndices, HoleIndices, previousPosition.HoleIndices.Length);
     SuitCount  = previousPosition.SuitCount;
     ValueCount = previousPosition.ValueCount;
 }
        public List <PretzelPosition> GetSubsequentPositions()
        {
            List <PretzelPosition> newPositions = new List <PretzelPosition>();

            for (short i = 0; i < SuitCount; ++i)
            {
                // find card that fits this hole
                ushort cardNumberThatFitsHole = NoCard;
                if (HoleIndices[i] % ValueCount == 0)
                {
                    // hole is in first column, so the 2 of row's suit fits hole
                    cardNumberThatFitsHole = (ushort)(HoleIndices[i] + 1);
                }
                else if (Tableau[HoleIndices[i] - 1] == NoCard)
                {
                    // hole follows another hole
                    cardNumberThatFitsHole = NoCard; // NOTE: redundant
                }
                else if (Tableau[HoleIndices[i] - 1] % ValueCount < (ValueCount - 1))
                {
                    // card before hole is not King, so next sequential card fits hole
                    // NOTE: In most versions of the game, the conditional for this else is extraneous
                    //       since the next card number after a King is an Ace and all Aces were
                    //       pulled out of the deck after the shuffle.  Left in for future variants.
                    cardNumberThatFitsHole = (ushort)(Tableau[HoleIndices[i] - 1] + 1);
                }
                // if possible, create position resulting from moving card into hole
                if (cardNumberThatFitsHole != NoCard)
                {
                    PretzelPosition newPosition             = new PretzelPosition(this);
                    short           indexOfCardThatFitsHole = (short)Array.IndexOf(newPosition.Tableau, cardNumberThatFitsHole);
                    newPosition.Tableau[HoleIndices[i]]          = newPosition.Tableau[indexOfCardThatFitsHole];
                    newPosition.Tableau[indexOfCardThatFitsHole] = NoCard;
                    newPosition.HoleIndices[i] = indexOfCardThatFitsHole;
                    bool isAntiGoalMove = cardNumberThatFitsHole == indexOfCardThatFitsHole + 1;
                    newPositions.Add(newPosition);
                }
            }
            return(newPositions);
        }
        public void RunTrials()
        {
            Console.WriteLine("Pretzel Solitaire Solver");
            string resultsHeader = "Suits: " + SuitCount.ToString();

            resultsHeader += ", Values: " + ValueCount.ToString();
            resultsHeader += ", Deal: " + Deal.ToString();
            resultsHeader += ", Approach: " + Approach.ToString();
            resultsHeader += ", Ouput: " + Output.ToString();
            Console.WriteLine(resultsHeader);

            string summaryHeader = "\nTrial \tIters \tSolved \tMvTot \tMvMean \tDuel2s \tDCrabs";

            if (Approach == ApproachType.FullTree)
            {
                summaryHeader += " \tNoFail \tDeadends_for_Solvable";
            }
            if (Output != OutputType.Verbose)
            {
                Console.WriteLine(summaryHeader);
            }

            CardDeck deck                             = new CardDeck(SuitCount, ValueCount);
            DateTime solveStartTime                   = DateTime.UtcNow;
            string   moveCounts                       = string.Empty;
            ushort   totalSolvedPretzels              = 0;
            ulong    totalMoves                       = 0;
            ushort   totalDuellingDeuces              = 0;
            ushort   totalDuckingCrabs                = 0;
            ushort   totalUnlosablePretzels           = 0;
            ulong    totalDeadendsForSolvablePretzels = 0;

            for (short t = 0; t < Trials; ++t)
            {
                ushort trialSolvedPretzels              = 0;
                ulong  trialMoves                       = 0;
                ushort trialDuellingDeuces              = 0;
                ushort trialDuckingCrabs                = 0;
                ushort trialUnlosablePretzels           = 0;
                ulong  trailDeadendsForSolvablePretzels = 0;
                for (short e = 0; e < Experiments; ++e)
                {
                    if (Output == OutputType.Verbose)
                    {
                        Console.WriteLine("\nTrial " + t.ToString() + " Experiment " + e.ToString());
                    }

                    deck.Shuffle(Deal);

                    PretzelPosition position = new PretzelPosition(deck);

                    if (Output == OutputType.Verbose)
                    {
                        Console.WriteLine(position.ToString());
                    }

                    SolveResults results = Solve(position, Approach, Output);

                    if (results.Solvable)
                    {
                        ++trialSolvedPretzels;
                        trialMoves += results.Moves;
                        if (Output == OutputType.SummaryWithMoveCounts)
                        {
                            moveCounts += results.Moves.ToString() + ", ";
                        }
                        trailDeadendsForSolvablePretzels += results.Deadends;
                        if (results.Deadends == 0)
                        {
                            ++trialUnlosablePretzels;
                        }
                    }
                    else
                    {
                        if (results.HasDuellingDeuces)
                        {
                            ++trialDuellingDeuces;
                            if (Output == OutputType.Verbose)
                            {
                                Console.WriteLine("Duelling Deuces Detected");
                            }
                        }
                        if (results.HasDuckingCrab)
                        {
                            ++trialDuckingCrabs;
                            if (Output == OutputType.Verbose)
                            {
                                Console.WriteLine("Ducking Crab Detected");
                            }
                        }
                    }
                }
                totalSolvedPretzels              += trialSolvedPretzels;
                totalMoves                       += trialMoves;
                totalDuellingDeuces              += trialDuellingDeuces;
                totalDuckingCrabs                += trialDuckingCrabs;
                totalUnlosablePretzels           += trialUnlosablePretzels;
                totalDeadendsForSolvablePretzels += trailDeadendsForSolvablePretzels;
                // output trial results
                if (Output == OutputType.Verbose)
                {
                    Console.WriteLine(summaryHeader);
                }
                string trialResults = t.ToString();
                trialResults += "\t" + Experiments.ToString();
                trialResults += "\t" + trialSolvedPretzels.ToString();
                trialResults += "\t" + trialMoves.ToString();
                trialResults += "\t" + ((double)trialMoves / (double)trialSolvedPretzels).ToString("F");
                trialResults += "\t" + trialDuellingDeuces.ToString();
                trialResults += "\t" + trialDuckingCrabs.ToString();
                if (Approach == ApproachType.FullTree)
                {
                    trialResults += "\t" + trialUnlosablePretzels.ToString();
                    trialResults += "\t" + trailDeadendsForSolvablePretzels.ToString();
                }
                Console.WriteLine(trialResults);
                if (Output == OutputType.SummaryWithMoveCounts)
                {
                    moveCounts += "\n\n";
                }
            }
            // output total results
            DateTime solveEndTime = DateTime.UtcNow;

            Console.WriteLine("");
            string totalResults = "TOTAL: ";

            totalResults += "\t" + (Trials * Experiments).ToString();
            totalResults += "\t" + totalSolvedPretzels.ToString();
            totalResults += "\t" + totalMoves.ToString();
            totalResults += "\t" + ((double)totalMoves / (double)totalSolvedPretzels).ToString("F");
            totalResults += "\t" + totalDuellingDeuces.ToString();
            totalResults += "\t" + totalDuckingCrabs.ToString();
            if (Approach == ApproachType.FullTree)
            {
                totalResults += "\t" + totalUnlosablePretzels.ToString();
                totalResults += "\t" + totalDeadendsForSolvablePretzels.ToString();
            }
            Console.WriteLine(totalResults);
            if (Output == OutputType.SummaryWithMoveCounts)
            {
                Console.WriteLine("\nMove Counts for All Solved Pretzels, Grouped by Trial");
                Console.WriteLine(moveCounts);
            }
            Console.WriteLine("");
            Console.WriteLine((solveEndTime - solveStartTime).ToString() + " Elapsed");
            Console.Write((char)7); // play bell
        }
        // WARNING: stores all attained positions in memory in two shapes, and can cause memory issues on some platforms
        public SolveResults Solve(PretzelPosition position, ApproachType approach, OutputType output)
        {
            bool hasDuellingDeuces = position.HasDuelingDeuces();
            bool hasDuckingCrab    = position.HasDuckingCrab();

            if (hasDuellingDeuces || hasDuckingCrab)
            {
                return(new SolveResults {
                    Solvable = false, Moves = 0, Deadends = 0, HasDuellingDeuces = hasDuellingDeuces, HasDuckingCrab = hasDuckingCrab
                });
            }
            else
            {
                LinkedList <PositionInfo>   attainablePositionList = new LinkedList <PositionInfo>();   // ordered doubly-linked list of positions known to be attainable (explored and not yet explored)
                Dictionary <ushort, object> attainedPositions      = new Dictionary <ushort, object>(); // trie of all positions attained (explored)
                                                                                                        // add initial position to list
                attainablePositionList.AddFirst(new PositionInfo(position, null, position.CalculateScore()));
                // add initial position to trie
                int lastTableauIndex = position.Tableau.Length - 1;
                Dictionary <ushort, object> childTrieNode = new Dictionary <ushort, object> {
                    { position.Tableau[lastTableauIndex], 0 }
                };
                for (int i = lastTableauIndex - 1; i >= 0; --i)
                {
                    Dictionary <ushort, object> trieNode = new Dictionary <ushort, object> {
                        { position.Tableau[i], childTrieNode }
                    };
                    childTrieNode = trieNode;
                }
                LinkedListNode <PositionInfo>         solutionListNode = null;
                List <LinkedListNode <PositionInfo> > deadendListNodes = new List <LinkedListNode <PositionInfo> >();
                LinkedListNode <PositionInfo>         currentListNode  = attainablePositionList.First;
                while (((solutionListNode == null) || (approach == ApproachType.FullTree)) && (currentListNode != null))
                {
                    if (currentListNode.Value.Position.IsSolved())
                    {
                        solutionListNode = currentListNode;
                    }
                    else
                    {
                        List <PretzelPosition> subsequentPositions = currentListNode.Value.Position.GetSubsequentPositions();
                        if (subsequentPositions.Count > 0)
                        {
                            if (approach == ApproachType.RandomPlay)
                            {
                                int randomPlayIndex = (short)rng.Next(0, subsequentPositions.Count);
                                attainablePositionList.AddLast(new PositionInfo(subsequentPositions[randomPlayIndex], currentListNode, subsequentPositions[randomPlayIndex].CalculateScore()));
                            }
                            else if (approach == ApproachType.ScoredLookahead)
                            {
                                short highestScore      = subsequentPositions[0].CalculateScore();
                                int   highestScoreIndex = 0;
                                for (int i = 1; i < subsequentPositions.Count; ++i)
                                {
                                    short positionScore = subsequentPositions[i].CalculateScore();
                                    if (positionScore > highestScore)
                                    {
                                        highestScore      = positionScore;
                                        highestScoreIndex = i;
                                    }
                                }
                                attainablePositionList.AddLast(new PositionInfo(subsequentPositions[highestScoreIndex], currentListNode, highestScore));
                            }
                            else
                            {
                                for (short i = 0; i < subsequentPositions.Count; ++i)
                                {
                                    // check if position has already been attained
                                    bool positionPreviouslyAttained = true;
                                    LinkedListNode <PositionInfo> attainablePositionListNode = null;
                                    ushort currentTableauIndex = 0;
                                    Dictionary <ushort, object> currentTrieNode = attainedPositions;
                                    object childNodeObject = null;
                                    while (positionPreviouslyAttained && (currentTableauIndex <= lastTableauIndex))
                                    {
                                        if (currentTrieNode.TryGetValue((ushort)subsequentPositions[i].Tableau[currentTableauIndex], out childNodeObject))
                                        {
                                            if (currentTableauIndex < lastTableauIndex)
                                            {
                                                currentTrieNode = (Dictionary <ushort, object>)childNodeObject;
                                            }
                                            else
                                            {
                                                attainablePositionListNode = (LinkedListNode <PositionInfo>)childNodeObject; // unwrap pointer to position in attainablePositionList from position's last grid position node in trie
                                            }
                                            ++currentTableauIndex;
                                        }
                                        else
                                        {
                                            positionPreviouslyAttained = false;
                                            // add remainder of position to trie, starting at the last grid position and chaining forward to divergent node
                                            // NOTE: last grid position in trie contains wrapped reference to this position within attainablePositionList
                                            childTrieNode = new Dictionary <ushort, object> {
                                                { subsequentPositions[i].Tableau[lastTableauIndex], currentListNode }
                                            };
                                            for (int j = lastTableauIndex - 1; j > currentTableauIndex; --j)
                                            {
                                                Dictionary <ushort, object> newTrieNode = new Dictionary <ushort, object> {
                                                    { subsequentPositions[i].Tableau[j], childTrieNode }
                                                };
                                                childTrieNode = newTrieNode;
                                            }
                                            currentTrieNode.Add((ushort)subsequentPositions[i].Tableau[currentTableauIndex], childTrieNode);
                                        }
                                    }
                                    // if new position attained, queue it at the end of attainable position list
                                    if (!positionPreviouslyAttained)
                                    {
                                        attainablePositionList.AddLast(new PositionInfo(subsequentPositions[i], currentListNode, subsequentPositions[i].CalculateScore()));
                                    }
                                    else if (approach == ApproachType.FullTree)
                                    {
                                        // position already reached; add new path to it if analyzing full decision tree
                                        attainablePositionListNode.Value.ParentIndexes.Add(currentListNode);
                                    }
                                }
                            }
                        }
                        else
                        {
                            // deadend
                            deadendListNodes.Add(currentListNode);
                        }
                    }
                    currentListNode = currentListNode.Next;
                }
                // output and return results
                if (output == OutputType.Verbose)
                {
                    Console.WriteLine(attainablePositionList.Count + " Attainable Positions Explored");
                    if (approach == ApproachType.FullTree)
                    {
                        Console.WriteLine(deadendListNodes.Count.ToString() + " Dead Ends");
                    }
                }
                if (solutionListNode != null)
                {
                    if (output == OutputType.Verbose)
                    {
                        Console.WriteLine("Solution (read last line to first):");
                    }
                    ushort solutionMoveCount = 0;
                    while (solutionListNode != null)
                    {
                        string scoreText = string.Empty;
                        if (approach == ApproachType.ScoredLookahead)
                        {
                            scoreText = " | Score = " + solutionListNode.Value.Score.ToString();
                        }
                        if (output == OutputType.Verbose)
                        {
                            Console.WriteLine(solutionListNode.Value.Position.ToString() + scoreText);
                        }
                        solutionListNode = solutionListNode.Value.ParentIndexes[0];
                        ++solutionMoveCount;
                    }
                    --solutionMoveCount; // do not count starting position
                    if (output == OutputType.Verbose)
                    {
                        Console.WriteLine(solutionMoveCount.ToString() + " Moves");
                    }
                    return(new SolveResults {
                        Solvable = true, Moves = solutionMoveCount, Deadends = (ushort)deadendListNodes.Count
                    });
                }
                else
                {
                    if (output == OutputType.Verbose)
                    {
                        Console.WriteLine("No Solution Found");
                    }
                    return(new SolveResults {
                        Solvable = false, Moves = 0, Deadends = (ushort)deadendListNodes.Count
                    });
                }
            }
        }