当前位置: 代码迷 >> 综合 >> 俄罗斯方块。
  详细解决方案

俄罗斯方块。

热度:38   发布时间:2024-02-05 13:23:50.0

目录

  • 零. 直接上代码
    • 代码
    • 运行截图
  • 一. 基本的游戏知识
    • 1.1 下面是这7种基本方块:
    • 1.2.游戏规则:
  • 二. 思路
    • 2.1游戏大体思路
    • 2.2 main()函数主要结构
  • 三. 编译环境:
  • 四. 关于一些函数的解释
    • 4.1关于控制台、游戏界面、颜色、光标设置、菜单函数
      • 4.1.1 setTitle函数
      • 4.1.2 setColor函数
      • 4.1.3 setPos函数
      • 4.1.4 setGametable函数
      • 4.1.5 showMenu函数
    • 4.2创建立方块、移动方块、变化方块的函数
      • 4.2.1 moveBlock函数
      • 4.2.2 print_Block函数
      • 4.2.3 SetLinkAndGetXY函数
      • 4.2.4 SwapBlock函数
    • 4.3 停止判断、死亡判断函数
      • 4.3.1 isDie函数
      • 4.3.2 isStop函数
    • 4.4 消除方块的函数
      • 4.4.1 isClean 函数
      • 4.4.2 CleanLine函数
      • 4.4.3 Down函数
  • 五. 优缺点分析
  • 六. 感受

零. 直接上代码

代码

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