[文章]

基于WiFi IoT套件开发的猜数字小游戏代码分享

2020-12-7 12:05:13  605 鸿蒙 Hi3861 IoT
分享

猜数字游戏演示

1
猜数字是一个很经典的小游戏,也是编程开发入门的典型,以下为基于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
      1. ## BSP Settings
      2. #
      3. # CONFIG_I2C_SUPPORT is not set
      4. CONFIG_I2C_SUPPORT=y
      5. # CONFIG_I2S_SUPPORT is not set
      复制代码

  • I2C复用端口设置:vendor/hisi/hi3861/hi3861/app/wifiiot_app/init/app_io_init.c

    • 1
      2
      3
      4
      5
      6
      7
      #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
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
#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 = 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请查看附件】
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
#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请查看附件】
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
#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;
// 按键电压范围值定义
float vlt_val_scopes[STATUS_LEN][2]={
   { 3.0, 9.0},    //KEY_EVENT_NONE
   { 0.4, 0.6},    //KEY_EVENT_S1
   { 0.8, 1.1},    //KEY_EVENT_S2
   { 0.01,0.3},    //KEY_EVENT_USER
};

// 按键状态列表
key_event_t key_status_list[STATUS_LEN] = {
   KEY_EVENT_NONE,
   KEY_EVENT_S1,
   KEY_EVENT_S2,
   KEY_EVENT_USER,
};
//获取当前按键
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://bbs.elecfans.com/jishu_2016346_1_1.html

相关文章

王丽 2020-12-8 09:57:40
这个帖子简直是太棒了,有视频,有讲解,有代码!
回复

举报

王磊 2020-12-8 09:58:13
我和楼上想法一样,哈哈哈
回复

举报

评论

您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发文章
快速回复 返回顶部 返回列表