单片机游戏设计及贪吃蛇程序

刚才有位同志上传上老外的贪吃蛇网页,并称赞老外的大方。

咱们中华民族的大虾们也应提倡共享精神!!今天由本白菜带头,以有限的能力献丑献

丑,以望能抛砖引玉,激起各位大虾的爱国爱民的精神,救菜鸟于水深火热之中!

单片机游戏设计

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基本点阵组

//蛇行标志在定时器上置位,这里为游戏的主体部分。

#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 15

#define lcd_no_read 1 //编译选项,把这项屏蔽掉就

采用LCD读出方式,否则采用显存形式

uchar snake_flag, //蛇头标志 7 6 5 4 3 2

1 0

// 上 下 左 右 x gameover food run

snake_len, //蛇身长度

snake_food; //食物位置,高4位Y,低4位x

uchar xdata snake_body[256]; //蛇身每个部分的数据

// 7 6 5 4 3 2 1 0

//

高4位Y方向 低4位X方向

#ifdef lcd_no_read

uchar xdata lcd_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

//使用方式:内部调用

void clr_game_dot(uchar x,uchar y)

{

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

LCMC=lcd_x&0x0f; //设置x位置

LCMC=(lcd_x>>4)|0x10;

LCMC=0xb0+y/2; //设置Y位置

LCMC=0xe0;

if(y%2)//行的下半部

{

for(i=0;i

{

#ifdef lcd_no_read //以下是显存法的清点程序,

其他例如亮点的部分和这个原理一样

tmp=lcd_buf[y>>1][(x

//先从缓冲读出要修改的LCD片的数据

tmp&=0x0f;

//清对应的游戏点

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

//把新数据写回缓冲

#else

tmp=LCMD;tmp=LCMD;

//读LCD的方法,要求连读2次

LCMD=tmp&0x0f;

#endif

}

}

else //行的上半部,下同

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp&=0xf0;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#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

//使用方式:内部调用

void fill_game_dot(uchar x,uchar y)

{

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

LCMC=lcd_x&0x0f; //设置x位置

LCMC=(lcd_x>>4)|0x10;

LCMC=0xb0+y/2; //设置Y位置

LCMC=0xe0;

if(y%2)//行的下半部

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0xf0;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0xf0;

#endif

}

}

else

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0x0f;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0x0f;

#endif

}

}

LCMC=0xee;

}

//函数名:fill_game_dot2

//功能:亮一个游戏点(另一种方式,这里用来显示食物用)

//输入参数:X,Y

//注意事项:X,Y为游戏的点阵,非LCD点阵...还有LCD填充数据是0x05或0x50

//使用方式:内部调用,显示蛇的食物的时候用这个函数,区分开蛇身和食物.

void fill_game_dot2(uchar x,uchar y)

{

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

LCMC=lcd_x&0x0f; //设置x位置

LCMC=(lcd_x>>4)|0x10;

LCMC=0xb0+y/2; //设置Y位置

LCMC=0xe0;

if(y%2)

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0x50;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0x50;

#endif

}

}

else

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0x05;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0x05;

#endif

}

}

LCMC=0xee;

}

/************************************************************

*

* 游戏算法部分(8*8LCD)

*

**************************************************************/

//函数名 game_init()

//功能:游戏开始的时候初始化画面的,这里只是简单地把132*64LCD用一条中间线划分开来

//注意事项:暂时在中间画条线用来划分游戏空间

//使用方式:内部调用,

void game_init()

{

uchar i;

uchar xdata *da;

for(da=0;da

*da=0x0;

cls(9); //丁丁的清屏函

initlcd();

for(i=0;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节蛇身,向右跑

//使用情况:内部调用

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,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后调用

uchar show_mark(uchar mode)

{

uchar ch;

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("回车继续");

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()

{

uchar tmp_head_x,tmp_head_y;

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

+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

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

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[i])

{

snake_flag|=0x04;

//撞中了就置GAMEOVER标志

break;

}

}

}

//函数名:set_food

//功能:放食物

//注意事项:这个函数在被调用前会先判断是否需要放食物,

// 这里用自己编写的随机数来产生食物,随机数和蛇身位

置,定时器有关

// 每次放食物的时候必须先判断是否和蛇身重叠了,重叠

了要重新放

// 这里设定了如果蛇长度达到某值了就不再放食物.

//影响变量:snake_food

//使用情况:内部调用

void set_food()

{

uchar seed0,seed1,i=0;

seed0=snake_body[snake_len-2];

if(snake_len==250)

{

return;//蛇都快满屏了,就不放食物了,事实上我还没玩过超过100的呢:)

}

seed0=((seed1>>3)*4+seed0+TL0);//随机数的生成,其实乱做就可以了:)

seed1=(snake_body[0]>>4)+seed0;

snake_food=(seed1+seed0*3);

food:

for(i=0;i

{

if( snake_body[i]==snake_food )

{

snake_food=(snake_food+0x01); //如

果重叠了,位置就+1,然后

goto food;

//重新比较,这里可以换成i=255,效果一样

}

}

fill_game_dot2(snake_food&0x

0f,snake_food>>4);//放食物

snake_flag|=0x02;//置有食物标志

}

// 函数名:定时器1初始化程序

// 晶振22.1184,定时时间35MS

void timer0_init(void)

{

TMOD=0x1;

TH0=0x0;

TL0=0x0;

}

//函数名:snake_game

//功能:整个游戏的主要函数

//注意事项:游戏利用了定时器产生蛇的运行速速度

// 调用前应该先初始化定时器

//使用情况:外部调用

//

extern uchar time_service; //这个东东是使

定时器公用的。

void snake_game()

{

uchar tmp_snake_flag,ch;

cls(8);

time_service=1;

lcdstring("贪吃蛇游戏\r\nC退出,任意键进入\r\n游戏说明:\r\n2:上5:下4:

左6:右");

while((ch=getkey(1000))==0);

if(ch=='C') return;

ch=0;

begin_game:

game_init(); //一堆初始化

snake_init();

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

return;

//游戏结束

}

}

}

刚才有位同志上传上老外的贪吃蛇网页,并称赞老外的大方。

咱们中华民族的大虾们也应提倡共享精神!!今天由本白菜带头,以有限的能力献丑献

丑,以望能抛砖引玉,激起各位大虾的爱国爱民的精神,救菜鸟于水深火热之中!

单片机游戏设计

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基本点阵组

//蛇行标志在定时器上置位,这里为游戏的主体部分。

#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 15

#define lcd_no_read 1 //编译选项,把这项屏蔽掉就

采用LCD读出方式,否则采用显存形式

uchar snake_flag, //蛇头标志 7 6 5 4 3 2

1 0

// 上 下 左 右 x gameover food run

snake_len, //蛇身长度

snake_food; //食物位置,高4位Y,低4位x

uchar xdata snake_body[256]; //蛇身每个部分的数据

// 7 6 5 4 3 2 1 0

//

高4位Y方向 低4位X方向

#ifdef lcd_no_read

uchar xdata lcd_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

//使用方式:内部调用

void clr_game_dot(uchar x,uchar y)

{

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

LCMC=lcd_x&0x0f; //设置x位置

LCMC=(lcd_x>>4)|0x10;

LCMC=0xb0+y/2; //设置Y位置

LCMC=0xe0;

if(y%2)//行的下半部

{

for(i=0;i

{

#ifdef lcd_no_read //以下是显存法的清点程序,

其他例如亮点的部分和这个原理一样

tmp=lcd_buf[y>>1][(x

//先从缓冲读出要修改的LCD片的数据

tmp&=0x0f;

//清对应的游戏点

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

//把新数据写回缓冲

#else

tmp=LCMD;tmp=LCMD;

//读LCD的方法,要求连读2次

LCMD=tmp&0x0f;

#endif

}

}

else //行的上半部,下同

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp&=0xf0;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#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

//使用方式:内部调用

void fill_game_dot(uchar x,uchar y)

{

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

LCMC=lcd_x&0x0f; //设置x位置

LCMC=(lcd_x>>4)|0x10;

LCMC=0xb0+y/2; //设置Y位置

LCMC=0xe0;

if(y%2)//行的下半部

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0xf0;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0xf0;

#endif

}

}

else

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0x0f;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0x0f;

#endif

}

}

LCMC=0xee;

}

//函数名:fill_game_dot2

//功能:亮一个游戏点(另一种方式,这里用来显示食物用)

//输入参数:X,Y

//注意事项:X,Y为游戏的点阵,非LCD点阵...还有LCD填充数据是0x05或0x50

//使用方式:内部调用,显示蛇的食物的时候用这个函数,区分开蛇身和食物.

void fill_game_dot2(uchar x,uchar y)

{

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

LCMC=lcd_x&0x0f; //设置x位置

LCMC=(lcd_x>>4)|0x10;

LCMC=0xb0+y/2; //设置Y位置

LCMC=0xe0;

if(y%2)

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0x50;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0x50;

#endif

}

}

else

{

for(i=0;i

{

#ifdef lcd_no_read

tmp=lcd_buf[y>>1][(x

tmp|=0x05;

LCMD=tmp;

_nop_();

lcd_buf[y>>1][(x

#else

tmp=LCMD;tmp=LCMD;

LCMD=tmp|0x05;

#endif

}

}

LCMC=0xee;

}

/************************************************************

*

* 游戏算法部分(8*8LCD)

*

**************************************************************/

//函数名 game_init()

//功能:游戏开始的时候初始化画面的,这里只是简单地把132*64LCD用一条中间线划分开来

//注意事项:暂时在中间画条线用来划分游戏空间

//使用方式:内部调用,

void game_init()

{

uchar i;

uchar xdata *da;

for(da=0;da

*da=0x0;

cls(9); //丁丁的清屏函

initlcd();

for(i=0;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节蛇身,向右跑

//使用情况:内部调用

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,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后调用

uchar show_mark(uchar mode)

{

uchar ch;

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("回车继续");

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()

{

uchar tmp_head_x,tmp_head_y;

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

+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

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

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[i])

{

snake_flag|=0x04;

//撞中了就置GAMEOVER标志

break;

}

}

}

//函数名:set_food

//功能:放食物

//注意事项:这个函数在被调用前会先判断是否需要放食物,

// 这里用自己编写的随机数来产生食物,随机数和蛇身位

置,定时器有关

// 每次放食物的时候必须先判断是否和蛇身重叠了,重叠

了要重新放

// 这里设定了如果蛇长度达到某值了就不再放食物.

//影响变量:snake_food

//使用情况:内部调用

void set_food()

{

uchar seed0,seed1,i=0;

seed0=snake_body[snake_len-2];

if(snake_len==250)

{

return;//蛇都快满屏了,就不放食物了,事实上我还没玩过超过100的呢:)

}

seed0=((seed1>>3)*4+seed0+TL0);//随机数的生成,其实乱做就可以了:)

seed1=(snake_body[0]>>4)+seed0;

snake_food=(seed1+seed0*3);

food:

for(i=0;i

{

if( snake_body[i]==snake_food )

{

snake_food=(snake_food+0x01); //如

果重叠了,位置就+1,然后

goto food;

//重新比较,这里可以换成i=255,效果一样

}

}

fill_game_dot2(snake_food&0x

0f,snake_food>>4);//放食物

snake_flag|=0x02;//置有食物标志

}

// 函数名:定时器1初始化程序

// 晶振22.1184,定时时间35MS

void timer0_init(void)

{

TMOD=0x1;

TH0=0x0;

TL0=0x0;

}

//函数名:snake_game

//功能:整个游戏的主要函数

//注意事项:游戏利用了定时器产生蛇的运行速速度

// 调用前应该先初始化定时器

//使用情况:外部调用

//

extern uchar time_service; //这个东东是使

定时器公用的。

void snake_game()

{

uchar tmp_snake_flag,ch;

cls(8);

time_service=1;

lcdstring("贪吃蛇游戏\r\nC退出,任意键进入\r\n游戏说明:\r\n2:上5:下4:

左6:右");

while((ch=getkey(1000))==0);

if(ch=='C') return;

ch=0;

begin_game:

game_init(); //一堆初始化

snake_init();

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

return;

//游戏结束

}

}

}


相关文章

  • 手游文献综述
  • 文献综述 前 言 本人毕业设计的论题为<基于IOS 平台唐僧去哪了游戏的设计与实现>,与传统主流PC 游戏相比,手机游戏的价值在于打发一些无聊的时间,作为一个移动的娱乐终端,手机游戏施展身手的机会永远可能是在地铁站.公交车或者是 ...查看


  • 09级计科专业毕业设计题目
  • 09级计算机科学与技术专业毕业设计题目指南 说明:1. 每个题目的选择人数最多不能超过2名同学,否则将退回重选.(如题目要求可多 人合作,则以题目要求为准),请各班级同学自行协调解决选题冲突问题. 2.学习委员上报题目请用EXCEL 表格, ...查看


  • 基于嵌入式的贪食蛇的设计和实现
  • 嵌入式大作业 贪食蛇的设计和实现 作 者 姓 名: 学科.专业 : 学 号 : 指 导 教 师: 完 成 日 期: 大连大学 Dalian University 摘 要 WinCE 是微软公司嵌入式.移动计算平台的基础 它是一个开放的. 可 ...查看


  • 单片机的电路原理
  • 单片机的电路原理 单片机技术自发展以来已走过了近20年的发展路程 .单片机技术的发展以微处理器(MPU)技术及超大规模集成电路技术的发展为先导,以广泛的应用领域拉动,表现出较微处理器更具个性的发展趋势.小到遥控电子玩具,大到航空航天技术等电 ...查看


  • 基于Java的贪吃蛇开发文档
  • 中南林业科技大学 <小组软件过程实验> 实验报告 题目: 2D 游戏贪食蛇软件开发 专业班级: 11级软件工程2班 组长: xxx 成员:指导教师: xxxxx 完成日期: 2014/4/15 目 录 1 软件项目开发计划--- ...查看


  • 贪吃蛇游戏测试计划
  • 软件测试计划文档 一. 引言 1. 编写目的 本测试计划文档作为指导此测试项目循序渐进的基础,帮助我们安排合适的资源和进度,避免可能的风险.本文档有助于实现以下目标: 1) 确定现有项目的信息和应测试的软件结构. 2) 列出推荐的测试需求 ...查看


  • 贪吃蛇游戏测试计划 1
  • 软件测试计划文档 一. 引言 1. 编写目的 本测试计划文档作为指导此测试项目循序渐进的基础,帮助我们安排合适的资源和进度,避免可能的风险.本文档有助于实现以下目标: 1) 确定现有项目的信息和应测试的软件结构. 2) 列出推荐的测试需求 ...查看


  • 游戏毕业设计论文
  • 游戏毕业设计论文 1. 游戏设计背景 随着科技与经济的快速发达,知识经济的快速增长,娱乐逐渐变成人与智能的交流.智能化的娱乐游戏在网络里愈来愈好,众多的游戏不胜枚举.比如益智类的小游戏.腾讯游戏.网页游戏.格斗游戏.模拟游戏等,尤其是最近网 ...查看


  • 8贪吃蛇项目分析文档
  • ----项目设计报告书 项目名称:贪吃蛇 指导老师:顾洪 所在系别:计算机系 班级:307051项目成员:张炜 [1**********] 孔金蕾[1**********] 目录 第一章引言......................... ...查看


热门内容