如上述,本程序分为了接口层和算法层。上述全局变量和常量,基本都属于接口层的内容。下面,来看接口层的具体实现。其工作的第一步,是要捕获扫雷窗口并取得其信息。这由函数GetMineWindow来完成:
=================================================================
//试图取得可用的扫雷窗口,返回值表示是否成功。若成功,则全局变量
//MineWnd、MineDC、AreaHeight、AreaWidth都得到相应的填充。若失败,则以上变量的值无意义。
function GetMineWindow: Boolean;
var
clientRect: TRect;
begin
result := false;
MineWnd := FindWindow(nil, MINE_WINDOW_TITLE); //检查是否存在“扫雷”窗口,并且必须为当前窗口
if (MineWnd = 0) or (GetForegroundWindow MineWnd) then
Exit;
MineDC := GetDC(MineWnd); //取得“扫雷”窗口的设备上下文
if MineDC = 0 then
Exit;
GetClientRect(MineWnd, clientRect); //检查“扫雷”窗口的内容是否全部显示在屏幕上
with TCanvas.Create do
try
Handle := MineDC;
if (ClipRect.Left clientRect.Left) or
(ClipRect.Right clientRect.Right) or
(ClipRect.Top clientRect.Top) or
(ClipRect.Bottom clientRect.Bottom) then
Exit;
finally
Free;
end;
//从已获得的clientRect中的数值,根据实测数据计算AreaWidth和AreaHeight的值。
AreaWidth := (clientRect.Right - LEFT_MARGIN - RIGHT_MARGIN) div CELL_WIDTH;
AreaHeight := (clientRect.Bottom - TOP_MARGIN - BOTTOM_MARGIN) div CELL_HEIGHT;
//检查游戏是否在进行中,原理为判断“重开始”按钮的图标上的
//某一像素是否是指定的值。该经验由实测得到,只有游戏进行中,该像素才为该值。
if TColor(GetPixel(MineDC, AreaWidth*8 + 8, 30)) clBlack then
Exit;
result := true;
end;
=================================================================
理解这个函数的工作过程,有几个要点:
WinAPI函数FindWindow:用来查找当前桌面上的某个窗口。第一个参数是指定该窗口的“窗口类”的名字,这个稍微高深了一点,只有研究过Windows SDK编程才会理解。当它为nil的时候,使用第二个参数,也就是窗口标题栏的字符串来查找。若找到这样一个窗口,则返回值为其窗口句柄,否则为0。
WinAPI函数GetForegroundWindow:无参数,返回桌面上的当前窗口,也就是标题条加亮的窗口的句柄。
WinAPI函数GetDC:给定一个窗口句柄,返回它的设备上下文句柄。“设备上下文”实际上就是一个“画布”,在Delphi中,被封装成了TCanvas类。获得了某个设备上下文句柄,就可以用一个TCanvas型的对象指向它(这个过程是,把句柄赋给TCanvas对象的Handle属性),从而实现画布的各种操作。
WinAPI函数GetClientRect:给定某个窗口句柄,取得它的客户区矩形,这个矩形是一个TRect类型的变量。调用这个函数,要用一个TRect型的变量来接收结果,而不是用返回值。这个结果的Left和Top成员都必定是0,而Right和Bottom成员其实就是窗口客户区的宽和高。
TCanvas类的属性ClipRect:简单的说,在此处,该TRect型属性取得的是该画布实际上被显示在屏幕上的矩形部分。只有该画布不被其它窗口遮挡,并且没有移出桌面边界的时候,这个矩形才完全等于等于窗口的客户区矩形。这用来判断扫雷窗口是否全部可见。
WinAPI函数GetPixel:给定一个设备上下文(画布)句柄和X,Y坐标,取得一个像素的值。这个值是整型的,可以简单的强制转换为TColor类型。
上述库函数,具体说明可以参考MSDN和Delphi自身的帮助文档,可以得到最为权威、详细、正确的说明。
不得不说一下GetMineWindow函数的最后几行,它牵涉到了对“重开始”按钮的hack。注意一下,可以发现那个简单的脸谱总共有5种状态:平时的笑脸,自身被按下时的笑脸,在雷区中按下鼠标时的紧张表情,触雷时的衰脸和胜利时酷酷的表情~——显然,只有在第一种情况时,扫雷外挂才应该动作,其它四种时则应该停止。我编了一个临时程序,找到了一个像素位置,它只有在第一种情况下值为clBlack,其它情况都不是。它的坐标为(AreaWidth*8 + 8, 30),横坐标是个随方块列数而变的变量,很好理解,因为无论窗口有多宽,该按钮都是水平居中的。