俄罗斯方块编写思路及源码
顾名思义,俄罗斯方块自然是俄罗斯人发明的。这位牛人叫做阿列克谢·帕基特诺夫(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一大堆呀。
回头重看代码,确实很难看懂。就重新整理了一下,修改了一些变量、函数名称,添加了部分注释,增加一些代码可读性。