目录
系列文章目录
前言
《贪吃蛇》,一款经典的、怀旧的小游戏,单片机入门必写程序。
之前写了8X8点阵屏、16X16点阵屏、LCD1602液晶屏、LCD12864四个版本的贪吃蛇,现在是OLED12864液晶屏版本的贪吃蛇。
B站对应视频的简介的下载链接中有两个版本,分别是:
①普中开发板矩阵按键版本:晶振频率为11.0592MHz。
②自制独立按键版本:晶振频率为12.0000MHz。
两个版本所用单片机芯片均为:STC89C52RC。
本文对应的版本是自制独立按键版本。
效果查看/操作演示:B站搜索"甘腾胜"或"gantengsheng"查看。
源代码下载:B站对应视频的简介有工程文件下载链接。
一、效果展示
二、原理分析
贪吃蛇的随机食物的生成、蛇身数据的保存、蛇的移动、蛇头方向控制、碰撞检测、定时器扫描按键等不在赘述,可以参考一下我的另外一篇文章:
基于51单片机和8X8LED点阵屏(板载74HC595驱动)的矩阵按键控制的小游戏《贪吃蛇》【普中A2开发板版本】
本文简单了解一下OLED的显示问题。
OLED12864和LCD12864硬件连接不同,所以驱动函数、取模设置等也不一样。
OLED12864一个字节所控制的8个点是纵向分布的,高位在下。整个屏幕分成了八页,每一页的像素均为宽128*高8=1024。设置光标的时候,纵向(从上到下)的范围是0~7,对应1~8页,横向(从左到右)的范围是0~127,对应1~128列。这就比LCD12864(需要横向取模,连续写2个字节光标才自动增加1)的简单多了,可以较轻松地控制屏幕的任意一个点。
跟LCD12864版本的贪吃蛇一样,我们可以把大小为8*8像素的区域作为最小单位来控制,将整个屏幕分为128个这样的区域,总共有16列和8行这样的8*8像素的区域。就相当于一个16*8的点阵屏了。
同时,由于每个区域总共是64个像素,我们就可以有更多的发挥,例如可以自定义蛇头、蛇尾、蛇身连接、食物的显示了。
蛇头方向有4个。
蛇身连接有6种(右上、右左、右下、上左、上下、左下)。
蛇尾方向也有4个。
因为有128个8*8的区域,每个区域都需要一个字节来保存应该显示什么的信息,所以除了蛇身数据要用128个字节(变量)保存,每个区域应该显示什么内容也需要128个字节(变量)来保存。刚好把片外RAM的256个字节给用完了。
四线OLED12864用的是I2C协议进行通讯,单片机用STC89C52RC速度会比较慢,更新显示整个屏幕所需的时间较长,刷新率较低,但是这对游戏体验没有影响。蛇往前移动一格,如果没吃到食物的话,相当于蛇头前进了一格,原来蛇头的地方变成了蛇身,蛇尾也前进了一格,即蛇尾的前一格由蛇身变成蛇尾,原来的蛇尾的位置清空显示。所以每次移动,蛇的中间部分不用更改,只需要操作4个8*8的区域就行了,即只需要更改256个像素的显示,比整个屏幕的8192个像素少多了。所以游戏时感觉还是比较流畅的。
三、各模块代码
1、八位独立按键
h文件
c
#ifndef __KEYSCAN_8_H__
#define __KEYSCAN_8_H__
unsigned char Key(void);
void Key_Tick(void);
#endif
c文件
c
#include <REGX52.H>
sbit Key1=P1^0;
sbit Key2=P1^1;
sbit Key3=P1^2;
sbit Key4=P1^3;
sbit Key5=P1^4;
sbit Key6=P1^5;
sbit Key7=P1^6;
sbit Key8=P1^7;
unsigned char KeyNumber;
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围:0,1~24,0表示无按键按下
*/
unsigned char Key(void)
{
unsigned char KeyTemp=0;
KeyTemp=KeyNumber;
KeyNumber=0; //主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回0
return KeyTemp;
}
/**
* @brief 获取当前按下按键的状态,无消抖及松手检测
* @param 无
* @retval 按键值,范围:0~8,无按键按下时返回值为0
*/
unsigned char Key_GetState()
{
unsigned char KeyValue=0;
if(Key1==0){KeyValue=1;}
if(Key2==0){KeyValue=2;}
if(Key3==0){KeyValue=3;}
if(Key4==0){KeyValue=4;}
if(Key5==0){KeyValue=5;}
if(Key6==0){KeyValue=6;}
if(Key7==0){KeyValue=7;}
if(Key8==0){KeyValue=8;}
return KeyValue;
}
/**
* @brief 按键驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Key_Tick(void)
{
static unsigned char NowState,LastState;
LastState=NowState; //按键状态更新
NowState=Key_GetState(); //获取当前按键状态
//如果上个时间点按键未按下,这个时间点按键按下,则是按下瞬间
if(LastState==0)
{
switch(NowState)
{
case 1:KeyNumber=1;break;
case 2:KeyNumber=2;break;
case 3:KeyNumber=3;break;
case 4:KeyNumber=4;break;
case 5:KeyNumber=5;break;
case 6:KeyNumber=6;break;
case 7:KeyNumber=7;break;
case 8:KeyNumber=8;break;
default:break;
}
}
//如果上个时间点按键按下,这个时间点按键按下,则是一直按住按键
if(LastState && NowState)
{
if(LastState==1 && NowState==1){KeyNumber=9;}
if(LastState==2 && NowState==2){KeyNumber=10;}
if(LastState==3 && NowState==3){KeyNumber=11;}
if(LastState==4 && NowState==4){KeyNumber=12;}
if(LastState==5 && NowState==5){KeyNumber=13;}
if(LastState==6 && NowState==6){KeyNumber=14;}
if(LastState==7 && NowState==7){KeyNumber=15;}
if(LastState==8 && NowState==8){KeyNumber=16;}
}
//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间
if(NowState==0)
{
switch(LastState)
{
case 1:KeyNumber=17;break;
case 2:KeyNumber=18;break;
case 3:KeyNumber=19;break;
case 4:KeyNumber=20;break;
case 5:KeyNumber=21;break;
case 6:KeyNumber=22;break;
case 7:KeyNumber=23;break;
case 8:KeyNumber=24;break;
default:break;
}
}
}
2、OLED12864
h文件
c
#ifndef __OLED_H
#define __OLED_H
unsigned char pdata SnakeStyle[][16]; //在此声明,则数据外部可调用
void OLED_WriteCommand(unsigned char Command);
void OLED_WriteData(unsigned char Data);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(unsigned char Line, unsigned char Column, char Char);
void OLED_ShowString(unsigned char Line, unsigned char Column, char *String);
void OLED_ShowNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length);
void OLED_ShowSignedNum(unsigned char Line, unsigned char Column, int Number, unsigned char Length);
void OLED_ShowHexNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length);
void OLED_ShowBinNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length);
void OLED_ShowImage_16x16(unsigned char Line, unsigned char Column, unsigned char *Array, unsigned char Length);
void OLED_ShowImage_8X8(unsigned char Line, unsigned char Column, unsigned char *Array);
void OLED_DrawBlock(unsigned char X,Y,State);
#endif
h文件
c
#ifndef __OLED_FONT_H
#define __OLED_FONT_H
/*OLED字模库,宽8像素,高16像素*/
unsigned char code OLED_F8x16[][16]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};
#endif
c文件
c
#include <REGX52.H>
#include "OLED_Font.h"
/*引脚配置*/
sbit OLED_SCL=P2^3;
sbit OLED_SDA=P2^4;
//用128个字节记录128个区域(每个区域的像素是8*8)应该显示什么内容
unsigned char pdata SnakeStyle[8][16];
//取模方式:每一个8*8区域逐列式取模,高位在下,阴码(亮点为1)
unsigned char code SnakeType[]={ //每个8*8区域的显示的内容的取模数据
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无显示,0
0x3C,0x7E,0x7E,0xFF,0xFF,0x5A,0x3C,0x18, //蛇头方向,右,1
0x18,0x7C,0xFA,0xFF,0xFF,0xFA,0x7C,0x18, //蛇头方向,上,2
0x18,0x3C,0x5A,0xFF,0xFF,0x7E,0x7E,0x3C, //蛇头方向,左,3
0x18,0x3E,0x5F,0xFF,0xFF,0x5F,0x3E,0x18, //蛇头方向,下,4
0x00,0x7E,0x7F,0x67,0x67,0x7F,0x7E,0x3C, //蛇身连接,右上,5
0x3C,0x7E,0x7E,0x66,0x66,0x7E,0x7E,0x3C, //蛇身连接,右左,6
0x00,0x7E,0xFE,0xE6,0xE6,0xFE,0x7E,0x3C, //蛇身连接,右下,7
0x3C,0x7E,0x7F,0x67,0x67,0x7F,0x7E,0x00, //蛇身连接,上左,8
0x00,0x7E,0xFF,0xE7,0xE7,0xFF,0x7E,0x00, //蛇身连接,上下,9
0x3C,0x7E,0xFE,0xE6,0xE6,0xFE,0x7E,0x00, //蛇身连接,左下,10
0x00,0x18,0x18,0x3C,0x3C,0x7E,0x7E,0x3C, //蛇尾方向,左,11
0x00,0x06,0x1F,0x7F,0x7F,0x1F,0x06,0x00, //蛇尾方向,下,12
0x3C,0x7E,0x7E,0x3C,0x3C,0x18,0x18,0x00, //蛇尾方向,右,13
0x00,0x60,0xF8,0xFE,0xFE,0xF8,0x60,0x00, //蛇尾方向,上,14
0x00,0x7E,0x42,0x5A,0x5A,0x42,0x7E,0x00, //食物,15
};
/*引脚初始化*/
void OLED_I2C_Init(void)
{
OLED_SCL=1;
OLED_SDA=1;
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_SDA=1;
OLED_SCL=1;
OLED_SDA=0;
OLED_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_SDA=0;
OLED_SCL=1;
OLED_SDA=1;
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
OLED_SDA=Byte&(0x80>>i);
OLED_SCL=1;
OLED_SCL=0;
}
OLED_SCL=1; //额外的一个时钟,不处理应答信号
OLED_SCL=0;
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(unsigned char Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(unsigned char Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(unsigned char Y, unsigned char X)
{
OLED_WriteCommand(0xB0|Y); //设置Y位置
OLED_WriteCommand(0x10|((X&0xF0)>>4)); //设置X位置高4位
OLED_WriteCommand(0x00|(X&0x0F)); //设置X位置低4位
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
unsigned char i, j;
for(j=0;j<8;j++)
{
OLED_SetCursor(j,0);
for(i=0;i<128;i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(unsigned char Line, unsigned char Column, char Char)
{
unsigned char i;
OLED_SetCursor((Line-1)*2,(Column-1)*8); //设置光标位置在上半部分
for(i=0;i<8;i++)
{
OLED_WriteData(OLED_F8x16[Char-' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line-1)*2+1,(Column-1)*8); //设置光标位置在下半部分
for(i=0;i<8;i++)
{
OLED_WriteData(OLED_F8x16[Char-' '][i+8]); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(unsigned char Line, unsigned char Column, char *String)
{
unsigned char i;
for(i=0;String[i]!='\0';i++)
{
OLED_ShowChar(Line,Column+i,String[i]);
}
}
/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
unsigned int OLED_Pow(unsigned int X, unsigned int Y)
{
unsigned int Result = 1;
while(Y--)
{
Result*=X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void OLED_ShowNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length)
{
unsigned char i;
for(i=0;i<Length;i++)
{
OLED_ShowChar(Line,Column+i,Number/OLED_Pow(10,Length-i-1)%10+'0');
}
}
/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(unsigned char Line, unsigned char Column, int Number, unsigned char Length)
{
unsigned char i;
unsigned int Number1;
if(Number >= 0)
{
OLED_ShowChar(Line,Column,'+');
Number1=Number;
}
else
{
OLED_ShowChar(Line,Column,'-');
Number1=-Number;
}
for(i=0;i<Length;i++)
{
OLED_ShowChar(Line,Column+i+1,Number1/OLED_Pow(10,Length-i-1)%10+'0');
}
}
/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length)
{
unsigned char i, SingleNumber;
for(i=0;i<Length;i++)
{
SingleNumber=Number/OLED_Pow(16,Length-i-1)%16;
if(SingleNumber<10)
{
OLED_ShowChar(Line,Column+i,SingleNumber+'0');
}
else
{
OLED_ShowChar(Line, Column+i,SingleNumber-10+'A');
}
}
}
/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length)
{
unsigned char i;
for(i=0;i<Length;i++)
{
OLED_ShowChar(Line,Column+i,Number/OLED_Pow(2,Length-i-1)%2+'0');
}
}
/**
* @brief OLED显示横向的Length个16*16像素的图像
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~8
* @param Array 传递过来的指针(地址)
* @retval 无
*/
void OLED_ShowImage_16x16(unsigned char Line, unsigned char Column, unsigned char *Array, unsigned char Length)
{
unsigned char i;
OLED_SetCursor((Line-1)*2,(Column-1)*16); //设置光标位置在上半部分
for(i=0;i<16*Length;i++)
{
OLED_WriteData(*(Array+2*i)); //显示上半部分内容
}
OLED_SetCursor((Line-1)*2+1,(Column-1)*16); //设置光标位置在下半部分
for (i=0;i<16*Length;i++)
{
OLED_WriteData(*(Array+2*i+1)); //显示下半部分内容
}
}
/**
* @brief OLED显示1个8*8像素的图像
* @param Y 起始行位置,范围:0~7
* @param X 起始列位置,范围:0~15
* @param Array 传递过来的指针(地址)
* @retval 无
*/
void OLED_ShowImage_8X8(unsigned char Y, unsigned char X, unsigned char *Array)
{
unsigned char i;
OLED_SetCursor(Y,X*8); //设置光标位置
for(i=0;i<8;i++)
{
OLED_WriteData(*(Array+i));
}
}
/**
* @brief OLED12864更显显示一个8*8区域的内容
* @param X 起始行位置,范围:0~15
* @param Y 起始列位置,范围:0~7
* @param State 更新的状态,范围:0~1,0:不显示,1:显示(内容由数组SnakeStyle中对应的值和数组SnakeType的取模数据决定)
* @retval 无
*/
void OLED_DrawBlock(unsigned char X,Y,State)
{
if(State)
{
switch(SnakeStyle[Y][X]%16)
{
case 1:OLED_ShowImage_8X8(Y,X,SnakeType+8*1);break;
case 2:OLED_ShowImage_8X8(Y,X,SnakeType+8*2);break;
case 3:OLED_ShowImage_8X8(Y,X,SnakeType+8*3);break;
case 4:OLED_ShowImage_8X8(Y,X,SnakeType+8*4);break;
case 5:OLED_ShowImage_8X8(Y,X,SnakeType+8*5);break;
case 6:OLED_ShowImage_8X8(Y,X,SnakeType+8*6);break;
case 7:OLED_ShowImage_8X8(Y,X,SnakeType+8*7);break;
case 8:OLED_ShowImage_8X8(Y,X,SnakeType+8*8);break;
case 9:OLED_ShowImage_8X8(Y,X,SnakeType+8*9);break;
case 10:OLED_ShowImage_8X8(Y,X,SnakeType+8*10);break;
case 11:OLED_ShowImage_8X8(Y,X,SnakeType+8*11);break;
case 12:OLED_ShowImage_8X8(Y,X,SnakeType+8*12);break;
case 13:OLED_ShowImage_8X8(Y,X,SnakeType+8*13);break;
case 14:OLED_ShowImage_8X8(Y,X,SnakeType+8*14);break;
case 15:OLED_ShowImage_8X8(Y,X,SnakeType+8*15);break;
default:OLED_ShowImage_8X8(Y,X,SnakeType);break;
}
}
else
{
OLED_ShowImage_8X8(Y,X,SnakeType);
}
}
/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
unsigned char i;
for (i=0;i<200;i++); //上电延时
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_Clear(); //OLED清屏
OLED_WriteCommand(0xAF); //开启显示
}
3、定时器0
h文件
c
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
#endif
c文件
c
#include <REGX52.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)
TMOD|=0x01; //设置定时器模式(通过低四位设为"定时器0工作方式1"的模式)
TL0=0x18; //设置定时初值,定时1ms
TH0=0xFC; //设置定时初值,定时1ms
TF0=0; //清除TF0标志
TR0=1; //定时器0开始计时
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
PT0=0; //当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}
/*定时器中断函数模板
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
static unsigned int T0Count; //定义静态变量
TL0=0x18; //设置定时初值,定时1ms
TH0=0xFC; //设置定时初值,定时1ms
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}
*/
四、主函数
main.c
c
/*
by甘腾胜@20241217
效果查看/操作演示:可以在B站搜索"甘腾胜"或"gantengsheng"查看
单片机:STC89C52RC
晶振:12T@12.0000MHz
外设:自制的八位独立按键、0.96寸OLED12864
操作说明:
(1)自制独立按键版本
K7 K2 上:K7
下:K6
K8 K5 K4 K1 左:K8
右:K5
K6 K3 开始/暂停/继续:K1
返回:K2
(2)普中开发板矩阵按键版本
S1 S2 S3 S4 上:S10
下:S14
S5 S6 S7 S8 左:S13
右:S15
S9 S10 S11 S12 开始/暂停/继续:S16
返回:S12
S13 S14 S15 S16
*/
#include <REGX52.H> //包含寄存器的定义
#include <STDLIB.H> //包含随机函数的声明
#include "KeyScan_8.h"
#include "OLED.h"
#include "Timer0.h"
unsigned char KeyNum; //存储获得的键码值
unsigned char Mode; //游戏模式,0:主界面(显示游戏名称和选择难度),1:游戏进行模式
//2:游戏结束全屏闪烁,3:显示得分和游戏用时,4:显示作者和编程日期
unsigned char MoveSnakeFlag; //移动蛇身的标志,1:移动,0:不移动
unsigned char NowDirection=1; //蛇头移动的方向,1:向右,2:向上,3:向左,4:向下,游戏开始时默认向右移动
unsigned char LastDirection=1; //蛇头上一次移动的方向,1:向右,2:向上,3:向左,4:向下,游戏开始时默认向右移动
unsigned char Length=2; //蛇的长度,初始值为2
unsigned char Head=1; //保存整条蛇的数据的数组(共128个数据,数据索引为:0~127)中,蛇头对应的数据的索引,蛇的初始长度为2,
//开始时只用了两个数据(数组的第1个数据和第2个数据),蛇头对应的是第2个数据(索引为1),Head的范围:0~127
unsigned char GameOverFlag; //游戏结束的标志,1:游戏结束,0:游戏未结束
unsigned char FlashFlag; //闪烁的标志,1:不显示,0:显示
unsigned int Food; //保存创造出来的食物的位置,高四位(范围:0~15)对应列(1~16列),低四位(范围:0~7)对应行(1~8行)
//从左往右数,分别是1~16列,从上往下数,分别是1~8行
unsigned char ExecuteOnceFlag=1; //各模式中只执行一次的标志,1:执行,0:不执行,默认为1
unsigned char SnakeMoveSpeed=100; //蛇移动的速度,值越小,速度越快,上电默认1s移动一次(定时器计时时间为10ms)
unsigned char T0Count0,T0Count1,T0Count2; //定时器计数的变量
unsigned char PauseFlag; //暂停的标志,1:暂停,0:不暂停
unsigned char Difficulty=1; //游戏难度,范围:1~5,上电后默认是难度1
unsigned char ChangedFlag; //游戏难度选择界面,游戏难度有变动的标志,1:有变动,0:无变动,用于更新数字的显示
unsigned int GameTime; //游戏时间,游戏结束时用来显示已玩时间
unsigned char pdata SnakeBody[128]; //蛇身最长的长度是8X16=128,需要用128个数据记录蛇身的数据
unsigned char DisplayBuffer[8][2]; //显示缓存,用来记录屏幕哪些区域显示,哪些区域不显示,需要用16个字节存储这些信息,
//8*8像素为一个最小单位,即一个区域,将整个屏幕分为8行*16列=128个区域,一个Bit对应一个8*8的区域,
//横向记录,高位在左,例如,DisplayBuffer[0][1]=0x0F,表示第一行第9~12个区域不显示,
//第13~16个区域显示相关信息,具体显示什么内容,由SnakeStyle和SnakeType的数据决定
//取模设置:宋体,字体大小16*16,逐列式取模,高位在下,阴码(亮点为1)
unsigned char code Table1[]={ //" 《贪吃蛇》 "
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*" ",0*/ //宽8,高16,无显示,作用是使游戏名称居中
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x40,0x01,0x20,0x02,0x90,0x04,
0x48,0x09,0x24,0x12,0x12,0x24,0x09,0x48,0x04,0x10,0x02,0x20,0x00,0x00,0x00,0x00,/*"《",1*/
0x20,0x00,0x20,0x80,0x10,0x80,0x10,0x9F,0x28,0x41,0x24,0x41,0x22,0x21,0x29,0x1D,
0xB2,0x01,0x64,0x21,0x28,0x21,0x10,0x5F,0x10,0x40,0x20,0x80,0x20,0x00,0x00,0x00,/*"贪",2*/
0x00,0x00,0xFC,0x0F,0x04,0x04,0x04,0x04,0xFC,0x0F,0x20,0x00,0x10,0x30,0x4C,0x48,
0x4B,0x44,0x48,0x42,0x48,0x42,0x48,0x41,0xC8,0x40,0x08,0x40,0x08,0x70,0x00,0x00,/*"吃",3*/
0x00,0x20,0xF8,0x63,0x08,0x21,0xFF,0x1F,0x08,0x11,0xF8,0x19,0x20,0x30,0x18,0x00,
0xC8,0x3F,0x08,0x44,0x09,0x42,0x0E,0x41,0x88,0x40,0x28,0x40,0x18,0x78,0x00,0x00,/*"蛇",4*/
0x00,0x00,0x00,0x00,0x02,0x20,0x04,0x10,0x09,0x48,0x12,0x24,0x24,0x12,0x48,0x09,
0x90,0x04,0x20,0x02,0x40,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"》",5*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*" ",6*/ //宽8,高16,无显示,作用是使游戏名称居中
};
unsigned char code Table2[]={ //"难度选择:"
0x04,0x20,0x24,0x10,0xC4,0x0C,0x04,0x03,0xE4,0x04,0x5C,0x18,0x20,0x00,0xF8,0xFF,
0x4F,0x22,0x48,0x22,0x49,0x22,0xFA,0x3F,0x48,0x22,0x48,0x22,0x08,0x20,0x00,0x00,/*"难",0*/
0x00,0x40,0x00,0x30,0xFC,0x8F,0x24,0x80,0x24,0x84,0x24,0x4C,0xFC,0x55,0x25,0x25,
0x26,0x25,0x24,0x25,0xFC,0x55,0x24,0x4C,0x24,0x80,0x24,0x80,0x04,0x80,0x00,0x00,/*"度",1*/
0x40,0x00,0x40,0x40,0x42,0x20,0xCC,0x1F,0x00,0x20,0x50,0x50,0x4E,0x4C,0xC8,0x43,
0x48,0x40,0x7F,0x40,0xC8,0x4F,0x48,0x50,0x48,0x50,0x40,0x5C,0x00,0x40,0x00,0x00,/*"选",2*/
0x10,0x42,0x10,0x82,0xFF,0x7F,0x10,0x01,0x00,0x00,0x82,0x10,0x86,0x12,0x4A,0x12,
0x52,0x12,0xA2,0xFF,0x52,0x12,0x4A,0x12,0x86,0x12,0x80,0x10,0x80,0x00,0x00,0x00,/*"择",3*/
0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*":",4*/
};
unsigned char code Table3[]={ //难度选择的16*16的字模:"1~5"
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x10,0x20,0x10,0x20,0xF0,0x3F,
0xF8,0x3F,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,/*"1",0*/
0x00,0x00,0x00,0x00,0x30,0x30,0x70,0x30,0x28,0x28,0x08,0x24,0x08,0x24,0x08,0x22,
0x08,0x22,0x08,0x21,0x08,0x21,0xD8,0x20,0xF0,0x30,0x20,0x18,0x00,0x00,0x00,0x00,/*"2",1*/
0x00,0x00,0x00,0x00,0x30,0x18,0x30,0x18,0x28,0x28,0x08,0x20,0x08,0x20,0x08,0x21,
0x08,0x21,0x88,0x21,0x88,0x21,0x70,0x13,0x70,0x1E,0x00,0x0C,0x00,0x00,0x00,0x00,/*"3",2*/
0x00,0x00,0x00,0x04,0x00,0x06,0x00,0x05,0x80,0x04,0x80,0x04,0x40,0x24,0x20,0x24,
0x10,0x24,0xF0,0x3F,0xF8,0x3F,0xF8,0x3F,0x00,0x24,0x00,0x24,0x00,0x24,0x00,0x00,/*"4",3*/
0x00,0x00,0x00,0x00,0x00,0x18,0xF8,0x19,0x08,0x29,0x88,0x20,0x88,0x20,0x88,0x20,
0x88,0x20,0x88,0x20,0x88,0x20,0x88,0x11,0x08,0x1F,0x00,0x0E,0x00,0x00,0x00,0x00,/*"5",4*/
};
unsigned char code Table4[]={ //"您的得分是:"
0x20,0x40,0x10,0x30,0x08,0x00,0xFC,0x77,0x23,0x80,0x10,0x81,0x88,0x88,0x67,0xB2,
0x04,0x84,0xF4,0x83,0x04,0x80,0x24,0xE0,0x54,0x00,0x8C,0x11,0x00,0x60,0x00,0x00,/*"您",0*/
0x00,0x00,0xF8,0x7F,0x0C,0x21,0x0B,0x21,0x08,0x21,0x08,0x21,0xF8,0x7F,0x40,0x00,
0x30,0x00,0x8F,0x00,0x08,0x43,0x08,0x80,0x08,0x40,0xF8,0x3F,0x00,0x00,0x00,0x00,/*"的",1*/
0x00,0x02,0x10,0x01,0x88,0x00,0xC4,0xFF,0x33,0x00,0x00,0x02,0xBE,0x0A,0xAA,0x12,
0xAA,0x02,0xAA,0x42,0xAA,0x82,0xAA,0x7F,0xBE,0x02,0x80,0x02,0x00,0x02,0x00,0x00,/*"得",2*/
0x80,0x00,0x40,0x80,0x20,0x40,0x90,0x20,0x88,0x18,0x86,0x07,0x80,0x00,0x80,0x40,
0x80,0x80,0x83,0x40,0x8C,0x3F,0x10,0x00,0x20,0x00,0x40,0x00,0x80,0x00,0x00,0x00,/*"分",3*/
0x00,0x81,0x00,0x41,0x00,0x21,0x7F,0x1D,0x49,0x21,0x49,0x41,0x49,0x81,0x49,0xFF,
0x49,0x89,0x49,0x89,0x49,0x89,0x7F,0x89,0x00,0x89,0x00,0x81,0x00,0x81,0x00,0x00,/*"是",4*/
0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*":",5*/
};
unsigned char code Table5[]={ //"游戏用时:"
0x10,0x04,0x60,0x04,0x02,0x7E,0x8C,0x81,0x00,0x40,0x08,0x30,0xF9,0x0F,0x4E,0x40,
0xC8,0x7F,0x20,0x00,0x58,0x44,0x4F,0x84,0x48,0x7F,0xC8,0x04,0x08,0x04,0x00,0x00,/*"游",0*/
0x00,0x00,0x08,0x20,0x48,0x10,0x88,0x0C,0x08,0x03,0xC8,0x04,0x38,0x18,0x40,0x80,
0x40,0x40,0x40,0x20,0xFF,0x17,0x20,0x18,0x22,0x26,0xAC,0x41,0x20,0xF0,0x00,0x00,/*"戏",1*/
0x00,0x80,0x00,0x60,0xFE,0x1F,0x22,0x02,0x22,0x02,0x22,0x02,0x22,0x02,0xFE,0x7F,
0x22,0x02,0x22,0x02,0x22,0x42,0x22,0x82,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,/*"用",2*/
0x00,0x00,0xFC,0x3F,0x84,0x10,0x84,0x10,0x84,0x10,0xFC,0x3F,0x00,0x00,0x10,0x00,
0x10,0x01,0x10,0x06,0x10,0x40,0x10,0x80,0xFF,0x7F,0x10,0x00,0x10,0x00,0x00,0x00,/*"时",3*/
0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*":",4*/
};
unsigned char code Table6[]={ //"分秒"
0x80,0x00,0x40,0x80,0x20,0x40,0x90,0x20,0x88,0x18,0x86,0x07,0x80,0x00,0x80,0x40,
0x80,0x80,0x83,0x40,0x8C,0x3F,0x10,0x00,0x20,0x00,0x40,0x00,0x80,0x00,0x00,0x00,/*"分",0*/
0x24,0x08,0x24,0x06,0xA4,0x01,0xFE,0xFF,0x23,0x01,0x22,0x06,0x00,0x81,0xC0,0x80,
0x38,0x40,0x00,0x40,0xFF,0x27,0x00,0x10,0x08,0x0C,0x10,0x03,0x60,0x00,0x00,0x00,/*"秒",1*/
};
unsigned char code Table7[]={ //"甘腾胜"
0x00,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0xFF,0xFF,0x10,0x42,0x10,0x42,0x10,0x42,
0x10,0x42,0x10,0x42,0x10,0x42,0xFF,0xFF,0x10,0x00,0x10,0x00,0x10,0x00,0x00,0x00,/*"甘",0*/
0x00,0x80,0xFE,0x7F,0x22,0x02,0x22,0x82,0xFE,0xFF,0x28,0x01,0xA9,0x20,0x6E,0x2D,
0x28,0x29,0x3F,0x29,0x28,0x29,0x6C,0x4F,0xAB,0x88,0x28,0x79,0x20,0x01,0x00,0x00,/*"腾",1*/
0x00,0x80,0xFE,0x7F,0x22,0x02,0x22,0x82,0xFE,0xFF,0x00,0x00,0x40,0x40,0x3C,0x42,
0x10,0x42,0x10,0x42,0xFF,0x7F,0x10,0x42,0x10,0x42,0x10,0x42,0x00,0x40,0x00,0x00,/*"胜",2*/
};
/**
* @brief 蛇移动后蛇身连接、蛇头前进一格、蛇尾前进一格(没吃到食物)等有变动的地方,更新显示这些变动
* @brief 要注意的是,此函数更新的是缓存信息,需要通过函数UpdateDisplay更新到屏幕才更新显示
* @brief 中间数据不用处理,因为蛇移动一格,相当于蛇尾移到蛇头的前一格
* @param Position 需要更新的位置,高四位(0~15)对应1~16列,低4位(0~7)对应1~8行
* @param Type 修改显示的类型,范围:0~1,0:修改蛇头的方向,1:修改蛇身的连接
* @retval 无
*/
void UpdateAlteration(unsigned char Position,Type)
{
if(Type==0) //蛇的头部
{
if(NowDirection==1){SnakeStyle[Position%16][Position/16]=1;} //蛇头向右
if(NowDirection==2){SnakeStyle[Position%16][Position/16]=2;} //蛇头向上
if(NowDirection==3){SnakeStyle[Position%16][Position/16]=3;} //蛇头向左
if(NowDirection==4){SnakeStyle[Position%16][Position/16]=4;} //蛇头向下
}
else if(Type==1) //蛇的中间部分(除蛇头和蛇尾)
{ //用SnakeStyle数组(共128个字节)记录每个8*8区域应该显示什么信息
if( (LastDirection==4 && NowDirection==1) || (LastDirection==3 && NowDirection==2) )
{ //低四位(0~15)存储应该显示什么信息,高四位(范围:1~4)记录此位置蛇头经过时的方向,用来控制蛇尾的方向
SnakeStyle[Position%16][Position/16]+=5; //蛇身连接,右和上
} //这里只操作低四位,高四位存储蛇头在此位置的移动方向,高四位用来控制蛇尾的方向
if( (LastDirection==1 && NowDirection==1) || (LastDirection==3 && NowDirection==3) )
{
SnakeStyle[Position%16][Position/16]+=6; //蛇身连接,右和左
}
if( (LastDirection==2 && NowDirection==1) || (LastDirection==3 && NowDirection==4) )
{
SnakeStyle[Position%16][Position/16]+=7; //蛇身连接,右和下
}
if( (LastDirection==4 && NowDirection==3) || (LastDirection==1 && NowDirection==2) )
{
SnakeStyle[Position%16][Position/16]+=8; //蛇身连接,上和左
}
if( (LastDirection==2 && NowDirection==2) || (LastDirection==4 && NowDirection==4) )
{
SnakeStyle[Position%16][Position/16]+=9; //蛇身连接,上和下
}
if( (LastDirection==2 && NowDirection==3) || (LastDirection==1 && NowDirection==4) )
{
SnakeStyle[Position%16][Position/16]+=10; //蛇身连接,左和下
}
}
}
/**
* @brief 更新显示缓存数组DisplayBuffer的数据,并更新到屏幕的显示
* @param Position 需要更新显示的位置,字节高四位范围:0~15(对应1~16列),字节低四位范围:0~7(对应1~8行)
* @param State 需要更新成的状态,范围:0~1,0:每个8*8区域不显示,1:每个8*8区域显示相关图形(该显示什么,
* @param 由SnakeStyle和SnakeType的数据决定)
* @retval 无
*/
void UpdateDisplay(unsigned char Position,State)
{
//更新缓存数组数据
if(State==1){DisplayBuffer[Position%16][Position/16/8] |= (0x80>>Position/16%8);}
else{DisplayBuffer[Position%16][Position/16/8] &= ~(0x80>>Position/16%8);}
OLED_DrawBlock(Position/16,Position%16,State); //屏幕对应位置按State的值来更新显示
}
/**
* @brief 创造出随机位置的食物,数据的高四位(范围:0~15)代表食物所在的列(1~16),数据的低四位(范围:0~7)代表食物所在的行(1~8)
* @brief 从左往右数,分别是1~16列,从上往下数,分别是1~8行
* @param 无
* @retval 创造出的食物位置的数据
*/
unsigned char CreateFood(void)
{
unsigned char FoodTemp;
unsigned char i,j,m,n;
m=rand()%16; //产生一个0~15的随机数
n=rand()%8; //产生一个0~7的随机数
for(j=0;j<8;j++) //产生一个随机位置,判断该位置是否是蛇身,如果不是,就返回该位置所对应的数据
{ //如果该位置不是蛇身的位置,则从该点向周围寻找不是蛇身的空位置
for(i=0;i<16;i++)
{
if( ( DisplayBuffer[(n+j)%8][(m+i)%16/8] & (0x80>>(m+i)%8) ) == 0 )
{
FoodTemp=(m+i)%16*16+(n+j)%8;
break; //找到了空位置就退出循环
}
}
}
return FoodTemp; //返回食物位置的数据
}
/**
* @brief 控制蛇的移动
* @param 无
* @retval 无
*/
void MoveSnake(void)
{
if(NowDirection==1) //如果向右移动
{
if(SnakeBody[Head]/16==15){GameOverFlag=1;} //移动前判断一下移动后是否撞墙,如果是,则游戏结束,游戏结束的标志置1
//(Head+1)%128,取余的目的是为了防止越界,SnakeBody数组的索引范围是:0~127,SnakeBody的值为127后再加1,就越界了
else{SnakeBody[(Head+1)%128]=SnakeBody[Head]+16;} //SnakeBody数组中蛇头的下一个数据等于上一个数据加16(即高四位加1),即蛇头移动到了右边这一列
}
if(NowDirection==2) //如果向上移动
{
if(SnakeBody[Head]%16==0){GameOverFlag=1;}
else{SnakeBody[(Head+1)%128]=SnakeBody[Head]-1;} //SnakeBody数组中蛇头的下一个数据等于上一个数据减1(即低四位减1),即蛇头移动到了上边这一行
}
if(NowDirection==3) //如果向左移动
{
if(SnakeBody[Head]/16==0){GameOverFlag=1;}
else{SnakeBody[(Head+1)%128]=SnakeBody[Head]-16;} //SnakeBody数组中蛇头的下一个数据等于上一个数据减16(即高四位减1),即蛇头移动到了左边这一列
}
if(NowDirection==4) //如果向下移动
{
if(SnakeBody[Head]%16==7){GameOverFlag=1;}
else{SnakeBody[(Head+1)%128]=SnakeBody[Head]+1;} //SnakeBody数组中蛇头的下一个数据等于上一个数据加1(即低四位加1),即蛇头移动到了下边这一行
}
if(GameOverFlag==0) //如果没撞墙
{
switch(NowDirection) //用数组SnakeStyle中的数据的高四位保存蛇头在此位置的移动方向,用来控制蛇尾来到此位置的方向
{
case 1:SnakeStyle[SnakeBody[Head]%16][SnakeBody[Head]/16]=0x10;break;
case 2:SnakeStyle[SnakeBody[Head]%16][SnakeBody[Head]/16]=0x20;break;
case 3:SnakeStyle[SnakeBody[Head]%16][SnakeBody[Head]/16]=0x30;break;
case 4:SnakeStyle[SnakeBody[Head]%16][SnakeBody[Head]/16]=0x40;break;
default:break;
}
if(SnakeBody[(Head+1)%128]==Food) //判断蛇头移动后的位置是否是食物所在的位置
{ //如果是
Length++; //蛇身长度加1
UpdateAlteration(Food,0); //食物变成蛇头
UpdateDisplay(Food,1); //更新显示
UpdateAlteration(SnakeBody[Head],1); //原来的蛇头变成蛇身
UpdateDisplay(SnakeBody[Head],1); //更新显示
if(Length<128) //如果蛇身长度没有达到最大值128
{
Food=CreateFood(); //重新创造一个食物
SnakeStyle[Food%16][Food/16]=15; //设置在生成的食物的位置显示食物的形状
UpdateDisplay(Food,1); //更新显示
}
else //如果蛇身长度达到了最大值128
{
GameOverFlag=1; //游戏结束
}
FlashFlag=0; //创造出新的食物时,食物暂不闪烁
T0Count2=0; //定时器T0Count2重新计数
}
else if ( (SnakeBody[(Head+1)%128]!=SnakeBody[(Head+128-Length+1)%128]) &&
( DisplayBuffer[SnakeBody[(Head+1)%128]%8][SnakeBody[(Head+1)%128]/16/8] &
(0x80>>SnakeBody[(Head+1)%128]/16%8) ) )
{ //如果蛇头移动后的位置不是食物,且撞在蛇身上(且不是蛇尾),则游戏结束
GameOverFlag=1; //游戏结束的标志置1
}
else //如果蛇头移动后的位置不是食物,也不是撞墙,也不是撞到蛇身的话
{
//显示缓存数组DisplayBuffer中蛇头前进后的新位置对应的位写1
UpdateAlteration(SnakeBody[(Head+1)%128],0);
UpdateDisplay(SnakeBody[(Head+1)%128],1); //更新显示
//更新原来蛇头位置的显示,此时变成了蛇身
UpdateAlteration(SnakeBody[Head],1);
UpdateDisplay(SnakeBody[Head],1); //更新显示
//如果蛇即将移动的前面一个位置是蛇尾的话,则蛇头来到了原来蛇尾的位置,就不需要清空显示,否则就需要
//显示缓存数组DisplayBuffer中蛇尾的位置清空显示(蛇身移动,如果没有吃到食物,相当于蛇尾对应的点跑到了
//蛇头的前一个位置,变成了蛇头,原来的蛇头变成蛇身)整条蛇中间的数据不用操作
if(SnakeBody[(Head+1)%128]!=SnakeBody[(Head+128-Length+1)%128])
{
UpdateDisplay(SnakeBody[(Head+128-Length+1)%128],0); //更新显示
}
//蛇往前移动一格的话,原来的蛇尾清空显示,倒数第二格变成蛇尾
//取出高四位的值,用来确定蛇头当时在此位置的移动方向,以此确定蛇尾的方向
//按下面注释部分也可以,两种写法是等效的
SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]>>=4;
SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]+=10;
// switch(SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]/16)
// {
// case 1:SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]=11;break;
// case 2:SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]=12;break;
// case 3:SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]=13;break;
// case 4:SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]=14;break;
// default:break;
// }
// SnakeStyle[SnakeBody[(Head+128-Length+2)%128]%16][SnakeBody[(Head+128-Length+2)%128]/16]=11;
UpdateDisplay(SnakeBody[(Head+128-Length+2)%128],1); //移动前的倒数第二格变成蛇尾
//数组SnakeBody中,蛇尾的数据清零
// SnakeBody[(Head+128-Length+1)%128]=0; //其实可以不清零
}
}
Head++; //SnakeBody数组中,蛇头对应的数据的索引加1
Head%=128; //蛇头变量Head的范围是0~127(蛇最长是128个8*8的区域)
}
void main()
{
unsigned char i;
OLED_Init(); //OLED初始化
Timer0_Init(); //定时器初始化
ChangedFlag=1; //上电显示难度对应的数字
while(1)
{
KeyNum=Key(); //获取键码值
if(KeyNum) //如果有按键按下
{
srand(TL0); //以定时器0的低八位数据作为随机数的种子,用来产生真随机的数据
if(Mode==4) //如果是显示作者和编程时间的界面
{
if(KeyNum==18) //如果按下了返回键K2(松手瞬间)
{
Mode=0;
ExecuteOnceFlag=1;
ChangedFlag=1; //返回主界面时显示难度对应的数字
}
}
if(Mode==3) //如果是显示得分和游戏用时界面
{
if(KeyNum==18) //如果按下了返回键K2(松手瞬间)
{
Mode=0; //返回主界面
ExecuteOnceFlag=1;
ChangedFlag=1; //返回主界面时显示难度对应的数字
}
if(KeyNum==19) //如果按下了K3(松手瞬间)
{
Mode=4; //显示作者和编程时间
ExecuteOnceFlag=1;
}
}
if(Mode==2) //如果是游戏结束全屏闪烁模式
{
if(KeyNum==17) //如果按下了开始键K1(松手瞬间)
{
Mode=3; //切换到显示得分和游戏用时界面
ExecuteOnceFlag=1;
}
}
if(Mode==1) //如果是游戏进行模式
{
if(KeyNum==17) //按下K1暂停或继续(松手瞬间)
{
PauseFlag=!PauseFlag;
}
if(PauseFlag==0) //如果不是暂停状态
{ //按下瞬间、长按、松手瞬间都进行检测,这样控制方向更有效,防止按键没检测出来导致没能改变方向
if((KeyNum==8 || KeyNum==16 || KeyNum==24) && LastDirection!=1)
{ //如果按了"左"键,且蛇头原来的移动方向不是向右
NowDirection=3; //则方向蛇头方向改为向左
}
if((KeyNum==7 || KeyNum==15 || KeyNum==23) && LastDirection!=4)
{ //如果按了"上"键,且蛇头原来的移动方向不是向下
NowDirection=2; //则方向蛇头方向改为向上
}
if((KeyNum==6 || KeyNum==14 || KeyNum==22) && LastDirection!=2)
{ //如果按了"下"键,且蛇头原来的移动方向不是向上
NowDirection=4; //则方向蛇头方向改为向左
}
if((KeyNum==5 || KeyNum==13 || KeyNum==21) && LastDirection!=3)
{ //如果按了"右"键,且蛇头原来的移动方向不是向左
NowDirection=1; //则方向蛇头方向改为向左
}
}
}
if(Mode==0) //如果是主界面
{
if(KeyNum==23) //如果按了"上"键(松手瞬间)
{
Difficulty++;
if(Difficulty>5){Difficulty=1;}
ChangedFlag=1; //说明更改了难度,更新显示
}
if(KeyNum==22) //如果按了"下"键(松手瞬间)
{
Difficulty--;
if(Difficulty<1){Difficulty=5;}
ChangedFlag=1; //说明更改了难度,更新显示
}
if(KeyNum==17) //如果按下了开始键K1(松手瞬间)
{
Mode=1; //切换到游戏进行模式
ExecuteOnceFlag=1;
}
}
}
if(Mode==0) //如果是主界面,即显示游戏名称"《贪吃蛇》"和选择游戏难度的界面
{
if(ExecuteOnceFlag) //进入到该模式(切换为其他模式之前)后,此if中的内容只执行1次
{
ExecuteOnceFlag=0;
OLED_Clear();
OLED_ShowImage_16x16(1,2,Table1,6); //" 《贪吃蛇》 "
OLED_ShowString(2,6,"SNAKE"); //显示"贪吃蛇"的英文名
OLED_ShowImage_16x16(3,2,Table2,5); //"难度选择:"
OLED_ShowString(4,4,"DIFFICULTY"); //显示"难度"的英文名
}
if(ChangedFlag) //如果难度改变了,则更新显示,下次改变难度前,此if的内容不执行
{
ChangedFlag=0;
OLED_ShowImage_16x16(3,7,Table3+32*(Difficulty-1),1);
switch(Difficulty) //更改蛇移动的时间间隔,时间间隔越小,难度越大
{
case 1:SnakeMoveSpeed=100;break; //1s移动一次
case 2:SnakeMoveSpeed=75;break; //0.75s
case 3:SnakeMoveSpeed=50;break; //0.5s
case 4:SnakeMoveSpeed=25;break; //0.25s
case 5:SnakeMoveSpeed=12;break; //0.12s
default:break;
}
}
}
if(Mode==1) //如果是游戏进行模式
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0;
OLED_Clear(); //清屏
GameTime=0; //游戏时间清零
GameOverFlag=0; //游戏结束标志清零
PauseFlag=0; //游戏暂停标志清零
NowDirection=1; //蛇头默认向右移动
LastDirection=1; //上一次蛇头默认向右移动
Length=2; //蛇的初始长度为2
Head=1; //蛇头对应数组中的第2个数据(索引为1)
for(i=0;i<8;i++) //显示缓存数据全部清零
{
DisplayBuffer[i][0]=0;
DisplayBuffer[i][1]=0;
}
// for(i=0;i<128;i++) //蛇身数据全部清零
// {
// SnakeBody[i]=0; //其实可以不清零
// }
SnakeBody[0]=1*16+1; //蛇身数据全部清零后,写入蛇尾所在位置,二行二列
SnakeBody[1]=2*16+1; //蛇身数据全部清零后,写入蛇头所在位置,二行三列
//将LCD12864分成了128个区域,每个区域是8*8像素
SnakeStyle[1][1]=11; //用数组SnakeStyle保存二行二列要显示的内容(蛇尾)
SnakeStyle[1][2]=1; //用数组SnakeStyle保存二行三列要显示的内容(蛇头)
UpdateDisplay(SnakeBody[0],1); //更新显示
UpdateDisplay(SnakeBody[1],1); //更新显示
Food=CreateFood(); //进入游戏前,先创造出一个食物
SnakeStyle[Food%16][Food/16]=15; //15:变量Food对应的位置显示食物的形状
UpdateDisplay(Food,1); //更新显示
MoveSnakeFlag=0; //蛇移动的标志清零
T0Count1=0; //定时器计数变量T0Count1清零,重新计数
}
if(PauseFlag) //如果暂停了
{
UpdateDisplay(Food,1); //食物不闪烁,一直显示
}
else if(FlashFlag) //如果不暂停,且闪烁标志为1
{
UpdateDisplay(Food,0); //不显示食物
}
else //如果不暂停,且闪烁标志为0
{
UpdateDisplay(Food,1); //显示食物
}
if(MoveSnakeFlag && GameOverFlag==0 && PauseFlag==0)
{ //如果移动的标志为1,且不暂停,且游戏也没结束
MoveSnakeFlag=0; //移动标志清零
MoveSnake(); //移动一次,需要移动后再将NowDirection的值赋给LastDirection
//因为需要在MoveSnake函数中根据这两个变量确定蛇身的连接方向
LastDirection=NowDirection; //保存上一次移动的方向,用于按键的判断(要确保蛇不能往后移动)
}
if(GameOverFlag==1) //如果游戏结束
{
UpdateDisplay(Food,1); //显示食物
Mode=2; //切换到全屏闪烁模式
ExecuteOnceFlag=1;
}
}
if(Mode==2) //游戏结束全屏闪烁模式
{
if(FlashFlag){OLED_WriteCommand(0xAE);} //关闭显示
else{OLED_WriteCommand(0xAF);} //开启显示
}
if(Mode==3) //显示游戏得分
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0;
OLED_WriteCommand(0xAF); //防止关闭显示的时候切换到此模式,导致不能显示
OLED_Clear(); //清屏
OLED_ShowImage_16x16(1,1,Table4,6); //"您的得分是:"
OLED_ShowNum(2,1,Length,3); //三位数得分
OLED_ShowImage_16x16(3,1,Table5,5); //"游戏用时:"
OLED_ShowImage_16x16(4,2,Table6,1); //"分"
OLED_ShowImage_16x16(4,4,Table6+32,1); //"秒"
OLED_ShowNum(4,1,GameTime/60%60,2); //显示分
OLED_ShowNum(4,5,GameTime%60,2); //显示秒
}
}
if(Mode==4) //显示作者和编程日期
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0;
OLED_Clear(); //清屏
OLED_ShowString(2,1,"by"); //"by"
OLED_ShowImage_16x16(2,3,Table7,3); //"甘腾胜"
OLED_ShowString(3,1,"at 20241217"); //"at 20241217"
}
}
}
}
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
TL0=0xF0; //设置定时初值,定时10ms,晶振@12.0000MHz
TH0=0xD8; //设置定时初值,定时10ms,晶振@12.0000MHz
T0Count0++;
if(PauseFlag==0) //不暂停时,T0Count1和T0Count2才计数
{
T0Count1++;
T0Count2++;
}
if(T0Count0>=2) //每隔20ms检测按键状态
{
T0Count0=0;
Key_Tick();
}
if(T0Count1>=SnakeMoveSpeed) //用来控制蛇移动的速度
{
T0Count1=0;
MoveSnakeFlag=1;
}
if(T0Count2>=50) //每隔0.5s改变闪烁标志FlashFlag的值
{
T0Count2=0;
FlashFlag=!FlashFlag;
//每隔1s,GameTime加1
if(PauseFlag==0 && GameOverFlag==0 && Mode==1 && FlashFlag==0){GameTime++;}
}
}
总结
以《贪吃蛇》为载体,玩转各种屏幕。