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 ? 0 BLUE ? 1 GREEN ? 2 CYAN ? 3 RED ? 4 MAGENTA ? 5 BROWN ? 6 LIGHTGRAY ? 7 DARKGRAY ? 8 LIGHTBLUE ? 9 LIGHTGREEN ?10 LIGHTCYAN ?11 LIGHTRED ?12 LIGHTMAGENTA ?13 YELLOW ?14 WHITE ?15
void 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 text vert ?BOTTOM_TEXT ? 0 ?Justify from bottom ?CENTER_TEXT ? 1 ?Center text ?TOP_TEXT ? 2 ?Justify from top
void 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 this program with Test Stack Overflow turned on and get an executable file which will operate correctly. */
/* 这个程序每隔1秒钟输出一个整数,10秒钟后结束程序。 按escape键提前退出程序 。*/
#include #include #include
/* Escape key */ #define VK_ESC 0x11b
#define TIMER 0x1c /* 时钟中断的中断号 */
/* 中断处理函数在C和C++中的表示略有不同。 假如定义了_cplusplus则表示在C++环境下,否则是在C环境下。 */
#ifdef __cplusplus #define __CPPARGS ... #else #define __CPPARGS #endif
int 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中的n
ext就表示当前外形逆时针旋转后的下一个外形的序号。例如:第一种外形及其旋 转变形的外形用结构表示如下。
□□□□ □□□□ □□□□ □□□□ □■□□ □□□□ □■■□ □□□□ □■□□ □□■□ □□■□ ■■■□ □■■□ ■■■□ □□■□ ■□□□
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 910 1□□□□□□□□□□ 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 910111213141516171819202122232425262728293031 -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 910 1□■□□□□□□□□ 外形的坐标为(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 910 1■□□□□□□□□□ 这在当前外形刚出来的时候,是可能发生的。但是我们只给游戏板 2□□□□□□□□□□ 加了一层“边”。对于这个外形的最左边的那个方块将失去判定, 3□□□□□□□□□□ 假如不予理会,这个外形将会“挂”在游戏板的左上角!当初我也 4□□□□□□□□□□ 没有想到这一点,后来发现会有外形“挂”在最顶层,而导致游戏 5□□□□□□□□□□ 提前退出。发现了这个问题。 6□□□□□□□□□□ 7□□□□□□□□□□ 8□□□□□□□□□□ 加了这个判定后,游戏板的左右两个“边”对冲突的判定就是去意 9□□□□□□□□□□ 义了。因为没有这两个“边”,对于冲突的判定也不会出错。不过 10□□□□□□□□□□ 为了程序易于理解,还是保留了游戏板的左右两个“边”。 11□□□□□□□□□□ 12□□□□□□□□□□ 13□□□□□□□□□□ 14□□□□□□□□□□ 15□□□□□□□□□□ 16□□□□□□□□□□ 17□□□□□□□□□□ 18□□□□□□□□□□ 19□□□□□□□□□□ 20□□□□□□□□□□
假如你对我上面提出的新问题及对于这个问题的解释不太明白,没关系,这并不重要。因为现在才刚刚开始,而且刚才所说的这个问题只 有在非凡情况下才出现(当然,一旦发生上面说的问题,游戏就出错啦!^_^ ),对于理解整个程序的思路影响不大。看多了就会明白了(你 会说:原来就这么简单!)。
视频教程列表
文章教程搜索
C语言程序设计推荐教程
C语言程序设计热门教程
|