STM32掌机教程5,程序框架,随机,加命与升级

9 篇文章 10 订阅

随机生成地鼠

  随机数是游戏里边非常重要的组成部分,贪吃蛇随机刷新下一个食物,俄罗斯方块随机生成下一个方块,大富翁扔骰子,都是随机的。甚至微信群红包,金额也是随机的。正是因为这些事件不可预测,游戏才充满趣味性。我们地鼠的生成,当然也要随机。
  然而,计算机产生的随机数,都是“伪随机”。伪,指的是说它是随机的,但是却都是有规律可循的。对于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);
}

到此,打地鼠程序的基本框架就列好了,也可以来玩打地鼠了。接下来增加背景音乐。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值