前言
课程设计报告以及keil文件和protues仿真在文末。
一、设计内容及要求
设计一个贪吃蛇游戏,使其具有以下游戏规则:
①当没有改变方向时,贪吃蛇沿原来路径一直前进
②贪吃蛇无法回头,只能异于当前方向改变行动
③蛇头碰到蛇身时死亡,游戏结束
④贪吃蛇吃到豆子则蛇身增加
⑤蛇头碰到墙壁时死亡,游戏结束
⑥当蛇长达到17及以上时出现山洞出口,进入则游戏胜利
二、系统的硬件设计
- 主控芯片为AT89C51
- LCD-12864为显示器输出
- 多个按键作为控制器
2.1 Protues仿真图
2.2 主控芯片连接
其中P0口连接LCD-12864的DB0~DB7,P2.0~P2.1连接LCD两个片选端,P2.2~P2.4连接LCD的指令线,读写线,以及电源线,P1.0~P1.4连接控制的四个按键。
2.3 LCD-12864连接
分别连接至AT89C51的引脚
2.4 控制按键连接
此法连接,当按下按键后,相应端口置零
三、系统的软件设计
3.1 贪吃蛇算法介绍
贪吃蛇游戏算法的实现,即如何通过液晶屏显示蛇的移动。
1、其实蛇看似移动的过程中,实质只有两个点再变,即蛇头前进方向增加一个点,蛇尾减少一个点。
2、知道如何显示蛇的移动后,再一个关键问题就是蛇的转折问题,如何控制蛇尾消失的点沿着蛇的路径。通俗的说就是蛇怎么实现曲折移动,这里就用到了循环队列,每次蛇头前进方向发生变化后,队列增加一个结点,结点中包含蛇头方向变化的位置,以及蛇头转变的方向。
3、蛇尾每次移动的过程中都会与队列头指针所储存的位置做一次判断,当蛇尾到达这一位置后,从结点中取出保存的蛇头的移动方向赋给蛇尾,结点出队列即删除这一结点。
4、由于队列是先进先出,所以每前进一步会先push进蛇头的节点,再pop出蛇尾的节点,即实现了蛇的曲折运动。
3.2 典型算法实现
3.2.1 采用循环队列存储蛇头的转向信息
存储蛇头转向的信息使用了step记录,即每走一步使step+1,初始化蛇头step>蛇尾step,当按下按键时代表蛇头发生转向,用循环队列记录此时蛇头step坐标入队,同时使队尾指针+1,当不停前进蛇尾到达此step时,代表之前蛇头在此发生转向,则可不用具体xy坐标记录信息,从而节省存储空间。
// 按键扫描
void Key_Scan(void){
if (KEY0 == 0)
pause();
else if (KEY1 == 0) {
if (direction != 2) { //蛇不能直接掉头
direction = 1;
tailtrun_step[rear] = head_step;
tailtrun_direction[rear] = 1;
rear = (rear + 1) % 20;
}
}
else if (KEY2 == 0) {
if (direction != 1) {
direction = 2;
tailtrun_step[rear] = head_step;
tailtrun_direction[rear] = 2;
rear = (rear + 1) % 20;
}
}
else if (KEY3 == 0) {
if (direction != 4) {
direction = 3;
tailtrun_step[rear] = head_step;
tailtrun_direction[rear] = 3;
rear = (rear + 1) % 20;
}
}
else if (KEY4 == 0) {
if (direction != 3) {
direction = 4;
tailtrun_step[rear] = head_step;
tailtrun_direction[rear] = 4;
rear = (rear + 1) % 20;
}
}
}
3.2.2 根据行列具体点亮或熄灭LCD-12864某一个点
由于LCD-12864的点阵点亮是八位DB0~DB7一次性操作一个page,代表一次只能八位一起操作,无法根据某一点的line,column点亮一个点,这里采用了位与或的思想,即根据某一点的line算出此坐标点对应的page中所在的位,后读取这点所在整列的数据与此位进行位或,即可在不改变原始数据的情况下点亮此点,熄灭相似,先对位信息进行取反,后与所在整列的数据与此位进行位与,即可熄灭某一具体点。
// 点亮一个点,行列
void show_p(uint8 screen, uint8 line, uint8 column) {
SelectScreen(screen);
dat = read_LCD_data(line / 8, column);
Set_page(line/8);
Set_column(column);
write_LCD_data(dat | bitTo16(line % 8));
}
// 熄灭一个点,行列
void out_p(uint8 screen, uint8 line, uint8 column) {
SelectScreen(screen);
dat = read_LCD_data(line / 8, column);
Set_page(line/8);
Set_column(column);
write_LCD_data(dat & ~(bitTo16(line % 8)));
}
3.2.3 碰撞检测
同样采取位与的思想,函数传入具体点的line,column坐标,读取此列的信息,后进行位与操作,若结果不为零,则代表此点已被点亮,表示发生碰撞(蛇身,墙壁)。
// 碰撞检测
void hit_block(uint8 line, uint8 column) {
dat = read_LCD_data(line * 2 / 8, column * 2);
Set_page(line * 2 /8);
Set_column(column * 2);
if ((dat & bitTo16(line * 2 % 8)) != 0)
over();
}
3.2.4 2×2点扩大
由于一次只亮一个点,导致贪吃蛇和豆子会很小,对视觉和操作是一个很大的考验,于是可以使用比例扩大到一个点为2×2,使得游戏界面变得更加亲和。对于原先的1点扩大到2个点,我使用了每个点对应一个坐标,使得4个点是一体的,为后续的函数书写提供极大便利。
// 2×2合并为一个点
void show_bp(uint8 screen,uint8 line,uint8 column){
SelectScreen(screen);
start_line = line * 2;start_column = column * 2;
for(j=0;j<2;j++){
dat = read_LCD_data(start_line / 8, start_column + j);
write_LCD_data(dat | bitTo16(start_line % 8));
dat = read_LCD_data(start_line / 8, start_column + j);
write_LCD_data(dat | bitTo16(start_line % 8 + 1));
}
Set_page(start_line / 8); //前面设置完会自动+1,需重新设置回来
Set_column(start_column + 1);
}
void out_bp(uint8 screen,uint8 line,uint8 column){
SelectScreen(screen);
start_line = line * 2;start_column = column * 2;
for(j=0;j<2;j++){
dat = read_LCD_data(start_line / 8, start_column + j);
write_LCD_data(dat & ~( bitTo16(start_line % 8)));
dat = read_LCD_data(start_line / 8, start_column + j);
write_LCD_data(dat & ~( bitTo16(start_line % 8 + 1)));
}
Set_page(start_line / 8); //前面设置完会自动+1,需重新设置回来
Set_column(start_column + 1);
}
3.2.5 字模算法
由于LCD-12864采用的是点阵点亮,若想显示具体汉字信息,则需使用16×16的字模运算。可使用“PCtoLCD2002”软件进行字模提取。
在具体调用时,应设计相应算法,适应具体字模,例如16×16,由于一次只能书写8位,16则需分两次书写,先写上面的8×16,再写下方的8×16。
// 16×16字模
void show_ch(uint8 screen,uint8 page,uint8 column,uint8 *p)
{
uint8 i;
SelectScreen(screen);
Set_page(page);
Set_column(column);
for(i=0;i<16;i++) //采用16*16的字模
{
write_LCD_data(p[i]);
}
Set_page(page+1);
Set_column(column);
for(i=0;i<16;i++) //采用16*16的字模,"小四号字"
{
write_LCD_data(p[i+16]);
}
}
//游戏开始结束字模
uint8 code ch[]=
{
0x10,0x60,0x02,0x8C,0x00,0x08,0xF9,0x4E,0xC8,0x20,0x58,0x4F,0x48,0xC8,0x08,0x00,
0x04,0x04,0x7E,0x81,0x40,0x30,0x0F,0x40,0x7F,0x00,0x44,0x84,0x7F,0x04,0x04,0x00,/*"游",0*/
0x00,0x08,0x48,0x88,0x08,0xC8,0x38,0x40,0x40,0x40,0xFF,0x20,0x22,0xAC,0x20,0x00,
0x00,0x20,0x10,0x0C,0x03,0x04,0x18,0x80,0x40,0x20,0x17,0x18,0x26,0x41,0xF0,0x00,/*"戏",1*/
0x80,0x82,0x82,0x82,0xFE,0x82,0x82,0x82,0x82,0x82,0xFE,0x82,0x82,0x82,0x80,0x00,
0x00,0x80,0x40,0x30,0x0F,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,/*"开",2*/
0x10,0x10,0xF0,0x1F,0x10,0xF0,0x00,0x40,0xE0,0x58,0x47,0x40,0x50,0x60,0xC0,0x00,
0x40,0x22,0x15,0x08,0x16,0x21,0x00,0x00,0xFE,0x42,0x42,0x42,0x42,0xFE,0x00,0x00,/*"始",3*/
0x20,0x30,0xAC,0x63,0x20,0x18,0x08,0x48,0x48,0x48,0x7F,0x48,0x48,0x48,0x08,0x00,
0x22,0x67,0x22,0x12,0x12,0x12,0x00,0xFE,0x42,0x42,0x42,0x42,0x42,0xFE,0x00,0x00,/*"结",4*/
0x04,0x04,0xE4,0x24,0x24,0x24,0x24,0xFF,0x24,0x24,0x24,0x24,0xE4,0x04,0x04,0x00,
0x40,0x40,0x27,0x22,0x12,0x0A,0x06,0xFF,0x06,0x0A,0x12,0x22,0x27,0x40,0x40,0x00,/*"束",5*/
0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"!",6*/
};
// 游戏开始界面
void start(){
show_im(1, 2, 0 * 16, image1);
show_ch(1, 4, 2 * 16, ch + 32 * 0);
show_ch(1, 4, 3 * 16, ch + 32 * 1);
show_ch(2, 4, 0 * 16, ch + 32 * 2);
show_ch(2, 4, 1 * 16, ch + 32 * 3);
show_ch(2, 4, 2 * 16, ch + 32 * 6);
show_ch(2, 4, 3 * 16, ch + 32 * 6);
pause();
ClearScreen(0);
}
附录
百度网盘,提取码:r70l