C++实现俄罗斯方块(windows API)
本文分享的这些俄罗斯方块代码是我最近放假在家里自己写的,虽然以前有过看别人写的代码,但是那个游戏代码好像不是很全面,因为无法实现全部的方块和实现随机的产生任意方向的方块,现在也基本上是忘光了当时的代码,下面的这些代码是我最近写的,没有参考其他人的代码,真正写俄罗斯方块起来感觉真的是挺难的,关键是在于方块的旋转。当然下面的代码仅仅是一个框架,只能够实现大致上的功能,还不全面,贴出来和大家交流学习。
编译器是code::block + MinGW,感觉CB这个IDE真的是太强大,太棒了,下面的代码直接复制到VC里面运行应该不会出错,有个问题一直不知道怎么解决,就是更新客户区时窗口总是闪不知道有哪位达人能指点我一下。有的都是windowsAPI写的,对windows编程还不是很懂,望大家多多留言,指点一下本人。
#include#include #include #include usingnamespacestd; #defineCellWidth20 #defineMAP_WIDTH12 #defineMAP_HEIGHT18 #defineID_TIMER1 classmap_floor; classBlock; LRESULTCALLBACKWindowProcedure(HWND,UINT,WPARAM,LPARAM); /*Maketheclassnameintoaglobalvariable*/ charszClassName[]="CodeBlocksWindowsApp"; intWINAPIWinMain(HINSTANCEhThisInstance, HINSTANCEhPrevInstance, LPSTRlpszArgument, intnCmdShow) { HWNDhwnd;/*Thisisthehandleforourwindow*/ MSGmessages;/*Heremessagestotheapplicationaresaved*/ WNDCLASSEXwincl;/*Datastructureforthewindowclass*/ /*TheWindowstructure*/ wincl.hInstance=hThisInstance; wincl.lpszClassName=szClassName; wincl.lpfnWndProc=WindowProcedure;/*Thisfunctioniscalledbywindows*/ wincl.style=CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW;/*Catchdouble-clicks*/ wincl.cbSize=sizeof(WNDCLASSEX); /*Usedefaulticonandmouse-pointer*/ wincl.hIcon=LoadIcon(NULL,IDI_APPLICATION); wincl.hIconSm=LoadIcon(NULL,IDI_APPLICATION); wincl.hCursor=LoadCursor(NULL,IDC_ARROW); wincl.lpszMenuName=NULL;/*Nomenu*/ wincl.cbClsExtra=0;/*Noextrabytesafterthewindowclass*/ wincl.cbWndExtra=0;/*structureorthewindowinstance*/ /*UseWindows'sdefaultcolourasthebackgroundofthewindow*/ wincl.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);//COLOR_BACKGROUND; /*Registerthewindowclass,andifitfailsquittheprogram*/ if(!RegisterClassEx(&wincl)) return0; /*Theclassisregistered,let'screatetheprogram*/ hwnd=CreateWindowEx( 0,/*Extendedpossibilitesforvariation*/ szClassName,/*Classname*/ "Code::BlocksTemplateWindowsApp",/*TitleText*/ WS_OVERLAPPEDWINDOW,/*defaultwindow*/ CW_USEDEFAULT,/*Windowsdecidestheposition*/ CW_USEDEFAULT,/*wherethewindowendsuponthescreen*/ CW_USEDEFAULT,/*Theprogramswidth*/ CW_USEDEFAULT,/*andheightinpixels*/ NULL,/*Thewindowisachild-windowtodesktop*/ NULL,/*Nomenu*/ hThisInstance,/*ProgramInstancehandler*/ NULL/*NoWindowCreationdata*/ ); /*Makethewindowvisibleonthescreen*/ ShowWindow(hwnd,nCmdShow); /*Runthemessageloop.ItwillrununtilGetMessage()returns0*/ while(GetMessage(&messages,NULL,0,0)) { /*Translatevirtual-keymessagesintocharactermessages*/ TranslateMessage(&messages); /*SendmessagetoWindowProcedure*/ DispatchMessage(&messages); } /*Theprogramreturn-valueis0-ThevaluethatPostQuitMessage()gave*/ returnmessages.wParam; } enum{e_LINE,e_CORNER,e_STAIR,e_TANCK,e_TIAN}; constintTOTAL_BLOCK_STYLE=5;//方块类型有4种 classBlock { public: Block(intx=100,inty=100); Block(constBlock&rh)//复制构造函数,可能没什么用,但是还是定义它吧 { this->m_style=rh.m_style; this->m_direct=rh.m_direct; for(inti=0;i<4;i++) this->m_block[i]=rh.m_block[i]; } Block&operator=(constBlock&rh)//重载=号,实现方块的赋值 { this->m_style=rh.m_style; this->m_direct=rh.m_direct; for(inti=0;i<4;i++) this->m_block[i]=rh.m_block[i]; return*this; } ~Block(){} intcreate_block(intx=100,inty=100); //显示在游戏区内移动的方块 intshow_block(HDChdc,constPOINT&GameLeftTop); //显示将要出现的方块,即游戏区左边的方块 intshow_next_block(HDChdc); //旋转,该函数比较难实现,代码量也比较大,以后有时间在慢慢优化,关于解析看定义处 introtate(); //产生随机方块 intrandom_block(); //下面为方块移动的成员函数 intget_block_height(){returnm_block[1].y;} intmove_down(constRECT&GameClient); intmove_left(constRECT&GameClient); intmove_right(constRECT&GameClient); intmove_up(constRECT&GameClient); intmove_to(intx,inty); //检测方块是否在游戏区内 //intcheck_block(constmap_floor&map,constRECT&GameClent); intcheck_block(constmap_floor&map,constPOINT&LeftTopScrCdnt); intprint_to_map(map_floor&map,constPOINT&LeftTopScrCdnt); private: intm_style;//方块的风格样式,具体看定义的枚举变量 intm_direct;//方块的方向,是对m_style的具体数据 POINTm_block[4];//下标为1的方块是中心坐标,旋转都是围绕着该方块进行,这样可以利于旋转和逻辑清晰 }; classmap_floor { public: map_floor() { ZeroMemory(m_block_bar,sizeof(int)*12*18); } ~map_floor(){} voidshow_block_bar(HDChdc,constPOINT&LeftTopScrCdnt) { for(inti=0;i =MAP_WIDTH||y>=MAP_HEIGHT)//不用检测y<0的情况 return0; if(y<0)continue; if(map.m_block_bar[y][x]) return0; } return1; } intBlock::move_down(constRECT&GameClient)//下移,由计时器消息调用 { inti; //for(i=0;i<4;i++) //{ //if(m_block[i].y==GameClient.bottom-CellWidth) //return0; //} for(i=0;i<4;i++) { m_block[i].y+=CellWidth; } return1; } intBlock::move_up(constRECT&GameClient) { move_to(m_block[1].x,m_block[1].y-CellWidth); return1; } intBlock::move_left(constRECT&GameClient) { move_to(m_block[1].x-CellWidth,m_block[1].y); return1; } intBlock::move_right(constRECT&GameClient) { move_to(m_block[1].x+CellWidth,m_block[1].y); return1; } intBlock::create_block(intx,inty) { m_block[1].x=x; m_block[1].y=y; random_block(); rotate(); return1; } intBlock::move_to(intx,inty) { intVx=x-m_block[1].x; intVy=y-m_block[1].y; for(inti=0;i<4;i++) { m_block[i].x+=Vx; m_block[i].y+=Vy; } } intBlock::print_to_map(map_floor&map,constPOINT&LeftTopScrCdnt) { intx,y; inti,j; for(i=0;i<4;i++) { x=(m_block[i].x-LeftTopScrCdnt.x)/CellWidth; y=(m_block[i].y-LeftTopScrCdnt.y)/CellWidth; if(x<0||x>=MAP_WIDTH||y<0||y>=MAP_HEIGHT)//为保安全,测试之用,完成后将被注释掉 return0; map.m_block_bar[y][x]=1; for(j=0;j =0;j--) { if(map.m_block_bar[j][i]!=5) { map.m_block_bar[idx--][i]=map.m_block_bar[j][i]; } } while(idx>=0) { map.m_block_bar[idx--][i]=0; } } return1; } //下面该函数功能是实现方块旋转,可以说是整个【俄罗斯方块】的难点所在,也是其核心部分 //方块用以数组block【4】表示,其余3个方格都将围绕block【1】旋转,方块由于有不对称方块 //存在,我原本是要分7种,但是后面代码量太大了,所以我将方块根据样式归为了四种,分别是: // //e_LINE线形就是一条线的那个,这个是对称的方块,只需分两个方向分别为横向和纵向,方向 //用m_direct保持,其他的方块一样 // //e_TANCK坦克形这个是方块是对称的,分四种方向,根据m_direct对4进行求余的方法可以大大缩减 //代码量,对于下面两种方块也是利用了求余的方式化简许多,才使得代码不会那么冗余, //这是后面我才想到的方法。 // //e_STAIR楼梯形这个方块相对前面两种来说有点难度,主要是因为它不是对称的,但是相对下面的这种 //来说比较简单,原本我没用对m_direct求余的方法时,我将它分为了e_STAIR_BACK和e_STAIR_FRONT //两类来讨论的,后面发现代码可以缩减才将其归为一类只要记住block【0】和block【1】的位置不会 //变化,变化的是block【2】和block【3】,block【2】相对block【1】上移或下移,x坐标与block【1】 //相同,block【3】.y一直在block【1】下面一行,相对其左右变化 // //e_CORNER角形这个方块个人觉得是最难旋转的方块,与上面一种异样,原本我将它分为e_CORNER_FRONT,e_CORNER_BACK //两类,每类有四个方向的变化,后来根据求余可以将同一个方向的变化变为一种,只是block【3】号方块要 //根据m_direct方向来进行调整 intBlock::rotate() { switch(m_style) { casee_LINE: { switch(m_direct) { case0://横向转为纵向 { for(inti=0;i<4;i++) { m_block[i].x=m_block[1].x; m_block[i].y=m_block[1].y+(1-i)*CellWidth; } m_direct=1; } break; case1://纵向转为横向 { for(inti=0;i<4;i++) { m_block[i].y=m_block[1].y; m_block[i].x=m_block[1].x+(1-i)*CellWidth; } m_direct=0; } break; } } break; //下面为楼梯风格的方块,由于其不是对称的分类为正反两种,正反种风格各有两种变化, //m_direct%==0是正反两面的同种变化 casee_STAIR: { intflag; flag=m_direct<2?1:-1; m_block[0].x=m_block[1].x+flag*CellWidth; m_block[0].y=m_block[1].y; m_block[2].x=m_block[1].x; m_block[3].y=m_block[1].y+CellWidth; if(m_direct%2==0) { m_block[2].y=m_block[1].y-CellWidth; m_block[3].x=m_block[1].x+flag*CellWidth; m_direct++; } else { m_block[2].y=m_block[1].y+CellWidth; m_block[3].x=m_block[1].x-flag*CellWidth; if(m_direct<2)m_direct=0; elsem_direct=2; } } break; //角形方块,与楼梯形方块一样非对称,有正反俩个种,每种有四种变化, //下面根据m_direct%4的值将这些变化归类解决,对于正,反面对应的相同 //变化的方向,只有block【3】方格位置不一样,可以看我画的图对比即可了解 casee_CORNER: { switch(m_direct%4) { case0: { m_block[0].x=m_block[1].x+CellWidth; m_block[0].y=m_block[2].y=m_block[1].y; m_block[2].x=m_block[1].x-CellWidth; m_block[3].x=m_block[1].x-CellWidth; if(m_direct>=4)m_block[3].y=m_block[1].y-CellWidth; elsem_block[3].y=m_block[1].y+CellWidth; m_direct++; } break; case1: { m_block[0].x=m_block[2].x=m_block[1].x; m_block[0].y=m_block[1].y+CellWidth; m_block[2].y=m_block[1].y-CellWidth; if(m_direct>=4)m_block[3].x=m_block[1].x+CellWidth; elsem_block[3].x=m_block[1].x-CellWidth; m_block[3].y=m_block[1].y-CellWidth; m_direct++; } break; case2: { m_block[0].x=m_block[1].x-CellWidth; m_block[0].y=m_block[2].y=m_block[1].y; m_block[2].x=m_block[1].x+CellWidth; m_block[3].x=m_block[1].x+CellWidth; if(m_direct>=4)m_block[3].y=m_block[1].y+CellWidth; elsem_block[3].y=m_block[1].y-CellWidth; m_direct++; } break; case3: { m_block[0].x=m_block[2].x=m_block[1].x; m_block[0].y=m_block[1].y-CellWidth; m_block[2].y=m_block[1].y+CellWidth; if(m_direct>=4){m_block[3].x=m_block[1].x-CellWidth;m_direct=4;} else{m_block[3].x=m_block[1].x+CellWidth;m_direct=0;} m_block[3].y=m_block[1].y+CellWidth; } break; default: break; } } break; casee_TANCK://坦克形方块,与线形方块一样是对称的,分四种变化 { switch(m_direct%2) { case0: { m_block[0].x=m_block[2].x=m_block[1].x; m_block[0].y=m_block[1].y-CellWidth; m_block[2].y=m_block[1].y+CellWidth; intflag=m_direct==0?1:-1; m_block[3].x=m_block[1].x+flag*CellWidth; m_block[3].y=m_block[1].y; m_direct++; } break; case1: { m_block[0].y=m_block[2].y=m_block[1].y; m_block[0].x=m_block[1].x-CellWidth; m_block[2].x=m_block[1].x+CellWidth; m_block[3].x=m_block[1].x; intflag=m_direct==3?-1:1; m_block[3].y=m_block[1].y+flag*CellWidth; if(m_direct==3)m_direct=0; elsem_direct++; } break; default: break; } } break; casee_TIAN: { m_block[0].y=m_block[1].y; m_block[0].x=m_block[1].x+CellWidth; m_block[2].x=m_block[1].x; m_block[2].y=m_block[1].y+CellWidth; m_block[3].x=m_block[1].x+CellWidth; m_block[3].y=m_block[1].y+CellWidth; } break; default: break; } return0; } intBlock::show_block(HDChdc,constPOINT&GameLeftTop) { for(inti=0;i<4;i++) { if(m_block[i].y>=GameLeftTop.y) Rectangle(hdc,m_block[i].x,m_block[i].y,m_block[i]. x+CellWidth,m_block[i].y+CellWidth); if(i==0)//测试所用,完成后将会被注释掉 {MoveToEx(hdc,m_block[i].x,m_block[i].y,NULL); LineTo(hdc,m_block[i].x+CellWidth,m_block[i].y+CellWidth);} } return1; } intBlock::show_next_block(HDChdc) { for(inti=0;i<4;i++) { Rectangle(hdc,m_block[i].x,m_block[i].y,m_block[i]. x+CellWidth,m_block[i].y+CellWidth); } return1; } Blockblock,next_block,try_block; map_floormap;intd=0; LRESULTCALLBACKWindowProcedure(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam) { HDChdc; PAINTSTRUCTps; //游戏客户区 staticRECTGameClient; //一个方格的像素为CellWidth=20游戏区宽12个方格高18个方格 constintWidth=240,Height=360; staticPOINTLeftTopScrCdnt;//游戏区得左上角坐标 switch(message) { caseWM_CREATE: SetTimer(hwnd,ID_TIMER,500,NULL); return0; caseWM_SIZE: GetClientRect(hwnd,&GameClient); LeftTopScrCdnt.x=(GameClient.right-GameClient.left)/2-Width/2; LeftTopScrCdnt.y=GameClient.top+50; GameClient.left=LeftTopScrCdnt.x; GameClient.top=LeftTopScrCdnt.y; GameClient.right=LeftTopScrCdnt.x+Width; GameClient.bottom=LeftTopScrCdnt.y+Height; //创建下一个将要出现的方块 next_block.create_block(GameClient.right+2*CellWidth,(GameClient.bottom+GameClient.top)/2-3*CellWidth); block.move_to((GameClient.right+GameClient.left)/2,GameClient.top-CellWidth); break; caseWM_TIMER: block.move_down(GameClient); if(!block.check_block(map,LeftTopScrCdnt))//检测方块的碰撞,如果则说明方块到底底部,将其上移然后打印进地图 { block.move_up(GameClient); if(!block.check_block(map,LeftTopScrCdnt)|| block.get_block_height()<=LeftTopScrCdnt.y)//检测游戏是否结束 { KillTimer(hwnd,ID_TIMER); d=4; } block.print_to_map(map,LeftTopScrCdnt); SendMessage(hwnd,WM_KEYDOWN,VK_ESCAPE,0); } InvalidateRect(hwnd,NULL,true); break; caseWM_PAINT: hdc=BeginPaint(hwnd,&ps); MoveToEx(hdc,LeftTopScrCdnt.x,LeftTopScrCdnt.y,NULL); Rectangle(hdc,GameClient.left,GameClient.top,GameClient.right,GameClient.bottom);//游戏区边框 SelectObject(hdc,GetStockObject(BLACK_BRUSH)); map.show_block_bar(hdc,LeftTopScrCdnt); block.show_block(hdc,LeftTopScrCdnt); next_block.show_next_block(hdc); EndPaint(hwnd,&ps); break; caseWM_KEYDOWN: InvalidateRect(hwnd,NULL,true); switch(wParam) { caseVK_SPACE: { try_block=block; try_block.rotate(); if(try_block.check_block(map,LeftTopScrCdnt)) block=try_block; break; } caseVK_LEFT: { block.move_left(GameClient); if(!block.check_block(map,LeftTopScrCdnt)) block.move_right(GameClient); } break; caseVK_RIGHT: { block.move_right(GameClient); if(!block.check_block(map,LeftTopScrCdnt)) block.move_left(GameClient); } break; caseVK_DOWN: { //block.move_down(GameClient); SendMessage(hwnd,WM_TIMER,0,0); } break; caseVK_ESCAPE://测试用,完成后将会被注释掉 { block=next_block; next_block.create_block(GameClient.right+2*CellWidth,(GameClient.bottom+GameClient.top)/2-3*CellWidth); block.move_to((GameClient.right+GameClient.left)/2,GameClient.top-CellWidth); } break; default: break; } break; caseWM_DESTROY: PostQuitMessage(0); return0; } returnDefWindowProc(hwnd,message,wParam,lParam); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。