欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

俄罗斯方块。

程序员文章站 2023-12-23 20:50:03
...

零. 直接上代码

代码

/*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月才来梳理,有点小惭愧,但是还是感觉挺棒的。
思路启发还是之前写的贪吃蛇,它们的移动原理都差不多。

上一篇:

下一篇: