C语言俄罗斯方块游戏解析(图文+源码)
以下文章来源于嵌入式Linux ,作者写代码的篮球球痴
最近在看俄罗斯方块的游戏,看到一个大神写的俄罗斯方块代码,非常非常优秀,拿出来解析给大家看看,也希望大家自己尝试运行试试,从中能得到一些启发。
#先了解下俄罗斯方块的几个形状
一共分成7形状,有的形状有4种状态,有的形状有1种状态。
不管是多少种状态,一个方块需要一个2个字节来存储,也就是16bit来保存一个方块的信息。
基于上面的理论,我们可以使用4x4的数组来保存方块的信息。
注意,下面代码中的"■"占用的是2个字节。
#写代码来显示这个方块
方块数据,这两个数组其实是两种游戏模式的方块信息,我们只需要分析一种就可以了。
intTGM[7][4]={{0x159D,0x89AB,0x159D,0x89AB},{0x126A,0x4856,0x159A,0x4526},{0x926A,0x456A,0x1592,0x0456},{0x4859,0x4859,0x4859,0x4859},{0x5926,0x0156,0x5926,0x0156},{0x4159,0x4596,0x1596,0x4156},{0x156A,0x4152,0x156A,0x4152}};
intSRS[7][4]={{0x159D,0x89AB,0x26AE,0x4567},{0x0159,0x4856,0x159A,0x4526},{0x8159,0x456A,0x1592,0x0456},{0x4859,0x4859,0x4859,0x4859},{0x4815,0x459A,0x5926,0x0156},{0x4159,0x4596,0x1596,0x4156},{0x0459,0x8596,0x156A,0x4152}};
我们分析这段代码
/***********擦除显示*************/
intDisplay(intx,inty,intCAC,intMode)
{
for(j=0;j=3;j++){
P[j]=CAC0xF,CAC=4;
if(Mode==1){Pos((P[j]2)+x,(P[j]0x3)+y);printf("■");}
elseif(Mode==0){Pos((P[j]2)+x,(P[j]0x3)+y);printf("");}
}
return0;
}
P[j]=CAC0xF取到的是 4个bit,然后通过判断这 4个bit决定输出方块的位置。
我们拿0这个方块的数据0x4859对应的二进制0B0100100001011001来做个例子。
0100----对应坐标(1,0)
1000----对应坐标(2,0)
0101----对应坐标(1,1)
1001----对应坐标(2,1)
通过这个坐标,我们会输出这样一个方块
再举个例子
我们拿Z这个方块的数据0x0156对应的二进制0B0000000101010110来做个例子。
0000----对应坐标(0,0)
0001----对应坐标(0,1)
0101----对应坐标(1,1)
0110----对应坐标(1,2)
显示方块没有问题了
上面的代码中,如果mode等于0的话,也就是把某个方块从显示中擦除掉。
我们用devc++写的程序,需要知道几个细节。
#构建固定行列的窗口
正常情况,我们显示cmd是一个默认值的黑框「如下图」。
实际上,我们可以用代码来固定cmd的框框大小。
默认代码输出
示例代码:
#includeconio.h
#includestdio.h
#includestdlib.h
#includetime.h
#includeWindows.h
/**********Main主函数***********/
intmain()
{
system("colorF1modeconcols=35lines=25");
getchar();
return0;
}
程序输出:
#延迟函数的设计
跟其他不同的是,这个程序的延迟没有使用usleep,我觉得这也是非常值得称赞的地方,不过空跑cpu,哈哈,是有那么一点调皮。
把延迟放到键值获取的函数中。
/**********按键获取**************/
intGetkey(intN,intT)
{
intstart=clock();
if(KEY_V==115){return115;}
do{
if(kbhit()){
KEY_V=(int)(getch());
if(KEY_V97){KEY_V+=32;}
returnKEY_V;
}
for(i=0;i=N;i++);
}while((clock()-start)T);
dy=1;
return-1;
}
就先简单讲解下这些,至于得分机制,碰撞检测,消除的代码,还没有想好怎么讲明白,需要再花时间剖析剖析。
#试玩一下
#源码
#includeconio.h
#includestdio.h
#includestdlib.h
#includetime.h
#includeWindows.h
/**********初始化参数************/
inti,j,N,T,F,J,X,Y,dx,dy,KEY_V,Cache1,Cache2,NU,NI,RU,RI,P_X,P_Y,POS_H_MAX,LEVEL=1,SCORE=0,P[4],POINT_V[12][22],MARK[21],FLAG[5]={0,0,0,1,0};
intTGM[7][4]={{0x159D,0x89AB,0x159D,0x89AB},{0x126A,0x4856,0x159A,0x4526},{0x926A,0x456A,0x1592,0x0456},{0x4859,0x4859,0x4859,0x4859},{0x5926,0x0156,0x5926,0x0156},{0x4159,0x4596,0x1596,0x4156},{0x156A,0x4152,0x156A,0x4152}};
intSRS[7][4]={{0x159D,0x89AB,0x26AE,0x4567},{0x0159,0x4856,0x159A,0x4526},{0x8159,0x456A,0x1592,0x0456},{0x4859,0x4859,0x4859,0x4859},{0x4815,0x459A,0x5926,0x0156},{0x4159,0x4596,0x1596,0x4156},{0x0459,0x8596,0x156A,0x4152}};
/**********光标位置函数**********/
voidPos(intx,inty)
{
COORDpos;
HANDLEhOutput;
pos.X=2*x;
pos.Y=y;
hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOutput,pos);
}
voidHideCursor()
{
CONSOLE_CURSOR_INFOcursor_info={1,0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),cursor_info);
}
/**********初始化界面************/
voidCreatUI()
{
inti,j,BOUNDARY;
printf("┏━━━━━━━━━━┓\n");
for(j=1;j=20;j++){
if(j==3){printf("┃┃LEVEL:1\n");}
elseif(j==5){printf("┃┃SCORE:0\n");}
elseif(j==7){printf("┃┃NEXT\n");}
else{printf("┃┃\n");}
}
printf("┗━━━━━━━━━━┛\n");
printf("CopyRight@2016~2018BYHAPPY\n");
for(j=1;j=21;j++){
for(i=0;i=11;i++){
BOUNDARY=i*(i-11)*(j-21);
if(BOUNDARY==0){
POINT_V[i][j]=1;
}else{
POINT_V[i][j]=0;
}
}
}
}
/**********按键获取**************/
intGetkey(intN,intT)
{
intstart=clock();
if(KEY_V==115){return115;}
do{
if(kbhit()){
KEY_V=(int)(getch());
if(KEY_V97){KEY_V+=32;}
returnKEY_V;
}
for(i=0;i=N;i++);
printf("%d\n",clock()-start);
}while((clock()-start)T);
dy=1;
return-1;
}
/***********块体转置*************/
intRote(intS,intI)
{
return(F==0)TGM[S][(I+4)%4]:SRS[S][(I+4)%4];
}
/***********擦除显示*************/
intDisplay(intx,inty,intCAC,intMode)
{
for(j=0;j=3;j++){
P[j]=CAC0xF,CAC=4;
if(Mode==1){Pos((P[j]2)+x,(P[j]0x3)+y);printf("■");}
elseif(Mode==0){Pos((P[j]2)+x,(P[j]0x3)+y);printf("");}
}
return0;
}
/***********固化块体*************/
intDoBlocks()
{
//~~~游戏结束
if(Y2){
Pos(1,22);printf("GAME OVER!");
exit(0);
}
//~~~固化块体
POS_H_MAX=0,FLAG[3]=1;
for(j=0;j=3;j++){
P_X=(P[j]2)+X,P_Y=(P[j]0x3)+Y;
if(POS_H_MAXP_Y){POS_H_MAX=P_Y;}
POINT_V[P_X][P_Y]=1;
}
//~~~关卡得分
for(j=Y;j=POS_H_MAX;j++){
FLAG[2]=1;
for(i=1;i=10;i++){
if(POINT_V[i][j]==0){FLAG[2]=0;}
}
if(FLAG[2]){
SCORE+=10,MARK[j]=1;
if(SCORE==400){
SCORE=0,LEVEL+=1,T-=100;
FLAG[4]=1;
}
}
}
//~~~极品消行
for(j=20;j=5;j--){
if(FLAG[4]){
for(i=1;i=10;i++){
POINT_V[i][j]=0;
Pos(i,j);printf("");
}
}
elseif(MARK[j])
{
MARK[j]=0,J=j-1;
for(N=1;N=3;N++){
if(MARK[J]){J--;}
}
MARK[J]=1;
for(i=1;i=10;i++){
Pos(i,j);
if(POINT_V[i][j]=POINT_V[i][J]){
printf("■");
}else{
printf("");
}
}
}
}
FLAG[4]=0;
return0;
}
/***********碰撞检测*************/
intCheckCollision()
{
for(j=0;j=3;j++){
P_X=(P[j]2)+X+dx,P_Y=(P[j]0x3)+Y+dy;
if(POINT_V[P_X][P_Y]){
if(dx!=0){return1;}
if(dy){
DoBlocks();
Pos(12,3);printf("LEVEL:%-3d",LEVEL);
Pos(12,5);printf("SCORE:%-3d",SCORE);
return2;
}
if(KEY_V==119){FLAG[0]=1;}
}
}
return0;
}
unsigned
/***********循环核心*************/
intGameCycle(intN,intT,intF)
{
srand((unsigned)time(NULL));RU=rand()%7,RI=(rand()%4);
while(1){
if(FLAG[3]){
Display(12,8,Rote(RU,RI),0);
X=4,Y=1,NU=RU,NI=RI,RU=rand()%7,RI=(rand()%4),FLAG[3]=0,KEY_V=0;
Display(12,8,Rote(RU,RI),1);
Display(X,Y,Rote(NU,NI),1);
}
dx=0,dy=0;
KEY_V=Getkey(N,T);
if(KEY_V==119){
NI++;
Display(X,Y,Rote(NU,NI),2);
}//旋W
elseif(KEY_V==115){dy=1;}//下S
elseif(KEY_V==97){dx=-1;}//左A
elseif(KEY_V==100){dx=1;}//右D
elseif(KEY_V==112){getch();}//暂停P
elseif(KEY_V==113){return0;}//退出Q
if(dx!=0||dy!=0||KEY_V==119){
if(!CheckCollision()){
if(FLAG[0]){
NI--,FLAG[0]=0;
Display(X,Y,Rote(NU,NI),0);
}
elseif(KEY_V==119){
Display(X,Y,Rote(NU,NI-1),0);
}else{
Display(X,Y,Rote(NU,NI),0);
}
Display(X+dx,Y+dy,Rote(NU,NI),1);
X+=dx,Y+=dy;
}
}
}
return0;
}
/**********Main主函数***********/
intmain()
{
system("colorF0modeconcols=35lines=25");
HideCursor();
CreatUI();
GameCycle(10,800,1);
return0;
}
#后记
里面涉及的很多东西,我都没有完全总结出来,作者的巧妙非常令我佩服,后面应该还会有文章分析。
大家有什么想法或者自己的解读,可以留言,欢迎一起讨论,共同进步,探索代码里面的奥秘和乐趣。
方块消除
准备下一个方块
得分机制
Game Over 判断
C语言俄罗斯方块游戏解析(图文+源码) 相关文章
C语言头文件math.h(cmath)
C语言头文件math.h(cmath) 如果已经看过算术运算符,也许你已经能进行加减乘除这些基本操作,但对于数学来说远远不够,甚至不能一下子计算一些常见函数的值,比如计算lg 10。 math头文件正是为了解决这一基础性的问题,在做PTA的时候如果适当引入math模块,
实体
!DOCTYPE html!-- 设置html的语言为英文网站 --html lang="en"head meta charset="UTF-8" titlehtml中的字符实体/title/headbody !-- 在网页中编写的多个空格默认情况会自动被浏览器解析为一个空格 在html页面中有些时候, 我们不能直接书写一些特殊符号 比如
C语言学习之我见-strncpy()字符串复制函数(可控制范围)
strncpy()函数,用于两个字符串值的复制。 (1)函数原型 char *strncpy(char * _Dest,const char * _Source,size_t _Count); (2)头文件 string.h (3)功能 将从const char * _Source到'\0'结尾的字符串(包括'\0')复制到char * _Dest所指的字符串处。siz
C语言学习之我见-strcpy()字符串复制函数
strcpy()函数,用于两个字符串值的复制。 (1)函数原型 char * strcpy(char * _Dest,const char * _Source); (2)头文件 string.h (3)功能 将从const char * _Source到'\0'结尾的字符串(包括'\0')复制到char * _Dest所指的字符串处。返回值char * = cha
C语言入门学习(第三天EX——定点数、浮点数以及如何确定取值范围)
镇楼图 之前没讲清楚,特此新开一个blog好好讲一下定点数、浮点数。 一、定点数 组成部分:符号位、整数部分、小数部分 如图所示,假设有n位整数部分,m位小数部分 在整数部分、小数部分之间确定的位置便是小数点 因为能够明确小数点的位置,这种小数称为定
c语言关于freopen函数最全使用方法
此文转载自: hey,好久不见,最近沉迷游戏无法自拔,现在抽出时间来写写关于freopen函数的使用方法,这个freopen函数主要作用就是从文件读取函数同时写入文件,不像我们平常的控
R语言可视化学习笔记之ggridges包. crossed coverage
library(viridis)library(ggridges)head(lincoln_weather[ ,1:4])ggplot(lincoln_weather, aes(x=`Mean Temperature [F]`, y=`Month`, fill=..x..))+ geom_density_ridges_gradient(scale=3, re
五种C语言非数值计算的常用经典排序算法
摘要: 排序是计算机的一种操作方法,其目的是将一组“无序”的记录序列调整为“有序”的记录序列,主要分为内部排序和外部排序。 排序 排序是计算机的一种操作方法,其目的是将一组“无序”的记录序列调整为“有序”的记录序列,主要分为内部排序和外部排序
C/C++ 跳出多重循环方法
c语言的break语句只能跳出离它最近的一层循环,但是我们有时候需要跳出多层循环,以下有几种跳出多重循环的方法: 1. 使用goto for (int i = 0; i MAX1; i++){ for (int j = 0; j MAX2; j++) { if (condition) { symbol = 1; goto outloop; } }}outloop://so
JS简写技巧
任何编程语言的简写技巧都能够帮助你编写更简练的代码,让你用更少的代码实现你的目标。让我们一个个来看看 JavaScript 的简写技巧吧。 1. 声明变量 //Longhandlet x;let y = 20;//Shorthandlet x, y = 20; 2. 给多个变量赋值 我们可以使用数组解构来在一行