📄 slace.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 "study.h"
#include "reg51.h"
#include "absacc.h"
#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][i]=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[i]=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[i]=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[i])
{
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[i]==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 + -