高分求“空当接龙”的解牌局算法

时间:2022-04-18 03:28:37
程序已经实现生成牌局,读取实时牌局等前期功能。尚缺破解牌局的算法。后期继续加分
using System;
using System.Collections.Generic;
using System.Text;
using ProcessMemoryReaderLib;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.ComponentModel;

namespace ProcessMemoryReaderLib
{
    // 进程内存读取器
    class ProcessMemoryReader:IDisposable
    {
        // 进程句柄
        IntPtr m_hProcess;
        // 读出指定内存区域
        public byte[] ReadProcessMemory(IntPtr MemoryAddress, uint bytesToRead, out int bytesReaded)
        {
            byte[] buffer = new byte[bytesToRead];
            IntPtr ptrBytesReaded;
            API.ReadProcessMemory(m_hProcess, MemoryAddress, buffer, bytesToRead, out ptrBytesReaded);
            bytesReaded = ptrBytesReaded.ToInt32();
            return buffer;
        }
        // 初始化进程句柄
        public ProcessMemoryReader(string processName, string fileName)
        {
            Process myProcess=null;
            Process[] myProcesses = Process.GetProcessesByName(processName);
            if (myProcesses.Length == 1)
                myProcess = myProcesses[0];
            else if (myProcesses.Length == 0)
            {
                myProcess = new Process();
                myProcess.StartInfo.FileName = fileName;
                try
                {
                    myProcess.Start();
                    myProcess.WaitForInputIdle();
                }
                catch (Win32Exception e)
                {
                    if (e.NativeErrorCode == 2)
                        Console.WriteLine(@"找不到文件{0},请重新安装{1}程序。", fileName,processName);
                    throw e;
                }
            }
            else
                do
                {
                    for (int i = 0; i < myProcesses.Length; i++)
                    {
                        Console.Write("程序{0,2}/{1,-2}:\"{2,20}\".\n您想要这个吗?(Y/N):", i + 1, myProcesses.Length, myProcesses[i].MainWindowTitle);
                        string answer = Console.ReadLine();
                        if (answer.ToUpper() == "Y")
                        {
                            myProcess = myProcesses[i];
                            break;
                        }
                    }
                } while (myProcess == null);
            m_hProcess = API.OpenProcess(0x10U, 1, (uint)myProcess.Id); 
        }
        // 释放句柄
        public void Dispose()
        {
            int iRetValue;
            iRetValue = API.CloseHandle(m_hProcess);
            if (iRetValue == 0)
                throw new Exception("CloseHandle failed");
        }
        // 嵌套类,包装进程内存读取器ProcessMemoryReader所需的API
        static class API
        {
            [DllImport("kernel32.dll")]
            public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);

            [DllImport("kernel32.dll")]
            public static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] buffer, UInt32 size, out IntPtr lpNumberOfBytesRead);

            [DllImport("kernel32.dll")]
            public static extern Int32 CloseHandle(IntPtr hObject);
        }
    }
}

namespace FreecellHelper
{
    // 封装牌局数据和相关方法的类
    class FreeCell
    {
        const int MAXPOS = 21;
        const int MAXCOL = 9;                           // includes top row as column 0
        const int EMPTY = -1;

        int gameNumber;
        int[,] card = new int[MAXCOL, MAXPOS];          // current layout of cards, CARDs are ints

        public FreeCell(int gameNumber)
        {
            if (gameNumber >= 1 && gameNumber <= 1000000)
                Shuffle(gameNumber);
            else
                ReadMemory();
        }
        // 洗牌
        void Shuffle(int gameNumber)
        {
            int wLeft = 52;                             // cards left to be chosen in shuffle
            int[] deck = new int[52];                   // deck of 52 unique cards

            for (int col = 0; col < MAXCOL; col++)      // clear the deck
                for (int pos = 0; pos < MAXPOS; pos++)
                    card[col, pos] = EMPTY;

            /* shuffle cards */
            for (int i = 0; i < 52; i++)                // put unique card in each deck loc.
                deck[i] = i;
            long holdrand = gameNumber;                 // gamenumber is seed for rand()
            for (int i = 0; i < 52; i++)
            {
                // 实际上这句大部分是MFC的rand()源码,在.net中的Random和rand()不一样。
                int j = (int)(((holdrand = holdrand * 214013L + 2531011L) >> 16) & 0x7fff) % wLeft;
                card[(i % 8) + 1, i / 8] = deck[j];
                deck[j] = deck[--wLeft];
            }
            this.gameNumber = gameNumber;
        }
        // 读内存
        void ReadMemory()
        {
            string processName = "FreeCell";
            string fileName = Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\freecell.exe";
            int bytesReaded;
            byte[] memory;
            using (ProcessMemoryReader pReader = new ProcessMemoryReader(processName, fileName))
            {
                memory = pReader.ReadProcessMemory((IntPtr)0x1000000, 0xE000U, out bytesReaded);


                int startIndex = 0x7500;
                for (int col = 0; col < MAXCOL; col++)
                    for (int pos = 0; pos < MAXPOS; pos++, startIndex += 4)
                        card[col, pos] = BitConverter.ToInt32(memory, startIndex);
                this.gameNumber = BitConverter.ToInt32(memory, 0x834C);
            }
        }
        // 用字符串表达当前牌局
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(string.Format("\t\t\t   空当接龙游戏 #{0}\n Free Cell->", gameNumber == 0 ? "尚未开局" : gameNumber.ToString()));
            for (int pos = 0; pos < 8; pos++)
                sb.Append(GetPatternString(card[0, pos]));
            sb.Append("   <-Fixed Card\n\n");
            for (int pos = 0; pos < MAXPOS; pos++)      // display the card
            {
                sb.Append("\t    ");
                bool stop = true;
                for (int col = 1; col < MAXCOL; col++)
                {
                    int theCard = card[col, pos];
                    if (stop && theCard != EMPTY) stop = false;
                    sb.Append(GetPatternString(card[col, pos]));
                }
                if (stop) break;
                sb.Append('\n');
            }
            sb.Append("\r\t       ---------------------------------------------\n");
            return sb.ToString();
        }
        // 根据牌的数值返回字符串形式的样子,ToString()的辅助方法
        string GetPatternString(int theCard)
        {
            string[] value = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };
            string suit = "";
            return theCard == EMPTY ? "      " : string.Format("{0,4}{1,2}", suit[theCard % 4], value[theCard / 4]);
        }
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine(@"
说明:
   输入 0 等微软没有的牌局号可以读内存中的现有牌局。
   如果没开空当接龙,会尝试自动打开空当接龙。
   如果同时开着多个空当接龙程序,会提示选择。

   输入 1 到 1000000 可以按照微软的算法生成牌局。

   直接按回车或输入非 Int32 的值可以退出。");
            while (true)
            {
                Console.Write("请输入您的选择:");
                int result;
                bool stop = !int.TryParse(Console.ReadLine(), out result);
                if (stop) break;
                Console.WriteLine(new FreeCell(result));
            }
        }
    }
}

23 个解决方案

#1


很强大

#2


楼主很猛很厉害

#3


我也很想知道怎么解那个11982

#4


很强很暴力,
偶不知道什么叫“空当接龙”

#5


mark

#6


LZ很厉害,学习了。
逆算法:http://blog.5ilily.cn/article.asp?id=1688

#7


http://bbs.pediy.com/showthread.php?p=390928

真强啊。我是看不懂,你看看去吧。

#8


牛啊,学习了

#9


我对走迷宫算法不太了解,我设想把每种可能的走法都试一遍,找出最优的摆法,但问题是可能转了半天,又回到了起点的牌局。所以要保存每一步的牌局。这样每走一步都要和以前的大量牌局做比对,计算量很大。何况分支太多(每一步都面临很多选择),这种走迷宫的算法似乎不太现实。

想来想去,除了走迷宫别无他法。能把这个做出来也行。

这就需要一个递归调用的方法,参数是当前的牌局数组和记录走法的堆栈,方法会尝试该牌局的任何一步走法,也就是把走动了一步的新牌局作为参数,并在堆栈里记录下这一步,然后递归调用自己。
会有三种情况出现,一是面临可以挪动的牌,这就要比对以往的临时牌局,不要绕回去。一是发现当前牌局一步都挪不动了,即死局。一是方法发现传来的牌局已经是一张空桌子,没有牌了,即胜利了。所以该方法有返回值,是一个或一组走法堆栈,复制了最短的胜利的走法或走法集合。保证了用户最终获得的是一个或一组走法堆栈。

具体实现应该也不难了,只是精力有限,暂时写不出来。

#10


又思考了一下优先级的问题,其实我上述的算法优不优先都一样。不过在剪枝提速的时候,也许要优先一下。
在起初挑选挪动哪张牌时,应该优先挪动可以摆到右上区域的牌,以减少牌的数量,但应该保持右上区域四种花色的数字同步增长,相差不超过1。其次应关注只剩一张牌的列,这张牌一拿走,空出来的地方很有价值。再次应该是左上方四个临时点的牌,争取空出位置来。最后才应该考虑桌面上普通的牌,假如普通牌挪不动,可以考虑强行往右上区域放牌,不理会数字同步的限制。

牌挪到什么地方也有优先级,如果能保证数字同步,应该优先挪到最终右上角,以减少牌的数量。其次能挪到其他牌上,就挪到其他牌上。再次挪到左上区域。再次挪到空着的列里。再次不顾及数字同步挪到右上区域。

然后判断是否出现绕圈子回到起点的重复牌局。这最好是考察堆栈的走法,通过计算来验证。如果把每步的牌局记录并比对,不太现实。

#11


我刚想动手写代码,新的问题就又出现了,真的很复杂,就好像54只老鼠同时在走迷宫一样,判断重复的牌局我没想到更好的办法,只能全保存下来挨个比对,一考虑计算速度和内存容量,我心里就一凉,这代码写不下去了,除非能找到更好的判断重复牌局的方法。

#12


该回复于2008-09-03 09:33:04被版主删除

#13


牛人 学习下

#14


引用 2 楼 maco_wang 的回复:
楼主很猛很厉害

#15


Markxia

#16


这个你搜索一下历史帖子,有个同样的帖子.

#17


很好很强大

#18


学习了!!

#19


好家伙,学习了

#20


继续学习

#21


        void Crack(Stack<int[,]> Cards)
        {

            // 空盘代表胜利
            // 判断右上是否四个K就知道是否胜利

            // 检验是否重复牌局
            // 即和牌局堆栈一一比较
            // 检验通过后
            //      寻找当前牌局可以挪的牌(只剩一张的列、左上、每排最前一张)挪动
            // 检验没通过
            //      证明上一步是导致重复的禁步,应当撤销。

            // 重复牌局、无可挪牌、可挪牌都试过,就会执行到这。
            // 清除牌局堆栈最顶上一层,返回
        }

#22


  检验重复牌局想到了一个提速的方法,可以为整个牌局做一个哈希算法,求得一整数,比较哈希码就可以初步判断出可能重复的牌局,缩小比较范围,如果找到哈希码重复的牌局,再进行一张张牌的仔细比较。
  现在关键是这个哈希算法怎么实现,初步设想是每行(8张牌)求和,再把每行的和与一个素数相乘,最后把这些乘积累加,取和的低32位,做为一整数。不知道这种算法能否站得住脚,数学功底不深,请大家给点意见。
  另外,保存所有牌局,内存可能存不了,有没有算法可以压缩牌局,减小内存的消耗。

#23


-0-!

#1


很强大

#2


楼主很猛很厉害

#3


我也很想知道怎么解那个11982

#4


很强很暴力,
偶不知道什么叫“空当接龙”

#5


mark

#6


LZ很厉害,学习了。
逆算法:http://blog.5ilily.cn/article.asp?id=1688

#7


http://bbs.pediy.com/showthread.php?p=390928

真强啊。我是看不懂,你看看去吧。

#8


牛啊,学习了

#9


我对走迷宫算法不太了解,我设想把每种可能的走法都试一遍,找出最优的摆法,但问题是可能转了半天,又回到了起点的牌局。所以要保存每一步的牌局。这样每走一步都要和以前的大量牌局做比对,计算量很大。何况分支太多(每一步都面临很多选择),这种走迷宫的算法似乎不太现实。

想来想去,除了走迷宫别无他法。能把这个做出来也行。

这就需要一个递归调用的方法,参数是当前的牌局数组和记录走法的堆栈,方法会尝试该牌局的任何一步走法,也就是把走动了一步的新牌局作为参数,并在堆栈里记录下这一步,然后递归调用自己。
会有三种情况出现,一是面临可以挪动的牌,这就要比对以往的临时牌局,不要绕回去。一是发现当前牌局一步都挪不动了,即死局。一是方法发现传来的牌局已经是一张空桌子,没有牌了,即胜利了。所以该方法有返回值,是一个或一组走法堆栈,复制了最短的胜利的走法或走法集合。保证了用户最终获得的是一个或一组走法堆栈。

具体实现应该也不难了,只是精力有限,暂时写不出来。

#10


又思考了一下优先级的问题,其实我上述的算法优不优先都一样。不过在剪枝提速的时候,也许要优先一下。
在起初挑选挪动哪张牌时,应该优先挪动可以摆到右上区域的牌,以减少牌的数量,但应该保持右上区域四种花色的数字同步增长,相差不超过1。其次应关注只剩一张牌的列,这张牌一拿走,空出来的地方很有价值。再次应该是左上方四个临时点的牌,争取空出位置来。最后才应该考虑桌面上普通的牌,假如普通牌挪不动,可以考虑强行往右上区域放牌,不理会数字同步的限制。

牌挪到什么地方也有优先级,如果能保证数字同步,应该优先挪到最终右上角,以减少牌的数量。其次能挪到其他牌上,就挪到其他牌上。再次挪到左上区域。再次挪到空着的列里。再次不顾及数字同步挪到右上区域。

然后判断是否出现绕圈子回到起点的重复牌局。这最好是考察堆栈的走法,通过计算来验证。如果把每步的牌局记录并比对,不太现实。

#11


我刚想动手写代码,新的问题就又出现了,真的很复杂,就好像54只老鼠同时在走迷宫一样,判断重复的牌局我没想到更好的办法,只能全保存下来挨个比对,一考虑计算速度和内存容量,我心里就一凉,这代码写不下去了,除非能找到更好的判断重复牌局的方法。

#12


该回复于2008-09-03 09:33:04被版主删除

#13


牛人 学习下

#14


引用 2 楼 maco_wang 的回复:
楼主很猛很厉害

#15


Markxia

#16


这个你搜索一下历史帖子,有个同样的帖子.

#17


很好很强大

#18


学习了!!

#19


好家伙,学习了

#20


继续学习

#21


        void Crack(Stack<int[,]> Cards)
        {

            // 空盘代表胜利
            // 判断右上是否四个K就知道是否胜利

            // 检验是否重复牌局
            // 即和牌局堆栈一一比较
            // 检验通过后
            //      寻找当前牌局可以挪的牌(只剩一张的列、左上、每排最前一张)挪动
            // 检验没通过
            //      证明上一步是导致重复的禁步,应当撤销。

            // 重复牌局、无可挪牌、可挪牌都试过,就会执行到这。
            // 清除牌局堆栈最顶上一层,返回
        }

#22


  检验重复牌局想到了一个提速的方法,可以为整个牌局做一个哈希算法,求得一整数,比较哈希码就可以初步判断出可能重复的牌局,缩小比较范围,如果找到哈希码重复的牌局,再进行一张张牌的仔细比较。
  现在关键是这个哈希算法怎么实现,初步设想是每行(8张牌)求和,再把每行的和与一个素数相乘,最后把这些乘积累加,取和的低32位,做为一整数。不知道这种算法能否站得住脚,数学功底不深,请大家给点意见。
  另外,保存所有牌局,内存可能存不了,有没有算法可以压缩牌局,减小内存的消耗。

#23


-0-!