俄罗斯方块。
目录
零. 直接上代码
代码
/*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月才来梳理,有点小惭愧,但是还是感觉挺棒的。
思路启发还是之前写的贪吃蛇,它们的移动原理都差不多。