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

Q_282898034 2008-04-17 02:35:35
程序已经实现生成牌局,读取实时牌局等前期功能。尚缺破解牌局的算法。后期继续加分
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));
}
}
}
}
...全文
1087 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
Q_282898034 2008-04-26
  • 打赏
  • 举报
回复
-0-!
Q_282898034 2008-04-19
  • 打赏
  • 举报
回复
  检验重复牌局想到了一个提速的方法,可以为整个牌局做一个哈希算法,求得一整数,比较哈希码就可以初步判断出可能重复的牌局,缩小比较范围,如果找到哈希码重复的牌局,再进行一张张牌的仔细比较。
  现在关键是这个哈希算法怎么实现,初步设想是每行(8张牌)求和,再把每行的和与一个素数相乘,最后把这些乘积累加,取和的低32位,做为一整数。不知道这种算法能否站得住脚,数学功底不深,请大家给点意见。
  另外,保存所有牌局,内存可能存不了,有没有算法可以压缩牌局,减小内存的消耗。
rangeon 2008-04-18
  • 打赏
  • 举报
回复
好家伙,学习了
Q_282898034 2008-04-18
  • 打赏
  • 举报
回复
        void Crack(Stack<int[,]> Cards)
{

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

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

// 重复牌局、无可挪牌、可挪牌都试过,就会执行到这。
// 清除牌局堆栈最顶上一层,返回
}
yagebu1983 2008-04-18
  • 打赏
  • 举报
回复
学习了!!
changjiangzhibin 2008-04-18
  • 打赏
  • 举报
回复
很好很强大
grearo 2008-04-18
  • 打赏
  • 举报
回复
继续学习
  • 打赏
  • 举报
回复
这个你搜索一下历史帖子,有个同样的帖子.
nik_Amis 2008-04-17
  • 打赏
  • 举报
回复
Markxia
我是风 2008-04-17
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 maco_wang 的回复:]
楼主很猛很厉害
[/Quote]
chutou 2008-04-17
  • 打赏
  • 举报
回复
牛人 学习下
Q_282898034 2008-04-17
  • 打赏
  • 举报
回复
我刚想动手写代码,新的问题就又出现了,真的很复杂,就好像54只老鼠同时在走迷宫一样,判断重复的牌局我没想到更好的办法,只能全保存下来挨个比对,一考虑计算速度和内存容量,我心里就一凉,这代码写不下去了,除非能找到更好的判断重复牌局的方法。
Q_282898034 2008-04-17
  • 打赏
  • 举报
回复
又思考了一下优先级的问题,其实我上述的算法优不优先都一样。不过在剪枝提速的时候,也许要优先一下。
在起初挑选挪动哪张牌时,应该优先挪动可以摆到右上区域的牌,以减少牌的数量,但应该保持右上区域四种花色的数字同步增长,相差不超过1。其次应关注只剩一张牌的列,这张牌一拿走,空出来的地方很有价值。再次应该是左上方四个临时点的牌,争取空出位置来。最后才应该考虑桌面上普通的牌,假如普通牌挪不动,可以考虑强行往右上区域放牌,不理会数字同步的限制。

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

然后判断是否出现绕圈子回到起点的重复牌局。这最好是考察堆栈的走法,通过计算来验证。如果把每步的牌局记录并比对,不太现实。
Q_282898034 2008-04-17
  • 打赏
  • 举报
回复
我对走迷宫算法不太了解,我设想把每种可能的走法都试一遍,找出最优的摆法,但问题是可能转了半天,又回到了起点的牌局。所以要保存每一步的牌局。这样每走一步都要和以前的大量牌局做比对,计算量很大。何况分支太多(每一步都面临很多选择),这种走迷宫的算法似乎不太现实。

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

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

具体实现应该也不难了,只是精力有限,暂时写不出来。
  • 打赏
  • 举报
回复
牛啊,学习了
zccmy22 2008-04-17
  • 打赏
  • 举报
回复
http://bbs.pediy.com/showthread.php?p=390928

真强啊。我是看不懂,你看看去吧。
CathySun118 2008-04-17
  • 打赏
  • 举报
回复
LZ很厉害,学习了。
逆算法:http://blog.5ilily.cn/article.asp?id=1688
apollolb2005 2008-04-17
  • 打赏
  • 举报
回复
mark
grearo 2008-04-17
  • 打赏
  • 举报
回复
很强很暴力,
偶不知道什么叫“空当接龙”
jdczp 2008-04-17
  • 打赏
  • 举报
回复
我也很想知道怎么解那个11982
加载更多回复(2)

110,534

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧