1) 设计一款在以51单片机为核心的硬件系统中运行的推箱子游戏。游戏一共4关,功能包括:显示游戏开始界面、游戏界面、游戏结束界面;
2) 按键处理包括移动小人和移动箱子,通过移动上下左右键来控制小人的移动,通过小人推动箱子,把箱子推到指定的目的地为过关,箱子只能推不能拉;
3) 具有蓝牙控制功能
4) 据有时间计时,显示玩家的总游戏耗时。
。
(1) 需要具有51单片机、蓝牙模块、12864LCD液晶模块
(2) 需要了解LCD显示图片和文字的原理和设计思路。
(3) 需要了解数码管显示的接线原理和设计思路。
(4) 需要了解定时器模块的原理和设计思路。
(5) 需要了解蓝牙模块的原理和设计思路。
1. LCD12864的引脚结构:
图1 LCD12864引脚结构图
(1) LCD12864显示驱动程序:
图2 LCD12864显示驱动程序
(2) LCD12864指令集:
在使用LCD前必须对其进行初始化操作,初始化的内容包括光标指针设置,图形区首地址,图形区宽度,显示方式设置,图形方式显示,标形状设置。
(1) LCD12864连线图:
LCD12864内置连接实验电路如图3所示。(我的51单片机所需LCD12864直接插入插口即可)
图3 LCD12864内置连接图
(2) 动态数码管连接图:
动态数码管连接实验原理路如图4所示。
图4 动态数码管接连线原理图
(3) 蓝牙模块原理图
蓝牙模块连接实验原理路如图5所示。(其中RXD\TXD在烧入程序时需要先互换连接)
图5 蓝牙模块管接连线原理图
(4) 独立按键原理图:
独立按键连接实验原理路如图6所示。
图6 独立按键接连线原理图
(5) 实际连线图:
图6 实际连线图
1) 游戏元素设计:
对于出现在显示画面中的游戏元素总共有6种:墙壁,箱子,小人,目的地,空白,箱子在目的地。每个元素在液晶上以一个字符显示,即8×8的点阵。在字模提取软件设计各个元素,并获得元素的显示码。显示码的每8位构成字符的一行,8就行有8个字节。其中1表示该点显示,0表示该点不显示。
小人 墙壁 箱子
目标 箱子到达目标 空白
我通过定义一个二维数组elements[][8],存储这些点阵信息。其中第一个地址为元素代号。
unsigned char codeelements[][8]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //空白
0x20,0x96,0xDF,0x7B,0x7B,0xDF,0x96,0x20, //人物
0x70,0x77,0x77,0x07,0x77,0x77,0x77,0x77, //砖头
0xFF,0xC3,0xA5,0x99,0x99,0xA5,0xC3,0xFF, //箱子
0x00,0x18,0x24,0x4A,0x52,0x24,0x18,0x00, //目标
0xFF,0xBD,0xDB,0xE7,0xE7,0xDB,0xBD,0xFF, //箱子到达目标
};
2) 元素的显示
由于LCD12864是按以列1字符(8点),行1点来定位的,所以显示时,要先定义一个for循环将i设置为0到7,来定位显示字符位的列字符和行字符首地址,即行=0xb0+i,列从0x10,0x00开始;显示一行数据时,需要定义两层for循环,首循环先确定显示元素内循环确定对应元素所要生成的8个字符,即elements[元素代号][k],就可以在液晶的指定字符位置上显示该元素了。
3) 游戏关卡设计
游戏每关都是在8×8个字符组成的区域中进行的,只要在每一个字符显示相应的元素,就可以组成游戏界面。定义一个三维数组elements1[game][8][8],存储每一关每一个字符位上要显示的元素,其中game表示关卡。
4) 游戏流程
游戏中,程序循环执行if语句判断按键是否按下或者是否有对应蓝牙输入,并做出相应判断。整个程序中,最关键的部分是要根据玩家的输入,控制小人、箱子的移动。
这里由于上下左右移动原理类似我只介绍向上移动人物的原理:
a) 在向上移动人物的函数中我首先通过扫描函数确定人物所在位置(x,y)。
b) 在确定人物位置后,我定义两个循环来确定临时存储元素的三维数elements2中人物上方位置(x+1,y)所在元素是什么。当上方所在元素是空白时,将人物所在位置元素设置为空白,(x+1,y)所在位置设置为人物。类似如此,当人物上方时砖头时不做任何改变,当人物上方位置为箱子时要考虑人和箱子一起移动的情况:这时需要进入内置if语句来判断箱子上方物体是什么,由于简便程序这里未考虑有两个箱子、目标的情况。
c) 判断完毕并改变完元素位置后若仍无到达目标则调用changement()函数来显示新的元素图像。
d) 当本次移动出现达到目标的情况时需要将game关卡数加一,由于一共设计了4个关卡,当game不等于4时会通过调用printelement()函数直接显示下一关的地图。
e) 我需要知道整个图像中人所在的位置,我通过定义一个二维数组,用于保存当前游戏状态每个字符位的元素,当玩家按键输入时,改变该数组中相应得数据,再将此二维数组每个数据代码的元素显示出来,就可以出现小人,箱子移动的效果。
每关开始,进行游戏初始化。将关卡数据写入上述二维数组中,并记入小人的位置(man_x,man_y)。
程序只要根据小人移动方向周围的元素信息,结合游戏规则,只要判断能够移动箱子、小人的情况,才显示出来,而不用对不能移动的情况进行判定,这大大简化了程序的编写。
游戏流程框图如下(以向上移动为例,其他方向类似):
图7向上移动流程图
5) 计时模块设计
我通过定时器0中断来计时,
a) 设计定时器0中断初始化函数Timer0Init(),设置IT1=1为下降沿触发;设置EX1=1打开INT1的中断允许;设置TMOD|=0X01来选择为定时器0模式,工作方式1,仅用TR0打开启动;设置TH0=0Xd8,TL0=0Xf0来给定时器赋初值,定时10ms;设置ET0=1打开定时器0中断允许;设置EA=1打开总中断。
b) 设置定时器0中断函数,先为定时器写入初值10ms;将ssec++记录已经过10ms当ssec等于100时将sec++记录过了1秒,由于为了简便程序,总计时只设置了99s。
6) 蓝牙模块设计
a) 设计串口中断函数InitUART():初始化串口波特率9600,打开外部中断
b) 设计中断实现函数:通过RI确定是否有输入,当有输入时先将RI清0,并用m2记录输入数据。(需要注意程序下载进51单片机时蓝牙模块连线为RXD接P3.0(内置RXD),TXD接P3.0(内置TXD),当实际通过蓝牙写入数据时,需要将RXD\TXD互换连接)
7) 主程序初始化
a) 调用Lcd12864_Init()函数将LCD软件初始化:调用reset命令、设置左右方向为正常方向、设置上下方向为反向、设置字体为黑色北京为白色、设置像素为正常显示、设置偏压为1/7、设置增压、设置背光对比度等。
b) 调用Lcd12864_ClearScreen()函数清除屏幕。
c) 调用蓝牙模块初始化函数
d) 调用定时器中断初始化函数
e) 设计while(1)循环等待按键、蓝牙输入或等待中断。
图8 主函数程序流程图
函数名: |
函数作用: |
delay1(u16 p) |
延时函数1,用于短延时 |
delay(u16 t) |
延时函数2,用于t ms延时 |
printelement() |
用于LCD生成图片,其内容为先通过elements1数组选择元素,在通过elements数组生成对应元素的每个字符位数据 |
changement()
|
用于LCD生成改变后图片,其内容为先通过elements2数组选择元素,在通过elements数组生成对应元素的每个字符位数据 |
InitUART() |
蓝牙模块串口初始化 |
Timer0Init() |
定时器模块初始化设计 |
DigDisplay() |
位选显示数码管数字 |
datapros0() |
数码管初始内容为0 |
datapros1() |
设计数码管内容为计时时间 |
scan_x() |
遍历所选关卡的元素找到人物所在位置并返回行数 |
scan_y() |
遍历所选关卡的元素找到人物所在位置并返回列数 |
key_up() |
调整人物位置向上移动 |
key_down() |
调整人物位置向下移动 |
key_left() |
调整人物位置向左移动 |
key_right() |
调整人物位置向右移动LcdSt7565_WriteCmd |
LcdSt7565_WriteCmd |
打开LCD模块写入对应命令 |
LcdSt7565_WriteData |
打开LCD模块写入对应数据 |
Lcd12864_Init() |
LCD模块初始化设计 |
Lcd12864_ClearScreen |
LCD模块清屏Lcd12864_Write16CnCHAR |
Lcd12864_Write16CnCHAR |
LCD模块显示设置的汉字 |
(1) st7565.c
#include"st7565.h"
//函数名: LCD12864_WriteCmd函数功能:写入一个命令到12864
void LcdSt7565_WriteCmd(cmd)
{
LCD12864_CS = 0; //chip select,打开片选
LCD12864_RD = 1; //disable read,读失能
LCD12864_RS = 0; //select command,选择命令
LCD12864_RW = 0; //select write,选择写模式
_nop_();
_nop_();
DATA_PORT = cmd; //put command,放置命令
_nop_();
_nop_();
LCD12864_RW = 1; //command writing ,写入命令
}
// 函 数 名: LcdSt7565_WriteData 函数功能 : 写入一个数据到12864
void LcdSt7565_WriteData(dat)
{
LCD12864_CS = 0; //chip select,打开片选
LCD12864_RD = 1; //disable read,读失能
LCD12864_RS = 1; //select data,选择数据
LCD12864_RW = 0; //select write,选择写模式
_nop_();
_nop_();
DATA_PORT = dat; //put data,放置数据
_nop_();
_nop_();
LCD12864_RW = 1; //data writing,写数据
}
// 函 数 名: LCD12864_Init 函数功能 初始化12864
void Lcd12864_Init()
{
uchar i;
LCD12864_RSET = 0;
for (i=0; i<100; i++);
LCD12864_CS = 0;
LCD12864_RSET = 1;
//--软件初始化--//
LcdSt7565_WriteCmd(0xE2); //reset
for (i=0; i<100; i++); //延时一下
//0xA0段(左右)方向选择正常方向(0xA1为反方向)--//
LcdSt7565_WriteCmd(0xA0); //ADC select segment direction
//0xC8普通(上下)方向选择选择反向,0xC0为正常方向--//
LcdSt7565_WriteCmd(0xC8); //Common direction
//0xA6为设置字体为黑色,背景为白色---//
//--0xA7为设置字体为白色,背景为黑色---//
LcdSt7565_WriteCmd(0xA6); //reverse display
//0xA4像素正常显示,0xA5像素全开--//
LcdSt7565_WriteCmd(0xA4); //normal display
//0xA3偏压为1/7,0xA2偏压为1/9--//
LcdSt7565_WriteCmd(0xA2); //bias set 1/9
//-这个是个双字节的命令,0xF800选择增压为4X;--//
//--0xF801,选择增压为5X,其实效果差不多--//
LcdSt7565_WriteCmd(0xF8); //Boost ratio set
LcdSt7565_WriteCmd(0x01); //x4
//-这个是个双字节命令,高字节为0X81,低字节可以--//
//--选择从0x00到0X3F。用来设置背景光对比度。---/
LcdSt7565_WriteCmd(0x81); //V0 a set
LcdSt7565_WriteCmd(0x23);
//选择调节电阻率--//
LcdSt7565_WriteCmd(0x25); //Ra/Rb set
//电源设置。--//
LcdSt7565_WriteCmd(0x2F);
for (i=0; i<100; i++);
//设置显示开始位置--//
LcdSt7565_WriteCmd(0x40); //start line
//开启显示--//
LcdSt7565_WriteCmd(0xAF); // display on
for (i=0; i<100; i++);
}
// 函 数 名: LCD12864_ClearScreen 函数功能: 清屏12864
void Lcd12864_ClearScreen(void)
{
uchar i, j;
for(i=0; i<8; i++)
{
//--设置Y的坐标--//
//--Y轴有64个,一个坐标8位,也就是有8个坐标--//
//所以一般我们使用的也就是从0xB0到0x07,就够了--//
LcdSt7565_WriteCmd(0xB0+i);
//--设置X坐标--//
//--当你的段初始化为0xA1时,X坐标从0x10,0x04到0x18,0x04,一共128位--//
//--当你的段初始化为0xA0时,X坐标从0x10,0x00到0x18,0x00,一共128位--//
//--在写入数据之后X坐标的坐标是会自动加1的,我们初始化使用0xA0所以--//
//--我们的X坐标从0x10,0x00开始---//
LcdSt7565_WriteCmd(0x10);
LcdSt7565_WriteCmd(0x00);
//--X轴有128位,就一共刷128次,X坐标会自动加1,所以我们不用再设置坐标
for(j=0; j<128; j++)
{
LcdSt7565_WriteData(0x00); //如果设置背景为白色时,清屏选择0XFF
}
}
}
//函 数 名: LCD12864_Write16CnCHAR 函数功能: 在12864上面书写16X16的汉字
#ifdef CHAR_CODE
#include"charcode.h"
uchar Lcd12864_Write16CnCHAR(uchar x, uchar y, uchar *cn)
{
uchar j, x1, x2, wordNum;
//--Y的坐标只能从0到7,大于则直接返回--//
if(y > 7)
{
return 0;
}
//--X的坐标只能从0到128,大于则直接返回--//
if(x > 128)
{
return 0;
}
y += 0xB0; //求取Y坐标的值
//--设置Y坐标--//
LcdSt7565_WriteCmd(y);
while ( *cn != '\0') //在C语言中字符串结束以‘\0’结尾
{
//--设置Y坐标--//
LcdSt7565_WriteCmd(y);
x1 = (x >> 4) & 0x0F; //由于X坐标要两句命令,分高低4位,所以这里先取出高4位
x2 = x & 0x0F; //去低四位
//--设置X坐标--//
LcdSt7565_WriteCmd(0x10 + x1); //高4位
LcdSt7565_WriteCmd(0x00 + x2); //低4位
for (wordNum=0; wordNum<50; wordNum++)
{
//--查询要写的字在字库中的位置--//
if ((CN16CHAR[wordNum].Index[0] == *cn)
&&(CN16CHAR[wordNum].Index[1] == *(cn+1)))
{
for (j=0; j<32; j++) //写一个字
{
if (j == 16) //由于16X16用到两个Y坐标,当大于等于16时,切换坐标
{
//--设置Y坐标--//
LcdSt7565_WriteCmd(y + 1);
//--设置X坐标--//
LcdSt7565_WriteCmd(0x10 + x1); //高4位
LcdSt7565_WriteCmd(0x00 + x2); //低4位
}
LcdSt7565_WriteData(CN16CHAR[wordNum].Msk[j]);
}
x += 16;
}//if查到字结束
} //for查字结束
cn += 2;
} //while结束
return 1;
}
#endif
(2) main.c
#include
#include"st7565.h"
#include"beep.h"
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
sbit k1=P1^0; //定义按键K1按键
sbit k2=P1^1; //定义按键K2按键
sbit k3=P1^2; //定义按键K3按键
sbit k4=P1^3; //定义按键K4按键
sbit k5=P1^4; //定义按键K5按键
u8 x=9; //记录人的位置
u8 y=9;
u8 game=0;
u8 start=0;
u8 i1,j1,n1;
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
u8 ssec,sec,min;
u8 DisplayData[2];
u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~F的值
u8 IrValue[6]; /*红外所需数据*/
sbit IRIN=P3^2;
u8 Time;
u8 m2;
sbit state = P3^6; //蓝牙连通的状态标志位
u8 r_buf='6'; //蓝牙接收到的字符存储缓冲区
unsigned char code elements[][8]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //空白
0x20,0x96,0xDF,0x7B,0x7B,0xDF,0x96,0x20, //人物
0x70,0x77,0x77,0x07,0x77,0x77,0x77,0x77, //砖头
0xFF,0xC3,0xA5,0x99,0x99,0xA5,0xC3,0xFF, //箱子
0x00,0x18,0x24,0x4A,0x52,0x24,0x18,0x00, //目标
0xFF,0xBD,0xDB,0xE7,0xE7,0xDB,0xBD,0xFF, //箱子到达目标
};
u8 code elements1[][8][8]=
{
0,0,2,2,2,2,2,0,
2,2,2,0,0,0,2,0,
2,0,3,0,2,0,2,2,
2,0,2,0,0,4,0,2,
2,0,0,0,0,2,0,2,
2,2,0,2,0,0,0,2,
0,2,1,0,0,2,2,2,
0,2,2,2,2,2,0,0,
2,2,2,2,2,2,2,2, //第2关
2,0,0,0,0,0,1,2,
2,0,0,0,5,0,0,2,
2,0,0,2,2,2,2,2,
2,2,0,0,2,0,0,0,
2,0,3,0,2,0,0,0,
2,0,0,4,2,0,0,0,
2,2,2,2,2,0,0,0,
0,0,0,2,2,2,2,0, /*第3关*/
0,2,2,2,0,1,2,0,
0,2,0,0,3,0,2,0,
2,2,0,2,0,2,2,2,
2,0,0,2,0,2,4,2,
2,0,2,0,0,3,4,2,
2,0,0,0,0,0,0,2,
2,2,2,2,2,2,2,2,
};
u8 xdata elements2[][8][8]= //xdata 表示外部ram
{
0,0,2,2,2,2,2,0, //第1关
2,2,2,0,0,0,2,0,
2,0,3,0,2,0,2,2,
2,0,2,0,0,4,0,2,
2,0,0,0,0,2,0,2,
2,2,0,2,0,0,0,2,
0,2,1,0,0,2,2,2,
0,2,2,2,2,2,0,0,
2,2,2,2,2,2,2,2, //*第2关
2,0,0,0,0,0,1,2,
2,0,0,0,5,0,0,2,
2,0,0,2,2,2,2,2,
2,2,0,0,2,0,0,0,
2,0,3,0,2,0,0,0,
2,0,0,4,2,0,0,0,
2,2,2,2,2,0,0,0,
0,0,0,2,2,2,2,0, /*第3关*/
0,2,2,2,0,1,2,0,
0,2,0,0,3,0,2,0,
2,2,0,2,0,2,2,2,
2,0,0,2,0,2,4,2,
2,0,2,0,0,0,0,2,
2,0,0,0,0,0,0,2,
2,2,2,2,2,2,2,2,
0,0,2,2,2,2,2,0, /*第4关*/
2,2,2,0,0,0,2,0,
2,0,0,0,2,0,2,2,
2,0,2,0,0,4,0,2,
2,0,0,0,0,2,0,2,
2,2,3,2,0,0,0,2,
0,2,1,0,0,2,2,2,
0,2,2,2,2,2,0,0,
};
void delay1(u16 p)
{
while(p--);
}
void delay(u16 t)
{
u8 i;
while(t--)
{
for(i=1330;i>0;i--){}
}
}
void printelement()//生成地图
{
unsigned char i,j,k;
uint n=1;
for(i=0;i<8;i++) //行地址加+1
{
LcdSt7565_WriteCmd(0x40);
//--设置Y坐标--//
LcdSt7565_WriteCmd((0xb0+i));
//--设置X坐标--//
LcdSt7565_WriteCmd(0x10);
LcdSt7565_WriteCmd(0x04);
for(j=0; j<8; j++)
{
for(k=0; k<8; k++)
{
LcdSt7565_WriteData(elements[elements1[game][i][j]][k]);
}
}
}
}
void change()
{
switch(IrValue[2])
{
case(0x46):
m2=2; break; //上
case(0x0C): //开始
m2=1; break;
case(0x18):
m2=3; break; //下
case(0x5E):
m2=4; break; //左
case(0x08):
m2=5; break; //右
default: m2=99; break; //别的键按下返回9
}
}
void changement()//改变地图上的元素
{
u8 i,j,k;
//重新加载
Lcd12864_ClearScreen(); //清屏
for(i=0;i<8;i++) //行地址加+1
{
LcdSt7565_WriteCmd(0x40);
//--设置Y坐标--//
LcdSt7565_WriteCmd((0xb0+i));
//--设置X坐标--//
LcdSt7565_WriteCmd(0x10);
LcdSt7565_WriteCmd(0x04);
for(j=0; j<8; j++)
{
for(k=0; k<8; k++)
{
LcdSt7565_WriteData(elements[elements2[game][i][j]][k]);
}
}
}
}
void InitUART() //串口初始化9600
{
//初始化串口定时器9600
TMOD = 0x20;
PCON = 0x00;
SCON = 0x50;
TH1 = 0xFD;
TL1 = 0xFD;
TR1 = 1;
ES = 1;
EA = 1;
}
void Timer0Init()
{
IT1=1;//跳变沿出发方式(下降沿)
EX1=1;//打开INT1的中断允许。
TMOD|=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0Xd8; //给定时器赋初值,定时10ms
TL0=0Xf0;
ET0=1;//打开定时器0中断允许
EA=1;//打开总中断
}
void DigDisplay()
{
u8 i;
for(i=0;i<2;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
}
P0=DisplayData[i];//发送段码
delay1(50); //间隔一段时间扫描
P0=0x00;//消隐
}
}
void IrInit()
{
IT0=1;//下降沿触发
EX0=1;//打开中断0允许
EA=1; //打开总中断
IRIN=1;//初始化端口
}
void datapros0()
{
DisplayData[1]=smgduan[sec]; //从左边数第二位
DisplayData[0]=smgduan[sec/10];
}
void datapros1()
{
DisplayData[1]=smgduan[0];
DisplayData[0]=smgduan[0];
}
u8 scan_x()
{
u8 i, j,k;
k=0;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(elements2[game][i][j]==1){ k=1;return i;}
}
}
if(k==0){ return x;} //特殊情况 人在目标上未显示
}
u8 scan_y()
{
u8 i, j,k;
k=0;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(elements2[game][i][j]==1) {k=1;return j; }
}
}
if(k==0){ return y;} //特殊情况 人在目标上未显示
}
void key_up()
{
u8 i, j,xh,yh;
x= scan_x();
y= scan_y();
xh=x;
yh=y;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(i==xh-1&j==yh) //找到人上一个的位置
{
if(elements2[game][i][j]==0) //人上一个为空白
{
elements2[game][i][j]=1 ; //找到改变后人的位置
if(elements2[game][i+1][j]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i+1][j]=0;
}
}
else if(elements2[game][i][j]==2){} //人上一个为墙壁
else if(elements2[game][i][j]==3) //人上一个为箱子
{
if(elements2[game][i-1][j]==0) //箱子的上面为空白
{
elements2[game][i-1][j]=3;
elements2[game][i][j]=1;
elements2[game][i+1][j]=0;
}
else if(elements2[game][i-1][j]==2|elements2[0][i-1][j]==3){} //箱子的上面为墙壁、箱子
else if(elements2[game][i-1][j]==4) // 箱子的上面为目标 只考虑一个目标时 游戏结束
{
elements2[game][i-1][j]=5;
elements2[game][i][j]=1;
if(elements2[game][i-1][j]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i-1][j]=0;
}
game=game+1; //进入下一关
if(game!=4)
{
//display() //第一关结束进入第二关
delay(3000);
printelement();//显示下一关
}
}
}
else if(elements2[game][i][j]==4) //人上一个为目标 目标不变 人变空白 此时记录(人)目标所在位置
{
elements2[game][i+1][j]=0;
x=i;y=j ;
}
}
}
}
changement();
}
void key_down()
{
u8 i, j,xh,yh;
x= scan_x();
y= scan_y();
xh=x;
yh=y;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(i==xh+1&j==yh) //找到人下一个的位置
{
if(elements2[game][i][j]==0) //人下一个为空白
{
elements2[game][i][j]=1 ; //找到改变后人的位置
if(elements2[game][i-1][j]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i-1][j]=0;
}
}
else if(elements2[game][i][j]==2){} //人下一个为墙壁
else if(elements2[game][i][j]==3) //人下一个为箱子
{
if(elements2[game][i+1][j]==0) //箱子的下面为空白
{
elements2[game][i+1][j]=3;
elements2[game][i][j]=1;
if(elements2[game][i-1][j]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i-1][j]=0;
}
}
else if(elements2[game][i+1][j]==2|elements2[0][i+1][j]==3){} //箱子的下面为墙壁、箱子
else if(elements2[game][i+1][j]==4) // 箱子的下面为目标 只考虑一个目标时 游戏结束
{
elements2[game][i+1][j]=5;
elements2[game][i][j]=1;
if(elements2[game][i-1][j]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i-1][j]=0;
}
game=game+1; //进入下一关
if(game!=4)
{
//*display() 第一关结束进入第二关
delay(3000);
printelement();//显示下一关
}
}
}
else if(elements2[game][i][j]==4) //人下一个为目标 目标不变 人变空白 此时记录(人)目标所在位置
{
elements2[game][i-1][j]=0;
x=i;y=j ;
}
}
}
}
changement();
}
void key_left()
{
u8 i, j,xh,yh;
x= scan_x();
y= scan_y();
//u8 i, j,k;
//调整地图位置
// u8 xh,yh;
xh=x;
yh=y;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(i==xh&j==yh-1) //找到人左一个的位置
{
if(elements2[game][i][j]==0) //人左一个为空白
{
elements2[game][i][j]=1 ; //找到改变后人的位置
if(elements2[game][i][j+1]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i][j+1]=0;
}
}
else if(elements2[game][i][j]==2){} //人左一个为墙壁
else if(elements2[game][i][j]==3) //人左一个为箱子
{
if(elements2[game][i][j-1]==0) //箱子的左面为空白
{
elements2[game][i][j-1]=3;
elements2[game][i][j]=1;
if(elements2[game][i][j+1]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i][j+1]=0;
}
}
else if(elements2[game][i][j-1]==2|elements2[0][i][j-1]==3){} //箱子的左面为墙壁、箱子
else if(elements2[game][i][j-1]==4) // 箱子的左面为目标 只考虑一个目标时 游戏结束
{
elements2[game][i][j-1]=5;
elements2[game][i][j]=1;
if(elements2[game][i][j+1]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i][j+1]=0;
}
game=game+1; //进入下一关
if(game!=4)
{
//*display() 第一关结束进入第二关
delay(3000);
printelement();//显示下一关
}
}
}
else if(elements2[game][i][j]==4) //人左一个为目标 目标不变 人变空白 此时记录(人)目标所在位置
{
elements2[game][i][j+1]=0;
x=i;y=j ;
}
}
}
}
changement(); //判断是否完成本关
}
void key_right()
{
u8 i, j,xh,yh;
x= scan_x();
y= scan_y();
xh=x;
yh=y;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(i==xh&j==yh+1) //找到人右一个的位置
{
if(elements2[game][i][j]==0) //人右一个为空白
{
elements2[game][i][j]=1 ; //找到改变后人的位置
if(elements2[game][i][j-1]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i][j-1]=0;
}
}
else if(elements2[game][i][j]==2){} //人右一个为墙壁
else if(elements2[game][i][j]==3) //人右一个为箱子
{
if(elements2[game][i][j+1]==0) //箱子的右面为空白
{
elements2[game][i][j+1]=3;
elements2[game][i][j]=1;
if(elements2[game][i][j-1]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i][j-1]=0;
}
}
else if(elements2[game][i][j+1]==2|elements2[0][i][j+1]==3){} //箱子的右面为墙壁、箱子
else if(elements2[game][i][j+1]==4) // 箱子的右面为目标 只考虑一个目标时 游戏结束
{
elements2[game][i][j+1]=5;
elements2[game][i][j]=1;
if(elements2[game][i][j-1]!=4) //人原来的位置上不是目标 找到改变前人的位置
{
elements2[game][i][j-1]=0;
}
game=game+1; //进入下一
if(game!=4)
{
//*display() 第一关结束进入第二关
delay(3000);
printelement();//显示下一关
}
}
}
else if(elements2[game][i][j]==4) //人右一个为目标 目标不变 人变空白 此时记录(人)目标所在位置
{
elements2[game][i][j-1]=0;
x=i;y=j ;
}
}
}
}
changement();
}
void main()
{
uchar i = 128;
Lcd12864_Init();
Lcd12864_ClearScreen();
InitUART();
Timer0Init(); //定时器0初始化
P0=0X00;
datapros1();
while (1)
{
//蓝牙
if(m2==1&game!=4)
{
TR0=1;//打开定时器
printelement();
start=1;
m2=0;
}
if(m2==2&start==1&game!=4) //上
{
key_up();
m2=0;
}
if(m2==3&start==1&game!=4) //下
{
key_down();
m2=0;
}
if(m2==4&start==1&game!=4) //左
{
key_left();
m2=0;
}
if(m2==5&start==1&game!=4) //右边
{
key_right();
m2=0;
}
//k按键
if(k5==0&game!=4)
{
TR0=1;//打开定时器
printelement();
start=1;
} //显示关卡画面
if(k1==0&start==1&game!=4) //上
{
delay(300); //延时消抖
if(k1==0) //上
{
key_up();
}
}
if(k2==0&start==1&game!=4) //下
{
delay(300); //延时消抖
if(k2==0) //上
{
key_down();
}
}
if(k3==0&start==1&game!=4) //左
{
delay(300); //延时消抖
if(k3==0) //上
{
key_left();
}
}
if(k4==0&start==1&game!=4) //右边
{
delay(300); //延时消抖
if(k4==0)
{
key_right();
}
}
if(game==4)
{
TR0=0;//关闭定时器
Lcd12864_ClearScreen();
Lcd12864_Write16CnCHAR(0, 4, "游戏结束游戏结束");
datapros0();
DigDisplay();
}
}
}
void UARTInterrupt(void) interrupt 4 //串口接收字符
{
if(RI)
{
RI = 0;
m2= SBUF;
}
}
void Timer0() interrupt 1
{
TH0=0Xd8; //给定时器赋初值,定时10ms
TL0=0Xf0;
ssec++;
if(ssec>=100) //1s
{
ssec=0;
sec++;
if(sec>=100) //100s
{
sec=0;
}
}
}
图9 关卡1
图10关卡2
图11关卡3
图12关卡4
图13结束画面
1) 在设计人物元素时由于没有设定好LCD左右显示为正常模式导致人物是正常情况下有旋转90度的样子。我通过改变LCD的初始化函数重新调整LCD显示内容。
2) 一开始是想使用红外线模块而不是蓝牙模块,但是由于集成的51板P3.2管脚已经被LCD内置使用,而红外模块的中断也要通过P3.0来开启和关闭,导致我打开LCD显示关卡时红外模块无法使用,我调试了很久在确定无其他问题后才发现了这一错误原因。
a) 具有独立按键控制还具有蓝牙控制控制功能
b) 元素设计:人物模块生动形象。
c) LCD模块自行学习,独立设计。
在时间充足情况下还可以深入添加游戏难度增加如人物进入死角自动结束功能、添加更多关卡、添加多箱子考虑情况等。