用户形象图片

大家都对游戏都比较感兴趣吧?我反正挺喜欢的,下文就是qq游戏中的 “火拼俄罗斯”进行分析的文章。俄罗斯方块程序中用到了一些比较灵巧的方法,为了让大家比较容易理解这些方法,我在讲述的同时写了些专门针对这些方法的示例程序。这些示例程序力求短小,目的是用最小的
大家都对游戏都比较感兴趣吧?我反正挺喜欢的,下文就是qq游戏中的 “火拼俄罗斯”进行分析的文章。俄罗斯方块程序中用到了一些比较灵巧的方法,为了让大家比较容易理解这些方法,我在讲述的同时写了些专门针对这些方法的示例程序。这些示例程序力求短小,目的是用最小的代码能够清楚的示例所用的方法,这些示例程序都经过TC2.0测试。本人初学编程,文章有不足之处还请指教,接下来,我分九个小步骤来分析。

方块设置与构造
首先,这个游戏有几个问题要解决,我把流程图写到主函数旁边:
main(int argc,char *argv[])
{
if (argc!=1)
{
if (argv[1]!="") Heng=atoi(argv[1]);
if (argv[2]!="") Shu=atoi(argv[2]);
}
Init();                       /*初始化界面*/
PreAct=random(8);            /*取得当前的方块*/
for(;;)                        /*以下是游戏流程*/
{
NextAct=random(8);           /*取得下一个方块*/
DrawNext(1);                 /*画出下一个方块*/
Act=PreAct;                  
if (Heng%2==0) ActH=Heng/2; else ActH=(Heng-1)/2;  
ActS=0;                  /*方块开始从游戏空间的中间下落*/
Staus=0;                 /*取开始的状态*/
NoPass=CAN;            /*物体可以下落*/
Give();                   /*取得当前的方块*/
Display(Act+1);          /*显示当前的方块,每种方块的颜色不同*/
GoOn();                  /*游戏的算法精髓所在*/
PreAct=NextAct;          /*方块下落完毕,取得下一个方块*/
DrawNext(0);  
}
}

小提示:C2.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)没有参数,代表从图形模式直接返回字符模式。
游戏中用到的绘图用的图形函数:
setcolor();
rectangle();
outtextxy();
setfillstyle();
bar();
void far setcolor(int color);
设置画线、画框和在图形模式下显示文字的当前颜色。这个函数将影响Line()、Rectangle()和Outtext xy()函数绘图的颜色。

速度控制
这个速度控制应不受用户按键的影响,每隔一定时间下落的方块(以下简称下落物)就无条件下落,并且随着时间的延长速度越来越快。因此可以通过一个系统变量来控制其值,让它越来越小,下面程序用变量“Float Delays=15000;Delays”来实现控制,闲时可用Delay(Delays)进行延时:
……
float Delays=15000;
……
void GoOn()
{
for(;;)
{
Seconds+=0.2;       /*控制方块的下落速度*/
if (Seconds>=Delays)
{
Down();
Seconds=0;
if (NoPass==BOTTOM)
{
DetectFill();
middle[ActH][ActS]=Act;
if (ActS==0) Fail();
return;
}
}
if (kbhit())
Select();
}
}
void Down()  /*方块下降*/
{
Display(0);
if (Touch(ActH,ActS,0,1)==CAN)
ActS++;
Else middle[ActH][ActS]=Act;
Display(Staus+1);
}
int Touch(int x,int y,int dx,int dy)
{
NoPass=CAN;
for (i=0;i<4;i++)
for (j=0;j<4;j++)
Position[x+dx+i][y+dy+j]+=b[i][j];
for (i=0;i<MAX;i++)
for (j=0;j<MAX;j++)
if (Position[i][j]>1) NoPass=CANNOT;
for (i=0;i<4;i++)
  for (j=0;j<4;j++)
{Position[x+dx+i][y+dy+j]-=b[i][j];
middle[x+dx+i][y+dy+j]=Act;
}
if (NoPass==CANNOT && dx==0 && dy==1)
{
for (i=0;i<4;i++)
for (j=0;j<4;j++)
Position[x+i][y+j]+=b[i][j];
NoPass=BOTTOM;
}
return NoPass;
}
……

下落物的表示
下落物的表示也就是表示方块的数据结构(4X4的数组)可以有各种形式下落物,并且每个下落物又有几种状态。为了表示它们,可以用数组表示。鉴于其形状特点,建一个4X4的数组,数组值为0则表示该位不显示,为1则显示。这里有个不好处理的地方是每种下落物的状态个数不同。有的只有一个状态,有的却有四个状态。为了处理方便,全部定义为四个状态,只是部分有重复状态而已。
定义如下:下列原代码用Act来定义方快的种数。
int a[8][4][4][4]={{{1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0},/*■■■■是显示这个图形*/
    {1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0},
    {1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0}},
  {{1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},   /*              ■■
                                               ■■显示这个图形的数组
                                                         */
    {1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},
    {1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},
    {1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0}},
  {{1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0},  /*            ■    
                                            ■■■是显示这个图形/*
    {0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0},
    {0,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0},
    {1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0}},
  {{1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0},/*         ■
                                       ■■
                                         ■*/
    {0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0},
    {1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0},
    {0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0}},
  {{0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0},/*            ■
                                        ■■
                                        ■       */
    {1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0},
    {0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0},
    {1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0}},
  {{1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0},/*         ■
                                       ■■■*/
    {1,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0},
    {1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0},
    {0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0}},
  {{0,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0},                ■
                                       /*■■■*/
    {1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0},
    {1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0},
    {1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0}},
  {{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},/    *■
                                  ■*/
    {1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}};
挺好玩吧?!是不是终于知道怎么构造出相关的图形了?呵呵,其实不难的,现在你也可以自己添加你想要的方块了。

游戏空间
游戏空间指的是整个游戏主要的界面,这个定义我实在想不出更准确的,还请哪位大虾指点。实际上的游戏空间就是一个宽12格、高20格的游戏板。用一个全局数组Position[30][30]表示:
for (i=0;i<MAX;i++)
for (j=0;j<MAX;j++)
{Position[i][j]=1;
middle[i][j]=-1;
}
for (i=0;i<Heng;i++)
for (j=0;j<Shu;j++)
Position[i][j]=0;
for (i=0;i<Heng;i++)
for (j=0;j<Shu;j++)
DrawBox(i,j,0);表示的时候:position[x][y]为1时表示游戏板上(x,y)这个位置上已经有方块占着了,Position[x][y]为0表示游戏板上这位置还空着。为了便于判断形状的移动是否到边、到底,初始的时候把数组初使化大一些!然后定义一个Position[12][20]游戏空间!表示方法同下落物,为0表示该位没有方块,为1有方块并显示出来。

下落处理
在下落过程中,有两种可能:一种是半空被挂住,一种是落到底部。无论哪种都不能再继续。我的处理办法是把下落物的数组和下落区的数组对应相加。由于二者值都或为0或为1,因此如果出现2,则表示两个有重合的,这样当然就不能下降了。恢复办法也很简单,就是相减一下就变成原来的了。当然这里还有一个必须考虑的地方就是落到底了,也得停,不然就是无底洞了:
int Touch(int x,int y,int dx,int dy)
{
NoPass=CAN;
for (i=0;i<4;i++)
for (j=0;j<4;j++)
Position[x+dx+i][y+dy+j]+=b[i][j];
for (i=0;i<MAX;i++)
for (j=0;j<MAX;j++)
if (Position[i][j]>1) NoPass=CANNOT;
for (i=0;i<4;i++)
  for (j=0;j<4;j++)
Position[x+dx+i][y+dy+j]-=b[i][j];
if (NoPass==CANNOT && dx==0 && dy==1)
{
for (i=0;i<4;i++)
for (j=0;j<4;j++)
Position[x+i][y+j]+=b[i][j];
NoPass=BOTTOM;
}
return NoPass;
}

取消满排
大家都玩过这个游戏吧?每一排充满后都要消掉。这个处理相对简单,为了刺激玩者,还可以对同时消多排的加大分量,比如同时消两排的分数要比单独两次消一排的分数要多。实现代码如下:
void DetectFill()
{
int Number,Fall,FallTime=0;
for (i=Shu-1;i>=0;i--)
{
Number=0;
for (j=0;j<Heng;j++)
if (Position[j][i]==1) Number++;
if (Number==Heng)
{
FallTime++;
if (Sounds==CAN)
{
sound(500);
delay(500);
nosound();
}
for (Fall=i;Fall>0;Fall--)
  for (j=0;j<Heng;j++)
  {
Position[j][Fall]=Position[j][Fall-1];
if (Position[j][Fall]==0) DrawBox(j,Fall,0);
      else DrawBox(j,Fall,1);
  }
i++;
}
}
switch(FallTime)
{
case 0:break;
case 1:Scores+=1;break;
case 2:Scores+=3;break;
case 3:Scores+=6;break;
case 4:Scores+=10;break;
}
if (FallTime!=0)
{
GetScores();
if (Scores%100==0) Delays-=1000;
}
}

键盘控制
对于TC编程人员来说最困难的可能就是键盘控制了,因为对于dos系统,可视字符可直接获得ASCII码,但控制字符由于只能获得SCAN扫描码,因此相对比较困难。这里有个函数,可以让大家方便的一次转换:
int GetKey(void)
{
int Ch,Low,Hig;
Ch=bioskey(0);
Low=Ch&0x00ff;
Hig=(Ch&0xff00)>>8;
return(Low==0?Hig+256:Low);
}
这样,不管用户按哪一个键,都可以直接获得。对于其中ASCII部分,得的结果小于128,对于控制字符(比如上下左右键)得到的大于128。

控制方块移动
怎样控制方块的移动?实现方块的移动其实很简单:将方块原来的位置用背景色画一个同样大小的方块,将原来的方块涂去,然后在新的位置上重新绘制方块就可以了:
void DrawBox(int x,int y,int Color)
{
x=BeginH+x*(Wid+2);
y=BeginS+y*(Wid+2);
setfillstyle(1,Color);
bar(x+2,y+2,x+Wid-1,y+Wid-1);
if (Color==0)
setcolor(9);
else
setcolor(Act+1);
rectangle(x+4,y+4,x+Wid-4,y+Wid-4);
}
这个函数就是用来画方块的,参数“x,y”用来确定正方形的位置(这里是相对位置)。Color这个参数很重要,它取两个值0和1,当Color为0的时候是用一种颜色填充,这是背景色。将方块原来的位置用背景色画一个同样大小的方块,将原来的方块用背景色填充,这样就实现了方块的移动。
最后的步骤就是编译程序了,在dos环境TC2.0下,本文程序调试通过。
(文中涉及到的完整源程序已收录到杂志配套光盘“杂志相关”栏目,按文章名查找即可) 
 

 

回到帖子顶部