假如让你写一个扫雷程序,在没有相关资料的前提下,应该从哪里入手呢?
我们先想想扫雷程序都有什么:菜单,秒表,记雷数,复位,还有雷区等杂七杂八的东西。而整个程序的关键,就在于雷区。因为如果没有秒表,记雷数等功能,扫雷勉强还是可以玩的,但如果没了雷区,就玩不了了。那么雷区又是怎么一回事呢?肯定要有一个数据结构来记录地雷和周围的数,而程序跟你交互的主要部分,就是在雷区上点一下,然后显示一幅图片,还有双击雷区翻开周围一片的操作。说白了,就是在画图。所以整个扫雷程序,就从画图先开始吧。
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap; static int cxClient,cyClient,cxSource,cySource; //位图类结构,包含位图的信息 BITMAP bitmap; //设备内容句柄 HDC hdc,hdcMem; //这条消息的实例句柄,因为只在WM_CREATE消息中获得,所以是创建的窗口的实例句柄 HINSTANCE hInstance; int x,y,mx,my; //PAINTSTRUCT结构包含了绘制客户区域的信息,第一个参数就是HDC类的 PAINTSTRUCT ps; //控制距离的变量 int dstx = 100; int dsty = 100; switch(message) { //创建窗口消息:第一个消息 case WM_CREATE: //LPCREATESTRUCT是一个指向结构CREATESTRUCT的指针 //对lParam强制类型转换以后指向结构体,结构体中的hInstance hInstance = ((LPCREATESTRUCT)lParam)->hInstance; //装载位图:参数为实例句柄,位图,返回值为位图句柄 //虽然也可以使用位图名称来装载位图,但是通常都是用序号 hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE (IDB_BITMAP2)); //从指定的图形对象中获取信息 //参数为:图形句柄,对象信息缓冲区的大小,对象信息缓冲区 GetObject(hBitmap,sizeof(BITMAP),&bitmap); //一幅位图的宽 cxSource = bitmap.bmWidth; //一幅位图的高:整个位图由16种不同的图形组成,每幅图形应为整个位图的16分之1 cySource = bitmap.bmHeight/16; return 0; //改变窗口大小消息 case WM_SIZE: //32位值的高低位代表客户区的长宽 //不同的消息间需要保留,所以用的是static cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; //绘制窗口消息 case WM_PAINT: //BeginPaint函数准备画图,向PAINTSTRUCT结构中填充信息 //参数为:窗口句柄、指向PAINTSTRUCT结构的画图信息,返回值为一个HDC hdc = BeginPaint (hwnd, &ps) ; //创建内存的设备环境,返回值是一个内存设备环境的句柄 hdcMem = CreateCompatibleDC(hdc); //将一个对象选择到设备环境中,参数为:设备环境句柄,对象句柄 SelectObject(hdcMem,hBitmap); for(y = dsty; y < cyClient-dsty;y += cySource) { for(x = dstx; x < cxClient-dstx;x += cxSource) { //贴图函数,参数为:目标DC句柄,贴图点的左上角的x坐标,贴图点的左上角的y坐标,目标和源的长,目标和圆的宽,源DC句柄,源的左上角x坐标,源的左上角y坐标,光栅操作代码(这里是直接从源矩形复制到目标矩形) BitBlt(hdc,x,y,cxSource,cySource,hdcMem,0,0,SRCCOPY); } } DeleteDC(hdcMem); //绘制窗口结束 EndPaint (hwnd, &ps) ; return 0;
注意到,使用WM_SIZE消息获取了客户区的大小。在WM_PAINT消息的二重循环中,通过它来改变贴多少方砖。
那么如何实现鼠标点一下就翻开呢?
case WM_LBUTTONDOWN: //左键会显示雷区被翻开的效果 hdc = GetDC(hwnd); hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem,hBitmap); //使得鼠标点到哪里,就选择那里的雷区,并画一幅翻开的图片 //鼠标点到雷区外,不起作用 //鼠标点到雷区里,用这个方块的左上角代替贴图的位置 //鼠标的位置: mx = (LOWORD(lParam)); my = (HIWORD(lParam)); //点到雷区里面才有效,雷区的位置:离上下dsty,左右dstx if(mx>dstx && mx<cxClient-dstx && my>dsty && my<cyClient-dsty ) { //将鼠标的位置改为这个点的对应的雷区的方砖的左上角 //结果总是在第一块砖上 mx = ((int)(mx-dstx)/cxSource)*cxSource+dstx; my = ((int)(my-dsty)/cySource)*cySource+dsty; //贴图函数,参数为:目标DC句柄,贴图点左上角的x坐标,贴图点左上角的y坐标,目标矩形的长,目标矩形的宽,源DC句柄,源的左上角x坐标,源的左上角y坐标,光栅操作代码(这里是直接从源矩形复制到目标矩形) BitBlt(hdc,mx,my,cxSource,cySource,hdcMem,0,cySource*15,SRCCOPY); } DeleteDC(hdcMem); ReleaseDC(hwnd,hdc); return 0;
//关闭窗口消息 case WM_DESTROY: PostQuitMessage (0) ; return 0; } return DefWindowProc (hwnd, message, wParam, lParam) ; //执行缺省消息 }
就是鼠标点一下,将鼠标点的位置换成它属于的方砖的位置,然后贴上另一种方砖即可。位置的转化原理其实很简单,就是利用C语言中int类型数据整除的性质,举个例子:
如果一块方砖的大小为16*16,你鼠标的位置为(30,50),那么它所属的方砖的左上角,肯定是在30/16 =1, 50/16 = 3就是第一列,第三行的那里,具体的像素就是(1*16,3*16)。