【制作】基于金沙滩51单片机的贪吃蛇程序

零、起因

要离开实验室了,但是还是有点不放心学弟们的学习,为了让他们知道单片机能干嘛,体会到单片机的快乐,特意作此程序,以提高他们对单片机的学习兴趣。
要实现以下功能:

  1. 食物根据随机种子的不同出现的序列也不同
  2. 经典贪吃蛇游戏,能穿墙
  3. 贪吃蛇速度随分数加快,分数越高,贪吃蛇速度越快
  4. 能显示分数

一、电路原理图

用的是金沙滩的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单片机的贪吃蛇程序相关推荐

  1. 【制作】基于金沙滩51单片机的电子密码锁程序

    基于金沙滩51单片机的电子密码锁程序 很久之前做的一个课设,在B站发了效果视频,发现忘记分享代码了,现在整理分享一下. 零.设计报告 1.设计要求 这部分是讲的整个系统实现了什么功能. 1.1.密码的 ...

  2. 基于51单片机的贪吃蛇小程序(8*8LED点阵实现)by_jy

    ** 基于51单片机的贪吃蛇小程序(8*8LED点阵实现)by_jy ** 一直很想写一个贪吃蛇的小程序,这两天终于抽空完成了,这里把我的思路分享给大家,仅供参考! 代码如下: 先放段主函数压压惊 v ...

  3. 基于51单片机的贪吃蛇游戏设计

    1绪 论 1.1本课题研究的背景及意义 随着当今社会的发展,人们的生活节奏变得越来越快,人们开始逐渐的融入全球化的世界.人们已经不再局限于一小块天地,加班,出差已经占据了现代人生活的绝大部分.这个时候 ...

  4. 基于51单片机的贪吃蛇小游戏8X8点阵 proteus仿真原理图程序

    硬件设计 基于C51作为MCU 一块8X8点阵作为游戏的显示器 四个按键作为操控按键 仿真图: 程序设计 #include <at89x51.h> #include "18b20 ...

  5. 基于51单片机的贪吃蛇小游戏

    #include <reg52.h>// 点阵寄存器定义 sbit LOAD = P1 ^ 0; sbit CLK = P1 ^ 1; sbit DATA = P1 ^ 2;sbit mK ...

  6. 金沙滩51单片机LED 流水灯程序

    我们学了点亮一个 LED 小灯,然后又学了 LED 小灯闪烁,现在我们要进一步学习如何让 8 个小灯依次一个接一个的点亮,流动起来,也就是常说的流水灯.先来看 8 个 LED的核心电路图,如图 4-1 ...

  7. 基于单片机的贪吃蛇游戏

    绪论 1.1 研究背景与意义 随着社会的发展,人们生活的步调日益加快,越来越多的人加入了全球化的世界. 人们不再拘泥于--,J,块天地,加班,出差成了现代人不可避免的公务.而此时一款可以 随时随地娱乐 ...

  8. 【去后厂村开游戏厅吧】基于pp-tinypose的体感贪吃蛇游戏

    [去后厂村开游戏厅吧]基于pp-tinypose的体感贪吃蛇游戏 你是否也被腰痛所困扰!你是否也是久坐一族!你是否也是网瘾少年! 来玩体感贪吃蛇吧!只需要电脑上有摄像头就可以玩体感游戏啦~远离屏幕,扭 ...

  9. Python制作当年第一款手机游戏-贪吃蛇游戏(练习)

    前言: 文章利用Python pygame做一个贪吃蛇的小游戏而且讲清楚每一段代码是用来干嘛的. 据说是贪吃蛇游戏是1976年,Gremlin公司推出的经典街机游戏,那我们今天用Python制作的这个 ...

  10. c语言五子棋代码_基于控制台的C语言贪吃蛇

    相信对很多人来说,学完C语言以后,都会找一些小程序来练练手.例如贪吃蛇.五子棋.俄罗斯方块等等. 今天给大家分享一个基于控制台的C语言贪吃蛇小程序. 基础知识要求:C语言基础. 知识点补充 这里写一些 ...

最新文章

  1. 黎曼曲面Riemann Surface
  2. 剑指offer--day07
  3. 【Android基础】趣谈Intent
  4. 程序兵法:Java String 源码的排序算法(一)
  5. 前端学习(1870)vue之电商管理系统电商系统之配置message全局弹框组件
  6. GDI+用PNG图片做半透明异型窗口
  7. 计算机视觉 | Python OpenCV 3 使用背景减除进行目标检测
  8. Spring 字符编码过滤
  9. 桥接网络,nat网络,静态IP配置,相关命令
  10. 控制台应用程序《石头剪刀布》——新手,
  11. Android上使用MP3格式录制声音
  12. 城市大脑已经几岁?城市大脑发展成熟度的年龄评估模型
  13. 计算机二级刷题库刷的到原题吗,刷题能过计算机二级吗?
  14. Asterisk入门教程
  15. 华为无线路由器信道怎么测试软件,路由器无线信道是什么怎么设置
  16. linux下firefox浏览器的flash版本过低解决方案
  17. 这五个数据分析师技巧你一定要知道!
  18. 想成功就不要设定目标,你信吗?
  19. nginx http 跳转到https
  20. 花木兰荣耀典藏皮肤特效一览 花木兰九霄神辉值得入手吗

热门文章

  1. suse11 升级glibc版本
  2. 根据城市编码提取出省份名和城市名
  3. Mac电脑网页完整的长截图怎么截
  4. 2021年电工(技师)考试内容及电工(技师)复审考试
  5. 基于hilbert变换的数字信号_通过Hilbert变换实现移相算法
  6. 17年前那场疫情:马云隔离在家,刘强东关了12家店,俞敏洪欠债700万......
  7. 抖音:时间熔炉的诞生
  8. 无线通信模块种类和优点
  9. 电脑主板线路连接图解_教你如何连接主板路线图文教程
  10. 为Macbook添加自己喜欢的英汉辞典