这个俄罗斯方块令我自豪的地方是,我没有参考任何人的代码和思想,完全凭借自己的想象力来实现的。因此本游戏的算法不是很好,现在看来有些拙劣。不过好歹也是我寒假里最大的一个项目啊,还是自己鼓励自己一下吧!
写完本程序后感觉收获很多,大一新生不是没有能力开发图形界面的东西,而是缺乏一种勇气以及耐心。
开始时的界面:
结束后的界面:
目录
寒假程序设计报告... 1
一、 应用程序的最终界面...3
1.1. 制作目的...4
1.2关于编译软件... 4
1.3关于兼容性... 4
第二章 概要设计... 4
2.1 软件的主要功能设计... 4
第三章 游戏的实现... 5
3.1函数、变量及资源的声明... 5
3.2 主要功能的实现... 7
第四章 结果与讨论... 12
4.1 程序运行... 12
4.2 错误调试与分析... 12
本游戏是通过键盘控制下落体的移动,最终运行界面如下:
游戏结束的画面如下:
一、通过编写俄罗斯方块来提高自己对于复杂问题的独立分析能力以及独立解决问题能力,所以此次程序设计没有参考他人编写的代码及思路。
二、验证一下自己上一个学期的学习成果,对上学期学习的C++知识加以巩固和提高。
三、通过编写俄罗斯方块来提高自己对于Windows API 学习的乐趣,加深对GDI绘图的理解。
四、通过编写俄罗斯方块的方式来不断的学习新的知识以丰富自己的寒假生活,过一个更有意义的寒假。
本程序采用Microsoft Visual Studio 2010进行编译的。Visual Studio是微软公司推出的开发环境。是目前最流行的Windows平台应用程序开发环境。Visual Studio 2010版本于2010年4月12日上市,其集成开发环境(IDE)的界面被重新设计和组织,变得更加简单明了。Visual Studio 2010同时带来了 NET Framework 4.0、Microsoft Visual Studio 2010 CTP( Community Technology Preview--CTP),并且支持开发面向Windows 7的应用程序。除了Microsoft SQL Server,它还支持 IBM DB2和Oracle数据库。
本程序经过调试可以在Windows XP,Windows 7下正常运行。
根据分析俄罗斯方块主要需要以下几个功能:
一、暂停游戏
二、继续游戏
三、开始新的游戏
四、选择不同的难度
五、背景音乐的播放
六、退出
具体的程序结构如下图所示
根据分析后的俄罗斯方块的结构设计出相应的流程。俄罗斯方块的主要内容包括:游戏开始,随机生成下落物体,根据键盘的按键做出相应的变化。在游戏进行过程中可以开始新的游戏,可以改变游戏的难度,暂停游戏等。
根据程序的结构,将俄罗斯方块的功能细化成为相应的函数
voidInitialize_MAP();//初始化地图
voidInitialize_OBJECT();//将Object中的数据传递到OBJECT中
voidRUS::GETObjectPosition(int *XX,int *YY,int OBject[4][4]);//将子形状坐标化
voidInitializeXY();//将OBJECT坐标化
voidDrawDeformation();//画出下一个OBJECT的形状
voidVerticalMove(bool);//使红色方块的坐标向下移动
voidRightMove(bool);//使红色方块的坐标向右移动
voidLeftMove(bool);//使红色方块的坐标向左移动
voidDeleteLine();//得分后删除填满的行
voidJudgeGetScore();//判断能否得分
boolDeformation();//变形函数
boolJudgeBottom();//判断红的方块是否到底
boolJudgeRight();//判断红色方块是否到最右端
boolJudgeLeft();//判断红色方块是否到达最左端
boolGameOver;//判断游戏是否结束
intGetHeight();//取得OBJECT的最大高度
intGetWideth();//取得OBJECT的最大宽度
voidCheckDownWhite();//坐标化覆盖红色方块的方块的坐标(根据OBJECT的坐标)
voidDrawGameOver();//画出GameOver
int*GetX();//取得OBJECT的横坐标
int*GetY();//取得OBJECT的纵坐标
intOBJECT[4][4];//储存下落物体的数据
intNEXT_DEFORMATION;//下一个的形状
int x[4],y[4];//用于记录OBJECT在MAP中的坐标
intHEIGHT;//记录OBJECT的高度
intWIDETH;//记录OBJECT的宽度
intNOW_DEFORMATION;//当前形状
intCHANGE;//子类型(形状)
intDeletLinePosition_Y;//记录消掉行的Y坐标
intDeleteHeight;//需要删除的高度
intSCORE;//分数,在初始化地图时初始化SCORE
intNUM;//记录需要消掉的行数
intMAP[12][22];//12列22行
intNEXTOBJECT[4][4];//记录下一个OBJECT的数据
struct WPOINT//用于覆盖的方块
{
int x;
int y;
}wPoint[4],rPoint[4],lPoint[4],TempPoint[4];
};// wPoint覆盖向下移动的方块
// rPoint覆盖向右移动的方块
// lPoint覆盖向左移动的方块
// TemPoint覆盖变形前的方块
bool Mm=0;//当难度更改后重画难度的显示区域
UINT Time=1000;
int num=1;//控制消行时的闪烁次数
HWND hWnd;
int LINE=0;//用于gameover后刷屏
bool Mm=0;//当难度更改后重画难度的显示区域
bool IsMoveRight=false,IsMoveLeft=false,IsMoveVertical=true,IsDeformation=false,puse=false,IsShining=false;intmm=1;//刷屏以及判断能否移动的开关
intWhichLineNeedDelete[4]={0,0,0,0};
菜单:
ID_32772://第一级
ID_32773://第二级
ID_32774://第三级
ID_32775://第四级
ID_32777://暂停
ID_32778://继续
ID_32779://新游戏
IDM_ABOUT://关于
IDM_EXIT://退出
头文件:
#include "stdafx.h"
#include "俄罗斯方块A.h"
#include
#include
#pragma comment(lib, "WINMM.LIB")
通过void Initialize_MAP(); 函数初始化一个22行12列的数组,并将第[0]、[21]行的所有列的值赋值为1,第[0]、[10]列的所有行的值赋值为1,其余部分赋值为0.这样做是为了标示出地图的边界,以及被物块占据的位置。
先将数据写入到Object数组中如:
intObject10[4][4]={ 0,0,0,0,
1,1,1,1,
0,0,0,0,
0,0,0,0};//一字型
然后再利用随机数发生器随机生成1—7之间的任意一个数子,通过switch 开关选择相应的函数开初始化OBJECT。
具体代码如下:
srand(time(NULL));
static int DEFORMATION=((rand() % 7)+1)*10;//形状编号
NOW_DEFORMATION=DEFORMATION;
switch(DEFORMATION)
{
case 10:
for(int i=0;i<4;i++)
for(intj=0;j<4;j++)
OBJECT[i][j]=Object10[i][j];
break;
case 20:
for(int i=0;i<4;i++)
for(intj=0;j<4;j++)
OBJECT[i][j]=Object20[i][j];
break;
case 30:
for(int i=0;i<4;i++)
for(intj=0;j<4;j++)
OBJECT[i][j]=Object30[i][j];
break;
case 40:
for(int i=0;i<4;i++)
for(intj=0;j<4;j++)
OBJECT[i][j]=Object40[i][j];
break;
case 50:
for(int i=0;i<4;i++)
for(intj=0;j<4;j++)
OBJECT[i][j]=Object50[i][j];
break;
case 60:
for(int i=0;i<4;i++)
for(intj=0;j<4;j++)
OBJECT[i][j]=Object60[i][j];
break;
case 70:
for(int i=0;i<4;i++)
for(intj=0;j<4;j++)
OBJECT[i][j]=Object70[i][j];
break;
default: break;
}
DEFORMATION=((rand() % 7) +1)*10;
NEXT_DEFORMATION=DEFORMATION;
下落物块的位置是由x,y两个变量储存,在窗口创建时建立一个时钟,第一级别的速度是时钟每秒更新一次,时钟每更新一次,y的值减一。在y值减少之前需要Judgement()函数判断MAP[x][y-1]的值,如果MAP[x][y-1]的值为一则停止y的自减,返回一个false给CheckDownWhite()并将OBJECT的中的数据写入MAP中以标志该处被占据。如果MAP[x][y-1]的值为0,则执行y--.
当按下左右光标键时通过WM_KEYDOWN来处理消息。具体实现过程如下:
case WM_KEYDOWN:
switch(wParam)
{
case VK_UP:
if(!puse&&!rus.GameOver)
{IsDeformation=rus.Deformation();//判断是否变形
MM=1;}
break;
case VK_DOWN:
if(!rus.GameOver&&!puse)
{
IsMoveVertical=true;
rus.VerticalMove(rus.JudgeBottom());
}
break;
case VK_RIGHT:
if(!rus.GameOver&&!puse)
{
if(!rus.JudgeRight())//如果到达右边界判断OBJECT的右边的MAP中是有方块¨
IsMoveRight=true;
rus.RightMove(!rus.JudgeRight());
}
break;
caseVK_LEFT:
if(!rus.GameOver&&!puse)
{
if(!rus.JudgeLeft())//如果到达左边界判断OBJECT的左边的MAP中是有方块¨
IsMoveLeft=true;
rus.LeftMove(!rus.JudgeLeft());
}
break;
}
InvalidateRect(hwnd,NULL,FALSE);
俄罗斯方块的变形使得俄罗斯方块变得十分的精巧灵活,极大的增加了游戏的趣味性。但这却给编程者出了一道难题,我的解决方案是建立一个Deformation()函数当按下向上的光标键后执行该函数。
Deformation()函数的作用主要是根据随机生成的 DEFORMATION 变量来确定哪种类型的方块需要变形例如对一个一字型的物块进行变形:
Deformation()函数根据DEFORMATION变量确定出需要变形的物块为一字型,然后再根据CHANGE变量确定出一字型的变形形状,让后将该形状对应的Object数据写入OBJECT中,具体代码如下:
intXX[4],YY[4];
bool JUdge=false;
switch(NOW_DEFORMATION)
{
case 10:
switch(CHANGE)
{
case 0:
GETObjectPosition(XX,YY,Object10);
for(int i=0;i<4;i++)
if(MAP[XX[i]-1][YY[i]+2])
return0;
for(int i=0;i<4;i++)
{
TempPoint[i].x=x[i];
TempPoint[i].y=y[i];
x[i]=XX[i]-1;
y[i]=YY[i]+2;
}
CheckDownWhite(); //一字型
CHANGE=1;
JUdge=true;
returnJUdge;
case 1:
GETObjectPosition(XX,YY,Object11);
for(int i=0;i<4;i++)
if(MAP[XX[i]][YY[i]])
return0;
for(int i=0;i<4;i++)
{
TempPoint[i].x=x[i];
TempPoint[i].y=y[i];
x[i]=XX[i];
y[i]=YY[i];
}
CheckDownWhite();
CHANGE=0;
JUdge=true;
returnJUdge;
}
break;
之后的画图任务就交给WM_PAIN中的函数来处理,具体代码如下:
SelectObject(hdc,hpenw);
for(int i=0;i Rectangle(hdc,rus.wPoint[i].x*20,(rus.wPoint[i].y-1)*20,rus.wPoint[i].x*20+18,(rus.wPoint[i].y-1)*20+18); 在独立完成俄罗斯方块后感觉到实际上学完面向过程的C后就应该能写出俄罗斯方块这个游戏的内核,因为俄罗斯方块的内核代码完全没有用到课外的知识,完全是课内知识的灵活运用,但要想写出完整的俄罗斯方块游戏则需要学习一下Windows编程。我利用寒假的时间简单学习了一下Windows编程后发现Windows编程对大一的学生来说并非遥不可及,只要动动脑子还是会有收获的。 我是利用 Rectangle(hdc,PointX[i]*20,(PointY[i]-1)*20,PointX[i]*20+18,(PointY[i]-1)*20+18);来绘制一个矩形图形,通过rus.wPoint[i].x*20来确定矩形的起始横坐标,(rus.wPoint[i].y-1)*20来确定矩形起始的纵坐标。rus.wPoint[i].x*20+18,(rus.wPoint[i].y-1)*20+18来确定矩形的终止横纵坐标。最后利用for循环来画出所有的矩形 SelectObject(hdc,hpen); for(int i=0;i<4;i++) Rectangle(hdc,PointX[i]*20,(PointY[i]-1)*20,PointX[i]*20+18,(PointY[i]-1)*20+18);//画红色方块 当下落物体下落一次后应该画出与背景颜色相同的方块来覆盖掉之前的方块,来实现物块下落的效果。 覆盖方块的坐标由CheckDownWhite()函数来确定,其具体代码为 int m=0;bool Same=false; WIDETH=1;HEIGHT=1; boolJudgeRepeat[4]={1,1,1,1};//用来判断方块的位置是否重复 for(int i=0;i<4;i++) { for(int j=i+1;j<4;j++) if(x[i]==x[j]) { JudgeRepeat[j]=false; if(!JudgeRepeat[i]) { Same=true; break; } if(y[i] { wPoint[m].x=x[i]; wPoint[m].y=y[i]-1; m++; Same=true; } else { wPoint[m].x=x[i]; wPoint[m].y=y[j]-1; m++; Same=true; } } if(!Same&&JudgeRepeat[i]) { wPoint[m].x=x[i]; wPoint[m].y=y[i]-1; m++; } Same=false; } 白色方块的绘制由下列语句实现 SelectObject(hdc,hpenw); for(int i=0;i Rectangle(hdc,rus.wPoint[i].x*20,(rus.wPoint[i].y-1)*20,rus.wPoint[i].x*20+18,(rus.wPoint[i].y-1)*20+18); 这里的游戏设置主要是指暂停,继续,级别的设置。 以暂停为例,当玩家按下暂停时产生一个菜单消息,并由WM_COMMAND来处理 caseWM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); switch(wmId) { caseID_32778://继¨¬续? GoOn(); break; default: returnDefWindowProc(hwnd, message, wParam, lParam); } break; 游戏开始后会进入界面,并且立即进行游戏,在游戏过程中玩家可以暂停游戏,调整适合自己的难度,并继续游戏。 暂停游戏 继续游戏 游戏难度的选择 当游戏得分时得分行会闪烁,提示玩家哪一行将被消掉 本游戏存在一个BUG主要是由于对OBJECT的高度和宽度测量不准确所造成的,这会导致偶尔屏幕上会有一个小方块消失但一秒后又会出现的现象,由于时间关系一直未能解决这个BUG,因此希望大家多多指正。3.2.5 游戏主体的绘制
3.2.5 游戏设置
第四章 结果与讨论
4.1 程序运行
4.2 错误调试与分析