俄罗斯方块编写思路及源码

  

    顾名思义,俄罗斯方块自然是俄罗斯人发明的。这位牛人叫做阿列克谢·帕基特诺夫(Alexey Pazhitnov) 。

  俄罗斯方块的基本规则:
  1、一个用于摆放小型正方形的平面虚拟场地,其标准大小:行宽为10,列高为20,以每个小正方形为单位;
  2、一组由4个小型正方形组成的规则图形,共有7种,分别以S、Z、L、J、I、O、T这7个字母的形状来命名;
  3、随机地输出方块到场地顶部,以一定的规则进行移动、旋转、下落和摆放,锁定并填充到场地中。每次摆放如果将场地的一行或多行完全填满,则组成这些行的所有小正方形将被消除,并且以此来换取一定的积分。而未被消除的方块会一直累积,并对后来的方块摆放造成各种影响。
  4、方块移到区域最下方或是落到其他方块上无法移动时,就会固定在该处。如果未被消除的方块堆放的高度超过场地所规定的最大高度,则游戏结束。

  
  下面我简单说一下我的思路,理解下面几点会比较清楚一些。

  1,基础:你首先要能画出一个带颜色的方块。举一反三:可以画一个就可以画4个了。(TC中画方块用到了EGAVGA.BGI这个文件)
  2,移动:一个方块消失,相邻地方一个方块出现,在视觉上就是移动了。
  3,消失:用背景颜色在同样的地方画同样大小的方块。
  4,俄罗斯方块:由四个方块组成,方块互相邻接。共有7种俄罗斯方块(S、Z、L、J、I、O、T)。
  5,每种俄罗斯方块,又可以旋转,以90度旋转,则每种就有4个旋转的状态。
  6,相对坐标:视觉上像素这个单位太小,用方块的大小作为相对坐标的单位。
  7,随机产生:使用伪随机函数,参数一般用上系统当前时间,你再随意捏造个四则运算,就会产生出独一无二的随机数了。随机数用于产生新的俄罗斯方块的种类,以及旋转的状态。
  8,按键分四种:上、下、左、右。上键:旋转当前的俄罗斯方块;下键:快速下降到底;左键:左移一格;右键:右移一格。
  9,旋转:默认按顺时钟旋转。这里用到了一个坐标的转换,当方块旋转时,横坐标与纵坐标要互换,我这里是把数学中的坐标转换公式应用进来了,这是这个游戏中最有难度的部分了。
  10,一行填满:一行里面,填充满小方块,此时需要进行记分,并消掉这一行。
  11,记分:每消掉一行,进行记分;若同时消掉多行,记分要更多。--这是鼓励一次消多行。
  12,结束条件:方块填充到了顶部。

  也就这么多,理解了这些,整个框架也就出来了。


源代码如下:

#include <graphics.h>
 #include <stdio.h>
 #include <stdlib.h>char x,y;/*当前操作的俄罗斯方块的坐标,实际以一个俄罗斯方块的左上角的小方块的坐标表示*/
 char str[16];
 char num_line_full=0,num_line_old=0;/*消掉的行数,用于计分*/
 char type_cur,type_new;/*俄罗斯方块共有7种,以变量type_cur表示*/
 char block_status;/*无论如何转动,只有四种状态。以变量block_status来表示。*/
 char screen[10][29]={0};/*表示画面内的各个格是否填充。  画面大小:横向可以放下10个方块,纵向可以放下29个方块 */
 char block_unit[4][2]={0};/*表示一个俄罗斯方块,其中有4个小方块,每个小方块有坐标(x,y) */
 int score1=0,speed=1000;/*speed其实是个延迟时间,可根据CPU的速度调整。更好的方式是使用别的方式进行延时*/void draw_unit(char,char,char);  /* 画一个小方块  */          
 void dl(int); /* 进行延时  */  
 void draw_block(char);  /* 画一个俄罗斯方块  */                  
 void set_block_pos(char,char);/*给一个俄罗斯方块的位置赋值*/
 void up(void);  /* 按下上键后的操作  */                         
 void down(void);/* 按下下键后的操作  */    
 void left(void);/* 按下左键后的操作  */                         
 void right(void);/* 按下右键后的操作  */    
 char rotate_x(char,char,char);    /* x 坐标旋转*/         
 char rotate_y(char,char,char);/* y 坐标旋转*/
 char max1(char,char,char);  /*取最大值*/       
 char min1(char,char,char);/*取最小值*/
 void score(void);/* 计算并显示得分  */                    
 void process_full_line(void);/*处理填满小方块的行*/
 void save(void);/* 保存最高分值  */ main()
 { 
      char cycy=0,i,j,b,ci;
      initgraph(VGA,VGAHI,"c://tc//egavga.bgi");/*图形模式初始化*/
      getch();     setfillstyle(1,7);/*设置填充模式*/
      setcolor(7);/*设置颜色*/
      bar(200,10,350,430);/*按设置的模式,画一个长方形*/
      setcolor(15);rectangle(198,8,352,432);
  x=4;type_cur=0;block_status=0;y=1;
  do
  { 
    set_block_pos(type_cur,block_status);/*进行位置赋值*/
    draw_block(14);/*画出*/
    score();/*记分*/
    for(ci=0;ci<10;ci++)/*下降一格的时间里面,提供10次机会,判断是否有按键,并进行相应处理*/
    {
      if(kbhit()!=0)
        b=getch();/*获取按键值*/
      else
        b=0;     if(b==13)
       break;
      switch(b)
      {
                   case 72: up();    break;/*上键处理*/
                   case 75: left();  break;/*左键处理*/
                   case 77: right(); break;/*右键处理*/
                   case 80: down();  break;/*下键处理*/
       }
       dl(speed/2);/*通过延时控制速度*/
    }   j=max1(block_unit[3][1],block_unit[1][1],block_unit[2][1]);
   if((j>=27)||(screen[block_unit[0][0]][block_unit[0][1]+1]+screen[block_unit[1][0]][block_unit[1][1]+1]+screen[block_unit[2][0]][block_unit[2][1]+1]+screen[block_unit[3][0]][block_unit[3][1]+1]>=1))/*已经触底*/
   { 
    screen[block_unit[0][0]][block_unit[0][1]]=1;screen[block_unit[1][0]][block_unit[1][1]]=1;
    screen[block_unit[2][0]][block_unit[2][1]]=1;screen[block_unit[3][0]][block_unit[3][1]]=1;
    y=2;
    x=5;
    
    do/*随机产生一个新的俄罗斯方块,其形状与上一个不同*/
    {
     randomize();           
     type_new=random(100);
     type_new%=7;
     if(type_new==0)
       type_new=7;
    }
    while(type_cur==type_new);   type_cur=type_new;
    j=0;
    process_full_line();/*判断是否能消一行*/
   }
   else/*未触底,继续下降*/
   { 
      draw_block(0);/*在原位置消失--准备更新位置,再画出*/
      y++;
    }
    for(i=0;i<10;i++)/*在已经经过下降一格的时间之后,判断在顶部是否有块存在,若存在,则game over*/
      if(screen[i][0]==1)
      { 
       cycy=1;
       getch();
       break;
      }  if(cycy==1)
   { 
    cycy=0;
    break;
   }
  }
  while(b!=13); getch();
  closegraph();/* 关闭图形模式 */
  save();/* 若创造新记录,保存分值 */
  getch();
 }void draw_unit(char x,char y,char color)/* 画一个小方块  */
 { 
  setfillstyle(1,color);
  setcolor(color);
  bar(200+x*15,10+y*15,200+(x+1)*15-2,10+(y+1)*15-2);
 }void draw_block(char color)/* 画一个俄罗斯方块  */ 
 { 
  char i,t;
  for(i=0;i<4;i++)
     draw_unit(block_unit[i][0],block_unit[i][1],color);
 }void dl(int a)/* 进行延时 ,更好的方式是使用别的方式进行延时,避免完全占用cpu */ 
 { 
  int r,n;
  for(r=0;r<a;r++)
   for(n=0;n<30000;n++)
   { 
    n++;
    n--;
   }
 }char rotate_x(char x0,char y0,char n)/* x 坐标旋转*/ 
 { 
  char x1,x2,y1,y2;
  x1=y0+x-y;    
  y1=x+y-x0;
  if(n==0)
     return(x0);
  if(n==1)
     return(x1);
  if(n>=2)
  { 
   x2=y1+x-y;  
   y2=x+y-x1;
   if(n==2)
      return(x2);
   else
   { 
    x1=y2+x-y; 
    y1=x+y-x2;
    if(n==3)
       return(x1);
   }
  }
 }char rotate_y(char x0,char y0,char n)/* y 坐标旋转*/
 { 
  char x1,x2,y1,y2;
  x1=y0+x-y;    
  y1=x+y-x0;
  if(n==0)
     return(y0);
  if(n==1)
   return(y1);
  if(n>=2)
  { 
   x2=y1+x-y;  
   y2=x+y-x1;
   if(n==2)
      return(y2);
   else
   { 
    x1=y2+x-y; 
    y1=x+y-x2;
    if(n==3)
       return(y1);
   }
  }
 }void left()/* 按下左键后的操作  */ 
 { 
  char j;
  set_block_pos(type_cur,block_status);
  draw_block(0);/*原位置去掉*/
  j=min1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
  if((j>=1)&&(screen[block_unit[0][0]-1][block_unit[0][1]]==0)&&(screen[block_unit[1][0]-1][block_unit[1][1]]==0)&&(screen[block_unit[2][0]-1][block_unit[2][1]]==0)&&(screen[block_unit[3][0]-1][block_unit[3][1]]==0))
  { 
   j--; 
   x--; 
  } set_block_pos(type_cur,block_status);
  draw_block(14);/*新位置画出*/
 }void right()/* 按下右键后的操作  */  
 { 
  char j;
  set_block_pos(type_cur,block_status);
  draw_block(0);/*原位置去掉*/
  j=max1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
  if((j<9)&&(screen[block_unit[0][0]+1][block_unit[0][1]]==0)&&(screen[block_unit[1][0]+1][block_unit[1][1]]==0)&&(screen[block_unit[2][0]+1][block_unit[2][1]]==0)&&(screen[block_unit[3][0]+1][block_unit[3][1]]==0))
  { 
   j++;
   x++; 
  } set_block_pos(type_cur,block_status);
  draw_block(14);/*新位置画出*/
 }void set_block_pos(char n,char block_status)/* 给一个俄罗斯方块赋值,含4个块,每个块的坐标(x,y)*/
 {     
  block_unit[0][0]=x;
  block_unit[0][1]=y;
  n=n%7;
  if(n==0)
     n=7;
         block_status=block_status%4;
  switch (n)
  { 
   case 1: block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
      block_unit[2][0]=rotate_x(x,y+1,block_status);block_unit[2][1]=rotate_y(x,y+1,block_status);
      block_unit[3][0]=rotate_x(x,y+2,block_status);block_unit[3][1]=rotate_y(x,y+2,block_status);
     break;
   case 2:     block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
      block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
      block_unit[3][0]=rotate_x(x,y-2,block_status);block_unit[3][1]=rotate_y(x,y-2,block_status);
     break;
   case 3:     block_unit[1][0]=rotate_x(x-1,y-1,block_status);block_unit[1][1]=rotate_y(x-1,y-1,block_status);
      block_unit[2][0]=rotate_x(x,y+1,block_status);block_unit[2][1]=rotate_y(x,y+1,block_status);
      block_unit[3][0]=rotate_x(x-1,y,block_status);block_unit[3][1]=rotate_y(x-1,y,block_status);
     break;
   case 4:     block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
      block_unit[2][0]=rotate_x(x-1,y+1,block_status);block_unit[2][1]=rotate_y(x-1,y+1,block_status);
      block_unit[3][0]=rotate_x(x,y-1,block_status);block_unit[3][1]=rotate_y(x,y-1,block_status);
     break;
   case 5:     block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
      block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
      block_unit[3][0]=rotate_x(x,y+1,block_status);block_unit[3][1]=rotate_y(x,y+1,block_status);
     break;
   case 6:     block_unit[1][0]=rotate_x(x,y+1,block_status);block_unit[1][1]=rotate_y(x,y+1,block_status);
      block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
      block_unit[3][0]=rotate_x(x,y-2,block_status);block_unit[3][1]=rotate_y(x,y-2,block_status);
     break;
   case 7:     block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
      block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
      block_unit[3][0]=rotate_x(x-1,y-1,block_status);block_unit[3][1]=rotate_y(x-1,y-1,block_status);
     break;
  }
 }void up()/* 按下上键后的操作  */ 
 { 
  char j,k;
  set_block_pos(type_cur,block_status);
  draw_block(0);
  block_status++;
  block_status=block_status%4;
  set_block_pos(type_cur,block_status);
  j=min1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
  k=max1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
  if((k<=9)&&(j>=0))
  { 
   if((screen[block_unit[0][0]][block_unit[0][1]]==0)&&(screen[block_unit[1][0]][block_unit[1][1]]==0)&&(screen[block_unit[2][0]][block_unit[2][1]]==0)&&(screen[block_unit[3][0]][block_unit[3][1]]==0))
    draw_block(14);      /* not overlap */
   else
   {             
    block_status+=3;     
    block_status%=4;
    set_block_pos(type_cur,block_status);
    draw_block(14);
   }
  }
  else
  { 
   block_status+=3;     
   block_status%=4;
   set_block_pos(type_cur,block_status);
   draw_block(14);
  }
 }void down(void)/* 按下下键后的操作  */   
 { 
  char j,cy=0;
  set_block_pos(type_cur,block_status);
  draw_block(0);
  j=max1(block_unit[3][1],block_unit[2][1],block_unit[1][1]); while(j<28)
  {     
   j=max1(block_unit[3][1],block_unit[1][1],block_unit[2][1]);
   if((j>=27)||(screen[block_unit[0][0]][block_unit[0][1]+1]==1)||(screen[block_unit[1][0]][block_unit[1][1]+1]==1)||(screen[block_unit[2][0]][block_unit[2][1]+1]==1)||(screen[block_unit[3][0]][block_unit[3][1]+1]==1))
   { 
    screen[block_unit[0][0]][block_unit[0][1]]=1;
    screen[block_unit[1][0]][block_unit[1][1]]=1;
      screen[block_unit[2][0]][block_unit[2][1]]=1;
    screen[block_unit[3][0]][block_unit[3][1]]=1;
      draw_block(14);   /* avoid block disappeared */
      process_full_line();
    y=2;
    x=5;
    do
    {
        randomize();           
     type_new=random(100);
        type_new%=7;
        if(type_new==0)
       type_new=7;
    }
    while(type_new==type_cur);        /* avoid the same block appear continually */   type_cur=type_new;
   j=0;
    set_block_pos(type_cur,block_status);
    draw_block(14);
    cy=1;
   }
   else
   { 
    dl(speed/4);
      set_block_pos(type_cur,block_status);
      draw_block(0);
      y++;
   }
   if(cy==1)
   { 
    cy=0;
    break;
   }
   set_block_pos(type_cur,block_status);
   draw_block(14);
  }
 }void process_full_line(void)/*消掉一行*/
 { 
  char jj,y1,r,x1,y2;
  for(jj=0;jj<4;jj++)/*分别判断四个小方块---似乎不需要??下面已经是整屏幕判断了*/
  {
   for(y1=27;y1>0;y1--)/*从底下判断到顶上。*/
   { 
    r=0;
    for(x1=0;x1<10;x1++)/*对于每一行,从左判断到右*/
    { 
     if(screen[x1][y1]==1)/*统计已经填充的小方块的个数*/
         r++;
    }
    if(r==10)/*一行已经填满了*/
    { 
     num_line_full++;
     for(x1=0;x1<10;x1++)
     { 
      draw_unit(x1,y1,0);/*使该行消失*/
      screen[x1][y1]=0;/*清标志*/
     }    for(y2=y1-1;y2>0;y2--)/*上面的所有块,整体下移一行*/
     { 
      for(x1=0;x1<10;x1++)
      { 
       if(screen[x1][y2]==1)
       { 
        screen[x1][y2]=0;
        draw_unit(x1,y2,0);
        draw_unit(x1,y2+1,14); 
        screen[x1][y2+1]=1;
       }
      }
     }
    }
   }
  }
 }char max1(char a,char b,char c)/*取最大值*/   
 { 
  char max3;
  if(a>=b)
            max3=a;
  else
            max3=b;        if(max3<c)
            max3=c;        return(max3);
   }char min1(char a,char b,char c)/*取最小值*/
 { 
  char min;
  if(a<=b)
     min=a;
  else
     min=b;
  
  if(min>c)
     min=c; return(min);
   }void score(void)/* 计算并显示得分  */ 
 { 
  char a,b=1;
  setcolor(0);
  outtextxy(20,20,str);
  setcolor(14);
  for(a=0;a<num_line_full-num_line_old;a++)
     b*=2;
  score1+=b-1;
  if(b>1)
     speed-=2;
  num_line_old=num_line_full;
  sprintf(str,"Your score:%d.",score1);
  outtextxy(20,20,str);
 }void save(void)/* 保存最高分值  */
 { 
  FILE * fp;
  int i;
  i=0;
  fp=fopen("bloscore.txt","rb+");
  i=(int)(fgetc(fp))-48;
  if(fp=NULL)
     i=0;
  fclose(fp); if(i<score1)
  { 
     fp=fopen("bloscore.txt","wb+");
     fprintf(fp,"%d",score1);
     fclose(fp);
     printf("Your score: %d are the highest!",score1);
  }
  getch();}

 

    编译运行需要一个文件:egavga.bgi,网络上可以下载到,我是存放在我的电脑的 c:/tc 目录下。

    这个程序还不是很完善,只是实现了基本功能,第一次的开始时,需要按一下方向键,最后的退出也有一点小问题,不过作为一个小游戏玩玩还是可以的了。

    有兴趣的朋友帮忙完善一下,加上了通知我一声哦。

后记:
 
    其实现在来发布贪吃蛇与俄罗斯方块这两个小游戏的源码,已经显不出有什么特别了,因为网络上已经有多个版本了。只是在2001年那时,这两个小游戏却是我的快乐与光荣。当时才学C语言,自己估摸着可以做点东西,就开始做了。都是我一个人编写与调试,在其他人玩乐的时候,是有一点孤单的。做出来后,看别人玩自己编写的游戏,很是自豪,不过却没有人来一起讨论编程中的得失,有些遗憾。现在虽然迟了,发布出来,也是了一个心愿吧。回头看以前编写的程序,格式不够规范,思路也很粗糙,远称不上完善,但那也是我成长过程中的一个脚印啊,与大家分享一下。

    这个游戏是我编写贪吃蛇之后的又一个尝试,本以为也只是几个方块的组合,类似于贪吃蛇,结果费了很大的功夫才编写成型,主要的困难在于边界的处理,需要非常仔细的考虑,否则bug一大堆呀。

  回头重看代码,确实很难看懂。就重新整理了一下,修改了一些变量、函数名称,添加了部分注释,增加一些代码可读性。