由连连看游戏作弊器想到的
?? 前幾天逛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);
?*/
}
總結
以上是生活随笔為你收集整理的由连连看游戏作弊器想到的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (转)mysql explain
- 下一篇: 珠海市世鸿置业有限公司5周年线下交流会带