8*8点阵贪吃蛇

功能简介

一个贪吃蛇游戏,运行在51单片机入门开发板上,显示在8*8点阵上,可以发出声音

代码

main.c



/*贪吃蛇第二版,初始长度为3的贪吃蛇,可移动,撞到边界或自己会死亡
相比第一版,改变了按键位置,使操作更方便,禁止了贪吃蛇原地回头
增加了贪吃蛇吃豆功能
增加了自动移动,添加了游戏音效,添加了死亡动画*/

/*
连线说明:请将J24上OE与GND链接,从而使点阵屏能显示
操作说明:K1贪吃蛇向上移动、K2贪吃蛇向下移动、K3贪吃蛇向左移动、K4贪吃蛇向右移动
*/
/*
使用数组screen作为显示存储器,一个数组元素控制一列像素,screen[0]控制最左列像素,一个元素的第一位控制最下面一个像素,置1表示像素点亮,
置0表示像素点灭。8*8点阵,纵向取模,字节正序 
*/

#include<reg51.h>
#include<intrins.h>
#include<Music.h>		//音乐播放头文件
#include<queue.h>		//队列操作头文件
#include <stdlib.h>		  //随机数头文件


#define uchar unsigned char
#define uint unsigned int
#define T0_NUM 1000
#define GPIO_KEY P1			//按键接口输入
#define DIE 0		 //贪吃蛇死亡
#define EAT 1		 //吃豆
#define MOV 2		 //移动
#define NUL 3		 //未定义指令

bit flag_s = 1;

uchar code number[]={0x00,0x00,0x22,0x7E,0x02,0x00,0x22,0x46,0x4A,0x32,0x00,0x42,0x52,0x52,0x6C,0x00,0x3C,
0x04,0x7E,0x04,0x00,0x72,0x52,0x52,0x4C,0x00,0x3C,0x52,0x52,0x0C,0x00,0x40,0x4E,
0x50,0x60,0x00,0x2C,0x52,0x52,0x2C,0x00,0x30,0x4A,0x4A,0x3C,};	 //mun	: num*5-4	~	num*5			

sbit Clock = P3^6;		//Clock移位信号
sbit Data = P3^4;		//Data数据输出
sbit Lock = P3^5;		//Lock锁存信号
void send_74HC595(uchar LedVer);	 //向移位寄存器发送字节
/*以上是对点阵的纵向控制的准备,由于使用的入门开发板,点阵屏的纵向由移位寄存器控制,所以纵向数据需要通过移位寄存器输入*/


void JzKey();	 //矩阵按键
void KeyCheck();		//按键执行

void MoveServ(uchar STA);	 //移动后服务
uchar CheckSnake(uchar dire);	//贪吃蛇的检查与移动
unsigned char screen[8],die[]={0xFB,0x8B,0x73,0x8B,0xFB,0x8B,0xFB,0xAB};	  //显存,死亡画面

//void DrawSnake();			//贪吃蛇显示程序,将贪吃蛇画在显存中

uchar KeyValue=0;

uchar key_step=0;		//	按键执行步骤

SqQueue snake;			//创建结构体snake

uchar Bean;  	//豆子坐标

bit flag_die = 0;

void DrawPoint(uchar point);

void NewBean();

void main()
{
	TMOD &= 0X0F;
	TMOD |= 0X10;
	TH1 = (65536 - T0_NUM) / 256;
	TL1 = (65536 - T0_NUM) % 256;		//计时器0每2ms对点阵屏进行一次扫描
	EA = 1;
	ET1 = 1;
	TR1 = 1;

		

	MainInitialize_Music();	 //音乐模块初始化
	InitQueue(&snake);			//贪吃蛇队列初始化
	AddQueue(&snake,0x03);	 //为贪吃蛇添加两节身体
	AddQueue(&snake,0x13);
	DrawPoint(GetHead(&snake));
	DrawPoint(GetRear(&snake));
	NewBean();

	while(1)
	{
		if(flag_s)
			Auto_PlayMusic();
	}
}
void DrawPoint(uchar point)
{
	screen[point >> 4]|=0x01 << (point&0x0f); 	//画上点
}

void NewBean()
{
	uchar i,j,s_rear;
	s_rear = snake.rear;
	for(i=0;i<8;i++)
		for(j=0;j<8;j++)
			if(!(screen[i]&(0x01<<j)))
				AddQueue(&snake,(i<<4)+j);
	snake.rear=s_rear;
	srand(rand());
	Bean = snake.base[(snake.rear+rand()%((snake.head+64-snake.rear)%64))%64];
	DrawPoint(Bean);			
}

/*下面是计时器0的中断服务程序,我们需要在下面的程序中完成,对按键的扫描,和屏幕的刷新,
如果按键按下,那么要在屏幕刷新前,完成对贪吃蛇位置的计算,和对显示存储数组的刷新*/
void T1_int() interrupt 3	   
{
	static unsigned char NowVer = 0,fine = 0x80;		//NowVer当前扫描列
										 
	TH1 = (65536 - T0_NUM) / 256;
	TL1 = (65536 - T0_NUM) % 256;	  //重置计数器
	if(!flag_die)
	{	
		JzKey();	//矩阵按键检测
		KeyCheck();		//执行按键效果
	}
	 
	/*点阵屏刷新程序*/
	P0 = 0xff;						//位选信号(行信号)置零,消鬼	
	if(!flag_die)
		send_74HC595(screen[NowVer]);	//(列信号)赋值
	else
		send_74HC595(die[NowVer]);	//死亡画面
	P0 = ~(fine>>NowVer);	//点阵行是低电平触发,数组内容是高电平有效,所以取反
	NowVer++;						   //每中断1次(2ms),NowVer加1
	if(NowVer>=8)		//列回车判断,完成一次扫描周期	16ms	
	{
		NowVer=0;
	}
}




void JzKey()   //矩阵按键程序
{
	static uchar keybuf = 0;			//按键缓冲区,记录按键的历史检测值											

	GPIO_KEY=0x0f;  //矩阵按键低4位低电平,高四位为高电平,测试行

	if(GPIO_KEY!=0x0f)
		keybuf = (keybuf << 1) | 0;
	else
		keybuf = (keybuf << 1) | 1;

    if ((keybuf == 0x00)&(key_step == 0))			//连续 8 次扫描值都为 0,即 16ms 内都只检测到按下状态时,可认为按键已按下
	{
		key_step = 1;					//key_step=1:第一步,读取按键值
		switch(GPIO_KEY)
		{
		case(0x07):KeyValue=1;break;
		case(0x0b):KeyValue=2;break;
		case(0x0d):KeyValue=3;break;
		case(0x0e):KeyValue=4;break;
			
		}	
		GPIO_KEY=0xf0;//与上相反,测试列			
		switch(GPIO_KEY)
		{
		case(0x70):KeyValue=KeyValue;break;
		case(0xb0):KeyValue=KeyValue+4;break;
		case(0xd0):KeyValue=KeyValue+8;break;
		case(0xe0):KeyValue=KeyValue+12;break;					
		}
	}
	else if ((keybuf == 0xff)&(key_step==2))	  //连续 8 次扫描值都为 1,即 16ms 内都只检测到弹起状态时,可认为按键已弹起
	{	
		key_step = 0;						 //第三步,归零步骤
						
    }
   	 //其它情况则说明按键状态尚未稳定
    					
}
void KeyCheck()			   //按键检测程序,执行按键命令
{

	if(key_step==1)	  //按键状态变换	
	{
		key_step = 2;			  //key_step=2:第二步,执行按键命令
		MoveServ(CheckSnake(KeyValue));
	
	}	
}


uchar CheckSnake(uchar dire)	//执行蛇头移动,dire:移动方向
{
	uchar SnakeHead,x,y;
	static uchar back = 16;	  //初始移动方向

	SnakeHead = GetRear(&snake);
	x = SnakeHead >> 4;				 //0~7
	y = SnakeHead & 0x0f;			 //0~7

	switch(dire)		//第一步,检查是否越界或回头
	{
		case 11:		//向上移动 
			if(back==15)
				return NUL;
			else if(y < 7)			
				SnakeHead += 0x01;
			else
				return DIE;
			break;
		case 15:	   //向下移动
			if(back==11)
				return NUL;
			else if(y > 0)  
				SnakeHead -= 0x01;	
			else
				return DIE;
			break;
		case 14:	  //向左移动
			if(back==16)
				return NUL;
			else if(x > 0)  
				SnakeHead -= 0x10;	
			else
				return DIE;
			break;
		case 16:	  //向右移动
			if(back==14)
				return NUL;
			else if(x < 7)  
				SnakeHead += 0x10;	
			else
				return DIE;
			break;
		case 1:
			flag_s = !flag_s;
		default:
			return NUL;
			break;
	}
//	P2=~SnakeHead>>4;
	back = dire;
	if(SnakeHead == Bean)	//二步,检查是否吃豆
	{
		AddQueue(&snake,SnakeHead);	 //新蛇头
		DrawPoint(GetRear(&snake));
//		screen[(GetRear(&snake)>>4)]|=0x01 << (GetRear(&snake)&0x0f); 	//画上蛇头
		NewBean();
		return EAT;
	}

	else if(screen[SnakeHead>>4] & (0x01 << (SnakeHead&0x0f))) //第三步,检查是否自食
		return DIE;

	else
	{
		AddQueue(&snake,SnakeHead);	 //新蛇头
		DrawPoint(GetRear(&snake));
	//	screen[(GetRear(&snake)>>4)]|=0x01 << (GetRear(&snake)&0x0f); 	//画上蛇头
		screen[(GetHead(&snake)>>4)]&=~(0x01 << (GetHead(&snake)&0x0f)); //擦除蛇尾
		DelQueue(&snake);		//弃蛇尾
		return MOV;
	}
}

void MoveServ(uchar STA)	 //移动后服务
{
	switch(STA)		//检查移动状态
	{
		case MOV:
		 	WriteMusicOrder(1);
			break;
		case NUL:
		 	WriteMusicOrder(2);	break;
		case EAT:
			WriteMusicOrder(3);break;
		case DIE:
		 	flag_die = 1; break;
	}
}



//void DrawSnake()			//贪吃蛇显示程序,将贪吃蛇画在显存中
//{
//
//	screen[(GetHead(&snake)>>4)]&=~(0x01 << (GetHead(&snake)&0x0f)); 
//	screen[(GetRear(&snake)>>4)]|=0x01 << (GetRear(&snake)&0x0f); 
//
//	//snake高四位作为x坐标,低四位作为y坐标
//	//x坐标控制点画在屏幕的第几列,y坐标控制点向上移动多少行
//}

void  send_74HC595(uchar LedVer)			//发送列信号LedVer
{
	uchar i,temp = 0x80;
	for(i = 0;i<8;i++)			
	{
		if( LedVer&(temp>>i) )	//按位分解LedVer
			Data = 1;
		else
			Data = 0;
		Clock = 0;
		Clock = 1;		//移位
	}
	Lock = 0;
	Lock = 1;		//锁存
}

 queue.h



/*********************queue.h*************************************/

typedef unsigned char Status; // Status 是函数的类型,其值是函数结果状态代码,如 OK 等
typedef unsigned char QElemType;
#define uchar unsigned char
#define MAXQSIZE 64 // 最大队列长度(对于循环队列,最大队列长度要减 1)	

typedef struct
{
	uchar base[MAXQSIZE]; // 初始化的静态分配内存
	uchar head; // 头指针,若队列不空,指向队列头元素
	uchar rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;

//--定义使用的IO口--//

//--声明调用函数--//
Status InitQueue(SqQueue *Q);
Status AddQueue(SqQueue *Q,QElemType e);	//new蛇头
Status DelQueue(SqQueue *Q);		//del蛇尾
Status GetHead(SqQueue* Q);			//get蛇尾
Status GetRear(SqQueue* Q);			//get蛇头
Status QueueLength(SqQueue *Q);		//get蛇长

queue.c


/*纯c语言队列*/


#include<stdio.h>
#define OK 1
#define ERROR 0
//typedef unsigned char uchar; // Status 是函数的类型,其值是函数结果状态代码,如 OK 等
//typedef	unsigned char Status;
//typedef unsigned char QElemType;
//#define MAXQSIZE 64 // 最大队列长度(对于循环队列,最大队列长度要减 1)

#include<queue.h>		//队列操作头文件

//typedef struct
//{
//	uchar base[MAXQSIZE]; // 初始化的静态分配内存
//	uchar head; // 头指针,若队列不空,指向队列头元素
//	uchar rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
//}SqQueue;


Status InitQueue(SqQueue *Q)
{
// 初始化空队列Q,

  (*Q).head=(*Q).rear=0;
  return OK;
}


Status AddQueue(SqQueue *Q,QElemType e)	//new蛇头
{
	// 插入元素e为Q的新的队尾元素,如果队伍满了怎么办  (*Q)
  if(((*Q).rear+1)%MAXQSIZE==(*Q).head)
  {
      return ERROR;
  }

  (*Q).base[(*Q).rear]=e;
  (*Q).rear=((*Q).rear+1)%MAXQSIZE;
  return OK;
}


Status DelQueue(SqQueue *Q)		//del蛇尾
{
// 若队列不空, 则删除Q的队头元素,并返回e; 否则返回ERROR
	uchar e;
   if((*Q).head==(*Q).rear)
   {
       return ERROR;
   }
   e=(*Q).base[(*Q).head];
   (*Q).head=((*Q).head+1)%MAXQSIZE;
   return e;
}


Status GetHead(SqQueue* Q)			//get蛇尾
{
// 若队列不空,则返回队头元素,否则返回ERROR
   if((*Q).head==(*Q).rear)
   {
       return ERROR;
   }
  return (*Q).base[(*Q).head];
}


Status GetRear(SqQueue* Q)			//get蛇头
{

  return (*Q).base[((*Q).rear+MAXQSIZE-1)%MAXQSIZE];
}



Status QueueLength(SqQueue *Q)		//get蛇长
{
// 返回Q的元素个数
   return ((*Q).rear-(*Q).head+MAXQSIZE)%MAXQSIZE;
}

 Music.h



/*********************Music.h*************************************/
#define uchar unsigned char
#define uint unsigned int

//--定义使用的IO口--//

sbit BUZZ = P2^5;  //蜂鸣器控制引脚

//--声明调用函数--//
void WriteMusicOrder(uchar num);
void MainInitialize_Music();	//主函数初始化函数
void Auto_PlayMusic();	//预设音乐演奏函数
uchar PlayMusic(uchar *MusicNote,uchar *MusicBeat,uchar MusicLong); 
		//音乐播放函数,音符数组,节拍数组,音符/节拍长度

 Music.c



/* ---------------------------

   	Music音乐播放函数

----------------------------*/

#include <reg52.h>
#include<string.h>
#include<Music.h>
#define uchar unsigned char
#define uint unsigned int 

unsigned char T0RH = 0xFF;  //T0重载值的高字节
unsigned char T0RL = 0x00;  //T0重载值的低字节
uchar MusicOrder=0;	//音乐播放器控制指令

unsigned int code NoteFrequ[] = {  //低音1-7中音1-7和高音1-7对应频率列表
	262,  294,  330,  349,  392,  440,  494,  //低音1-7
    523,  587,  659,  698,  784,  880,  988,  //中音1-7
    1047, 1175, 1319, 1397, 1568, 1760, 1976  //高音1-7
};
unsigned int code NoteReload[] = { //低音1-7中音1-7和高音1-7对应的定时器重载值
	65536 - (11059200/12) / (262*2),  //低音1
	65536 - (11059200/12) / (294*2),  //2
	65536 - (11059200/12) / (330*2),  //3
	65536 - (11059200/12) / (349*2),  //4
	65536 - (11059200/12) / (392*2),  //5
	65536 - (11059200/12) / (440*2),  //6
    65536 - (11059200/12) / (494*2),  //7
    65536 - (11059200/12) / (523*2),  //中音1
    65536 - (11059200/12) / (587*2),  //2
    65536 - (11059200/12) / (659*2),  //3
    65536 - (11059200/12) / (698*2),  //4
    65536 - (11059200/12) / (784*2),  //5
    65536 - (11059200/12) / (880*2),  //6
    65536 - (11059200/12) / (988*2),  //7
    65536 - (11059200/12) / (1047*2), //高音1
    65536 - (11059200/12) / (1175*2), //2
    65536 - (11059200/12) / (1319*2), //3
    65536 - (11059200/12) / (1397*2), //4
    65536 - (11059200/12) / (1568*2), //5
    65536 - (11059200/12) / (1760*2), //6
    65536 - (11059200/12) / (1976*2), //7
};
//预设音符表1
uchar code NormolNote[]={10,14};
uchar code NormolBeat[]={1,1};

//预设音符表2
uchar code ErrorNote[]={4,2};
uchar code ErrorBeat[]={1,1};

//预设音符表3
uchar code GoodNote[]={17,19,20};
uchar code GoodBeat[]={1,1,2};

bit enable = 0;   //蜂鸣器发声使能标志
bit tmrflag = 0;  //定时器中断完成标志

void WriteMusicOrder(uchar num)
{
	MusicOrder=num;

}

void MainInitialize_Music()
{	    
    EA = 1;       //使能全局中断
    TMOD &= 0xf0;  //配置T0工作在模式1
	TMOD |= 0x01;
    TH0 = T0RH;
    TL0 = T0RL;
    ET0 = 1;      //使能T0中断
    TR0 = 1;      //启动T0
}

void Auto_PlayMusic()	//预设音乐演奏函数,放在main函数while(1)循环中
{
	switch(MusicOrder)
	{
		case 1:	  PlayMusic(NormolNote,NormolBeat,2);break;
		case 2:	  PlayMusic(ErrorNote,ErrorBeat,2);break;
		case 3:	  PlayMusic(GoodNote,GoodBeat,3);break;
	}
}

/* 乐曲播放函数 */
uchar PlayMusic(uchar *MusicNote,uchar *MusicBeat,uchar MusicLong)
{
    unsigned char beat;   //当前节拍索引
    unsigned char note;   //当前节拍对应的音符
    unsigned int time = 0;      //当前节拍计时
    unsigned int beatTime = 0;  //当前节拍总时间
    unsigned int soundTime = 0; //当前节拍需发声时间
	 MusicOrder=0;		//音乐指令
    for (beat=0; beat<MusicLong; )  //用节拍索引作为循环变量
    {
        while (!tmrflag);  //每次定时器中断完成后,检测并处理节拍
        tmrflag = 0;
		if(MusicOrder!=0)	//检测到中止信号后退出程序
			return 0;
        if (time == 0)  //当前节拍播完则启动一个新节拍
        {
            note = *(MusicNote+beat) - 1;
            T0RH = NoteReload[note] >> 8;
            T0RL = NoteReload[note];
            //计算节拍总时间,右移2位相当于除4,移位代替除法可以加快执行速度
            beatTime = ((*(MusicBeat+beat)) * NoteFrequ[note]) >> 2;
            //计算发声时间,为总时间的0.75,移位原理同上
            soundTime = beatTime - (beatTime >> 2);
            enable = 1;  //指示蜂鸣器开始发声
            time++;
        }
        else  //当前节拍未播完则处理当前节拍
        {
            if (time >= beatTime)  //当前持续时间到达节拍总时间时归零,
            {                      //并递增节拍索引,以准备启动新节拍
                time = 0;
                beat++;
            }
            else  //当前持续时间未达到总时间时,
            {
                time++;   //累加时间计数
                if (time == soundTime)  //到达发声时间后,指示关闭蜂鸣器,
                {                       //插入0.25*总时间的静音间隔,
                    enable = 0;         //用以区分连续的两个节拍
                }
            }
        }
    }
	return 0;
}
/* T0中断服务函数,用于控制蜂鸣器发声 */
void InterruptTimer0() interrupt 1
{
    TH0 = T0RH;   //重新加载重载值
    TL0 = T0RL;
    tmrflag = 1;
    if (enable)   //使能时反转蜂鸣器控制电平
        BUZZ = ~BUZZ;
    else          //未使能时关闭蜂鸣器
        BUZZ = 1;
}

实物

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值