基于51单片机和OLED12864的小游戏《贪吃蛇》

目录

系列文章目录


前言

《贪吃蛇》,一款经典的、怀旧的小游戏,单片机入门必写程序。

之前写了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++;}
	}
}

总结

以《贪吃蛇》为载体,玩转各种屏幕。

相关推荐
LXL_2426 分钟前
模拟——郑益慧_笔记1_绪论
嵌入式硬件
weixin_452600696 小时前
串行时钟保持芯片D1380/D1381,低功耗工作方式自带秒、分、时、日、日期、月、年的串行时钟保持芯片,每个月多少天以及闰年能自动调节
科技·单片机·嵌入式硬件·时钟·白色家电电源·微机串行时钟
森旺电子9 小时前
51单片机仿真摇号抽奖机源程序 12864液晶显示
单片机·嵌入式硬件·51单片机
不过四级不改名67711 小时前
蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)
stm32·嵌入式硬件·蓝桥杯
小A15912 小时前
STM32完全学习——SPI接口的FLASH(DMA模式)
stm32·嵌入式硬件·学习
Rorsion12 小时前
各种电机原理介绍
单片机·嵌入式硬件
善 .15 小时前
单片机的内存是指RAM还是ROM
单片机·嵌入式硬件
超级码农ProMax15 小时前
STM32——“SPI Flash”
stm32·单片机·嵌入式硬件
Asa31915 小时前
stm32点灯Hal库
stm32·单片机·嵌入式硬件
end_SJ17 小时前
初学stm32 --- 外部中断
stm32·单片机·嵌入式硬件