基于51单片机的贪吃蛇游戏设计Proteus仿真含完整程序.docx
《基于51单片机的贪吃蛇游戏设计Proteus仿真含完整程序.docx》由会员分享,可在线阅读,更多相关《基于51单片机的贪吃蛇游戏设计Proteus仿真含完整程序.docx(17页珍藏版)》请在冰豆网上搜索。
![基于51单片机的贪吃蛇游戏设计Proteus仿真含完整程序.docx](https://file1.bdocx.com/fileroot1/2023-1/23/2ba26192-b49c-4380-a0fa-41b02cd4ae68/2ba26192-b49c-4380-a0fa-41b02cd4ae681.gif)
基于51单片机的贪吃蛇游戏设计Proteus仿真含完整程序
中北大学
硬件大型实验说明书
学生姓名:
学号:
学院:
计算机与控制工程学院
专业:
计算机科学与技术
题目:
“贪吃蛇”游戏设计
指导教师:
职称:
2016年9月14日
目录
一、需求分析1
二、工具1
三、概要设计1
四、详细设计(硬件设计和连接部分)2
1.组件介绍2
1)AT89C55单片机2
2)Matrix-8X8点阵屏幕3
3)74LS154译码器3
4)CMOS反相器4
5)按键5
6)7-SEGBCD5
2.硬件设计5
五、心得体会7
附录I(程序)9
附录II(运行截图)16
一、需求分析
我们的课程设计题目是基于51单片机和一些基础组件设计出一个贪吃蛇游戏。
游戏的实现需要一个16x16的屏幕(由4个8x8的点阵屏组成)和5个按键,其中4个按键控制贪吃蛇的前进方向,另外一个按键可以使游戏重新开始。
另外还需要一个显示BCD码的数字型LED灯组用于记录得分。
贪吃蛇游戏需要实现的功能是:
游戏开始时会出现一个长度为init_length的贪吃蛇,和一个随机出现的苹果(必须是贪吃蛇蛇身以外的一个点)。
玩家可以通过按键控制贪吃蛇的前进方向,但是只能转向,而不能向前或者向后,例如:
当贪吃蛇向上行走时,只能通过左键和右键来让它左转或者右转,另外两个按键将失灵。
当贪吃蛇吃到苹果后,贪吃蛇尾部将会增加一个点,而屏幕上又会随机出现一个不与贪吃蛇重合的点。
此时积分器加一。
当贪吃蛇马上就要“撞向”屏幕边缘时,会从相反的一边钻出来。
所以“撞墙”并不会导致游戏结束。
而当贪吃蛇吃到自身时则会使游戏结束,并且会自动回到游戏初始化时的状态。
任何时候按下“重新开始”按键都将使游戏回到初始状态。
二、工具
51单片机开发板,KeiluVision3,ISIS7Professional。
三、概要设计
根据我们要实现的功能,我们决定把这次课程设计分成三个部分。
第一个部分为硬件设计和连接部分。
负责该部分的组员需要根据课程设计的要求选取适当的组件,并且自主设计连接方式。
当这部分完成后,要求其他组员能直接通过程序控制点阵屏幕上面显示的图形,同时也能让按键、数字型LED等组件能正常工作。
负责这部分的组员需要与负责图形显示部分的同学沟通,统一好各个引脚对应的功能和使用方式。
第二个部分为图形显示和外部、内部中断部分。
要让点阵屏幕持续显示图形就要通过软件重复扫描需要显示的点,这时候需要同时兼顾外部中断和内部中断,因为经过一个时间单位或者有按钮输入信息都会使得显示的图形发生变化。
因此我们把图形显示部分和外部中断、内部中断分到一个部分。
负责这部分的组员要求能将任何想要其显示的图形显示到点阵屏幕上,并且通过程序控制其变化。
第三个部分为游戏逻辑设计部分。
第二个部分已能将想要输出的图形显示出来,所以这部分要实现的功能就是显示哪些图形。
根据贪吃蛇的游戏规则,如果没有方向键按下图形会怎么变化、按下哪些键贪吃蛇会有哪些变化、吃到苹果或者碰到墙会怎么变化等都是这个部分需要解决的问题。
负责这部分组员也要与负责第二个部分的组员沟通好,因为游戏中使用的坐标信息是要求能直接在屏幕上显示的,这与第二个部分的任务有很大关系。
四、详细设计(硬件设计和连接部分)
1.组件介绍
1)AT89C55单片机
和89C51相比,该单片机具有更大存储量的片内ROM,在编程时能减少很多麻烦。
而且89C55和89C51的引脚完全相同,程序也都兼容,根据以前所学知识可以直接使用。
2)Matrix-8X8点阵屏幕
我们采用4个8X8的点阵屏幕来组成一个16X16的LED点阵屏幕。
而需要注意的是,单个8X8LED点阵屏的上面8个引脚和下面8个引脚要严格区分,上面8个引脚用于选择行,低电平有效;下面8个引脚用于选择列,高电平有效。
当组成一个大的点阵屏时同行的上面8个引脚要接在一起,而同列的下面8个引脚要接在一起。
4块屏幕一共64个引脚显然不能都直接接到单片机上,所以要用到下面的译码器。
3)74LS154译码器
以前通常使用74LS138译码器,但是现在该译码器并不能满足需求。
如果我们将用于选择列的引脚都连接到单片机上,而用于选择行的引脚连接译码器的话,需要2个74138译码器,而74LS154译码器的话仅需要连接一个即可,编写程序也较为方便。
当G1和G2接地时,该译码器工作。
输入端输入4个二进制数,输出端将对应的一个引脚以低电平方式译出。
该译码器结构如下图所示:
74LS154译码器的工作方式如下列表所示:
4)CMOS反相器
在有的时候可以快速地在硬件电路上实现按位求反,而不需要改程序。
5)按键
按键的功能是对游戏进行控制,四个方向按键可以控制贪吃蛇左转或者右转,而另外一个按键可以让游戏重新开始。
按键可以直接接到单片机的P0-P3口,在程序内部实现扫描、响应,或者也可以接到外部中断INT0和INT1上。
我们选择的是将重新开始按钮接到INT0上,而将方向按键接到P3口的其他引脚上,使用程序内部扫描查询到这组引脚的状态。
6)7-SEGBCD
这是一组排成数字形状的LED灯,可以直接对输入的10进制数译出显示出来。
该组件一共有4个引脚,可以显示0到F共16个数。
该组件需要实现的功能是显示出玩家当前分数。
2.硬件设计
主要是点阵LED屏幕与单片机的连接。
同行的8X8点阵屏的上面8个引脚连接在一起,通过反相器后连接到单片机上。
使用反相器的原因是74154输出端是选择一个低电平译出,而选择列的引脚是高电平有效,故需要将各个引脚取反然后连接到LED灯组上。
上面两个连到P1口,上面两个连到P0口。
同列的8X8点阵屏的下面8个引脚连接在一起,然后连到74154译码器的输出端。
译码器输入端4个引脚连到P2口的低4位上。
需要注意的是P0口需要接上拉电阻,这里直接接上阻排,然后电阻另一端接电源即可。
具体连接方式如下图所示:
在这个部分以外,需要连接一个7-SEGBCD数字型LED。
因为空出来的引脚中P2口的高4位空闲,而连续的引脚更容易实现直接输出数字图形,所以将该组件连接到P2口的高4位。
P3口有5根引脚连接5个按键,用于控制游戏重新开始和控制贪吃蛇转向。
完整的硬件设计图如下:
五、心得体会
此次试验遇到的最大的问题是存储空间不足的问题,为了解决这个问题我试过很多不同的办法,包括使用XDATA存储区和PDATA存储区,和使用不同型号的MCS-51单片机。
最后是通过压缩定义的变量类型(即尽量使用char和unsignedchar)使得情况有所缓解。
不过,存储容量不足的问题仍然在运行效果上体现出来了。
考虑到DATA区容量的限制,我们没有用全局变量数组来存储列信息,如果这样做的话程序会根据列来扫描及显示,即会显示较为流畅,但会占用较大空间;而如果不这样做,按每一个点来扫描的话,游戏会越到后面刷新越慢,但是会占用较小空间。
我们选择的是后者,所以无可避免的面临了“越玩越卡”的困境。
下一次如果我们在开始是就考虑到并且尽力规避这个问题的话,应该就可以避免出现这一类问题了。
此次大型试验带给我的最大收获就是对计算机有了更深入的了解。
我们是计算机专业的学生,在学习计算机知识的时候硬件部分和软件部分一样是必修内容,而因为计算机硬件发展至今已成为一个非常庞大复杂的体系,直接学习这类知识会导致理论和实际的脱节,因为我们很难依据自己学到知识自行设计一台功能完善的微型计算机。
但是单片机就直接解决了这个问题。
因为单片机就是一个很简单的计算机系统,通过连接硬件和设计程序我们一步一步的掌握了单片机里面的I/0功能,中断功能等,对计算机各部分组成和基本工作方式也掌握得更熟练了,这对以后工作的帮助是非常可观的。
并且自行设计实现简单的单片机功能也可以让我以后再接触此类工作时游刃有余,这是最直接的帮助。
附录I(程序)
#include
#include
#include
pointpoints[25];
unsignedchardirection=0;//0-上,1-下,2-左,3-右
unsignedcharcount=100;
unsignedcharsnakeLength=1;
unsignedcharscore=0;
unsignedcharscore_1;
unsignedcharscore_10;
unsignedcharkey_start=0;
unsignedcharrow;
unsignedcharspeed=60;
sbitkey_w=P3^0;
sbitkey_a=P3^1;
sbitkey_s=P3^6;
sbitkey_d=P3^7;
sbita5=P3^5;
sbita4=P3^4;
sbita3=P3^3;
charisOverlaping(pointp);
voidinit();
voidgameOver();
voidstart();
voidkeyScan();
voidsetP3(unsignedcharnum)//设置数码显示管的十位
{switch(num)
{
case0:
a5=0;a4=0;a3=0;break;
case1:
a5=0;a4=0;a3=1;break;
case2:
a5=0;a4=1;a3=0;break;
case3:
a5=0;a4=1;a3=1;break;
case4:
a5=1;a4=0;a3=0;break;
case5:
a5=1;a4=0;a3=1;break;
case6:
a5=1;a4=1;a3=0;break;
case7:
a5=1;a4=1;a3=1;break;
}
}
voiddisplay(chargroup,intvalue)//根据区块位置和值显示单个点
{row=group/2;
score_1=score%10;
score_10=score/10;
setP3(score_10);
P2=row+score_1*16;
if(group%2==0)
{
P1=~value;
P0=0xff;
}
else
{
P0=~value;
P1=0xff;
}
//delay1ms();
}
voidshowPoints()//根据坐标数组显示到LED
{
unsignedchari;
charscore_1;
charscore_10;
while(key_start==0)
{
for(i=0;i{
chargroup=points[i].y*2+points[i].x/8;//
unsignedcharval=(int)pow(2,7-points[i].x%8);
display(group,val);
}
//score_1=5;//score%10;
//score_10=score/10;
//P3=P3+score_10*8;
}
if(key_start==1)
{
start();
}
}
voidcreateApple()//随机生成一个苹果
{pointapplePoint;
unsignedcharx=rand()%16;
unsignedchary=rand()%16;
applePoint.x=x;
applePoint.y=y;
while(isOverlaping(applePoint))
{
applePoint.x++;
applePoint.y++;
}
points[0]=applePoint;
}
charisOverlaping(pointp)//判断生成的苹果是否与蛇身重合
{unsignedchari;
unsignedcharflag=0;
for(i=1;i{
if(points[i].x==p.x&&points[i].y==p.y)
{
flag=1;
break;
}
}
returnflag;
}
//判断蛇头是否咬到身子,即蛇头是否蛇身其他点重合
charisSelfOverlaping(pointp)
{unsignedchari;
unsignedcharflag=0;
for(i=2;i{
if(points[i].x==p.x&&points[i].y==p.y)
{
flag=1;
break;
}
}
returnflag;
}
voidcreateNextApple()//生成下一个苹果的坐标
{pointnextApple;
nextApple.x=(points[0].x+randomfact)%16;
nextApple.y=(points[0].y+randomfact)%16;
while(isOverlaping(nextApple))
{nextApple.x++;
nextApple.y++;
}
points[0]=nextApple;
}
//本来蛇身长就不止snakeLength,而是snakeLength+1,程序中只显示前snakeLength个点。
//所以只要将蛇长加一,同时生成下一个苹果,就能实现“吃苹果”的功能了。
voideatApple()
{snakeLength++;
score++;
createNextApple();
if(speed>10)
speed-=10;
}
//初始化界面,显示一条初始长度的贪吃蛇和一个随机生成的点。
voidinit()
{speed=60;
direction=0;
key_start=0;
snakeLength=init_length;
score=0;
createApple();
points[1].x=13;points[1].y=1;
points[2].x=14;points[2].y=1;
points[3].x=15;points[3].y=1;
EA=1;
TR1=0;
IT0=1;
EX0=1;
}
voidkeyScan()//扫描键盘获取方向操控信息
{if(direction<2)
{
if(key_a==0)
{
direction=2;
}
if(key_d==0)
{
direction=3;
}
}
else
{
if(key_w==0)
{
direction=0;
}
if(key_s==0)
{
direction=1;
}
}
}
voidnextPlot()//0-北,1-南,2-西,3-东
{unsignedchari;
pointp;
pointq;
p=points[1];
if(direction==0)
{if(points[1].x==0)
points[1].x=15;
else
points[1].x--;
}
elseif(direction==1)
{
if(points[1].x==15)
points[1].x=0;
else
points[1].x++;
}
elseif(direction==2)
{
if(points[1].y==0)
points[1].y=15;
else
points[1].y--;
}
elseif(direction==3)
{
if(points[1].y==15)
points[1].y=0;
else
points[1].y++;
}
for(i=2;i{
q=points[i];
points[i]=p;
p=q;
}
if((points[1].x==points[0].x)&&(points[1].y==points[0].y))
{
eatApple();
}
if(isSelfOverlaping(points[1]))
{
key_start=1;
}
if(score==79)
{
key_start=1;
}
}
voidinitT1()//初始化定时器1
{EA=1;
TH1=0xDC;
TL1=0X00;
TMOD=0x10;
TR1=1;
ET1=1;
}
//设置定时器1的触发函数,初始时间为10ms,累计100次记1s
voidt1()interrupt3
{TH1=0xDC;
TL1=0X00;
count++;
if(count>speed)
{
count=0;
nextPlot();
}
keyScan();
}
voidint0()interrupt0//外部中断INT0
{key_start=1;
}
voidstart()
{init();initT1();showPoints();
}
voidmain()
{start();
}
#ifndefUTIL_H
#defineUTIL_H
#include
#defineinit_length3
#definerandomfact7
typedefstructPoint
{charx;
chary;
}point;
#endif
附录II(运行截图)