猜数字是一个很经典的小游戏,也是编程开发入门的典型,以下为基于WiFi IoT套件开发的猜数字小游戏的具体开发过程和效果。
基本规则:
由甲方(玩家)默想一个1-99(包含)内的任意数字(整数),然后由乙方进行猜测,并询问甲方猜测的数字是大了还是小了,甲方根据实际情况进行回复,则乙方最多问6个问题,就一定能够猜中甲方默想的数字。
基本原理:
乙方问最多6次,包括最后一次说出猜中的数字,实际上乙方最多有7次猜测的机会。
而使用二分进行查找,2^7=128,则99以内的数字,完全可以覆盖,因此乙方绝对可以猜中。
实现概述:
以上的基本规则和基本原理明确了,我们要在WiFi IoT套件上实现,并且甲方需要参与,需要处理以下三个部分:
- 猜数字的主逻辑
- 使用OLED屏幕显示提示信息,让玩家进行互动操作:我们需要在屏幕上显示汉字,进行玩家当前猜测的数字,以及玩家按键后告知玩家结果
- 使用按键接收玩家操作(大了或者小了等):在这个实例中,我们使用了ADC方式来读取按键信息,从而获得玩家具体操作。所使用的按键为核心板上的USR按键,和OLED板上的S1,S2按键。使用ADC方式读取的时候,他们所使用的输入端口为GPIO5/ADC2,具体的按键作用如下:
- USR:开始游戏,或者确认
- S1:如果猜小了,则玩家按S1告知
- S2:如果猜大了,则玩家按S2告知
原始代码修改处理:【代码基础为code-1.0.tar.gz】
- 开启I2C:vendor/hisi/hi3861/hi3861/build/config/usr_config.mk
## BSP Settings## CONFIG_I2C_SUPPORT is not setCONFIG_I2C_SUPPORT=y# CONFIG_I2S_SUPPORT is not set - I2C复用端口设置:vendor/hisi/hi3861/hi3861/app/wifiiot_app/init/app_io_init.c
#ifdef CONFIG_I2C_SUPPORT /* I2C IO复用也可以选择3/4; 9/10,根据产品设计选择 */ // hi_io_set_func(HI_IO_NAME_GPIO_0, HI_IO_FUNC_GPIO_0_I2C1_SDA); // hi_io_set_func(HI_IO_NAME_GPIO_1, HI_IO_FUNC_GPIO_1_I2C1_SCL); hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_I2C0_SDA); hi_io_set_func(HI_IO_NAME_GPIO_14, HI_IO_FUNC_GPIO_14_I2C0_SCL);#endif
主逻辑代码:guess.c
#include #include #include #include #include #include #include #include #include "button/button.h"#include "oled/oled.h"/*0123456789请在心中默想一个1~99的整数,我能在6个问题之内猜出这个数想好了就按【USER】开始游戏吧,【RST】重启小了按【S1】,大了按【S2】,正确按【USER】第?个问题,是这个数吗:??大了啊!那我再猜小一点小了啊!那我再猜大一点哈哈,我猜到了吧!按【USER】再玩一次(请先默想一个1~99的整数)你默想的数一定是??// 开始:0,长度10// 开始:10,长度30// 开始:40,长度24// 开始:64,长度25// 开始:89,长度14// 开始:103,长度11// 开始:114,长度11// 开始:125,长度9// 开始:134,长度26// 开始:160,长度10*/char *str[] = { "0123456789", "请在心中默想一个1~99的整数,我能在6个问题之内猜出这个数", "想好了就按【USER】开始游戏吧,【RST】重启", "小了按【S1】,大了按【S2】,正确按【USER】", "第?个问题,是这个数吗:??", "大了啊!那我再猜小一点", "小了啊!那我再猜大一点", "哈哈,我猜到了吧!", "按【USER】再玩一次(请先默想一个1~99的整数)", "你默想的数一定是??"};int pos[][2] = { {0, 10}, {10, 30}, {40, 24}, {64, 25}, {89, 14}, {103, 11}, {114, 11}, {125, 9}, {134, 26}, {160, 10}};void display_string(int idx,int delay,int num1, int num2){ int start=0; int len=0; start = pos[idx][0]; len = pos[idx][1]; if(idx==4 && num2==100) { len = len +1; } u8 no[len]; for(int i=0;i = start+i; } // 4 "第?个问题,是这个数吗:??", if(idx==4) { no[1] = num1; if(num2==100) { no[len-3] = 1; no[len-2] = 0; no[len-2] = 0; } else { no[len-2] = num2/10; no[len-1] = num2%10; } } OLED_Clear(); OLED_ShowChineseString(0,0,no,len,16); usleep(delay*1000*1000); }// 主任务static void *GuessTask(const char *arg){ (void)arg; gpio_button_init(); oled_display_init(); OLED_Clear(); printf("请在心中默想一个1~100的整数,我能在6个问题之内猜出这个数是什么:n"); display_string(1,2,0,0); printf("想好了就按【USER】开始游戏吧,【RST】重启n"); display_string(2,2,0,0); printf("小了按【S1】,大了按【S2】,正确按【USER】n"); display_string(3,0,0,0); key_event_t ***; //声明char类型来存放输入的字符 char number; //电脑猜测的数字 while ((*** = gpio_button_get())!=KEY_EVENT_NONE) { // getchar();//忽略回车 char min_shu = 1; // 1是初始最小数。 char max_shu = 100; // 100是初始最大数。 if (*** == KEY_EVENT_USER) { int jishu = 1; // 计数用的,6个问题以内嘛。 while (1) // 条件一直为真,死循环,能用break跳出循环,或用return跳出整个函数。 { number = (min_shu + max_shu) / 2; // 最小数和最大数的和除2 ,意思就是取它们的中间值。 printf("n第%d个问题,是这个数吗:%d", jishu, number); display_string(4,0,jishu, number); *** = gpio_button_get(); // getchar();//忽略回车 if (*** == KEY_EVENT_S2) { printf("n大了啊!那我再猜小一点n"); display_string(5,2,0,0); max_shu = number - 1; //如果是大了,那最大值至少比目前的数小1。 jishu++; //回答次数加1 ,如果你回答了电脑6次问题,电脑还没有猜对,那电脑就输了。 } if (*** == KEY_EVENT_S1) { printf("n小了啊!那我再猜大一点n"); display_string(6,2,0,0); min_shu = number + 1; //如果是小了,那最小值至少比目前的数大1。 jishu++; //同上面,计数加1 } if (*** == KEY_EVENT_USER) { // printf("yn"); printf("n哈哈,我猜到了吧!n"); display_string(7,2,0,0); printf("按【USER】再玩一次(请在心中先默想一个1~100的整数),【RST】重启n"); display_string(8,0,0,0); break; } if (jishu == 7) { printf("n你默想的数一定是%d",(min_shu + max_shu) / 2); display_string(9,2,0,0); printf("n按【USER】再玩一次(请在心中先默想一个1~100的整数),【RST】重启n"); display_string(8,0,0,0); break; } } } else { printf("n按键无效,请重新选择(按【USER】开始,【RST】重启):"); } } return NULL;}// 程序入口static void GuessEntry(void){ osThreadAttr_t attr; WatchDogDisable(); SetLogLevel(HILOG_LV_ERROR); attr.name = "GuessTask"; attr.attr_bits = 0U; attr.cb_mem = NULL; attr.cb_size = 0U; attr.stack_mem = NULL; attr.stack_size = 1024; attr.priority = osPriorityNormal; if (osThreadNew((osThreadFunc_t)GuessTask, NULL, &attr) == NULL) { printf("[GuessNum] Falied to create GuessTask!n"); }}SYS_RUN(GuessEntry);
主逻辑代码说明:
因为在OLED上面显示字符(包括汉字),需要预先取得汉字的字模点阵数据;在这个实例中,会有不同的提示语出现,且未中文,为了方便处理,我将每句话的字模点阵数据单独取出,所以定义了str[],pos[][2],以及display_string(),用于显示对应的语句。其最终调用oled/oled.c中的OLED_ShowChineseString()来将汉字输出到OLED屏幕;特别的,语句4“第?个问题,是这个数吗:??”需要处理具体数字,所以进行了特殊的处理。
获取按键的部分,在button/button.c中的gpio_button_get(),代码随后展示,用于获取按键的状态
OLED部分代码:【以下为oled/oled.h,oled/oled.c和字模数据oled/oledfont.h请查看附件】
#ifndef __OLED_H#define __OLED_H#define OLED_MODE 0#define SIZE 8#define XLevelL 0x00#define XLevelH 0x10#define Max_Column 128#define Max_Row 64#define Brightness 0xFF #define X_WIDTH 128#define Y_WIDTH 64 #define OLED_CMD 0 //写命令#define OLED_DATA 1 //写数据#define u8 unsigned char#define u16 unsigned short#define u32 unsigned int//OLED控制用函数void delay_ms(unsigned int ms);void OLED_ColorTurn(u8 i);void OLED_DisplayTurn(u8 i);void OLED_WR_Byte(u8 dat,u8 cmd);void OLED_Set_Pos(u8 x, u8 y);void OLED_Display_On(void);void OLED_Display_Off(void);void OLED_Clear(void);void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 sizey);u32 oled_pow(u8 m,u8 n);void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 sizey);void OLED_ShowString(u8 x,u8 y,char *chr,u8 sizey);void OLED_ShowChinese(u8 x,u8 y,u8 no,u8 sizey);void OLED_ShowChineseString(u8 x,u8 y,u8 no[],u8 length,u8 sizey);void OLED_Direct_ShowString(u8 x,u8 y,char *chr,u8 sizey);void OLED_DrawBMP(u8 x,u8 y,u8 sizex, u8 sizey,u8 BMP[]);void OLED_Init(void);void oled_display_init(void);#endif
OLED汉字字模数据获取方式:
在OLED上面显示字符(包括汉字),本质上是描点,所以获取对应字符的点阵数据即可。
生成字模数据的工具为PCToLCD,设置为字符模式和C51格式;这个工具还可以用于取图片的点阵数据。
具体获取方式如下:
按键部分代码:【以下为button/button.h,button/button.c请查看附件】
#ifndef __BUTTON_H#define __BUTTON_H#include #define APP_DEMO_ADC#define ADC_TEST_LENGTH 64#define VLT_MIN 100#define STATUS_LEN 4// 按键状态定义typedef enum{ KEY_EVENT_NONE = 0, KEY_EVENT_S1, KEY_EVENT_S2, KEY_EVENT_USER} key_event_t;//获取当前按键key_event_t get_key_event(void);// ADC转换hi_void convert_to_voltage(hi_u32 data_len);// ADC获取void button_adc_test(void);// 设置 按键中断响应void gpio_button_init(void);// 获取需要的按键状态key_event_t gpio_button_get(void);#endif
按键部分代码说明:
当使用ADC方式来读取按键状态的时候,本质上,是读取了ADC输入端口的数据,这个数据进过一定的转换,能够化为对应的电压数据。而不同的按键按下后,ADC端口读取的电压是不同的,并且是在一定范围内波动的,对应按键的电压范围在上述vlt_val_scopes中进行了定义。我们获取到了对应的电压数据,然后与vlt_val_scopes每个范围数据进行对比,从而据此得到对应的按键信息。
实际结果演示:
视频地址: 链接: https://pan.baidu.com/s/1RtT8Wh3ZPbasJ-dK7x1QRg 提取码: vkyh
完整代码:
下载地址: https://pan.baidu.com/s/1RtT8Wh3ZPbasJ-dK7x1QRg 提取码: vkyh
文章来自51社区
|