c语言俄罗斯方块编程

俄罗斯方块最详解

    (由于写这个文档的排板与预览的效果不一样,故到现在才有空改成原来的样子,请大家不要整个页面文字

复制,而是请整个页面另存为,这样的排板便于阅读理解,又或者QQ:42595947(黑伯爵)联系我(那个qq

提问的答案是13457371117),我给排板好的.txt文件你(好像是68k大)。在最后附有完整的源代码细心、慢慢地看吧,有不妥之处还请高手请教)

    做每一件事前,都会有一个粗略的构想。编程更应该这样,现在先说一些大的、粗略的东西。(过程中有例子,均通过了检验)
****************************************************************************************
****************************************************************************************
目录:
    ●屏幕的划分
    ●图形显示
    ●三种坐标。 绝对坐标、相对坐标、左上角坐标
    ●方块的构造
    ●动画效果 (有例子)
    ●键盘控制 (有例子)
    ●判断方块碰撞
    ●消行
    ●变形
    ●关于菜单的制作 (有例子)
    ●附录(完整的源程序)
****************************************************************************************
****************************************************************************************
1、屏幕的划分
    将整个屏幕划分成四部分:a、一个没盖的杯子;b、一个不断下落4*4数组的盒子;c、一个给
预览下一个方块4*4数组的盒子;d、提示信息。由于提示信息比较简单,这里只讨论前三样。

没盖的杯子:      
    即平时说玩这款游戏时,下落方块不可超出的那个边界,下落的方块从这个“杯口”的上方往下下落,方块只在“杯子”里移动、变形、停止。  
    游戏空间指的是整个游戏主要的界面(呵呵,其实就是所说的“杯子”)。实际上是一个宽10格子、高20格子的  游戏板。用一个全局数组GameSpace[22][12]表示。表示的时候:GameSpace[x][y]为1时表示游戏板上(x,y)这个位置上已经有方块占着了,GameSpace[x][y]为0表示游戏板上这位置还空着。为了便于判断形状的移动是否到边、到底,初始的时候在游戏板的两边各加一列,在游戏板的下面加一行,全 部填上1,表示不能移出界。即GameSpace[x][0],GameSpace[x][11](其中x从0到20)初始都为1,GameSpace[20][y](其中y从0到11)初始都为1。 
 
   0 1 2 3 4 5 6 7 8 910 
00■□□□□□□□□□□■ 
01■□□□□□□□□□□■ 
02■□□□□□□□□□□■ 
03■□□□□□□□□□□■ 
04■□□□□□□□□□□■ 
05■□□□□□□□□□□■ 
06■□□□□□□□□□□■ 
07■□□□□□□□□□□■ 
08■□□□□□□□□□□■
09■□□□□□□□□□□■ 
10■□□□□□□□□□□■ 
11■□□□□□□□□□□■ 
12■□□□□□□□□□□■ 
13■□□□□□□□□□□■ 
14■□□□□□□□□□□■ 
15■□□□□□□□□□□■ 
16■□□□□□□□□□□■ 
17■□□□□□□□□□□■ 
18■□□□□□□□□□□■ 
19■□□□□□□□□□□■
20■■■■■■■■■■■■
 
下落的4*4盒子:
    即7种标准的方块形状,如我认为比较经典的测试方块“7字形”
可看作:
  0 1 2 3
 0□□□□                      { {0,0,0,0},
 1□■■□  用数组表示则是   {0,1,1,0},
 2□□■□                       {0,0,1,0},
 3□□■□                       {0,0,1,0}}

预览4*4数组盒子:
    即玩这款游戏时,给看下一个方块是什么样的窗口。
    这三样东西可以这样联系起来:一个不断下落的盒子由杯子的上方下落到杯子底部,之后将预览盒子的东西放到下落的4*4盒子中,如此循环反复……


2、图形显示 
  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表示存放图形驱动文件的路径。 特别值得一提的是驱动文件路径的写法,由于有转义字符,如bgi文件夹(一般这个文件夹就在Tc文件夹目录下,这个说明文档黙认bgi在c:/,后面举的例子亦然)在c:/,写的时候很多人写成"c:/bgi",少了个/,应写成"c://bgi",切记。
    此外,我还强烈建议借一本叫《c作图与c汉字技术》的书,这是我见过介绍c 绘图方面最全面的书了,为了节省时间,可以直接看这几个函数,这些函数均包含在头文件#include <graphics.h>中
void line( int x,int y,int xx,int yy); /*从(x,y)到(xx,yy)处以setcolor()决定的颜色画直线*/

void setlinestyle(int linestyle , unsigned upattern ,int thickness );     
                                     /*linestyle 变化范围为 0 ~ 4,也可以是大写的英文*/
                                     /*分别表示实线,点线,中心线,点画线,用户自定义线*/
                                     /*upattern处一般写0*/
                              /*thickness只有1、3两个值,分别表示一个像素宽,三个像素宽*/
                                      
void setcolor (int color );   /*决定前景颜色,color 变化范围为 0 ~ 15也可以是大写的英文*/

void setbkcolor(int color);   /*决定背景颜色,color 变化范围为 0 ~ 15也可以是大写的英文*/

void rectangle(int x,int y,int xx,int yy);/*以(x,y)为左上角,以(xx,yy)为右下角画矩形*/

void bar(int x,int y,int xx,int yy);   /*以(x,y)为左上角,以(xx,yy)为右下角画条形*/

void bar3d(int x,int y,int xx,int yy,int depth,int topflag);
                                       /*以(x,y)为左上角,以(xx,yy)为右下角画立体条形*/
                                       /*depth决定了三维直方图的长度*/
                                       /*当topflag非0时,画出三维顶,否则不画出三维顶*/

void setfillstyle(int pattern,int color);/*pattern变化范围为 0 ~ 12也可以是大写的英文*/
                                         /*color 变化范围为 0 ~ 15也可以是大写的英文*/

char* itoa(int n,char* str,int radix);  /*n是要转的整型变量 */
     /*str用来存的字符串*/
     /*radix是按什么进制转化*/
                                        /*能将整型数字转成字符型数字,结合outtextxy()*/
                                        /*可将得分、菜单上的数值等简单地显示在屏幕上*/

void settextjustify(int horiz,int vert);
/*horiz变化范围为 0 ~ 2,也可以是大写的英文,分别代表左对齐,字符串中心对齐,右对齐*/
/*vert 变化范围为 0 ~ 2,也可以是大写的英文,分别代表底部对齐,中心对齐,顶部对齐*/

void settextstyle(int font,int direction,int charsize);
/*font变化范围为 0 ~ 12也可以是大写的英文*/
/*direction只能是0、1,也可以是大写的英文,0、1分别代表水平输出,垂直输出*/
/*charsize变化范围为 1 ~ 10也可以是大写的英文,数值越大,字体越大*/

void outtextxy(int x,int y,char* str);    /*按settextjustify()决定的对齐方式*/
                                        /*以setcolor()决定的颜色,在(x,y)附近输出字符串*/
(在第10点的小程序中有以上的大部分函数的实现)


3、三种坐标。 绝对坐标、相对坐标、左上角坐标
绝对坐标:
    图形屏幕其实是个y轴的正方向朝下,原点在屏幕最左上角的直角坐标系,vga模式下的一般分辨率为640*480,这样就把屏幕横向、纵向分别平分640等分、480等分,横、纵坐标变化范围分别为0 ~ 639,0 ~ 479,每个像素点占用一个1的长度。我们说的绝对坐标就是以屏幕原点为相对点的切切实实的坐标。

相对坐标:
    即以屏幕上某个绝对坐标为相对点(或把它看成原点),在这个基础上再计算的坐标。
    如以(0,0)为相对点,(1,1)的绝对坐标为(1,1),相对坐标为(1,1);以(2,2)相对点,(1,1)的绝对坐标为(1,1),相对坐标为(-1,-1)。

左上角坐标:
    特别提出这个,是因为在俄罗斯方块中解决数组与屏幕连接的关健所在。如画个小正方形时用函数rectangle(x,y,x+单位长度,y+单位长度)
(其中(x,y)为这个正方形的左上角坐标,单位长度可以是10,16,我就取了16,这样在640*480中便变成了 (640/16)*(480/16) = 40*30 ),又如前面说的GameSpace[21][12]可以这样化散为整成GameSpace[(x-相对原点的x)/单位长度][(y-相对原点的y)/单位长度]。


4、方块的构造
    为了简单处理,我选择了数组构造方块,而不是坐标描点,但这样浪费空间。在要画的地方赋1,不画的地方赋0,如长条可表示为
  0 1 2 3
 0□■□□                      { {0,1,0,0},      到了这里,我们应     ____
 1□■□□  用数组表示则是   {0,1,0,0},      已深入的将各种方    |__  |          ■■
 2□■□□                       {0,1,0,0},      块看成由四个小正        | |   看成     ■
 3□■□□                       {0,1,0,0}}      形组成,如               |_|             ■

为突出立体感,可以在画了一个小正方形后,再用line()函数在这个小正方形的左上角坐标附近画横、纵两条白线,再在小正方形的右侧画一条黒线,如图是放大的小正方形
   ________   
  | ______ |  (其中,里面的横线为白,里面左侧的纵线为白,里面右侧的纵线为黒)
  ||         ||
  ||         ||
  ||         ||       
  |_______|
  
   在我的程序里就没搞立体感,只为体现算法而已。
   现在,整个屏幕在我们眼里应达到这样理性的看法:
 
□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ 
□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ 
□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ 
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□
□□□□□□■□□□□□□□□□□■□□□■■■■■■□□□□
□□□□□□■□□□□□■■□□□■□□□■□□□□■□□□□
□□□□□□■□□□□■■□□□□■□□□■□■■□■□□□□
□□□□□□■□□□□□□□□□□■□□□■□■■□■□□□□ 其中每个小正方形放大看为
□□□□□□■□□□□□□□□□□■□□□■□□□□■□□□□
□□□□□□■□□□□□□□□□□■□□□■■■■■■□□□□          (x,y)______
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□             |           |
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□             |           |
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□             |           |
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□             |_______|   
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□ 
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□     (x,y)为左上角坐标
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□ 
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□ 
□□□□□□■□□□□□□□□□□■□□□□□□□□□□□□□ 
□□□□□□■□□□□□□□■□□■□□□□□□□□□□□□□
□□□□□□■□□□□□□□■□□■□□□□□□□□□□□□□ 
□□□□□□■□□□□□□□■■□■□□□□□□□□□□□□□ 
□□□□□□■■■■■■■■■■■■□□□□□□□□□□□□□ 
□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ 


5、动画效果
    说穿了就是先画一个图形显示出来,延时一小段时间认人看清楚后,再将显示的图形在原来的位置涂黒(或涂成背景颜色)。其中延时函数为delay (数值),包含在头文件#include <dos.h>中。
括号中的数值越大,延时越长,数值最大为65535,可配合for 循环让delay()超过65535这个数,下面给出的小程序是熟悉这个函数的.功能是一个红色的正方形斜斜地移动
/*animation.c*/
#include <graphics.h> 
#include <dos.h>       /*delay()函数的头文件*/

void main()
{
    int gr = DETECT , gm = 0;
    int x = 50 , y = 50;
    int i , j;
 initgraph( &gr , &gm , "c://bgi" ) ;
 for( i = 0 ; i < 8 ; ++ i , x += 50 , y += 50)
 { 
  setfillstyle( SOLID_FILL , RED );
  bar( x , y , x + 50 , y + 50 );
  for (j = 0 ; j < 30 ; ++ j)
   delay( 10000 );       
                      /*不同的编译器显示的快慢不同,10000这个数值可根据不同的编译器改*/
  setfillstyle( SOLID_FILL , BLACK );
  bar( x , y , x + 50 , y + 50 );
 }
}

 

6、键盘控制
    键盘上的每个键都有自己的键盘码,为了得到想要的,下面介绍一个函数
在Tc2.0中有一个处理键盘输入的函数bioskey(); 
int bioskey(int cmd); 
  当cmd为1时,bioskey()检测是否有键按下。没有键按下时返回0;有键按下时返回键盘码(任何键盘码都不为0),但此时并不将检测到的按  键码从键盘缓冲队列中清除。 
  当cmd为0时,bioskey()返回键盘缓冲队列中的键盘码,并将此按键码从键盘缓冲队列中清除。如果键盘缓冲队列为空,则一直等到有键按下,才将得到的键盘码返回。 

  Escape键的按键码为0x11b,下面的小程序可以获取按键的键盘码 
/*keyboard_code.c*/
#include <stdio.h>
#include <bios.h>

void main()
{
    int key ;
    while ( 1 ) 
    { 
         key = bioskey( 0 );        /* wait for a keystrike */ 
         printf ( "0x%x/n" , key ); 
         if ( key == 0x11b )
             break;                 /* Escape */ 
    }

常用按键的按键码如下: 

#define LEFT  0x4b00 
#define RIGHT 0x4d00 
#define DOWN  0x5000 
#define UP    0x4800 
#define HOME  0x4700 
#define END   0x4f00 
#define SPACE 0x3920 
#define ESC   0x011b 
#define ENTER 0x1c0d 


    这样只要事先用#define 这个宏定义,将要用的键盘码定义好,然后用switch语句分支各个键的功能即可。这里多加说明一个函数kbhit(),其头文件为#include <conio.h>.当没有按键时,返回0值;有按键时,返回非0值,结合while循环,就可实现键盘控制了。下面的函数功能是按上下左右键,屏幕上的正方形跟着做出相应地移动,按逃跑键退出
/*control.c*/
#include <dos.h>       /*delay()函数的头文件*/
#include <graphics.h>
#include <bios.h>
#include <conio.h>

#define LEFT  0x4b00
#define RIGHT 0x4d00
#define DOWN  0x5000
#define UP    0x4800
#define ESC   0x011b

void main()
{
    int gr = DETECT , gm = 0;
    int x = 100 , y = 100;
    int i , key;
 initgraph( &gr , &gm , "c://bgi" ) ;
 while ( 1 )
 {
  while ( !kbhit() )            /*前面刚说完,不记得了?往上翻翻*/
  { 
   setfillstyle( SOLID_FILL , RED );
   bar( x , y , x + 50 , y + 50 );
   for (i = 0 ; i < 30 ; ++ i)
    delay( 10000 );      
   setfillstyle( SOLID_FILL , BLACK );
   bar( x , y , x + 50 , y + 50 );
  }
  key = bioskey( 0 );           /*前面刚说完,不记得了?往上翻翻*/
  switch ( key )
  {
   case LEFT:
    x -= 50;
    break;
   case RIGHT:
    x += 50;
    break;
   case UP:
    y -= 50;
    break;
   case DOWN:
    y += 50;
    break;
   case ESC:
    exit( 0 );
  }
 }
}


7、判断方块碰撞(即方块是否还能下落)
    用一个带有返回值的函数,若碰撞则说明不能下落,返回1;反则说明没有碰撞,返回0.
    具体一点就是整个4*4方块数组下落一个单位长度,与游戏空间数组(即前面说的“无盖的杯子”)有重叠的1,则在当前位置4*4数组是1的地方赋值给游戏空间对应的数组元素,表示停止下落,并画有1 的地方。对于左移、右移一个单位长度有重叠的1 ,则不允许左移、右移,继续自然下落。

8、消行
    总的想法是先认为每一行都是满1的,从游戏空间的数组由上到下扫描,一旦测试到某一行中某个列元素为0,则认为这一行没满,跳出这行的扫描循环,进入下一行的扫描。
    若扫描完某一行的元素都没有发现0,则以这行以上的每一行完完整整地将上一行的元素赋值给下一行,这个过程以由下到上进行,然后将整个游空间(GameSpace)画黒,再在GameSpace[21][12]中有1的地方画小正方形,如示例

A 0 0 0 1 0 0   从A行到D行扫描                           A 0 0 0 0 0 0
B 1 1 0 1 0 1   扫描后发现C行是满的                      B 0 0 0 1 0 0
C 1 1 1 1 1 1   则从B行起B行赋给C行,A行赋给B行    C 1 1 0 1 0 1
D 0 1 1 1 1 1   最后在最行A行赋全0 ,变成               D 0 1 1 1 1 1

对应的   ↓                                                   对应的   ↓
图形     ↓                                                   图形      ↓

A□□□■□□                                              A□□□□□□
B■■□■□■                                              B□□□■□□
C■■■■■■                                              C■■□■□■
D□■■■■■                                              D□■■■■■

 


读者可能会问这样不是每次都消一行而己,若要一次消两行以上怎么办(如恰好有一长条一次能消4行)?这个简单,因为即使一次消多行也是建立在一行一行消的基础上的。把消行函数再细化成两部分,一部分为为扫描哪行满行并返回这个满行的行号的值,另一部分就消这个满行行号的行,用一个while 循环,判断条件为 “扫描函数(在我的程序里是fullrow())!= 0”,形如
while(fullrow() != 0 )
{                          其中flrw是个整型变量,用来记录哪行满行,而clearrow(flrw)
    flrw=fullrow();              则消指定的行。由于计算速度快,人眼看多次消行,就像一
    clearrow( flrw );            次搞掂的样子
}


9、极度重要的变形函数
   分两部分讲 a、怎么变 
              b、变后插入到已有方块里的处理(即卡位处理),情况如图:
                         □■□□□□□              □■□□□□□
                         □■◆□□□□              □■□□□□□
                         □■◆□□□□              □■□□□□□
                         □■◆□□□□   变形后   ◆◆◆◆□□□<

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值