随机生成地鼠
随机数是游戏里边非常重要的组成部分,贪吃蛇随机刷新下一个食物,俄罗斯方块随机生成下一个方块,大富翁扔骰子,都是随机的。甚至微信群红包,金额也是随机的。正是因为这些事件不可预测,游戏才充满趣味性。我们地鼠的生成,当然也要随机。
然而,计算机产生的随机数,都是“伪随机”。伪,指的是说它是随机的,但是却都是有规律可循的。对于C语言,可以直接调用一个随机数生产函数srand()。但是这个函数需要种子。随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就不会变。也就是说,伪随机数也是某种对应映射的产物,而这个自变量就是种子。
如果你每次调用srand()时都提供相同的种子值,那么,你将会得到相同的随机数序列。因此要想产生看似“更随机的随机数”的关键,就是找个靠谱的种子。
对于计算机系统来说,经常用时间做种子,因为大概率每次调用程序的时间是不一样的。单片机系统可以使用定时器,把定时器中的计数值作为种子。计数值飞快自加,可以认为每次需要生成随机数的时候,计数值都是不可预测的,也就达到了生成随机数的目的。
我选用了定时器4来生成种子。定义了一个全局的变量作为计数值。定时器大约每隔1us每溢出一次,变量自增。
定时器4的初始化与中断服务
//timer.c
void TIM4_Seed_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = 71; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =0; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ClearITPendingBit(TIM4, TIM_IT_Update ); //清除TIMx更新中断标志
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM4中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM4, ENABLE); //使能TIMx
}
//定时器4中断服务程序
void TIM4_IRQHandler(void) //TIMx中断
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) //检查TIMx更新中断发生与否
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update ); //清除TIMx更新中断标志
time_us++;
}
}
然后在主函数中调用初始化函数,并把地鼠编号递增的代码改为1~8之间随机。
//main.c
u32 time_us = 0; //随机生成地鼠,用到的随机数种子
TIM4_Seed_Init();
//main.c if(life)
if(next_flg) //需要生成下一个地鼠
{
next_flg = 0;
// mouse++;
// if(mouse > 8)//1到8
// mouse = 1;
u8 i = 0; //从随机数到地鼠用的临时变量
srand(time_us);
i = rand()%8 +1;
mouse = i;
......
}
主函数中这个if条件语句太长了,不适合阅读,因此把随机生成地鼠与点亮地鼠灯的功能提取函数。
//main.c if(life)
if(next_flg) //需要生成下一个地鼠
{
next_flg = 0;
mouse = random_num(); //产生随机数
CreatMouse(mouse); //随机生成地鼠
timeout_flg = 0;
TIM_SetCounter(TIM2, 0);//定时器清零
}
//按照参数,点亮某个LED
void CreatMouse(u8 mouse)
{
AllLED_OFF(); //先把灯全都关掉
delay_ms(50); //然后稍微延时,避免地鼠刷新到同一个位置时,看不出来
switch(mouse)
{
case 1: SLED1 = 0;break;
case 2: SLED2 = 0;break;
case 3: SLED3 = 0;break;
case 4: SLED4 = 0;break;
case 5: SLED5 = 0;break;
case 6: SLED6 = 0;break;
case 7: SLED7 = 0;break;
case 8: SLED8 = 0;break;
default: break;
}
}
//使用定时器的计数值生成随机数
u8 random_num(void)
{
u8 i = 0; //从随机数到地鼠用的临时变量
srand(time_us);
i = rand()%8 +1;
return i;
}
编译并运行,可以发现地鼠是随机生成的。考虑到有可能两个地鼠生成到同一个位置,所以打中每次生成地鼠之前先关掉LED灯是很有必要的。
加命
一般在游戏里,得到一定的分数以后都会加命,我们也来定义一个功能:得分每次超过整百,都加一条命。
在打中地鼠的时候会加分,所以我们把加命的逻辑写在加分之后。需要注意这么个情况:假如现在得分是102,加命,再打中一个地鼠后,假如分数是108,不应该加命,应该等到分数大于200后,再加命。因此需要定义一个变量,帮助判断是否需要加命。
//main.c
u8 add_life_cnt = 1; //用于增加生命计数,每到100的整数倍以后加命
//while(1) if(life)
key = KEY_Scan(0);
if(key) //如果按下按键
{
next_flg = 1; //不论打的对不对,都要生产下一个地鼠
TIM_SetCounter(TIM2, 0);//不论打中的地鼠对不对,定时器都清零
if(key == mouse)//正确打中地鼠 加分,生成下一个地鼠
{
score+= level;
showNumber(56,6,score,DEC,8,FONT_16_EN);
if(score/100 == add_life_cnt) //一定积分以后加命
{
add_life_cnt++;
life++;
showNumber(56,2,life,DEC,8,FONT_16_EN);
}
}
else //打错 减命
{
life--;
showNumber(56,2,life,DEC,8,FONT_16_EN);
}
}
在测试的时候,可以把加命的分数设置的小一点,比如20:if(score/20 == add_life_cnt) //一定积分以后加命 。
为了让代码更方便阅读与维护,我把加命的操作提取出函数来。
//加命并显示
void add_life(void)
{
add_life_cnt++;
life++;
showNumber(56,2,life,DEC,8,FONT_16_EN);
}
然后加命的判断分支里就只需要一行代码了:
if(score/100 == add_life_cnt) //一定积分以后加命
{
add_life();
}
升级
难度升级以后,每个地鼠出生停留的时间都会变短,我设置一个数组,用于存放地鼠停留的时间,从2s到100ms——我还真不信有人反应能这么快。博尔特反应速度133ms。
我的思路是,每击中10个地鼠难度增加,难度增加其实也就是数组的索引+1,然后更新定时器2的溢出时间。
首先要定义数组,变量,并修改定时器2初始化。
//main.c
#define LEVEL_UP_CNT 10 //击中x个地鼠难度增加
//不同难度对应的时间,(数值+1)/10 = 时间ms
u16 level1_time_arr[15] = {19999,14999,11999,9999,7999,5999,4999,3999,2999,1999,1799,1599,1399,1199,999};
u8 level_cnt = 0; //击中的地鼠数量,用于升级计数
TIM2_Int_Init(level1_time_arr[level_cnt],7199);
编写一个函数用于提升难度。
//难度提升并显示
void level_up(void)
{
level_cnt = 0;
level++;
if(level>14)//默认14关
level = 14;
TIM_SetAutoreload(TIM2,level1_time_arr[level]);
showNumber(56,4,level,DEC,8,FONT_16_EN);
}
然后在打中地鼠的逻辑里来判断是否需要升级。注意,有时候升级与加命的条件会同时满足,这当然没什么弊端,知识考虑到后续会增加BGM,BGM最好持续一段时间,所以这两种情况先不同时处理。
//main.c if(key == mouse)
else if(++level_cnt > LEVEL_UP_CNT) //升级与加命不同时处理
{
level_up();
}
提取函数
为了让主函数看起来尽可能简洁,我把屏幕显示,加分,减命的操作也提取出了函数。主函数最好能只体现业务逻辑。
int main(void)
{
LED_Init();
KEY_Init();
delay_init();
initIIC();
initOLED();
TIM4_Seed_Init();
FirstScreen(); //显示完屏幕内容以后,再开启打地鼠计时用的定时器
show_opt();
TIM2_Int_Init(level1_time_arr[level_cnt],7199);
while(1)
{
if(life)//还有命
{
if(next_flg) //需要生成下一个地鼠
{
next_flg = 0;
mouse = random_num(); //产生随机数
CreatMouse(mouse); //随机生成地鼠
TIM_SetCounter(TIM2, 0);//定时器清零
timeout_flg = 0;
}
if(timeout_flg) //超时,减命,生成下一个地鼠
{
timeout_flg = 0;
next_flg = 1;
sub_life();
}
key = KEY_Scan(0);
if(key) //如果按下按键
{
next_flg = 1; //不论打的对不对,都要生产下一个地鼠
TIM_SetCounter(TIM2, 0);//不论打中的地鼠对不对,定时器都清零
if(key == mouse)//正确打中地鼠 加分,生成下一个地鼠
{
add_score();
if(score/100 == add_life_cnt) //一定积分以后加命
{
add_life();
}
else if(++level_cnt > LEVEL_UP_CNT) //升级与加命不同时处理
{
level_up();
}
}
else //打错 减命
{
sub_life();
}
}
}
else//没命了
{
}
}
}
以下是提取出来的函数。
//按照level加分并显示
void add_score(void)
{
score += level;
showNumber(56,6,score,DEC,8,FONT_16_EN);
}
//减命并显示
void sub_life(void)
{
life--;
showNumber(56,2,life,DEC,8,FONT_16_EN);
}
//设置固定显示的内容
void FirstScreen(void)
{
//显示大LOGO
formatScreen(0x00);
showImage(0,0,128,8,Y_LOGO_ENUM);
delay_ms(1000);
//显示汉字
formatScreen(0x00);
// showString(0,0,"yoodao",FONT_16_EN);
showCNString(0,0,"小极客打地鼠掌机",FONT_16_CN);
//显示生命、难度与分数
showString(0,2,"life:",FONT_16_EN);
showString(0,4,"level:",FONT_16_EN);
showString(0,6,"score:",FONT_16_EN);
}
//设置屏幕上的参数
void show_opt(void)
{
showNumber(56,2,life,DEC,8,FONT_16_EN);
showNumber(56,4,level,DEC,8,FONT_16_EN);
showNumber(56,6,score,DEC,8,FONT_16_EN);
}
到此,打地鼠程序的基本框架就列好了,也可以来玩打地鼠了。接下来增加背景音乐。
上一篇:STM32掌机教程6,电子琴
下一篇:STM32掌机教程4,STM32驱动OLED屏幕
推荐阅读最新更新时间:2024-11-07 23:21
设计资源 培训 开发板 精华推荐
- 具有薄膜电池备份功能的 LTC3106IUDC 远程户外太阳能收割机的典型应用电路
- 使用 Richtek Technology Corporation 的 RT8575 的参考设计
- 使用 Semtech 的 EZ1587C 的参考设计
- 使用 Analog Devices 的 LT8608IMSE 的参考设计
- OP113ESZ耳机放大器多媒体声音编解码器典型应用
- robomaster
- LTC2155-14 演示板,14 位,170Msps,1.8V 双路 ADC,DDR LVDS 输出,5-140MHz
- 使用 Analog Devices 的 LT3470HTS8 的参考设计
- 具有电源系统管理功能的高性能单相 DC/DC 控制器
- TTL-RS232串口通信模块 SP3232