单片机C语言贪吃蛇程序可下.docx
《单片机C语言贪吃蛇程序可下.docx》由会员分享,可在线阅读,更多相关《单片机C语言贪吃蛇程序可下.docx(19页珍藏版)》请在冰点文库上搜索。
单片机C语言贪吃蛇程序可下
【单片机游戏设计及贪吃蛇程序】
管理提醒:
本帖被icneo执行取消置顶操作(2009-01-02)
【单片机游戏设计及贪吃蛇程序】
单片机游戏设计
1。
概念
对于大部分单片机+LCD的游戏设计,基本采用前后台方式,就是一个台中断,一个台循环(哪个前哪个后忘了),LCD部分基本是以固定点阵形式设计,什么叫固定点阵?
?
首先先明确,我们设计的游戏不是什么魔兽争霸或CS,而是黑白形式的固定点阵游戏,例如常见著名游戏贪吃蛇或俄罗斯方块。
他们的每个点都是预先就固定下来的,而且是比较大的点,这类专门的游戏机玻璃是经过厂家开模出来的,有固定的COM,SEG线,然后接到专门的单片机上,例如常用的6502指令集合的单片机,呵呵,我以前就用6502设计过一个。
对于业余设计的游戏,我们一般用如128*64的LCD来显示,那么我们设计的时候首先应该把这个128*64的LCD分块,也就是分出固定点阵出来。
LCD的基本点阵是128*64,就是•¥##¥总之就是好多个点啦,但我们事实上不一定要运算这么多个点,除非你做的游戏很有看头。
例如你只用左边64*64的地方来做贪吃蛇,那么你打算你的贪吃蛇的活动空间是多少呢?
如果是8*8个点的话,算一下就是每个点64/8,64/8,也就是8*8个基本点阵,不过想好玩一点,当然就是要有16*16个点的活动空间啦,那么每个固定点阵就要占4*4的基本点阵了。
要注意,这些4*4的东西在64*64LCD上共16*16个,每个都要用来独立运算。
2。
时钟
这个其实是游戏的速度,对于一般的弱智类游戏机,他也代表了难度,物体在每个时钟到达的时候就传动一次,例如俄罗斯方块没个时刻向下跑一层。
赛车游戏每个时刻想前走一步。
一般这类时钟的时间在0.X秒到1秒之间,物体有规律地匀速运动,让人看到感觉是连动。
3。
运动
在这里,我先介绍两种比较普遍的弱智游戏机的物体运动规则:
柔体传动,刚体传动。
刚体传动
代表作是俄罗斯方块,所谓刚体,就是硬硬的一个东东,运动的时候也不怎么旋转(注意,俄罗斯方块是会旋转,但其实他是没有经过算法的旋转,纯提取数组的方式,也就是把一个放块做成4个模式的点阵结构,其实就是4个方向,呵呵)对于刚体的传动,在每个时钟到达的时候向一个方向(很可能是用户输入的)运动一个固定点阵。
如果以坐标来表达,就是物体的所有基本点阵同时向一个方向(X或Y)移动一个单位。
柔体传动
代表作是贪吃蛇,贪吃蛇跑动的时候并不是整条蛇向一个方向动的(呵呵,蛇蛇身体僵硬了),而是在每个时钟的到来,物体由能量头带动(如蛇头),每个点的方向都向下一个点传播,然后自己向新的方向走动一步,走动后,下一个点由于得到了上一个点的方向并同样地运动一步,所以,他会马上填补上一个点的地方,如此类推。
说的好象没说,看不懂没关系,因为实际的算法可以简化(傻瓜才会一个个点来走的),实际上在设计贪吃蛇的时候,只需要把蛇尾巴的那个点阵去掉,然后在蛇头的新方向放一个点阵就是了。
期间需要记录下每个蛇身的固定点阵的位置,并且在每个运动时刻过后刷新一次每个点的位置。
4。
显示接口
我们用的一般是点阵式LCD,就是一大片点点,128*64,132*64,240*128等等等等啦,这些又叫条屏,就是一写就写一条——8个点(有的也提供写一个点的功能,但贵,至少我没有),那么如果你只想写一个点怎么办?
那就得先把这个点所在的条读出来,然后通过
与,或,的运算后,再放回到LCD上,这时候就要涉及到一个读LCD的问题了,有的LCD提供读的功能,你写过什么在上面他记的很清楚(就好象老丁实验板上的LCD),但有的便宜货就不行了,那么我们怎么办?
没关系,你在内存中提取出一片空间,虚拟一个LCD出来,每次写在真实LCD上面的时候,也同时写到内存的哪个虚拟LCD上,那么你要读出LCD的值的时候实际就是读出虚拟LD上的数据,然后与或后,再重新写到LCD上,记得也要写到虚拟LCD上哦。
你可以把这片缓冲叫做显存(COOL吧?
?
)
5。
流程
这是成功设计游戏的灵魂,你在设计游戏之前必须能正确构思到一个基本模型出来。
这个基本是菜鸟和虾米的一个区别,有了构思,其他的其实都是时间问题了。
以贪吃蛇为例,我们需要有这样的基本思路:
(普通手机上的那种)
蛇运动处理,吃到食物的处理,放新食物的处理,死亡的处理。
以上是基本的思路,至于那些记录分数,音乐效果,玩到一定分数会自动加速度等不是游戏的必须,可以在后期处理!
分析下来:
运动:
根据用户输入按键进行柔体传动。
吃到食物:
置没有食物标志了,蛇长大一个点阵。
放新食物:
判断食物标志,如果没有食物,就要放食物,判断放的食物是否和蛇身重叠,
重叠了要重放。
死亡处理:
判断是否撞中自己或撞墙。
这就是基本要做的东西,实际上就是程序要做的东西,那么把上面的东西连成一个流程是怎样的呢?
我以文字表达:
蛇向一个固定方向进行柔体传动,没个运动时钟到达要做:
1。
判断食物标志,没有食物了就放一个,放的时候判断,不能和蛇身重叠2。
得到用户按键值,蛇走一步,并判断是否撞死了,没撞死,再判断是否吃到东西了,没有吃到,就等下一个运动时钟,吃了?
就增长一点。
置一个没有食物的标志。
然后等待下一个时刻的来临。
呵呵,其实程序就是这么简单,基本设计只有LCD部分和按键部分是和单片机有关的,其他都是程序思维和算法。
对于菜鸟来说,难度在于思维,而不是单片机。
说了屁话一堆,还得放上个能玩的,这里我介绍我的贪吃蛇程序,在实验板上跑的,很久以前就写的了,有人也玩过。
放到现在才拿出来希望大家不要介意:
)
程序注意:
这是在丁丁的板子上跑的程序,有些函数部分采用了别人写的底层:
例如键盘扫描,汉字显示,LCD显示等,为了保障老丁的利益,我没有完全给出所有的底层部分,其实他们和贪吃蛇本身没有太大关系。
贪婪者别以为拿来就用,我只希望大家用来交流学习。
其实改改就能玩的了。
注释应该很详尽,有不懂自己想啦。
还有,我有点反感有些人公布程序了,但却把很多注释去掉,这个不知道是什么心态呢?
?
希望大家能大方点,要给,就要给最好的!
!
【单片机游戏设计及贪吃蛇程序】
程序:
/*snake_flag是游戏标志,第一位是跑动标志,在定时器中断上设置,下面程序没有定时
器中断函数,因为定时器函数在丁板上是给很多个程序共用的,函数根据标志判断当前是
为那个进程服务*/
//贪吃蛇游戏程序,屏左半部用于游戏活动,右半部为分数显示
//游戏屏为16*16游戏点阵,可容纳蛇身块数256。
每个游戏点阵又由4*4个LCD基本点阵组
成
//蛇行标志在定时器上置位,这里为游戏的主体部分。
#defineucharunsignedchar
#defineuint unsignedint
#defineulongunsignedlong
#defineLCMDXBYTE[0xAfff]//液晶数据口
#defineLCMCXBYTE[0xAbff]//液晶命令口
#defineTIME_RUN10//定时器分品系
数
#include"study.h"
#include"reg51.h"
#include"absacc.h"
#include"intrins.h"
//游戏部分
//x,y最大极限
#defineMAX_GAME_X15
#defineMAX_GAME_Y15
#definelcd_no_read1//编译选项,把这项屏蔽掉就
采用LCD读出方式,否则采用显存形式
ucharsnake_flag,//蛇头标志 7 6 5 4 3 2
1 0
// 上下左右x gameover food run
snake_len,//蛇身长度
snake_food;//食物位置,高4位Y,低4位x
ucharxdatasnake_body[256];//蛇身每个部分的数据
// 7 6 5 4 3 2 1 0
//
高4位Y方向 低4位X方向
#ifdeflcd_no_read
ucharxdatalcd_buf[8][64];//lcd缓冲,用于记录LCD内部的点阵,可以理解为显存
//当
LCD无读出功能时,就要采用显示缓冲。
本LCD为可读,一般不用这个功能
//缓
冲只记录蛇身活动的部分,即LCD左半屏
#endif
/******************************************************
*游戏LCD部分,根据游戏的特点把LCD分成16*16块
*用作游戏点阵,
*******************************************************/
//
//函数名:
clr_game_dot
//功能:
清一个游戏点
//输入参数:
游戏点的X,Y坐标
//注意事项:
这里的X,Y坐标和LCD底层的X,Y坐标不同,他最大只能是
MAX_GAME_X,MAX_GAME_Y
//使用方式:
内部调用
voidclr_game_dot(ucharx,uchary)
{
ucharlcd_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;
LCMC=lcd_x&0x0f;//设置x位置
LCMC=(lcd_x>>4)|0x10;
LCMC=0xb0+y/2;//设置Y位置
LCMC=0xe0;
if(y%2)//行的下半部
{
for(i=0;i<4;i++)
{
#ifdeflcd_no_read//以下是显存法的清点程序,
其他例如亮点的部分和这个原理一样
tmp=lcd_buf[y>>1][(x<<2)+i];
//先从缓冲读出要修改的LCD片的数据
tmp&=0x0f;
//清对应的游戏点
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
//把新数据写回缓冲
#else
tmp=LCMD;tmp=LCMD;
//读LCD的方法,要求连读2次
LCMD=tmp&0x0f;
#endif
}
}
else//行的上半部,下同
{
for(i=0;i<4;i++)
{
#ifdeflcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp&=0xf0;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp&0xf0;
#endif
}
}
LCMC=0xee;
}
//函数名:
fill_game_dot
//功能:
亮一个游戏点
//输入参数:
游戏坐标的X,Y坐标
//注意事项:
这里的X,Y坐标和LCD底层的X,Y坐标不同,他最大只能是
MAX_GAME_X,MAX_GAME_Y
// 这个函数和上面的clr_game_dot基本相同,只是在写LCD数据的时候是全1而
不是0
//使用方式:
内部调用
voidfill_game_dot(ucharx,uchary)
{
ucharlcd_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;
LCMC=lcd_x&0x0f;//设置x位置
LCMC=(lcd_x>>4)|0x10;
LCMC=0xb0+y/2;//设置Y位置
LCMC=0xe0;
if(y%2)//行的下半部
{
for(i=0;i<4;i++)
{
#ifdeflcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0xf0;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0xf0;
#endif
}
}
else
{
for(i=0;i<4;i++)
{
#ifdeflcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0x0f;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0x0f;
#endif
}
}
LCMC=0xee;
}
【单片机游戏设计及贪吃蛇程序】
程序续:
//函数名:
fill_game_dot2
//功能:
亮一个游戏点(另一种方式,这里用来显示食物用)
//输入参数:
X,Y
//注意事项:
X,Y为游戏的点阵,非LCD点阵...还有LCD填充数据是0x05或0x50
//使用方式:
内部调用,显示蛇的食物的时候用这个函数,区分开蛇身和食物.
voidfill_game_dot2(ucharx,uchary)
{
ucharlcd_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;
LCMC=lcd_x&0x0f;//设置x位置
LCMC=(lcd_x>>4)|0x10;
LCMC=0xb0+y/2;//设置Y位置
LCMC=0xe0;
if(y%2)
{
for(i=0;i<4;i++)
{
#ifdeflcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0x50;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0x50;
#endif
}
}
else
{
for(i=0;i<4;i++)
{
#ifdeflcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0x05;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0x05;
#endif
}
}
LCMC=0xee;
}
/************************************************************
*
*游戏算法部分(8*8LCD)
*
**************************************************************/
//函数名game_init()
//功能:
游戏开始的时候初始化画面的,这里只是简单地把132*64LCD用一条中间线划分开来
//注意事项:
暂时在中间画条线用来划分游戏空间
//使用方式:
内部调用,
voidgame_init()
{
uchari;
ucharxdata*da;
for(da=0;da<0x8000;da++)//清空xDATA,
*da=0x0;
cls(9);//丁丁的清屏函
数
initlcd();
for(i=0;i<8;i++)
{
LCMC=64&0x0f;
LCMC=(64>>4)|0x10;//线在x=64,
LCMC=0xb0+i;//y=(0-15)的地方
LCMC=0xe0;//把LCD划分,左边用来游戏
LCMD=0xff;
}
LCMC=0xee;
}
//函数名:
snake_init
//功能:
蛇初始化
//注意事项:
初始化只有3节蛇身,向右跑
//使用情况:
内部调用
voidsnake_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,0);
lcdstring("分数为:
\r\n");
setcursor(8,2);
lcddigit(snake_len-2);
}
//函数名:
show_mark
//功能:
显示当前分数,暂时以蛇身个数为分数
//参数说明:
0,和非0,0代表游戏中的显示,!
0代表挂了的显示
//注意事项:
调用到LCD.c显示函数,并需要汉字库的支持.
//返回值在GAMEOVER时候有效,返回0退出游戏,1从新游
戏
//使用情况:
snake_run()在蛇吃到食物的时候调用,在GAMEOVER后调用
ucharshow_mark(ucharmode)
{
ucharch;
setcursor(8,0);
lcdstring("分数为:
\r\n");
setcursor(8,2);
lcddigit(snake_len-2);
if(mode)//gameover中显示
{
setcursor(8,0);
lcddigit(snake_len-2);
lcdstring("分.");
setcursor(8,2);
lcdstring("C退出");
setcursor(8,4);
lcdstring("回车继续");
doch=getkey(1000);
while((ch!
='C')&&(ch!
='Y'));
//游戏结束了会在这里死等,直到用户按键
if(ch=='Y')
return
(1);
else
return(0);
}
return(0);
}
//函数名:
snake_run
//功能:
蛇运行函数
//输入参数:
一个全局变量flag_snake,蛇根据这个变量判断运动方向
//注意事项:
蛇跑动函数,用于判断路径,食物,长大,死亡
//使用情况:
内部调用
voidsnake_run()
{
uchartmp_head_x,tmp_head_y;
uchari;
switch(snake_flag&0xf0)//取蛇头方向
{
case0x80:
//向上走y--
tmp_head_x=snake_body[0]
&0x0f;
tmp_head_y=(snake_body[0]
>>4);
if(tmp_head_y==0)
snake_flag|=0x04;//这个代表撞墙了,就置GAMEOVER标志,下同
elsetmp_head_y--;
break;
case0x40:
//向下走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;
elsetmp_head_y++;
break;
case0x20:
//向左走x--
tmp_head_y=snake_body[0]
>>4;
tmp_head_x=snake_body[0]
&0x0f;
if(tmp_head_x==0)
snake_flag|=0x04;
elsetmp_head_x--;
break;
case0x10:
//向右走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;
elsetmp_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//判断是否撞中自己
{
if(snake_body[0]==snake_body)
{
snake_flag|=0x04;
//撞中了就置GAMEOVER标志
break;
}
}
}
//函数名:
set_food
//功能:
放食物
//注意事项:
这个函数在被调用前会先判断是否需要放食物,
//这里用自己编写的随机数来产生食物,随机数和蛇身位
置,定时器有关
//每次放食物的时候必须先判断是否和蛇身重叠了,重叠
了要重新放
//这里设定了