用c语言编写俄罗斯方块程序 求详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用c语言编写俄罗斯方块程序 求详解相关的知识,希望对你有一定的参考价值。

1、用C语言绘制图形界面

EasyX图形库(http://www.easyx.cn)即TC的图形库在VC下的移植。

包含库#include <graphics.h>

先初始化图形窗口

initgraph(WINDOW_WIDTH, WINDOW_HIGH) ;WINDOW_WIDTH为窗口的宽带,WINDOW_HIGH为窗口的高度。

清空绘图设备

cleardevice();

设置画笔颜色

setcolor(RED) ;

设置线条风格

setlinestyle(PS_SOLID, NULL, 0);

画矩形

rectangle

还有画线、显示文字等函数,可以参照其帮助文档。

注意:由于我们用的是EasyX图形库,故源文件后缀要为.cpp,但其中内容都是C的语法。

2、存储表示出俄罗斯方块的形状

一、我们可以用编号,不同的编号代表不同的俄罗斯方块,根据编号把不同方块的画法写在代码中,这样19种

方块就得有19种相应的代码来描绘。而且这样扩展性不好,若以后设计了新的方块,则需要更改大量源代码。

二、我们很自然的想到可用字模点阵的形式来表示,即设置一个4行4列的数组,元素置1即代表这个位置有小

方块,元素置0即代表这个位置无小方块,这个整个的4*4的数组组成俄罗斯方块的形状。

1000

1000

1100

0000

这个方法挺靠谱,但我们还可以优化一下:不用4*4的数组,而是用16个bit位来表示这个点阵。这样存储起来比较方便,故我们用unsigned int 的低16位来表示方块的点阵。

我们可以用掩码与表示俄罗斯方块的位进行操作,来识别并在屏幕上画出方块。

我们把俄罗斯方块点阵的数位存在rockArray中,我们可以事先把这19种方块的字模点阵自己转化成十六进制,然后在rockArray数组的初始化时赋值进去。

但这样做未免有点太费力,且扩展性也不太好,若以后设计的新方块种类加入,要改变数组rockArray中的值。

我们可以考虑把所有俄罗斯方块的点阵存储在配置文件中,在程序初始化时读取文件,把这些点阵转换成unsigned int的变量存储在rockArray中。

这样,以后我们增添新的方块形状只需要在配置文件中增加新的点阵即可。

@###

@###

@@##

####   (为使得看起来更醒目,我们用@表示1,用#表示0)

3、让图形动起来

在某位置处用函数DrawRock在屏幕上画出俄罗斯方块,然后再擦除掉(即用背景色在原位置处重绘一次方块),最后在下落的下一个位置处用函数DrawRock在屏幕上画出俄罗斯方块,如此循环,中间用计时器间隔一段时间以控制下落的速度。

同理,按下屏幕的左右键也是如此,只是在按下键盘时把方块的位置重新计算了。

那么按下上方向键时,如何让方块翻转呢?

我们在配置文件中就把方块的顺时针翻转形态放在了一起:

@###

@###

@@##

####

@@@#

@###

####

####

@@##

#@##

#@##

####

##@#

@@@#

####

####

我们每按一次上方向键改变一次方块的形状即可。若一直按上键,形状应该是循环地翻滚。

我们想到了循环链表的数据结构可实现这个效果。

可是我们若把这些一种类的方块的各种形态串成循环链表形式,那么每次重新生成方块时我们就难以随机地生成方块了。

故还是得用数组来存储,但又要有循环链表的功能,于是我们想到了静态循环链表。

我们用结构体来作为一个方块在rockArray中的元素

typedef struct ROCK

  //用来表示方块的形状(每一个字节是8位,用每4位表示方块中的一行)

unsigned int rockShapeBits ; 

int          nextRockIndex ;  //下一个方块,在数组中的下标

RockType ;

这样,当我们按下上方向键时,把传入函数DrawRock中的rockIndex变为当前方块结构体中的nextRockIndex即可。

参考资料:C语言图形界面篇

参考技术A

在写一个程序之前得先有思路,本题中得思路是:

随机给出不同的形状(长条形、Z字形、反Z形、田字形、7字形、反7形、T字型)下落填充给定的区域,若填满一条便消掉,记分,当达到一定的分数时,过关,每关方块下落的速度不同,若在游戏中各形状填满了给定区域,为输者。

有了思路再动手,如果不会可以参考一下别人开源的项目!

因为项目复杂性,我给出了一个俄罗斯方块程序的项目地址在最后,祝您好运!

项目地址:俄罗斯方块游戏

参考技术B

俄罗斯方块C源代码

#include <stdio.h>

#include <windows.h>

#include <conio.h>

#include <time.h>

#define  ZL  4     //坐标增量, 不使游戏窗口靠边

#define WID  36    //游戏窗口的宽度

#define HEI  20    //游戏窗口的高度

int i,j,Ta,Tb,Tc;      // Ta,Tb,Tc用于记住和转换方块变量的值

int a[60][60]=0;    //标记游戏屏幕各坐标点:0,1,2分别为空、方块、边框

int b[4];        //标记4个"口"方块:1有,0无,类似开关

int x,y, level,score,speed;    //方块中心位置的x,y坐标,游戏等级、得分和游戏速度

int flag,next;   //当前要操作的方块类型序号,下一个方块类型序号

void gtxy(int m, int n);   //以下声明要用到的自编函数

void gflag( );  //获得下一方块序号

void csh( );  //初始化界面

void start( );  //开始部分

void prfk ( );  //打印方块

void clfk( );  //清除方块

void mkfk( );  //制作方块

void keyD( );  //按键操作

int  ifmov( );  //判断方块能否移动或变体

void clHA( );  //清除满行的方块

void clNEXT( );  //清除边框外的NEXT方块

int main( )

 csh( );   

   while(1)

     start( );  //开始部分

       while(1)

        prfk( );  

         Sleep(speed);  //延时

          clfk( );

          Tb=x;Tc=flag;  //临存当前x坐标和序号,以备撤销操作

          keyD( );  

          y++;     //方块向下移动

         if (ifmov( )==0)  y--; prfk( ); dlHA( ); break; //不可动放下,删行,跨出循环

      

      for(i=y-2;i<y+2;i++) if (i==ZL)  j=0;    //方块触到框顶

     if (j==0)  system("cls");gtxy(10,10);printf("游戏结束!"); getch(); break;  

     clNEXT( );   //清除框外的NEXT方块

   

  return 0;

void gtxy(int m, int n)  //控制光标移动

COORD pos;  //定义变量

 pos.X = m;  //横坐标

 pos.Y = n;   //纵坐标

 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); 

void csh( )    //初始化界面

gtxy(ZL+WID/2-5,ZL-2); printf("俄罗斯方块");      //打印游戏名称

 gtxy(ZL+WID+3,ZL+7); printf("******* NEXT:");  //打印菜单信息

 gtxy(ZL+WID+3,ZL+13); printf("**********");

 gtxy(ZL+WID+3,ZL+15); printf("Esc :退出游戏");

gtxy(ZL+WID+3,ZL+17); printf("↑键:变体");

 gtxy(ZL+WID+3,ZL+19); printf("空格:暂停游戏");

 gtxy(ZL,ZL);  printf("╔");  gtxy(ZL+WID-2,ZL);  printf("╗");  //打印框角

 gtxy(ZL,ZL+HEI);  printf("╚");  gtxy(ZL+WID-2,ZL+HEI);  printf("╝");

a[ZL][ZL+HEI]=2;  a[ZL+WID-2][ZL+HEI]=2;  //记住有图案

 for(i=2;i<WID-2;i+=2) gtxy(ZL+i,ZL);  printf("═");   //打印上横框

 for(i=2;i<WID-2;i+=2) gtxy(ZL+i,ZL+HEI); printf("═"); a[ZL+i][ZL+HEI]=2;  //下框

 for(i=1;i<HEI;i++)  gtxy(ZL,ZL+i);  printf("║"); a[ZL][ZL+i]=2;   //左竖框记住有图案

 for(i=1;i<HEI;i++) gtxy(ZL+WID-2,ZL+i); printf("║"); a[ZL+WID-2][ZL+i]=2;  //右框

 CONSOLE_CURSOR_INFO cursor_info=1,0;   //以下是隐藏光标的设置

 SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info);

 level=1; score=0; speed=400;

 gflag( );  flag=next;  //获得一个当前方块序号

void gflag( )   //获得下一个方块的序号

 srand((unsigned)time(NULL)); next = rand()%19+1;

void start( )  //开始部分

 gflag( ); Ta=flag; flag=next;  //保存当前方块序号,将下一方块序号临时操作

  x=ZL+WID+6; y=ZL+10; prfk( );  //给x,y赋值,在框外打印出下一方块

 flag=Ta; x=ZL+WID/2; y=ZL-1;  //取回当前方块序号,并给x,y赋值

void prfk ( )  //打印俄罗斯方块

 for(i=0;i<4;i++) b[i]=1;   //数组b[4]每个元素的值都为1

  mkfk ( );  //制作俄罗斯方块

 for( i= x-2; i<=x+4; i+=2 )  //打印方块

   for(j=y-2;j<= y+1;j++)  if( a[i][j]==1 && j>ZL ) gtxy(i,j); printf("□");   

 gtxy(ZL+WID+3,ZL+1);   printf("level : %d",level);  //以下打印菜单信息

 gtxy(ZL+WID+3,ZL+3);  printf("score : %d",score);

 gtxy(ZL+WID+3,ZL+5);  printf("speed : %d",speed);

void clfk( )  //清除俄罗斯方块

 for(i=0;i<4;i++)  b[i]=0;   //数组b[4]每个元素的值都为0

  mkfk ( );  //制作俄罗斯方块

 for( i=x-2; i<=x+4; i+=2 )  //清除方块

   for(j=y-2;j<=y+1;j++) if( a[i][j]==0 && j>ZL ) gtxy(i,j); printf("  ");   

void mkfk( )  //制作俄罗斯方块

 a[x][ y]=b[0];  //方块中心位置状态: 1-有,0-无

 switch(flag)   //共6大类,19种小类型

  case 1:  a[x][y-1]=b[1]; a[x+2][y-1]=b[2]; a[x+2][y]=b[3]; break;   //田字方块

  case 2:  a[x-2][y]=b[1]; a[x+2][y]=b[2]; a[x+4][y]=b[3]; break;   //直线方块:----

  case 3:  a[x][y-1]=b[1]; a[x][y-2]=b[2]; a[x][y+1]=b[3]; break;   //直线方块: |

  case 4:  a[x-2][y]=b[1]; a[x+2][y]=b[2]; a[x][y+1]=b[3]; break;   //T字方块

  case 5:  a[x][y-1]=b[1]; a[x][y+1]=b[2]; a[x-2][y]=b[3]; break;   //T字顺时针转90度

  case 6:  a[x][y-1]=b[1]; a[x-2][y]=b[2]; a[x+2][y]=b[3]; break;   //T字顺转180度

  case 7:  a[x][y-1]=b[1]; a[x][y+1]=b[2]; a[x+2][y]=b[3]; break;   //T字顺转270度

  case 8:  a[x][y+1]=b[1]; a[x-2][y]=b[2]; a[x+2][y+1]=b[3]; break;  //Z字方块

  case 9:  a[x][y-1]=b[1]; a[x-2][y]=b[2]; a[x-2][y+1]=b[3]; break;   //Z字顺转90度

  case 10:  a[x][y-1]=b[1]; a[x-2][y-1]=b[2]; a[x+2][y]=b[3]; break;   //Z字顺转180度

  case 11:  a[x][y+1]=b[1]; a[x+2][y-1]=b[2]; a[x+2][ y]=b[3]; break;  //Z字顺转270度

  case 12:  a[x][y-1]=b[1]; a[x][y+1]=b[2]; a[x-2][y-1]=b[3]; break;   //7字方块

  case 13: a[x-2][y]=b[1]; a[x+2][y-1]=b[2]; a[x+2][y]=b[3]; break;   //7字顺转90度

  case 14:  a[x][y-1]=b[1]; a[x][y+1]=b[2]; a[x+2][y+1]=b[3]; break;   //7字顺转180度

  case 15:  a[x-2][y]=b[1]; a[x-2][y+1]=b[2]; a[x+2][y]=b[3]; break;   //7字顺转270度

  case 16:  a[x][y+1]=b[1]; a[x][y-1]=b[2]; a[x+2][y-1]=b[3]; break;   //倒7字方块

 case 17:  a[x-2][y]=b[1]; a[x+2][y+1]=b[2]; a[x+2][y]=b[3]; break;   //倒7字顺转90度

 case 18:  a[x][y-1]=b[1]; a[x][y+1]=b[2]; a[x-2][y+1]=b[3]; break;   //倒7字顺转180度

  case 19:  a[x-2][y]=b[1]; a[x-2][y-1]=b[2]; a[x+2][y]=b[3]; break;   //倒7字顺转270度

 

void keyD( )  //按键操作

 if (kbhit( ))       

    int key;

     key=getch();

     if (key==224)

        key=getch();

         if (key==75)  x-=2;   //按下左方向键,中心横坐标减2

         if (key==77)  x+=2;   //按下右方向键,中心横坐标加2

        if (key==72)     //按下向上方向键,方块变体

           if (flag>=2 && flag<=3 )  flag++; flag%=2; flag+=2; 

           if ( flag>=4 && flag<=7 )  flag++; flag%=4; flag+=4; 

           if (flag>=8 && flag<=11 )  flag++; flag%=4; flag+=8; 

           if (flag>=12 && flag<=15 )  flag++; flag%=4; flag+=12; 

           if ( flag>=16 && flag<=19 )  flag++; flag%=4; flag+=16;  

        

      if (key==32)     //按空格键,暂停

         prfk( ); while(1)  if (getch( )==32)  clfk( );break;    //再按空格键,继续游戏

      if (ifmov( )==0)  x=Tb; flag=Tc;    //如果不可动,撤销上面操作

      else  prfk( ); Sleep(speed); clfk( ); Tb=x;Tc=flag;   //如果可动,执行操作

  

int ifmov( )   //判断能否移动

 if (a[x][y]!=0)  return 0;   //方块中心处有图案返回0,不可移动

 else if ( (flag==1 && ( a[x][ y-1]==0 && a[x+2][y-1]==0 && a[x+2][y]==0 ) ) ||  

         (flag==2 && ( a[x-2][y]==0 && a[x+2][y]==0 && a[x+4][y]==0 ) ) || 

         (flag==3 && ( a[x][y-1]==0 && a[x][y-2]==0 && a[x][y+1]==0 ) ) ||

         (flag==4 && ( a[x-2][y]==0 && a[x+2][y]==0 && a[x][y+1]==0 ) ) ||

         (flag==5 && ( a[x][y-1]==0 && a[x][y+1]==0 && a[x-2][y]==0 ) ) ||

         (flag==6 && ( a[x][ y-1]==0 && a[x-2][y]==0 && a[x+2][y]==0 ) ) ||

         (flag==7 && ( a[x][y-1]==0 && a[x][y+1]==0 && a[x+2][y]==0 ) ) ||

         (flag==8 && ( a[x][y+1]==0 && a[x-2][y]==0 && a[x+2][y+1]==0 ) ) ||

         (flag==9 && ( a[x][y-1]==0 && a[x-2][y]==0 && a[x-2][y+1]==0 ) ) ||

         (flag==10 && ( a[x][y-1]==0 && a[x-2][y-1]==0 && a[x+2][y]==0 ) ) ||

         (flag==11 && ( a[x][y+1]==0 && a[x+2][y-1]==0 && a[x+2][y]==0 ) ) ||

         (flag==12 && ( a[x][y-1]==0 && a[x][y+1]==0 && a[x-2][y-1]==0 ) ) ||

        ( flag==13 && ( a[x-2][y]==0 && a[x+2][y-1]==0 && a[x+2][y]==0 ) ) ||

        ( flag==14 && ( a[x][y-1]==0 && a[x][y+1]==0 && a[x+2][y+1]==0 ) ) ||

        (flag==15 && ( a[x-2][y]==0 && a[x-2][y+1]==0 && a[x+2][y]==0 ) ) ||

        (flag==16 && ( a[x][y+1]==0 && a[x][y-1]==0 && a[x+2][y-1]==0 ) ) ||

        ( flag==17 && ( a[x-2][y]==0 && a[x+2][y+1]==0 && a[x+2][y]==0 ) ) ||

        (flag==18 && ( a[x][y-1]==0 &&a[x][y+1]==0 && a[x-2][y+1]==0 ) ) ||

        (flag==19 && ( a[x-2][y]==0 && a[x-2][y-1]==0

              && a[x+2][y]==0 ) ) )  return 1; 

    

  return 0;   //其它情况返回0

void clNEXT( )   //清除框外的NEXT方块

 flag = next;  x=ZL+WID+6;  y=ZL+10;  clfk( ); 

void clHA( )   //清除满行的方块

 int k, Hang=0;    //k是某行方块个数, Hang是删除的方块行数

  for(j=ZL+HEI-1;j>=ZL+1;j--)  //当某行有WID/2-2个方块时,则为满行

    k=0; for(i=ZL+2;i<ZL+WID-2;i+=2)

     if (a[i][j]==1)   //竖坐标从下往上,横坐标由左至右依次判断是否满行

      k++;   //下面将操作删除行

       if (k==WID/2-2)    for(k=ZL+2;k<ZL+WID-2;k+=2) 

            a[k][j]=0; gtxy(k,j); printf("  "); Sleep(1); 

          for(k=j-1;k>ZL;k--)

           for(i=ZL+2;i<ZL+WID-2;i+=2)  //已删行数上面有方块,先清除再全部下移一行

             if(a[i][k]==1)  a[i][k]=0; gtxy(i,k); printf("  ");a[i][k+1]=1; 

              gtxy(i,k+1); printf("□");  

           

          j++;     //方块下移后,重新判断删除行是否满行

          Hang++;  //记录删除方块的行数

        

     

    

  

   score+=100*Hang;   //每删除一行,得100分

 if ( Hang>0 && (score%500==0 || score/500> level-1 ) )  //得分满500速度加快升一级

     speed-=20; level++; if(speed<200)speed+=20;  

参考技术C http://tieba.baidu.com/f?kz=1090419809这里有个高手写了一个。 参考技术D ////////////////////////////////////////////
// 程序名称:俄罗斯方块
// 编译环境:Visual C++ 6.0,EasyX 2011惊蛰版
// 程序编写:krissi <zhaoh1987@qq.com>
// 最后更新:2010-12-18
//
#include <graphics.h>
#include <conio.h>
#include <time.h> /////////////////////////////////////////////
// 定义常量、枚举量、结构体、全局变量
/////////////////////////////////////////////#define WIDTH 10 // 游戏区宽度
#define HEIGHT 22 // 游戏区高度
#define SIZE 20 // 每个游戏区单位的实际像素// 定义操作类型
enum CTRL

CTRL_ROTATE, // 方块旋转
CTRL_LEFT, CTRL_RIGHT, CTRL_DOWN, // 方块左、右、下移动
CTRL_SINK, // 方块沉底
CTRL_QUIT // 退出游戏
;// 定义绘制方块的方法
enum DRAW

SHOW, // 显示方块
HIDE, // 隐藏方块
FIX // 固定方块
;// 定义七种俄罗斯方块
struct BLOCK

WORD dir[4]; // 方块的四个旋转状态
COLORREF color; // 方块的颜色
g_Blocks[7] = 0x0F00, 0x4444, 0x0F00, 0x4444, RED, // I
0x0660, 0x0660, 0x0660, 0x0660, BLUE, // 口
0x4460, 0x02E0, 0x0622, 0x0740, MAGENTA, // L
0x2260, 0x0E20, 0x0644, 0x0470, YELLOW, // 反L
0x0C60, 0x2640, 0x0C60, 0x2640, CYAN, // Z
0x0360, 0x4620, 0x0360, 0x4620, GREEN, // 反Z
0x4E00, 0x4C40, 0x0E40, 0x4640, BROWN; // T// 定义当前方块、下一个方块的信息
struct BLOCKINFO

byte id; // 方块 ID
char x, y; // 方块在游戏区中的坐标
byte dir:2; // 方向
g_CurBlock, g_NextBlock;// 定义游戏区
BYTE g_World[WIDTH][HEIGHT] = 0; /////////////////////////////////////////////
// 函数声明
/////////////////////////////////////////////void Init(); // 初始化游戏
void Quit(); // 退出游戏
void NewGame(); // 开始新游戏
void GameOver(); // 结束游戏
CTRL GetControl(bool _onlyresettimer = false); // 获取控制命令
void DispatchControl(CTRL _ctrl); // 分发控制命令
void NewBlock(); // 生成新的方块
bool CheckBlock(BLOCKINFO _block); // 检测指定方块是否可以放下
void DrawBlock(BLOCKINFO _block, DRAW _draw = SHOW); // 画方块
void OnRotate(); // 旋转方块
void OnLeft(); // 左移方块
void OnRight(); // 右移方块
void OnDown(); // 下移方块
void OnSink(); // 沉底方块 /////////////////////////////////////////////
// 函数定义
/////////////////////////////////////////////// 主函数
void main()

Init(); CTRL c;
while(true)

c = GetControl();
DispatchControl(c); // 按退出时,显示对话框咨询用户是否退出
if (c == CTRL_QUIT)

HWND wnd = GetHWnd();
if (MessageBox(wnd, "您要退出游戏吗?", "提醒", MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
Quit();



// 初始化游戏
void Init()

initgraph(640, 480);
srand((unsigned)time(NULL)); // 显示操作说明
setfont(14, 0, "宋体");
outtextxy(20, 330, "操作说明");
outtextxy(20, 350, "上:旋转");
outtextxy(20, 370, "左:左移");
outtextxy(20, 390, "右:右移");
outtextxy(20, 410, "下:下移");
outtextxy(20, 430, "空格:沉底");
outtextxy(20, 450, "ESC:退出"); // 设置坐标原点
setorigin(220, 20); // 绘制游戏区边界
rectangle(-1, -1, WIDTH * SIZE, HEIGHT * SIZE);
rectangle((WIDTH + 1) * SIZE - 1, -1, (WIDTH + 5) * SIZE, 4 * SIZE); // 开始新游戏
NewGame();

// 退出游戏
void Quit()

closegraph();
exit(0);

// 开始新游戏
void NewGame()

// 清空游戏区
setfillstyle(BLACK);
bar(0, 0, WIDTH * SIZE - 1, HEIGHT * SIZE - 1);
ZeroMemory(g_World, WIDTH * HEIGHT); // 生成下一个方块
g_NextBlock.id = rand() % 7;
g_NextBlock.dir = rand() % 4;
g_NextBlock.x = WIDTH + 1;
g_NextBlock.y = HEIGHT - 1; // 获取新方块
NewBlock();

// 结束游戏
void GameOver()

HWND wnd = GetHWnd();
if (MessageBox(wnd, "游戏结束。\n您想重新来一局吗?", "游戏结束", MB_YESNO | MB_ICONQUESTION) == IDYES)
NewGame();
else
Quit();

// 获取控制命令
CTRL GetControl(bool _onlyresettimer)

static DWORD oldtime = GetTickCount(); // 重置计时器
if (_onlyresettimer)

oldtime = GetTickCount();
return CTRL_DOWN; // 仅仅为了重置计时器,随便返回一个值
// 获取控制值
while(true)

// 如果超时,自动下落一格
DWORD newtime = GetTickCount();
if (newtime - oldtime >= 500)

oldtime = newtime;
return CTRL_DOWN;
// 如果有按键,返回按键对应的功能
if (kbhit())

switch(getch())

case 'w':
case 'W': return CTRL_ROTATE;
case 'a':
case 'A': return CTRL_LEFT;
case 'd':
case 'D': return CTRL_RIGHT;
case 's':
case 'S': return CTRL_DOWN;
case 27: return CTRL_QUIT;
case ' ': return CTRL_SINK;
case 0:
case 0xE0:
switch(getch())

case 72: return CTRL_ROTATE;
case 75: return CTRL_LEFT;
case 77: return CTRL_RIGHT;
case 80: return CTRL_DOWN;





// 分发控制命令
void DispatchControl(CTRL _ctrl)

switch(_ctrl)

case CTRL_ROTATE: OnRotate(); break;
case CTRL_LEFT: OnLeft(); break;
case CTRL_RIGHT: OnRight(); break;
case CTRL_DOWN: OnDown(); break;
case CTRL_SINK: OnSink(); break;
case CTRL_QUIT: break;


// 生成新的方块
void NewBlock()

g_CurBlock.id = g_NextBlock.id, g_NextBlock.id = rand() % 7;
g_CurBlock.dir = g_NextBlock.dir, g_NextBlock.dir = rand() % 4;
g_CurBlock.x = (WIDTH - 4) / 2;
g_CurBlock.y = HEIGHT + 2; // 下移新方块直到有局部显示
WORD c = g_Blocks[g_CurBlock.id].dir[g_CurBlock.dir];
while((c & 0xF) == 0)

g_CurBlock.y--;
c >>= 4;
// 绘制新方块
DrawBlock(g_CurBlock); // 绘制下一个方块
setfillstyle(BLACK);
bar((WIDTH + 1) * SIZE, 0, (WIDTH + 5) * SIZE - 1, 4 * SIZE - 1);
DrawBlock(g_NextBlock);

// 画方块
void DrawBlock(BLOCKINFO _block, DRAW _draw)

WORD b = g_Blocks[_block.id].dir[_block.dir];
int x, y; int color = BLACK;
switch(_draw)

case SHOW: color = g_Blocks[_block.id].color; break;
case HIDE: color = BLACK; break;
case FIX: color = g_Blocks[_block.id].color / 3; break;

setfillstyle(color); for(int i=0; i<16; i++)

if (b & 0x8000)

x = _block.x + i % 4;
y = _block.y - i / 4;
if (y < HEIGHT)

if (_draw != HIDE)
bar3d(x * SIZE + 2, (HEIGHT - y - 1) * SIZE + 2, (x + 1) * SIZE - 4, (HEIGHT - y) * SIZE - 4, 3, true);
else
bar(x * SIZE, (HEIGHT - y - 1) * SIZE, (x + 1) * SIZE - 1, (HEIGHT - y) * SIZE - 1);


b <<= 1;


// 检测指定方块是否可以放下
bool CheckBlock(BLOCKINFO _block)

WORD b = g_Blocks[_block.id].dir[_block.dir];
int x, y; for(int i=0; i<16; i++)

if (b & 0x8000)

x = _block.x + i % 4;
y = _block.y - i / 4;
if ((x < 0) || (x >= WIDTH) || (y < 0))
return false; if ((y < HEIGHT) && (g_World[x][y]))
return false;

b <<= 1;
return true;

// 旋转方块
void OnRotate()

// 获取可以旋转的 x 偏移量
int dx;
BLOCKINFO tmp = g_CurBlock;
tmp.dir++; if (CheckBlock(tmp)) dx = 0; goto rotate;
tmp.x = g_CurBlock.x - 1; if (CheckBlock(tmp)) dx = -1; goto rotate;
tmp.x = g_CurBlock.x + 1; if (CheckBlock(tmp)) dx = 1; goto rotate;
tmp.x = g_CurBlock.x - 2; if (CheckBlock(tmp)) dx = -2; goto rotate;
tmp.x = g_CurBlock.x + 2; if (CheckBlock(tmp)) dx = 2; goto rotate;
return;rotate:
// 旋转
DrawBlock(g_CurBlock, HIDE);
g_CurBlock.dir++;
g_CurBlock.x += dx;
DrawBlock(g_CurBlock);

// 左移方块
void OnLeft()

BLOCKINFO tmp = g_CurBlock;
tmp.x--;
if (CheckBlock(tmp))

DrawBlock(g_CurBlock, HIDE);
g_CurBlock.x--;
DrawBlock(g_CurBlock);


// 右移方块
void OnRight()

BLOCKINFO tmp = g_CurBlock;
tmp.x++;
if (CheckBlock(tmp))

DrawBlock(g_CurBlock, HIDE);
g_CurBlock.x++;
DrawBlock(g_CurBlock);


// 下移方块
void OnDown()

BLOCKINFO tmp = g_CurBlock;
tmp.y--;
if (CheckBlock(tmp))

DrawBlock(g_CurBlock, HIDE);
g_CurBlock.y--;
DrawBlock(g_CurBlock);

else
OnSink(); // 不可下移时,执行“沉底方块”操作

// 沉底方块
void OnSink()

int i, x, y; // 连续下移方块
DrawBlock(g_CurBlock, HIDE);
BLOCKINFO tmp = g_CurBlock;
tmp.y--;
while (CheckBlock(tmp))

g_CurBlock.y--;
tmp.y--;

DrawBlock(g_CurBlock, FIX); // 固定方块在游戏区
WORD b = g_Blocks[g_CurBlock.id].dir[g_CurBlock.dir];
for(i = 0; i < 16; i++)

if (b & 0x8000)

if (g_CurBlock.y - i / 4 >= HEIGHT)
// 如果方块的固定位置超出高度,结束游戏
GameOver();
return;

else
g_World[g_CurBlock.x + i % 4][g_CurBlock.y - i / 4] = 1;
b <<= 1;
// 检查是否需要消掉行,并标记
int row[4] = 0;
bool bRow = false;
for(y = g_CurBlock.y; y >= max(g_CurBlock.y - 3, 0); y--)

i = 0;
for(x = 0; x < WIDTH; x++)
if (g_World[x][y] == 1)
i++;
if (i == WIDTH)

bRow = true;
row[g_CurBlock.y - y] = 1;
setfillstyle(WHITE, DIAGCROSS2_FILL);
bar(0, (HEIGHT - y - 1) * SIZE + SIZE / 2 - 2, WIDTH * SIZE - 1, (HEIGHT - y - 1) * SIZE + SIZE / 2 + 2);

if (bRow)

// 延时 200 毫秒
Sleep(200); // 擦掉刚才标记的行
IMAGE img;
for(i = 0; i < 4; i++)

if (row[i])

for(y = g_CurBlock.y - i + 1; y < HEIGHT; y++)
for(x = 0; x < WIDTH; x++)

g_World[x][y - 1] = g_World[x][y];
g_World[x][y] = 0;
getimage(&img, 0, 0, WIDTH * SIZE, (HEIGHT - (g_CurBlock.y - i + 1)) * SIZE);
putimage(0, SIZE, &img);


// 产生新方块
NewBlock(); // 重新计算延时
GetControl(true);

C语言程序设计小游戏之俄罗斯方块!适合初学者上手练手!

       俄罗斯方块的核心玩法非常简单,所以制作起来并不是很复杂,我准备先用2篇文字的篇幅详细讲解一下俄罗斯方块的制作方法。

       今天咱们算是第一篇,主要讲解俄罗斯方块中如何定义方块,以及如何实现方块的移动、旋转、下落等操作。

1. 需求分析

       俄罗斯方块中的基本逻辑非常简单,不过在介绍之前,我们先确定一下名词,防止出现词不达意的现象。


       这种方块,是构成容器的基本单位,我们称之为小方块。

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       由四个小方块组成,用来填充容器的东西,我们叫做大方块。

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       用来存放大方块的地方,我们叫做容器。

       定义了这三种东西之后,我们就可以这样描述俄罗斯方块这个游戏了。俄罗斯方块主要由一个存放正方形小方块的虚拟容器和实时下落的7种大方块构成。

       容器的宽是 10 列,高是 20 行,行高和列宽是相等的,所以容器可以看作是 200 个正方形小方块平铺的结果。

       每个实时下落的大方块都是由 4个正方形小方块组成,共 7 种固定样式,每种方块都可以旋转,所以理论上最多有 28 种样式,但其中有一些大方块在旋转的时候样式不会发生变化。

       7 种大方块按照形象可以由 7种字母替代,它们是:S、Z、L、J、I、O、T。

       这些大方块在下落到容器最底部或碰撞到其它容器中的小方块时会被固定在容器中,然后容器上方会重新产生一个随机的大方块,重复原来的下落流程。

       当容器中出现满行的时候,整行会被消除,该行上面的所有小方块都会依次掉落。


2. 大方块的定义

       俄罗斯方块中虽然基本的单位是小方块,但是所有操作的对象都是大方块,所以这里直接定义大方块的结构类型。

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       在定义大方块之前,我们需要先定义两个结构,分别是大方块的类型和大方块的旋转状态。

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       使用上面两个类型,就可以确定当前大方块的样式。除了样式之外,因为大方块会按照固定的速度移动,所以这里需要定义大方块的位置信息,加上前面两个成员,大方块可定义如下:

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       这里之所以没有定义大方块的下落速度是因为所有的大方块下落速度都是一样的,因此不需要在每个大方块内部都定义一个变量,只需要在外部定义一个速度变量即可。

       确认了大方块的结构,我们还有一个问题需要解决,就是如何获取大方块的具体形状。

       虽然大方块里面已经定义了类型和状态,但是并没有保存当前方块的形状,为了解决这个问题,我们可以定义一个全局的静态数组,数组的每一项代表一种方块的形状,算上重复的,一共可存放 28 种形状。

static unsigned short gBlockList[BT_NUM][BS_NUM];

       注意这里,数组的每一项用了一个 unsigned short 类型表示。

       因为大方块是由 4 个小方块组成,所以每个大方块都可以用一个 4 * 4 的二维数组来表示。

       因为二维数组的每一项都只有两种状态,所以可以进一步变形,每一项用一个比特位,这样算下来,16 位比特就可以描述一个大方块的形状,而 16 位比特对应 C语言中的数据类型,正好是 unsigned short。

       虽然确认了方案,但是我们还需要确认一个表达规则,就是 16 位比特串低四位是表示大方块的第一行,还是表示最后一行,又或者表示左边的列,还是右边的列。

       方法很多,只需要取一个认为方便的就好,这里我是这样定义的:

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       即低四位表示第一行。最终我们可以求出 28 个形状对应的每一个值:

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       以后如果在程序中如果需要获取当前大方块的形状,只需要使用类似下面的语句:

gBlockList[i][j]


3. 大方块的操作

       确定了大方块的定义,接下来就是对大方的操作实现。首先是初始化操作:

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       接着就是移动操作,因为我们使用两个变量来确定大方块的位置,所以代码逻辑非常简单:

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       除了移动,大方块还有一个常用旋转操作,因为前面我们已经使用打表法定义了所有方块的旋转状态,这里直接获取即可:

【C语言程序设计】小游戏之俄罗斯方块(一)!适合初学者上手、练手!

       除了上面这些操作,其实还有碰撞检测以及渲染等操作。我们将在下一章节将为大家详细阐述!

       如果你也想学编程,可以来我的C语言/C++编程学习基地【Q群:1083154082】!

       还有免费的(源码,零基础教程,项目实战教学视频)!   

       涉及:游戏开发、课程设计、常用软件开发、编程基础知识、黑客等等...




以上是关于用c语言编写俄罗斯方块程序 求详解的主要内容,如果未能解决你的问题,请参考以下文章

求用JAVA编写俄罗斯方块游戏的源代码

俄罗斯方块(C语言实现)

俄罗斯方块软件:C语言应用初步感受

C# 俄罗斯方块源代码

只会C语言编程还要学哪些才能做俄罗斯方块这样的小游戏?

用Shell编写的俄罗斯方块代码