计算机工程学院
课程设计说明书
课程名称:______________________________________
设计项目:______________________________________
学生姓名:______________________________________
学 号:______________________________________
专 业:______________________________________
班 级:______________________________________
指导教师:______________________________________
______年______月
一、任务与具体要求
二、设计说明书包括的内容
三、 应完成的图纸
四、 评语及成绩
指导教师(签字)_____________
________年____月____日
目 录
1.系统概述………………………………2
2.原有程序概况…………………………3
3.现在系统操作界面……………………5
4.现在程序详细设计……………………7
5.用户手册………………………………14
6.测试计划………………………………15
7.课程设计心得体会……………………16
8.参考书籍及资料………………………17
系统概述
1.1 现状分析
在个人电脑日益普及的今天,一些有趣的桌面游戏已经成为人们在使用计算机进行工作学习之余休闲娱乐的首选,而俄罗斯方块游戏是人们最熟悉的小游戏之一,它以其趣味性强,易上手等诸多特点得到了大众认可,因此开发此游戏软件可满足人们的一些娱乐需求。
此俄罗斯方块游戏可以为用户提供一个可在普通个人电脑上运行的,界面美观的,易于控制的俄罗斯方块游戏。
1.2 项目要求
俄罗斯方块是一款适合大众的游戏软件,它适合不同年龄的人玩。本软件实现的基本功能如下:
l 游戏区:玩家可以在游戏区中堆积方块,并能够在游戏过程中随时了解得分情况和下一个将要出现方块的提示。
l 游戏控制:玩家可以通过游戏控制功能来控制游戏开始,暂停,结束游戏,游戏难度的设置以及音效控制。
l 玩家游戏信息存储及删除:玩家的得分在前五名时,将会记录在排行榜上,同时可为玩家清空排行榜。
1.3 系统模块结构图
依据需求分析结果,俄罗斯方块游戏可以分为三个模块:游戏区、游戏控制区、数据操作区。系统模块结构如图:
原有程序概况
2.1 原有程序实现的功能
原来程序只能实现开始和退出游戏,还有记录游戏的得分!
类一:控制面板类
(1)类定义:class GameMain
(2)功能描述:界面设计应遵循简介美观方便易用的基本原则。
类二:画布类
类定义:class GameCanvas
类三:方块类
(1)类定义:class RussiaBlock
(2)功能描述:七种类型方块的父类,定义了所有类型方块的共同特征和行为。
类四:主游戏类
(1)类定义:RussiaBlocksGame
(2)功能描述: 游戏主类,负责游戏的全局控制。
类五:虚拟单个方格类类
(1)类定义:RussiaBlocksGame;功能描述:控制方格的颜色。
2.2原有游戏打开界面
2.3原有游戏运行界面
2.4改进目标
?增加暂停和继续按钮,这样可以方便游戏者,可以暂停游戏。
?增加游戏的等级,这样可改变游戏的难易程度,增加游戏的趣味 性。
?增加填写游戏者姓名的的功能,这样可以记录游戏者的名字。
现在系统操作界面
3.1现在游戏打开界面
3.2现在游戏运行界面
现在程序详细设计
类一:控制面板类
(1)类定义:class GameMain
(2)功能描述:界面设计应遵循简介美观方便易用的基本原则。
(3)设计方法:
方法①Game方法
方法定义:Public GameMain()
功能描述: 这个方法就是要保证在放大和缩小程序实现窗口大小的时候,其中的格子也会随之改变,也就是我们经常说的最大化最小化,当窗口发生变化时,方块自动调整大小与之相匹配。
方法②:actionPerformed方法
方法定义:public void actionPerformed ()
功能描述:这个就是将游戏界面重置,将游戏界面回到初始状态,这个方法主要是通过将窗口中的着色小方块全部消除颜色,然后再重新去给窗口着色,即着上背景色,给玩家看上去也就是被刷新了。
(4)设计描述:
① controlPanel面板中包含tipPanel, InfoPanel, buttonPanel子面板,分别为图形界面面板,信息面板,按钮面板
② 图形显示面板设计为BorderLayout布局样式.其中添加tipPanel面板居中(以显示下一个俄罗斯方块样式)
③ 信息面板设计为4行1列式的GridLayout网格状布局,并且添加当前等级,显示当前等级文本域,当前分数,显示当前分数文本域
④ 按钮面板设计为5行1列式的GridLayout网格状布局 ,并且添加开始游戏, 暂停/继续,结束三个按钮
面板设计效果图如下:
类二:画布类
(1)类定义:class GameCanvas
(2)设计方法:
方法①:paintComponent方法
方法定义public void paintComponent(Graphics g)
功能描述;其中box就是表示预显示框,然后检查显示框中每个小格子是否需要着色,如果需要着色,则着上方块得颜色,不需要着色的则着上背景颜色,一共有16个小格子构成了一个预显示块,共需要检测16次,其实当中和实现游戏中的程序有着共同点。
方法②:removeLine方法
方法定义:public void removeLine(int row)
功能描述:这个就是实现如何将行消除的,其实就是在判断了该行该消除的时候将上面的方块都向下平移一格,这样在实现程序界面也就给玩家是消去一行的视觉效果。
类三:方块类
(1)类定义:class RussiaBlock
(2)功能描述:七种类型方块的父类,定义了所有类型方块的共同特征和行为。
(3)设计方法:
方法①:run方法
方法定义:public void run()
功能描述:实现方块的运行,下落。
方法②:moveTo方法
方法定义:public synchronized boolean moveTo()
功能描述:将当前画移动到newRow/ newCol所指定的位置。
方法③:isMoveable方法
方法定义:private boolean isMoveable()
功能描述:判定当前块是否能被边界、其他方块挡住以及不能移动的情况。
方法④:turnNext方法
方法定义:public void turnNext()
功能描述:实现方块的旋转。
方法⑤:turnTo方法
方法定义:private synchronized boolean turnTo()
功能描述:将当前块变成newStyle所指定的块样式。
方法⑥:isTurnable方法
方法定义:private boolean isTurnable()
功能描述:判定当前块是否能变成newStyle所指定的块样式。
方法⑦:erase方法
方法定义:private void erase()
功能描述:将当前块从画布的对应位置移除。
方法⑧:display方法
方法定义:private void display()
功能描述:实现重画画布的时候显示当前块。
类四:主游戏类
(1)类定义:RussiaBlocksGame
(2)功能描述: 游戏主类,负责游戏的全局控制。
(3)设计方法:
方法①:checkFullLine 方法
方法定义:public void checkFullLine()
功能描述: 这个是表示的清除需要消除的行的算法,只需要对行中的小方块进行判断,判断是否都已经着色,如果都着色了说明改行是要消除的,而且还需要进行加分措施,在进行多行消除的时候,可以给玩家更多的奖励,这也符合俄罗斯方块的游戏股则。
方法②:play方法
方法:private void play()
功能实现:实现游戏开始,同时在游戏的开始时,方块随即从顶端的任何位置落下。
方法③:getCurLevelScore方法
方法定义:public int getCurLevelScore()
功能描述:实现获得每个等级的分数
方法④:setLevel方法
方法定义:public void setLevel(int level)
功能描述:进行等级的手动调节
方法⑤:levelUpdate 方法
方法定义:public void levelUpdate()
功能描述:这是更新等级的函数,一个是通过分数的判断,在程序中定义了一个本级得分,然后当本级得分达到一个分数的时候,就可以进入下一个等级,同时根据等级的提高,之前定义的下落速度也会提高,同时减去升级需要的分数,也就可以用作下一次升级的判断。
(4) 设计描述:
①窗体设计
窗体大小:(300,400),默认居中显示,代码如下:
Dimension scrSize=Toolkit.getDefaultToolkit().getScreenSize();
setLocation((scrSize.width-getSize().width)/2,(scrSize.height-getSize().height)/2);
添加windows窗口事件:
addWindowListener( new WindowAdapter()
{
public void windowClosing(WindowEvent event)
{
System.exit(0);
}
} );
窗体名称为:俄罗斯方块游戏。
②菜单设计
游戏菜单:包括重新游戏,退出两个菜单项。
控制菜单:包括暂停,继续,结束三个菜单项。
以菜单项为例
gameMenu = new JMenu("游戏");
newGameItem = new JMenuItem("重置");
exitItem = new JMenuItem("退出");
gameMenu.add(newGameItem);
gameMenu.add(exitItem);
③添加背景音乐方法
public void Music ()
{
String music = "background.wav";
final AudioClip clip = Applet.newAudioClip(getClass().getResource(music));
clip.play();
}
通过调用Music()来实现背景音乐播放。
类五:虚拟单个方格类类
(1)类定义:RussiaBlocksGame
(2) 功能描述:控制方格的颜色。
(3) 设计方法:
方法:setColor方法
方法定义:public void setColor()
功能描述:对有标记的方块进行着色
用户手册
l 控制区:玩家在界面上可以看到开始游戏的按钮,点击该按钮,开始游戏,控制区和界面上都有暂停,继续的按钮,按下暂停则游戏暂停,并且界面上的按钮变成继续的字样,同理按下继续,游戏退出,用户结束游戏。
l 游戏菜单区:重置:如果玩家在结束了游戏或者在游戏中向开始进行新的游戏,则可以点击该按钮,如果玩家不想继续玩得话,直接点击结束按钮结束游戏,并退出窗口。
l 控制菜单:暂停,继续,退出游戏的功能都和控制区一样的效果
l 关于菜单:该菜单游戏的操作的简介,方向键的左,下,右就是游戏中的操作方法,方向键上则是对图形的反转变化。
测试计划
右侧按钮测试
1.点击“开始/重置” 按钮
效果:游戏开始与重置
结果:正常
2.点击“暂停/继续” 按钮
效果:游戏根据操作能暂停与继续相互切换
结果:正常
3.点击“结束” 按钮
效果:游戏重置,屏幕重新刷新
结果正常
键盘事件的功能测试
1.方块移动测试
游戏过程中,点击“左、右、下”方向键
输出:方块正常移动,不会移出边界
结果:正常
2. 方块反转测试
游戏过程中,点击向上方向键
输出:方块无边界和障碍物阻挡的情况下可反转
结果:正常
方块堆砌与消行测试
1. 当方块下落到最底面或与障碍物接触时
输出:方块成功堆砌在底部
结果:正常
2. 当出现满行现象时
输出:所有满行被消除,方块依次下移
结果:正常
课程设计心得体会
课程设计是培养学生综合运用所学知识,发现,提出,分析和解决实际问题,锻炼实践能力的重要环节,是对学生实际工作能力的具体训练和考察过程.
Java语言是一门强大的语言,比我们所学过的C语言有很强的功能,可以有美化的图形界面设计,可以连接数据库,解决用文件存储的繁琐过程,使得软件所产生的数据更容易管理和维护。
在设计初期时,组员通过交流,讨论,定下了课题——俄罗斯方块游戏。分工之后,将整个课题分为三个部分。在编程阶段,我们发现一学期所学的知识难易填补课题所需的知识漏洞,通过参考课外书籍,网络资源,掌握了课本之外的一些技巧与知识。我们在课程设计的同时感觉到我们还欠缺很多。通过此次课程设计,我们对专业知识又有了更进一步的了解,对培养我们专业兴趣起到良好的推动作用。
课程设计在衔接之时也遇到一些问题,有一些程序BUG,经过讨论修改,完善了程序。因为是分工作业,所以相互之间的交流是十分重要的,每次进度的保存要记录,这样方便组员之间共享。因为专业水平还有不足,所以程序不算完美,有一些功能没有得到实现,这也激励着我们在以后的专业课学习中,要更加的努力,填补知识的空缺。经过两周的课程设计,俄罗斯方块游戏也可以运行,有了此次宝贵的经验,相信以后的对以后的课程设计有更大的帮助。
参考书籍及资料
1、《JAVA语言程序设计》张思民编著,清华大学出版社
2、《Java开发实战1200例》李钟尉 陈丹丹编著,清华出版社
3、《Java编程》(美)法瑞尔编著,科学出版社
4、《JAVA程序设计教程》郭广军 刘安丰编著,武汉大学出版社
第二篇:俄罗斯方块游戏
有偿征集原创文章,为您创收 投递文章减小字体 增大字体 很多编程爱好者都编写过俄罗斯方块的游戏程序。很久以前,我用Tc2.0也做过一个;最近有好些朋友看见我以前的俄罗斯方块的程序后, 问我是怎么做的。我一直想把这个程序的整个过程写一份详细的东西,与各位编程爱好者分享,一直没空。正好现在放假了,而且离回家还有几天。于是我就把这个程序重新写了一遍,尽量使程序的结构比较清晰好懂一些。同时写了下面的这份 很多编程爱好者都编写过俄罗斯方块的游戏程序。很久以前,我用Tc2.0也做过一个;最近有好些朋友看见我以前的俄罗斯方块的程序后,问我是怎么做的。我一直想把这个程序的整个过程写一份详细的东西,与各位编程爱好者分享,一直没空。正好现在放假了,而且离回家还有几天。于是我就把这个程序重新写了一遍,尽量使程序的结构比较清晰好懂一些。同时写了下面的这份东西。 俄罗斯方块游戏的程序中用到了一些方法。为了比较容易理解这些方法,我在讲述的同时写了些专门针对这些方法的示例程序。这些示例程序力求短小,目的是用最小的代码能够清楚的示例所用的方法。这些示例程序都经过tc2.0测试。最后还附了完整的俄罗斯方块游戏的源代码,和最终的可执行程序。如果你看了这份东东,有什么意见和想法,请发电子邮件告诉我。我将会继续更新这分东东,最新的版本可以在我的个人主页上下载。 下面的问题是有关俄罗斯方块程序的,其中有些是朋友问我的,有些是我认为可能会被问到的。我尽量按问题从易到难排列这些问题。 关于俄罗斯方块程序的一些问题:******************************************************Tc2.0中怎么样设置图形显示?Tc2.0中常用图形函数的用法?怎样获取鍵盘输入?怎样控制方块的移动?怎样控制时间间隔(用于游戏中控制形状的下落)?游戏中的各种形状及整个游戏空间怎么用数据表示?游戏中怎么判断左右及向下移动的可能性?游戏中怎么判断某一形状旋转的可能性?按向下方向键时加速某一形状下落速度的处理?怎么判断某一形状已经到底?怎么判断某一已经被填满?怎么消去已经被填满的一行?怎么消去某一形状落到底后能够消去的所有的行?(如长条最多可以消去四行)怎样修改游戏板的状态?怎样统计分数?怎样处理升级后的加速问题?怎样判断游戏结束?关于计分板设计的问题。关于“下一个”形状取法的问题。剩下的问题。******************************************************新的问题: 我想有一个最高记录的显示,
应该怎么做呀? 我想实现一个进度存储功能,应该怎么做呀?Tc2.0中怎么样设置图形显示? Tc2.0中有两种显示模式,一种是我们所熟知的字符模式,另一种是图形模式。在字符模式下只能显式字符,如ASCII字符。一般是显示25行,每行80个字符。程序缺省的是字符模式。在字符模式下不能显式图形和进行绘图操作。要想进行图形显示和绘图操作,必须切换到图形模式下。 Tc2.0中用initgraph()函数可以切换到图形模式,用closegraph()可以从图形模式切换回字符模式。initgraph()和closegraph()都是图形函数,使用图形函数必须包括头文件"graphics.h"。 void far initgraph(int far *graphdriver,int far *graphmode,char far *pathtodriver);graphdriver是上涨指向图形驱动序号变量的指针;graphmode是在graphdriver选定后,指向图形显示模式序号变量的指针。pathtodriver表示存放图形驱动文件的路径。 Tc2.0中有多种图形驱动,每种图形驱动下又有几种图形显示模式。在我的程序中图形驱动序号为VGA,图形显示模式序号为VGAHI。这是一种分辨率为640*480(从左到右坐标依次为0-639,从上到下坐标依次为0-479),能够显示16种颜色的图形模式。别的图形驱动序号和图形显示模式序号,可以从手册或联机帮助中找到。 pathtodriver指示存放图形驱动文件的路径。图形驱动序号不同,图形驱动文件也不同。序号为VGA图形驱动对应"egavga.bgi"这个图形驱动文件。"egavga.bgi"一般在Tc目录下。void far closegraph(void); 没有参数,从图形模式直接返回字符模式。initgraph()和closegraph()的常用用法如下:int gdriver = VGA, gmode=VGAHI, errorcode;/* initialize graphics mode */initgraph(&gdriver, &gmode, "e:\\tc2");/* read result of initialization */errorcode = graphresult();if (errorcode != grOk) /* an error occurred */{printf("Graphics error: %s\n", grapherrormsg(errorcode));printf("Press any key to halt:");getch();exit(1); /* return with error code */}/* return to text mode */closegraph();Tc2.0中常用图形函数的用法?在这里讲几个游戏中用到的绘图用的图形函数:setcolor();line();rectangle();settextjustify();outtextxy();setfillstyle();bar();void far setcolor(int color); 设置画线、画框和在图形模式下显示文字的当前颜色。这个函数将影响line()、rectangle()和outtextxy()函数绘图的颜色。color可以取常的颜色常量:BLACK ? 0BLUE ? 1GREEN ? 2CYAN ? 3RED ? 4MAGENTA ? 5BROWN ? 6LIGHTGRAY ? 7DARKGRAY ? 8LIGHTBLUE ? 9LIGHTGREEN ?10LIGHTCYAN ?11LIGHTRED ?12LIGHTMAGENTA ?13YELLOW ?14WHITE ?15void far line(int x1,int y1,int x2,int y2);用当前颜色从(x1,y1)画一条到(x2,y2)的
线段。void far rectangle(int left,int top,int right,int bottom);用当前颜色画一个左上角为(left,top)、右下角为(right,bottom)的矩形框。void far settextjustify(int horz,int vert);设置图形模式下文字输出的对齐方式。主要影响outtextxy()函数。horiz和vert可取如下枚举常量:horiz ?LEFT_TEXT ? 0 ?Left-justify text?CENTER_TEXT ? 1 ?Center text?RIGHT_TEXT ? 2 ?Right-justify textvert ?BOTTOM_TEXT ? 0 ?Justify from bottom?CENTER_TEXT ? 1 ?Center text?TOP_TEXT ? 2 ?Justify from topvoid far outtextxy(int x,int y,char * textstring);在(x,y)处用当前字体(缺省的字体是DEFAULT_FONT)显示字符串textstring,字符串的对齐方式由settextjustify()指定。void far setfillstyle(int pattern,int color);设置图形的填充模式和填充颜色,主要影响bar()等函数。pattern一般取枚举常量值SOLID_FILL,color的取值与setcolor(int color)中color的取值范围相同。 介绍完了前面两个问题,现在来写一个程序。这个程序演示前了面所介绍的几个图形函数。 程序prog1.c怎样获取鍵盘输入? 在Tc2.0中有一个处理键盘输入的函数bioskey();int bioskey(int cmd); 当cmd为1时,bioskey()检测是否有键按下。没有键按下时返回0;有键按下时返回按键码(任何按键码都不为0),但此时并不将检测到的按键码从键盘缓冲队列中清除。 当cmd为0时,bioskey()返回键盘缓冲队列中的按键码,并将此按键码从键盘缓冲队列中清除。如果键盘缓冲队列为空,则一直等到有键按下,才将得到的按键码返回。 Escape键的按键码为0x11b,下面的小程序可以获取按键的按键码。for (;;){key=bioskey(0); /* wait for a keystroke */printf("0x%x\n",key);if (key==0x11b) break; /* Escape */}常用按键的按键码如下:#define VK_LEFT 0x4b00#define VK_RIGHT 0x4d00#define VK_DOWN 0x5000#define VK_UP 0x4800#define VK_HOME 0x4700#define VK_END 0x4f00#define VK_SPACE 0x3920#define VK_ESC 0x011b#define VK_ENTER 0x1c0d 完整的程序请参见prog2.c、prog3.c。prog2.c获取按键的按键码,按Escape键退出程序。prog3.c根据不同的按键进行不同的操作,按Escape键退出程序。怎样控制方块的移动? 方块移动的实现很简单,将方块原来的位置用背景色画一个同样大小的方块,将原来的方块涂去。然后在新的位置上重新绘制方块就可以了。这样就实现了方块的移动。完整的程序请参见prog4.c。这个用方向键控制一个黄色的小方块在屏幕上上、下、左、右移动。这个程序用到了前面几个问题讲的内容,如果你有点忘了,还要回头看看哦。:)怎样控制时间间隔(用于游戏中控制形状的下落)? 解决这个问题要用到时钟中断。时钟中
断大约每秒钟发生18.2次。截获正常的时钟中断后,在处理完正常的时钟中断后,将一个计时变量加1。这样,每秒钟计时变量约增加18。需要控控制时间的时候,只需要看这个计时变量就行了。 截获时钟中断要用到函数getvect()和setvect()。两个函数的声明如下:?void interrupt (*getvect(int interruptno))();?void setvect(int interruptno, void interrupt (*isr) ( )); 保留字interrupt指示函数是一个中断处理函数。在调用中断处理函数的时候,所有的寄存器将会被保存。中断处理函数的返回时的指令是iret,而不是一般函数用到的ret指令。getvect()根据中断号interruptno获取中断号为interruptno的中断处理函数的入口地址。setvect()将中断号为interruptno的中断处理函数的入口地址改为isr()函数的入口地址。即中断发生时,将调用isr()函数。 在程序开始的时候截获时钟中断,并设置新的中断处理。在程序结束的时候,一定要记着恢复时钟中断哦,不然系统的计时功能会出问题的。具体演示程序请参见prog5.c。由于中断处理大家可能用的不多,所以我把prog5.c这个程序完整地贴在下面,并加上详细的解释。/* prog5.c */This is an interrupt service routine. You can NOT compile thisprogram with Test Stack Overflow turned on and get an executablefile which will operate correctly. *//* 这个程序每隔1秒钟输出一个整数,10秒钟后结束程序。按escape键提前退出程序 。*/#include <stdio.h>#include <dos.h>#include <conio.h>/* Escape key */#define VK_ESC 0x11b #define TIMER 0x1c /* 时钟中断的中断号 *//* 中断处理函数在C和C++中的表示略有不同。如果定义了_cplusplus则表示在C++环境下,否则是在C环境下。 */#ifdef __cplusplus#define __CPPARGS ...#else#define __CPPARGS#endifint TimerCounter=0; /* 计时变量,每秒钟增加18。 *//* 指向原来时钟中断处理过程入口的中断处理函数指针(句柄) */void interrupt ( *oldhandler)(__CPPARGS);/* 新的时钟中断处理函数 */void interrupt newhandler(__CPPARGS){/* increase the global counter */TimerCounter++;/* call the old routine */oldhandler();}/* 设置新的时钟中断处理过程 */void SetTimer(void interrupt (*IntProc)(__CPPARGS)){oldhandler=getvect(TIMER);disable(); /* 设置新的时钟中断处理过程时,禁止所有中断 */setvect(TIMER,IntProc);enable(); /* 开启中断 */}/* 恢复原有的时钟中断处理过程 */void KillTimer(){disable();setvect(TIMER,oldhandler);enable();}void main(void){int key,time=0;SetTimer(newhandler); /* 修改时钟中断 */for (;;){if (bioskey(1)){key=bioskey(0);if (key==VK_ESC) /* 按escape键提前退出程序 */{printf("User cancel!\n")
;break;}}if (TimerCounter>18) /* 1秒钟处理一次 */{/* 恢复计时变量 */TimerCounter=0;time++;printf("%d\n",time);if (time==10) /* 10秒钟后结束程序 */{printf("Program terminated normally!\n");break;}}}KillTimer(); /* 恢复时钟中断 */}游戏中的各种形状及整个游戏空间怎么用数据表示?以后我提到的形状都是指下面七种形之一及它们旋转后的变形体。□□□□ □□□□ □□□□ □□□□ □■□□ □■■□ □□□□ □□□□ □■□□ □■□□ □■□□ □■■□ □■■□ □■□□ ■■■□ ■■□□ □□□□ □■□□ □□□□ □□□□ □■□□ □□□□ ■■□□ □■□□ □■■□ □■■□ □■□□ □■■□ 我定义了一个结构来表示形状。struct shape{int xy[8];int color;int next;}-1 0 1 2-3□□□□-2□□□□-1□□□□0□■□□ 所有的各种形状都可以放在4x4的格子里。假定第二列,第四行的格子坐标为(0,0)(如上图中黑块所示),则每个形状的四个方块都可以用4个数对来表示。坐标x从左向右依次增加,y从上到下依次增加。表示的时候,组成该形状的四个方块从左到右,从上到下(不一定非要按这个顺序)。如上面七种形状的第一个用数对来表示就是(-2,0)、(-1,0)、(0,0)、(1,0)。结构shape中的xy就是用来表示这4个数对的。为了简化程序,用一维数组xy[8]来表示。xy[0]、xy[1]表示第一个数对,xy[2]、xy[3]表示第二个数对,依次类推。 shape中的color表示形状的颜色,不同的形状有不同的颜色。七种形状及它们旋转后的变形体一共有19种形状,用一个全局数组表示。假定旋转的方向是逆时针方向(顺时针方向道理一样)。shape中的next就表示当前形状逆时针旋转后的下一个形状的序号。例如:第一种形状及其旋转变形的形状用结构表示如下。□□□□ □□□□ □□□□ □□□□ □■□□ □□□□ □■■□ □□□□ □■□□ □□■□ □□■□ ■■■□ □■■□ ■■■□ □□■□ ■□□□ struct shape shapes[19]={/*{x1,y1,x2,y2,x3,y3,x4,y4, color, next}*/{ 0,-2, 0,-1, 0, 0, 1, 0, CYAN, 1}, /* */{-1, 0, 0, 0, 1,-1, 1, 0, CYAN, 2}, /* # */{ 0,-2, 1,-2, 1,-1, 1, 0, CYAN, 3}, /* # */{-1,-1,-1, 0, 0,-1, 1,-1, CYAN, 0}, /* ## */……} 游戏空间指的是整个游戏主要的界面(呵呵,这个定义我实在想不出更准确的,还请哪位大虾指点)。实际上是一个宽10格子、高20格子的游戏板。用一个全局数组board[12][22]表示。表示的时候:board[x][y]为1时表示游戏板上(x,y)这个位置上已经有方块占着了,board[x][y]为0表示游戏板上这位置还空着。为了便于
判断形状的移动是否到边、到底,初始的时候在游戏板的两边各加一列,在游戏板的下面加一行,全部填上1,表示不能移出界。即board[0][y],board[11][y](其中y从0到21)初始都为1,board[x][21](其中x从1到10)初始都为1。1 2 3 4 5 6 7 8 9101□□□□□□□□□□2□□□□□□□□□□3□□□□□□□□□□4□□□□□□□□□□5□□□□□□□□□□6□□□□□□□□□□7□□□□□□□□□□8□□□□□□□□□□9□□□□□□□□□□ 10□□□□□□□□□□11□□□□□□□□□□12□□□□□□□□□□13□□□□□□□□□□14□□□□□□□□□□15□□□□□□□□□□16□□□□□□□□□□17□□□□□□□□□□18□□□□□□□□□□19□□□□□□□□□□20□□□□□□□□□□ prog6.c演示了用结构表示各种形状的方法。虽然程序稍长一些,但并不是特别复杂。其中游戏板初始化部分并没有真正用到,但是后面的程序会用到的。其中SIZE定义为16,这样将整个屏幕的坐标系由原来的640×480转换成40×30(640/16=40,480/16=30)。游戏中所有的坐标都是基于40×30的坐标系的,这样有助于简化程序。坐标的转换在程序中由DrawBlock(int x,int y)来体现。 新的坐标系如下图所示:-8-7-6-5-4-3-2-1 0 1 2 3 4 5 6 7 8 9101112131415161xxxxxxxxxxxx2425262728293031-4□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□-3□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□-2□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□-1□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□0□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□1□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□2□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□3□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□4□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□5□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□6□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□7□□□□□□□□□■■■■■■■■■■□
□□□□□□□□□□□□□□□□□□□□8□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□9□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□10□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□11□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□12□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□13□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□14□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□15□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□16□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□17□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□18□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□19□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□20□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□21□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□22□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□23□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□24□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□25□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□26□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ 新坐标中最主要的是就是上面两块黑色的部分。左边那块大的就是游戏板(横坐标从1到10,纵坐标从1到20),右边那块小的就是显示“下一个”形状的部分(横坐标从14到17,纵坐标从3到6)。这个新的坐标系是整个游戏的基础,后面所有的移动、变形等的计算都是基于这个坐标系的。游戏中怎么判断左右及向下移动的可能性? 看懂了前面的各种形状和游戏板等的表示,接下来的东西就都好办多了。先来看一下某个形状如何
显示在游戏板当中。假设要在游戏板中显示第一个形状。第一个形状在结构中的表示如下:struct shape shapes[19]={/*{x1,y1,x2,y2,x3,y3,x4,y4, color, next}*/{ 0,-2, 0,-1, 0, 0, 1, 0, CYAN, 1}, ……} 那么这个组成形状四个方块的坐标表示为(0,-2)、(0,-1)、(0,0)和(1,0)。这实际上是相对坐标。假形状的实际坐标指的是4x4方块中的第二列、第三行的方块的位置,设这个位置为(x,y)。那么组成这个形状的四个小方块的实际坐标(以第一个形状为例)就是(x+0,y-2)、(x+0,y-1)、(x+0,y+0)和(x+1,y+0)。由于所有的形状都可以在4x4的方块阵列中表示,这样就找到了一种统一的方法来表示所有的形状了。-1 0 1 2-3□□□□ 相对坐标-2□■□□ -1□■□□ 组成第一种形状的四个方块的相对坐标为(0,-2)、(0,-1)、(0,0)和(1,0)。0□■■□ 让我们看看形状是如何显示在游戏板中的(以第一个形状为例)。1 2 3 4 5 6 7 8 9101□■□□□□□□□□ 形状的坐标为(2,3)。组成形状的四个方块的坐标由形状的2□■□□□□□□□□ 坐标加上这四个小方块各自的相对坐标得出。它们分别是:3□■■□□□□□□□ (2+0,3-2)、(2+0,3-1)、(2+0,3-0)和(2+1,3-0)。即:4□□□□□□□□□□ (2,1)、(2,2)、(2,3)和(3,3)。如左图所示。5□□□□□□□□□□6□□□□□□□□□□7■□□□□□□□□□ 形状的坐标为(1,9)。组成形状的四个方块的坐标分别是:8■□□□□□□□□□ (1+0,9-2)、(1+0,9-1)、(1+0,9-0)和(1+1,9-0)。即:9■■□□□□□□□□ (1,7)、(1,8)、(1,9)和(2,9)。如左图所示。10□□□□□□□□□□11□□□□□□□□□□12□□□□□□□□□□13□□□□□□□□□□14□□□□□□□□□□15□□□□□□□□□□16□□□□□□□□□□17□□□□□□□□□□18□□□□□□□□■□ 形状的坐标为(9,20)。组成形状的四个方块的坐标分别是:19□□□□□□□□■□ (9+0,20-2)、(9+0,20-1)、(9+0,20-0)和(9+1,20-0)。即:20□□□□□□□□■■ (9,18)、(9,19)、(9,20)和(10,20)。如左图所示。 从现在起,我不再举别的示例程序了。从现在开始所有的示例代码均来自于我写的"Russia.c"。为了记录游戏板的状态,用了一个全局数组board[12][22]。board[x][y](其中x从0到11,y从1到21)等于1表示(x,y)这个位置已经被填充了,组成形状的四个方块的坐标都不能为(x,y),否则将发生冲突。board[x][y](其中x从1到10,y从1到20)等于表示(x,y)这个位置还没有被填充。 游戏板初始化时,给board[0][y],board[11][y](其中y从1到21)都赋为1,给board[x][21](其中x从1到10)都赋为1。这相当
于一开始就给游戏板左右和下方加了个“边”。所有的形状都不能够移入这个“边”,否则将发生冲突。 现在我们可以开始讨论如何判断一个形状向左、向右和向下移动的可能性了。先说个概念,“当前形状”是指那个正在下落还没有落到底的那个形状。如果当前形状向左移动,不与游戏板现有状态发生冲突,则可以向左移动。具体做法是:先假设当前形状已经向左移动了,判断此时是否与游戏板现有状态发生冲突。如果不发生冲突,则可以向左移动。否则,不可以向左移动。 判断索引号为ShapeIndex的形状在坐标(x,y)是否与游戏板当前状态发生冲突的代码如下。我把详细的说明加在这段代码中。enum bool Confilict(int ShapeIndex,int x,int y){int i;/* 对组成索引号为ShapeIndex的形状的四个方块依次判断 */for (i=0;i<=7;i++,i++) /* i分别取0,2,4,6 */{/* 如果四个方块中有任何一个方块的x坐标小于1或大于10,表示超出左边界或右边界。此时,发生冲突。 */if (shapes[ShapeIndex].xy[i]+x<1 ||shapes[ShapeIndex].xy[i]+x>10) return True;/* 如果四个方块中某个方块的y坐标小于1,表示整个形状还没有完全落入游戏板中。此时,没有必要对这个方块进行判断。*/if (shapes[ShapeIndex].xy[i+1]+y<1) continue;/* 如果四个方块中有任何一个方块与游戏板当前状态发生冲突,则整个形状在(x,y)处与游戏板当前状态冲突 */if (board[shapes[ShapeIndex].xy[i]+x][shapes[ShapeIndex].xy[i+1]+y])return True;}/* 四个方块中没有任何一个方块与游戏板当前状态发生冲突,则整个形状在(x,y)处没有与游戏板当前状态冲突 */return False;}对以上代码附加说明如下: shapes[ShapeIndex].xy[i](其中i等于0,2,4,6)表示组成索引号为ShapeIndex的形状的某个方块的x相对坐标。(i等于0时,表示第1个方块的x相对坐标;i等于2时,表示第2个方块的x相对坐标;i等于4时,表示第3个方块的x相对坐标;i等于6时,表示第4个方块的x相对坐标。) shapes[ShapeIndex].xy[i](其中i等于1,3,5,7)表示组成索引号为ShapeIndex的形状的某个方块的y相对坐标。(i等于1时,表示第1个方块的y相对坐标;i等于3时,表示第2个方块的y相对坐标;i等于5时,表示第3个方块的y相对坐标;i等于7时,表示第4个方块的y相对坐标。) shapes[ShapeIndex].xy[i]+x(其中i等于0,2,4,6)表示索引号为ShapeIndex的形状的坐标为(x,y)时,组成该形状的某个方块的x实际坐标。(i等于0时,表示第1个方块的x实际坐标;i等于2时,表示第2个方块的x实际坐标;i等于4时,表示第3个方块的x实际坐标;i等于6时,表示第4个方块的x实际坐标。) shapes[ShapeIndex].xy[
i]+y(其中i等于1,3,5,7)表示索引号为ShapeIndex的形状的坐标为(x,y)时,组成该形状的某个方块的y实际坐标。(i等于1时,表示第1个方块的y实际坐标;i等于3时,表示第2个方块的y实际坐标;i等于5时,表示第3个方块的y实际坐标;i等于7时,表示第4个方块的y实际坐标。)现在来看看这句是什么意思吧。board[shapes[ShapeIndex].xy[i]+x][shapes[ShapeIndex].xy[i+1]+y]可以这样理解,把上面一句分开来看::ActualX=shapes[ShapeIndex].xy[i]+x;/* 其中x为0,2,4,6 */表示某个方块实际的x坐标。ActualY=[shapes[ShapeIndex].xy[i+1]+y;表示某个方块实际的y坐标。board[ActualX][ActualY]就是与某个方块坐标相同处的游戏板的标志。如果此标志不为0(为1),表示这个方块与游戏板发生冲突。如果此标志为0,表示这个方块没有与游戏板发生冲突。这段写的比较长,但是不是特别难理解。游戏中很多地方都用到了这种相对坐标向实际坐标的转换方式,看懂了这一段对理解其他部分的代码很有帮助。仔细看过这段代码后,你可能会提一个问题:不是已经在游戏板的左右两边都加了“边”了吗,为什么还要加下面这个对x坐标的判断呢?/* 如果四个方块中有任何一个方块的x坐标小于1或大于10,表示超出左边界或右边界。此时,发生冲突。 */if (shapes[ShapeIndex].xy[i]+x<1 ||shapes[ShapeIndex].xy[i]+x>10) return True;这是因为有一种特殊情况,如下图所示:■■■ 2 3 4 5 6 7 8 9101■□□□□□□□□□ 这在当前形状刚出来的时候,是可能发生的。但是我们只给游戏板2□□□□□□□□□□ 加了一层“边”。对于这个形状的最左边的那个方块将失去判断,3□□□□□□□□□□ 如果不予理会,这个形状将会“挂”在游戏板的左上角!当初我也4□□□□□□□□□□ 没有想到这一点,后来发现会有形状“挂”在最顶层,而导致游戏 5□□□□□□□□□□ 提前退出。发现了这个问题。6□□□□□□□□□□7□□□□□□□□□□ 8□□□□□□□□□□ 加了这个判断后,游戏板的左右两个“边”对冲突的判断就是去意9□□□□□□□□□□ 义了。因为没有这两个“边”,对于冲突的判断也不会出错。不过10□□□□□□□□□□ 为了程序易于理解,还是保留了游戏板的左右两个“边”。11□□□□□□□□□□12□□□□□□□□□□13□□□□□□□□□□14□□□□□□□□□□15□□□□□□□□□□16□□□□□□□□□□ 17□□□□□□□□□□18□□□□□□□□□□19□□□□□□□□□□20□□□□□□□□□□