《俄罗斯方块-C语言.docx》由会员分享,可在线阅读,更多相关《俄罗斯方块-C语言.docx(43页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、 俄罗斯方块游戏在本章内容中,将介绍使用C语言开发一个简单的俄罗斯方块游戏的方法,并详细介绍其具体的实现流程。1.1 第一个工程1.1.1 作业2004年7月1日,晴,我的作业在离校前的10分钟,我们最敬仰的程序教师TC给我们布置了一个暑期作业:题目很简单用C语言实现俄罗斯方块游戏(提示用实现),并提醒务必做好前期的分析工作。1.1.2 准备2004年7月3日,微风阵阵教师的建议:在做工程前一定要好好地构思和规划工程,根据需求规划开发流程。于是,我在电脑上画了一个简单的工程开发流程图,如图1-1所示。图1-1 开发流程图q 功能分析:分析整个系统所需要的功能;q 模块构造规划:规划系统中所需要
2、的功能模块;q 总体设计:分析系统处理流程,探索系统核心模块的运作;q 数据构造:设计系统中需要的数据构造;q 规划函数:预先规划系统中需要的功能函数;q 具体编码:编写系统的具体实现代码。1.2 功 能 分 析2004年7月4日,阳光明媚其根本构造如图1-2所示。图1-2 俄罗斯方块游戏的根本构造这样,我总结出了俄罗斯方块游戏的根本功能模块,并做了一个简单的工程规划书,整个规划书分为两个局部:q 系统需求分析;q 构造规划。1.2.1 系统需求分析1)游戏方块的预览功能当游戏运行后并在底部出现一个游戏方块时,必须在预览界面中出现下一个方块,这样便于玩家提前进展控制处理。因为在该游戏中共有19
3、种方块,所以在方块预览区内要显示随机生成的游戏方块。2)游戏方块的控制功能游戏玩家可以对出现的方块进展移动处理,分别实现左移、右移、快速下移、自由下落和行满自动消除功能的效果。3)更新游戏显示当在游戏中移动方块时,需要先消除先前的游戏方块,然后在新坐标位置重新绘制新方块。4)游戏速度设置和分数更新通过游戏分数能够实现对行数的划分,例如,可以设置消除完整的一行为10分。当到达一定数量后,需要给游戏者进展等级上的升级。当玩家级别升高后,方块的下落速度将加快,从而游戏的难度就相应地提高了。5)系统帮助游戏玩家进入游戏系统后,通过帮助了解游戏的操作提示。一个俄罗斯方块游戏的根本功能也就上述5条了,当然
4、现实中的游戏产品更加复杂,但其根本的功能都是大同小异的。1.2.2 构造规划现在开场步入构造规划阶段。为了加深印象,我做了一个模块构造图,如图1-3所示。图1-3 游戏的模块构造1.2.3 选择工具2004年7月5日,晴,工具的困惑建议选Turbo C。因为在DEV-C+中使用比拟复杂!历时两天,我确定好了整个工程的功能模块,做好了整体规划,也选好了开发工具。接下来我将要步入总体设计阶段。1.3 总 体 设 计经过总体构成功能的分析后,接下来就可以根据各构成功能模块进展对应的总体设计处理。主要包括两个方面:q 运行流程分析;q 核心处理模块分析。1.3.1 运行流程分析2004年7月6日,上午
5、,阳光明媚了整个游戏的具体运作流程图。游戏的具体运作流程如图1-4所示,用左移VK_LEFT、右移VK_RIGHT、下移VK_DOWN、旋转VK_UP和退出VK_Esc键判断键值。上述几个按键移动处理的具体说明如下。q VK_LEFT:调用MoveAble( )函数,判断是否能左移,如果可以那么调用EraseBox函数,去除当前的游戏方块。并在下一步调用show_box( )函数,在左移位置显示当前游戏的方块。q VK_RIGHT:右移处理,及上面的VK_LEFT处理类似。q VK_DOWN:下移处理,如果不能再移,必须将flag_newbox标志设置为1。q VK_UP:旋转处理,首先判断旋
6、转动作是否执行,在此需要满足多个条件,如果不合条件,那么不予执行。q VK_Esc:按Esc键后将退出游戏。图1-4 游戏运行流程1.3.2 核心处理模块分析2004年7月6日,下午,多云,还有很长的路要走1方块预览新游戏的方块将在44的正方形小方块中预览,使用随机函数rand( )可以产生1-19之间的游戏方块编号,并作为预览的方块编号。其中品尼高正方形小方块的大小由BSIZEBSIZE来计算。2游戏方块控制处理方块的移动控制是整个游戏的重点和难点,具体信息如下。1)左移处理处理过程如下。(1)判断是否能够左移,判断条件有两个:左移一位前方块不能超越游戏底板的左边线,否那么将越界;并且在游戏
7、方块有值(值为1)的位置,游戏底板不能是被占用的(占用时值为1)。(2)去除左移前的游戏方块;(3)在左移一位的位置处,重新显示此游戏的方块。2)右移处理处理过程如下。(1)判断是否能够右移,判断条件有两个:右移一位前方块不能超越游戏底板的右边线,否那么将越界;游戏方块有值位置,游戏底板不能被占用;(2)去除右移前的游戏方块;(3)在右移一位的位置处,重新显示此游戏的方块。3)下移处理处理过程如下。(1)判断是否能够下移,判断条件有两个:下移一位前方块不能超越游戏底板的底边线,否那么将越界;游戏方块有值位置,游戏底板不能被占用。满足上述两个条件后,可以被下移处理。否那么将flag_newbox
8、设置为1,在主循环中会判断此标志;(2)去除下移前的游戏方块;(3)在下移一位的位置处,重新显示此游戏的方块。4)旋转处理处理过程如下。(1)判断是否能够旋转,判断条件有两个:旋转前方块不能超越游戏底板的底边线、左边线和右边线,否那么将越界;游戏方块有值位置,游戏底板不能被占用;(2)去除旋转前的游戏方块;(3)在游戏方块显示区域(44)的位置,使用当前游戏方块的数据构造中的next值作为旋转后形成的新游戏方块的编号,并重新显示这个编号的游戏方块。3更新显示当游戏中的方块在进展移动处理时,要去除先前的游戏方块,用新坐标重绘游戏方块。当消除满行后,要重绘游戏底板的当前状态。去除游戏方块的方法是先
9、画轮廓再填充,具体过程如下:绘制一个轮廓,使用背风光填充小方块,然后使用前风光画一个游戏底板中的小方块。循环此过程,变化当前坐标,填充并画出19个这样的小方块,从而在游戏底板中去除此游戏方块。4游戏速度和分数更新处理当行满后,积分变量score会增加一个固定的值,然后将等级变量level和速度变量speed相关联,实现等级越高速度越快的效果。2004年7月6日,晚上,总体设计的重要性数据构造设计1.4 数 据 结 构2004年7月7日,上午,阳光充足我就设计好了系统所需要的数据构造。1游戏底板构造体此处的游戏底板构造体是BOARD,具体的代码如下。struct BOARD/*游戏底板构造,表示
10、每个点所具有的属性*/ int var;/*当前状态只有0和1,1表示此点已被占用*/ int color;/*颜色,游戏底板的每个点可以拥有不同的颜色,增强美观性*/Table_boardVertical_boxsHorizontal_boxs;其中,BOARD构造体表示了游戏底板中每个小方块的属性,var表示了当前的状态,为0时表示未被占用,为1时表示已经被占用。2游戏方块构造体此处的游戏方块构造体是SHAPE,具体的代码如下。struct SHAPE/*一个字节是8位,用每4位表示游戏方块中的一行,例如:box0=0x88,box1=0xc0表示的是:1000100011000000*/
11、 char box2; int color;/*每个方块的颜色*/ int next;/*下个方块的编号*/;SHAPE构造体表示某个小方块的属性,char box2表示用2个字节来表示这个块的形状,每4位来表示一个方块的一行。color表示每个方块的颜色,颜色值可以根据需要设置。3SHAPE构造数组此处的游戏方块构造体是SHAPE,具体的代码如下。/*初始化方块内容,即定义MAX_BOX个SHAPE类型的构造数组,并初始化*/struct SHAPE shapesMAX_BOX=/* * 口 口口口 口口 口 * 口 口 口 口口口 * 口口 口 */ 0x88, 0xc0, CYAN, 1
12、, 0xe8, 0x0, CYAN, 2, 0xc4, 0x40, CYAN, 3, 0x2e, 0x0, CYAN, 0,/* * 口 口口 口口口 * 口 口 口 口 * 口口 口口口 口 */ 0x44, 0xc0, MAGENTA, 5, 0x8e, 0x0, MAGENTA, 6, 0xc8, 0x80, MAGENTA, 7, 0xe2, 0x0, MAGENTA, 4,/* * 口 * 口口 口口 * 口 口口 */ 0x8c, 0x40, YELLOW, 9, 0x6c, 0x0, YELLOW, 8,/* * 口 口口 * 口口 口口 * 口 */ 0x4c, 0x80, B
13、ROWN, 11, 0xc6, 0x0, BROWN, 10,/* * 口 口 口 * 口口口 口口 口口口 口口 * 口 口 口 */ 0x4e, 0x0, WHITE, 13, 0x8c, 0x80, WHITE, 14, 0xe4, 0x0, WHITE, 15, 0x4c, 0x40, WHITE, 12,/* 口 * 口 * 口 口口口口 * 口 */ 0x88, 0x88, RED, 17, 0xf0, 0x0, RED, 16,/* * 口口 * 口口 */ 0xcc, 0x0, BLUE, 18;在上述代码中,定义了MAX_BOX个SHAPE类型的构造数组,并进展了初始化处理。
14、因为共有19种不同的方块类型,所以MAX_BOX为19。2004年7月7日,晚上,数据构造的重要性1.5 一个神秘的箱子2004年7月8日,晴空万里 武侠小说,是成年人的童话。从少年开场我就迷恋上了武侠小说,一直伴我读到大学。记得在某一段时间,我曾经特别痴迷一个神秘的箱子.出自古龙的武侠名著?英雄无泪?的一段对白,没错最厉害的武器是一口箱子。等我看完全文之后我才明白,这不是一口简单的箱子,箱子里有很多个零部件,能够根据不同的对手而迅速组成一个战胜对手的武器。现在我发现这个箱子和程序中的函数是那么的相似!我们要编程解决一个问题,要实现某个功能,我们可以编写一个函数来实现它。如果有多个问题,那么编
15、写多个函数就可以实现,函数就是我们编程中的那个神秘的箱子。书归正传,我预先设置好了整个工程中需要的函数,并做好了定义。1函数NewTimer函数NewTimer用于实现新的时钟,具体构造如下:void interrupt newtimer(void)2函数SetTimer函数SetTimer用于设置新时钟的处理过程,具体构造如下:void SetTimer(void interrupt(*IntProc)(void)3函数KillTimer函数KillTimer用于恢复原有的时钟处理过程,具体构造如下:void KillTimer()4函数initialize函数initialize用于初始化
16、界面,具体构造如下:void initialize(int x,int y,int m,int n)5函数DelFullRow函数DelFullRow用于删除满行,y设置删除的行数,具体构造如下:int DelFullRow(int y)6函数setFullRow函数setFullRow用于查询满行,并调用DelFullRow函数进展处理,具体构造如下:void setFullRow(int t_boardy)7函数MkNextBox函数MkNextBox用于生成下一个游戏方块,并返回方块号,具体构造如下:int MkNextBox(int box_numb)8函数EraseBox函数Eras
17、eBox用于去除以(x,y)位置开场的编号为box_numb的游戏方块,具体构造如下:void EraseBox(int x,int y,int box_numb)9函数show_box函数show_box用于显示以(x,y)位置开场的编号为box_numb,颜色值为color的游戏方块,具体构造如下:void show_box(int x,int y,int box_numb,int color)10函数MoveAble函数MoveAble首先判断方块是否可以移动,其中(x,y)是当前的位置,box_numb是方块号,direction是方向标志。具体构造如下:int MoveAble(in
18、t x,int y,int box_numb,int direction)2004年7月8日,晚上,函数的重要性我历经8天的忙碌,终于完成了前期的所有工作。整个过程很顺利,虽然工程很简单,但是我很细心,可以说这是我第一次这么细心地做一个工程。到此为止,我更加认识到了函数在C语言中的作用。几乎工程中的所有功能都是通过函数来实现的,函数构成了整个工程的主体。1.6 具 体 实 现2004年7月9日,晴空万里 开发征程正式拉开序幕!1.6.1 预处理如果说前面的准备工作是开发工程的第一步,那么预处理就打响了程序开发的第一枪。我们先看一下预处理的定义:预处理是在程序源代码被编译之前,由预处理器(Pre
19、processor)对程序源代码进展的处理。这个过程并不对程序的源代码进展解析,但它把源代码分割或处理成为特定的符号用来支持宏调用。看来预处理也是一个准备工作,不搬到台面上。这就像我们辛苦学习知识,为将来的工作而做准备一样。在以后工作中,所学的知识只会在后台默默无闻地支持着我们。在开场之前画了一个简单的实现流程图,如图1-5所示。图1-5 预处理流程图(1)先引用图形函数库等公用文件,具体代码如下所示。#include #include #include #include /*图形函数库*/(2)定义按键码,即操控游戏的按键:左移、右移、下移、上移等。具体代码如下所示。/*定义按键码*/#de
20、fine VK_LEFT 0x4b00#define VK_RIGHT 0x4d00#define VK_DOWN 0x5000#define VK_UP 0x4800#define VK_ESC 0x011b#define TIMER 0x1c /*设置中断号*/(3)定义系统中需要的常量,例如方块种类、方块大小、方块颜色等。具体实现代码如下所示。/*定义常量*/#define MAX_BOX 19 /*总共有19种形态的方块*/#define BSIZE 20 /*方块的边长是20个像素*/#define Sys_x 160 /*显示方块界面的左上角x坐标*/#define Sys_y 2
21、5 /*显示方块界面的左上角y坐标*/#define Horizontal_boxs 10 /*水平的方向以方块为单位的长度*/#define Vertical_boxs 15 /*垂直的方向以方块为单位的长度,也就说长是15个方块*/#define Begin_boxs_x Horizontal_boxes 2 /*产生第一个方块时出现的起始位置*/#define FgColor 3 /*前景颜色,如文字.2-green*/#define BgColor 0 /*背景颜色.0-blac*/#define LeftWin_x Sys_x+Horizontal_boxs*BSIZE+46 /*右
22、边状态栏的x坐标*/#define false 0#define true 1/*移动的方向*/#define MoveLeft 1#define MoveRight 2#define MoveDown 3#define MoveRoll 4/*以后坐标的每个方块可以看作像素点是BSIZE*BSIZE的正方形*/(4)定义系统中需要的全局变量,例如,方块的下落速度、玩家的分数、当前的方块编号等。具体实现代码如下所示。/*定义全局变量*/int current_box_numb; /*保存当前方块编号*/*x,y是保存方块的当前坐标的*/int Curbox_x=Sys_x+Begin_boxs
23、_x*BSIZE,Curbox_y=Sys_y;int flag_newbox=false;/*是否要产生新方块的标记0*/int speed=0; /*下落速度*/int score=0; /*总分*/int speed_step=30; /*每等级所需要分数*/* 指向原来时钟中断处理过程入口的中断处理函数指针 */void interrupt (*oldtimer)(void);(5)定义底板构造和方块构造。每一个新出现的方块构造是不同的,当方块下落到游戏底板后,构造也是不同的,所以必须编写两个构造来存储即时构造。具体实现代码如下所示。struct BOARD/*游戏底板构造,表示每个点
24、所具有的属性*/ int var;/*当前状态 只有0和1,1表示此点已被占用*/ int color;/*颜色,游戏底板的每个点可以拥有不同的颜色,增强美观性*/Table_boardVertical_boxsHorizontal_boxs;/*方块构造*/struct SHAPE char box2;/*一个字节等于8位,每4位表示一个方块的一行 如:box0=0x88,box1=0xc0表示的是: 1000 000 1100 0000*/ int color;/*每个方块的颜色*/ int next;/*下个方块的编号*/;(6)开场初始化方块内容,即定义允许MAX_BOX个预定义类型的
25、数组,其中MAX_BOX代表允许的最多箱子个数,并初始化。初始化就是把变量赋为默认值,把控件设为默认状态,把没准备的准备好。具体实现代码如下所示。/*初始化方块内容 */struct SHAPE shapesMAX_BOX=/* * 口 口口口 口口 口 * 口 口 口 口口口 * 口口 口 */ 0x88, 0xc0, CYAN, 1, 0xe8, 0x0, CYAN, 2, 0xc4, 0x40, CYAN, 3, 0x2e, 0x0, CYAN, 0,/* * 口 口口 口口口 * 口 口 口 口 * 口口 口口口 口 */ 0x44, 0xc0, MAGENTA, 5, 0x8e, 0
26、x0, MAGENTA, 6, 0xc8, 0x80, MAGENTA, 7, 0xe2, 0x0, MAGENTA, 4,/* * 口 * 口口 口口 * 口 口口 */ 0x8c, 0x40, YELLOW, 9, 0x6c, 0x0, YELLOW, 8,/* * 口 口口 * 口口 口口 * 口 */ 0x4c, 0x80, BROWN, 11, 0xc6, 0x0, BROWN, 10,/* * 口 口 口 * 口口口 口口 口口口 口口 * 口 口 口 */ 0x4e, 0x0, WHITE, 13, 0x8c, 0x80, WHITE, 14, 0xe4, 0x0, WHITE,
27、 15, 0x4c, 0x40, WHITE, 12,/* 口 * 口 * 口 口口口口 * 口 */ 0x88, 0x88, RED, 17, 0xf0, 0x0, RED, 16,/* * 口口 * 口口 */ 0xcc, 0x0, BLUE, 18;2004年7月9日,晚上,时刻要学习在具体编码之前,在我脑海中关于预处理的知识毫无印象。因此在具体编码时,我发现一行代码也写不出来。这种情况我相信在很多初学者身上也发生过,而且不止发生一次。我没有方法,只能自己搜集资料学习。看来无论是一个学生,还是以后步入职场,都要随时提高自己,来应对新技术的开展。1.6.2 主函数我深知主函数的重要性,所以
28、在设计之前,特意请教了师兄A:我:“开场主函数设计了,哈哈!A:“呵呵,看来准备工作都已经做完了。我不知你前面的工作流程,但是我还是提醒你要注意:前期准备工作的重要性!整体分析和规划都要仔细考虑,并且尽可能的书面化!我:“嗯,明白了!作为主函数,您有什么建议?A:“五个字:尽量的简单!因为主函数肩负着入口和出口的重任,所以尽量不要把太多细节方面的逻辑直接放在主函数内,这样不利于维护和扩展。主函数应该尽量简洁,具体的实现细节应该封装到被调用的子函数中。简单是编写函数的第一要务,我编写的主函数如下。void main() int GameOver=0; int key,nextbox; int C
29、urrentaction=0;/*标记当前动作状态*/ int gd=VGA,gm=VGAHI,errorcode; initgraph(&gd,&gm,); errorcode = graphresult(); if (errorcode != grOk) printf(nNotice:Graphics error: %sn, grapherrormsg(errorcode); printf(Press any key to quit!); getch(); exit(1); setbkcolor(BgColor); setcolor(FgColor); randomize(); SetTi
30、mer(newtimer); initialize(Sys_x,Sys_y,Horizontal_boxs,Vertical_boxs);/*初始化*/ nextbox=MkNextBox(-1); show_box(Curbox_x,Curbox_y,current_box_numb,shapescurrent_box_numb.color); show_box(LeftWin_x,Curbox_y+200,nextbox,shapesnextbox.color); show_intro(Sys_x,Curbox_y+320); getch(); while(1) /* Currentact
31、ion=0; flag_newbox=false; 检测是否有按键*/ if (bioskey(1)key=bioskey(0); else key=0; switch(key) case VK_LEFT: if(MoveAble(Curbox_x,Curbox_y,current_box_numb,MoveLeft) EraseBox(Curbox_x,Curbox_y,current_box_numb);Curbox_x-=BSIZE;Currentaction=MoveLeft; break; case VK_RIGHT: if(MoveAble(Curbox_x,Curbox_y,cu
32、rrent_box_numb,MoveRight) EraseBox(Curbox_x,Curbox_y,current_box_numb);Curbox_x+=BSIZE;Currentaction=MoveRight; break; case VK_DOWN: if(MoveAble(Curbox_x,Curbox_y,current_box_numb,MoveDown) EraseBox(Curbox_x,Curbox_y,current_box_numb);Curbox_y+=BSIZE;Currentaction=MoveDown; else flag_newbox=true; br
33、eak; case VK_UP:/*旋转方块*/ if(MoveAble(Curbox_x,Curbox_y,shapescurrent_box_numb.next,MoveRoll) EraseBox(Curbox_x,Curbox_y,current_box_numb);current_box_numb=shapescurrent_box_numb.next; Currentaction=MoveRoll; break; case VK_ESC: GameOver=1; break; default: break; if(Currentaction) /*表示当前有动作,移动或转动*/ s
34、how_box(Curbox_x,Curbox_y,current_box_numb,shapescurrent_box_numb.color); Currentaction=0; /*按了向下键,但不能下移,就产生新方块*/ if(flag_newbox) /*这时相当于方块到底部了,把其中占满一行的清去,置0*/ ErasePreBox(LeftWin_x,Sys_y+200,nextbox); nextbox=MkNextBox(nextbox); show_box(LeftWin_x,Curbox_y+200,nextbox,shapesnextbox.color); /*刚一开场,游
35、戏完毕*/ if(!MoveAble(Curbox_x,Curbox_y,current_box_numb,MoveDown) show_box(Curbox_x,Curbox_y,current_box_numb,shapescurrent_box_numb.color); GameOver=1; else flag_newbox=false; Currentaction=0; else /*自由下落*/ if (Currentaction=MoveDown | TimerCounter (20-speed*2) if(MoveAble(Curbox_x,Curbox_y,current_b
36、ox_numb,MoveDown) EraseBox(Curbox_x,Curbox_y,current_box_numb);Curbox_y+=BSIZE; show_box(Curbox_x,Curbox_y,current_box_numb,shapescurrent_box_numb.color); TimerCounter=0; if(GameOver )/*| flag_newbox=-1*/ printf(game over,thank you! your score is %d,score); getch(); break; getch(); KillTimer(); clos
37、egraph();2004年7月10日,黄昏的夜色,体会主函数的美丽经过一天的努力,我完成了预处理和主函数的编码工作,在此阶段体会到了主函数的重要性。在C语言中,任何程序的执行都是从主函数main( )开场,并且在主函数中完毕,退出程序。主函数可以调用其他函数,其他函数可以互相调用,但不能调用主函数。一般而言,编写一个能运行在操作系统上的程序,都需要一个主函数。主函数意味着建立一个独立进程,且该进程成为程序的入口,对其他各函数(在某些OOP 语言里称作方法,比方Java)进展调用,当然其他被调用函数也可以再去调用更多函数。工作一切顺利,接下来我开场进展界面初始化的设计工作。1.6.3 界面初始
38、化2004年7月11日,上午,晴早晨起来,呼吸着这座海边小城新鲜的空气,我顿时精神抖擞了许多。昨天疲惫的心态已经被我消灭的无影无踪,可以饱满的精神开发界面了!在每次玩俄罗斯方块时,需要对游戏的界面进展初始化处理。然后在主函数中对其进展调用,实现最终的初始化处理。初始化界面的处理流程如下。(1)循环调用函数line( ),用于绘制当前的游戏板。(2)调用函数ShowScore( ),显示初始的得分,初始得分是0。(3)调用函数ShowSpeed( ),显示初始的等级速度,初始速度是1。在这里有两个参数需要特别注意:q x,y:代表左上角坐标;q m,n:对应于Vertical_boxes,Horizontal_boxs,分别表示纵横方向上方块的个数(以方块为单位)。编写具体的实现代码。/*初始化界面*/void initialize(int x,int y,int m,int n) int i,j,oldx; oldx=x; for(j=0;jn;j+) for(i=0;im;i+) Table_boardji.var=0;