一、需求分析

1.以三维数组block[n][i][j]来表示俄罗斯方块的不同形态。0表示无方块,1代表有方块。以二维数组visit[i][j]判断游戏区域某一位置是否有方块。0代表无方块,1代表有方块。以一维数组color[n]表示不同形状方块的颜色。以二维数组markcolor[i][j]固定方块降落完成后在游戏区域的方块颜色。

2.程序通过键盘输入判断Ascll值来决定执行操作。

3.用户可在进入界面进行等级选择,再通过等级大小进行方块降落速度的计算。

4.程序要完成方块的堆积以及当行满时对该行的消除。

5.要计算好方块的大小及游戏区域的大小,要使游戏区域大小为方块大小的整数倍。

6.设置好初始界面的大小、颜色及游戏界面的大小、颜色,预览区域大小颜色及字体背景色等。

7.程序要完成对方块能否旋转、左移、右移、下移的判断及实现

二、代码实现

1、 预处理 

#include <iostream>
#include <graphics.h>
#include <conio.h>		// 接受键盘输入输出 
#include <time.h>
#include <stdio.h>
#include <stdlib.h>


//界面的相关参数
#define WALL_SQUARE_WIDTH 10  //围墙方块的宽度 
#define xWALL_SQUARE_NUM 30   //x轴方向的方块数目 
#define yWALL_SQUARE_WIDTH 46 //y轴方向的方块数目 
#define GAME_WALL_WIDTH (WALL_SQUARE_WIDTH*xWALL_SQUARE_NUM)  //游戏区域的宽度 300 
#define GAME_WALL_HTGH (WALL_SQUARE_WIDTH*yWALL_SQUARE_WIDTH) //游戏区域的高度 460 
#define Mid_x 90   //新方块出现的位置 
#define Mid_y -50 		//新方块出现的位置 

#define WINDOW_WIDTH 480  //游戏总窗口宽度  480 
#define WINDOW_HIGH 460  //游戏总窗口高度   460

//俄罗斯方块的参数 
#define ROCK_SQUARE_WIDTH (2*WALL_SQUARE_WIDTH) //俄罗斯方块的大小是围墙的2倍 20
#define xROCK_SQUARE_NUM  ((GAME_WALL_WIDTH-20)/ROCK_SQUARE_WIDTH) //游戏区x轴放的方块数目 14
#define yROCK_SQUARE_NUM  ((GAME_WALL_HTGH-20)/ROCK_SQUARE_WIDTH) //游戏区y轴放的方块数目 22
#define ROCK_NUM 5	//五种方块
#define ROCK_CASE 5 //方块外壳:表示方块形态的壳子 


//键盘数据
#define KEY_Leave 27    //Esc
#define KEY_Up 294		//方向键上 
#define KEY_Right 295	//方向键右 
#define KEY_Left 293	//方向键左 
#define KEY_Down 296	//方向键下 
#define KEY_Space 32	//空格 
#define KEY_One 49		//数字键 1 
#define KEY_Two 50		//数字键 2
#define KEY_Three 51	//数字键 3
#define KEY_Four 52		//数字键 4 
#define KEY_Zero 48		//数字键 0


int score;		 //分数 
int rank;			 //等级
int speed=500; 
int MinX=10,MinY=10; //最小可以放置方块的坐标 
int NextIndex=-1;	 //下一个方块的种类 
int NowIndex=-1; 	 //当前方块的种类
int visit[yROCK_SQUARE_NUM][xROCK_SQUARE_NUM];		  //将游戏区域分为n行m列的数组用1 or 0判断游戏区是否有方块 
int markcolor[yROCK_SQUARE_NUM][xROCK_SQUARE_NUM];	  //将游戏区域分为n行m列的数组来表示颜色 

int color[ROCK_NUM]={GREEN,CYAN,MAGENTA,YELLOW,BLUE };//方块颜色 

//初始化各种方块,及其形态变化 
int block[ROCK_NUM*4][ROCK_CASE][ROCK_CASE]={
			//I型方块 
			{0,0,0,0,0,
			 0,0,1,0,0,
			 0,0,1,0,0,
			 0,0,1,0,0,
			 0,0,1,0,0
			},
			{0,0,0,0,0,
			 0,0,0,0,0,
			 1,1,1,1,0,
			 0,0,0,0,0,
			 0,0,0,0,0
			},
			{0,0,0,0,0,
			 0,0,1,0,0,
			 0,0,1,0,0,
			 0,0,1,0,0,
			 0,0,1,0,0
			},
			{0,0,0,0,0,
			 0,0,0,0,0,
			 1,1,1,1,0,
			 0,0,0,0,0,
			 0,0,0,0,0
			},
			//L型方块
			{0,0,0,0,0,
			 0,1,0,0,0,
			 0,1,0,0,0,
			 0,1,1,0,0,
			 0,0,0,0,0
			}, 
			{0,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,0,0,
			 0,1,1,0,0,
			 0,0,1,0,0,
			 0,0,1,0,0,
			 0,0,0,0,0
			},
			{0,0,0,0,0,
			 0,0,0,1,0,
			 0,1,1,1,0,
			 0,0,0,0,0,
			 0,0,0,0,0
			},
			//田型方块
			{0,0,0,0,0,
			 0,1,1,0,0,
			 0,1,1,0,0,
			 0,0,0,0,0,
			 0,0,0,0,0
			},  
			{0,0,0,0,0,
			 0,1,1,0,0,
			 0,1,1,0,0,
			 0,0,0,0,0,
			 0,0,0,0,0
			}, 
			{0,0,0,0,0,
			 0,1,1,0,0,
			 0,1,1,0,0,
			 0,0,0,0,0,
			 0,0,0,0,0
			}, 
			{0,0,0,0,0,
			 0,1,1,0,0,
			 0,1,1,0,0,
			 0,0,0,0,0,
			 0,0,0,0,0
			}, 
			//T型方块 
			{0,0,0,0,0,
			 0,0,0,0,0,
			 0,1,1,1,0,
			 0,0,1,0,0,
			 0,0,0,0,0
			}, 
			{0,0,0,0,0,
			 0,0,1,0,0,
			 0,1,1,0,0,
			 0,0,1,0,0,
			 0,0,0,0,0
			}, 
			{0,0,0,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,0,0,
			 0,1,0,0,0,
			 0,1,1,0,0,
			 0,1,0,0,0, 
			 0,0,0,0,0
			}, 
			//Z型方块
			{0,0,0,0,0,
			 0,0,0,0,0,
			 0,1,1,0,0,
			 0,0,1,1,0,
			 0,0,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,0
			},
			{0,0,0,0,0,
			 0,0,0,0,0,
			 0,1,1,0,0,
			 0,0,1,1,0,
			 0,0,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,0
			},  
	   };
typedef enum
{
	BLOCK_UP,
	BLOCK_RIGHT,
	BLOCK_DOWN,
	BLOCK_LEFT
}block_dir_t;
typedef enum
{
	MOVE_DOWN,
	MOVE_LEFT,
	MOVE_RIGHT
}move_dir_t;


void Inter_Face();			//初始化界面 
void Inter_GameFace();		//初始化游戏界面
void clearBlock();			//清除右上角的方块
void clearblock(int x,int y,block_dir_t blockDir);//清除游戏区域移动的方块进行更新 
void drawBlock(int x,int y);//在右上角绘制下一个方块
void drawBlock(int x,int y,int blockIndex,block_dir_t blockDir);//特定位置的特定方块 
void nextblock();			//随机选取下一个方块 
void newblock();			//游戏区方块的连续移动 
int moveAble(int x0,int y0,move_dir_t moveDir,block_dir_t blockDir);//判断方块能否移动 
int rotatable(int x,int y,block_dir_t blockDir);//判断能否转向 
void move();				//方块的移动 
void mark(int x,int y,int blockIndex,block_dir_t blockDir);				//方块的固定 
void down(int x);			//消行函数 
void addscore(int line);	//更新分数函数 
void check();				//对行进行检测,并进行消行更分 
void GameProcess();			//整个游戏串联 
void GameEnd();				//游戏结束页面

2、子函数

//初始化界面 
void Inter_Face() {
	setinitmode(0,400,200);//窗口位置 
	initgraph(WINDOW_WIDTH,WINDOW_HIGH,0);//窗口大小
	setcaption("俄罗斯方块");
	setfont(40,0,"隶体");
	setcolor(RED);
	outtextxy(100,100,"俄 罗 斯 方 块");
	setcolor(WHITE);
	setfont(20,0,"隶体");
	outtextxy(130,200,"请从1-4选择等级并输入");
	
	while(1)
	{
		if(kbhit())
		{
			int key=getch();
			if(key==KEY_One)
				{
					rank=1;	break;
				}
			else if(key==KEY_Two)
				{
					rank=2;	break;
				}
			else if(key==KEY_Three) 
				{
					rank=3;	break;
				}
			else if(key==KEY_Four)
				{
					rank=4;	break;
				}
		}
	}
	setfont(20,0,"宋体");
	setcolor(LIGHTGRAY);
	outtextxy(150,300,"请按任意键开始游戏");
	getch();
}
void Inter_GameFace()
{
	char str[16];
	int i,j;
	score=0;//初始化分数 
	cleardevice(); 
	setfillcolor(BLACK);//颜色 
	bar(0,0,300,460);				  // 游戏框 
	setfillcolor(BLACK);  //颜色 
	bar(300,0,480,460);				  //预览区 
	setcolor(WHITE);
	setfont(10,0,""); 
	
	//游戏界面边框 
	for(i=0;i<xWALL_SQUARE_NUM;i++)
		for(j=0;j<yWALL_SQUARE_WIDTH;j++)
		{
			//outtextxy(i*WALL_SQUARE_WIDTH,0,"▉");
			outtextxy(i*WALL_SQUARE_WIDTH,450,"▉");//下边界 
			outtextxy(0,j*WALL_SQUARE_WIDTH,"▉");//左边界 
			outtextxy(290,j*WALL_SQUARE_WIDTH,"▉");//右边界 
		}
	
	//预览区 
	setfontbkcolor(BLACK);
	setfont(15,0,"宋体");
	setcolor(WHITE);
	outtextxy(350,10,"下一个方块");

	outtextxy(350,170,"你的分数");
	sprintf(str,"%d",score);
	outtextxy(370,185,str);
	
	outtextxy(360,210,"等级");
	sprintf(str,"%d",rank);
	outtextxy(370,230,str);
	 
	outtextxy(350,275,"游戏操作");
	outtextxy(350,300,"↑:旋转");
 	outtextxy(350,325,"↓:下降");
 	outtextxy(350,350,"←:左移");
 	outtextxy(350,375,"→:右移");
 	outtextxy(350,400,"0:重新开始"); 
 	outtextxy(350,425,"空格:暂停"); 


}

//清除预览“下一个”5*5的界面 
void clearBlock()
{
	setcolor(BLACK);
	setfont(ROCK_SQUARE_WIDTH,0,"");
	for(int i=0;i<ROCK_CASE;i++)
	{
		for(int j=0;j<ROCK_CASE;j++)
		{
			int x=340+ROCK_SQUARE_WIDTH*j;
			int y=30+ROCK_SQUARE_WIDTH*i;
			outtextxy(x,y,"▉");
		}
	 } 
} 
//清除游戏区域的方块 
void clearblock(int x,int y,block_dir_t blockDir)
{
	int id=NowIndex*4+blockDir;
	y+=Mid_y;//获得该方块距离上边界的位置 
	
	for(int i=0;i<ROCK_CASE;i++)
	{
		for(int j=0;j<ROCK_CASE;j++)
		{
			if(block[id][i][j]==1)
			{
				setcolor(BLACK);
				setfont(ROCK_SQUARE_WIDTH,0,"");
				outtextxy(x+j*ROCK_SQUARE_WIDTH,y+i*ROCK_SQUARE_WIDTH,"▉");
			}
			
		}
	 } 
} 

//绘制新的下一个方块 
void drawBlock(int x,int y)
{
	setcolor(color[NextIndex]);
	setfont(ROCK_SQUARE_WIDTH,0,"");
	for(int i=0;i<ROCK_CASE;i++)
	{
		for(int j=0;j<ROCK_CASE;j++)
		{
			if(block[NextIndex*4][i][j]==1)
			{	int x2=x+ROCK_SQUARE_WIDTH*j;
				int y2=y+ROCK_SQUARE_WIDTH*i;
				outtextxy(x2,y2,"▉");
			}
		}
	 } 
}
//绘制特定位置特定方块 
void drawBlock(int x,int y,int blockIndex,block_dir_t blockDir)
{
	setcolor(color[NowIndex]);
	setfont(ROCK_SQUARE_WIDTH,0,""); 
	int id=blockIndex*4+blockDir;
	for(int i=0;i<ROCK_CASE;i++)
	{
		for(int j=0;j<ROCK_CASE;j++)
		{
			if(block[id][i][j]==1)
			{	
				int x2=x+ROCK_SQUARE_WIDTH*j;
				int y2=y+ROCK_SQUARE_WIDTH*i;
				outtextxy(x2,y2,"▉");
			}
		}
	}
}
 
//随机选取一种方块
void nextblock()
{

	clearBlock();	//调用清除函数
	srand(time(NULL));
	NextIndex=rand()%ROCK_NUM-0;//随机取一种方块形态 
	drawBlock(340,30); 
}


//判断方块是否能移动 
//在前面条件成立下y+j不能超过行所容纳的小方块数量,x+i同理,且方块所移动区域无方块 
int moveAble(int x0,int y0,move_dir_t moveDir,block_dir_t blockDir)
{
	int x=(y0-MinY)/ROCK_SQUARE_WIDTH;//该点到上边界有几个小方块的位置 
	int y=(x0-MinX)/ROCK_SQUARE_WIDTH;//该点到左边界有几个小方块的位置 
	int id=NowIndex*4+blockDir;	  //方块此时的形态 
	int ret=1;
	
	if(moveDir==MOVE_DOWN)				//判断能否下移 
	{
		for(int i=0;i<ROCK_CASE;i++)
		{
			for(int j=0;j<ROCK_CASE;j++)
			{
				if(block[id][i][j]==1&&(x+i+1>=yROCK_SQUARE_NUM||visit[x+i+1][y+j]==1))/*上边界到方块的之间可容纳小方块的个数+该方块该列的小方块个数不能>=游戏区域所能容纳的最大值;方块下面无以固定的方块。*/ 
					ret=0;
			} 
		}
	}
	else if(moveDir==MOVE_LEFT)			//判断能否左移 
	{
		for(int i=0;i<ROCK_CASE;i++)
		{
			for(int j=0;j<ROCK_CASE;j++)
			{
				if(block[id][i][j]==1&&(y+j==0||visit[x+i][y+j-1]==1))/*左墙到方块的之间可容纳小方块的个数+该方块该行的小方块个数不能<=0;方块左侧无以固定的方块。*/ 
					ret=0;
			} 
		}
	}
	else if(moveDir==MOVE_RIGHT)	//判断能否右移 
	{
		for(int i=0;i<ROCK_CASE;i++)
		{
			for(int j=0;j<ROCK_CASE;j++)
			{
				if(block[id][i][j]==1&&(y+j+1>=xROCK_SQUARE_NUM||visit[x+i][y+j+1]==1))/*左墙到方块的之间可容纳小方块的个数+该方块该行的小方块个数不能>=游戏区域所能容纳的最大值;方块右侧无以固定的方块。*/ 
					ret=0;
			} 
		}
	}
	return ret;
}




//游戏区域方块的连续移动部分过程的衔接 
void newblock()
{
	NowIndex=NextIndex;			// 获得使用方块的信息
	 
	drawBlock(Mid_x,Mid_y);		//在游戏区制作方块
	 
	Sleep(100);					//停留0.1秒反应时间
	 
	nextblock();				//右上角绘制下一个方块
	 
	move();						//降落 
}

//判断方块能否旋转 Y-> return 1;N->return 0
int rotatable(int x,int y,block_dir_t blockDir)
{
	int id=NowIndex*4+blockDir;
	int xIndex=(y-MinY)/ROCK_SQUARE_WIDTH;
	int yIndex=(x-MinX)/ROCK_SQUARE_WIDTH;
	if(!moveAble(x,y,MOVE_DOWN,blockDir))//向下不能移动不能进行旋转 
		return 0;
	for(int i=0;i<ROCK_CASE;i++)
	{
		for(int j=0;j<ROCK_CASE;j++)
		{
			if(block[id][i][j]==1&&(yIndex+j<0||yIndex+j>=xROCK_SQUARE_NUM||visit[xIndex+i][yIndex+j]==1))//方块紧贴左右墙或旋转后的位置有方块(被标记)都不能旋转成功 
			return 0;
		}
	}
	return 1;
} 

//方块降落 包括键盘的使用 
void move()
{
	int x=Mid_x;
	int y=Mid_y;
	int k=0;		//偏移量
	int cur_speed=speed-(rank-1)*100;//根据等级决定下降速度 
	block_dir_t blockDir=BLOCK_UP;//表示新方块的形状 
	//判断游戏是否结束 
	if(moveAble(Mid_x,Mid_y,MOVE_DOWN,BLOCK_UP))
	{
		while(1)
		{
     		if(kbhit())
			{
				int key=getch();
				if(key==KEY_Space)
				{	getch();			//空格暂停
				    clearblock(x,k,blockDir);//清除上一时刻的方块,后面几个同意 
				}
				else  if(key==KEY_Up)
				{	clearblock(x,k,blockDir);
					block_dir_t nextDir=(block_dir_t)((blockDir+1)%4);//方块旋转后的形状 
					if(rotatable(x,y+k,nextDir))//判断能否旋转成功 
						blockDir=nextDir;
				}
				else if(key==KEY_Down)
				{	clearblock(x,k,blockDir);
					cur_speed=50;//改下降速度 
				}
				
				else if(key==KEY_Left)
				{	clearblock(x,k,blockDir);
					if(moveAble(x,y+k+ROCK_SQUARE_WIDTH,MOVE_LEFT,blockDir))//判断能否移动 
						x-=ROCK_SQUARE_WIDTH;
				}
				else if(key==KEY_Right)
				{	clearblock(x,k,blockDir);
					if(moveAble(x,y+k+ROCK_SQUARE_WIDTH,MOVE_RIGHT,blockDir))//判断能否移动
						x+=ROCK_SQUARE_WIDTH;
				}
				else if(key==KEY_Zero)//重新开始 
				{
				 	
					Inter_Face();//初始化界面 
					Inter_GameFace();//初始化游戏界面 
					GameProcess();
				}
				else
					clearblock(x,k,blockDir);	
			}
			else
				clearblock(x,k,blockDir); 
				
			flushkey();//清理多余的键盘输入 
 			k+=ROCK_SQUARE_WIDTH;//无论有没有按键方块都会向下移动 
			drawBlock(x,y+k,NowIndex,blockDir);//绘制目前的方块
			Sleep(cur_speed); 
			cur_speed=speed-(rank-1)*100;//恢复原有速度,针对KEY_Down 
			if(!moveAble(x,y+k,MOVE_DOWN,blockDir))	//如果方块不能移动便将其固定 
			{
				mark(x,y+k,NowIndex,blockDir);
				break;
			}	
		} 
	}
	else
		{
			
			GameEnd();
		}
	
	
} 
//固定方块 
void mark(int x,int y,int blockIndex,block_dir_t blockDir)
{
	int id=blockIndex*4+blockDir;
	int x1=(y-MinY)/ROCK_SQUARE_WIDTH;//被固定的方块距顶部边框间隔小方块的个数 
	int y1=(x-MinX)/ROCK_SQUARE_WIDTH;//被固定的方块距离左边边框间隔小方块的个数 
	for(int i=0;i<ROCK_CASE;i++)
	{
		for(int j=0;j<ROCK_CASE;j++)
		{
			if(block[id][i][j]==1)//将方块固定 
			{
				visit[x1+i][y1+j]=1;//标记该处有方块,为后续的判断条件 
				markcolor[x1+i][y1+j]=color[blockIndex];//标记颜色 
			}
		}
	}
}
//清除函数 
void down(int x)
{
	//清除i行j列 
	for(int i=x;i>0;i--)
	{
		for(int j=0;j<xROCK_SQUARE_NUM;j++)
		{
			if(visit[i-1][j])//如果被删除的第i行上面有方块将其原样移到下面 
			{
				visit[i][j]=1;
				markcolor[i][j]=markcolor[i-1][j];
				setcolor(markcolor[i][j]);
				setfont(ROCK_SQUARE_WIDTH,0,"");
				outtextxy(ROCK_SQUARE_WIDTH*j+MinX,ROCK_SQUARE_WIDTH*i+MinY,"▉");
			}
			else//如果第i行上面没有方块只需将第i行删除即可 
			{
				visit[i][j]=0;
				setcolor(BLACK);
				setfont(ROCK_SQUARE_WIDTH,0,"");
				outtextxy(ROCK_SQUARE_WIDTH*j+MinX,ROCK_SQUARE_WIDTH*i+MinY,"▉");
			}	
		}
	}
	if(x==0) 
	{
		setcolor(BLACK);
		setfont(ROCK_SQUARE_WIDTH,0,"");
		//清除0行即最顶层行 
		for(int j=0;xROCK_SQUARE_NUM;j++)
		{
			visit[0][j]=0;
			outtextxy(ROCK_SQUARE_WIDTH*j+MinX,MinY,"▉");
		}		
	}
	
}
//更新分数
void addscore(int line)
{
	char str[10];
	setcolor(LIGHTGRAY);
	score+=line*10;
	setfont(20,0,"宋体");
	sprintf(str,"%d",score);/*sprintf(char *a,const char *format,[argument]...);buffer是char类型的指针,指向写入的字符串指针;format为格式化字符串,即在程序中想要的格式;argument可选参数,可以为任意类型的数据*/ 
	outtextxy(370,185,str);	
} 
//消行更新
void check()
{
	int clearlines=0;//消除的行数 
	for(int i=yROCK_SQUARE_NUM-1;i>=0;i--)//从最底部的一行开始检查 
	{
		int flag=1;
		for(int j=0;j<xROCK_SQUARE_NUM;j++)	//不仅要j=xROCK_SQUARE_NUM而且要保证该行都有方块 
		{
			if(visit[i][j]==0)//判断该行是否都有方块 
			{
				flag=0;
				break;
			}
		}
		if(flag==1)		
		{
			down(i);//消除第i行 
			i++;//方块落下来有可能又是满行,再检查一遍 
			clearlines++;//消除行数 
		}
	}
	addscore(clearlines);//加分 
} 
void GameProcess()
{
	nextblock();//在右上角产生第一个方块 
	Sleep(500);//缓冲0.5秒 
	memset(visit,0,sizeof(visit));//memset(char *b,int a,count);b为指针或数组,a为将初始化为的数值,count要初始化的的长度 
	while(1)//游戏过程 
	{
		newblock();//方块的移动与堆积过程 
		check();//消行得分 
	}
}
void GameEnd()
{
		setcolor(RED);
		setfont(40,0,"宋体");
		outtextxy(60,150,"GAME OVER!");
		delay_ms(2000);
		 
		initgraph(WINDOW_WIDTH,WINDOW_HIGH,0);//更新窗口,用cleardevice()有时清理不了 
		char str[10];
		
		setcolor(WHITE);
		setfont(20,0,"宋体");
		outtextxy(100,170,"你的分数是:");
	
		sprintf(str,"%d",score);
		outtextxy(220,170,str);
		outtextxy(100,200,"重新开始请按'0''");
		outtextxy(100,230,"退出游戏请按'Esc'");
		while(1)
		{
			if(kbhit())
			{
				int key=getch();
				if(key==KEY_Zero)//重新开始 
				{
					Inter_Face();//初始化界面 
					Inter_GameFace();//初始化游戏界面 
					GameProcess();
				}
				else if(key==KEY_Leave)
				{
					closegraph();
					exit(0);			//正常退出游戏 
				}
				else
					outtextxy(100,290,"请重新输入"); 
			}
		}	
}

3、主函数

int main(){
	Inter_Face();//初始化界面 
	Inter_GameFace();//初始化游戏界面 
	GameProcess();//游戏过程 
	closegraph(); 
	return 0;
}