想了解更多内容,请访问:

发展壮大离不开广大客户长期以来的信赖与支持,我们将始终秉承“诚信为本、服务至上”的服务理念,坚持“二合一”的优良服务模式,真诚服务每家企业,认真做好每个细节,不断完善自我,成就企业,实现共赢。行业涉及除甲醛等,在网站建设、网络营销推广、WAP手机网站、VI设计、软件开发等项目上具有丰富的设计经验。
和华为官方合作共建的鸿蒙技术社区
https://harmonyos./#zz
一、原理
俄罗斯方块相信大家都玩过,首先把场景分成可移动部分、和固定部分;
- unsigned short data_blk[16]; //游戏固定部分
 - unsigned short data_act[4]; //游戏移动部分
 - unsigned char display_blk_data[53] = {0x40,0xff,0x55}; //游戏场景部分用于显示
 - unsigned char display_nst_data[17] = {0x40}; //游戏显示将出场的下一个方块
 - unsigned char data_nst; //下一个方块的内容
 - unsigned int score = 0; //得分
 - unsigned int delay = 100000; //下降延时控制速度
 - char row_act = -1; //活动方块所在行数
 - hi_i2c_data display_blk; //用于显示
 - hi_i2c_data display_nst; //用于显示
 
固定场景部分大小为16x12, 用16个无符号short(16位)型表示,仅用到低12位;
可移动部分大小为4x12, 用4个无符号short(16位)型表示,仅用到低12位;
所有的方块(19种)有预定义为block[19][4],下一个预告用一个无符号char型(0-18)表示19个其中的一个;
通过row_act(活动方块所在行数)控制活动方块向下移动。
二、显示
- void display(void)
 - {
 - //show the canvas
 - unsigned short temp;
 - for(unsigned char i=0;i<8;++i)
 - {
 - for(unsigned char j=0;j<12;++j)
 - {
 - for(unsigned char k=0;k<4;++k)
 - {
 - display_blk_data[3+j*4+k] = 0x00;
 - temp = i*2>=row_act && i*2
 - display_blk_data[3+j*4+k] |= temp&1<
 - temp = i*2+1>=row_act && i*2
 - display_blk_data[3+j*4+k] |= temp&1<
 - }
 - }
 - oled_write_data(0, i, &display_blk);
 - }
 - //show the nest block
 - for(unsigned char i=0;i<2;++i)
 - {
 - for(unsigned char j=0;j<4;++j)
 - {
 - for(unsigned char k=0;k<4;++k)
 - {
 - display_nst_data[j*4+k+1] = 0;
 - display_nst_data[j*4+k+1] |= block[data_nst][i*2]&0x10<
 - display_nst_data[j*4+k+1] |= block[data_nst][i*2+1]&0x10<
 - }
 - }
 - oled_write_data(64, i+1, &display_nst);
 - }
 - //show the score
 - oled_write_num(64, 7, score, 0);
 - }
 
显示函数由三部分组成:游戏场景、下一块预告、分数;
重点介绍一下游戏场景部分:
最外层i循环共8次,每次显示16行中的两行;
第二层j循环共12次,每次处理一行中的一个像素;
第三层k循环把第个游戏像素换算成用于显示的4x4个像素
- temp = i*2>=row_act && i*2
 
temp = 行数遇到可移动部分 ? 背景+前景 : 背景;
- display_blk_data[3+j*4+k] |= temp&1<
 
用于显示的像素数据 |= 显性像素? img中的一列 : 不显示;
下一块预告部分与上面类似,相信能举一反三的理解一下;
再简单介绍一下显示分数的部分“void oled_write_num(hi_u8 x, hi_u8 y, unsigned int n, hi_bool zero)"
x y 是要显示的数值所在的坐标,n是要显示的数值,zero是否显示前面的0;
- void oled_write_num(hi_u8 x, hi_u8 y, unsigned int n, hi_bool zero)
 - {
 - unsigned int number = n;
 - unsigned char str_num[9];
 - for(unsigned char i=0;i<8;++i)
 - {
 - str_num[7-i] = num[number%10];
 - number /= 10;
 - }
 - str_num[8] = 0;
 - if(zero)
 - {
 - oled_write_string_57(x, y, (hi_u8 *)str_num);
 - }
 - else
 - {
 - hi_u8 *p = str_num;
 - for(;*p=='0';++p);
 - oled_write_string_57(x, y, p);
 - }
 - }
 
这部分比较简单相信大家都能理解,把int型按位转换成字符串显示,
如果去除前面的0直接将字符串的起始地址向后移动,直到有非0数字。
如果想仔细研究显示原理请下载附件显示驱动芯片数据手册
三、方块移动
- void block_left(void)
 - {
 - //限制移动代码
 - //move to right on screen left
 - for(unsigned char i=0;i<4;++i)
 - {
 - data_act[i]>>=1;
 - }
 - }
 
直接把活动方块进行移动操作即可,左右原理一样;
就这么简单? 当然不是!
在移动前还要加一些限制:到边界了不能再移动、有固定方块阻挡不能移动
下面就是限制移动代码,如果触发限制移动条件,直接返回,不进行移动操作
- //if close to edge give up move
 - for(unsigned char i=0;i<4;++i)
 - {
 - if(data_act[i]&0x0001)
 - {
 - return;
 - }
 - if((data_act[i]>>1) & data_blk[row_act+i])
 - {
 - return;
 - }
 - }
 
这个最烧脑的就是方块的旋转了,发视频前就差旋转函数没有写了,直到昨天才调到合适,
先看一下基础代码:
- static void block_turn(char* arg)
 - {
 - (void)arg;
 - unsigned short turned[4]={0, 0, 0, 0};
 - unsigned char i;
 - for(i=0;i<12;++i)
 - {
 - if(data_act[0]&1<
 - {
 - break;
 - }
 - }
 - for(unsigned char j=0;j<4;++j)
 - {
 - for(unsigned char k=0;k<4;++k)
 - {
 - turned[3-j] |= data_act[k]&1<<(i+j) ? 1<<(i+k) : 0;
 - }
 - }
 - for(unsigned char j=0;j<4;++j)
 - {
 - data_act[j] = turned[j];
 - }
 - }
 
首先是声明一个"turned[4]"用于存放旋转后的方块,为什么不直接在原图旋转呢?
第一个循环从低到高到位扫描找到方块所在列,
第二个循环从找到方块的列取4X4进行行列转置,
第三个循环把旋转后的方块更新到当前活动方块。
重点:前面讲了这是一个基础代码,功能实现了,但有一个问题不得不考虑:旋转后干涉吗?干涉怎么办?
解析:除了上面不会干涉,下左右都可能因为旋转干涉,干涉我就不转了呗。
如图旋转会造成方块下移:
- for(unsigned char j=0;turned[0]==0&&j<2;++j)
 - {
 - turned[0] = turned[1];
 - turned[1] = turned[2];
 - turned[2] = turned[3];
 - turned[3] = 0;
 - }
 
如果己经在边上了,可能会造成出界:
- for(;turned[0]&1<<12 || turned[1]&1<<12 || turned[2]&1<<12 || turned[3]&1<<12;)
 - {
 - for(unsigned char j=0;j<4;++j)
 - {
 - turned[j] >>= 1;
 - }
 - }
 
因为是左对齐的,所以左边不会存在这个情况,且只有右边有富裕空间刚好利用一下。
最近再检测一下是否与固定方块干涉:
- for(unsigned j=0;j<4;++j)
 - {
 - if(turned[j] & data_blk[row_act+j])
 - {
 - return;
 - }
 - }
 
以上条件都满足了,才能执行最后的更新到当前活动方块,否则放弃旋转。
这也是为什么要事先声明一个“turned[4]“,如果在原图旋转万一干涉了还要转回去!
四、按键的实现(重点)
按键用到了两个接口分别是GPIO5和GPIO8,
- void init_key(void)
 - {
 - GpioInit();
 - IoSetFunc(WIFI_IOT_IO_NAME_GPIO_5, WIFI_IOT_IO_FUNC_GPIO_5_GPIO);
 - GpioSetDir(WIFI_IOT_IO_NAME_GPIO_5, WIFI_IOT_GPIO_DIR_IN);
 - IoSetPull(WIFI_IOT_IO_NAME_GPIO_5, WIFI_IOT_IO_PULL_NONE);
 - GpioRegisterIsrFunc(WIFI_IOT_IO_NAME_GPIO_5, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, key_press, NULL);
 - IoSetFunc(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_IO_FUNC_GPIO_8_GPIO);
 - GpioSetDir(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_GPIO_DIR_IN);
 - IoSetPull(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_IO_PULL_UP);
 - GpioRegisterIsrFunc(WIFI_IOT_IO_NAME_GPIO_8, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, block_turn, NULL);
 - }
 
这两个接口还是有区别的,5#口上接了三个按键,8#口上一个按键,分别指定了中断服务函数:
8#比较简单检测到下降沿进行中断服务程序(方块旋转)即前面讲到的“block_turn()”;
5#稍复杂一点,进行中断服务程序后再进行AD转换,通过AD转换检出是哪一个按键被按下,再进行不同的操作
当不同的按键按下时,会通过AD检测到不同的采样值,可以通过计算得到,也可以通过实际采集得到:
读取端口的模拟量值:
- hi_u16 read_key(void)
 - {
 - hi_u16 data=0;
 - hi_adc_read(HI_ADC_CHANNEL_2, &data, HI_ADC_EQU_MODEL_4, HI_ADC_CUR_BAIS_DEFAULT, 10);
 - return data;
 - }
 
用到了自带的“hi_adc_read”,参数分别是(要读取的端口、接收数据的变量、取N次采样平均结果、基准电压、采样间隔)
这里读的是端口2(见原理图)取4次平均值,自动基准电压,10us间隔,
可以是方法还没有完全掌握,更改基准电压没有影响检测值,而且当没有按键按下时应该读到3.3V的电压,却只读到了1.8V的电压。
后面再仔细研究后更新一下。
这里提供计算方法供参考:
当S1按下 采集电压 = 3.3 * 1 / (1+4.7) = 0.578947368V
采集到的值 = 4096 * 1 / (1+4.7) = 718
当S2按下 采集电压 = 3.3 * (1+1) / (1+1+4.7) = 0.985074627
采集到的值 = 4096 * (1+1) / (1+1+4.7) = 1223
以下参考值来源实际采集!
- static void key_press(char* arg)
 - {
 - (void)arg;
 - unsigned int ret = read_key();
 - usleep(500);
 - if (abs(ret - read_key()) > 30)
 - {
 - return;
 - }
 - if(ret>300 && ret<360)
 - {
 - block_left();
 - return;
 - }
 - if(ret>530 && ret<590)
 - {
 - block_right();
 - return;
 - }
 - }
 
五、自然下降
向下移动就简单多了,直接进行++就OK了。
- row_act++;
 
但是在加之前也有附加条件,是不是到底了?到底了是不是有满足消除条件的行了?会不会已经到顶行了?
- char flag = 0;
 - for(unsigned char i=0;i<4;++i)
 - {
 - if(data_blk[row_act+i+1] & data_act[i])
 - {
 - flag = 1;
 - break;
 - }
 - }
 - if(flag || (row_act>11 && data_act[15-row_act]!=0))
 - {
 - for(unsigned char i=0;i<4;++i)
 - {
 - data_blk[row_act+i] |= data_act[i];
 - data_act[i] = block[data_nst][i];
 - }
 - remove_full();
 - row_act = -1;
 - data_nst = get_next();
 - //Game over
 - if(data_blk[0])
 - {
 - oled_write_string_16(20, 3, (hi_u8 *)"Game over!");
 - while(1)
 - {
 - usleep(5000);
 - }
 - }
 - }
 
如果到底了(不管是到游戏场景的底部,还是遇到固定的方块)当前活动方法结束
当前活动划到固定方块,重新在顶部生成新的方块;
一个方块落定后要判断是否有满足可消除的行,如果有消除;
如果最顶行都被固定方块填充的时候判定“Game over!”。
如果有人还没有配置好开发环境,也可以下载我编译好的,直接用HiBurn烧进行去可以玩了!
想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos./#zz
            
            新闻标题:2020征文-开发板鸿蒙Hi3861之俄罗斯方块小游戏(附源码)            
            文章分享:http://www.wtcwzsj.com/article/cdjggod.html
        
Copyright © 2009-2022 www.wtcwzsj.com 青羊区广皓图文设计工作室(个体工商户) 版权所有 蜀ICP备19037934号