前言

在我的51单片机课程设计中,我最初计划制作一个打地鼠小游戏。然而,由于后来全班决定统一做同一个项目,再加上我参加了电赛,项目进展受到了一些耽误。不过,我利用暑假的时间将这个项目补上了,并成功实现了打地鼠小游戏。

效果图

image-20230615162539477

image-20230615162547348

main.c程序

使用到资源有,定时器产生随机数种子、st7565型号的128*64的显示器、矩阵按键。目前实现的比较简单,按哪个按键打对应格子的地鼠,打死才会出现下一个

#include < reg51.h > 
#include < stdlib.h >
#include "st7565.h"

unsigned char keyvalue = 0; //存放按键值
bit flag = 0; //打地鼠 为1 刷新下一个地鼠的标志
bit keyflag = 0; //启动按键的标志
bit startflag = 0; //游戏开始标志


unsigned char num[3] = {
5, 5, 5
}; //1-9随机数储存
unsigned char t = 0, a = 0; //随机数的变量
sbit KEY_IN_1 = P1 ^ 3;
sbit KEY_IN_2 = P1 ^ 2;
sbit KEY_IN_3 = P1 ^ 1;
sbit KEY_IN_4 = P1 ^ 0;
sbit KEY_OUT_1 = P1 ^ 4;
sbit KEY_OUT_2 = P1 ^ 5;
sbit KEY_OUT_3 = P1 ^ 6;
sbit KEY_OUT_4 = P1 ^ 7;

void KeyDriver(); //按键驱动函数

unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
{
0x30, 0x37, 0x34, 0x31
}, //数字键0、数字键7、数字键4、数字键1
{
0x1b, 0x38, 0x35, 0x32
}, //ESC键、数字键8、数字键5、数字键2
{
0x0d, 0x39, 0x36, 0x33
}, //回车键、数字键9、数字键6、数字键3
{
0x27, 0x28, 0x25, 0x26
} //向右键,向下键,向左键,向上键

};

unsigned char KeySta[4][4] = { //全部矩阵按键的当前状态
{
1, 1, 1, 1
}, {
1, 1, 1, 1
}, {
1, 1, 1, 1
}, {
1, 1, 1, 1
}
};

//10ms 延时
void Delay10ms(unsigned int c) //延时工具自动生成
{
unsigned char a, b;
for (; c > 0; c--)
for (b = 38; b > 0; b--)
for (a = 130; a > 0; a--);
}


// 刷新地鼠
void refresh12864() {
//关闭敲死的地鼠
Lcd12864_Write16CnCHAR(96, 2, "壤土"); //1
Lcd12864_Write16CnCHAR(64, 2, "壤土"); //2
Lcd12864_Write16CnCHAR(32, 2, "壤土"); //3
Lcd12864_Write16CnCHAR(96, 4, "壤土"); //4
Lcd12864_Write16CnCHAR(64, 4, "壤土"); //5
Lcd12864_Write16CnCHAR(32, 4, "壤土"); //6
Lcd12864_Write16CnCHAR(96, 6, "壤土"); //7
Lcd12864_Write16CnCHAR(64, 6, "壤土"); //8
Lcd12864_Write16CnCHAR(32, 6, "壤土"); //9

Delay10ms(30);
if (num[1] == 1)
Lcd12864_Write16CnCHAR(96, 2, "鼠地"); //1
else if (num[1] == 2)
Lcd12864_Write16CnCHAR(64, 2, "鼠地"); //2
else if (num[1] == 3)
Lcd12864_Write16CnCHAR(32, 2, "鼠地"); //3
else if (num[1] == 4)
Lcd12864_Write16CnCHAR(96, 4, "鼠地"); //4
else if (num[1] == 5)
Lcd12864_Write16CnCHAR(64, 4, "鼠地"); //5
else if (num[1] == 6)
Lcd12864_Write16CnCHAR(32, 4, "鼠地"); //6
else if (num[1] == 7)
Lcd12864_Write16CnCHAR(96, 6, "鼠地"); //7
else if (num[1] == 8)
Lcd12864_Write16CnCHAR(64, 6, "鼠地"); //8
else
Lcd12864_Write16CnCHAR(32, 6, "鼠地"); //9
}


void Timer0Init() //定时器
{
TMOD |= 0X01; //选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0 = 0XFC; //给定时器赋初值,定时1ms
TL0 = 0X18;
ET0 = 1; //打开定时器0中断允许
EA = 1; //打开总中断
TR0 = 1; //打开定时器
}

// 生成随机数
void freenum() {
while (1) {
srand(t); //函数来设置随机数的种子
a = (char)(rand() % 10); //生成一个0到9之间的随机数
if (a == num[1] | a == 0) //不能与上次的相同,且不能为0,因为只有9个洞
{
continue;
} else {
num[2] = num[1]; //存入上一次的值
num[1] = num[0]; //存入上一次的值
num[0] = a;
break;
}
}
}




void main() {
Timer0Init(); //定时器0初始化
Lcd12864_Init();
freenum();

Lcd12864_ClearScreen();
Lcd12864_Write16CnCHAR(40, 3, "鼠地打");
while (1) {
if (flag == 1) {
Lcd12864_ClearScreen();
flag = 0;
refresh12864(); //刷新num[1]里面的地鼠
}

KeyDriver(); //调用按键驱动函数

if (keyflag) {
keyflag = 0;
if (num[1] == keyvalue) //num[1]随机数;ketvalue即按下值
{
freenum();
flag = 1;
} else {
Lcd12864_ClearScreen();
Lcd12864_Write16CnCHAR(32, 3, "束结戏游");

}
}

}
}


// 按键动作函数,根据键码执行相应的操作,keycode-按键键码
void KeyAction(unsigned char keycode) {

if ((keycode >= 0x31) && (keycode <= 0x39)) //输入0-9的数字
{
if (startflag) {
keyvalue = (keycode - 0x30); //把ascll转数字
keyflag = 1;
}
} else if (keycode == 0x1B) //返回主菜单 esc键
{
startflag = 0;
flag = 0;
Lcd12864_ClearScreen();
Lcd12864_Write16CnCHAR(40, 3, "鼠地打");
} else if (keycode == 0x0D) //回车键,开始游戏
{
flag = 1;
startflag = 1;
}


}

//按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用
void KeyDriver() {
unsigned char i, j;
static unsigned char backup[4][4] = { //按键值备份,保存前一次的值
{
1, 1, 1, 1
}, {
1, 1, 1, 1
}, {
1, 1, 1, 1
}, {
1, 1, 1, 1
}
};
for (i = 0; i < 4; i++) //循环检测4*4的矩阵按键
{
for (j = 0; j < 4; j++) {
if (backup[i][j] != KeySta[i][j]) //检测按键动作
{
if (backup[i][j] != 0) //按键按下时执行动作
{
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
}
backup[i][j] = KeySta[i][j]; //刷新前一次的备份值
}
}
}
}

//按键扫描函数,需在定时中断中调用,推荐调用间隔1ms
void KeyScan() {
unsigned char i;
static unsigned char keyout = 0; //矩阵按键扫描输出索引
static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区
{
0xFF, 0xFF, 0xFF, 0xFF
}, {
0xFF, 0xFF, 0xFF, 0xFF
}, {
0xFF, 0xFF, 0xFF, 0xFF
}, {
0xFF, 0xFF, 0xFF, 0xFF
}
};

//将一行的4个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按键状态
for (i = 0; i < 4; i++) //每行4个按键,所以循环4次
{
if ((keybuf[keyout][i] & 0x0F) == 0x00) { //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下
KeySta[keyout][i] = 0;
} else if ((keybuf[keyout][i] & 0x0F) == 0x0F) { //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
}
}
//执行下一次的扫描输出
keyout++; //输出索引递增
keyout = keyout & 0x03; //索引值加到4即归零
switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0:
KEY_OUT_4 = 1;
KEY_OUT_1 = 0;
break;
case 1:
KEY_OUT_1 = 1;
KEY_OUT_2 = 0;
break;
case 2:
KEY_OUT_2 = 1;
KEY_OUT_3 = 0;
break;
case 3:
KEY_OUT_3 = 1;
KEY_OUT_4 = 0;
break;
default:
break;
}
}

void Timer0() interrupt 1 {
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
t++; //随机数种子生成
if (t == 100)
t = 0;
KeyScan(); //调用按键扫描函数
}

总结

随机数获取随机数是当时遇到的难点,最终采用定时器获取随机数种子,从而获得伪随机数。显示屏是st7565,需要使用文字取模软件将图片或文字转换成对应的显示码。


有任何疑问和想法,欢迎在评论区与我交流。