更多信息请访问:与华为官方共建的鸿蒙技术社区https://harmonyos.51cto.com/#zz1.原理相信大家都玩过俄罗斯方块。首先,将场景分为可移动部分和固定部分;unsignedshortdata_blk[16];//游戏固定部分unsignedshortdata_act[4];//游戏移动部分unsignedchardisplay_blk_data[53]={0x40,0xff,0x55};//游戏场景部分用于显示unsignedchardisplay_nst_data[17]={0x40};//游戏显示下一个会出现的方块unsignedchardata_nst;//下一个方块的内容unsignedintscore=0;//得分unsignedintdelay=100000;//延时延迟控制速度charrow_act=-1;//活动块所在的行数hi_i2c_datadisplay_blk;//用于显示hi_i2c_datadisplay_nst;//用于显示固定场景部分大小为16x12,使用16unsignedshort(16位)类型表示,只有低位使用了12位;可移动部分的大小为4x12,用4个unsignedshort(16位)类型表示,只用到低12位;所有的方块(19种类型)都是预定义的block[19][4],接下来的预览使用一个unsignedchar类型(0-18)来表示这19个中的一个;活动块通过row_act(活动块所在的行数)向下移动。二、显示voiddisplay(void){//showthecanvasunsignedshorttemp;for(unsignedchari=0;i<8;++i){for(unsignedcharj=0;j<12;++j){for(unsignedchark=0;k<4;++k){display_blk_data[3+j*4+k]=0x00;temp=i*2>=row_act&&i*2=row_act&&i*2=row_act&&i*2=1;}}直接移动可移动方块即可,左右原理相同;就这么简单?当然不是!移动前要加一些限制:到达边界不能移动,下面有固定方块挡住不能移动是限制移动的代码。如果触发移动限制条件,则不执行移动操作直接返回//ifclosetoedgegiveupmovefor(unsignedchari=0;i<4;++i){if(data_act[i]&0x0001){return;}if((data_act[i]>>1)&data_blk[row_act+i]){return;}}最费脑的就是立方体的旋转。发视频之前没有写旋转功能,直到昨天才调整。首先,看一下底层代码:staticvoidblock_turn(char*arg){(void)arg;unsignedshortturned[4]={0,0,0,0};unsignedchari;for(i=0;i<12;++i){if(data_act[0]&1<{break;}}for(unsignedcharj=0;j<4;++j){for(unsignedchark=0;k<4;++k){转[3-j]|=data_act[k]&1<<(i+j)?1<<(i+k):0;}}for(unsignedcharj=0;j<4;++j){data_act[j]=turned[j];}}首先是声明一个“turned[4]”来存放旋转后的块,为什么不直接在原图中旋转呢?第一个循环从低到高扫描找到所在的列block被定位,第二个block的列为4X4进行行列转置,第三个循环将旋转后的block更新为当前activeblock。划重点:前面说了,这是一个基础代码,功能已经实现了,但是有一个问题不得不考虑:旋转之后有没有干扰?干扰怎么办?分析:除了top不会干扰,bottom、left、right可能因为旋转会干扰,干扰到我。我不会再回头了。如图旋转,会导致方块向下移动:for(unsignedcharj=0;turned[0]==0&&j<2;++j){turned[0]=turned[1];turned[1]=转向[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(unsignedcharj=0;j<4;++j){turned[j]>>=1;}}因为是左对齐,左边不存在这种情况,只有右边有充足的空间可以使用。最近查看是否干扰了固定块:for(unsignedj=0;j<4;++j){if(turned[j]&data_blk[row_act+j]){return;}}满足以上条件,然后对当前激活的方块进行最后一次更新,否则放弃旋转。这就是为什么需要提前声明一个“turned[4]”。如果干扰了原图的旋转,一定要转回去!Fourth,theimplementationofthebutton(key)Thebuttonusestwointerfaces,GPIO5andGPIO8,voidinit_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_u16read_key(void){hi_u16data=0;hi_adc_read(HI_ADC_CHANNEL_2,&data,HI_ADC_EQU_MODEL_4,HI_ADC_CUR_BAIS_DEFAULT,10);returndata;}使用内置的“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)=718S2按下时,采集电压=3.3*(1+1)/(1+1+4.7)=0.985074627采集值=4096*(1+1)/(1+1+4.7)=1223以下参考值来源实际采集!staticvoidkey_press(char*arg){(void)arg;unsignedintret=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了。行行为++;但是加之前还有附加条件,是不是就结束了?是否有满足淘汰条件的行?它会到达顶行吗?charflag=0;for(unsignedchari=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(unsignedchari=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();//Gameoverif(data_blk[0]){oled_write_string_16(20,3,(hi_u8*)"Gameover!");while(1){usleep(5000);}}}如果结束(无论走到游戏场景底部,还是遇到固定块)当前activity方法结束当前activity,移动到固定块,并且再次在顶部生成一个新块;结算一个区块后,需要判断是否有可以消除的线,如果有;如果最上面一行都是固定的方块,判断“Gameover!”。如果有人没有配置好开发环境,也可以下载编译好的,直接用HiBurn烧录开始玩!更多信息请访问:与华为官方共建的鸿蒙科技社区https://harmonyos.51cto.com/#zz