基于MFC的双人版俄罗斯方块游戏

Pubertyly

发布日期: 2020-12-30 09:03:19 浏览量: 247
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

1 题目要求

设计一个双人俄罗斯方块游戏。

2 功能需求

  • 实现双人俄罗斯方块 这是老师要求的最基本功能

  • 实现下一个砖块预测功能 这是附加功能,网上的俄罗斯方块小游戏都有这个功能

  • 隐藏工具栏、状态栏 这主要是为了美观

  • 实现难度可以选择 这是老师的代码已经实现好了的功能

  • 添加游戏说明菜单 添加作者菜单 添加网址超链接

  • 实现砖块三维化 为了美观

  • 实现总分统计功能,和每步消除所得分数显示

  • 实现背景音乐播放功能

  • 暂停功能

  • 增加了新的方块类

  • 设置窗口大小,禁用最大化按钮,禁止鼠标拖动改变窗口大小

3 总体设计

  • 矩形框类的设计:设计CBin类描述Tetris游戏的矩形框,用image的二维数组来描述这个矩形框。设置不同的值显示不同颜色的矩形,若没有砖块则为0。

  • 砖块的设计:设计CBrick抽象类来设计砖块,应用多态性的原理,其他不同类型的砖块类继承CBrick,来设计不同的砖块

  • 砖块在面板中的显示设计:在视图类中设计并显示砖块

3.1 系统模块

绘制系统功能模块图。

3.2 系统业务处理流程

4 详细设计

CBin类

函数名称 函数说明
CBin(unsigned int w, unsigned int h) 构造函数,用来初始化数据成员 width 和 height ,并为image 分配空间并初始化。
~CBin() 析构函数,删除在构造函数中为 image分配的空间。
void getImage(unsigned char** destImage) 将image 的数据拷贝到 destImage. 你可以假设destImage 指向的空间足够容纳image
void setImage(unsigned char** srcImage) 把srcImage 中的数据拷贝到image. 你可以假设srcImage 是一个合法的指针
unsigned int removeFullLines() 检查image ,如果任何一行完全填 满,则删除这一行,并让上面行的 数据下移一行,返回删除的总行数。

CBrick类

设计CBrick抽象类来设计砖块。

CBrick类的成员函数:

  1. virtual int shiftLeft(CBin* bin)=0; //将砖块在游戏的矩形框内左移一位
  2. virtual int shiftRight(CBin* bin)=0; //将砖块在游戏的矩形框内右移一位
  3. virtual int shiftDown(CBin* bin)=0; //将砖块在游戏的矩形框内下移
  4. virtual int rotateClockwise(CBin* bin)=0; //将砖块在游戏的矩形框内顺时针旋转
  5. virtual int checkCollision(CBin* bin)=0; //检查砖块是否冲突
  6. virtual void operator>>(unsigned char** binImage)=0; //重载运算符>>,通过设置映射到游戏矩形的二维数组binImage,设置砖块的颜色,这里假设binimage是一个合适的二维数组
  7. virtual void putAtTop(int newOrient, int newPosX)=0; //置顶

应用多态性的原理,其他不同类型的砖块类继承CBrick,来设计不同的砖块。

老师的代码只给出了三种不同的砖块,而我进行了完善,共设计了七种不同类型的砖块。

可视化设计:

在视图类中设计并显示砖块。

定义相关的变量并在构造函数中初始化。

部分代码如下:

  1. public:
  2. CNewTetrisDoc* GetDocument();
  3. COLORREF GetLightColor(COLORREF m_crBody);
  4. COLORREF GetDarkColor(COLORREF m_crBody);
  5. //////////////// 面板 1 ///////////////
  6. CBin*bin; //定义游戏矩形框指针
  7. CBrick*activeBrick; //定义指向当前下落砖块的指针
  8. int gameOver; //判断游戏是否结束
  9. int brickInFlight; //判断砖块是否处于下落状态
  10. int brickType; //砖块类别
  11. unsigned int initOrientation; //初始状态
  12. int notCollide; //冲突否
  13. unsigned int numLines; //消的行数
  14. unsigned char**outputImage;
  15. int difficulty; //定义难度
  16. void DrawImage(CBin *bin,CBin *bin2,unsigned char** image,unsigned char** image2,unsigned char **imageY1, unsigned char **imageY2,CDC *pDC);

添加DrawImage(CBin bin,CBin bin2,unsigned char image,unsigned char image2,unsigned char imageY1,unsigned char imageY2,CDC *pDC)函数,用来绘制游戏砖块。

部分代码如下:

  1. unsigned int width,i,j; unsigned int height; width=bin->getWidth(); height=bin->getHeight();
  2. int nSize = 20; //砖块大小
  3. CRect rect;
  4. CBitmap bitmap;
  5. bitmap.LoadBitmap(IDB_BITMAP3); //加载位图
  6. CBrush brush;
  7. brush.CreatePatternBrush(&bitmap);
  8. GetClientRect(&rect);
  9. pDC->FillRect(rect,&brush);
  10. /* pDC->FillSolidRect(rect,RGB(255,255,255)); //绘制背景色*/
  11. /* pDC->Rectangle(100,0,300,400); */
  12. char buf[100];
  13. sprintf(buf,"玩家一:分数:%d",numLines*10);
  14. pDC->TextOut(300,20,buf);
  15. /* pDC->Rectangle(450,0,650,400); */
  16. char buf2[100];
  17. sprintf(buf2,"玩家二:分数:%d",numLines2*10);
  18. pDC->TextOut(850,20,buf2);
  19. CRect rc,rc2;
  20. CRect rcY1;
  21. CRect rcY2; //定义矩形区
  22. COLORREF BrickColor[8]={0xFFFFFF,0xFF0000,0x00FF00,0x0000FF, 0x00FFFF,0xFFFF00,0x800000,0x800080};
  23. /////////游戏界面显示
  24. for (i=0; i<height; i++) //一行一行的画砖块
  25. {
  26. for(j=0;j<width;j++)
  27. {
  28. rc=CRect(j*nSize+100,i*nSize+100,(j+1)*nSize+100,(i+1)*nSize+100);
  29. rc2=CRect(j*nSize+750,i*nSize+100,(j+1)*nSize+750,(i+1)*nSize+100);
  30. //绘制面板1
  31. if (image[i][j]!=0)
  32. {
  33. pDC->FillRect(rc, &CBrush(BrickColor[image[i][j]])); //画临时砖块(运动中)
  34. pDC->Draw3dRect(rc,GetLightColor(BrickColor[image[i][j]]),GetDarkColor(BrickColor[image[i][j]]));
  35. }

在菜单中添加:

  • ID_Game_Start 开始(&S)

难度(&D)属性选择pop-up,子菜单为:

  • ID_DIFF_EASY 容易

  • ID_DIFF_MID中等

  • ID_DIFF_SUP高级

暂停(&P)属性选择pop-up,子菜单为:

  • IDD_Pause 暂停

  • IDD_Restart 开始

为其分别添加消息响应函数:

  1. void CNewTetrisView::OnGameStart()
  2. {
  3. // TODO: Add your command handler code here
  4. gameOver=0; brickInFlight=0; numLines=0;
  5. for (unsigned int i = 0; i<20; i++)
  6. {
  7. for (unsigned int j = 0; j<10; j++)
  8. {
  9. outputImage[i][j]=0;
  10. }
  11. }
  12. bin->setImage(outputImage);
  13. SetTimer(0,difficulty,NULL); //设置定时器
  14. }
  15. void CNewTetrisView::OnDiffEasy()
  16. {
  17. // TODO: Add your command handler code here
  18. difficulty=500;
  19. OnGameStart();
  20. }
  21. void CNewTetrisView::OnDiffMid()
  22. {
  23. // TODO: Add your command handler code here
  24. difficulty=350;
  25. OnGameStart();
  26. }
  27. void CNewTetrisView::OnDiffSup()
  28. {
  29. // TODO: Add your command handler code here
  30. difficulty=150;
  31. OnGameStart();
  32. }

暂停功能的实现:

按下暂停键的时候停止定时器,按下重新开始键开启定时器就行了。

  1. void CNewTetrisView::OnPause()
  2. {
  3. // TODO: Add your command handler code here
  4. KillTimer(0);
  5. }
  6. void CNewTetrisView::OnRestart()
  7. {
  8. // TODO: Add your command handler code here
  9. SetTimer(0,difficulty,NULL);
  10. }

为视图类添加WM_TIMER的消息响应函数。初始化面板,并设置定时器。(这里省略代码,最终版代码在下面)

在OnDraw()函数中调用DrawImage函数,显示面板。(这里省略代码,最终版代码在下面)

添加WM_KEYDOWON的消息响应函数,以响应用户按键。

  1. void CNewTetrisView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
  2. {
  3. //控制面板1
  4. if (nChar == VK_RIGHT) activeBrick->shiftRight(bin);
  5. else if (nChar == VK_LEFT) activeBrick->shiftLeft(bin);
  6. else if (nChar == VK_UP) activeBrick->rotateClockwise(bin);
  7. else if (nChar == VK_DOWN) activeBrick->shiftDown(bin);
  8. Invalidate();
  9. CView::OnKeyDown(nChar, nRepCnt, nFlags);
  10. }

这样一个基本的单人版俄罗斯方块游戏就做好了

进一步完善和改进:

双人版功能的实现:

就是把前面的代码重新写一遍再显示在另一个区域,用另一个定时器。

定义相关的变量并在构造函数中初始化。

在DrawImage函数中绘制游戏砖块,显示在另一个区域。

部分代码如下:

  1. if (0 != imageY2[i][j])
  2. {
  3. pDC->FillRect(rcY2, &CBrush(BrickColor[imageY2[i][j]]));//画临时砖块(运动中)
  4. pDC->Draw3dRect(rcY2,GetLightColor(BrickColor[imageY2[i][j]]),GetDarkColor(BrickColor[imageY2[i][j]]));
  5. }

在OnDraw()函数中调用DrawImage函数,显示面板。代码省略。

修改WM_TIMER的消息响应函数。初始化面板,并设置另一个定时器。

修改ID_Game_Start的消息响应函数,初始化面板,启动定时器。这里省略代码

修改WM_KEYDOWON的消息响应函数,以响应用户按键。

部分代码如下:

  1. //控制面板2
  2. if (nChar =='D' ) activeBrick2->shiftRight(bin2);
  3. else if (nChar == 'A') activeBrick2->shiftLeft(bin2);
  4. else if (nChar == 'W') activeBrick2->rotateClockwise(bin2);
  5. else if (nChar == 'S') activeBrick2->shiftDown(bin2);
  6. Invalidate();

修改暂停和开始的消息响应函数,这里省略代码。

预显示功能的实现:

在生成砖块的时候,一次生成两块,一块显示,一块下落。

  • 定义flag 用于标记是不是第一次下落,并初始化

  • 在DrawImage函数中绘制游戏砖块,显示在相应的区域

部分代码如下:

  1. //预显示
  2. for (i = 0; i < 4; ++i)//一行一行的画砖块
  3. {
  4. for (j = 2; j < 8; ++j)
  5. {
  6. rcY1 = CRect((j-2)*nSize+380, i*nSize+250, (j+1-2)*nSize+380, (i+1)*nSize+250);
  7. rcY2 = CRect((j-2)*nSize+530, i*nSize+250, (j+1-2)*nSize+530, (i+1)*nSize+250);
  8. //绘制面板
  9. //1
  10. if (0 != imageY1[i][j])
  11. {
  12. pDC->FillRect(rcY1, &CBrush(BrickColor[imageY1[i][j]]));//画临时砖块(运动中)
  13. pDC->Draw3dRect(rcY1,GetLightColor(BrickColor[imageY1[i][j]]),GetDarkColor(BrickColor[imageY1[i][j]]));
  14. }
  15. //2
  16. if (0 != imageY2[i][j])
  17. {
  18. pDC->FillRect(rcY2, &CBrush(BrickColor[imageY2[i][j]]));//画临时砖块(运动中)
  19. pDC->Draw3dRect(rcY2,GetLightColor(BrickColor[imageY2[i][j]]),GetDarkColor(BrickColor[imageY2[i][j]]));
  20. }
  21. }
  22. }

在OnDraw()函数中调用DrawImage函数,显示面板,代码如下:

  1. void CNewTetrisView::OnDraw(CDC* pDC)
  2. {
  3. CNewTetrisDoc* pDoc = GetDocument();
  4. ASSERT_VALID(pDoc);
  5. // pDC->Rectangle(0,0,200,400);
  6. // char buf[100];
  7. // sprintf(buf,"玩家一:分数:%d",numLines*10);
  8. // pDC->TextOut(200,20,buf);
  9. // pDC->Rectangle(350,0,550,400);
  10. // char buf2[100];
  11. // sprintf(buf2,"玩家二:分数:%d",numLines2*10);
  12. // pDC->TextOut(550,20,buf2);
  13. // DrawImage(bin,bin2,outputImage,outputImage2,pDC);
  14. int m_nWidth,m_nHeight;
  15. CDC m_memDC;
  16. CBitmap m_memBmp;
  17. /* m_memBmp.LoadBitmap(IDB_BITMAP2); //装载位图*/
  18. //1.用于映射屏幕的内存设备环境
  19. //获取游戏窗口的大小用于下面设置内存位图的尺寸
  20. CRect windowRect;
  21. GetClientRect(&windowRect);
  22. m_nWidth = windowRect.Width();
  23. m_nHeight = windowRect.Height();
  24. //内存设备环境与屏幕设备环境关联(兼容)
  25. m_memDC.CreateCompatibleDC(pDC);
  26. //内存位图与与屏幕关联(兼容),大小为游戏窗口的尺寸
  27. /* m_memBmp.CreateCompatibleBitmap(pDC,m_nWidth,m_nHeight); */
  28. m_memDC.FillSolidRect(windowRect,RGB(0,0,0));
  29. //内存设备环境与内存位图关联,以便通过m_memDC 在内存位图上作画
  30. m_memDC.SelectObject(&m_memBmp);
  31. DrawImage(bin,bin2,outputImage,outputImage2,outputImageY1,outputImageY2,pDC);
  32. //把内存DC 上的图形拷贝到电脑屏幕
  33. pDC->BitBlt(0,0,m_nWidth,m_nHeight,&m_memDC,0,0,SRCCOPY);
  34. m_memDC.DeleteDC(); //删除DC
  35. m_memBmp.DeleteObject(); //删除位图
  36. }

界面的美化:

修改标题:在doc类中OnNewDocument()函数修改

  1. BOOL CNewTetrisDoc::OnNewDocument()
  2. {
  3. if (!CDocument::OnNewDocument())
  4. return FALSE;
  5. // TODO: add reinitialization code here
  6. // (SDI documents will reuse this document)
  7. SetTitle(_T("俄罗斯方块"));
  8. return TRUE;
  9. }

设置标题栏图标: 首先在资源视图引入ICON文件

然后在CMainFrame中的OnCreate()函数中添加如下代码:

  1. //设置标题栏的图标
  2. HICON m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON1);
  3. SetIcon(m_hIcon,TRUE);
  4. SetIcon(m_hIcon,FALSE);

工具栏、菜单栏、状态栏的隐藏,最大化按钮的禁用,窗口大小的设定:

在CMainFrame中的OnCreate()函数中添加如下代码:

  1. m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
  2. EnableDocking(CBRS_ALIGN_ANY);
  3. DockControlBar(&m_wndToolBar);
  4. /* SetMenu(NULL);//菜单栏的隐藏*/
  5. ShowControlBar(&m_wndToolBar,FALSE,FALSE);//工具栏的隐藏
  6. ShowControlBar(&m_wndStatusBar,FALSE,FALSE);//状态栏的隐藏
  7. CMainFrame中的PreCreateWindow函数中添加如下代码:
  8. cs.cx = 1024;//设定窗口的大小 (窗口大小的确定可以用QQ截图)
  9. cs.cy = 571;
  10. cs.style &= ~WS_THICKFRAME;//使窗口不能用鼠标改变大小
  11. cs.style &= ~WS_MAXIMIZEBOX; //禁止窗口最大化

背景位图的插入:

  • 首先在资源视图导入文件(bmp格式)

  • 然后在DrawImage函数中添加如下代码:

  1. CBitmap bitmap;
  2. bitmap.LoadBitmap(IDB_BITMAP3); //加载位图
  3. CBrush brush;
  4. brush.CreatePatternBrush(&bitmap); //创建位图画刷
  5. GetClientRect(&rect); //获得客户区大小
  6. pDC->FillRect(rect,&brush); //将矩形区域用位图填充

添加背景音乐功能:

  • 首先先把你要添加的音乐用格式工厂转化为WAV格式,然后放入res文件夹。将VC98文件夹下的WINMM.LIB文件放入res文件夹

  • 然后添加lib文件 选择 Project->Add to Project->Files ,然后找到WINMM,添加进去。因为后面要用到PlaySound函数

  • 然后在资源中插入 WAVE 命名为 IDR_WAVE3

  • 然后在CNewTetrisView类实现文件中引入mmsystem.h

  • 然后在Gamestart函数中添加

  1. PlaySound((LPCTSTR)IDR_WAVE3, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC | SND_LOOP);

帮助对话框的添加:

首先插入对话框资源,然后为它对应一个类 HelpDialog.h

在视图类实现文件中添加#include “HelpDialog.h”

为帮助 菜单添加消息响应函数:

  1. HelpDialog dlg;
  2. dlg.DoModal();

超文本链接的添加:

在资源中插入光标文件 ID设为IDC_HAND

在Dialog中加入静态文本控件,并更改其ID为IDC_LINK。

在对话框头文件中加入数据成员:

  1. protected:
  2. RECT m_pRectLink; //用于保存静态文本框的屏幕坐标

在对话类成员函数OnInitDialog()中添加以下代码:

  1. GetDlgItem(IDC_LINK)->GetWindowRect(&m_pRectLink); //将静态文本的屏幕坐标存放在m—pRectLink中
  2. ScreenToClient(&m_pRectLink);//将屏幕坐标转换为客户坐标

变换鼠标形状,为对话框添加OnMouseMove函数 添加如下代码:

  1. //下面设置鼠标在静态文本区时,将光标设成小手状
  2. if (point.x>m_pRectLink.left&&point.x<m_pRectLink.right&&point.y>m_pRectLink.top&&point.y<m_pRectLink.bottom)
  3. //此处添加判断坐标算法
  4. {
  5. HCURSOR hCursor;
  6. hCursor=AfxGetApp()->LoadCursor(IDC_HAND);
  7. //将鼠标设为小手状
  8. SetCursor(hCursor);
  9. }
  10. //下面语句用来设置默认(箭头)鼠标形状,一般鼠标移开后窗口会自动恢复默认鼠标形状,可酌情添加
  11. // if (...)//此处酌情添加鼠标不在静态文本区的坐标算法,本例可不加
  12. // {
  13. // hCURSOR hCursor;
  14. //
  15. // hCursor=AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
  16. //
  17. // //将光标设为默认值(箭头)
  18. //
  19. // SetCursor(hCursor); }

添加鼠标单击响应事件,为Dialog类添加OnLButtonDown函数 添加如下代码:

  1. if (point.x>m_pRectLink.left&&point.x<m_pRectLink.right&&point.y>m_pRectLink.top&&point.y<m_pRectLink.bottom)//此处添加判断坐标算法
  2. { if (nFlags==MK_LBUTTON)//鼠标左键按下
  3. { //为改善鼠标效果,此处加入以上变换鼠标形状的代码
  4. ShellExecute(0, NULL, "http://blog.csdn.net/qq_29187355?", NULL,NULL, SW_NORMAL);
  5. //也可以添加电子邮件的链接
  6. }

5 测试与实现

游戏开始前

游戏进行中

上传的附件 cloud_download 基于MFC的双人版俄罗斯方块游戏.7z ( 31.25mb, 1次下载 )
error_outline 下载需要10点积分

发送私信

走在一起是缘分,一起在走是幸福

21
文章数
20
评论数
最近文章
eject