共有回帖数  0  个 
	 
	
	
	
     
          
          
               
				
			 
				
					 
 
            
				   - 
						
						
							 
									先写原理: 
 本次的俄罗斯方块代码出其的简单,比我去年写的四十几K要小得多 
实际上核心代码只有3-4K,而且很容易理解,适合有一点C语言基础并对此 
有兴趣的人. 
 这前面只粗略讲解核心算法: 
 这里把游戏的关键设计放在三个盒子和一个坐标上: 
 大盒子:一个两维数组,记录着方块点阵的开与关(把游戏的舞台想像 
成一个点阵),在下面也把这个东西称为地图 
 两个5*5小盒子:两维数组,一个盛放着正在下落的方块,一个盛放在 
下一个下落的方块(即next),当然这两个也必须想像成一个点阵:如长条 
的点阵为: 
00000 
00100 
00100 
00100 
00100 
 现在你只要有这么一个概念:一个不断定时下落的小盒子从大盒子顶 
部下降到底部,之后再将next盒子放在下落盒子,再进行下一轮的下落... 
中间的控制等尚不要太着急. 
 现在面临着一个问题: 
 下落的盒子和地图之间要怎么联系起来? 
一个好的方法是再定义一个坐标:x,y,保存着小盒子左上角在地图上对应 
的下标(位置),即当x = 0, y = 0时,小盒子处于地图的左上部.如此,当 
小盒子需要移动时,即只须要改变x,y的值. 
 现在说说旋转. 
 小盒子保存着当前下落形状的点阵,那么旋转就只须要将这个点阵旋 
转90度:例如: 
00000 00000 
00100 00000 
00100 - 01111 
00100 00000 
00100 00000 
这一点实现起来还是不太难的. 
 判断碰撞 
 通常这种情况只须要在有移动小盒或旋转盒子时发生:也即点阵非空 
是互斥的,当小盒要向下移(x++)时,如果小盒里的点阵与地图上的点阵(非 
空的地方)重叠,则不能下移,(卡住了),旋转则转换后的形状与地图有冲 
突则要放弃旋转. 
 到了这里,你应该有一个大概的了解了,至于怎样在屏幕上画出来,这 
个是比较简单的,下面的代码会慢慢与你解释. 
*/ 
/* 
平台:DOS+TC2.0 
*/ 
#include stdio.h 
#include stdlib.h 
#include bios.h /*这里须要读取系统运行时间来作为定时器*/ 
#include graphics.h /*很不幸,TC2的简单图形,让我放弃了用*/ 
#include conio.h /*win32+openGL来讲解.*/ 
#define MAX_X 14 /*可见最大X*/ 
#define MAX_Y 21 /*可见最大Y*/ 
 /*我们定义了最大的可见X和Y,那么即还有不 
 可见的部分,事实上地图(大盒子)里的左右 
 两侧和底部各两行都被1填充,这样大大简化 
 出界的判断,事实上,在本例中没有这样的 
 代码,因为旁边有一圈1阻止小盒子越出大 
 盒子的按制范围 
 */ 
#define MAX_C 7 /*最大种类,这个无须解释*/ 
#define KEY_UP 'w' /*定义上下左右按按键*/ 
#define KEY_DOWN 's' 
#define KEY_LEFT 'a' 
#define KEY_RIGHT 'd' 
#define KEY_ESC 27 /*退出*/ 
typedef int BOOL; 
#define FALSE 0 
#define TRUE 1 /*这几个TC中没有...自己定义一下吧:)*/ 
/*时钟结构*/ 
typedef struct { /*时钟结构*/ 
 BOOL enabled; /*时钟是否开启*/ 
 unsigned int intervel; /*定时间隔*/ 
 unsigned int lasttime; /*这个属于内部使用变量*/ 
} Timer; 
/* 
 *现在进入了编程的初质阶段 
 *在开始处我会写出所有的函数原形,以及它们的作用 
 *main函数在程序的最后,你可以在这里看到整个游戏的组织架构 
 *很好,它只有几十行,并且非常容易理解,当然,还是先看一下函数原形 
 *及解释 
 */ 
/****************************************************** 
 * 函数原形及说明 * 
******************************************************/ 
/*以下三个函数可以参照Timer结构体.在函数声明后面*/ 
int GetTickCount(); /*返回电脑或操作系统运行逝去的时间*/ 
 /*在win32环境下已包含在windows.h里边,返回的是4byte*/ 
 /*在DOS(本代码)环境下,要自己编写,使用到BIOS.h内容*/ 
int setTimer(Timer *t, unsigned int intv, BOOL en); 
 /*设置时钟t,参数分别为时钟指针,时间间隔,是否活动*/ 
 /*时间间隔,win32下为毫秒,DOS下为1/18秒(有点低)*/ 
BOOL testTimer(Timer *t); /*测试时钟t是否到达定时时间*/ 
 /*如下面这段代码:*/ 
 /* 
 setTimer(&t, 1, 1); 设置1个单位的间隔 
 while(1) { 
 if(testTimer(&t)) printf("Active!n"); 
 } 
 将会定时(1个单位)在屏幕上打印Active! 
 一般来说testTimer必须放在循环中反复执行,激活时返回1 
 */ 
void render(void); /*唯一的绘图函数*/ 
 /*注意,此函数重画整个地图,根据地图中的点阵,以及根据 
 小盒在地图的中坐标在恰当位置画出小盒子*/ 
 /*DOS的图形当然是很低的,但,全屏绘图在这里还是过得去 
 的,我用的是双缓冲,交换绘图,这样感觉好点*/ 
void initMap(void); /*初始化地图(大盒子)*/ 
 /*之前提到过在这个两维数组中有一圈为1的东西来阻止 
 小盒子出界,这就是生成这一圈的函数*/ 
void newGame(); /*新建一个游戏*/ 
 /*这个函数初始化一几个时钟和建造第一个下落的小盒子*/ 
 /*当然建造完后要生成一个个的预览*/ 
void rotateBox(int box1[5][5], int box2[5][5]); 
 /*核心函数成员,把box1逆时针旋转90度,并保存到box2中*/ 
void rebuidNext(); 
 /*核心函数成员,生成下一个方块*/ 
int drop(); 
 /*核心函数成员,将下落的盒子向下移(实际上增加下落盒 
 子的Y值而已,当然要判断是否与地图点阵重叠*/ 
 /*与地图重叠,无法完成下落操作,返回0*/ 
void putBox(); 
 /*在这之上,下落的盒子与地图之前是独立的两个两维数*/ 
 /*当下落失败后,小盒子要回到顶端再次重新执行下落,这*/ 
 /*时原来的盒子内容当然就要变成地图上的内容了,putBox 
 就是将下落盒子的内容根据XY写到地图上*/ 
void clear(); 
 /*这个函数在下落失败并putBox后执行,扫描整个地图*/ 
 /*清除掉满行的点阵,具体细节在函数内讲*/ 
int move(int dir); 
 /*左右移动下落盒子,dir指出向左还是向右,这个与drop 
 是一样的*/ 
int test(int mx, int my, int box[5][5]); 
 /*这个比较重点,判断box在mx,my为坐标上,与地图上的 
 非空点阵是否有重叠.很通用的一个函数*/ 
int rotate(); 
 /*旋转下落的盒子,当然如果转了之后与地图有冲突,会 
 取消转动,返回0,但返回的值好像没什么用~*/ 
int newfall(); 
 /*创建下落元素,把"下一个"预览的内容复制到下落盒子*/ 
 /*并将下落的盒子移动到地图顶部,当然这个过程,如果顶 
 部有冲突,会返回0,这时说明已经满了...gameOver*/ 
int main(); 
 /*终于到了最后的主函数,在这里可以看到整个游戏的架*/ 
 /*构,包括游戏主循环,键盘处理等...*/ 
/****************************************************** 
 * 变量区 * 
******************************************************/ 
/*在上面的说明中,可能会有一些蒙,因为可能对所用到的实际变量没 
 *有了解 
 */ 
int map[MAX_Y+4][MAX_X+4]; /*地图大盒子...MAX_X,Y是可见面积*/ 
 /*我已说过需要在外面布两圈"卫兵"*/ 
int curbox[5][5]; /*当前下落的盒子*/ 
int curx, cury; /*保存着当前活动盒子在地图上的位置*/ 
int nextbox[5][5]; /*保存着下一个形状的盒子*/ 
/*以上就是这么几个盒子和坐标了*/ 
/*这里列出了标准七种俄罗斯方块图形点阵,用到时它们会被复制到相*/ 
/*应的盒子...:)*/ 
int box[MAX_C][5][5] = { /*MAX_C(7)种预定义的盒子*/ 
 { 
 {0,0,0,0,0}, 
 {0,0,0,0,0}, 
 {1,1,1,1,0}, 
 {0,0,0,0,0}, 
 {0,0,0,0,0} 
 }, 
  
 { 
 {0,0,0,0,0}, 
 {0,0,1,0,0}, 
 {0,1,1,1,0}, 
 {0,0,0,0,0}, 
 {0,0,0,0,0} 
 }, 
  
 { 
 {0,0,0,0,0}, 
 {0,1,1,0,0}, 
 {0,0,1,1,0}, 
 {0,0,0,0,0}, 
 {0,0,0,0,0} 
 }, 
  
 { 
 {0,0,0,0,0}, 
 {0,0,1,1,0}, 
 {0,1,1,0,0}, 
 {0,0,0,0,0}, 
 {0,0,0,0,0} 
 }, 
  
 { 
 {0,0,0,0,0}, 
 {0,1,1,0,0}, 
 {0,0,1,0,0}, 
 {0,0,1,0,0}, 
 {0,0,0,0,0} 
 }, 
  
{ 
 {0,0,0,0,0}, 
 {0,0,1,1,0}, 
 {0,0,1,0,0}, 
 {0,0,1,0,0}, 
 {0,0,0,0,0} 
 }, 
  
 { 
 {0,0,0,0,0}, 
 {0,0,1,1,0}, 
 {0,0,1,1,0}, 
 {0,0,0,0,0}, 
 {0,0,0,0,0} 
 } 
 }; 
/****************************************************** 
 * 时钟 * 
******************************************************/ 
/*时钟部分也非常理解的,一个用到设置时钟,一个用来测试时钟激活态*/ 
Timer tDown; /*正常下落定时时钟intervel会比较大*/ 
Timer tFast; /*按KEY_DOWN时使用的快速下落*/ 
int speed = 13; /*控制下落时间间隔*/ 
#define FAST_INTV 1 /*快时钟的间隔*/ 
int GetTickCount() { /*读取BIOS时钟*/ 
 int ret; 
 ret = peek(0x0,0x46e); /*实际上读取了内存0:046e处的内容*/ 
 ret = 8; /*这个地方是$%#$^$%&^*/ 
 ret += peek(0x0,0x46c); /*太多新的东西了,找点书看一看吧*/ 
 return (ret); 
} 
int setTimer(Timer *t, unsigned int intv, BOOL en) { 
 t - enabled = en; /*设置一个时钟罗*/ 
 t - intervel = intv; 
 t - lasttime = GetTickCount(); /*lasttime记录的是上一个*/ 
 /*tickcount返回的东西*/ 
 /*这样当再一次测试时间时新的tickcount产生了 
 它来减去上一次的tickcount就得出了一个时间 
 间隔,这个就可以和intervel比较从而得出是否 
 激活了 
 */ 
 return 0; 
} 
BOOL testTimer(Timer *t) { /*在上面6行的地方解释了:)*/ 
 unsigned int tmp, dt; 
 if (!(t - enabled)) return FALSE; 
 tmp = GetTickCount(); 
 dt = tmp - (t - lasttime); 
 if(dt = t - intervel) { 
 t - lasttime = tmp; 
 return TRUE; 
 } 
 return FALSE; 
} 
/****************************************************** 
 * 渲染部分 * 
******************************************************/ 
/*提供render更新整个屏幕*/ 
/*关于这个函数,要说的东西还是比较多,为了追求漂亮和编译*/ 
/*时的灵活性,这个函数被写得颇为冗长...*/ 
/*现在写一下本游戏图形的东西...使用TC2的Graphics我也不 
 太乐意,毕竟它本身已过时,但鉴于实在简单实用,它用来教学 
 再合适不过,这也是教学总是用TC的原因,老师们不喜欢让学 
 生问一些让他们掌握起来也困难的东西...*/ 
/*这里我使用了VGAMED模式,而不是 VGAHI,因为VGAMED有两个 
 页(可以想像成缓冲),这样可以用来做到不闪动画.即:在后台 
 页绘制图形,完成后再显示出来. 
 这里用到了两个函数: 
 setactivepage(1 | 0) 参数只能是1或0,选择绘图页,例如选 
 择了1后,以后所有的绘图动作将画到页1上. 
 setvisualpage(1 | 0) 这个叫做选择可见页,即选择在屏幕上 
 显示页面1还是0 
*/ 
void render(void) { 
 int x, y; 
 static int cPage = 0; /*当前页,换页用*/ 
#define STARTX 50 /*定义几个常量*/ 
#define STARTY 0 
#define LEN 18 
 setactivepage(cPage=(cPage == 0?1:0)); /*选择页*/ 
 cleardevice(); /*清屏*/ 
 setcolor(15); 
 rectangle( STARTX + LEN * 2 - 2,  
 STARTY + LEN * 3 - 2,  
 STARTX + LEN * (MAX_X - 2) + 2,  
 STARTY + LEN * (MAX_Y - 2) + 2); 
 /*用白色画一个外框*/ 
 setfillstyle(SOLID_FILL, 5); 
 for(y = 3; y  MAX_Y - 2; y++) { /*画地图 */ 
 for(x = 2; x  MAX_X - 2; x++) { 
 if(map[y][x]) {  
 rectangle( x * LEN + STARTX,  
 y * LEN + STARTY, 
 x * LEN + STARTX + LEN, 
 y * LEN + STARTY + LEN); 
 bar( x * LEN + STARTX + 1, 
 y * LEN + STARTY + 1, 
 x * LEN + STARTX + LEN - 2, 
 y * LEN + STARTY + LEN - 2); 
 } 
 } 
 } 
/*绘图操作就不要作太复杂的介绍了,这只写作用*/ 
/*以上段,根据地图上的点阵情况将地图反映到屏幕上*/ 
悲凉.............. 
被格式化了.......... 
TMD 白肚! 
建议用一些语法高亮的编辑器看 
有格式化工具重新格式化一下更好:)
							 
							 
							 
							  
							  
							  楼主 2016-03-09 13:11 回复
						 
						 
           
          
          
         
   
         
      
 
   
             
                  
                  
 
 
 
     
	 
  
	Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号
	
	意见反馈 | 
	关于直线 | 
	版权声明 | 
	会员须知