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(%dn,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 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□□□□□□□□□□