送 福 利 啦

关注HarmonyOS技术社区,回复【鸿蒙】送小米小爱音箱mini(数量不多,先到先得),还可以免费下载鸿蒙入门资料!
👇扫码立刻关注👇

专注开源技术,共建鸿蒙生态
01
原理

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(活动方块所在行数)控制活动方块向下移动。
02
显示
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<row_act+4 ? data_blk[i*2]|data_act[i*2-row_act] : data_blk[i*2]; display_blk_data[3+j*4+k] |= temp&1<<j ? img[k] : 0x00; temp = i*2+1>=row_act && i*2<row_act+3 ? data_blk[i*2+1]|data_act[i*2+1-row_act] : data_blk[i*2+1]; display_blk_data[3+j*4+k] |= temp&1<<j ? img[k]<<4 : 0x00; } } 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<<j ? img[k] : 0x00; display_nst_data[j*4+k+1] |= block[data_nst][i*2+1]&0x10<<j ? img[k]<<4 : 0x00; } } oled_write_data(64, i+1, &display_nst); } //show the score oled_write_num(64, 7, score, 0);}
显示函数由三部分组成:游戏场景、下一块预告、分数。
重点介绍一下游戏场景部分:
最外层 i 循环共 8 次,每次显示 16 行中的两行。
第二层 j 循环共 12 次,每次处理一行中的一个像素。
temp = i*2>=row_act && i*2<row_act+4 ? data_blk[i*2]|data_act[i*2-row_act] : data_blk[i*2];
display_blk_data[3+j*4+k] |= temp&1<<j ? img[k] : 0x00;
用于显示的像素数据 |= 显性像素?img 中的一列:不显示。
下一块预告部分与上面类似,相信能举一反三的理解一下。
再简单介绍一下显示分数的部分“void oled_write_num(hi_u8 x,hi_u8 y,unsigned int n,hi_bool zero)"。
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 数字。
如果想仔细研究显示原理请下载附件显示驱动芯片数据手册。
03
方块移动
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<<i || data_act[1]&1<<i || data_act[2]&1<<i || data_act[3]&1<<i) { 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]“,如果在原图旋转万一干涉了还要转回去!
04
按键的实现(重点)
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; }}
05
自然下降
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 烧进行去可以玩了!欢迎提建议交流(尽量不要喷我的注释)!

点“阅读原文”立即下载源码




