C图形化第一步
之前的贪吃蛇都是在cmd下实现,每次都要调用cls刷新屏幕,简直是闪瞎了我的狗眼。
度娘得知有一种方法可以避免闪烁,即:双缓冲。原理是先在内存中作图,然后将做好的图复制到前台,同时禁止背景刷新。
主要使用函数:
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain(HINSTANCE, HINSTANCE,PSTR, int);
涉及主要方法:
WNDCLASS wndclass; wndclass.style=CS_HREDRAW|CS_VREDRAW;//位置改变时重绘 wndclass.lpfnWndProc=(WNDPROC)WndProc;//消息处理函数 wndclass.hInstance=0;//当前实例句柄 wndclass.hbrBackground=(HBRUSH)COLOR_WINDOWFRAME;//背景色 wndclass.lpszClassName=szWindowClass;//参窗口类名 wndclass.hIcon=0;//图标 wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);//光标 wndclass.lpszMenuName=0;//菜单名称 wndclass.hIconSm=0;//最小化图标 RegisterClassEx(&wndclass);//注册窗口类
CreateWindow:
HWND CreateWindow( LPCTSTR lpClassName, // 如果lpClassName是一个字符串,它指定了窗口的类名 LPCTSTR lpWindowName, // 指向一个指定窗口名的空结束的字符串指针,可使用lpWindowName来指定控制文本 DWORD dwStyle, // 指定创建窗口的风格, WS_CAPTION:创建一个有标题框的窗口 // WS_SYSMENU:创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格 int x, // 指定窗口的初始水平位置 // 如果该参数被设为CW_USEDEFAULT则系统为窗口选择缺省的左上角坐标并忽略Y参数。 // CW_USEDEFAULT只对层叠窗口有效,如果为弹出式窗口或子窗口设定,则X和y参数被设为零 int y, // int nWidth, // 以设备单元指明窗口的宽度 // 若nWidth是CW_USEDEFAULT,则系统为窗口选择一个缺省的高度和宽度: // 缺省宽度为从初始X坐标开始到屏幕的右边界,缺省高度为从初始Y坐标开始到目标区域的顶部。 // CW_USEDEFAULT只对层叠窗口有效;如果为弹出式窗口和子窗口设定CW_USEDEFAULT标志则nWidth和nHeight被设为零。 int nHeight, // HWND hWndParent, // 指向被创建窗口的父窗口或所有者窗口的句柄 HMENU hMenu, // 菜单句柄,或依据窗口风格指明一个子窗口标识 HANDLE hlnstance, // 与窗口相关联的模块实例的句柄 LPVOID lpParam // 指向一个值的指针,该值传递给窗口WM_CREATE消息 ) // 返回值:如果函数成功,返回值为新窗口的句柄:如果函数失败,返回值为NULL。
将字符贪吃蛇迁移过来,gcc 编译时需加 -mwindows 参数,如:
gcc snake.c -mwindows
代码如下:
#include <windows.h> #define ID_TIMER 1 #define WIDTH 12 // 宽 #define HEIGHT 8 // 高 #define DEBUG 0 const char FENCE ='*'; // 栅栏 const char HEAD ='@'; // 蛇头 const char BODY ='#'; // 蛇身 const char FOOD ='O'; // 食物 const char BLANK =' '; // 空白 char arr[HEIGHT][WIDTH]; struct Snake{ int y,x; }snake[WIDTH*HEIGHT]; // 结构体,保存坐标点 int len=0,food=0,key=0,score=0,alive=1,direct=4,speed=1,full=(WIDTH-2)*(HEIGHT-2); // 长度、食物、按键、总分、存活、方向、速度 int uldr[5][2]={{},{-1,0},{0,-1},{1,0},{0,1}}; // 上、左、下、右 void init(){ // 初始化游戏地图 int y=0,x=0,start_pos=HEIGHT/2; for(y=0;y<HEIGHT;y++) for(x=0;x<WIDTH;x++) if( y == 0 || y == HEIGHT-1 || x == 0 || x == WIDTH-1 ){ arr[y][x] = FENCE; } else{ arr[y][x] = BLANK; } snake[0].y=start_pos; snake[0].x=3; len++; snake[1].y=start_pos; snake[1].x=2; len++; arr[ snake[0].y ][ snake[0].x ] = HEAD ; arr[ snake[1].y ][ snake[1].x ] = BODY ; } void make_food(){ int rx,ry; while(!food){ rx = rand()%WIDTH; ry = rand()%HEIGHT; if( arr[ry][rx] == BLANK ){ arr[ry][rx]=FOOD; food=1; break; } } } void move(){ int cnt=0; len++; // 准备将当前位置放在snake数组首部 if(DEBUG)printf("len:%d\n",len); for(cnt=len-1;cnt>0;cnt--){ snake[cnt].x=snake[cnt-1].x; snake[cnt].y=snake[cnt-1].y; // 1234 变为 51234 } snake[0].y+=uldr[direct][0]; snake[0].x+=uldr[direct][1]; // 移动蛇头 } void check_head(){ int y=snake[0].y; int x=snake[0].x; int i=0; int cnt=0; if(y < 1 || y > HEIGHT-2 || x < 1 || x > WIDTH-2){ // 是否越界 alive=0; } if( arr[y][x] == BODY ){ // 是否吃到自己 alive=0; } if( arr[y][x] == BLANK){ arr[y][x] = HEAD; } if( arr[y][x] == FOOD ){ // 吃到食物 arr[y][x] = HEAD; score++; len++; food=0; snake[len-1].y=snake[len-2].y; // 蛇尾增加一节蛇身 snake[len-1].x=snake[len-2].x; make_food(); } if(DEBUG)printf("len:%d\n",len); if(DEBUG){ for(;i<len;i++){ printf("y,x:(%d,%d)\n",snake[i].y,snake[i].x); } } arr[ snake[len-1].y ][ snake[len-1].x ]=BLANK; // 先清除蛇尾显示 len--; for(cnt=1;cnt<=len-1;cnt++){ arr[ snake[cnt].y ][ snake[cnt].x ]=BODY; // 蛇身显示 } } void handle(){ init(); make_food(); while(alive && len<full){ move(); check_head(); speed=(speed > 9)?9:(score/5+1); if(alive){ Sleep(1000-speed*100); // 多久刷新一次 } } if( len == full){ printf("congratulations!\n"); } else{ printf("you lose\n"); } } LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("snake") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if(!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0; } hwnd = CreateWindow (szAppName, TEXT("snake"), WS_CAPTION | WS_SYSMENU , //| WS_THICKFRAME, 0, 0, 240, 240, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, SW_SHOW) ; //显示 UpdateWindow (hwnd) ; //ShowCursor(FALSE); //隐藏鼠标光标 while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } ShowCursor(TRUE); //显示鼠标光标 return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc ; static HDC hdcMem; HFONT hFont; static HBITMAP hBitmap; static int cxScreen, cyScreen; //屏幕的宽度 高度. static int iFontWidth=10, iFontHeight=15; //字体的宽度 高度 switch (message) { case WM_CREATE: cxScreen = GetSystemMetrics(SM_CXSCREEN) ; //屏幕宽度 cyScreen = GetSystemMetrics(SM_CYSCREEN) ; hdc = GetDC(hwnd); hdcMem = CreateCompatibleDC(hdc); hBitmap = CreateCompatibleBitmap(hdc, cxScreen, cyScreen); SelectObject(hdcMem, hBitmap); ReleaseDC(hwnd, hdc); //创建字体 hFont = CreateFont(iFontHeight, iFontWidth-5, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DRAFT_QUALITY, FIXED_PITCH | FF_SWISS, TEXT("Fixedsys")); SelectObject(hdcMem, hFont); DeleteObject (hFont) ; SetBkMode(hdcMem, TRANSPARENT); //设置背景模式为 透明 init(); make_food(); case WM_TIMER: hdc = GetDC(hwnd); PatBlt (hdcMem, 0, 0, cxScreen, cyScreen, BLACKNESS) ; //将内存设备映像刷成黑色 int y,x,size_s,size_p,size_l,size_w; TCHAR sText[64],pText[64],lText[64],wText[64]; move(); check_head(); speed=(speed > 9)?9:(score/5+1); SetTimer (hwnd, ID_TIMER, (1000-speed*100), NULL) ; size_s = wsprintf( sText,TEXT("your score: %d"),score); // 计算字符长度 size_p = wsprintf( pText,TEXT("current speed: %d"),speed); size_l = wsprintf( lText,TEXT("you lose")); size_w = wsprintf( wText,TEXT("you win")); SetTextColor(hdcMem, RGB(220, 220, 220)); // 字体颜色 TextOut(hdcMem, 0, 0 , sText, size_s); // hdc、x坐标、y坐标、字符地址、字符长度 TextOut(hdcMem, 0, 15, pText, size_p); if(alive && len<full){ for(y=0 ; y<HEIGHT ; y++ ){ for(x=0 ; x<WIDTH ; x++ ){ TextOut(hdcMem, x*10, (y+2)*15, &arr[y][x], 1); } } } else{ for(y=0 ; y<HEIGHT ; y++ ) for(x=0 ; x<WIDTH ; x++ ) if( y==0 || y==HEIGHT-1 || x==0 || x==WIDTH-1){ TextOut(hdcMem, x*10, (y+2)*15, &arr[y][x], 1); } if(alive == 0){ TextOut(hdcMem, 30, (2+HEIGHT/2)*15, lText, size_l); }else{ TextOut(hdcMem, 30, (2+HEIGHT/2)*15, wText, size_w); } BitBlt(hdc, 0, 0, cxScreen, cyScreen, hdcMem, 0, 0, SRCCOPY); KillTimer (hwnd, ID_TIMER) ; } BitBlt(hdc, 0, 0, cxScreen, cyScreen, hdcMem, 0, 0, SRCCOPY); ReleaseDC(hwnd, hdc); return 0; case WM_RBUTTONDOWN: KillTimer (hwnd, ID_TIMER) ; return 0; case WM_RBUTTONUP: SetTimer (hwnd, ID_TIMER, 10, NULL) ; return 0; // 按下 w a s d 时 case WM_CHAR: switch (wParam){ case 'w' : direct=(direct == 3)?3:1;break; case 'a' : direct=(direct == 4)?4:2;break; case 's' : direct=(direct == 1)?1:3;break; case 'd' : direct=(direct == 2)?2:4;break; default : direct; } return 0; //处理善后工作 case WM_DESTROY: KillTimer (hwnd, ID_TIMER) ; DeleteObject(hBitmap); DeleteDC(hdcMem); PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }