Click here to Skip to main content
15,892,005 members
Articles / Programming Languages / C# 4.0

Finding the solution to the game of Solitaire

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
27 Sep 2012CPOL2 min read 12.5K   482   6  
Using recursive programming to solve the puzzle
//********************************************************************************************************
//
// This class belongs to the Solitare system
// It holds all the groundwork for analysing the board and finding the solution to the game
//
// ------------------------------------------------------
// Version    : 1.0.0 September 2012 - original version
// Programmer : Morten Skaarup
// Language   : c#, .Net Framework 4.0
// ------------------------------------------------------
//********************************************************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing ;

namespace Solitaire
{
    public class SolitaireClass
    {
        #region --- Enumerators ---

        public enum Cell
        {
            Void = -1,
            Empty = 0,
            Full = 1,
        }

        public enum Direction
        {
            Up = 0,
            Down = 1,
            Left = 2,
            Right = 3
        }

        #endregion --- Enumerators ---

        public static Cell[,] Board;
        public static int movecount = 0; // Move counter - ensures unique ID
        public static List<SolitaireMove> Moves = new List<SolitaireMove>(); // Complete list of moves
        public static List<int> Path = new List<int>(); // The current sequence of moves
        public static List<string> History = new List<string>(); // Text based history of moves and findings
        public static List<int> PerformedMoves = new List<int>(); // List of actually performed moves (the ID)
        public static Int16 Level = 0; // The current level. 31 means only one pin left
        public static List<int> Best = new List<int>(); // The best sequence so far
        public static Int16 BestLevel = 0; // The current best level
        private static int _maxcount = 5000000; // The limitator of moves

        #region --- Delegates ---

        public delegate void ReportHistory(string str);
        private static ReportHistory _history;
        public static void OnCurrent(ReportHistory cur)
        {
            _history = cur;
        }

        public delegate void ReportCount(int count);
        private static ReportCount _count;
        public static void OnCount(ReportCount cnt)
        {
            _count = cnt;
        }

        #endregion --- Delelgates ---

        /// <summary>
        /// Initialize the board to the default
        /// </summary>
        public static void Init()
        {
            Board = new Cell[7, 7];
            for (int i = 0; i <= 6; i++)
            {
                for (int j = 0; j <= 6; j++)
                {
                    Board[i, j] = Cell.Full;
                }
            }
            Board[0, 0] = Cell.Void;
            Board[0, 1] = Cell.Void;
            Board[0, 5] = Cell.Void;
            Board[0, 6] = Cell.Void;
            Board[1, 0] = Cell.Void;
            Board[1, 1] = Cell.Void;
            Board[1, 5] = Cell.Void;
            Board[1, 6] = Cell.Void;
            Board[5, 0] = Cell.Void;
            Board[5, 1] = Cell.Void;
            Board[5, 5] = Cell.Void;
            Board[5, 6] = Cell.Void;
            Board[6, 0] = Cell.Void;
            Board[6, 1] = Cell.Void;
            Board[6, 5] = Cell.Void;
            Board[6, 6] = Cell.Void;

            Board[3, 3] = Cell.Empty;
        }

        /// <summary>
        /// Reset all the variables before the analysis
        /// </summary>
        public static void Reset()
        {
            movecount = 0;
            Moves = new List<SolitaireMove>();
            Path = new List<int>();
            History = new List<string>();
            PerformedMoves = new List<int>();
            Level = 0;            
            Best = new List<int>();
            BestLevel = 0;
        }

        /// <summary>
        /// Find the possible moves on the board in the current state
        /// </summary>
        /// <param name="parent">The link to the parent move</param>
        /// <returns>The list of possible moves - could be empty</returns>
        private static List<SolitaireMove> Analyze(int parent)
        {
            List<SolitaireMove> temp = new List<SolitaireMove>();

            for (int i = 0; i <= 6; i++)
            {
                for (int j = 0; j <= 6; j++)
                {
                    if (Board[i, j] == Cell.Full)
                    {
                        //up
                        if (i >= 2)
                        {
                            if ((Board[i - 1, j] == Cell.Full) && (Board[i - 2, j] == Cell.Empty))
                            {
                                temp.Add(new SolitaireMove(movecount, parent, new Point(i, j), new Point(i - 2, j), Direction.Up));
                                movecount++;
                            }
                        }

                        // down
                        if (i <= 4)
                        {
                            if ((Board[i + 1, j] == Cell.Full) && (Board[i + 2, j] == Cell.Empty))
                            {
                                temp.Add(new SolitaireMove(movecount, parent, new Point(i, j), new Point(i + 2, j), Direction.Down));
                                movecount++;
                            }
                        }

                        //right
                        if (j <= 4)
                        {
                            if ((Board[i, j + 1] == Cell.Full) && (Board[i, j + 2] == Cell.Empty))
                            {
                                temp.Add(new SolitaireMove(movecount, parent, new Point(i, j), new Point(i, j + 2), Direction.Right));
                                movecount++;
                            }

                        }

                        //left
                        if (j >= 2)
                        {
                            if ((Board[i, j - 1] == Cell.Full) && (Board[i, j - 2] == Cell.Empty))
                            {
                                temp.Add(new SolitaireMove(movecount, parent, new Point(i, j), new Point(i, j - 2), Direction.Left));
                                movecount++;
                            }
                        }
                    }
                }
            }

            if (_history !=null)
                _history("To Parent " + parent.ToString() + " - added " + temp.Count.ToString() + " nodes");
            
            string s = "    ";
            foreach (SolitaireMove sm in temp)
            {
                s += sm.Id.ToString() + ": (" + sm.From.X.ToString() + "," + sm.From.Y.ToString() + ") -> (" + sm.To.X.ToString() + "," + sm.To.Y.ToString() + "), ";
            }
            if (_history !=null)
                _history(s.Substring(0, s.Length - 2));

            if (_count != null)
                _count(movecount);

            return temp;
        }

        /// <summary>
        /// Adjust the board, performing a given move
        /// </summary>
        /// <param name="sm">The move to perform</param>
        public static void DoMove(SolitaireMove sm)
        {
            Board[sm.From.X, sm.From.Y] = Cell.Empty;
            Board[sm.To.X, sm.To.Y] = Cell.Full;


            switch (sm.MoveDirection)
            {
                case Direction.Up: { Board[sm.From.X - 1, sm.From.Y] = Cell.Empty; break; }
                case Direction.Down: { Board[sm.From.X + 1, sm.From.Y] = Cell.Empty; break; }
                case Direction.Left: { Board[sm.From.X, sm.From.Y - 1] = Cell.Empty; break; }
                case Direction.Right: { Board[sm.From.X, sm.From.Y + 1] = Cell.Empty; break; }
                default: break;
            }
            if (_history !=null)
                _history("For Parent: " + sm.Parent.ToString() + " - Did move " + sm.ToString());

        }

        /// <summary>
        /// Adjust the board, performing a reverse move
        /// </summary>
        /// <param name="sm">The move to reverse</param>
        private static void UnDoMove(SolitaireMove sm)
        {
            Board[sm.From.X, sm.From.Y] = Cell.Full;
            Board[sm.To.X, sm.To.Y] = Cell.Empty;


            switch (sm.MoveDirection)
            {
                case Direction.Up: { Board[sm.From.X - 1, sm.From.Y] = Cell.Full; break; }
                case Direction.Down: { Board[sm.From.X + 1, sm.From.Y] = Cell.Full; break; }
                case Direction.Left: { Board[sm.From.X, sm.From.Y - 1] = Cell.Full; break; }
                case Direction.Right: { Board[sm.From.X, sm.From.Y + 1] = Cell.Full; break; }
                default: break;
            }
        }

        /// <summary>
        /// Start the analysis of the board
        /// </summary>
        /// <param name="max">The maximum number of moves</param>
        public static void Run(int max)
        {
            _maxcount = max;
            SolitaireMove root = new SolitaireMove();
            root.Moves = Analyze(-1);

            Moves.AddRange(root.Moves);
            GoDeeper(root);

        }

        /// <summary>
        /// Recursively iterate through the possible moves
        /// </summary>
        /// <param name="mn">The move to go deeper with</param>
        private static void GoDeeper(SolitaireMove mn)
        {
            if (_history !=null)
                _history("Digging into: "+mn.Id.ToString());

            if ((mn.Moves.Count > 0) && (movecount <= _maxcount))
            {

                foreach (SolitaireMove node in mn.Moves)
                {
                    if (_history != null)
                        _history("Examining " + node.Id.ToString());

                    if (node.Used == false)
                    {
                        Path.Add(node.Id);
                        PerformedMoves.Add(node.Id);
                        DoMove(node);

                        Level++;
                        if (Level > BestLevel)
                        {
                            Best.Clear();
                            foreach (int m in Path)
                            {
                                Best.Add(m);
                            }
                            BestLevel = Level;
                        }

                        node.Used = true;
                        node.Moves = Analyze(node.Id);

                        if (node.Moves.Count > 0)
                        {
                            Moves.AddRange(node.Moves);
                            GoDeeper(node);
                        }
                        else
                        {
                            if (Level <= 31)
                            {
                                UnDoMove(node);
                                Level--;
                                if (_history != null)
                                    _history(node.Reverse() + " - Level now=" + Level.ToString());

                                Path.RemoveAt(Path.Count - 1);
                                PerformedMoves.Add(-node.Id);

                            }
                            else
                                break;
                        }
                    }
                    else
                        if (_history != null)
                            _history(node.Id.ToString() + " already used");


                    if (movecount >= _maxcount)
                        break;
                }

                if ((Level <= 31) && (Level > 0) && (movecount < _maxcount))
                {
                    UnDoMove(mn);
                    Level--;
                    if (_history != null)
                        _history(mn.Reverse() + " - Level now=" + Level.ToString() + " *");

                    Path.RemoveAt(Path.Count - 1);
                    PerformedMoves.Add(-mn.Id);
                }
            }
        }
    }

    
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Denmark Denmark
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions