#2020征文-开发板# 基于WiFi IoT套件开发的猜数字小游戏代码分享 原创 精华

HonestQiao
发布于 2020-12-7 11:54
浏览
4收藏

猜数字是一个很经典的小游戏,也是编程开发入门的典型,以下为基于WiFi IoT套件开发的猜数字小游戏的具体开发过程和效果。

 

基本规则:
由甲方(玩家)默想一个1-99(包含)内的任意数字(整数),然后由乙方进行猜测,并询问甲方猜测的数字是大了还是小了,甲方根据实际情况进行回复,则乙方最多问6个问题,就一定能够猜中甲方默想的数字。

 

基本原理:
乙方问最多6次,包括最后一次说出猜中的数字,实际上乙方最多有7次猜测的机会。

而使用二分进行查找,2^7=128,则99以内的数字,完全可以覆盖,因此乙方绝对可以猜中。

 

实现概述:
以上的基本规则和基本原理明确了,我们要在WiFi IoT套件上实现,并且甲方需要参与,需要处理以下三个部分:

  1. 猜数字的主逻辑
  2. 使用OLED屏幕显示提示信息,让玩家进行互动操作:我们需要在屏幕上显示汉字,进行玩家当前猜测的数字,以及玩家按键后告知玩家结果
  3. 使用按键接收玩家操作(大了或者小了等):在这个实例中,我们使用了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每个范围数据进行对比,从而据此得到对应的按键信息。

 


实际结果演示:

#2020征文-开发板# 基于WiFi IoT套件开发的猜数字小游戏代码分享-鸿蒙开发者社区

视频地址: 链接: https://pan.baidu.com/s/1RtT8Wh3ZPbasJ-dK7x1QRg 提取码: vkyh

 


完整代码:

#2020征文-开发板# 基于WiFi IoT套件开发的猜数字小游戏代码分享-鸿蒙开发者社区

下载地址: https://pan.baidu.com/s/1RtT8Wh3ZPbasJ-dK7x1QRg 提取码: vkyh

 

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
guess_num.zip 18.63K 62次下载
已于2021-1-14 07:26:40修改
3
收藏 4
回复
举报
5条回复
按时间正序
/
按时间倒序
鲜橙加冰
鲜橙加冰

乔帮主周六晚上辛苦了,回答了那么多问题。和粉丝们互动到10点多。

回复
2020-12-7 12:13:11
红叶亦知秋
红叶亦知秋

请问乔老师能把完整代码贴在文章附件下吗,百度云实在太慢了

1
回复
2020-12-7 12:17:00
HonestQiao
HonestQiao 回复了 红叶亦知秋
请问乔老师能把完整代码贴在文章附件下吗,百度云实在太慢了

已上传。

回复
2020-12-8 11:20:50
鸿蒙开发者社区官方账号
鸿蒙开发者社区官方账号

征文大赛正在火热进行中,楼主这么优秀的文章真的不考虑参加吗?

 

例如这篇在标题开头添加“#2020征文-开发板#“,
再找到相应的专栏位置投稿,

就可以参加比赛啦!

 

详细步骤可以点击链接https://harmonyos.51cto.com/posts/1940进行了解

 

用更多的文章来赢取更多的奖励和人气吧!期待楼主后续的活跃表现。

回复
2020-12-8 14:23:26
HonestQiao
HonestQiao 回复了 鸿蒙开发者社区官方账号
征文大赛正在火热进行中,楼主这么优秀的文章真的不考虑参加吗? 例如这篇在标题开头添加“#2020征文-开发板#“,再找到相应的专栏位置投稿, 就可以参加比赛啦! 详细步骤可以点击链接https://harmonyos.51cto.com/posts/1940进行了解 用更多的文章来赢取更多的奖励和人气吧!期待楼主后续的活跃表现。

已经设置好啦!

回复
2021-1-14 07:27:04
回复
    相关推荐