【制作】基于金沙滩51单片机的贪吃蛇程序
【制作】基于金沙滩51单片机的贪吃蛇程序
零、起因
要离开实验室了,但是还是有点不放心学弟们的学习,为了让他们知道单片机能干嘛,体会到单片机的快乐,特意作此程序,以提高他们对单片机的学习兴趣。
要实现以下功能:
- 食物根据随机种子的不同出现的序列也不同
- 经典贪吃蛇游戏,能穿墙
- 贪吃蛇速度随分数加快,分数越高,贪吃蛇速度越快
- 能显示分数
一、电路原理图
用的是金沙滩的51单片机开发板,同款的电路应该是一致的,这部分可略过。
单片机最小系统部分
跳线部分
这部分连的都是ADDR。
数码管、LED部分
这部分使用74HC245三态缓冲器来提高单片机P0口的负载能力,通过138译码器提高单片机的IO口复用。
按键部分
这部分为矩阵按键,连接到单片机的P2口。
蜂鸣器部分
蜂鸣器使用无源蜂鸣器,更自由,可以自定义音调等。
二、代码
新建51单片机工程,输入以下代码:
/*
2020-11-17 Minuye
*/#include <reg52.h>
#include <stdlib.h>/* IO引脚分配定义 */
sbit KEY_IN_1 = P2^4; //矩阵按键的扫描输入引脚1
sbit KEY_IN_2 = P2^5; //矩阵按键的扫描输入引脚2
sbit KEY_IN_3 = P2^6; //矩阵按键的扫描输入引脚3
sbit KEY_IN_4 = P2^7; //矩阵按键的扫描输入引脚4
sbit KEY_OUT_1 = P2^3; //矩阵按键的扫描输出引脚1
sbit KEY_OUT_2 = P2^2; //矩阵按键的扫描输出引脚2
sbit KEY_OUT_3 = P2^1; //矩阵按键的扫描输出引脚3
sbit KEY_OUT_4 = P2^0; //矩阵按键的扫描输出引脚4sbit ADDR0 = P1^0; //LED位选译码地址引脚0
sbit ADDR1 = P1^1; //LED位选译码地址引脚1
sbit ADDR2 = P1^2; //LED位选译码地址引脚2
sbit ADDR3 = P1^3; //LED位选译码地址引脚3
sbit ENLED = P1^4; //LED显示部件的总使能引脚sbit BUZZ = P1^6; //蜂鸣器控制引脚#define MAP_SIZE 8 //地图大小
#define MAP_DATA_SIZE 64 //地图数据大小
#define SLEEP_TIME 100 //每帧间隔时间
#define SNAKE_DEFAULT_LEN 3 //蛇默认长度//按键值
#define KEY_VAL_W 0x26 //向上键
#define KEY_VAL_A 0x27 //左
#define KEY_VAL_S 0x28 //下
#define KEY_VAL_D 0x25 //右//map: 地图, 每个元素的映射, -1为食物 0为空地 大于0为蛇(值为存活回合)
char pdata map[MAP_DATA_SIZE];
unsigned char dztBuff[8];
unsigned char isShowHeader;
unsigned char len, i, X, Y;
unsigned char move, inputBuf;//随机算法相关
unsigned char seed;//矩阵按键到标准键码的映射表//矩阵按键到标准键码的映射表
const unsigned char code KeyCodeMap[4][4] = { { '1', '2', '3', 0x26 }, //数字键1、数字键2、数字键3、向上键{ '4', '5', '6', 0x25 }, //数字键4、数字键5、数字键6、向左键{ '7', '8', '9', 0x28 }, //数字键7、数字键8、数字键9、向下键{ '0', 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键
};
//全部矩阵按键的当前状态
unsigned char pdata KeySta[4][4] = { {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};//数码管真值表
unsigned char code LedChar[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};//Led显存
unsigned char ledBuff;
//数码管显存
#define SMG_BUFF_SIZE 6
unsigned char smgBuff[SMG_BUFF_SIZE];
//Led点阵显存
#define DZT_BUFF_SIZE 8
unsigned char dztBuff[8];
//当前状态(状态机)
unsigned char mode = 1;
//当前按键值
unsigned char currentKeyVal = 0;
//蜂鸣器开关,打开后蜂鸣器响,并自动置0
bit flagBuzzOn = 0;unsigned char _kbhit()
{if(currentKeyVal){return 1;} return 0;
}unsigned char _getch()
{unsigned char ckv = currentKeyVal;currentKeyVal = 0;return ckv;
}void UpdateSmg(unsigned int val)
{ledBuff = ~(0x80>>(val%8));smgBuff[0] = LedChar[val%10];smgBuff[1] = LedChar[val/10%10];smgBuff[2] = LedChar[val/100%10];smgBuff[3] = LedChar[val/1000%10];smgBuff[4] = LedChar[val/10000%10];smgBuff[5] = LedChar[val/100000%10];
}//游戏初始化
void InitGreedySnake()
{unsigned char j;move = KEY_VAL_D;//初始化方向inputBuf = 0;//重置输入缓存len = SNAKE_DEFAULT_LEN;//设置蛇的长度X = 0;//初始化蛇头坐标Y = 0;//初始化地图for (j = 0; j < MAP_DATA_SIZE; j++){map[j] = 0;}//初始化随机srand(seed);//找一块空地,等下设置食物while (map[i = rand() % MAP_DATA_SIZE]);//设为食物map[i] = -1;
}//贪吃蛇游戏
unsigned char GreedySnake()
{char mi,temp;char * p = 0;/*//蛇头闪烁if (isShowHeader){//使用位操作把蛇头置空dztBuff[Y] = dztBuff[Y] & (~(0x80 >> (X % MAP_SIZE)));isShowHeader = 0;}else{isShowHeader = 1;}*///如果没按退出键if(inputBuf != 0x1B){//检测输入if (_kbhit()) {//获取输入inputBuf = _getch();switch (inputBuf)//动作冲突检测,如果与原动作不冲突,则覆盖原动作{case KEY_VAL_A:if (move != KEY_VAL_D)move = KEY_VAL_A; break;case KEY_VAL_D:if (move != KEY_VAL_A)move = KEY_VAL_D; break;case KEY_VAL_S:if (move != KEY_VAL_W)move = KEY_VAL_S; break;case KEY_VAL_W:if (move != KEY_VAL_S)move = KEY_VAL_W; break;}}//输入switch (move){case KEY_VAL_A:p = &X, *p -= 1; break;//p指向对应轴, 并更新坐标case KEY_VAL_D:p = &X, *p += 1; break;case KEY_VAL_S:p = &Y, *p += 1; break;//因为Y轴向下为正, 所以这里是加1case KEY_VAL_W:p = &Y, *p -= 1; break;}//如果越界, 则移动至另一端*p = (*p + MAP_SIZE) % MAP_SIZE; //p指向蛇头对应的地图元素p = map + X + Y * MAP_SIZE;if (*p > 1)//如果撞到自己{//游戏结束 (1为蛇尾)return 1;}if (*p == -1)//如果为食物{//寻找空地while (map[i = rand() % MAP_DATA_SIZE]);//设置食物, 蛇长+1map[i] = -1, len += 1;//蜂鸣器响flagBuzzOn = 1;}else {//空地for (i = 0; i < MAP_DATA_SIZE; i++) {//遍历地图, 所有蛇的值-1 (去掉蛇尾)if (map[i] > 0){map[i]--;}}} //状态判断 p指向地图元素, i为空地下标for (*p = len,mi = 0, i = 0,temp = 0; i < MAP_DATA_SIZE;) //蛇头赋值, 遍历地图{if (map[i] == 0) {dztBuff[mi] = dztBuff[mi] & (~(0x80 >> (temp)));}else if (map[i] > 0) {dztBuff[mi] = dztBuff[mi] | (0x80 >> (temp));}else {//食物dztBuff[mi] = dztBuff[mi] | (0x80 >> (temp));}i++;temp = i % MAP_SIZE;if (temp == 0) {//如果到下一行的元素mi++;}}//正常调用return 0;}else {//按了退出键,执行退出程序return 1;}
}//延迟5ms*unit
void DelayN5ms(unsigned char unit)
{unsigned char a,b,c;while(unit--){for(c=1;c>0;c--)for(b=200;b>0;b--)for(a=10;a>0;a--);}
}//按键驱动
void KeyDriver()
{unsigned char i, j;static unsigned char pdata backup[4][4] = { //按键值备份,保存前一次的值{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};for (i=0; i<4; i++) //循环检测4*4的矩阵按键{for (j=0; j<4; j++){if (backup[i][j] != KeySta[i][j]) //检测按键动作{if (backup[i][j] != 0) //按键按下时执行动作{if(currentKeyVal == 0){currentKeyVal = KeyCodeMap[i][j];}}backup[i][j] = KeySta[i][j]; //刷新前一次的备份值}}}
}void InitSys(unsigned char val)
{unsigned char i;flagBuzzOn = 1;ledBuff = val;for(i=0;i<DZT_BUFF_SIZE;i++){if(i<SMG_BUFF_SIZE){smgBuff[i] = val;}dztBuff[i] = ~val;}
}void main()
{unsigned char i;EA = 1; //使能总中断ENLED = 0; //使能U3TMOD = 0x11; //设置T1为模式1,T0为模式1ET1 = 1; //使能T1中断TR1 = 1; //启动T1ET0 = 1; //使能T0中断TR0 = 1; //启动T0while (1){switch(mode){case 1://初始化模式,自检InitSys(0);//延时1秒,让灯全亮以检查DelayN5ms(200);InitSys(0xff);mode = 2;break;case 2://随机种子模式,输入初始化随机种子KeyDriver();if(currentKeyVal == 0x0D){InitSys(0xff);mode = 3;break; }//随机种子seed += _getch();//显示随机种子UpdateSmg(seed);break;case 3://初始化游戏InitGreedySnake();mode = 4;break;case 4://游戏中i = 50 - (len*4);if(i<20){i = 20;}DelayN5ms(i);KeyDriver();if (GreedySnake()) {//游戏结束mode = 5;ledBuff = 0;flagBuzzOn = 1;DelayN5ms(200);flagBuzzOn = 1;DelayN5ms(200);flagBuzzOn = 1;}//显示分数UpdateSmg(len - SNAKE_DEFAULT_LEN);//break;case 5:KeyDriver();DelayN5ms(10);i++;if(i>240){i = 0;}if(i%10 == 0){flagBuzzOn = 1; }if(_getch() == 0x1b)//按下退出{InitSys(0xff);mode = 2;}break; }}
}//以下代码完成数码管动态扫描刷新
void SmgRefresh()
{static unsigned char i = 0;//显示消隐P0 = 0xFF;ADDR3 = 1; switch (i){case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=smgBuff[0]; break;case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=smgBuff[1]; break;case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=smgBuff[2]; break;case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=smgBuff[3]; break;case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=smgBuff[4]; break;case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=smgBuff[5]; break; case 6: ADDR2=1; ADDR1=1; ADDR0=0; i=0; P0=ledBuff; break;default: break;}
}void DzlRefresh()
{static unsigned char i = 0;P0 = 0xFF;ADDR3=0;switch(i){case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=~dztBuff[0]; break;case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=~dztBuff[1]; break;case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=~dztBuff[2]; break;case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=~dztBuff[3]; break;case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=~dztBuff[4]; break;case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=~dztBuff[5]; break; case 6: ADDR2=1; ADDR1=1; ADDR0=0; i++; P0=~dztBuff[6]; break; case 7: ADDR2=1; ADDR1=1; ADDR0=1; i=0; P0=~dztBuff[7]; break;default: break;}
}//按键扫描程序
void KeyScan()
{unsigned char i;static unsigned char keyout = 0; //矩阵按键扫描输出索引static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}};//将一行的4个按键值移入缓冲区keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;//消抖后更新按键状态for (i=0; i<4; i++) //每行4个按键,所以循环4次{if ((keybuf[keyout][i] & 0x07) == 0x00){ //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下KeySta[keyout][i] = 0;}else if ((keybuf[keyout][i] & 0x07) == 0x07){ //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起KeySta[keyout][i] = 1;}}//执行下一次的扫描输出keyout++; //输出索引递增keyout &= 0x03; //索引值加到4即归零switch (keyout) //根据索引值,释放当前输出引脚,拉低下次的输出引脚{case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;default: break;}
}/* 定时器1中断服务函数 */
void InterruptTimer1() interrupt 3
{static unsigned char cnt = 0;TH1 = 0xFC; //重新加载初值TL1 = 0x66;cnt++;KeyScan(); if(cnt%2 == 0){SmgRefresh();}else{DzlRefresh();}
}/* T0中断服务函数,执行串口接收监控和蜂鸣器驱动 */
void InterruptTimer0() interrupt 1
{static unsigned char cnt = 0;TH0 = 0xFD; //重新加载重载值TL0 = 0x34;if (flagBuzzOn) //执行蜂鸣器鸣叫或关闭{BUZZ = ~BUZZ;cnt++;if(cnt>240){cnt = 0;flagBuzzOn = 0;}}else{BUZZ = 1;}
}
代码只有525行,还包括注释和空行!!!
主要使用了状态机和随机种子来管理整个项目。
注释很完整了,有问题可以下方留言讨论哦~
三、效果演示
Bilibili:https://b23.tv/f12pdg(点击连接到B站看效果~)
可以完整实现贪吃蛇游戏的效果。
三、总结
- 状态机是一个很不错的东西,在裸机的情况下很实用。
- 兴趣是最好的老师,希望同学们能因此对单片机感兴趣,从而去学习它,单片机真的是个很有用的好东西!
【制作】基于金沙滩51单片机的贪吃蛇程序相关推荐
- 【制作】基于金沙滩51单片机的电子密码锁程序
基于金沙滩51单片机的电子密码锁程序 很久之前做的一个课设,在B站发了效果视频,发现忘记分享代码了,现在整理分享一下. 零.设计报告 1.设计要求 这部分是讲的整个系统实现了什么功能. 1.1.密码的 ...
- 基于51单片机的贪吃蛇小程序(8*8LED点阵实现)by_jy
** 基于51单片机的贪吃蛇小程序(8*8LED点阵实现)by_jy ** 一直很想写一个贪吃蛇的小程序,这两天终于抽空完成了,这里把我的思路分享给大家,仅供参考! 代码如下: 先放段主函数压压惊 v ...
- 基于51单片机的贪吃蛇游戏设计
1绪 论 1.1本课题研究的背景及意义 随着当今社会的发展,人们的生活节奏变得越来越快,人们开始逐渐的融入全球化的世界.人们已经不再局限于一小块天地,加班,出差已经占据了现代人生活的绝大部分.这个时候 ...
- 基于51单片机的贪吃蛇小游戏8X8点阵 proteus仿真原理图程序
硬件设计 基于C51作为MCU 一块8X8点阵作为游戏的显示器 四个按键作为操控按键 仿真图: 程序设计 #include <at89x51.h> #include "18b20 ...
- 基于51单片机的贪吃蛇小游戏
#include <reg52.h>// 点阵寄存器定义 sbit LOAD = P1 ^ 0; sbit CLK = P1 ^ 1; sbit DATA = P1 ^ 2;sbit mK ...
- 金沙滩51单片机LED 流水灯程序
我们学了点亮一个 LED 小灯,然后又学了 LED 小灯闪烁,现在我们要进一步学习如何让 8 个小灯依次一个接一个的点亮,流动起来,也就是常说的流水灯.先来看 8 个 LED的核心电路图,如图 4-1 ...
- 基于单片机的贪吃蛇游戏
绪论 1.1 研究背景与意义 随着社会的发展,人们生活的步调日益加快,越来越多的人加入了全球化的世界. 人们不再拘泥于--,J,块天地,加班,出差成了现代人不可避免的公务.而此时一款可以 随时随地娱乐 ...
- 【去后厂村开游戏厅吧】基于pp-tinypose的体感贪吃蛇游戏
[去后厂村开游戏厅吧]基于pp-tinypose的体感贪吃蛇游戏 你是否也被腰痛所困扰!你是否也是久坐一族!你是否也是网瘾少年! 来玩体感贪吃蛇吧!只需要电脑上有摄像头就可以玩体感游戏啦~远离屏幕,扭 ...
- Python制作当年第一款手机游戏-贪吃蛇游戏(练习)
前言: 文章利用Python pygame做一个贪吃蛇的小游戏而且讲清楚每一段代码是用来干嘛的. 据说是贪吃蛇游戏是1976年,Gremlin公司推出的经典街机游戏,那我们今天用Python制作的这个 ...
- c语言五子棋代码_基于控制台的C语言贪吃蛇
相信对很多人来说,学完C语言以后,都会找一些小程序来练练手.例如贪吃蛇.五子棋.俄罗斯方块等等. 今天给大家分享一个基于控制台的C语言贪吃蛇小程序. 基础知识要求:C语言基础. 知识点补充 这里写一些 ...
最新文章
- 黎曼曲面Riemann Surface
- 剑指offer--day07
- 【Android基础】趣谈Intent
- 程序兵法:Java String 源码的排序算法(一)
- 前端学习(1870)vue之电商管理系统电商系统之配置message全局弹框组件
- GDI+用PNG图片做半透明异型窗口
- 计算机视觉 | Python OpenCV 3 使用背景减除进行目标检测
- Spring 字符编码过滤
- 桥接网络,nat网络,静态IP配置,相关命令
- 控制台应用程序《石头剪刀布》——新手,
- Android上使用MP3格式录制声音
- 城市大脑已经几岁?城市大脑发展成熟度的年龄评估模型
- 计算机二级刷题库刷的到原题吗,刷题能过计算机二级吗?
- Asterisk入门教程
- 华为无线路由器信道怎么测试软件,路由器无线信道是什么怎么设置
- linux下firefox浏览器的flash版本过低解决方案
- 这五个数据分析师技巧你一定要知道!
- 想成功就不要设定目标,你信吗?
- nginx http 跳转到https
- 花木兰荣耀典藏皮肤特效一览 花木兰九霄神辉值得入手吗