由连连看游戏作弊器想到的

原创 2005年05月01日 15:19:00

   前几天逛VCKbase的时候看到了这样一篇文章:http://www.vckbase.com/document/viewdoc/?id=1415。文章一开始就介绍了基本算法,即先获得QQ连连看窗口句柄,然而获得其DC,然后对每个方格进行颜色取样,计算出每种方格的颜色特征值,构造出方格特征矩阵,在该矩阵上实现连连看的算法,模拟鼠标点击事件。

   文章还给了一段关键的代码,即构造方格特征矩阵部分的代码。不过老实说,那些代码太难看了,命名混乱得很,我看了看源代码,比那个更糟,基本没注释。不过既然知道了算法,自己写也许比读那些难看的代码来得更快一些。

   经过实验,作者的算法的确不错,准确率接近100%,只有4种左右的方格无法识别。但是这个不是问题,只需要将取样点增加到6个就可以达到很高的分辨能力了。但是在模拟鼠标的过程中遇到了一些麻烦。一开始使用mouse_event模拟鼠标的单击,但是未能如愿以偿,每次只能单击一个方格,无法单击第二个。后来又试了试SendInput,问题依旧。去CSDN上逛了逛,没什么收获,现在还不知道问题出在哪里,看来还得自己慢慢研究。

    试验过后,觉得可以将这个思路用到类似的游戏中,比如QQ对对碰。构造特征矩阵的方法一样,只不过游戏算法不一样而已。

附:我的Code,算法有很大的改进余地。

// For SendInput() and INPUT Struct

#include "Winable.h"

//
// 一些常量和数据的定义

// 自定义消息
#define    MM_DONE   WM_USER + 1000
// 空格
#define    GRID_BLANK  (0)
// QQ连连看的游戏区方格是11行×19列
#define  ROWS  11
#define  COLS  19
// 游戏区相对于客户区的偏移(可以改变)
#define  YCOFFSET 181
#define  XCOFFSET 14
// 连连看方格的尺寸(可以改变)
#define  WIDTH  31
#define  HEIGHT  35
// 每个方格的取样点个数(可以改变)
#define  SAMPLES  4
// 各取样点相对于方格左上角的偏移量(可以改变)
const static POINT g_Offsets[SAMPLES] = {
 {15, 17},
 {11, 19},
 {16, 17},
 {21, 17}
};
// 判断两种不同颜色的容差(可以改变)
#define  TOLERANCE 15
// 方格结构
typedef struct _GRID_{
 // 方格的类别ID(hash),大于等于0,0为空格。
 //  它的作用很明显,只有同类的方格才可能被消除。
 int   iID;
 // 该方格中心坐标(客户的,为了避免因为连连看窗口位置发生变化带来的问题)
 CPoint  pt;
 // 该方格的取样点的颜色。它的作用是为了帮助确定所有方格的iID。
 COLORREF cl[SAMPLES];
}GRID;
// 整个游戏区所有的方格
static GRID  g_Grid[ROWS][COLS];
// 当前的方格种类ID(Hash)最大值
static  int   g_iGridID = 0;
// 作弊器窗口句柄。给它发送消息的时候要用到
static  HWND  g_hThisWnd = NULL;
// 连连看 HWND
static  HWND  g_hLLKWnd = NULL;

// 
// 一些辅助宏

// 获得RGB个分量
#define  B(X)  (GetBValue(X))
#define  G(X)  (GetGValue(X))
#define  R(X)  (GetRValue(X))
// 比较大小
#define  MIN(X, Y) ((X) > (Y) ? (Y) : (X))
#define  MAX(X, Y) ((X) > (Y) ? (X) : (Y))
// 差的绝对值
#define  ABS(X, Y) ((X) < (Y) ? (Y) - (X) : (X) - (Y))

//////////////////////////////////////////////////////////////////
// 计算两种颜色个分量的差值之和
int   Difference(const COLORREF &c1, const COLORREF &c2)
{
 return ABS(R(c1), R(c2))
  +  ABS(G(c1), G(c2))
  +  ABS(B(c1), B(c2));
}
// 在矩阵中查找和指定颜色的差别在容差范围内的方格(r, c)。如果不存在则返回 - 1,
// 否则返回0和该颜色所在方格的位置。
int   FindColor(const COLORREF cl[SAMPLES], int &r, int &c)
{
 int i, j, k, iDiff;
 for(i = 0; i < ROWS; i ++)
 {
  for(j = 0 ; j < COLS; j ++)
  {
   // 不用和空格比较
   if(g_Grid[i][j] .iID == GRID_BLANK)
    continue;
   iDiff = 0;
   // 计算该颜色和指定颜色之间的差别
   for(k = 0; k < SAMPLES; k ++)
   {
    iDiff += Difference(g_Grid[i][j] .cl[k], cl[k]);
   }
   if(iDiff <= TOLERANCE)
   {
    // 找到
    r = i;
    c = j;
    return 0;
   }
  }
 }
 return - 1;
}
// 通过取样点的RGB值确定一个方格的类别ID(Hash)
int   GetGridID(const COLORREF cl[SAMPLES])
{
 // 根据颜色将方格分类
 for(int i = 0; i < SAMPLES; i ++)
 {
  // 空格的RGB分量范围
  // R(44~74), G(49~91), B(102~103)
  if (R(cl[i]) > 44 && R(cl[i]) < 74
   && G(cl[i]) > 49 && G(cl[i]) < 91
   && B(cl[i]) > 102 && R(cl[i]) < 130)
  {
  }
  else
  {
   // 是否已经存在?
   int r, c;
   if(FindColor(cl, r, c) == - 1)
   {
    // 不存在,则添加
    g_iGridID ++;
    return g_iGridID;
   }
   else
   {
    // 存在则返回ID
    return g_Grid[r][c] .iID;
   }
  }
 }
 // 空格
 return GRID_BLANK;
}
// 报告错误
void   ReportError(CString strMsg)
{
 AfxMessageBox(strMsg);
}

// 获得游戏方格特征矩阵
BOOL  InitMatrix(HWND hWnd)
{
 // 获得连连看CWnd
 CWnd* pWnd = CWnd ::FromHandle(hWnd);
 if(NULL == pWnd)
  return FALSE;
 // 连连看最小化了?
 if(pWnd ->IsIconic())
 {
  pWnd ->ShowWindow(SW_RESTORE);
  Sleep(200);
 }
 // 连连看没有激活?
 if(pWnd ->GetActiveWindow() ->GetSafeHwnd() != hWnd)
 {
  pWnd ->SetForegroundWindow();
  pWnd ->SetActiveWindow();
  Sleep(200);
 }
 // 获得连连看的CDC
 CDC* pDC = pWnd ->GetDC();
 if(pDC == NULL)
  return FALSE;
 // 获得连连看游戏区左上角第一个方格的中心坐标(客户区的)
 CPoint pt(0, 0);
 // 获得对应所有方格的特征
 for(int i = 0; i < ROWS; i ++)
 {
  // 方格左上角的Y坐标
  pt .y = YCOFFSET + i * HEIGHT;
  for(int j = 0; j < COLS; j ++)
  {
   // 方格左上角的X坐标
   pt .x = XCOFFSET + j * WIDTH;
   // 保存方格中心坐标(客户区)
   g_Grid[i][j] .pt .Offset(pt .x + WIDTH / 2, pt .y + HEIGHT / 2);
   // 得到该方格的取样点的RGB
   for(int k = 0; k < SAMPLES; k ++)
   {
    // 如果用户关闭了程序窗口
    if(::IsWindow(hWnd))
    {
     g_Grid[i][j] .cl[k] = pDC ->GetPixel(pt .x + g_Offsets[k] .x, pt .y + g_Offsets[k] .y);
    }
    else
    {
     // 窗口已经被关闭!
     ReportError(_T("窗口在编码时被关闭,请检查连连看的状态,并尝试重新编码!"));
     return FALSE;
    }
   }
   // 确定该方格类别Hash
   g_Grid[i][j] .iID = GetGridID(g_Grid[i][j] .cl);
   TRACE(_T("%4d,"), g_Grid[i][j] .iID);
  }
  TRACE(_T("/r/n"));
 }
 return TRUE;
}

// 判断两个在同一行(列)的方格(必须同类,可以是空格)之间是否存在直线通路
BOOL  GridsConnected(int pivot, int l, int h, BOOL bHorizontal = TRUE)
{
 // 是否同类?
 if(bHorizontal)
 {
  if(g_Grid[pivot][l] .iID != g_Grid[pivot][h] .iID)
   return FALSE;
 }
 else
 {
  if(g_Grid[l][pivot] .iID != g_Grid[h][pivot] .iID)
   return FALSE;
 }
 int L = MIN(l, h);
 int H = MAX(l, h);
 // 两个方格之间必须都是空格
 for(int i = L + 1; i < H; i ++)
 {
  if(bHorizontal)
  {
   if(g_Grid[pivot][i] .iID != GRID_BLANK)
    return FALSE;
  }
  else
  {
   if(g_Grid[i][pivot] .iID != GRID_BLANK)
    return FALSE;
  }
 }
 return TRUE;
}
// 输出路径
void  OutputPath(int r1, int c1, int r2, int c2)
{
 if(r1 == r2 && c1 == c2)
  return;
 //TRACE(_T(">>>(%d, %d) -> (%d, %d)./r/n"), r1, c1, r2, c2);
}
// 判断两个方格(r1, c1)和(r2, c2)(不能是空格)是否可以消除
BOOL  Match(const int r1, const int c1, const int r2, const int c2)
{
 // 不能是空格
 if(g_Grid[r1][c1] .iID == GRID_BLANK
  || g_Grid[r2][c2] .iID == GRID_BLANK)
  return FALSE;
 // 必须是同一种方格
 if(g_Grid[r1][c1] .iID != g_Grid[r2][c2] .iID)
  return FALSE;
 // 起始点的类别
 const int iID = g_Grid[r1][c1] .iID;

 // 它们在同一行?
 if(r1 == r2)
 {
  if(c1 <= c2)
  {
   // 它们之间是否有直线通路?
   if(GridsConnected(r1, c1, c2))
   {
    OutputPath(r1, c1, r1, c2);
    return TRUE;
   }
   // 是否有其他通路?
   for(int i = 0; i < ROWS; i ++)
   {
    // 已经判断过了
    if(i == r1)
     continue;
    // 两个拐点必须是空格
    if(GRID_BLANK == g_Grid[i][c1] .iID
     && GRID_BLANK == g_Grid[i][c2] .iID)
    {
     // 将两个拐点的类别设置为iID,然后判断是否有路径
     g_Grid[i][c1] .iID = g_Grid[i][c2] .iID = iID;
     if(GridsConnected(c1, i, r1, FALSE)
      && GridsConnected(i, c1, c2)
      && GridsConnected(c2, i, r1, FALSE))
     {
      // OK,还原类别
      g_Grid[i][c1] .iID = GRID_BLANK;
      g_Grid[i][c2] .iID = GRID_BLANK;
      OutputPath(i, c1, r1, c1);
      OutputPath(i, c1, i, c2);
      OutputPath(i, c2, r1, c2);
      return TRUE;
     }
     else
     {
      // 不通,则测试下一条路径,并且还原拐点类别
      g_Grid[i][c1] .iID = GRID_BLANK;
      g_Grid[i][c2] .iID = GRID_BLANK;
     }
    }
   } // end for
   // 不通
   return FALSE;
  }
  else
  {
   return Match(r1, c2, r1, c1);
  }
 }
 // 它们在同一列?
 if(c1 == c2)
 {
  if(r1 <= r2)
  {
   if(GridsConnected(c1, r1, r2, FALSE))
   {
    OutputPath(r1, c1, r2, c1);
    return TRUE;
   }
   // 是否有其他通路?
   for(int i = 0; i < COLS; i ++)
   {
    if(i == c1)
     continue;
    // 必须为空格
    if( GRID_BLANK == g_Grid[r1][i] .iID
     && GRID_BLANK == g_Grid[r2][i] .iID)
    {
     //将两个拐点的类别设置为iID,然后判断是否有路径
     g_Grid[r1][i].iID = g_Grid[r2][i] .iID = iID;
     if(GridsConnected(r1, i, c1)
      && GridsConnected(i, r1, r2, FALSE)
      && GridsConnected(r2, i, c1))
     {
      // OK, 还原类别
      g_Grid[r1][i].iID = GRID_BLANK;
      g_Grid[r2][i].iID = GRID_BLANK;
      OutputPath(r1, c1, r1, i);
      OutputPath(r1, i, r2, i);
      OutputPath(r2, i, r2, c1);
      return TRUE;
     }
     else
     {
      // 不通,则测试下一条路径,并且还原拐点类别
      g_Grid[r1][i].iID = GRID_BLANK;
      g_Grid[r2][i].iID = GRID_BLANK;
     }
    }
   }
   // 不通
   return FALSE;
  }
  else
  {
   return Match(r2, c1, r1, c1);
  }
 }
 // 不在同一列或者行
 // 通过在拐点构造和方格类别相同的方格,然后判断它们是否联通(最多3条线,最少2两条
 //  其中一条退化)
 for(int i = 0; i < ROWS; i ++)
 {
  // 拐点1必须为空格
  if(g_Grid[i][c1] .iID != GRID_BLANK)
   continue;
  // 起点和拐点1联通吗?
  g_Grid[i][c1] .iID = iID;
  if(GridsConnected(c1, i, r1, FALSE))
  {
   // 是的,拐点2必须为空格
   if(g_Grid[i][c2] .iID != GRID_BLANK)
   {
    // 还原拐点1的类别,尝试下一条路径
    g_Grid[i][c1] .iID = GRID_BLANK;
    continue;
   }
   // 拐点2和拐点1联通吗?
   g_Grid[i][c2] .iID = iID;
   if(GridsConnected(i, c1, c2))
   {
    // 是的,然后判断拐点2和(r2, c2)是否联通
    if(GridsConnected(c2, i, r2, FALSE))
    {
     // 是的,路径找到,还原拐点类别
     g_Grid[i][c2] .iID = GRID_BLANK;
     g_Grid[i][c1] .iID = GRID_BLANK;
     //
     OutputPath(r1, c1, i, c1);
     OutputPath(i, c1, i, c2);
     OutputPath(i, c2, r2, c2);
     return TRUE;
    }
   }
   // 拐点2和拐点1不通,还原拐点类别,尝试下一行
   g_Grid[i][c1] .iID = GRID_BLANK;
   g_Grid[i][c2] .iID = GRID_BLANK;
  }
  else
  {
   // 起点和拐点1不联通,还原拐点类别
   g_Grid[i][c1] .iID = GRID_BLANK;
  }
 } // end for
  
 for(i = 0; i < COLS; i ++)
 {
  // 拐点1必须为空格
  if(g_Grid[r1][i] .iID != GRID_BLANK)
   continue;
  // 起点和拐点1联通吗?
  g_Grid[r1][i] .iID = iID;
  if(GridsConnected(r1, i, c1))
  {
   // 是的,拐点2 必须为空格
   if(g_Grid[r2][i] .iID != GRID_BLANK)
   {
    // 还原拐点1的类别,尝试下一条路径
    g_Grid[r1][i] .iID = GRID_BLANK;
    continue;
   }
   // 拐点2和拐点1联通吗?
   g_Grid[r2][i] .iID = iID;
   if(GridsConnected(i, r1, r2, FALSE))
   {
    // 是的,然后判断拐点2和(r2, c2)是否联通
    if(GridsConnected(r2, i, c2))
    {
     // 是的,路径找到,还原拐点类别
     g_Grid[r1][i] .iID = GRID_BLANK;
     g_Grid[r2][i] .iID = GRID_BLANK;
     //
     OutputPath(r1, c1, r1, i);
     OutputPath(r1, i, r2, i);
     OutputPath(r2, i, r2, c2);
     return TRUE;
    }
   }
   // 拐点2和拐点1不通,还原拐点类别,尝试下一行
   g_Grid[r1][i] .iID = GRID_BLANK;
   g_Grid[r2][i] .iID = GRID_BLANK;
  }
  else
  {
   // 起点和拐点1不通,则还原拐点类别,尝试下一条路径
   g_Grid[r1][i] .iID = GRID_BLANK;
  }
 }
 return FALSE;
}
// 模拟点击两次鼠标。实际情况有点不一样,鼠标没有点击两次,至少看起来是这样。事实上只是点击了第一次,后一次没有点击。用SendInput问题一样。
void  MouseClick(const CPoint& p1, const CPoint& p2)
{
 // 将客户区坐标转换为屏幕坐标
 CPoint pp1(p1), pp2(p2);
 ::ClientToScreen(g_hLLKWnd, &pp1);
 ::ClientToScreen(g_hLLKWnd, &pp2);
 // 点击两次鼠标
 /* */
 ::SetCursorPos(pp1 .x, pp1 .y);
 mouse_event( MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
 mouse_event( MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
 Sleep(100);
 ::SetCursorPos(pp2 .x, pp2 .y);
 mouse_event( MOUSEEVENTF_LEFTDOWN , 0, 0, 0, 0);
 mouse_event( MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

 /*
 INPUT clk;
 ZeroMemory(&clk, sizeof(clk));
 clk .type  = INPUT_MOUSE;
 clk . mi .dwFlags =  MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP;
 clk . mi .dwFlags =  MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP;
 ::SetCursorPos(pp1 .x, pp1 .y);
 VERIFY(1 == SendInput(1, &clk, sizeof(clk)));
 Sleep(300);
 ::SetCursorPos(pp2 .x, pp2 .y);
 VERIFY(1 == SendInput(1, &clk, sizeof(clk)));
 Sleep(300);
 */
}

相关文章推荐

短信作弊器-来自1979年的问候

短信作弊器-来自1979年的问候说明这是一个可以伪造收件箱短信的APP,在没有收到信息的情况,可以在收件箱中伪造出短信,而且在第二次打开APP时,系统会自动记住你时候设置默认短信APP注意:请在伪造完...

一个失败的作品,QQ找茬作弊器

这个是专为我自己电脑的显示器写的,原理非常的简单易懂,不具有通用性,还有,有内存泄露问题,长时间开会死机,要想正常应用还需要一个配套的批处理文件才可以,但是我找不到了.如果有时间的话也许会修正这个问题...

微信摇骰子和猜拳作弊器制作流程(一)

声明: 本文的开发原理和流程皆来自-尼古拉斯_赵四:http://blog.csdn.net/jiangwei0910410003/article/details/52892330的这篇文章,所有技术...

微信摇骰子和猜拳作弊器制作流程(二)

本文链接: 微信摇骰子和猜拳作弊器制作流程(一) 微信摇骰子和猜拳作弊器制作流程(二) 悬浮窗开发: 基于第一篇文章最后的一些思考和期望,本人查阅了很多有关于悬浮窗的教学和Demo,个人感觉...

Android"挂逼"修炼之行---微信摇骰子和猜拳作弊器原理解析

在之前的一篇文章中我们已经详细介绍了Android中Hook工作的一款神器Xposed工具:Xposed框架原理解析和使用案例分析 在那一篇文章中我们介绍了如何安装Xposed框架,以及如何使用Xpo...

Android中Xposed框架篇-微信摇色子和剪刀石头布作弊器

本文转载自微信摇骰子和猜拳作弊器原理解析 本文借助之前的Xposed框架来介绍如何编写微信的一个外挂功能,这个功能就是微信摇色子和剪刀石头布的作弊器。这个功能肯定具有随机性,如果我们找到这个方法了,...

Android"挂逼"修炼之行---微信摇骰子和猜拳作弊器原理解析

一、前言 在之前的一篇文章中我们已经详细介绍了Android中Hook工作的一款神器Xposed工具:Xposed框架原理解析和使用案例分析 在那一篇文章中我们介绍了如何安装Xposed框架,以...
  • dj0379
  • dj0379
  • 2016年11月08日 17:30
  • 2273

数独作弊器

为了方便使用,用java写了一个很简陋的java应用程序版的数独作弊器。^_^ 对java现在不感冒了。。。不过没办法// class: sudo import java.awt.BorderLa...
  • zdsfwy
  • zdsfwy
  • 2011年04月22日 21:05
  • 1568

MFC学习笔记整理:002_腾讯游戏连连看外挂制作(一)

今天心血来潮,准备写个外挂练练手,当然也是从简单的开始了,就选腾讯连连看下手。 之前整合过2个外部的exe程序,接触了找句柄、读写内存的操作,写游戏外挂找基址是关键,当然离不开CE(不知道CE的直接退...

Eclipse SWT开发教程以及一个连连看游戏的代码实现下载

Eclipse SWT开发教程以及一个连连看游戏的代码实现下载,代码下载地址:http://www.zuidaima.com/share/1772672482675712.htm...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:由连连看游戏作弊器想到的
举报原因:
原因补充:

(最多只允许输入30个字)