俄罗斯方块。

零. 直接上代码

代码

/*2020.3.14*/
/*C语言俄罗斯方块*/
/*作者:Tjohn9*/
#include<stdio.h>
#include<stdlib.h>//要使用srand(time(NULL)) ;与rand()函数; 
#include<conio.h>//光标、getch()等 
#include<windows.h>// 控制台
#include<time.h>//srand(time(NULL)) ;
//宏定义游戏 
#define WIDE 26//有26个横着的方块组成横墙,实际占52个横坐标的间距。 
#define HIGH 30 
#define OUTLINE WIDE*2+3 
#define Startx (WIDE/2-1)*2//方块在最上面形成时的横坐标 
#define Starty 1 //方块在最上面形成时的纵坐标 

/*定义方块结构体。*/
struct block//总共有19种类方块。7种基本类型。按上键,切换flag就切换方块。
{
	int X[5];//   4个小方块的横坐标 
	int Y[5];//  4个小方快的纵坐标	
	int flag;//标号。 
	int nextblockflag;
}; 

/*全局变量 */
int i,j,k,flag=12,lastflag;//flag是现在时刻方块标志,lastflag是用来储存变化之前的方块的标志。 
int key,score=0;//key在switch中起作用,score记录得分情况。 
struct block b[19];//共19种俄罗斯方块,每种方块用一个结构体封装起来。 
struct block *pb[19];//结构体指针,方标操作。 
int maparr[26][30]={0};
/* 关于控制台界面大小、 游戏界面大小、 maparr[][]的一些说明。 
 
1.控制台界面(黑框框)大小: system("mode con cols=100 lines=40");

2.打印的游戏界面(四周围墙)大小: [WIDE*2][HIGH] 打印出的每个方块■占据两个字符的宽度,占据一个字符的高度。
(0~1,0)是左上角的方块 (0~1,30)是左下角的方块 (50~51,0)是右上角的方块  (50~51,30)是右下角的方块。当然,打印的时候只需要关注横坐标:0 2 4 6 8...就是了 

3.起标记作用的游戏地图maparr大小: maparr[26][30]; 
maparr的第一维度(坐标x): 有24个位置(不算上游戏界面的左右墙壁) 或 有26个位置(算上游戏界面的左右墙壁)  
第二维度(坐标y)有28个位置 (不算上游戏界面的上下墙壁) 或 有30个位置(算上游戏界面的上下墙壁)。

4.游戏方块界面中左墙壁占的:0~1  右墙壁:50~51  
maparr[][]的下标(0,0) (1,1)表示地图坐标对。游戏地图 与 游戏方块界面的关系:  maparr[][]的x==pb[flag]->X[i]/2 而 maparr[][]的y==pb[flag]->Y[i]。 
比如一个最小的单元方块 在游戏界面的坐标是 (16,8) 那么对应在maparr[][]中,对应的下标就是(16/2,8); 

*/

/*设置控制台大小*/
void setTitle()
{
	system("mode con cols=100 lines=40");
	SetConsoleTitle("2020俄罗斯方块"); 
}

/*控制光标位置,将光标移到位置(x,y)*/ 
void setPos(int x,int y)
{
	HANDLE a;
	a=GetStdHandle(STD_OUTPUT_HANDLE);
	COORD b={x,y};
	SetConsoleCursorPosition(a,b);
	 
}

/*设置游戏界面大小*/ 
void setGametable()
{
	 for(i=0;i<HIGH;i++)
	 {
		if(i==0||i==HIGH-1)
		{
			for(j=0;j<WIDE*2;j+=2)//j<52 50~51为最右边的小方块的坐标,踩的是50,占位50,51。 
			{						
				maparr[j/2][i]=1; //墙壁设为 1 
				setPos(j,i);
				printf("卍"); 
			}	
		} 	
		else
		{	
			for(k=0;k<WIDE*2;k+=2)
			{
				if(k==0 || k==50)
				{
				maparr[k/2][i]=1;
				setPos(k,i);
				printf("卍"); 
				}
				
			}
		}
	} 
}

/*设置控制台颜色*/ 
void setColor(int colorchoose)//颜色:10,12,13,14,8都可以试一下。 
{
	HANDLE winhandle;
	winhandle=GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(winhandle,colorchoose);
	
}

/*显示菜单在右边的值*/ 
void showMenu()
{
	setColor(12);
	setPos(OUTLINE,2);
	printf("欢迎来到俄罗斯方块!");
	setPos(OUTLINE,6);
	printf("按↑改变方块的形状:");
	setPos(OUTLINE,10);
	printf("按↓加快方块的下落速度:\n");
	setPos(OUTLINE,14);
	printf("按← →使方块左右移动:\n");
	setPos(OUTLINE,18);
	printf("按空格暂停游戏,再按一下空格键继续游戏");
	setPos(OUTLINE,22);
	printf("得分:%d",score);
	 
}

/*建立19种大方块内部小方块的联系,(x,y)为大方块最上面的主要小方块位置坐标,以它为参考系,可以写出剩下的小方块的坐标*/
void SetLinkAndGetXY(int x,int y)
{
	
    //只要给出一个方块,就能根据它的位置敲出其他方块。 
    //开口向上的T 
    pb[0]->X[0]=x;  pb[0]->Y[0]=y; 
	pb[0]->X[1]=x ; pb[0]->Y[1]=y+1;
	pb[0]->X[2]=x-2; pb[0]->Y[2]=y+1;
	pb[0]->X[3]=x+2; pb[0]->Y[3]=y+1;  
    
    // 正着的T ,开口向下的T 
	pb[1]->X[0]=x;pb[1]->Y[0]=y;
	pb[1]->X[1]=x-2;pb[1]->Y[1]=y;
	pb[1]->X[2]=x+2;pb[1]->Y[2]=y;
	pb[1]->X[3]=x; pb[1]->Y[3]=y+1;
	
	//开口向右T
	pb[2]->X[0]=x; pb[2]->Y[0]=y; 
	pb[2]->X[1]=x;pb[2]->Y[1]=y+1;
	pb[2]->X[2]=x+2;pb[2]->Y[2]=y+1;
	pb[2]->X[3]=x; pb[2]->Y[3]=y+2; 
	
	//开口向左的T
	pb[3]->X[0]=x; pb[3]->Y[0]=y; 
	pb[3]->X[1]=x;pb[3]->Y[1]=y+1;
	pb[3]->X[2]=x-2;pb[3]->Y[2]=y+1;
	pb[3]->X[3]=x; pb[3]->Y[3]=y+2; 
	//L1
	pb[4]->X[0]=x; pb[4]->Y[0]=y; 
	pb[4]->X[1]=x;pb[4]->Y[1]=y+1;
	pb[4]->X[2]=x-2;pb[4]->Y[2]=y+1;
	pb[4]->X[3]=x-4; pb[4]->Y[3]=y+1; 
	
	pb[5]->X[0]=x; pb[5]->Y[0]=y; 
	pb[5]->X[1]=x;pb[5]->Y[1]=y+1;
	pb[5]->X[2]=x;pb[5]->Y[2]=y+2;
	pb[5]->X[3]=x+2; pb[5]->Y[3]=y+2; 
	
	pb[6]->X[0]=x; pb[6]->Y[0]=y; 
	pb[6]->X[1]=x;pb[6]->Y[1]=y+1;
	pb[6]->X[2]=x+2;pb[6]->Y[2]=y;
	pb[6]->X[3]=x+4; pb[6]->Y[3]=y; 
	
	pb[7]->X[0]=x; pb[7]->Y[0]=y; 
	pb[7]->X[1]=x-2;pb[7]->Y[1]=y;
	pb[7]->X[2]=x;pb[7]->Y[2]=y+1;
	pb[7]->X[3]=x; pb[7]->Y[3]=y+2; 
	
	//L2
	pb[8]->X[0]=x; pb[8]->Y[0]=y; 
	pb[8]->X[1]=x+2;pb[8]->Y[1]=y+1;
	pb[8]->X[2]=x;pb[8]->Y[2]=y+1;
	pb[8]->X[3]=x+4; pb[8]->Y[3]=y+1;  
	
	pb[9]->X[0]=x; pb[9]->Y[0]=y; 
	pb[9]->X[1]=x+2;pb[9]->Y[1]=y;
	pb[9]->X[2]=x;pb[9]->Y[2]=y+1;
	pb[9]->X[3]=x; pb[9]->Y[3]=y+2; 
	
	pb[10]->X[0]=x; pb[10]->Y[0]=y; 
	pb[10]->X[1]=x+2;pb[10]->Y[1]=y;
	pb[10]->X[2]=x+4;pb[10]->Y[2]=y;
	pb[10]->X[3]=x+4; pb[10]->Y[3]=y+1; 
	
	pb[11]->X[0]=x; pb[11]->Y[0]=y; 
	pb[11]->X[1]=x;pb[11]->Y[1]=y+1;
	pb[11]->X[2]=x-2;pb[11]->Y[2]=y+2;
	pb[11]->X[3]=x; pb[11]->Y[3]=y+2; 
	
	//横条
	pb[12]->X[0]=x; pb[12]->Y[0]=y; 
	pb[12]->X[1]=x-2;pb[12]->Y[1]=y;
	pb[12]->X[2]=x+2;pb[12]->Y[2]=y;
	pb[12]->X[3]=x+4; pb[12]->Y[3]=y;
	
	pb[13]->X[0]=x; pb[13]->Y[0]=y; 
	pb[13]->X[1]=x;pb[13]->Y[1]=y+1;
	pb[13]->X[2]=x;pb[13]->Y[2]=y+2;
	pb[13]->X[3]=x; pb[13]->Y[3]=y+3; 
	//Z1 
	pb[14]->X[0]=x; pb[14]->Y[0]=y; 
	pb[14]->X[1]=x;pb[14]->Y[1]=y+1;
	pb[14]->X[2]=x+2;pb[14]->Y[2]=y+1;
	pb[14]->X[3]=x+2; pb[14]->Y[3]=y+2;
	
	pb[15]->X[0]=x; pb[15]->Y[0]=y; 
	pb[15]->X[1]=x+2;pb[15]->Y[1]=y;
	pb[15]->X[2]=x;pb[15]->Y[2]=y+1;
	pb[15]->X[3]=x-2; pb[15]->Y[3]=y+1;
	//Z2
	pb[16]->X[0]=x; pb[16]->Y[0]=y; 
	pb[16]->X[1]=x;pb[16]->Y[1]=y+1;
	pb[16]->X[2]=x-2;pb[16]->Y[2]=y+1;
	pb[16]->X[3]=x-2; pb[16]->Y[3]=y+2;
	
	pb[17]->X[0]=x; pb[17]->Y[0]=y; 
	pb[17]->X[1]=x-2;pb[17]->Y[1]=y;
	pb[17]->X[2]=x;pb[17]->Y[2]=y+1;
	pb[17]->X[3]=x+2; pb[17]->Y[3]=y+1;
	//最后一个大方块 
	pb[18]->X[0]=x; pb[18]->Y[0]=y; 
	pb[18]->X[1]=x-2;pb[18]->Y[1]=y;
	pb[18]->X[2]=x;pb[18]->Y[2]=y+1;
	pb[18]->X[3]=x-2; pb[18]->Y[3]=y+1;
}

/*打印出一个完整的大方块*/ 
void print_Block()//有了坐标,就可以打印。 
{
                                   //只要给出一个方块,就能根据它的位置敲出其他方块。 
    setColor(10);
	for(i=0;i<4;i++)
	{
	setPos(pb[flag]->X[i],pb[flag]->Y[i]);
	printf("■");
	}
}

/*让方块移动,就是把原来的大方块整体往下移,然后打印出下移的方块,当然下移之前自然要把原来位置的方块给清了*/ 
void moveBlock()//先把原来的方块清了,然后坐标变换,再打印。 
{
	
	for(i=0;i<4;i++)//先清方块,少了两个空格还不行! 
	{
		setPos(pb[flag]->X[i],pb[flag]->Y[i]);
		printf("  ");
	} 

	for(i=0;i<4;i++)//改变纵坐标位置 
	{	
	pb[flag]->Y[i]+=1;
	}
	print_Block();//清了以后,坐标改变以后再打印出来 
}

/*按上键切换方块*/
void SwapBlock()
{
	
	for(i=0;i<4;i++)//先把原来的方块清了 
	{
		setPos(pb[lastflag]->X[i],pb[lastflag]->Y[i]);//把之前的方块位置打印出来的东西清除掉。 
		printf("  ");
	} 
	SetLinkAndGetXY(pb[lastflag]->X[0],pb[lastflag]->Y[0]);
	print_Block();
}

/*判断方块是否停止*/ 
int isStop()
{
	
	for(i=0;i<4;i++)
	{
		if(pb[flag]->Y[i]==HIGH-2 || maparr[pb[flag]->X[i]/2][pb[flag]->Y[i]+1]==3)//HIGH-2表示方块到最下边墙上方,3标志是已经停下来的方块,标记在maparr[][]中 
		{
			return 1;
		}
	}
	return 0;
}
/*方块排满了一排之后的消掉方块并且使上面的方块下移的 3个函数*/ 
void Down(int y)//使方块下移 
{
	int i,j,k;
	for(j=y;j>=2;j--)//(自下而上) 
	{
		for(i=1;i<=24;i++)
		{
			setPos(i*2,j);
	     	printf("  ");
		
			if(maparr[i][j]==3)
			{
				maparr[i][j]=0;
				maparr[i][j+1]=3;
				setColor(10); 
				setPos(i*2,j+1); 
				printf("■");	
			}
		}
	}	
} 
void CleanLine(int y)//清除某一行 
{
	int i;
	for(i=2;i<=48;i+=2)
	{
		maparr[i/2][y]=0;
	 	setPos(i,y);
	 	printf("  ");
	 	
	}
	Down(y);
}
void isClean()//自上而下判断是否有清理的行。 
{
	int i,j; 
	int flag;
	
	for(j=2;j<=28;j++)//列 
	{
		flag=1;
		for(i=1;i<=24;i++)//横排 
		{
			if(maparr[i][j]!=3)
			{
				flag=0;
			    break;
			}
		}
		if(flag==1)
		{
			CleanLine(j); 
			score+=100;//清完一排加100分 
		}
	}
	
	
}
/*判断死亡*/ 
void isDie()
{
	int i,j,flag=0;
	for(i=1;i<=12;i++)
	 for(j=1;j<=2;j++)
	 {
		if(maparr[i][j]==3)
		{
			system("cls");
			setPos(WIDE+2,HIGH/2);
			setColor(13); 
			printf("游戏结束\n");
			setPos(WIDE+2,HIGH/2+2);
			printf("你的游戏分数:%d 分",score); 
			setPos(WIDE+2,HIGH/2+4);
			printf("相信你还可以得更高的分,加油,奥里给!\n\n\n\n\n\n\n\n\n"); 
			exit(0); 
		} 	
	}	
	
} 

/*游戏开始*/
void gameStart()
{
	setColor(9);
	setTitle();
	setGametable();
	showMenu(); 
	key=-1; 
	//标记maparr所有非墙的部分为0. 
	for(i=1;i<=24;i+=1) 
	 for(j=1;j<=28;j++)
	  maparr[i][j]=0; 
	for(i=0;i<19;i++)// 结构体指针数组装上内容并且为切换方块做了准备。 
	{
		pb[i]=&b[i];
		pb[i]->flag=i;
		 if(i==3||i==7||i==11)//四种形态的方块边界 
		 {
		 	pb[i]->nextblockflag=i-3;
		 }
		 else if(i==13||i==15||i==17)//两种形态的方块 边界 
		 {
		 	pb[i]->nextblockflag=i-1;
		 }
		 else if(i==18)//一种形态的方块边界 
		 {
		 	pb[i]->nextblockflag=i; 
		 } 
		 else pb[i]->nextblockflag=i+1;
	}
	//建立大方块内部小方块的联系 和 让方块位于最上方。 
	SetLinkAndGetXY(Startx,Starty); 
}
int main()
{
	gameStart(); 	
	/*while循环是方块下降的主要结构*/ 
	while(1)
	{
		int wallflag=0;
		j=0;//与按下键有关。 
	    if(kbhit())//这个函数判断用户是否有键入,比如说上下左右,有的话就操作。
	    {
	    	key=getch();
	    	key=getch();
	       	switch(key) 
        	{	
	  case 77://向右移,如果遇到了墙 或者 已有停下来的方块,那么就不要右移了,穿墙或穿自己的同伴的方块是个Bug。 
	  		  for(i=0;i<4;i++)
	  	     {
	  	     	//问maparr[][]第一维度和第二维度表示是什么?pb[flag]->X[i]/2+1    pb[flag]->Y[i]  pb[flag]->X[i]/2+1  又表示什么? 
	  	     	if(maparr[pb[flag]->X[i]/2+1][pb[flag]->Y[i]]==3 || maparr[pb[flag]->X[i]/2+1][pb[flag]->Y[i]]==1)//1是墙,3是停止方块所在的位置。 
				   {
				   	 wallflag=1;
				   	 break;
				   } 
			    /*答: 
			    > maparr[][]的第一维度表示整个游戏地图每个位置的横坐标  第二维度表示整个游戏地图每个位置的纵坐标 用maparr[][]作用主要是用来标记以便阻止穿墙、方便消除满了的方块。 
				> flag表示的是哪块方块在下落;  X[i] 与 Y[i]表示大方块中每个小方块的位置坐标; (pb[flag]->X[i],pb[flag]->Y[i])表示某个代号为flag的大方块某个最小方块在地图中位置定位。 
				> maparr[][]第二维度 与 pb[flag]->Y[i]是一一对应的关系而maparr[][]第一维度 与 pb[flag]->X[i]的关系是二倍关系,因为一个汉字打出来的小方块■占的是两个英文字符宽度,占一个字符高度。
				 即 pb[flag]->X[i]等于14 <=> maparr横坐标=7 ,pb[flag]->X[i]等于18 <=> maparr横坐标=9 , pb[flag]->Y[i]等于3时maparr纵坐标也是3  
				 注:每个小方块的横坐标是0~1 2~3所以pb[flag]->X[i]只取偶数(0、2、4、6...)。 
				> pb[flag]->X[i]/2+1的"+1"是因为判断方块右边的情况,下面情况是左移动自然是 pb[flag]->X[i]/2-1。 
				*/
					
			 }
			 if(wallflag==1)
			 {
			 	break;
			 }
			/*将整个方块的每一个小方块的横纵坐标都向右移,把原来的方块刷掉,然后再把方块打印出来*/
	  		for(i=0;i<4;i++)
			 {			 	 
			 	setPos(pb[flag]->X[i],pb[flag]->Y[i]); 
			 	printf("  ");
			  	pb[flag]->X[i]+=2;
			  	
			 } 	
			 print_Block();
			 break;
			 
	  case 75://左移,如果遇到了墙 或者 已有停下来的方块,那么就不要左移了,穿墙或穿自己的同伴的方块是个Bug。 
	  		 for(i=0;i<4;i++)
	  	     {
	  	     	
	  	     	if(maparr[pb[flag]->X[i]/2-1][pb[flag]->Y[i]]==3 || maparr[pb[flag]->X[i]/2-1][pb[flag]->Y[i]]==1)//1是墙,3是停止方块所在的位置。 
				   {
				   	 wallflag=1;
				   	 break;
					} 
					
			 }
			 if(wallflag==1)
			 {
			 	break;
			 }
	  	     for(i=0;i<4;i++)
	  	     {
	  	     	setPos(pb[flag]->X[i],pb[flag]->Y[i]); 
	  	     	printf("  "); 
	  	    	pb[flag]->X[i]-=2;
					
			 }
			 print_Block();
			 break;
		case 72://上键,修改方块的样式。 
			 for(i=0;i<4;i++)
			 {
			 	SetLinkAndGetXY(pb[flag]->X[0],pb[flag]->Y[0]);//就地建立下一种大方块的联系,桥梁是小方块的坐标(pb[flag]->X[0],pb[flag]->Y[0]). 
			    int nextflag=pb[flag]->nextblockflag;
			 	/*如果变换方块的时候 窗墙或者穿队友,那么就是个BUG*/
			 	if(maparr[pb[nextflag]->X[i]/2][pb[nextflag]->Y[i]]==1 || maparr[pb[nextflag]->X[i]/2][pb[nextflag]->Y[i]]==3)
			 	{
				   	 wallflag=1;
				   	 break;
				} 
			 }
			 if(wallflag==1)
			 {
			 	break;
			 }
			 lastflag=flag;//lastflag用途是在SwapBlock()中清楚掉以前的方块。 
	 		 flag=pb[flag]->nextblockflag; 
	 		 SwapBlock();
	 	case 80://下键,加速跑 
		 		j=1;
				Sleep(20); 
				break; 
			 }//end of whitch 
			 
			 /*解决连续按左右键会有延迟的现象*/ 
			if(key==77 || key==75)
			{
				while(kbhit())
				{
					getch();
					getch();
				}
			}
		} //end of if( kbhit() ) 
		
		/*自动判断一下是否有满排的现象,有的话就把它清理了,方块整体下落,然后加分数。*/ 
		isClean();
		/*判断大方块是否停下*/
		if(isStop()==1)
		{
			for(i=0;i<4;i++)
			{
				maparr[pb[flag]->X[i]/2][pb[flag]->Y[i]]=3;//方块自己本身的位置,标记在地图中。 
			}
			SetLinkAndGetXY(Startx,Starty);//从头开落下 
			srand(time(NULL));
	    	flag=rand() % 19+0;   //【0,18】 19+0  	 		
		} 
		moveBlock();
		isDie();
		if(j==1) Sleep(20);
		else Sleep(400);
		showMenu();//刷新一下菜单。		
	}//end of while
		
	return 0;	
}  

运行截图

在这里插入图片描述
在这里插入图片描述

一. 基本的游戏知识

俄罗斯方块总共有19种方块,每一种方块都是由4个小格子组成。但其实这19种方块是由7种基本方块通过变形得到的。

1.1 下面是这7种基本方块:

在这里插入图片描述
在这里插入图片描述

1.2.游戏规则:

1、在一个行高为30,列宽为26的矩形中,随机方块从上方以一定速率下落;
2、其下落过程中可通过按键使它左右移动、变形;
3、方块下落至矩阵底部或者下部与其他方块接触则固定该方块的位置,并生成新的方块重复步骤2;
4、如果矩阵中的某一行都是方块,则消去该行的方块,并使上面的方块下沉一行;
5、如果方块的堆积高度超过30,则游戏结束。

二. 思路

2.1游戏大体思路

  • 1.19种方块构成:19个结构体,其中只要知到一个主要的小方块的坐标就可以推出另外部分的坐标。

  • 方块向下移动:while循环 结合 大方块的所有小方块纵坐标+1,然后打印■ 和打印空格。

  • 方块停止与消掉:用一个额外的maparr[][]数组记录已经固定下来的方块位置,标记为2,如果遍历maparr数组一排满了,就消掉,使maparr数组中所有的值往下移,清空所有方块,然后打印maparr数组标记的位置。

2.2 main()函数主要结构

int main()
{
   gameStart(); //打印一些基本的组件(设置控制台大小、打印游戏界面等)
    
   while(1)//while循环是方块下降的主要结构
   {   
       ... 
       
       if(kbhit())//这个函数判断用户是否有键入,比如说上下左右,有的话就操作。
       {
           ...
           switch(key)
           ...
       } //end of if( kbhit() ) 
       
       isClean();//*自动判断一下是否有满排方块的现象,有的话就把它清理了,方块整体下落,然后加分数。
         
       if(isStop()==1)//判断大方块是否停下
       {    
           ...    
       } 
       
       moveBlock();//向下自动移动方块。 
       
       isDie();//判断一下是否死亡。 
       
       showMenu();//刷新一下菜单。 
       
       Sleep(20);//降低while的循环速度,以便方块慢慢下降。  
            
   }//end of while
         
   return 0;   
}  

三. 编译环境:

DEV-C++

四. 关于一些函数的解释

4.1关于控制台、游戏界面、颜色、光标设置、菜单函数

4.1.1 setTitle函数

/*设置控制台大小*/
void setTitle()
{
	system("mode con cols=100 lines=40");
	SetConsoleTitle("2020俄罗斯方块"); 
}

system(“mode con cols= lines = ”);是设置控制台长度与宽度。system需要包含头文件stdlib SetConsoleTitle 设置控制台的标题,用法依葫芦画瓢。

4.1.2 setColor函数

/*设置控制台颜色*/ 
void setColor(int colorchoose)//颜色:10,12,13,14,8都可以试一下。 
{
	HANDLE winhandle;
	winhandle=GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(winhandle,colorchoose);
	
}

不同的数字,可以有不同的颜色。

4.1.3 setPos函数

/*控制光标位置,将光标移到位置(x,y)*/ 
void setPos(int x,int y)
{
	HANDLE a;
	a=GetStdHandle(STD_OUTPUT_HANDLE);
	COORD b={x,y};
	SetConsoleCursorPosition(a,b);
	 
}

COORD实际上是一个C语言内部做好的结构体,结构体中只包含两个元素,x和y,这里的x、y就是代表着光标移动的位置。 SetConsoleCursorPosition(句柄,坐标),就将光标移到指定坐标(x,y)。

4.1.4 setGametable函数

/*设置游戏界面大小*/ 
void setGametable()
{
	 for(i=0;i<HIGH;i++)
	 {
		if(i==0||i==HIGH-1)
		{
			for(j=0;j<WIDE*2;j+=2)//j<52 50~51为最右边的小方块的坐标,踩的是50,占位50,51。 
			{						
				maparr[j/2][i]=1; //墙壁设为 1 
				setPos(j,i);
				printf("卍"); 
			}	
		} 	
		else
		{	
			for(k=0;k<WIDE*2;k+=2)
			{
				if(k==0 || k==50)
				{
				maparr[k/2][i]=1;
				setPos(k,i);
				printf("卍"); 
				}
				
			}
		}
	} 
}

打印墙思路:外循环从上往下,内循环从左往右,如果是第一排或者最后一排,左到右全部打印,否则只打印最左和最右。
开始宏定义的 宽度WIDE=26,有26个横着的方块组成横墙,实际占52个横坐标的间距。
每排第一个方块卍 占宽度0~1,第26个方块卍占 50~51
开始宏定义的 高度HIGH=30,每列第一个方块卍占0,最后一个卍占29。
maparr[][]是用来记录游戏界面上每个位置的坐标的。
maparr[][]的下标(0,0)、(1,1)等表示游戏界面的坐标,对应关系maparr[][]的x== 每个汉字打出来的方块的横坐标/2而y== 汉字打出来的方块的纵坐标。
先把每个墙壁标记为1,方便以后方块停止或者不穿左右两个墙。

4.1.5 showMenu函数

/*显示菜单在右边的值*/ 
void showMenu()
{
	setColor(12);
	setPos(OUTLINE,2);
	printf("欢迎来到俄罗斯方块!");
	setPos(OUTLINE,6);
	printf("按↑改变方块的形状:");
	setPos(OUTLINE,10);
	printf("按↓加快方块的下落速度:\n");
	setPos(OUTLINE,14);
	printf("按← →使方块左右移动:\n");
	setPos(OUTLINE,18);
	printf("按空格暂停游戏,再按一下空格键继续游戏");
	setPos(OUTLINE,22);
	printf("得分:%d",score);
	 
}

该函数作用就是在游戏界面(方块围成的墙)右边显示信息。

4.2创建立方块、移动方块、变化方块的函数

4.2.1 moveBlock函数

/*让方块移动,就是把原来的大方块整体往下移,然后打印出下移的方块,当然下移之前自然要把原来位置的方块给清了*/ 
void moveBlock()//先把原来的方块清了,然后坐标变换,再打印。 
{
	
	for(i=0;i<4;i++)//先清方块,少了两个空格还不行! 
	{
		setPos(pb[flag]->X[i],pb[flag]->Y[i]);
		printf("  ");
	} 

	for(i=0;i<4;i++)//改变纵坐标位置 
	{	
	pb[flag]->Y[i]+=1;
	}
	print_Block();//清了以后,坐标改变以后再打印出来 
}

把现在已经打印出来的方块擦了,再更新方块的坐标(纵坐标+1),再打印出来。

4.2.2 print_Block函数

/*打印出一个完整的大方块*/ 
void print_Block()//有了坐标,就可以打印。 
{
                                   //只要给出一个方块,就能根据它的位置敲出其他方块。 
    setColor(10);
	for(i=0;i<4;i++)
	{
	setPos(pb[flag]->X[i],pb[flag]->Y[i]);
	printf("■");
	}
}

主要是依次遍历大方块的四个小方块,一边遍历,一边把光标指向小方块的坐标并且然后打印方块。

4.2.3 SetLinkAndGetXY函数

void SetLinkAndGetXY(int x,int y)
{
	
    //只要给出一个方块,就能根据它的位置敲出其他方块。 
    //开口向上的T 
    pb[0]->X[0]=x;  pb[0]->Y[0]=y; 
	pb[0]->X[1]=x ; pb[0]->Y[1]=y+1;
	pb[0]->X[2]=x-2; pb[0]->Y[2]=y+1;
	pb[0]->X[3]=x+2; pb[0]->Y[3]=y+1;  
    
    // 正着的T ,开口向下的T 
	pb[1]->X[0]=x;pb[1]->Y[0]=y;
	pb[1]->X[1]=x-2;pb[1]->Y[1]=y;
	pb[1]->X[2]=x+2;pb[1]->Y[2]=y;
	pb[1]->X[3]=x; pb[1]->Y[3]=y+1;
	
	//开口向右T
	pb[2]->X[0]=x; pb[2]->Y[0]=y; 
	pb[2]->X[1]=x;pb[2]->Y[1]=y+1;
	pb[2]->X[2]=x+2;pb[2]->Y[2]=y+1;
	pb[2]->X[3]=x; pb[2]->Y[3]=y+2; 
	
	//开口向左的T
	pb[3]->X[0]=x; pb[3]->Y[0]=y; 
	pb[3]->X[1]=x;pb[3]->Y[1]=y+1;
	pb[3]->X[2]=x-2;pb[3]->Y[2]=y+1;
	pb[3]->X[3]=x; pb[3]->Y[3]=y+2; 
	//L1
	pb[4]->X[0]=x; pb[4]->Y[0]=y; 
	pb[4]->X[1]=x;pb[4]->Y[1]=y+1;
	pb[4]->X[2]=x-2;pb[4]->Y[2]=y+1;
	pb[4]->X[3]=x-4; pb[4]->Y[3]=y+1; 
	
	pb[5]->X[0]=x; pb[5]->Y[0]=y; 
	pb[5]->X[1]=x;pb[5]->Y[1]=y+1;
	pb[5]->X[2]=x;pb[5]->Y[2]=y+2;
	pb[5]->X[3]=x+2; pb[5]->Y[3]=y+2; 
	
	pb[6]->X[0]=x; pb[6]->Y[0]=y; 
	pb[6]->X[1]=x;pb[6]->Y[1]=y+1;
	pb[6]->X[2]=x+2;pb[6]->Y[2]=y;
	pb[6]->X[3]=x+4; pb[6]->Y[3]=y; 
	
	pb[7]->X[0]=x; pb[7]->Y[0]=y; 
	pb[7]->X[1]=x-2;pb[7]->Y[1]=y;
	pb[7]->X[2]=x;pb[7]->Y[2]=y+1;
	pb[7]->X[3]=x; pb[7]->Y[3]=y+2; 
	
	//L2
	pb[8]->X[0]=x; pb[8]->Y[0]=y; 
	pb[8]->X[1]=x+2;pb[8]->Y[1]=y+1;
	pb[8]->X[2]=x;pb[8]->Y[2]=y+1;
	pb[8]->X[3]=x+4; pb[8]->Y[3]=y+1;  
	
	pb[9]->X[0]=x; pb[9]->Y[0]=y; 
	pb[9]->X[1]=x+2;pb[9]->Y[1]=y;
	pb[9]->X[2]=x;pb[9]->Y[2]=y+1;
	pb[9]->X[3]=x; pb[9]->Y[3]=y+2; 
	
	pb[10]->X[0]=x; pb[10]->Y[0]=y; 
	pb[10]->X[1]=x+2;pb[10]->Y[1]=y;
	pb[10]->X[2]=x+4;pb[10]->Y[2]=y;
	pb[10]->X[3]=x+4; pb[10]->Y[3]=y+1; 
	
	pb[11]->X[0]=x; pb[11]->Y[0]=y; 
	pb[11]->X[1]=x;pb[11]->Y[1]=y+1;
	pb[11]->X[2]=x-2;pb[11]->Y[2]=y+2;
	pb[11]->X[3]=x; pb[11]->Y[3]=y+2; 
	
	//横条
	pb[12]->X[0]=x; pb[12]->Y[0]=y; 
	pb[12]->X[1]=x-2;pb[12]->Y[1]=y;
	pb[12]->X[2]=x+2;pb[12]->Y[2]=y;
	pb[12]->X[3]=x+4; pb[12]->Y[3]=y;
	
	pb[13]->X[0]=x; pb[13]->Y[0]=y; 
	pb[13]->X[1]=x;pb[13]->Y[1]=y+1;
	pb[13]->X[2]=x;pb[13]->Y[2]=y+2;
	pb[13]->X[3]=x; pb[13]->Y[3]=y+3; 
	//Z1 
	pb[14]->X[0]=x; pb[14]->Y[0]=y; 
	pb[14]->X[1]=x;pb[14]->Y[1]=y+1;
	pb[14]->X[2]=x+2;pb[14]->Y[2]=y+1;
	pb[14]->X[3]=x+2; pb[14]->Y[3]=y+2;
	
	pb[15]->X[0]=x; pb[15]->Y[0]=y; 
	pb[15]->X[1]=x+2;pb[15]->Y[1]=y;
	pb[15]->X[2]=x;pb[15]->Y[2]=y+1;
	pb[15]->X[3]=x-2; pb[15]->Y[3]=y+1;
	//Z2
	pb[16]->X[0]=x; pb[16]->Y[0]=y; 
	pb[16]->X[1]=x;pb[16]->Y[1]=y+1;
	pb[16]->X[2]=x-2;pb[16]->Y[2]=y+1;
	pb[16]->X[3]=x-2; pb[16]->Y[3]=y+2;
	
	pb[17]->X[0]=x; pb[17]->Y[0]=y; 
	pb[17]->X[1]=x-2;pb[17]->Y[1]=y;
	pb[17]->X[2]=x;pb[17]->Y[2]=y+1;
	pb[17]->X[3]=x+2; pb[17]->Y[3]=y+1;
	//最后一个大方块 
	pb[18]->X[0]=x; pb[18]->Y[0]=y; 
	pb[18]->X[1]=x-2;pb[18]->Y[1]=y;
	pb[18]->X[2]=x;pb[18]->Y[2]=y+1;
	pb[18]->X[3]=x-2; pb[18]->Y[3]=y+1;
}

主要的小方块坐标赋成(x,y) :pb[0]->X[0]=x; pb[0]->Y[0]=y;
然后以主要小方块为参考系,给其他3个小方块附上坐标。

4.2.4 SwapBlock函数

/*按上键切换方块*/
void SwapBlock()
{
	
	for(i=0;i<4;i++)//先把原来的方块清了 
	{
		setPos(pb[lastflag]->X[i],pb[lastflag]->Y[i]);//把之前的方块位置打印出来的东西清除掉。 
		printf("  ");
	} 
	SetLinkAndGetXY(pb[lastflag]->X[0],pb[lastflag]->Y[0]);
	print_Block();
}

将原来方块(lastflag)的 (pb[0]->X[0],pb[0]->Y[0]) 传给SetLinkAndGetXY(x,y)函数,然后就可以得到新大方块所有的坐标,然后使用print_Block()函数打印就是了。还是要把已打印出的方块清理掉。

4.3 停止判断、死亡判断函数

4.3.1 isDie函数

/*判断死亡*/ 
void isDie()
{
	int i,j,flag=0;
	for(i=1;i<=12;i++)
	 for(j=1;j<=2;j++)
	 {
		if(maparr[i][j]==3)
		{
			system("cls");
			setPos(WIDE+2,HIGH/2);
			setColor(13); 
			printf("游戏结束\n");
			setPos(WIDE+2,HIGH/2+2);
			printf("你的游戏分数:%d 分",score); 
			setPos(WIDE+2,HIGH/2+4);
			printf("相信你还可以得更高的分,加油,奥里给!\n\n\n\n\n\n\n\n\n"); 
			exit(0); 
		} 	
	}		
} 

查看(通过遍历)方块是否已经到达了游戏地图(maparr)的最上面两排,如果到达了,那么就游戏结束了。

4.3.2 isStop函数

/*判断方块是否停止*/ 
int isStop()
{
	
	for(i=0;i<4;i++)
	{
		if(pb[flag]->Y[i]==HIGH-2 || maparr[pb[flag]->X[i]/2][pb[flag]->Y[i]+1]==3)//HIGH-2表示方块到最下边墙上方,3标志是已经停下来的方块,标记在maparr[][]中 
		{
			return 1;
		}
	}
	return 0;
}

遍历当前大方块的四个小方块的坐标,如果任意一个方块的坐标在最底下的墙上面 或者 坐标下面是固定的兄弟(通过maparr比较)的话,那么大方块停下来。

4.4 消除方块的函数

4.4.1 isClean 函数

void isClean()//自上而下判断是否有清理的行。 
{
	int i,j; 
	int flag;
	
	for(j=2;j<=28;j++)//列 
	{
		flag=1;
		for(i=1;i<=24;i++)//横排 
		{
			if(maparr[i][j]!=3)
			{
				flag=0;
			    break;
			}
		}
		if(flag==1)
		{
			CleanLine(j); 
			score+=100;//清完一排加100分 
		}
	}
	
	
}

在maparr[][]中自上而下地扫描每一排,如果发现有一排全是标记,那么执行CleanLine()函数否则就继续扫描,直到退出循环。

4.4.2 CleanLine函数

void CleanLine(int y)//清除某一行 
{
	int i;
	for(i=2;i<=48;i+=2)
	{
		maparr[i/2][y]=0;
	 	setPos(i,y);
	 	printf("  ");
	 	
	}
	Down(y);
}

把该擦掉地那一排用打印空格的方式擦掉,并使那一排的所有maparr标记全部为0。再执行down函数。

4.4.3 Down函数

/*方块排满了一排之后的消掉方块并且使上面的方块下移的 3个函数*/ 
void Down(int y)//使方块下移 
{
	int i,j,k;
	for(j=y;j>=2;j--)//(自下而上) 
	{
		for(i=1;i<=24;i++)
		{
			setPos(i*2,j);
	     	printf("  ");
		
			if(maparr[i][j]==3)
			{
				maparr[i][j]=0;
				maparr[i][j+1]=3;
				setColor(10); 
				setPos(i*2,j+1); 
				printf("■");	
			}
		}
	}	
} 

使该移动的方块整体往下面移动,但是在最下面的先移动。移动的是方块对应的maparr标记。

五. 优缺点分析

优点:基本上不会穿左右墙壁 和 把自己的同伴穿了,别人写的其他版本就忽略了这点。
缺点: 移动的手感有点机械;颜色单调;没有在菜单里展示下一个方块。

六. 感受

3月自己写的(花了15天左右),8月才来梳理,有点小惭愧,但是还是感觉挺棒的。
思路启发还是之前写的贪吃蛇,它们的移动原理都差不多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值