目录:
一、原理
二、显示
三、方块移动
四、按键的实现
五、自然下降
视频相信不少人已经看过了!
一、原理
俄罗斯方块相信大家都玩过,首先把场景分成可移动部分、和固定部分;

1unsigned short data_blk[16]; //游戏固定部分 2unsigned short data_act[4]; //游戏移动部分 3unsigned char display_blk_data[53] = {0x40,0xff,0x55}; //游戏场景部分用于显示 4unsigned char display_nst_data[17] = {0x40}; //游戏显示将出场的下一个方块 5unsigned char data_nst; //下一个方块的内容 6unsigned int score = 0; //得分 7unsigned int delay = 100000; //下降延时控制速度 8char row_act = -1; //活动方块所在行数 9hi_i2c_data display_blk; //用于显示 10hi_i2c_data display_nst; //用于显示
固定场景部分大小为16x12, 用16个无符号short(16位)型表示,仅用到低12位;
可移动部分大小为4x12, 用4个无符号short(16位)型表示,仅用到低12位;
所有的方块(19种)有预定义为block[19][4],下一个预告用一个无符号char型(0-18)表示19个其中的一个;
通过row_act(活动方块所在行数)控制活动方块向下移动。
二、显示
1void display(void) 2{ 3 //show the canvas 4 unsigned short temp; 5 for(unsigned char i=0;i<8;++i) 6 { 7 for(unsigned char j=0;j<12;++j) 8 { 9 for(unsigned char k=0;k<4;++k) 10 { 11 display_blk_data[3+j*4+k] = 0x00; 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]; 13 display_blk_data[3+j*4+k] |= temp&1<<j ? img[k] : 0x00; 14 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]; 15 display_blk_data[3+j*4+k] |= temp&1<<j ? img[k]<<4 : 0x00; 16 } 17 } 18 oled_write_data(0, i, &display_blk); 19 } 20 //show the nest block 21 for(unsigned char i=0;i<2;++i) 22 { 23 for(unsigned char j=0;j<4;++j) 24 { 25 for(unsigned char k=0;k<4;++k) 26 { 27 display_nst_data[j*4+k+1] = 0; 28 display_nst_data[j*4+k+1] |= block[data_nst][i*2]&0x10<<j ? img[k] : 0x00; 29 display_nst_data[j*4+k+1] |= block[data_nst][i*2+1]&0x10<<j ? img[k]<<4 : 0x00; 30 } 31 } 32 oled_write_data(64, i+1, &display_nst); 33 } 34 //show the score 35 oled_write_num(64, 7, score, 0); 36}
显示函数由三部分组成:游戏场景、下一块预告、分数;
重点介绍一下游戏场景部分:
最外层i循环共8次,每次显示16行中的两行;
第二层j循环共12次,每次处理一行中的一个像素;
第三层k循环把第个游戏像素换算成用于显示的4x4个像素
temp = i*2>=row_act && i*2<row_act+4 ? data_blk[i*2]|data_act[i*2-row_act] : data_blk[i*2];
temp = 行数遇到可移动部分 ? 背景+前景 : 背景;
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)"
x y 是要显示的数值所在的坐标,n是要显示的数值,zero是否显示前面的0;
1void oled_write_num(hi_u8 x, hi_u8 y, unsigned int n, hi_bool zero) 2{ 3 unsigned int number = n; 4 unsigned char str_num[9]; 5 for(unsigned char i=0;i<8;++i) 6 { 7 str_num[7-i] = num[number%10]; 8 number /= 10; 9 } 10 str_num[8] = 0; 11 if(zero) 12 { 13 oled_write_string_57(x, y, (hi_u8 *)str_num); 14 } 15 else 16 { 17 hi_u8 *p = str_num; 18 for(;*p=='0';++p); 19 oled_write_string_57(x, y, p); 20 } 21}
这部分比较简单相信大家都能理解,把int型按位转换成字符串显示,
如果去除前面的0直接将字符串的起始地址向后移动,直到有非0数字。
如果想仔细研究显示原理请下载附件显示驱动芯片数据手册
三、方块移动
1void block_left(void) 2{ 3 //限制移动代码 4 //move to right on screen left 5 for(unsigned char i=0;i<4;++i) 6 { 7 data_act[i]>>=1; 8 } 9}
直接把活动方块进行移动操作即可,左右原理一样;
就这么简单? 当然不是!
在移动前还要加一些限制:到边界了不能再移动、有固定方块阻挡不能移动
下面就是限制移动代码,如果触发限制移动条件,直接返回,不进行移动操作
1 //if close to edge give up move 2 for(unsigned char i=0;i<4;++i) 3 { 4 if(data_act[i]&0x0001) 5 { 6 return; 7 } 8 if((data_act[i]>>1) & data_blk[row_act+i]) 9 { 10 return; 11 } 12 }
这个最烧脑的就是方块的旋转了,发视频前就差旋转函数没有写了,直到昨天才调到合适,
先看一下基础代码:
1static void block_turn(char* arg) 2{ 3 (void)arg; 4 unsigned short turned[4]={0, 0, 0, 0}; 5 unsigned char i; 6 for(i=0;i<12;++i) 7 { 8 if(data_act[0]&1<<i || data_act[1]&1<<i || data_act[2]&1<<i || data_act[3]&1<<i) 9 { 10 break; 11 } 12 } 13 for(unsigned char j=0;j<4;++j) 14 { 15 for(unsigned char k=0;k<4;++k) 16 { 17 turned[3-j] |= data_act[k]&1<<(i+j) ? 1<<(i+k) : 0; 18 } 19 } 20 for(unsigned char j=0;j<4;++j) 21 { 22 data_act[j] = turned[j]; 23 } 24}
首先是声明一个"turned[4]"用于存放旋转后的方块,为什么不直接在原图旋转呢?
第一个循环从低到高到位扫描找到方块所在列,
第二个循环从找到方块的列取4X4进行行列转置,
第三个循环把旋转后的方块更新到当前活动方块。

重点:前面讲了这是一个基础代码,功能实现了,但有一个问题不得不考虑:旋转后干涉吗?干涉怎么办?
解析:除了上面不会干涉,下左右都可能因为旋转干涉,干涉我就不转了呗。
如图旋转会造成方块下移:
1 for(unsigned char j=0;turned[0]==0&&j<2;++j) 2 { 3 turned[0] = turned[1]; 4 turned[1] = turned[2]; 5 turned[2] = turned[3]; 6 turned[3] = 0; 7 }
如果己经在边上了,可能会造成出界:

1 for(;turned[0]&1<<12 || turned[1]&1<<12 || turned[2]&1<<12 || turned[3]&1<<12;) 2 { 3 for(unsigned char j=0;j<4;++j) 4 { 5 turned[j] >>= 1; 6 } 7 }
因为是左对齐的,所以左边不会存在这个情况,且只有右边有富裕空间刚好利用一下。
最近再检测一下是否与固定方块干涉:
1 for(unsigned j=0;j<4;++j) 2 { 3 if(turned[j] & data_blk[row_act+j]) 4 { 5 return; 6 } 7 }
以上条件都满足了,才能执行最后的更新到当前活动方块,否则放弃旋转。
这也是为什么要事先声明一个“turned[4]“,如果在原图旋转万一干涉了还要转回去!
作者:Hallym6
想了解更多内容,请访问: 51CTO和华为官方战略合作共建的鸿蒙技术社区https://harmonyos.51cto.com