俄罗斯方块编写思路及源码
顾名思义,俄罗斯方块自然是俄罗斯人发明的。这位牛人叫做阿列克谢·帕基特诺夫(Alexey Pazhitnov) 。
俄罗斯方块的基本规则:
1、一个用于摆放小型正方形的平面虚拟场地,其标准大小:行宽为10,列高为20,以每个小正方形为单位;
2、一组由4个小型正方形组成的规则图形,共有7种,分别以S、Z、L、J、I、O、T这7个字母的形状来命名;
3、随机地输出方块到场地顶部,以一定的规则进行移动、旋转、下落和摆放,锁定并填充到场地中。每次摆放如果将场地的一行或多行完全填满,则组成这些行的所有小正方形将被消除,并且以此来换取一定的积分。而未被消除的方块会一直累积,并对后来的方块摆放造成各种影响。
4、方块移到区域最下方或是落到其他方块上无法移动时,就会固定在该处。如果未被消除的方块堆放的高度超过场地所规定的最大高度,则游戏结束。
下面我简单说一下我的思路,理解下面几点会比较清楚一些。
1,基础:你首先要能画出一个带颜色的方块。举一反三:可以画一个就可以画4个了。(TC中画方块用到了EGAVGA.BGI这个文件)
2,移动:一个方块消失,相邻地方一个方块出现,在视觉上就是移动了。
3,消失:用背景颜色在同样的地方画同样大小的方块。
4,俄罗斯方块:由四个方块组成,方块互相邻接。共有7种俄罗斯方块(S、Z、L、J、I、O、T)。
5,每种俄罗斯方块,又可以旋转,以90度旋转,则每种就有4个旋转的状态。
6,相对坐标:视觉上像素这个单位太小,用方块的大小作为相对坐标的单位。
7,随机产生:使用伪随机函数,参数一般用上系统当前时间,你再随意捏造个四则运算,就会产生出独一无二的随机数了。随机数用于产生新的俄罗斯方块的种类,以及旋转的状态。
8,按键分四种:上、下、左、右。上键:旋转当前的俄罗斯方块;下键:快速下降到底;左键:左移一格;右键:右移一格。
9,旋转:默认按顺时钟旋转。这里用到了一个坐标的转换,当方块旋转时,横坐标与纵坐标要互换,我这里是把数学中的坐标转换公式应用进来了,这是这个游戏中最有难度的部分了。
10,一行填满:一行里面,填充满小方块,此时需要进行记分,并消掉这一行。
11,记分:每消掉一行,进行记分;若同时消掉多行,记分要更多。--这是鼓励一次消多行。
12,结束条件:方块填充到了顶部。
也就这么多,理解了这些,整个框架也就出来了。
源代码如下:
#include <graphics.h>
#include <stdio.h>
#include <stdlib.h>char x,y;/*当前操作的俄罗斯方块的坐标,实际以一个俄罗斯方块的左上角的小方块的坐标表示*/
char str[16];
char num_line_full=0,num_line_old=0;/*消掉的行数,用于计分*/
char type_cur,type_new;/*俄罗斯方块共有7种,以变量type_cur表示*/
char block_status;/*无论如何转动,只有四种状态。以变量block_status来表示。*/
char screen[10][29]={0};/*表示画面内的各个格是否填充。 画面大小:横向可以放下10个方块,纵向可以放下29个方块 */
char block_unit[4][2]={0};/*表示一个俄罗斯方块,其中有4个小方块,每个小方块有坐标(x,y) */
int score1=0,speed=1000;/*speed其实是个延迟时间,可根据CPU的速度调整。更好的方式是使用别的方式进行延时*/void draw_unit(char,char,char); /* 画一个小方块 */
void dl(int); /* 进行延时 */
void draw_block(char); /* 画一个俄罗斯方块 */
void set_block_pos(char,char);/*给一个俄罗斯方块的位置赋值*/
void up(void); /* 按下上键后的操作 */
void down(void);/* 按下下键后的操作 */
void left(void);/* 按下左键后的操作 */
void right(void);/* 按下右键后的操作 */
char rotate_x(char,char,char); /* x 坐标旋转*/
char rotate_y(char,char,char);/* y 坐标旋转*/
char max1(char,char,char); /*取最大值*/
char min1(char,char,char);/*取最小值*/
void score(void);/* 计算并显示得分 */
void process_full_line(void);/*处理填满小方块的行*/
void save(void);/* 保存最高分值 */ main()
{
char cycy=0,i,j,b,ci;
initgraph(VGA,VGAHI,"c://tc//egavga.bgi");/*图形模式初始化*/
getch(); setfillstyle(1,7);/*设置填充模式*/
setcolor(7);/*设置颜色*/
bar(200,10,350,430);/*按设置的模式,画一个长方形*/
setcolor(15);rectangle(198,8,352,432);
x=4;type_cur=0;block_status=0;y=1;
do
{
set_block_pos(type_cur,block_status);/*进行位置赋值*/
draw_block(14);/*画出*/
score();/*记分*/
for(ci=0;ci<10;ci++)/*下降一格的时间里面,提供10次机会,判断是否有按键,并进行相应处理*/
{
if(kbhit()!=0)
b=getch();/*获取按键值*/
else
b=0; if(b==13)
break;
switch(b)
{
case 72: up(); break;/*上键处理*/
case 75: left(); break;/*左键处理*/
case 77: right(); break;/*右键处理*/
case 80: down(); break;/*下键处理*/
}
dl(speed/2);/*通过延时控制速度*/
} j=max1(block_unit[3][1],block_unit[1][1],block_unit[2][1]);
if((j>=27)||(screen[block_unit[0][0]][block_unit[0][1]+1]+screen[block_unit[1][0]][block_unit[1][1]+1]+screen[block_unit[2][0]][block_unit[2][1]+1]+screen[block_unit[3][0]][block_unit[3][1]+1]>=1))/*已经触底*/
{
screen[block_unit[0][0]][block_unit[0][1]]=1;screen[block_unit[1][0]][block_unit[1][1]]=1;
screen[block_unit[2][0]][block_unit[2][1]]=1;screen[block_unit[3][0]][block_unit[3][1]]=1;
y=2;
x=5;
do/*随机产生一个新的俄罗斯方块,其形状与上一个不同*/
{
randomize();
type_new=random(100);
type_new%=7;
if(type_new==0)
type_new=7;
}
while(type_cur==type_new); type_cur=type_new;
j=0;
process_full_line();/*判断是否能消一行*/
}
else/*未触底,继续下降*/
{
draw_block(0);/*在原位置消失--准备更新位置,再画出*/
y++;
}
for(i=0;i<10;i++)/*在已经经过下降一格的时间之后,判断在顶部是否有块存在,若存在,则game over*/
if(screen[i][0]==1)
{
cycy=1;
getch();
break;
} if(cycy==1)
{
cycy=0;
break;
}
}
while(b!=13); getch();
closegraph();/* 关闭图形模式 */
save();/* 若创造新记录,保存分值 */
getch();
}void draw_unit(char x,char y,char color)/* 画一个小方块 */
{
setfillstyle(1,color);
setcolor(color);
bar(200+x*15,10+y*15,200+(x+1)*15-2,10+(y+1)*15-2);
}void draw_block(char color)/* 画一个俄罗斯方块 */
{
char i,t;
for(i=0;i<4;i++)
draw_unit(block_unit[i][0],block_unit[i][1],color);
}void dl(int a)/* 进行延时 ,更好的方式是使用别的方式进行延时,避免完全占用cpu */
{
int r,n;
for(r=0;r<a;r++)
for(n=0;n<30000;n++)
{
n++;
n--;
}
}char rotate_x(char x0,char y0,char n)/* x 坐标旋转*/
{
char x1,x2,y1,y2;
x1=y0+x-y;
y1=x+y-x0;
if(n==0)
return(x0);
if(n==1)
return(x1);
if(n>=2)
{
x2=y1+x-y;
y2=x+y-x1;
if(n==2)
return(x2);
else
{
x1=y2+x-y;
y1=x+y-x2;
if(n==3)
return(x1);
}
}
}char rotate_y(char x0,char y0,char n)/* y 坐标旋转*/
{
char x1,x2,y1,y2;
x1=y0+x-y;
y1=x+y-x0;
if(n==0)
return(y0);
if(n==1)
return(y1);
if(n>=2)
{
x2=y1+x-y;
y2=x+y-x1;
if(n==2)
return(y2);
else
{
x1=y2+x-y;
y1=x+y-x2;
if(n==3)
return(y1);
}
}
}void left()/* 按下左键后的操作 */
{
char j;
set_block_pos(type_cur,block_status);
draw_block(0);/*原位置去掉*/
j=min1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
if((j>=1)&&(screen[block_unit[0][0]-1][block_unit[0][1]]==0)&&(screen[block_unit[1][0]-1][block_unit[1][1]]==0)&&(screen[block_unit[2][0]-1][block_unit[2][1]]==0)&&(screen[block_unit[3][0]-1][block_unit[3][1]]==0))
{
j--;
x--;
} set_block_pos(type_cur,block_status);
draw_block(14);/*新位置画出*/
}void right()/* 按下右键后的操作 */
{
char j;
set_block_pos(type_cur,block_status);
draw_block(0);/*原位置去掉*/
j=max1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
if((j<9)&&(screen[block_unit[0][0]+1][block_unit[0][1]]==0)&&(screen[block_unit[1][0]+1][block_unit[1][1]]==0)&&(screen[block_unit[2][0]+1][block_unit[2][1]]==0)&&(screen[block_unit[3][0]+1][block_unit[3][1]]==0))
{
j++;
x++;
} set_block_pos(type_cur,block_status);
draw_block(14);/*新位置画出*/
}void set_block_pos(char n,char block_status)/* 给一个俄罗斯方块赋值,含4个块,每个块的坐标(x,y)*/
{
block_unit[0][0]=x;
block_unit[0][1]=y;
n=n%7;
if(n==0)
n=7;
block_status=block_status%4;
switch (n)
{
case 1: block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
block_unit[2][0]=rotate_x(x,y+1,block_status);block_unit[2][1]=rotate_y(x,y+1,block_status);
block_unit[3][0]=rotate_x(x,y+2,block_status);block_unit[3][1]=rotate_y(x,y+2,block_status);
break;
case 2: block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
block_unit[3][0]=rotate_x(x,y-2,block_status);block_unit[3][1]=rotate_y(x,y-2,block_status);
break;
case 3: block_unit[1][0]=rotate_x(x-1,y-1,block_status);block_unit[1][1]=rotate_y(x-1,y-1,block_status);
block_unit[2][0]=rotate_x(x,y+1,block_status);block_unit[2][1]=rotate_y(x,y+1,block_status);
block_unit[3][0]=rotate_x(x-1,y,block_status);block_unit[3][1]=rotate_y(x-1,y,block_status);
break;
case 4: block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
block_unit[2][0]=rotate_x(x-1,y+1,block_status);block_unit[2][1]=rotate_y(x-1,y+1,block_status);
block_unit[3][0]=rotate_x(x,y-1,block_status);block_unit[3][1]=rotate_y(x,y-1,block_status);
break;
case 5: block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
block_unit[3][0]=rotate_x(x,y+1,block_status);block_unit[3][1]=rotate_y(x,y+1,block_status);
break;
case 6: block_unit[1][0]=rotate_x(x,y+1,block_status);block_unit[1][1]=rotate_y(x,y+1,block_status);
block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
block_unit[3][0]=rotate_x(x,y-2,block_status);block_unit[3][1]=rotate_y(x,y-2,block_status);
break;
case 7: block_unit[1][0]=rotate_x(x-1,y,block_status);block_unit[1][1]=rotate_y(x-1,y,block_status);
block_unit[2][0]=rotate_x(x,y-1,block_status);block_unit[2][1]=rotate_y(x,y-1,block_status);
block_unit[3][0]=rotate_x(x-1,y-1,block_status);block_unit[3][1]=rotate_y(x-1,y-1,block_status);
break;
}
}void up()/* 按下上键后的操作 */
{
char j,k;
set_block_pos(type_cur,block_status);
draw_block(0);
block_status++;
block_status=block_status%4;
set_block_pos(type_cur,block_status);
j=min1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
k=max1(block_unit[3][0],block_unit[1][0],block_unit[2][0]);
if((k<=9)&&(j>=0))
{
if((screen[block_unit[0][0]][block_unit[0][1]]==0)&&(screen[block_unit[1][0]][block_unit[1][1]]==0)&&(screen[block_unit[2][0]][block_unit[2][1]]==0)&&(screen[block_unit[3][0]][block_unit[3][1]]==0))
draw_block(14); /* not overlap */
else
{
block_status+=3;
block_status%=4;
set_block_pos(type_cur,block_status);
draw_block(14);
}
}
else
{
block_status+=3;
block_status%=4;
set_block_pos(type_cur,block_status);
draw_block(14);
}
}void down(void)/* 按下下键后的操作 */
{
char j,cy=0;
set_block_pos(type_cur,block_status);
draw_block(0);
j=max1(block_unit[3][1],block_unit[2][1],block_unit[1][1]); while(j<28)
{
j=max1(block_unit[3][1],block_unit[1][1],block_unit[2][1]);
if((j>=27)||(screen[block_unit[0][0]][block_unit[0][1]+1]==1)||(screen[block_unit[1][0]][block_unit[1][1]+1]==1)||(screen[block_unit[2][0]][block_unit[2][1]+1]==1)||(screen[block_unit[3][0]][block_unit[3][1]+1]==1))
{
screen[block_unit[0][0]][block_unit[0][1]]=1;
screen[block_unit[1][0]][block_unit[1][1]]=1;
screen[block_unit[2][0]][block_unit[2][1]]=1;
screen[block_unit[3][0]][block_unit[3][1]]=1;
draw_block(14); /* avoid block disappeared */
process_full_line();
y=2;
x=5;
do
{
randomize();
type_new=random(100);
type_new%=7;
if(type_new==0)
type_new=7;
}
while(type_new==type_cur); /* avoid the same block appear continually */ type_cur=type_new;
j=0;
set_block_pos(type_cur,block_status);
draw_block(14);
cy=1;
}
else
{
dl(speed/4);
set_block_pos(type_cur,block_status);
draw_block(0);
y++;
}
if(cy==1)
{
cy=0;
break;
}
set_block_pos(type_cur,block_status);
draw_block(14);
}
}void process_full_line(void)/*消掉一行*/
{
char jj,y1,r,x1,y2;
for(jj=0;jj<4;jj++)/*分别判断四个小方块---似乎不需要??下面已经是整屏幕判断了*/
{
for(y1=27;y1>0;y1--)/*从底下判断到顶上。*/
{
r=0;
for(x1=0;x1<10;x1++)/*对于每一行,从左判断到右*/
{
if(screen[x1][y1]==1)/*统计已经填充的小方块的个数*/
r++;
}
if(r==10)/*一行已经填满了*/
{
num_line_full++;
for(x1=0;x1<10;x1++)
{
draw_unit(x1,y1,0);/*使该行消失*/
screen[x1][y1]=0;/*清标志*/
} for(y2=y1-1;y2>0;y2--)/*上面的所有块,整体下移一行*/
{
for(x1=0;x1<10;x1++)
{
if(screen[x1][y2]==1)
{
screen[x1][y2]=0;
draw_unit(x1,y2,0);
draw_unit(x1,y2+1,14);
screen[x1][y2+1]=1;
}
}
}
}
}
}
}char max1(char a,char b,char c)/*取最大值*/
{
char max3;
if(a>=b)
max3=a;
else
max3=b; if(max3<c)
max3=c; return(max3);
}char min1(char a,char b,char c)/*取最小值*/
{
char min;
if(a<=b)
min=a;
else
min=b;
if(min>c)
min=c; return(min);
}void score(void)/* 计算并显示得分 */
{
char a,b=1;
setcolor(0);
outtextxy(20,20,str);
setcolor(14);
for(a=0;a<num_line_full-num_line_old;a++)
b*=2;
score1+=b-1;
if(b>1)
speed-=2;
num_line_old=num_line_full;
sprintf(str,"Your score:%d.",score1);
outtextxy(20,20,str);
}void save(void)/* 保存最高分值 */
{
FILE * fp;
int i;
i=0;
fp=fopen("bloscore.txt","rb+");
i=(int)(fgetc(fp))-48;
if(fp=NULL)
i=0;
fclose(fp); if(i<score1)
{
fp=fopen("bloscore.txt","wb+");
fprintf(fp,"%d",score1);
fclose(fp);
printf("Your score: %d are the highest!",score1);
}
getch();}
编译运行需要一个文件:egavga.bgi,网络上可以下载到,我是存放在我的电脑的 c:/tc 目录下。
这个程序还不是很完善,只是实现了基本功能,第一次的开始时,需要按一下方向键,最后的退出也有一点小问题,不过作为一个小游戏玩玩还是可以的了。
有兴趣的朋友帮忙完善一下,加上了通知我一声哦。
后记:
其实现在来发布贪吃蛇与俄罗斯方块这两个小游戏的源码,已经显不出有什么特别了,因为网络上已经有多个版本了。只是在2001年那时,这两个小游戏却是我的快乐与光荣。当时才学C语言,自己估摸着可以做点东西,就开始做了。都是我一个人编写与调试,在其他人玩乐的时候,是有一点孤单的。做出来后,看别人玩自己编写的游戏,很是自豪,不过却没有人来一起讨论编程中的得失,有些遗憾。现在虽然迟了,发布出来,也是了一个心愿吧。回头看以前编写的程序,格式不够规范,思路也很粗糙,远称不上完善,但那也是我成长过程中的一个脚印啊,与大家分享一下。
这个游戏是我编写贪吃蛇之后的又一个尝试,本以为也只是几个方块的组合,类似于贪吃蛇,结果费了很大的功夫才编写成型,主要的困难在于边界的处理,需要非常仔细的考虑,否则bug一大堆呀。
回头重看代码,确实很难看懂。就重新整理了一下,修改了一些变量、函数名称,添加了部分注释,增加一些代码可读性。