猜数字是一个很经典的小游戏,也是编程开发入门的典型,以下为基于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 set
- CONFIG_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 <stdio.h>
- #include <unistd.h>
- #include <ohos_init.h>
- #include <cmsis_os2.h>
- #include <hiview_config.h>
- #include <hiview_log.h>
- #include <wifiiot_watchdog.h>
- #include <hi_task.h>
- #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<len;i++){
- no[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 zf; //声明char类型来存放输入的字符
- char number; //电脑猜测的数字
- while ((zf = gpio_button_get())!=KEY_EVENT_NONE)
- {
- // getchar();//忽略回车
- char min_shu = 1; // 1是初始最小数。
- char max_shu = 100; // 100是初始最大数。
- if (zf == 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);
- zf = gpio_button_get();
- // getchar();//忽略回车
- if (zf == KEY_EVENT_S2)
- {
- printf("\n大了啊!那我再猜小一点\n");
- display_string(5,2,0,0);
- max_shu = number - 1; //如果是大了,那最大值至少比目前的数小1。
- jishu++; //回答次数加1 ,如果你回答了电脑6次问题,电脑还没有猜对,那电脑就输了。
- }
- if (zf == KEY_EVENT_S1)
- {
- printf("\n小了啊!那我再猜大一点\n");
- display_string(6,2,0,0);
- min_shu = number + 1; //如果是小了,那最小值至少比目前的数大1。
- jishu++; //同上面,计数加1
- }
- if (zf == KEY_EVENT_USER)
- {
- // printf("y\n");
- 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 <hi_types_base.h>
- #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