我的斗地主记牌器里面的记牌器,那天不小心点了个X,然后以…

2525人阅读
这是我2005年写的一个小程序,原来放在BOKEE上的,最近BOKEE经常打不开,打开的时候也是极慢、图片显不出来,中国最早的博客要关门了?为了不把程序弄丢了,还是转移到这里来吧。
由于前段时间玩起了QQ斗地主,从网上下载了一个QQ斗地主记牌器,但无法使用,于是就想自己开发一个。
一、记牌器实现的原理
当时查阅了一些资料,想到了三种实现的方法:
1、拦截IP包。这种方法应该是可行的,只是从查阅的资料来看,现在的游戏IP包都经过加密处理了,而且分析IP包的工作量比较大,所以就没采用这种方法。这方面使用的软件有:WPE(拦截发送封包),m2m封包分析。
2、拦截QQ斗地主显示扑克牌的函数。用W32DSM分析了斗地主的几个相关exe和dll竟然没找到显示扑克牌的函数:(。一开始觉得最可疑的应该是QQGame目录下的CardRes.dll,但分析后发现它竟没有程序出口。
3、屏幕抓图。感觉这种方法是最简单的了,觉得纯粹就是抓图,然后分析图形就行了。当时下载的记牌器应该也是应用这种方法实现的,但是它又不能用,网上又有评论说它被腾讯怎么给弄失效了。不管了,试试再说。
二、从图形分析开始
这是抓获的每张牌的区域,下图为放大后的效果,图中绿色网格为参考坐标,经过分析,发现只要使用12行X4列的区域就可以确定出牌的大小,使用20行X4列的区域就可以确定出大小和类型了。刚开始的时候,打算每次捕抓一张牌的区域,但是这样要想定的定义一系列的HBITMAP,不方便,所以就改为一列牌捕捉分析一次。考虑到一家的牌最多20张,而20张牌的屏幕区域为356X20像素。
分析牌大小和类型的函数如下:
//判断hdc中的牌
//////////////////////////////////////////////////////
BOOL CCard::Check(HDC hdc)
BYTE g_color, color1, color2;
// int i=1;
int nCardWidth=15; //每张牌的宽度为15像素
int nOffset = 2;
//牌边框与值之类的像素,见图片说明
for(int i=0; i&20; i++)
if(GetColor(hdc, 13, 1+i*nCardWidth)!= 0)
//找不到黑色的边框,说明没有牌
color1=GetColor(hdc, 1, 68+i*nCardWidth);
if(color1 &20 && color1 &150)
//表示为底色,此值为随便取的,视底色范围
if(GetColor(hdc, 1, 1+nOffset+i*nCardWidth) ==0)
value[i] = 13;
if(GetColor(hdc, 1,2+nOffset+i*nCardWidth) ==0 )
if((color1=GetColor(hdc,7,2+nOffset+i*nCardWidth))==0)
value[i] = 10;
value[i] = 5;
if((g_color=GetColor(hdc, 2,2+nOffset+i*nCardWidth)) ==0)
value[i] = 12;
if(GetColor(hdc, 12, 2+nOffset+i*nCardWidth) == 0)
value[i] = 1;
if(GetColor(hdc, 12, 1+nOffset+i*nCardWidth) == 0)
value[i] = 1;
if(GetColor(hdc, 6, 2+nOffset+i*nCardWidth) == 0)
value[i] = 4;
if(GetColor(hdc, 9, 2+nOffset+i*nCardWidth)== 0)
value[i] =11;
if((g_color=GetColor(hdc, 1, 3+nOffset+i*nCardWidth)) == 0)
if((color1=GetColor(hdc, 4,3+nOffset+i*nCardWidth)) ==0)
value[i] = 5;
if((color2=GetColor(hdc, 10, 3+nOffset+i*nCardWidth)) == 0)
value[i] = 3;
value[i] =7;
if(GetColor(hdc, 12, 3+nOffset+i*nCardWidth) ==0)
value[i] = 2;
if((color1=GetColor(hdc, 2, 3+nOffset+i*nCardWidth)) == 0)
if((GetColor(hdc, 6, 3+nOffset+i*nCardWidth)) ==0)
value[i] = 9;
value[i] = 8;
if(GetColor(hdc, 3, 3+nOffset+i*nCardWidth)==0)
value[i] = 6;
if(GetColor(hdc,1,1+nOffset+i*nCardWidth) == 255)
value[i] = 0;
value[i] = 14;
//判断花色
////////////////////////////////////////////////////////////////////
for(i=0; i&20; i++)
if(value[i] == 14)
type[i]=4;
if(value[i] == 0)
type[i]=0;
//先默认为大王
if(GetColor(hdc, 8, 4+nOffset+i*nCardWidth) & 250)
type[i]=1;
if(value[i]&0 && value[i]&11)
//牌为A、2-10时,花色的区域相同,为一种
if(GetColor(hdc, 20, 2+nOffset+i*nCardWidth) == 255) //方片A
type[i]=3;
if(GetColor(hdc, 17, 2+nOffset+i*nCardWidth) == 0) //红桃
type[i]=1;
if(GetColor(hdc, 18, 2+nOffset+i*nCardWidth) == 0) //方片
type[i]=3;
if(GetColor(hdc, 18, 4+nOffset+i*nCardWidth) == 0) //黑桃
type[i]=0;
type[i]=2;
if(value[i]&=11 && value[i]&=13)
//牌值为J-K时,花色区域为另一种
if(GetColor(hdc, 17, 1+nOffset+i*nCardWidth) == 0) //红桃
type[i]=1;
if(GetColor(hdc, 19, 1+nOffset+i*nCardWidth) == 255)//方片
type[i]=3;
if(GetColor(hdc, 16, 3+nOffset+i*nCardWidth) == 0) //黑桃
type[i]=2;
type[i]=0;
return TRUE;
//取得第row行,col列的G值,注意参数为先行后列,而且是1开始而非从0开始的
//原先是为了计算单张牌的方便,才采用了行列而非坐标,以至后来调试多花了好多时间^-^(是忘了这个规则了)
BYTE CCard::GetColor(HDC hdc, int row, int col)
COLORREF crC
crColor = GetPixel(hdc, col-1, row-1);
//取得第row行,col列的RGB值
//分离出蓝色值,为什么用蓝色呢,看看抓图的像素就知道了
//红色的G值为0,黑色的G值为0,而白色的G值为255,所以判断一下G值就知道是什么牌了
temp=GetGValue(crColor);
//分离出蓝色值
上图为各捕捉区域左上角的屏幕坐标,自己、左家、右家的牌一次捕捉356X20的区域,进行一次分析。底牌区域的坐标仅用于参考,视算法而定要不要进行分析(我的程序因为是旁观别人的牌进行分析的,自己的牌是一次分析出来的,后来自己玩的时候发现了一个问题,自己当地主时,底牌拿到手时跟其它的牌不是成一条线的!所以还要用到底牌区的区域来分析)。
三、屏幕抓图函数
可参见我的博客中其它的文章。我的思路是自己的牌抓一次,左右家的牌两家一起抓一次。因为左右家出的牌较多时,两家的牌会有一部分重叠在一起,但是经过分析,可以知道这两部分每张牌之间都是错开的,所以还是可以一次处理的。
另:使用spy++分析QQ斗地主的各个窗口坐标,可知:主窗口的左上角坐标为(-4,-4),而牌区的左上角坐标为(1,30),牌区的长宽为736X696像素。
我用的函数如下:
void CCaptureDlg::OnMiddle()
// TODO: Add your control notification handler code here
CCard cardL
int nLordX=212, nLordY=554;
HDC hdcScreen, hMemDC;
HBITMAP hBitmap, hOldB
HBITMAP hBitmap1, hOldBitmap1;
//建立一个屏幕设备环境句柄
hdcScreen = CreateDC(&DISPLAY&, NULL, NULL, NULL);
hMemDC = CreateCompatibleDC(hdcScreen);
hMiddleDC = CreateCompatibleDC(hdcScreen);
*/ //建立一个与屏幕设备环境句柄兼容、与鼠标所在处的窗口的区域等大的位图
hBitmap = CreateCompatibleBitmap(hdcScreen, nWidth, nHeight);
hBitmap1 = CreateCompatibleBitmap(hdcScreen, 356, 20);
// 把屏幕设备描述表拷贝到内存设备描述表中
hOldBitmap = (HBITMAP) SelectObject(hMemDC, hBitmap);
hOldBitmap1 = (HBITMAP) SelectObject(hMiddleDC, hBitmap1);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hdcScreen, rectCapture.left+5, rectCapture.top+34,SRCCOPY);
if(cardLord.GetColor(hMemDC, nLordY, nLordX-1) ==255)
nLordX -= 22;
BitBlt(hMiddleDC, 0, 0, 356,20, hMemDC, nLordX, nLordY, SRCCOPY);
cardLord.Check(hMiddleDC);
/* CString str1, str2, str3;
str2.Empty();
str2.Format(_T(&自己的牌:\n&));
for(int i=0; i&20; i++)
str1.Format(_T(&%4d&), cardLord.GetValue(i));
str2 += str1;
str1.Format(_T(&%4d&), cardLord.GetType(i));
str3 += str1;
str2+=&\n&;
str2+=str3;
AfxMessageBox(str2);
hBitmap =(HBITMAP)SelectObject(hMemDC, hOldBitmap);
hBitmap1 =(HBITMAP)SelectObject(hMiddleDC, hOldBitmap1);
DeleteDC(hMiddleDC);
DeleteDC(hdcScreen);
DeleteDC(hMemDC);
// 返回位图句柄
//打开剪贴板,并将位图拷到剪贴板上
OpenClipboard() ;
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap1);
//关闭剪贴板
CloseClipboard();
// MessageBox(&屏幕内容已经拷到剪贴板上!&);
//终止鼠标捕获
ReleaseCapture();
//恢复窗口显示模式
// ShowWindow(SW_NORMAL);
DeleteItem(cardLord);
void CCaptureDlg::OnLeft()
// TODO: Add your control notification handler code here
// KillTimer(1);
CCard cardLeft, cardR
int nLeftX=172, nLeftY=272;
int nRightX=208, nRightY=272;
/* HDC hdcScreen, hMemDC;
HBITMAP hBitmap, hOldB
HBITMAP hBitmap1, hOldBitmap1;
HBITMAP hBitmap2, hOldBitmap2;
//建立一个屏幕设备环境句柄
hdcScreen = CreateDC(&DISPLAY&, NULL, NULL, NULL);
hMemDC = CreateCompatibleDC(hdcScreen);
hLeftDC = CreateCompatibleDC(hdcScreen);
hRightDC = CreateCompatibleDC(hdcScreen);
//建立一个与屏幕设备环境句柄兼容、与鼠标所在处的窗口的区域等大的位图
hBitmap = CreateCompatibleBitmap(hdcScreen, nWidth, nHeight);
hBitmap1 = CreateCompatibleBitmap(hdcScreen, 356, 20);
hBitmap2 = CreateCompatibleBitmap(hdcScreen, 356, 20);
// 把屏幕设备描述表拷贝到内存设备描述表中
hOldBitmap = (HBITMAP) SelectObject(hMemDC, hBitmap);
hOldBitmap1 = (HBITMAP) SelectObject(hLeftDC, hBitmap1);
hOldBitmap2 = (HBITMAP) SelectObject(hRightDC, hBitmap2);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hdcScreen, rectCapture.left+5, rectCapture.top+34,SRCCOPY);
BitBlt(hLeftDC, 0, 0, 356,20, hMemDC, nLeftX, nLeftY, SRCCOPY);
BitBlt(hRightDC, 0, 0, 356,20, hMemDC, nRightX, nRightY, SRCCOPY);
cardLeft.Check(hLeftDC);
cardRight.Check(hRightDC);
DeleteItem(cardLeft);
DeleteItem(cardRight);
DeleteDC(hdcScreen);
DeleteDC(hMemDC);
DeleteDC(hLeftDC);
DeleteDC(hRightDC);
hBitmap =(HBITMAP)SelectObject(hMemDC, hOldBitmap);
hBitmap1 =(HBITMAP)SelectObject(hLeftDC, hOldBitmap1);
hBitmap2 =(HBITMAP)SelectObject(hRightDC, hOldBitmap2);
// SetTimer(1, 2000, NULL);
四、主程序
主程序采用4个列表来记牌,为什么不用一个列表来实现呢?因为列表只能在第一列能够同时显示图标和文字,所以用了4个列表。下方的5个按钮是用来测试各个函数的,调试用的。代码如下:
BOOL CCaptureDlg::OnInitDialog()
CDialog::OnInitDialog();
// Add &About...& menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX & 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
CString strAboutM
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
pSysMenu-&AppendMenu(MF_SEPARATOR);
pSysMenu-&AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
//加入WS_EX_LAYERED扩展属性
//设置窗口为半透明
SetWindowLong(this-&GetSafeHwnd(),GWL_EXSTYLE,
GetWindowLong(this-&GetSafeHwnd(),GWL_EXSTYLE)^0x80000);
HINSTANCE hInst = LoadLibrary(&User32.DLL&);
typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);
MYFUNC fun = NULL;
//取得SetLayeredWindowAttributes函数指针
fun=(MYFUNC)GetProcAddress(hInst, &SetLayeredWindowAttributes&);
if(fun)fun(this-&GetSafeHwnd(),0,255,2);
FreeLibrary(hInst);
//将窗口设为最前
::SetWindowPos(this-&m_hWnd,HWND_TOPMOST,815,395,0,0,SWP_NOSIZE);
// return TRUE
unless you set the focus to a control
// Set the icon for this dialog.
The framework does this automatically
when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE);
// Set big icon
SetIcon(m_hIcon, FALSE);
// Set small icon
// TODO: Add extra initialization here
bCapture=FALSE;
bLord = FALSE;
bMiddle = FALSE;
bLeft = FALSE;
bRight = FALSE;
bMiddle1 = FALSE;
bLeft1 = FALSE;
bRight1 = FALSE;
hwndCapture = ::FindWindow(NULL, &斗地主&);
::GetWindowRect(hwndCapture,&rectCapture);
nWidth=rectCapture.Width();
nHeight=rectCapture.Height();
if(nWidth != 1032 || nHeight != 746)
MessageBox(&斗地主的主窗口尺寸不对!\n\n检查显示器分辨率:请调整为\n并将斗地主窗口最大化\n\n不便之处,敬请原谅!&);
InitList();
//建立一个屏幕设备环境句柄
hdcScreen = CreateDC(&DISPLAY&, NULL, NULL, NULL);
hMemDC = CreateCompatibleDC(hdcScreen);
hMiddleDC = CreateCompatibleDC(hdcScreen);
hLeftDC = CreateCompatibleDC(hdcScreen);
hRightDC = CreateCompatibleDC(hdcScreen);
SetTimer(1,2000,NULL);
return TRUE;
// return TRUE
unless you set the focus to a control
//初始化4个列表
////////////////////////////////////////////////////////////////////////////////////////////////
void CCaptureDlg::InitList()
char str1[15][5]={&3&,&4&,&5&,&6&,&7&,&8&,&9&,&10&,&J&,&Q&,&K&,&A&,&2&,&王&,&无牌&};
//载入4种花色的图标
SmallImage.Create(14, 14, ILC_COLOR8 | ILC_MASK, 4, 1);
CBitmap cB
cBmp.LoadBitmap(IDB_BITMAP1);
SmallImage.Add(&cBmp, RGB(255,255,255));
cBmp.DeleteObject();
cBmp.LoadBitmap(IDB_BITMAP2);
SmallImage.Add(&cBmp, RGB(255,255,255));
cBmp.DeleteObject();
cBmp.LoadBitmap(IDB_BITMAP3);
SmallImage.Add(&cBmp, RGB(255,255,255));
cBmp.DeleteObject();
cBmp.LoadBitmap(IDB_BITMAP4);
SmallImage.Add(&cBmp, RGB(255,255,255));
cBmp.DeleteObject();
//初始化4个列表
for(i=0; i&4; i++)
m_list[i].SetImageList(&SmallImage, LVSIL_SMALL);
m_list[i].InsertColumn(0,&&);
m_list[i].SetColumnWidth(0,50);
CString strI
for(j=0; j&4; j++)
for(i=0;i&13;i++)
lvi.mask = LVIF_IMAGE|LVIF_TEXT;
lvi.iItem =
lvi.iSubItem = 0;
lvi.iImage=j;
strItem.Format(_T(&%s&),str1[i]);
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_list[j].InsertItem(&lvi);
//记下大小王
iconJoke1 = m_joke1.GetIcon();
iconJoke2 = m_joke2.GetIcon();
bMiddle1 = TRUE;
五、存在的问题
使用主界面下方的按钮来测试各个函数,可以正常捕捉记牌,但是使用OnTimer()来自动记牌时,记一段时间就出错了,是什么问题呢?
该采用什么算法来激活捕捉分析程序呢?欢迎大家跟我探讨。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:50272次
排名:千里之外
转载:31篇
评论:27条
(2)(16)(15)

参考资料

 

随机推荐