⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 snake.c

📁 贪食蛇游戏 贪食蛇游戏 贪食蛇游戏 贪食蛇游戏
💻 C
字号:
//贪食蛇程序代码
//贪吃蛇游戏程序,屏左半部用于游戏活动,右半部为分数显示
//游戏屏为16*16游戏点阵,可容纳蛇身块数256。每个游戏点阵又由4*4个LCD基本点阵组成
//蛇行标志在定时器上置位,这里为游戏的主体部分。

#define uchar unsigned char
#define uint  unsigned int
#define ulong unsigned long
//#define LCMD XBYTE[0xAfff]  // 液晶数据口
//#define LCMC XBYTE[0xAbff]  // 液晶命令口
#define TIME_RUN 10     //定时器分品系数



#include "intrins.h"
//游戏部分
//x,y最大极限
#define MAX_GAME_X 15
#define MAX_GAME_Y 12

//#define lcd_no_read 1    //编译选项,把这项屏蔽掉就采用LCD读出方式,否则采用显存形式

xdata uchar snake_flag,   //蛇头标志  7  6  5  4  3  2         1     0 
        //          上 下 左 右 x  gameover  food  run
         snake_len,   //蛇身长度
         snake_food;   //食物位置,高4位Y,低4位x

xdata uchar snake_body[256]; //蛇身每个部分的数据
       //          7  6  5  4  3  2  1  0
       //   高4位Y方向  低4位X方向 
#ifdef lcd_no_read
xdata uchar lcd_buf[8][64];//lcd缓冲,用于记录LCD内部的点阵,可以理解为显存
       //当LCD无读出功能时,就要采用显示缓冲。本LCD为可读,一般不用这个功能
       //缓冲只记录蛇身活动的部分,即LCD左半屏
#endif
extern uchar xdata set_time;   //贪吃蛇游戏频率计数值
/******************************************************
* 游戏LCD部分,根据游戏的特点把LCD分成16*16块
* 用作游戏点阵,
*******************************************************/
//
//函数名:clr_game_dot
//功能:清一个游戏点
//输入参数:游戏点的X,Y坐标
//注意事项:这里的X,Y坐标和LCD底层的X,Y坐标不同,他最大只能是MAX_GAME_X,MAX_GAME_Y
//使用方式:内部调用
void clr_game_dot(uchar x,uchar y)
{
xdata uchar lcd_x,i,tmp;
while(x>MAX_GAME_X)x-=(MAX_GAME_X+1); //这个是写程序习惯的保护措施,预防输入范围过大
while(y>MAX_GAME_Y)y-=(MAX_GAME_Y+1);
lcd_x=x<<2;
wricmd(0xb0+y/2);
    wricmd(lcd_x&0x0f);
wricmd(0x10|(lcd_x>>4));
wricmd(0xe0);
if(y%2)//行的下半部
{
  for(i=0;i<4;i++)
  {
#ifdef lcd_no_read    //以下是显存法的清点程序,其他例如亮点的部分和这个原理一样

   tmp=lcd_buf[y>>1][(x<<2)+i];   //先从缓冲读出要修改的LCD片的数据
   tmp&=0x0f;       //清对应的游戏点
   wridata(tmp);
   _nop_();
   lcd_buf[y>>1][(x<<2)+i]=tmp;   //把新数据写回缓冲
#else
  tmp=readdata();
  tmp=readdata();      //读LCD的方法,要求连读2次
  wridata(tmp&0x0f);
#endif
  }
}
else   //行的上半部,下同
{
  for(i=0;i<4;i++)
  {
#ifdef lcd_no_read
  
   tmp=lcd_buf[y>>1][(x<<2)+i];
   tmp&=0xf0;
   wridata(tmp);
   _nop_();
   lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
  tmp=readdata();
  tmp=readdata();     
  wridata(tmp&0xf0);
#endif
  }
}
wricmd(0xee);
}
//函数名:fill_game_dot
//功能:亮一个游戏点
//输入参数:游戏坐标的X,Y坐标    
//注意事项:这里的X,Y坐标和LCD底层的X,Y坐标不同,他最大只能是MAX_GAME_X,MAX_GAME_Y
//    这个函数和上面的clr_game_dot基本相同,只是在写LCD数据的时候是全1而不是0 
//使用方式:内部调用
void fill_game_dot(uchar x,uchar y)
{
xdata uchar lcd_x,i,tmp;
while(x>MAX_GAME_X)x-=(MAX_GAME_X+1);
while(y>MAX_GAME_Y)y-=(MAX_GAME_Y+1);
lcd_x=x<<2;
wricmd(0xb0+y/2);
    wricmd(lcd_x&0x0f);
wricmd(0x10|(lcd_x>>4));
wricmd(0xe0);
if(y%2)//行的下半部
{
  for(i=0;i<4;i++)
  {
#ifdef lcd_no_read  
   tmp=lcd_buf[y>>1][(x<<2)+i];
   tmp|=0xf0;
   wridata(tmp);
   _nop_();
   lcd_buf[y>>1][(x<<2)+i]=tmp;  
#else
  tmp=readdata();
  tmp=readdata();      
  wridata(tmp|0xf0);
#endif
  }
}
else
{
  for(i=0;i<4;i++)
  {
#ifdef lcd_no_read  
   tmp=lcd_buf[y>>1][(x<<2)+i];
   tmp|=0x0f;
   wridata(tmp);
   _nop_();
   lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
  tmp=readdata();
  tmp=readdata();      
  wridata(tmp|0x0f);
#endif
  }
}
wricmd(0xee);
}
//函数名:fill_game_dot2
//功能:亮一个游戏点(另一种方式,这里用来显示食物用)
//输入参数:X,Y
//注意事项:X,Y为游戏的点阵,非LCD点阵...还有LCD填充数据是0x05或0x50
//使用方式:内部调用,显示蛇的食物的时候用这个函数,区分开蛇身和食物.
void fill_game_dot2(uchar x,uchar y)
{
xdata uchar lcd_x,i,tmp;
while(x>MAX_GAME_X)x-=(MAX_GAME_X+1);
while(y>MAX_GAME_Y)y-=(MAX_GAME_Y+1);
lcd_x=x<<2;
wricmd(0xb0+y/2);
    wricmd(lcd_x&0x0f);
wricmd(0x10|(lcd_x>>4));
wricmd(0xe0);
if(y%2)
{
  for(i=0;i<4;i++)
  { 
#ifdef lcd_no_read  
   tmp=lcd_buf[y>>1][(x<<2)+i];
   tmp|=0x50;
   wridata(tmp);
   _nop_();
   lcd_buf[y>>1][(x<<2)+i]=tmp;   
#else
  tmp=readdata();
  tmp=readdata();      
  wridata(tmp|0x50);
#endif
  }
}
else
{
  for(i=0;i<4;i++)
  {
#ifdef lcd_no_read  
   tmp=lcd_buf[y>>1][(x<<2)+i];
   tmp|=0x05;
   wridata(tmp);
   _nop_();
   lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
  tmp=readdata();
  tmp=readdata();      
  wridata(tmp|0x05);
#endif
  }
}
wricmd(0xee);
}

/************************************************************
*
*     游戏算法部分(4*4LCD)
*
**************************************************************/
//函数名 game_init()
//功能:游戏开始的时候初始化画面的,这里只是简单地把132*64LCD用一条中间线划分开来
//注意事项:暂时在中间画条线用来划分游戏空间
//使用方式:内部调用,
void game_init()
{
xdata uchar i;
#ifdef lcd_no_read   //如果用显存,需要先置初值为0
xdata uchar j;
for(j=0;j<8;j++)
for(i=0;i<64;i++)
lcd_buf[j]=0;
#endif
cls(9);     
initlcd();
//画竖线
for(i=0;i<7;i++)
{
  wricmd(0xb0+i);
  wricmd(64&0x0f);
  wricmd(0x10|(64>>4));
  wricmd(0xe0);
  wridata(0xff);
}
wricmd(0xee);
}

//函数名:snake_init
//功能:蛇初始化
//注意事项:初始化只有3节蛇身,向右跑
//使用情况:内部调用
void snake_init()
{
fill_game_dot(0,0);  //显示射身
fill_game_dot(1,0);
fill_game_dot(2,0);
snake_len=2;
snake_flag=0x10;  //蛇的初始化,3个身.向右跑
snake_body[0]=0x02;  //装入射身数据
snake_body[1]=0x01;
snake_body[2]=0x00;
//一开始游戏时的文字部分
setcursor(8,2);
lcddigit(snake_len-2);
}
//函数名:show_mark
//功能:显示当前分数,暂时以蛇身个数为分数
//参数说明:0,和非0, 0代表游戏中的显示,!0代表挂了的显示
//注意事项:调用到LCD.c显示函数,并需要汉字库的支持.
//   返回值在GAMEOVER时候有效,返回0退出游戏,1从新游戏   
//使用情况:snake_run()在蛇吃到食物的时候调用,在GAMEOVER后调用
uchar show_mark(uchar mode)
{
xdata uchar ch;
setcursor(8,2);
lcddigit(snake_len-2);
if(mode)//gameover中显示
{ 
  setcursor(8,0);
  lcdstring("完");
  do ch=getkey(1000);
  while( (ch!='C') && (ch!='Y') );   //游戏结束了会在这里死等,直到用户按键
  if(ch=='Y')
   return(1);
  else 
   return(0);
}

return(0); 
}
//函数名:snake_run
//功能:蛇运行函数
//输入参数:一个全局变量flag_snake,蛇根据这个变量判断运动方向
//注意事项:蛇跑动函数,用于判断路径,食物,长大,死亡
//使用情况:内部调用
void snake_run()
{
xdata uchar tmp_head_x,tmp_head_y;
xdata uchar i;
switch(snake_flag&0xf0)  //取蛇头方向
  {
  case 0x80://向上走 y--
     tmp_head_x=snake_body[0]&0x0f;
     tmp_head_y=(snake_body[0]>>4);      
     if(tmp_head_y==0)snake_flag|=0x04;//这个代表撞墙了,就置GAMEOVER标志,下同
     else tmp_head_y--;
     break;
  case 0x40://向下走 y++
     tmp_head_x=snake_body[0]&0x0f;
     tmp_head_y=(snake_body[0]>>4);
     if(tmp_head_y==MAX_GAME_Y)snake_flag|=0x04;
     else tmp_head_y++;
     
     break;
  case 0x20://向左走 x--
     tmp_head_y=snake_body[0]>>4;
     tmp_head_x=snake_body[0]&0x0f;
     if(tmp_head_x==0)snake_flag|=0x04;
     else tmp_head_x--;
     break;
  case 0x10://向右走 x++      
     tmp_head_y=snake_body[0]>>4;
     tmp_head_x=snake_body[0]&0x0f;
     if(tmp_head_x==MAX_GAME_X)snake_flag|=0x04;
     else tmp_head_x++;
     break;
  default:break;
  }
  if(!(snake_flag&0x04))   //如果在之前没有撞墙,就可以进行下一步判断
  {   
   //以下是得到食物的判断。
   if(snake_food!=( (tmp_head_y<<4)+tmp_head_x ))//蛇头和食物坐标没重叠就代表没有吃到食物
   {//得不到食物的处理
    clr_game_dot(snake_body[snake_len]&0x0f,snake_body[snake_len]>>4);//灭蛇尾巴
    for(i=snake_len;i>0;i--)           //柔体传动
    snake_body=snake_body[i-1];
    snake_body[0]=( tmp_head_y<<4 ) + tmp_head_x;
   }
   else
   {//得到食物的处理   
    snake_body[snake_len+1]=snake_body[snake_len];    //保留蛇尾巴(这是增长型柔体传动)
    for(i=snake_len;i>0;i--)
    snake_body=snake_body[i-1];
    snake_body[0]=( tmp_head_y<<4 ) + tmp_head_x;    //新蛇头
    snake_len++;//长度增加1
    snake_flag&=~0x02;//清食物标志
    show_mark(0);//显示分数
   } 
   
   fill_game_dot(tmp_head_x,tmp_head_y);//显示新蛇头
  }
  for(i=1;i<snake_len+1;i++)      //判断是否撞中自己
  {
   if(snake_body[0]==snake_body)    
   {
    snake_flag|=0x04;      //撞中了就置GAMEOVER标志
    break;
   }
  }
}

//函数名:set_food
//功能:放食物
//注意事项:这个函数在被调用前会先判断是否需要放食物,
//   这里用自己编写的随机数来产生食物,随机数和蛇身位置,定时器有关
//   每次放食物的时候必须先判断是否和蛇身重叠了,重叠了要重新放
//   这里设定了如果蛇长度达到某值了就不再放食物.
//影响变量:snake_food
//使用情况:内部调用
void set_food()
{

xdata uchar seed0,seed1,i=0;
seed0=snake_body[snake_len-2];
if(snake_len==100)
{
    return;//蛇都快满屏了,就不放食物了,事实上我还没玩过超过100的呢:)
}
seed0=((seed1>>3)*4+seed0+TL0);//随机数的生成,其实乱做就可以了:)
seed1=(snake_body[0]>>4)+seed0;
snake_food=(seed1+seed0*3);
while( (snake_food&0xf0)>0xc0)
snake_food+=0x30;    //Y位置不得超过12


food: 
for(i=0;i<snake_len+1;i++) //食物不能和蛇身重叠
{
  if( snake_body==snake_food )
  {
  
   snake_food=(snake_food+0x01);  //如果重叠了,位置就+1,然后
   if((snake_food&0xf0)>0xc0)
    snake_food&=0x0f;    //Y方向只能到12,超过12就要回0
   goto food;       //重新比较,这里可以换成i=255,效果一样
  }
  }
fill_game_dot2(snake_food&0x0f,snake_food>>4);//放食物
snake_flag|=0x02;//置有食物标志
}
// 函数名:定时器1初始化程序
// 晶振22.1184,定时时间35MS
void snake_init_timer0(void)
{
TMOD=0x1;
  TH0=0x0;
  TL0=0x0;
}


//函数名:snake_game
//功能:整个游戏的主要函数
//注意事项:游戏利用了定时器产生蛇的运行速速度
//   调用前应该先初始化定时器
//使用情况:外部调用
//
extern data uchar time_service;    //这个东东是使定时器公用的。
void snake_game()
{
xdata uchar tmp_snake_flag,ch;
cls(8);
time_service=1;       //切换定时器为贪吃蛇所用
set_time=0;       
lcdstring("贪吃蛇游戏\r\nC退出,任意键进入");
while((ch=getkey(1000))==0);
if(ch=='C') return;
ch=0;
  
begin_game:
game_init(); //一堆初始化
snake_init();
snake_init_timer0();//timer0_init();
TR0=1;
ET0=1;
EA=1;

tmp_snake_flag=snake_flag;
while(1)
{
  ch=getkey(1000);
  switch(ch)
  {
   case '2'://按了上按键
     if( (snake_flag&0x40) || (snake_flag&0x80) )break;//向上走的时候,上下键盘都无效,下同
     tmp_snake_flag&=0x0f;tmp_snake_flag|=0x80;
     break;
   case '5'://按了下按键
     if( (snake_flag&0x80) || (snake_flag&0x40) )break;
     tmp_snake_flag&=0x0f;tmp_snake_flag|=0x40;
     break;
   case '4'://按了左按键
     if( (snake_flag&0x10 || (snake_flag&0x20) ))break;
     tmp_snake_flag&=0x0f;tmp_snake_flag|=0x20;
     break;
   case '6'://按了右按键
     if( (snake_flag&0x20 || (snake_flag&0x10) ))break;
     tmp_snake_flag&=0x0f;tmp_snake_flag|=0x10;
     break;
   case 'C'://任何时候,按C就结束游戏
     
     return;
   default:
     break;
   }
  if(!(snake_flag&0x02)) //如果图上已经没食物了,就
   set_food();   //放食物 
  if((snake_flag&0x01))        //判断是否够时间跑一步
  {
   snake_flag=( snake_flag&0x0e ) | tmp_snake_flag;//取消跑动标志,置新方向
   snake_run();
   tmp_snake_flag=snake_flag;
  }

  if((snake_flag&0x04))   //判断游戏结束标志
  {
   if(show_mark(1))
   {
    snake_flag&=~0x04;
    goto begin_game;
   }
   else
   {
    time_service=0;
    return;      //游戏结束   
   }
   
  }      
}
} 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -