猜数字是一个很经典的小游戏,也是编程开发入门的典型,以下为基于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
- 1.
- 2.
- 3.
- 4.
- 5.
- 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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
主逻辑代码: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);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
主逻辑代码说明:
因为在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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
按键部分代码说明:
当使用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