Bootstrap

基于STC89C52单片机的俄罗斯方块设计(含文档、源码与proteus仿真,以及系统详细介绍)

本篇文章论述的是基于STC89C52单片机的俄罗斯方块的设计的详情介绍,如果对您有帮助的话,还请关注一下哦,如果有资源方面的需要可以联系我。

目录

原理图

仿真图

PCB

元件清单

代码

俄罗斯方块原理介绍

资源下载


摘    要

这个游戏设计,本质上就是用一个线程或者定时器产生重绘事件,用线程和用户输入改变游戏状态。这个游戏也不例外,启动游戏后,就立即生成一个重绘线程,该线程每隔50ms绘制一次屏幕。当然,重绘时有一些优化措施,并不是屏幕上所有的像素都需要重绘,而是有所选择,比如游戏画布上那些已经固定下来的下坠物(下坠物一共有7种,由4个小砖块组成,每种下坠物颜色固定,可以上下左右旋转)就不需重绘。游戏画布是一个命令接受者,可以接受用户键盘命令,控制下坠物的左移,右移,下移,旋转动作。整个游戏的流程控制体现在游戏画布对象的paint()方法里。paint()根据当前的游戏状态,绘制出当时的游戏画面。欢迎画面和Game Over画面的绘制相当简单。游戏暂停画面的绘制也相当容易,就是设立标志,让paint()执行的时候无需真正执行重绘动作。对于游戏处于运行状态的画面的绘制,则需要在下坠物的当前位置,绘制下坠物。在绘制下坠物之前,判断下坠物是否还能下坠,如果能下坠的话,就让它下落一格,再进行绘制,如果下坠物已无法下坠,则判断游戏是否处于Game Over状态,如果是处于Game Over状态的话,则设置游戏状态为Game over状态,这样画布在下一次重绘时就绘出Game Over的画面.如果游戏不是处于Game Over状态,则把下坠物固定下来,同时检查游戏画布上下坠物当前行下面的所有行,看是否需要进行行删除动作,如果需要行删除,则清除游戏地图上被删行的数据,再把被删行绘制成背景色。然后初始化一个新的下坠物,绘制这个新的下坠物。

原理图


仿真图


PCB


 元件清单


代码(部分)


#include <REGX52.H>
#include"pic.c"
#include <intrins.h>
#define LCD_DATA P2
#define button_delay 150  //按键延时
#define button_acceleration 65  //按键加速度阈值
#define GAME_LOCATION 30
sbit button_a = P3^4;	 //变形
sbit button_b = P3^5;	//开始
sbit up = P3^2;	   //暂停开始
sbit down = P3^0;
sbit left = P3^1;
sbit right = P3^3;
sbit speaker=P3^6; 

sbit LCD_RS=P1^0;
sbit LCD_RW=P1^1;
sbit LCD_E=P1^2;
sbit LCD_CS2=P1^4;		//右屏选择(左右屏有时候相反)
sbit LCD_CS1=P1^3;		//左屏选择
sbit LCD_RST=P3^7;

unsigned int up_reg=button_delay;       //按键up累加器
unsigned int down_reg=button_delay;     //按键down累加器
unsigned int left_reg=button_delay;     //按键left累加器
unsigned int right_reg=button_delay;    //按键right累加器
unsigned int button_a_reg=button_delay; //按键button_a累加器
unsigned int button_b_reg=button_delay; //按键button_b累加器
unsigned int right_acceleration=0;		//按键right加速度寄存器
unsigned int left_acceleration=0;		//按键left加速度寄存器

unsigned int idata Box_Ram[19];//定义游戏点阵缓存10*16
unsigned char box_down_reg;//定义方块下落累加寄存器
unsigned char time0_reg;//定义定时器0累加寄存器
unsigned char next_mode;//定义下一个方块的类型
unsigned char next_shape;//定义下一个方块的形状
unsigned int destroy_row_num=0;//定义所消的行数
unsigned char speed_num=0;//定义游戏速度等级
unsigned char level_num;//定义游戏难度等级
bit game_over_flag;//游戏结束标志位置0表示游戏未结束
bit pause_game_flag;//游戏暂停标志位置0表示游戏未暂停

struct
{
	unsigned char mode;//类型
	unsigned char shape;//形状
	unsigned char x;//x坐标
	unsigned char y;//y坐标
	unsigned int box;//定义方块缓存
}s_box;	//定义方块结构体
//LCD检测忙状态函数
void LCD_check_busy()
{
	unsigned char temp;
	LCD_RS=0;
	LCD_RW=1;
	do
	{
		LCD_DATA=0xff;
		LCD_E=1;
		temp=LCD_DATA;
		LCD_E=0;
	}while((temp&0x80)==0x80);		
}
//写指令代码(cs为0选左屏,cs为1选右屏)
void LCD_W_code(unsigned char tpcode,bit cs)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_CS2=~cs;
	LCD_CS1=cs;
	LCD_DATA=tpcode;
	LCD_E=1;
	_nop_();
	LCD_E=0;
}
//写显示数据(cs为0选左屏,cs为1选右屏)
void LCD_W_data(unsigned char tpdata,bit cs)
{
	LCD_check_busy();
	LCD_RS=1;
	LCD_RW=0;
	LCD_CS2=~cs;
	LCD_CS1=cs;	
	LCD_DATA=tpdata;
	LCD_E=1;	
	_nop_();
	LCD_E=0;
}

//LCD初始化函数
void LCD_initialize()
{
	LCD_RST=0;
	_nop_();
	_nop_();
	LCD_RST=1;
	LCD_W_code(0x3f,0);		//开显示设置	
	LCD_W_code(0xc0,0);		//设置显示起始行为第一行	
	LCD_W_code(0xb8,0);		//页面地址设置	
	LCD_W_code(0x40,0);		//列地址设为0
	LCD_W_code(0x3f,1);
	LCD_W_code(0xc0,1);	
	LCD_W_code(0xb8,1);
	LCD_W_code(0x40,1);
}
//LCD清屏函数
void LCD_clear()
{
	unsigned char i,j;
	for(j=0;j<8;j++)
	{
		LCD_W_code(0xb8+j,0);
		LCD_W_code(0x40,0);
		LCD_W_code(0xb8+j,1);
		LCD_W_code(0x40,1);
		for(i=0;i<64;i++)
			{	
				LCD_W_data(0x00,0);	
				LCD_W_data(0x00,1);
			}
	}
}
//LCD显示字符串函数(word表示要显示的字符串,
 //length表示要显示的字符串宽度,
 //x表示首字符所在行数,
 //y表示首字符所在列数)
void LCD_display_word(unsigned char word[],
                      unsigned int length,
					  unsigned char x,
					  unsigned char y)
{
	unsigned char i;
	for(i=0;i<length;i++)
	{
		
		LCD_W_code(0xb8+x,0);
		LCD_W_code(0xb8+x,1);
		if(y+i<64)
		{
			LCD_W_code(0x40+y+i,0);	
			LCD_W_data(word[i],0);
		}
		else
		{
			LCD_W_code(y+i,1);	
			LCD_W_data(word[i],1);
		}
	}
}
//LCD画全屏函数
void LCD_full_draw(unsigned char word[])
{
	unsigned char i,j;
	for(i=0;i<8;i++)
	{
		LCD_W_code(0xb8+i,0);
		LCD_W_code(0x40,0);	
		for(j=0;j<64;j++)
		{
			LCD_W_data(word[i*128+j],0);
		}
		LCD_W_code(0xb8+i,1);
		LCD_W_code(0x40,1);	
		for(j=0;j<64;j++)
		{
			LCD_W_data(word[i*128+64+j],1);
		}			
	}
}
//LCD显示一个字节函数(
  //x表示x坐标,
  //y表示y坐标,
  //tpdata表示要显示的数据)
void LCD_display_byte(unsigned char x,
					  unsigned char y,
					  unsigned char tpdata)
{
	if(x<64)
	{
		LCD_W_code(0xb8+y,0);
		LCD_W_code(0x40+x,0);
		LCD_W_data(tpdata,0);	
	}
	else
	{
		LCD_W_code(0xb8+y,1);
		LCD_W_code(x,1);
		LCD_W_data(tpdata,1);	
	}
} 

void LCD_draw(unsigned char word[])
{
  unsigned char i,j;
  for(i=0;i<8;i++)
  {
    LCD_W_code(0xb8+i,1);
	LCD_W_code(0x40+20,1);
	for(j=0;j<44;j++)
	{
	  LCD_W_data(word[i*44+j],1);
	}
  }
}
//基本界面显示函数
void display_basic()
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		LCD_display_byte(GAME_LOCATION,i,0xff);
		LCD_display_byte(GAME_LOCATION+41,i,0xff);
	}
}
//刷新游戏区域函数
void refurbish_display()
{
	unsigned char i,j,tpdata;
	for(i=0;i<8;i++)
	{
		for(j=0;j<10;j++)
		{
			tpdata=0x00;
			if(  (Box_Ram[2*i]>>(12-j))&0x0001==1  )
			{
				tpdata=0x0f;
			}
			if(  (Box_Ram[2*i+1]>>(12-j))&0x0001==1  )
			{
				tpdata|=0xf0;
			}
			LCD_display_byte(GAME_LOCATION+1+j*4,i,tpdata);
			LCD_display_byte(GAME_LOCATION+2+j*4,i,0xbb&tpdata);
			LCD_display_byte(GAME_LOCATION+3+j*4,i,0xdd&tpdata);
			LCD_display_byte(GAME_LOCATION+4+j*4,i,tpdata);
		}
	}
}
//基本按键程序(返回0表示没按键被按下,返回1表示down被按下,返回2表示up被按下,返回3表示button_a被按下,返回4表示left被按下,返回5表示right被按下)
//游戏中按键识别程序(有优先级,从高到低依次是button_a_reg>down>left>right>up)
unsigned char basic_button()
{
	unsigned char tpflag=0;
	if(button_b==0)
	{
	    if(button_b_reg<button_delay*8)
		{
		  button_b_reg++;
		}
		else
		{
		  button_b_reg=0;
		  tpflag=6;
		}
	}
	else
	{
   	     button_b_reg=button_delay*8;
	}
	if(down==0)
	{
		if(down_reg<button_delay)//按键一直被按下时设置时间间隔触发
		{
			down_reg++;
		}
		else
		{
			down_reg=0;
			tpflag=1;//返回1表示down被按下
		}		
	}
	else
	{
		down_reg=button_delay;//释放按键时置按键缓存为button_delay,以便在下次按键时及时响应
	}
	if(up==0)
	{
		if(up_reg<button_delay)//按键一直被按下时设置时间间隔触发
		{
			up_reg++;
		}
		else
		{
			up_reg=0;
			tpflag=2;//返回2表示up被按下
		}		
	}
	else
	{
		up_reg=button_delay;//释放按键时置按键缓存为button_delay,以便在下次按键时及时响应
	}
	if(button_a==0)
	{
		if(button_a_reg<button_delay*8)//按键一直被按下时设置时间间隔触发
		{
			button_a_reg++;
		}
		else
		{
			button_a_reg=0;
			tpflag=3;//返回3表示button_a被按下
		}		
	}
	else
	{
		button_a_reg=button_delay*8;//释放按键时置按键缓存为button_delay,以便在下次按键时及时响应
	}
	if(left==0)
	{
		if(left_reg<(button_delay))//按键一直被按下时设置时间间隔触发
		{
			left_reg++;
		}
		else
		{
			left_reg=left_acceleration*button_acceleration;
			if(left_acceleration<2)left_acceleration++;
			tpflag=4;//返回4表示left被按下
		}		
	}
	else
	{
		left_acceleration=0;
		left_reg=button_delay;//释放按键时置按键缓存为button_delay,以便在下次按键时及时响应
	}
	if(right==0)
	{
		if(right_reg<(button_delay))//按键一直被按下时设置时间间隔触发
		{
			right_reg++;
		}
		else
		{
			right_reg=right_acceleration*button_acceleration;
			if(right_acceleration<2)right_acceleration++;
			tpflag=5;//返回5表示right被按下
		}		
	}
	else
	{
		right_acceleration=0;
		right_reg=button_delay;//释放按键时置按键缓存为button_delay,以便在下次按键时及时响应
	}
	return(tpflag);
}
//检查覆盖函数(检查此时带入的参数所确定的方块是否会覆盖原有图形,不会覆盖返回1,覆盖返回0)
bit check_cover(unsigned char tpx,unsigned char tpy,unsigned int tpbox)
{
	unsigned char i;
	bit tpflag=1;
	unsigned int temp;
	temp=s_box.box;
	for(i=0;i<4;i++)
	{
		Box_Ram[3-i+s_box.y]&=(~((temp&0x000f)<<(9-s_box.x))); 
		temp=temp>>4;
	}//先将现有的方块从游戏点阵缓存中删除
	temp=tpbox;
	for(i=0;i<4;i++)
	{
		if((((temp&0x000f)<<(9-tpx))&Box_Ram[3-i+tpy])!=0x0000)
		{
			tpflag=0;
		}
		temp=temp>>4;
	}//检查方块是否和原有图形重叠,重叠置标志位tpflag为0,不重叠不置标志位,即tpflag为1
	temp=s_box.box;
	for(i=0;i<4;i++)
	{
		Box_Ram[3-i+s_box.y]|=((temp&0x000f)<<(9-s_box.x));
		temp=temp>>4;
	}//在游戏点阵缓存中恢复原有方块
	return(tpflag);
}
//方块缓存数据函数(输入方块类型和形状即可获得方块缓存数据)
unsigned int box_read_data(unsigned char tpmode,unsigned char tpshape)
{
	unsigned int tpbox;
	switch(tpmode)
	{
		case 0: switch(tpshape)
				{
					case 0: tpbox=0xf000;break;
					case 1: tpbox=0x4444;break;
					case 2: tpbox=0xf000;break;
					case 3: tpbox=0x4444;break;
					default:;
				}break;	
		case 1: switch(tpshape)
				{
					case 0: tpbox=0xe800;break;
					case 1: tpbox=0xc440;break;
					case 2: tpbox=0x2e00;break;
					case 3: tpbox=0x88c0;break;
					default:;
				}break;	
		case 2: switch(tpshape)
				{
					case 0: tpbox=0xe200;break;
					case 1: tpbox=0x44c0;break;
					case 2: tpbox=0x8e00;break;
					case 3: tpbox=0xc880;break;
					default:;
				}break;	
		case 3: switch(tpshape)
				{
					case 0: tpbox=0xcc00;break;
					case 1: tpbox=0xcc00;break;
					case 2: tpbox=0xcc00;break;
					case 3: tpbox=0xcc00;break;
					default:;
				}break;	
		case 4: switch(tpshape)
				{
					case 0: tpbox=0xc600;break;
					case 1: tpbox=0x4c80;break;
					case 2: tpbox=0xc600;break;
					case 3: tpbox=0x4c80;break;
					default:;
				}break;	
		case 5: switch(tpshape)
				{
					case 0: tpbox=0x6c00;break;
					case 1: tpbox=0x8c40;break;
					case 2: tpbox=0x6c00;break;
					case 3: tpbox=0x8c40;break;
					default:;
				}break;
		case 6: switch(tpshape)
				{
					case 0: tpbox=0x4e00;break;
					case 1: tpbox=0x8c80;break;
					case 2: tpbox=0xe400;break;
					case 3: tpbox=0x4c40;break;
					default:;
				}break;
		default:;
	}
	return(tpbox);
}
//方块载入函数
void box_load()
{
	s_box.box=box_read_data(s_box.mode,s_box.shape);
}
//方块映射游戏点阵缓存函数(参数是原来方块的位置、缓存,先消去原有位置的方块)
void box_to_Box_Ram(unsigned char tpx,unsigned char tpy,unsigned int tpbox)
{
	unsigned char i;
	unsigned int temp;
	temp=tpbox;
	for(i=0;i<4;i++)
	{
		Box_Ram[3-i+tpy]=Box_Ram[3-i+tpy]&(~((temp&0x000f)<<(9-tpx))); 
		temp=temp>>4;
	}//从游戏点阵缓存中删除以前的方块
	temp=s_box.box;
	for(i=0;i<4;i++)
	{
		Box_Ram[3-i+s_box.y]=((temp&0x000f)<<(9-s_box.x))|Box_Ram[3-i+s_box.y];
		temp=temp>>4;
	}//在游戏点阵缓存中加入新的方块
}
//显示数字函数(
  //x表示x坐标,
  //y表示y坐标,
  //tpdata表示要显示的数字)
//显示速度函数
void show_num(unsigned char x,
					  unsigned char y,
					  unsigned char tpdata)
{
	unsigned char i;
	for(i=0;i<4;i++)
	{
		LCD_display_byte(x+i,y,num_data[tpdata*4+i]);	
	}
} 
void show_speed_num(unsigned char x,unsigned char y)
{
	show_num(x,y,speed_num);
}
//显示得分函数
void show_score_num(unsigned char x,unsigned char y)
{
	show_num(x,y,destroy_row_num/10000);
	show_num(x+=5,y,(destroy_row_num%10000)/1000);
	show_num(x+=5,y,(destroy_row_num%1000)/100);
	show_num(x+=5,y,(destroy_row_num%100)/10);
	show_num(x+=5,y,destroy_row_num%10);
}
//消行函数
void destroy_row()
{
	unsigned char i,j=0;
	unsigned char tpflag[4]={0,0,0,0};//最多一次只能消四行,所以设置四个标志位即可,初值为0
	for(i=0;i<16;i++)
	{
		if((Box_Ram[i]&0x3ffc)==0x3ffc)
		{
			tpflag[j]=i+1;//tpflag为0表示不标志,1表示第0行缓存为0xffff,n表示第n+1行缓存为0xffff
			destroy_row_num++;//消除的行数加一
			/*如不把Box_Ram[19]定义成idata类型的话加入这段代码显示数据区就溢出了*/
			if(destroy_row_num%30==0&&speed_num!=9)
			{
				speed_num++;//消够三十行游戏速度加一
				show_speed_num(13,4);//调用显示游戏速度函数
			}
			/*如不把Box_Ram[19]定义成idata类型的话加入这段代码显示数据区就溢出了*/
			j++;
			if(j==4)
			{
				break;
			}//检查完有四行要消除则退出检查循环
		}
	}//依次检测是否有行缓存为0xffff,如果是则标志tpflag为此行的行号
	for(j=0;j<4;j++)
	{
		if(tpflag[j]!=0)
		{
			for(i=tpflag[j]-1;i>0;i--)
			{
			Box_Ram[i]=Box_Ram[i-1];
			Box_Ram[0]=0x2004;
			}
		}
	}//被标志的行依次被上一行所取代,即被消去
	show_score_num(3,1);
}
//显示下一个方块函数
void show_next_box()
{
	unsigned char i,tpdata;
	unsigned int temp;
	temp=box_read_data(next_mode,next_shape);
	for(i=0;i<4;i++)
	{
		tpdata=0x00;
		if(  ((temp>>(15-i))&0x0001)==1  )
		{
			tpdata=0x0f;
		}
		if(  ((temp>>(11-i))&0x0001)==1  )
		{
			tpdata|=0xf0;
		}
		LCD_display_byte(7+i*4,6,tpdata);
		LCD_display_byte(8+i*4,6,0xbb&tpdata);
		LCD_display_byte(9+i*4,6,0xdd&tpdata);
		LCD_display_byte(10+i*4,6,tpdata);	
		tpdata=0x00;
		if(  ((temp>>(7-i))&0x0001)==1  )
		{
			tpdata=0x0f;
		}
		if(  ((temp>>(3-i))&0x0001)==1  )
		{
			tpdata|=0xf0;
		}
		LCD_display_byte(7+i*4,7,tpdata);
		LCD_display_byte(8+i*4,7,0xbb&tpdata);
		LCD_display_byte(9+i*4,7,0xdd&tpdata);
		LCD_display_byte(10+i*4,7,tpdata);		
	}
}
//方块生成函数
void box_build()
{
	s_box.mode=next_mode;
	s_box.shape=next_shape;
	s_box.x=3;
	s_box.y=0;
	next_mode=TL0%7;//产生随机数,但是是伪随机的
	next_shape=TL0%4;//产生随机数,但是是伪随机的
	show_next_box();//放到game_execute()函数中不知道为什么就是不正常显示
}
void game_button()
{
	
    switch(basic_button())
    {
        case 3: if(s_box.y!=0)//3表示button_a被按下
                {
                    EA=0;//关中断,如果不关的话可能引起游戏显示混乱
                    speaker=0;
                    if(s_box.shape==3&&check_cover(s_box.x,s_box.y,box_read_data(s_box.mode,0)))
                    {
							
                        s_box.shape=0;
                        box_load();
                        box_to_Box_Ram(s_box.x,s_box.y,box_read_data(s_box.mode,3));
                    }
                    else if(check_cover(s_box.x,s_box.y,box_read_data(s_box.mode,0)))
                    {	if(check_cover(s_box.x,s_box.y,box_read_data(s_box.mode,s_box.shape+1)))
							{	
								s_box.shape++;
								box_load();
								box_to_Box_Ram(s_box.x,s_box.y,box_read_data(s_box.mode,s_box.shape-1));
							}
                     }
                    EA=1;//开中断
                    speaker=1;
					}break;
        case 1: if(s_box.y!=0)//1表示down被按下
        {
            EA=0;//关中断,如果不关的话可能引起游戏显示混乱
            speaker=0;
            while(check_cover(s_box.x,s_box.y+1,s_box.box))//检测是否能下降,指导不能再下降为止
            {
                s_box.y++;
                box_to_Box_Ram(s_box.x,s_box.y-1,s_box.box);
            }
            destroy_row();
            box_build();
            box_load();
					//	game_over_flag=check_game_over();//游戏结束标志位置1表示游戏结束
					//	next_box();
            box_to_Box_Ram(s_box.x,s_box.y,s_box.box);
            EA=1;//开中断
            speaker=1;
            }break;
        case 4: if(s_box.y!=0)//4表示left被按下
        {
            EA=0;//关中断,如果不关的话可能引起游戏显示混乱
            speaker=0;
            if(check_cover(s_box.x-1,s_box.y,s_box.box))
            {
                s_box.x--;
                box_to_Box_Ram(s_box.x+1,s_box.y,s_box.box);
            }
            EA=1;//开中断
            speaker=1;
        }break;
        case 5: if(s_box.y!=0)//5表示right被按下
                {
                    EA=0;//关中断,如果不关的话可能引起游戏显示混乱
                    speaker=0;
                    if(check_cover(s_box.x+1,s_box.y,s_box.box))
                    {
                        s_box.x++;
                        box_to_Box_Ram(s_box.x-1,s_box.y,s_box.box);
                    }
						EA=1;//开中断
						speaker=1;
					}break;
        case 2: //2表示up被按下
            speaker=0;
            pause_game_flag=~pause_game_flag;//游戏暂停标志取反
            while(up==0);
            speaker=1;
            break;
        default:;
    }	
}
//检查游戏结束函数(游戏结束返回1,游戏没有结束返回0)
bit check_game_over()
{
	unsigned char i;
	bit tpflag=0;
	unsigned int temp;
	temp=s_box.box;
	for(i=0;i<4;i++)
	{
		if((((temp&0x000f)<<(9-s_box.x))&Box_Ram[3-i+s_box.y])!=0x0000)
		{
			tpflag=1;
		}
		temp=temp>>4;
	}//检查新建方块是否和原有图形重叠,重叠置标志位tpflag为1,不重叠不置标志位,即tpflag为0
	return(tpflag);
}
//游戏执行函数(控制方块下落,检测是否到底,如果到底调用消行函数)
void game_execute()
{
	if(box_down_reg<20-(speed_num<<1))
	{				  
		box_down_reg++;
	}
	else
	{
		box_down_reg=0;
		if(check_cover(s_box.x,s_box.y+1,s_box.box))
		{
			s_box.y++;
			box_to_Box_Ram(s_box.x,s_box.y-1,s_box.box);
		}//检测是否还可以下降,如果还能下降则继续下降
		else
		{
			destroy_row();
			box_build();
			box_load();
			game_over_flag=check_game_over();//游戏结束标志位置1表示游戏结束
			box_to_Box_Ram(s_box.x,s_box.y,s_box.box);
			box_down_reg=(20-(speed_num<<1)-1);//为了使方块一出现就能变换形状,所以需要尽快使得方块下降一行,不知道为什么最高行不能变换形状
		}//如果不能下降则调用消行函数检查是否可以消行,之后重新建立方块
	}	
}
//选择游戏速度函数
void select_speed()
{
	unsigned char i;
	bit tpflag=1;//置循环标志位为1
	LCD_clear();
	for(i=0;i<128;i++)
	{
		LCD_display_byte(i,0,0xff);
		LCD_display_byte(i,7,0xff);
	}
	LCD_display_byte(60,4,0x7f);
	LCD_display_byte(59,4,0x3e);
	LCD_display_byte(58,4,0x1c);
	LCD_display_byte(57,4,0x08);
	LCD_display_byte(67,4,0x7f);
	LCD_display_byte(68,4,0x3e);
	LCD_display_byte(69,4,0x1c);
	LCD_display_byte(70,4,0x08);
	LCD_display_word(speed_data,24,3,52);
	show_speed_num(62,4);
	while(tpflag)
	{
		switch(basic_button())
		{
			case 4: if(speed_num!=0)
					{
						speaker=0;
						speed_num--;
						show_speed_num(62,4);
						speaker=1;
					}
					while(left==0);
					break;
			case 5: if(speed_num!=9)
					{
					    speaker=0;
						speed_num++;
						show_speed_num(62,4);
						speaker=1;
					}
					while(right==0);
					break;
			case 6: tpflag=0;
		 	        speaker=0;
					while(button_b==0);
					speaker=1;
					break;
			default:;
		}
	}//选择游戏速度循环
}
//游戏开始显示画面
void game_start_show()
{
	bit tpflag=1;//置循环标志位为1
	LCD_full_draw(start_pic);
	while(tpflag)
	{
		switch(basic_button())
		{
			case 6: tpflag=0;
			        speaker=0;
					while(button_b==0);
					speaker=1;
					break;
			default:;
		}
	}//game_start_show循环
}
//游戏初始化函数
void game_initialize()
{
	box_down_reg=0;
	next_mode=6;
	next_shape=2;
	destroy_row_num=0;
	game_over_flag=0;
	pause_game_flag=0;
	LCD_clear();
	time0_reg=0;
	display_basic();	
	LCD_display_word(score_data,24,0,3);
	LCD_display_word(speed_data,24,3,3);
	show_score_num(3,1);
	show_speed_num(13,4);
}
//定时器0初始化函数
void time0_initialize()
{
	TMOD=0x03;//定时器0,16位工作方式
	TR0=1; //启动定时器
	ET0=1; //打开定时器0中断
			//默认中断优先级为低
	EA=1; //打开总中断
}
//俄罗斯方块游戏主函数
void Tetris_main()
{
	unsigned char i;
	for(i=0;i<19;i++)
	{
		Box_Ram[i]=Box_Ram_data[i];
	};//载入游戏初始显示画面
	LCD_draw(mpic);
	game_over_flag=0;//游戏结束标志位置0表示游戏未结束
	box_build();
	box_load();
//	next_box();
	box_to_Box_Ram(s_box.x,s_box.y,s_box.box);
	box_down_reg=(20-(speed_num<<1)-1);//为了使方块一出现就能变换形状,所以需要尽快使得方块下降一行,不知道为什么最高行不能变换形状
	time0_initialize();
	while(!game_over_flag)//如果游戏结束标志位置1,表示游戏结束,打破循环,调用游戏结束画面显示函数
	{
		game_button();
	}
	EA=0;//游戏结束后关中断
}
//游戏结束画面显示函数
void game_over_show()
{
	unsigned char i;
	bit tpflag=1;//置循环标志位为1
	LCD_full_draw(over_pic);
	while(button_a==0);
	while(tpflag)
	{
		switch(basic_button())
		{
			case 6: tpflag=0;
			        speaker=0;
					while(button_b==0);
					speaker=1;
					break;
			default:;
		}
	}//game over画面循环
	LCD_clear();
	for(i=0;i<128;i++)
	{
		LCD_display_byte(i,0,0xff);
		LCD_display_byte(i,7,0xff);
	}
	LCD_display_word(score_data,24,3,52);
	show_score_num(52,4);
	tpflag=1;
	while(tpflag)
	{
		switch(basic_button())
		{
			case 6: tpflag=0;
			        speaker=0;
					while(button_b==0);
					speaker=1;
					break;
			default:;
		}
	}//游戏得分显示循环
}

void main()
{
	LCD_initialize();
	LCD_clear();
	while(1)
	{
		game_start_show();
		select_speed();
		game_initialize();//调用游戏初始化函数,初始化游戏所有变量以及在液晶屏上显示基本的信息
		Tetris_main();
		game_over_show();	
	}	
}
//定时器0中断服务
void timer0() interrupt 1
{
	TH0=0x00;
	TL0=0x00;
	if(time0_reg<10)
	{				  
		time0_reg++;
	}
	else
	{
		time0_reg=0;
		if(pause_game_flag==0)
		{
			game_execute();
			refurbish_display();
		}
	}
}

俄罗斯方块原理介绍


Tc2.0中怎么样设置图形显示?

Tc2.0中有两种显示模式,一种是我们所熟知的字符模式,另一种是图形模式。在字符模式下只能显式字符,如ASCII字符。一般是显示25
行,每行80个字符。程序缺省的是字符模式。在字符模式下不能显式图形和进行绘图操作。要想进行图形显示和绘图操作,必须切换到图形模
式下。

Tc2.0中用initgraph()函数可以切换到图形模式,用closegraph()可以从图形模式切换回字符模式。initgraph()和closegraph()都是图形
函数,使用图形函数必须包括头文件"graphics.h"。

void far initgraph(int far *graphdriver,int far *graphmode,char far *pathtodriver);graphdriver是上涨指向图形驱动序号变量的指针;graphmode是在graphdriver选定后,指向图形显示模式序号变量的指针。pathtodriver表示存放图形驱动文件的路径。

Tc2.0中有多种图形驱动,每种图形驱动下又有几种图形显示模式。在我的程序中图形驱动序号为VGA,图形显示模式序号为VGAHI。这是一种分辨率为640*480(从左到右坐标依次为0-639,从上到下坐标依次为0-479),能够显示16种颜色的图形模式。别的图形驱动序号和图形显示模式序号,可以从手册或联机帮助中找到。

pathtodriver指示存放图形驱动文件的路径。图形驱动序号不同,图形驱动文件也不同。序号为VGA图形驱动对应"egavga.bgi"这个图形驱动文件。"egavga.bgi"一般在Tc目录下。

void far closegraph(void);
  没有参数,从图形模式直接返回字符模式。

initgraph()和closegraph()的常用用法如下:
int gdriver = VGA, gmode=VGAHI, errorcode;

/* initialize graphics mode */
initgraph(&gdriver, &gmode, "e:\\tc2");

/* read result of initialization */
errorcode = graphresult();

if (errorcode != grOk) /* an error occurred */
{
printf("Graphics error: %s\n", grapherrormsg(errorcode));
printf("Press any key to halt:");
getch();
exit(1); /* return with error code */
}

/* return to text mode */
closegraph();


Tc2.0中常用图形函数的用法?

在这里讲几个游戏中用到的绘图用的图形函数:
setcolor();
line();
rectangle();
settextjustify();
outtextxy();
setfillstyle();
bar();

void far setcolor(int color);
  设置画线、画框和在图形模式下显示文字的当前颜色。这个函数将影响line()、rectangle()和outtextxy()函数绘图的颜色。
color可以取常的颜色常量:
BLACK ? 0
BLUE ? 1
GREEN ? 2
CYAN ? 3
RED ? 4
MAGENTA ? 5
BROWN ? 6
LIGHTGRAY ? 7
DARKGRAY ? 8
LIGHTBLUE ? 9
LIGHTGREEN ?10
LIGHTCYAN ?11
LIGHTRED ?12
LIGHTMAGENTA ?13
YELLOW ?14
WH99vE ?15

void far line(int x1,int y1,int x2,int y2);
用当前颜色从(x1,y1)画一条到(x2,y2)的线段。

void far rectangle(int left,int top,int right,int bottom);
用当前颜色画一个左上角为(left,top)、右下角为(right,bottom)的矩形框。

void far settextjustify(int horz,int vert);
设置图形模式下文字输出的对齐方式。主要影响outtextxy()函数。
horiz和vert可取如下枚举常量:
horiz ?LEFT_TEXT ? 0 ?Left-justify text
?CENTER_TEXT ? 1 ?Center text
?RIGHT_TEXT ? 2 ?Right-justify text
vert ?BOTTOM_TEXT ? 0 ?Justify from bottom
?CENTER_TEXT ? 1 ?Center text
?TOP_TEXT ? 2 ?Justify from top

void far outtextxy(int x,int y,char * textstring);
在(x,y)处用当前字体(缺省的字体是DEFAULT_FONT)显示字符串textstring,字符串的对齐方式由settextjustify()指定。

void far setfillstyle(int pattern,int color);
设置图形的填充模式和填充颜色,主要影响bar()等函数。
pattern一般取枚举常量值SOLID_FILL,color的取值与setcolor(int color)中color的取值范围相同。

  介绍完了前面两个问题,现在来写一个程序。这个程序演示前了面所介绍的几个图形函数。
程序prog1.c


怎样获取鍵盘输入?

  在Tc2.0中有一个处理键盘输入的函数bioskey();
int bioskey(int cmd);
  当cmd为1时,bioskey()检测是否有键按下。没有键按下时返回0;有键按下时返回按键码(任何按键码都不为0),但此时并不将检测到的按
键码从键盘缓冲队列中清除。
  当cmd为0时,bioskey()返回键盘缓冲队列中的按键码,并将此按键码从键盘缓冲队列中清除。如果键盘缓冲队列为空,则一直等到有键按
下,才将得到的按键码返回。

Escape键的按键码为0x11b,下面的小程序可以获取按键的按键码。

for (;;)
{
key=bioskey(0); /* wait for a keystroke */
printf("0x%x\n",key);
if (key==0x11b) break; /* Escape */
}

常用按键的按键码如下:

#define VK_LEFT 0x4b00
#define VK_RIGHT 0x4d00
#define VK_DOWN 0x5000
#define VK_UP 0x4800
#define VK_HOME 0x4700
#define VK_END 0x4f00
#define VK_SPACE 0x3920
#define VK_ESC 0x011b
#define VK_ENTER 0x1c0d


  完整的程序请参见prog2.c、prog3.c。
prog2.c获取按键的按键码,按Escape键退出程序。
prog3.c根据不同的按键进行不同的操作,按Escape键退出程序。


怎样控制方块的移动?
  方块移动的实现很简单,将方块原来的位置用背景色画一个同样大小的方块,将原来的方块涂去。然后在新的位置上重新绘制方块就可以
了。这样就实现了方块的移动。完整的程序请参见prog4.c。这个用方向键控制一个黄色的小方块在屏幕上上、下、左、右移动。这个程序用到了前面几个问题讲的内容,如果你有点忘了,还要回头看看哦。:)


怎样控制时间间隔(用于游戏中控制形状的下落)?
  解决这个问题要用到时钟中断。时钟中断大约每秒钟发生18.2次。截获正常的时钟中断后,在处理完正常的时钟中断后,将一个计时变量
加1。这样,每秒钟计时变量约增加18。需要控控制时间的时候,只需要看这个计时变量就行了。


  截获时钟中断要用到函数getvect()和setvect()。
两个函数的声明如下:
?void interrupt (*getvect(int interruptno))();
?void setvect(int interruptno, void interrupt (*isr) ( ));

  保留字interrupt指示函数是一个中断处理函数。在调用中断处理函数的时候,所有的寄存器将会被保存。中断处理函数的返回时的指令是iret,而不是一般函数用到的ret指令。

getvect()根据中断号interruptno获取中断号为interruptno的中断处理函数的入口地址。
setvect()将中断号为interruptno的中断处理函数的入口地址改为isr()函数的入口地址。即中断发生时,将调用isr()函数。


  在程序开始的时候截获时钟中断,并设置新的中断处理。在程序结束的时候,一定要记着恢复时钟中断哦,不然系统的计时功能会出问题
的。具体演示程序请参见prog5.c。由于中断处理大家可能用的不多,所以我把prog5.c这个程序完整地贴在下面,并加上详细的解释。

/* prog5.c */
This is an interrupt service routine. You can NOT compile this
program with Test Stack Overflow turned on and get an executable
file which will operate correctly. */

/* 这个程序每隔1秒钟输出一个整数,10秒钟后结束程序。
按escape键提前退出程序 。*/

#include
#include
#include

/* Escape key */
#define VK_ESC 0x11b

#define TIMER 0x1c /* 时钟中断的中断号 */

/* 中断处理函数在C和C++中的表示略有不同。
如果定义了_cplusplus则表示在C++环境下,否则是在C环境下。 */

#ifdef __cplusplus
#define __CPPARGS ...
#else
#define __CPPARGS
#endif

int TimerCounter=0; /* 计时变量,每秒钟增加18。 */

/* 指向原来时钟中断处理过程入口的中断处理函数指针(句柄) */
void interrupt ( *oldhandler)(__CPPARGS);

/* 新的时钟中断处理函数 */
void interrupt newhandler(__CPPARGS)
{
/* increase the global counter */
TimerCounter++;

/* call the old routine */
oldhandler();
}

/* 设置新的时钟中断处理过程 */
void SetTimer(void interrupt (*IntProc)(__CPPARGS))
{
oldhandler=getvect(TIMER);
disable(); /* 设置新的时钟中断处理过程时,禁止所有中断 */
setvect(TIMER,IntProc);
enable(); /* 开启中断 */
}

/* 恢复原有的时钟中断处理过程 */
void KillTimer()
{
disable();
setvect(TIMER,oldhandler);
enable();
}


void main(void)
{
int key,time=0;

SetTimer(newhandler); /* 修改时钟中断 */

for (;;)
{
if (bioskey(1))
{
key=bioskey(0);
if (key==VK_ESC) /* 按escape键提前退出程序 */
{
printf("User cancel!\n");
break;
}
}
if (TimerCounter>18) /* 1秒钟处理一次 */
{
/* 恢复计时变量 */
TimerCounter=0;
time++;
printf("%d\n",time);
if (time==10) /* 10秒钟后结束程序 */
{
printf("Program terminated normally!\n");
break;
}
}
}
KillTimer(); /* 恢复时钟中断 */

}


游戏中的各种形状及整个游戏空间怎么用数据表示?

以后我提到的形状都是指下面七种形之一及它们旋转后的变形体。

□□□□ □□□□ □□□□ □□□□
□■□□ □■■□ □□□□ □□□□
□■□□ □■□□ □■□□ □■■□
□■■□ □■□□ ■■■□ ■■□□

□□□□ □■□□ □□□□
□□□□ □■□□ □□□□
■■□□ □■□□ □■■□
□■■□ □■□□ □■■□

我定义了一个结构来表示形状。
struct shape
{
int xy[8];
int color;
int next;
}
-1 0 1 2
-3□□□□
-2□□□□
-1□□□□
0□■□□

  所有的各种形状都可以放在4x4的格子里。假定第二列,第四行的格子坐标为(0,0)(如上图中黑块所示),则每个形状的四个方块都可以用4
个数对来表示。坐标x从左向右依次增加,y从上到下依次增加。表示的时候,组成该形状的四个方块从左到右,从上到下(不一定非要按这个顺
序)。如上面七种形状的第一个用数对来表示就是(-2,0)、(-1,0)、(0,0)、(1,0)。结构shape中的xy就是用来表示这4个数对的。为了简化程序,用一维数组xy[8]来表示。xy[0]、xy[1]表示第一个数对,xy[2]、xy[3]表示第二个数对,依次类推。
shape中的color表示形状的颜色,不同的形状有不同的颜色。七种形状及它们旋转后的变形体一共有19种形状,用一个全局数组表示。假定旋转的方向是逆时针方向(顺时针方向道理一样)。shape中的next就表示当前形状逆时针旋转后的下一个形状的序号。例如:第一种形状及其旋
转变形的形状用结构表示如下。

□□□□ □□□□ □□□□ □□□□
□■□□ □□□□ □■■□ □□□□
□■□□ □□■□ □□■□ ■■■□
□■■□ ■■■□ □□■□ ■□□□

struct shape shapes[19]=
{
/*{x1,y1,x2,y2,x3,y3,x4,y4, color, next}*/
{ 0,-2, 0,-1, 0, 0, 1, 0, CYAN, 1}, /* */
{-1, 0, 0, 0, 1,-1, 1, 0, CYAN, 2}, /* # */
{ 0,-2, 1,-2, 1,-1, 1, 0, CYAN, 3}, /* # */
{-1,-1,-1, 0, 0,-1, 1,-1, CYAN, 0}, /* ## */

……

}

  游戏空间指的是整个游戏主要的界面(呵呵,这个定义我实在想不出更准确的,还请哪位大虾指点)。实际上是一个宽10格子、高20格子的
游戏板。用一个全局数组board[12][22]表示。表示的时候:board[x][y]为1时表示游戏板上(x,y)这个位置上已经有方块占着了,board[x][y]
为0表示游戏板上这位置还空着。为了便于判断形状的移动是否到边、到底,初始的时候在游戏板的两边各加一列,在游戏板的下面加一行,全
部填上1,表示不能移出界。即board[0][y],board[11][y](其中y从0到21)初始都为1,board[x][21](其中x从1到10)初始都为1。
1 2 3 4 5 6 7 8 910
1□□□□□□□□□□
2□□□□□□□□□□
3□□□□□□□□□□
4□□□□□□□□□□
5□□□□□□□□□□
6□□□□□□□□□□
7□□□□□□□□□□
8□□□□□□□□□□
9□□□□□□□□□□
10□□□□□□□□□□
11□□□□□□□□□□
12□□□□□□□□□□
13□□□□□□□□□□
14□□□□□□□□□□
15□□□□□□□□□□
16□□□□□□□□□□
17□□□□□□□□□□
18□□□□□□□□□□
19□□□□□□□□□□
20□□□□□□□□□□

prog6.c演示了用结构表示各种形状的方法。虽然程序稍长一些,但并不是特别复杂。其中游戏板初始化部分并没有真正用到,但是后面的程
序会用到的。其中SIZE定义为16,这样将整个屏幕的坐标系由原来的640×480转换成40×30(640/16=40,480/16=30)。游戏中所有的坐标都是基于40×30的坐标系的,这样有助于简化程序。坐标的转换在程序中由DrawBlock(int x,int y)来体现。

  新的坐标系如下图所示:
-8-7-6-5-4-3-2-1 0 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031
-4□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
-3□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
-2□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
-1□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
0□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
1□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
2□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
3□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□
4□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□
5□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□
6□□□□□□□□□■■■■■■■■■■□□□■■■■□□□□□□□□□□□□□□
7□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
8□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
9□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
10□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
11□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
12□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
13□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
14□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
15□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
16□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
17□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
18□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
19□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
20□□□□□□□□□■■■■■■■■■■□□□□□□□□□□□□□□□□□□□□□
21□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
22□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
23□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
24□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
25□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
26□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

  新坐标中最主要的是就是上面两块黑色的部分。左边那块大的就是游戏板(横坐标从1到10,纵坐标从1到20),右边那块小的就是显示“下一个”形状的部分(横坐标从14到17,纵坐标从3到6)。这个新的坐标系是整个游戏的基础,后面所有的移动、变形等的计算都是基于这个坐标系的。


游戏中怎么判断左右及向下移动的可能性?

  看懂了前面的各种形状和游戏板等的表示,接下来的东西就都好办多了。先来看一下某个形状如何显示在游戏板当中。假设要在游戏板中
显示第一个形状。第一个形状在结构中的表示如下:

struct shape shapes[19]=
{
/*{x1,y1,x2,y2,x3,y3,x4,y4, color, next}*/
{ 0,-2, 0,-1, 0, 0, 1, 0, CYAN, 1},

……

}

  那么这个组成形状四个方块的坐标表示为(0,-2)、(0,-1)、(0,0)和(1,0)。这实际上是相对坐标。假形状的实际坐标指的是4x4方块中的第
二列、第三行的方块的位置,设这个位置为(x,y)。那么组成这个形状的四个小方块的实际坐标(以第一个形状为例)就是(x+0,y-2)、(x+0,y-1)、(x+0,y+0)和(x+1,y+0)。由于所有的形状都可以在4x4的方块阵列中表示,这样就找到了一种统一的方法来表示所有的形状了。

-1 0 1 2
-3□□□□ 相对坐标
-2□■□□
-1□■□□ 组成第一种形状的四个方块的相对坐标为(0,-2)、(0,-1)、(0,0)和(1,0)。
0□■■□

让我们看看形状是如何显示在游戏板中的(以第一个形状为例)。

1 2 3 4 5 6 7 8 910
1□■□□□□□□□□ 形状的坐标为(2,3)。组成形状的四个方块的坐标由形状的
2□■□□□□□□□□ 坐标加上这四个小方块各自的相对坐标得出。它们分别是:
3□■■□□□□□□□ (2+0,3-2)、(2+0,3-1)、(2+0,3-0)和(2+1,3-0)。即:
4□□□□□□□□□□ (2,1)、(2,2)、(2,3)和(3,3)。如左图所示。
5□□□□□□□□□□
6□□□□□□□□□□
7■□□□□□□□□□ 形状的坐标为(1,9)。组成形状的四个方块的坐标分别是:
8■□□□□□□□□□ (1+0,9-2)、(1+0,9-1)、(1+0,9-0)和(1+1,9-0)。即:
9■■□□□□□□□□ (1,7)、(1,8)、(1,9)和(2,9)。如左图所示。
10□□□□□□□□□□
11□□□□□□□□□□
12□□□□□□□□□□
13□□□□□□□□□□
14□□□□□□□□□□
15□□□□□□□□□□
16□□□□□□□□□□
17□□□□□□□□□□
18□□□□□□□□■□ 形状的坐标为(9,20)。组成形状的四个方块的坐标分别是:
19□□□□□□□□■□ (9+0,20-2)、(9+0,20-1)、(9+0,20-0)和(9+1,20-0)。即:
20□□□□□□□□■■ (9,18)、(9,19)、(9,20)和(10,20)。如左图所示。

  从现在起,我不再举别的示例程序了。从现在开始所有的示例代码均来自于我写的"Russia.c"。为了记录游戏板的状态,用了一个全局数组board[12][22]。board[x][y](其中x从0到11,y从1到21)等于1表示(x,y)这个位置已经被填充了,组成形状的四个方块的坐标都不能为(x,y),否则将发生冲突。board[x][y](其中x从1到10,y从1到20)等于表示(x,y)这个位置还没有被填充。

  游戏板初始化时,给board[0][y],board[11][y](其中y从1到21)都赋为1,给board[x][21](其中x从1到10)都赋为1。这相当于一开始就给游戏板左右和下方加了个“边”。所有的形状都不能够移入这个“边”,否则将发生冲突。

  现在我们可以开始讨论如何判断一个形状向左、向右和向下移动的可能性了。先说个概念,“当前形状”是指那个正在下落还没有落到底的那个形状。如果当前形状向左移动,不与游戏板现有状态发生冲突,则可以向左移动。具体做法是:先假设当前形状已经向左移动了,判断此时是否与游戏板现有状态发生冲突。如果不发生冲突,则可以向左移动。否则,不可以向左移动。

  判断索引号为ShapeIndex的形状在坐标(x,y)是否与游戏板当前状态发生冲突的代码如下。我把详细的说明加在这段代码中。

enum bool Confilict(int ShapeIndex,int x,int y)
{
int i;

/* 对组成索引号为ShapeIndex的形状的四个方块依次判断 */
for (i=0;i<=7;i++,i++) /* i分别取0,2,4,6 */
{
/* 如果四个方块中有任何一个方块的x坐标小于1或大于10,表示超出左边界或右边界。
此时,发生冲突。 */
if (shapes[ShapeIndex].xy[i]+x<1 ||
shapes[ShapeIndex].xy[i]+x>10) return True;

/* 如果四个方块中某个方块的y坐标小于1,表示整个形状还没有完全落入游戏板中。
此时,没有必要对这个方块进行判断。*/
if (shapes[ShapeIndex].xy[i+1]+y<1) continue;

/* 如果四个方块中有任何一个方块与游戏板当前状态发生冲突,则整个形状在(x,y)处
与游戏板当前状态冲突 */
if (board[shapes[ShapeIndex].xy[i]+x][shapes[ShapeIndex].xy[i+1]+y])
return True;
}

/* 四个方块中没有任何一个方块与游戏板当前状态发生冲突,则整个形状在(x,y)处
没有与游戏板当前状态冲突 */
return False;
}

对以上代码附加说明如下:
shapes[ShapeIndex].xy[i](其中i等于0,2,4,6)表示组成索引号为ShapeIndex的形状的某个方块的x相对坐标。(i等于0时,表示第1个方块的x相对坐标;i等于2时,表示第2个方块的x相对坐标;i等于4时,表示第3个方块的x相对坐标;i等于6时,表示第4个方块的x相对坐标。)

shapes[ShapeIndex].xy[i](其中i等于1,3,5,7)表示组成索引号为ShapeIndex的形状的某个方块的y相对坐标。(i等于1时,表示第1个方块的y相对坐标;i等于3时,表示第2个方块的y相对坐标;i等于5时,表示第3个方块的y相对坐标;i等于7时,表示第4个方块的y相对坐标。)

shapes[ShapeIndex].xy[i]+x(其中i等于0,2,4,6)表示索引号为ShapeIndex的形状的坐标为(x,y)时,组成该形状的某个方块的x实际坐标。(i等于0时,表示第1个方块的x实际坐标;i等于2时,表示第2个方块的x实际坐标;i等于4时,表示第3个方块的x实际坐标;i等于6时,表示第4个方块的x实际坐标。)

shapes[ShapeIndex].xy[i]+y(其中i等于1,3,5,7)表示索引号为ShapeIndex的形状的坐标为(x,y)时,组成该形状的某个方块的y实际坐
标。(i等于1时,表示第1个方块的y实际坐标;i等于3时,表示第2个方块的y实际坐标;i等于5时,表示第3个方块的y实际坐标;i等于7时,表示第4个方块的y实际坐标。)

现在来看看这句是什么意思吧。
board[shapes[ShapeIndex].xy[i]+x][shapes[ShapeIndex].xy[i+1]+y]

可以这样理解,把上面一句分开来看::

ActualX=shapes[ShapeIndex].xy[i]+x;/* 其中x为0,2,4,6 */
表示某个方块实际的x坐标。

ActualY=[shapes[ShapeIndex].xy[i+1]+y;
表示某个方块实际的y坐标。

board[ActualX][ActualY]就是与某个方块坐标相同处的游戏板的标志。如果此标志不为0(为1),表示这个方块与游戏板发生冲突。如果此标志为0,表示这个方块没有与游戏板发生冲突。

这段写的比较长,但是不是特别难理解。游戏中很多地方都用到了这种相对坐标向实际坐标的转换方式,看懂了这一段对理解其他部分的代码很有帮助。


仔细看过这段代码后,你可能会提一个问题:不是已经在游戏板的左右两边都加了“边”了吗,为什么还要加下面这个对x坐标的判断呢?

/* 如果四个方块中有任何一个方块的x坐标小于1或大于10,表示超出左边界或右边界。
此时,发生冲突。 */
if (shapes[ShapeIndex].xy[i]+x<1 ||
shapes[ShapeIndex].xy[i]+x>10) return True;

这是因为有一种特殊情况,如下图所示:

■■ 
■ 2 3 4 5 6 7 8 910
1■□□□□□□□□□ 这在当前形状刚出来的时候,是可能发生的。但是我们只给游戏板
2□□□□□□□□□□ 加了一层“边”。对于这个形状的最左边的那个方块将失去判断,
3□□□□□□□□□□ 如果不予理会,这个形状将会“挂”在游戏板的左上角!当初我也
4□□□□□□□□□□ 没有想到这一点,后来发现会有形状“挂”在最顶层,而导致游戏
5□□□□□□□□□□ 提前退出。发现了这个问题。
6□□□□□□□□□□
7□□□□□□□□□□
8□□□□□□□□□□ 加了这个判断后,游戏板的左右两个“边”对冲突的判断就是去意
9□□□□□□□□□□ 义了。因为没有这两个“边”,对于冲突的判断也不会出错。不过
10□□□□□□□□□□ 为了程序易于理解,还是保留了游戏板的左右两个“边”。
11□□□□□□□□□□
12□□□□□□□□□□
13□□□□□□□□□□
14□□□□□□□□□□
15□□□□□□□□□□
16□□□□□□□□□□
17□□□□□□□□□□
18□□□□□□□□□□
19□□□□□□□□□□
20□□□□□□□□□□

  如果你对我上面提出的新问题及对于这个问题的解释不太明白,没关系,这并不重要。因为现在才刚刚开始,而且刚才所说的这个问题只
有在特殊情况下才出现(当然,一旦发生上面说的问题,游戏就出错啦!^_^ ),对于理解整个程序的思路影响不大。看多了就会明白了(你
会说:原来就这么简单!)。  


资源下载

如果有需要这个系统的源码、仿真、论文等资源的可以私信我。感谢你的阅读~

;