用C语言写俄罗斯方块

来源:互联网 发布:js获取窗口高度 编辑:程序博客网 时间:2024/06/06 02:25

C语言写俄罗斯方块

 

目录(需求):

1.  屏幕作图与窗口实现;

2.  方块的构造与产生;

3.  方块的移动与翻转;

4.  中断计时与方块自由下落;

5.  判断方块碰撞与消行;

6.  按键控制;

7.  扩展新的功能;

开发环境:

1.       编译器tc2.0

2.       编辑器wintc1.8

3.       运行环境xpdosbox

1. 屏幕作图与窗口实现

将整个屏幕划分成四部分:a、主游戏窗口;b、一个给预览下一个方块的4*4窗口;c、记录窗口SCORE和记录窗口LEVELd、提示信息窗口。

 

 

 

先来看看这些窗口是怎么绘制的。

程序中所有窗口绘制都抽象为一个窗口实现函数:

 

void DrawWin(char* title, int linecolor, int fillcolor, int x0, int y0, int x1, int y1);

参数:

     title: 窗口标题;

     linecolor:边框线颜色;

     fillcolor:窗口背景填充颜色;

     x0,y0,x1,y1: 窗口左上角和右下角的坐标。

 

     在了解这个函数的具体实现之前,先来看看TC2.0都给我们提供了些什么作图函数。用TC作图时,需要包含头文件 graphics.h ,这个头文件使用了扩展库Graphics.lib。所以在使用作图函数之前,先确定你的Includelib目录下是否有这两个文件。以下是图形函数介绍:

 

void far initgraph(int far *graphdriver,int far *graphmode,char far *pathtodriver);

参数:

     graphdriver: 指向图形驱动序号变量的指针;

     graphmode:指向图形显示模式序号变量的指针;

     pathtodriver:存放图形驱动文件的路径;

 

Tc2.0中用此函数可以切换到图形模式。代码实现格式比较固定,请看代码128~136行:

 

 

 

其他一些重要的图形函数:

 

 

    

                                                                           

在来看DrawWin 的实现:

 

 

 

2.方块的构造与产生

在了解方块构造之前,先来看看程序中使用的几种坐标形式。

(1)    绝对坐标

(2)    相对坐标

(3)    像素坐标

绝对坐标:程序中默认分辨率是 640*480,单位是一个像素,以左上角为坐标(0,0)零点

像素坐标:即单位为一个像素。

相对坐标:程序中以绝对坐标(GS_X, GS_Y)为参考零点,以一个SSIZE*SSIZE方块为单位,水平轴为x,向右增加,垂直轴为y,向下增加的坐标。

方块坐标:即单位为一个SSIZE*SSIZE方块。

 

上面的SSIZEGS_X, GS_Y等均在头文件 square.h中定义:

 

 

在上面的代码注释中,提到了“杯子”。实际上,主窗口对应着一个全局的二维数组:

static unsigned char GameSpace[GS_RIGHT-GS_LEFT+1][GS_BOTTOM +1];

这个数组记录了主窗口主所有方块的堆叠情况,根据预定义不能看出它是一个12, 25列的数组,它就是上文提到的“杯子”的抽象。

初始化的时候,这个“杯子”将GS_LEFTGS_RIGHT作为左右壁,数组中对应都赋值为1GS_BOTTOM作为“杯子”的底,也赋值为1。“杯子”其他位置赋值为0,表示为,方块可以在这个区间活动。

有了这个杯子模型,方块的构造和移动实现都方便得多了。因为只要确定了方块的相对坐标(rxry),就很容易得出它在“杯子”中所占据的位置,它就是GameSpace[ry][rx]。正因为如此,方块形状的构造也用到了相对坐标,请看下面的方块形状结构:

 

 

 

现在主要关心前面三个成员,xy[32]size共同确定这个方块的形状。比如:

 

  

 

方块都是结构,它也是相对坐标表示,参考点是4*4 SSIZE方块的左上角。如上面的长条形,它由4个小方块(SSIZE*SSIZE)构成,size4.

xy[32]赋值要遵循从左到右,从上到下的循序。所以这个形状的4个小方块坐标依次是(1,0)、(1,1)、(1,2)、(1,3),把它们循序记录到xy[32]中。

第三个结构体成员,表示方块的颜色。

我们如何在游戏时产生这些方块呢?

程序中使用了随机选取的方法,请看:

3.方块的移动与翻转

上面的方块形状结构还有一个成员 next,这个成员就是用来完成翻转的工作的。程序中所有的方块形状都定义在这个结构的数组中。请看:

 

 

这里只列出AllShape[1].next=0,这说明什么呢?

AllShape中元素在数组中的位置作为索引index,那么第一个长条的索引index=0,在它翻转后,形状变为第二个长条,它的索引值为1,所以index=AllShape[0].next=1。第二个长条翻转也是这个原理。

所以,要得到翻转后的图形,只需要通过索引index等于当前形状的next成员,再通过这个indexAllShape中获得即可。

 

方块的移动,实际上是个不断的画方块、擦除方块,再在新位置画方块的过程。先来看看程序中的实现函数:

DrawShapeInMain是以相对坐标在主窗口中画出方块形状,两个参数rx0ry0代表4*4 SSIZE的方块形状左上角相对于“杯子”左上角的坐标,index代表要画的方块形状的索引。如果方块形状中某个小方块相对于本身4*4 SSIZE的大方块的相对坐标是(rx,ry),容易的算出它相对于杯子形状的相对坐标为(rx0+rxry0+ry),在通过这个相对坐标确定画出方块的绝对坐标。

EraseShapeInMainDrawShapeInMain的原理是一样的,不过填充颜色用的是背景色BkColor这也是一个全局变量。

4.中断计时与方块自由下落

传统的俄罗斯方块都有这样的特性,就是不管你的怎么移动或者翻转方块形状,每隔一定时间这个方块都会自由下落一格。而且,这个自由下落的速度还可能随你达到的分数或是等级而不断增加。这种功能是怎么实现的呢?

一种思路是通过延时来实现。例如,如果要求方块会每隔1s下落一格,就可以在循环中检测按键,响应,然后延时下落,之后再重复这个过程。这种方式有个弊端,就是如果延时的时间过长,按键响应就会变得很不灵敏。如果想用延时来实现方块自由下落,有一种较好的方式,就是将时间微分。比如要求每隔1s下落一格,我们可以在每次按键判断后延时10ms,然后再循环检测按键,当经过100次这样的循环后再执行自由下落。

一种更好的办法是引入中断。系统为我们提供了一个时钟中断,时钟中断大约每秒钟发生18.2次。我们可以设置一个全局的计时变量TimerCounter=0,这个变量会在每次发生中断后加1。在按键检测之后,判断这个变量是否大于我们设置的门限值,就可以确定方块是否该自由下落了。这种方法比起延时要节省系统资源,而且按键响应也会更好。

下面来看下怎么在程序中使用这个中断资源:

  

 

 下面是主程序中按键处理后,处理方块自由下落的一段代码:

 

 

 


5.判断方块碰撞与消行

俄罗斯方块还有另一个特性,当你左右移动,或是翻转遇到旁边正好有填充方块时,你的移动翻转操作就不会被执行;当你下移或者是方块自行下落,遇到下方有填充方块时,这个下落方块就会成为新的堆积物。你可以思考一下,要实现这种功能我们首先需要一个怎样的函数?

我想你很快会得出答案,没错,我们需要一个判断我们这次操作的结果会不会使我们移动或是翻转的方块与已有方块重合的函数。这个函数可以利用其相对坐标和“杯子”空间元素进行比较,得出是否这次移动或翻转是有效的。

这个判断过程在源码中是由MoveCheck这个函数来完成的,请看:

 

 

MoveCheck的具体用法可以分析第四节最后一段代码。下面来讨论消行的问题。首先思考:你直观理解的消行是什么概念呢?

方块消行应该是这个游戏最核心的问题之一,直观的理解是,当“杯子”一个整行都被方块填充后,这一行将被消除,同时,这一行以上所有行都会下移一行填充空出来的行。要实现这种效果,无疑需要我们扫描“杯子”空间的行,问题是我们该什么时候去扫描,又该如何去扫描呢?

前面说了MoveCheck可以判断方块是否可以再移动,如果当MoveCheck判断出方块不能下移后,活动方块就会成为新的堆积物。因此,如果判断得出这种结论,我们首先要做的工作就是根据新的堆积物更新“杯子”空间。请看:

  

 

在更新杯子空间之后,我们就可以进行消行扫描了。同样在UpdateGameSpace中,我们采用了一种效率较高的算法,只扫描新的堆积物方块纵坐标范围内行,如果有满行,只更新这行和目前最高行之间的行:

 

 

 

 

 

6.按键控制

游戏中是实现的按键功能有:(1)键盘方向键控制方块移动方向;(2)空格键控制翻转;(3)回车键控制暂停与继续;(4ESC键退出游戏。

程序运行时会在循环中不断检查键盘上这几个键的按下与否。在此获取键盘按键信息的函数,我们用的是库函数kbhit(),在执行时,如果按键有按下则该函数返回非0值,一般是1,没有按下则返回0。然后在判断有按键按下的情况下,利用函数bioskey(0)得到具体的按键值。我们将游戏中用到的按键在头文件square.h进行了宏定义:

  


    然后我们可以通过一个switch-case结构对按键作出相应的动作响应。如下所示:

 

 

 

请大家先自行思考各个按键功能的实现,在参考源码。

 

7.扩展新的功能

 

在游戏功能达到我们预先要求的情况下,我们还可以对该游戏进行扩展,比如可以增加方块的形状,实现具有穿透补空功能的小方块,类似炸弹功能对一定区域进行清除的小方块的功能等,可以充分发挥自己的想象力,并将自己的想法付诸实践,相信你可以做出更加好玩的游戏。在此我们对上述提到的扩展想法简要举出自己的例子:

 

1)添加新的方块:

这个过程其实很简单,在我们提供的源码中,你只需要在AllShape[ ]数组中增加相应的数组元素就可以了,例如游戏中用到的一个“巨大的累赘”方块你可以在AllShape[ ]中找到它:

 

 

 

当然,想添加什么方块,这个形状完全由你决定。

 

2)计分与升级:

可以根据消行函数UpdateGameSpace的返回值计算出分数,再通过分数的当前的速度等级。这个过程比较简单,这里就不详述了。

 

3)穿越方块与炸弹方块:

要实现这两种特殊方块,首先在AllShape[]添加这两个方块的形状等信息。

由于是特殊方块,需要进行特殊处理。比如穿越方块,它与其他方块不同在于,在下落过程中遇到障碍物,如果这个障碍物下方还有空格,它不会堆积在它首先遇到的这个障碍物上面,而是会穿越这个障碍,直至新的障碍下方不在有空格为止。你可以先思考下,要到达这个功能我们需要扩展那个函数的功能?

没错,我们要扩展的正是MoveCheck这个函数,让我们来看看:

  


炸弹方块功能就留给大家自行分析了。


 

原创粉丝点击