win32俄罗斯方块(c语言)
原创、转载请注明出处
windows游戏编程,参考《深入浅出MFC》,《windows游戏编程之从零开始》by浅墨,程序在《windows游戏编程之从零开始》中的人物移动程序基础上改的,毕竟win_main函数之类的固定流程都差不多。
其中没有用MFC,只是深入浅出MFC中有理解windows程序设计的部分,该程序是vs开发环境下的win32项目,浅墨的书中有介绍(MFC不能写游戏,离底层“远”)。
(结束暂停有空再补,游戏能玩)
当初写的时候网上win32俄罗斯方块各种各样,用的接口都不一样,无从参考,感觉以下自己的算法还不算冗长,当然也有偷懒的地方。
自己写的第一个小游戏,还是笔试时写的。。。
//-----------------------------------【程序说明】---------------------------------------------- // 程序名称:俄罗斯方块 // 2017年7月 Create by 刘 // 描述:基于Win32的俄罗斯方块游戏 //------------------------------------------------------------------------------------------------ //-----------------------------------【头文件包含部分】--------------------------------------- // 描述:包含程序所依赖的头文件 //------------------------------------------------------------------------------------------------ #include <windows.h> #include <tchar.h>//使用swprintf_s函数所需的头文件 //-----------------------------------【库文件包含部分】--------------------------------------- // 描述:包含程序所依赖的库文件 //------------------------------------------------------------------------------------------------ #pragma comment(lib,"winmm.lib") //调用PlaySound函数所需库文件 #pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件 //-----------------------------------【宏定义部分】-------------------------------------------- // 描述:定义一些辅助宏 //------------------------------------------------------------------------------------------------ #define WINDOW_WIDTH 800+20 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 #define WINDOW_HEIGHT 600+40 //为窗口高度定义的宏,以方便在此处修改窗口高度 #define WINDOW_TITLE L"【】" //为窗口标题定义的宏 #define OFFSET 10 //偏移量 //-----------------------------------【全局变量声明部分】------------------------------------- // 描述:全局变量的声明 //------------------------------------------------------------------------------------------------ //有单位方块的地方为1,没有的地方为0 struct TETRIS { int m_Matrix[4][4]; }; HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与两个全局内存DC句柄 DWORD g_tPre=0,g_tNow=0; //g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间 int g_iX=0,g_iY=0; //g_iX,g_iY分别表示贴图的横纵坐标 bool g_Map[25][20]; //记录堆积方块的状态,有方块的位置为TRUE,没有为FALSE int g_iBoxType,g_iBoxState,g_iNextBox; //记录当前下落方块的种类、状态(方向)和下个方块的种类,这里我们中以0,1,2,3代表方块顺时针0°,90°,180°,270° HBRUSH BlackBrush,WhiteBrush; //黑白画刷 int g_Score = 0; //分数 BOOL g_Flag = false; //按下开始键后为true TETRIS AllTetris[7][4] = {//一字 0,0,0,0, 1,1,1,1, 0,0,0,0, 0,0,0,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,0,0, 1,1,1,1, 0,0,0,0, 0,0,0,0, 0,1,0,0, 0,1,0,0, 0,1,0,0, 0,1,0,0, //山 0,1,0,0, 1,1,1,0, 0,0,0,0, 0,0,0,0, 0,1,0,0, 0,1,1,0, 0,1,0,0, 0,0,0,0, 1,1,1,0, 0,1,0,0, 0,0,0,0, 0,0,0,0, 0,1,0,0, 1,1,0,0, 0,1,0,0, 0,0,0,0, //田 1,1,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0, 1,1,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0, 1,1,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0, 1,1,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0, //右L 0,0,1,0, 1,1,1,0, 0,0,0,0, 0,0,0,0, 0,1,0,0, 0,1,0,0, 0,1,1,0, 0,0,0,0, 1,1,1,0, 1,0,0,0, 0,0,0,0, 0,0,0,0, 1,1,0,0, 0,1,0,0, 0,1,0,0, 0,0,0,0, //左L 1,0,0,0, 1,1,1,0, 0,0,0,0, 0,0,0,0, 0,1,1,0, 0,1,0,0, 0,1,0,0, 0,0,0,0, 0,0,0,0, 1,1,1,0, 0,0,1,0, 0,0,0,0, 0,1,0,0, 0,1,0,0, 1,1,0,0, 0,0,0,0, //上拐 0,1,0,0, 1,1,0,0, 1,0,0,0, 0,0,0,0, 1,1,0,0, 0,1,1,0, 0,0,0,0, 0,0,0,0, 0,1,0,0, 1,1,0,0, 1,0,0,0, 0,0,0,0, 1,1,0,0, 0,1,1,0, 0,0,0,0, 0,0,0,0, //下拐 1,0,0,0, 1,1,0,0, 0,1,0,0, 0,0,0,0, 0,0,0,0, 0,1,1,0, 1,1,0,0, 0,0,0,0, 1,0,0,0, 1,1,0,0, 0,1,0,0, 0,0,0,0, 0,0,0,0, 0,1,1,0, 1,1,0,0, 0,0,0,0, };//记录所有方块样式 //-----------------------------------【全局函数声明部分】------------------------------------- // 描述:全局函数声明,防止“未声明的标识”系列错误 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数 BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化 VOID Game_Main(HWND hwnd); //在此函数中进行绘图代码的书写 BOOL Game_CleanUp(HWND hwnd ); //在此函数中进行资源的清理 BOOL AbleDown(); //判断能否下降 BOOL AbleRotate(int t_iX, int t_iY, int t_iBoxType, int t_iBoxState); //判断能否旋转 BOOL AbleLeft(); //判断能否左移 BOOL AbleRight(); //判断能否右移 VOID AddOffset(); //把方块添加到堆积矩阵中再检查有没有可以消去的行,若有则消去 VOID OffsetLine(int T_iLine); //消去一行,被AddOffset()函数调用 VOID CreateBox(); //把下一个方块种类赋值给当前方块种类、随机产生下落位置和下一个方块种类 VOID Init_AllTetris(); //初始化AllTetris矩阵 //-----------------------------------【WinMain( )函数】-------------------------------------- // 描述:Windows应用程序的入口函数,我们的程序从这里开始 //------------------------------------------------------------------------------------------------ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) { //【1】窗口创建四步曲之一:开始设计一个完整的窗口类 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 wndClass.cbClsExtra = 0; //窗口类的附加内存,取0就可以了 wndClass.cbWndExtra = 0; //窗口的附加内存,依然取0就行了 wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //为hbrBackground成员指定一个白色画刷句柄 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 return -1; //【3】窗口创建四步曲之三:正式创建窗口 HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //【4】窗口创建四步曲之四:窗口的移动、显示与更新 MoveWindow(hwnd,250,80,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处 ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE if (!Game_Init (hwnd)) { MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 return FALSE; } //【5】消息循环过程 MSG msg = { 0 }; //定义并初始化msg while( msg.message != WM_QUIT ) //使用while循环,如果消息不是WM_QUIT消息,就继续循环 { if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 { TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 DispatchMessage( &msg ); //分发一个消息给窗口程序。 } else { g_tNow = GetTickCount(); //获取当前系统时间 if(g_tNow-g_tPre >= 50) //当此次循环运行与上次绘图时间相差0.05秒时再进行重绘操作 Game_Main(hwnd); } } //【6】窗口类的注销 UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类 return 0; } //-----------------------------------【WndProc( )函数】-------------------------------------- // 描述:窗口过程函数WndProc,对窗口消息进行处理 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) //switch语句开始 { case WM_TIMER: if(AbleDown())g_iY += 20;//判断能否下降,如果能下降,纵坐标加20(在这里判断而不是在绘图函数中是因为这样更能减少判断次数) else { AddOffset(); CreateBox(); }//不能下降时结束这一方块,添加到g_Map矩阵,检查是否有可消去的行,有则消去,创建下一方块(结束的一系列操作) break; case WM_KEYDOWN: //按下键盘消息 //判断按键的虚拟键码 switch (wParam) { case VK_ESCAPE: //按下【Esc】键 DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息 PostQuitMessage( 0 ); //结束程序 break; case VK_UP: //按下【↑】键 if(AbleRotate(g_iX,g_iY,g_iBoxType,g_iBoxState)) g_iBoxState = (g_iBoxState + 1) % 4 ; case VK_DOWN: //按下【↓】键 ,同定时下降消息 if(AbleDown())g_iY += 20; else { AddOffset(); CreateBox(); } break; case VK_LEFT: //按下【←】键 if(AbleLeft()) g_iX -= 20 ; break; case VK_RIGHT: //按下【→】键 if(AbleRight()) g_iX += 20 ; break; } break; //跳出该switch语句 case WM_DESTROY: //若是窗口销毁消息 Game_CleanUp(hwnd); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程 } return 0; //正常退出 } //-----------------------------------【Game_Init( )函数】-------------------------------------- // 描述:初始化函数,进行一些简单的初始化 //------------------------------------------------------------------------------------------------ BOOL Game_Init( HWND hwnd ) { SetTimer(hwnd,1,500,NULL); //计时器控制方块自动下落 g_hdc = GetDC(hwnd); //获取设备环境句柄 g_mdc = CreateCompatibleDC(g_hdc); //创建一个和hdc兼容的内存DC g_bufdc = CreateCompatibleDC(g_hdc); //再创建一个和hdc兼容的缓冲DC HBITMAP bmp,bmp1; //兼容位图 bmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); bmp1 = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); SelectObject(g_mdc,bmp); SelectObject(g_bufdc, bmp1); BlackBrush = CreateSolidBrush(RGB(0,0,0)); //创建黑白画刷 WhiteBrush = CreateSolidBrush(RGB(255,255,255)); //对后台进行背景的初试化 SelectObject(g_mdc,BlackBrush); Rectangle(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT); //后台黑色背景 SelectObject(g_mdc,WhiteBrush); Rectangle(g_mdc,50,50,450,550); //白色游戏区 Rectangle(g_mdc,525,50,725,250); //白色预测区 Rectangle(g_mdc,550,420,700,470); //开始键 Rectangle(g_mdc,550,500,700,550); //结束键 HFONT hFont; hFont=CreateFont(40,0,0,0,0,0,0,0,GB2312_CHARSET,0,0,0,0,TEXT("微软雅黑")); //创建字体 SelectObject(g_mdc,hFont); //选入字体到g_mdc中 SetBkMode(g_mdc, TRANSPARENT); //设置文字背景透明 SetTextColor(g_mdc,RGB(0,0,0)); //设置文字颜色 wchar_t str1[] = L"开始"; TextOut(g_mdc,590,425,str1,wcslen(str1)); wchar_t str2[] = L"结束"; TextOut(g_mdc,590,505,str2,wcslen(str2)); //缓冲区初始化方便之后的截图 Init_AllTetris(); //绘制g_bufdc中的内容 //初始化已堆积矩阵 for(int i=0;i<25;i++) for(int j=0;j<20;j++) g_Map[i][j]=false; ///初始化下一个方块的种类、当前方块的种类和下落位置 srand(timeGetTime()); g_iNextBox=rand()%7; g_iBoxType=rand()%7; int init_Situation=rand()%16; g_iX = 50 + init_Situation * 20; g_iY = 50; Game_Main(hwnd); return TRUE; } //-----------------------------------【Game_Main( )函数】-------------------------------------- // 描述:绘制函数,在此函数中进行绘制操作 //-------------------------------------------------------------------------------------------------- VOID Game_Main( HWND hwnd ) { SelectObject(g_mdc,WhiteBrush); Rectangle(g_mdc,50,50,450,550); Rectangle(g_mdc,525,50,725,250); //根据堆积矩阵贴堆积的方块 SelectObject(g_mdc,BlackBrush); for(int i=0;i<25;i++) for(int j=0;j<20;j++) if(g_Map[i][j]) Rectangle(g_mdc,50 + j * 20,50 + i * 20,50 + (j + 1) * 20,50 + (i + 1) * 20); //按照目前方块的种类取出对应方块,根据方块的状态截取方块图,放到“游戏区”中 BitBlt(g_mdc,g_iX,g_iY,80,80,g_bufdc,OFFSET + g_iBoxState * 80,OFFSET + g_iBoxType * 80,SRCAND); //按照下一个方块的的种类取出对应方块,取出第一状态显示到“下一个方块区”中 BitBlt(g_mdc,585,110,80,80,g_bufdc,OFFSET,OFFSET + g_iNextBox * 80,SRCAND); SelectObject(g_mdc,BlackBrush); Rectangle(g_mdc,570,260,700,360); SetTextColor(g_mdc,RGB(255,255,255)); //设置文字颜色 wchar_t str[20] = {}; swprintf_s(str,L"score:%d ",g_Score); TextOut(g_mdc,570,260,str,wcslen(str)); //将最后的画面显示在窗口中 BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY); g_tPre = GetTickCount(); //记录此次绘图时间 } //-----------------------------------【Game_CleanUp( )函数】-------------------------------- // 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作 //--------------------------------------------------------------------------------------------------- BOOL Game_CleanUp( HWND hwnd ) { //释放资源对象 DeleteObject(BlackBrush); DeleteObject(WhiteBrush); DeleteDC(g_bufdc); DeleteDC(g_mdc); ReleaseDC(hwnd,g_hdc); return TRUE; } //-----------------------------------【CreateBox()函数】-------------------------------- // 描述://把下一个方块种类赋值给当前方块种类、随机产生下落位置和下一个方块种类 //--------------------------------------------------------------------------------------------------- VOID CreateBox() { g_iBoxType = g_iNextBox; //当前方块 srand(timeGetTime()); //随机产生当前方块的下落位置 int T_ista = rand()%16; g_iX = 50 + T_ista * 20; g_iY = 50; srand(timeGetTime()); //随机产生下一个方块的种类 g_iNextBox = rand()%7; } //-----------------------------------【AbleDown()函数】-------------------------------- // 描述:判断能否下降 //--------------------------------------------------------------------------------------------------- BOOL AbleDown() { //根据方块种类、状态和坐标定位当前方块中每个单位方块的位置,下面有方块或者越界都不能继续下降,返回false,其他情况可以下降,返回true for(int i = 0 ; i < 4 ; i ++ ) for(int j = 0 ; j < 4 ; j ++) if(AllTetris[g_iBoxType][g_iBoxState].m_Matrix[i][j] == 1) if(g_Map[(g_iY - 50) / 20 + i + 1][(g_iX - 50) / 20 + j] || (g_iY + i * 20 + 20) >= 550 ) return false; return true; } //-----------------------------------【AbleRotate函数】-------------------------------- // 描述:判断能否旋转,传参试验 //--------------------------------------------------------------------------------------------------- BOOL AbleRotate(int t_iX, int t_iY, int t_iBoxType, int t_iBoxState) { t_iBoxState = (t_iBoxState + 1) % 4 ; for(int i = 0 ; i < 4 ; i ++ ) for(int j = 0 ; j < 4 ; j ++) if(AllTetris[t_iBoxType][t_iBoxState].m_Matrix[i][j] == 1) { if(g_Map[(t_iY - 50) / 20 + i][(t_iX - 50 ) / 20 + j] ) return false; if( (t_iX + j * 20) < 50 || (t_iX + j * 20 + 20) > 450 ) return false; } return true; } //-----------------------------------【AbleLeft()函数】-------------------------------- // 描述:判断能否左移 //--------------------------------------------------------------------------------------------------- BOOL AbleLeft() { for(int i = 0 ; i < 4 ; i ++ ) for(int j = 0 ; j < 4 ; j ++) if(AllTetris[g_iBoxType][g_iBoxState].m_Matrix[i][j] == 1) if( (g_iX + j * 20 - 20) < 50 || g_Map[(g_iY - 50) / 20 + i][(g_iX - 50 ) / 20 + j - 1] ) return false; return true; } ///-----------------------------------【AbleRight()函数】-------------------------------- // 描述:判断能否右移 //--------------------------------------------------------------------------------------------------- BOOL AbleRight() { for(int i = 0 ; i < 4 ; i ++ ) for(int j = 0 ; j < 4 ; j ++) if(AllTetris[g_iBoxType][g_iBoxState].m_Matrix[i][j] == 1) if( (g_iX + j * 20 + 40) > 450 || g_Map[(g_iY - 50) / 20 + i][(g_iX - 50 ) / 20 + j + 1] ) return false; return true; } //-----------------------------------【AddOffset()函数】-------------------------------- // 描述:把方块添加到堆积矩阵中再检查有没有可以消去的行,若有则消去 //--------------------------------------------------------------------------------------------------- VOID AddOffset() { //把方块添加到堆积矩阵中 for(int i = 0 ; i < 4 ; i ++) for(int j = 0 ; j < 4 ; j ++) if(AllTetris[g_iBoxType][g_iBoxState].m_Matrix[i][j] == 1) { g_Map[(g_iY - 50) / 20 + i][(g_iX - 50) / 20 + j] = true; } //检查有没有可以消去的行,若有则消去 int NowLine = (g_iY - 50) / 20 + 3; for(int i = 0 ; i < 4 ; i ++ ) { bool AbleDLine = true; for(int j = 0 ; j < 20 ; j ++ ) if(!g_Map[NowLine][j])//有个有空格的就判为不能消去该行 { NowLine --; AbleDLine = false; break; } if(AbleDLine)OffsetLine(NowLine); //若有可消去行,调用消去函数消去该行 } } //-----------------------------------【OffsetLine(int T_iLine)函数】-------------------------------- // 描述:按照要消去的行修改堆积矩阵 //--------------------------------------------------------------------------------------------------- VOID OffsetLine(int T_iLine) { for(int i = T_iLine ; i > 0 ; i --) { for(int j = 0 ; j < 20 ; j ++) { if(g_Map[i-1][j])g_Map[i][j] = true; if(!g_Map[i-1][j])g_Map[i][j] = false; } } g_Score += 20 ; } //-----------------------------------【Init_AllTetris()函数】-------------------------------- // 描述:初始化AllTetris矩阵 //--------------------------------------------------------------------------------------------------- VOID Init_AllTetris() { SelectObject(g_bufdc,WhiteBrush); Rectangle(g_bufdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT); SelectObject(g_bufdc,BlackBrush); for(int i = 0;i < 7;i ++) //在bufdc中绘制所有方块以便后面截取 for(int j = 0; j < 4; j ++) for(int k = 0; k < 4; k ++) for(int x = 0; x < 4; x ++) if(AllTetris[i][j].m_Matrix[k][x] == 1) Rectangle(g_bufdc, OFFSET+j * 80 + x * 20, OFFSET + i * 80 + k * 20, OFFSET + j * 80 + (x + 1) * 20, OFFSET + i * 80 + (k + 1) * 20); }
写的时候还没看《c++primer》勿喷