鸿蒙是问俄罗斯买的,太赞了!在鸿蒙上玩俄罗斯方块小游戏(附源码)
原标题:太赞了!在鸿蒙上玩俄罗斯方块小游戏(附源码)先来看视频:01原理俄罗斯方块相信大家都玩过,首先把场景分成可移动部分、和固定部分,如下图: unsignedshortdata_blk[ 16]; //游戏固定部分unsignedshortdata_act[ 4]; //游戏移动部分unsignedchardisplay_blk_data[ 53] = { 0x40, 0xff, 0x55};
原标题:太赞了!在鸿蒙上玩俄罗斯方块小游戏(附源码)
先来看视频:
01
原理
俄罗斯方块相信大家都玩过,首先把场景分成可移动部分、和固定部分,如下图:
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_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
显示
代码如下:
voiddisplay( void)
{
//show the canvas
unsignedshorttemp;
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
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( unsignedchari= 0;i< 2;++i)
{
for( unsignedcharj= 0;j< 4;++j)
{
for( unsignedchark= 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:
voidoled_write_num(hi_u8 x, hi_u8 y, unsignedintn, hi_bool zero)
{
unsignedintnumber = n;
unsignedcharstr_num[ 9];
for( unsignedchari= 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
方块移动
代码如下:
voidblock_left( void)
{
//限制移动代码
//move to right on screen left
for( unsignedchari= 0;i< 4;++i)
{
data_act[i]>>= 1;
}
}
直接把活动方块进行移动操作即可,左右原理一样。就这么简单?当然不是!
在移动前还要加一些限制:到边界了不能再移动、有固定方块阻挡不能移动。
下面就是限制移动代码,如果触发限制移动条件,直接返回,不进行移动操作:
//if close to edge give up move
for( 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)
{
turned[ 3-j] |= data_act[k]& 1<
}
}
for( unsignedcharj= 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( unsignedj= 0;j< 4;++j)
{
if(turned[j] & data_blk[row_act+j])
{
return;
}
}
以上条件都满足了,才能执行最后的更新到当前活动方块,否则放弃旋转。
这也是为什么要事先声明一个“turned[4]“,如果在原图旋转万一干涉了还要转回去!
04
按键的实现(重点)
按键用到了两个接口分别是 GPIO5 和 GPIO8:
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_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);
returndata;
}
用到了自带的“hi_adc_read”,参数分别是(要读取的端口、接收数据的变量、取N次采样平均结果、基准电压、采样间隔)。
这里读的是端口 2(见原理图)取 4 次平均值,自动基准电压,10us 间隔,可以是方法还没有完全掌握,更改基准电压没有影响检测值,而且当没有按键按下时应该读到 3.3V 的电压,却只读到了 1.8V 的电压。
后面再仔细研究后更新一下。
这里提供计算方法供参考:
以下参考值来源实际采集:
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;
}
}
05
自然下降
向下移动就简单多了,直接进行 ++ 就 OK 了。
row_act++;
但是在加之前也有附加条件,是不是到底了?到底了是不是有满足消除条件的行了?会不会已经到顶行了?
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;
//Game over
if(data_blk[ 0])
{
oled_write_string_16( 20, 3, (hi_u8 *) "Game over!");
while( 1)
{
usleep( 5000);
}
}
}
如果到底了(不管是到游戏场景的底部,还是遇到固定的方块)当前活动方法结束:
当前活动划到固定方块,重新在顶部生成新的方块。
一个方块落定后要判断是否有满足可消除的行,如果有消除。
如果最顶行都被固定方块填充的时候判定“Game over!”。
如果有人还没有配置好开发环境,也可以下载我编译好的,直接用 HiBurn 烧进行去可以玩了!欢迎提建议交流(尽量不要喷我的注释)!返回搜狐,查看更多
责任编辑:
更多推荐
所有评论(0)