为了账号安全,请及时绑定邮箱和手机立即绑定
慕课专栏

目录

索引目录

区块链游戏 ForeverBird 开发实战

原价 ¥ 49.90

立即订阅
14 bird 基因算法设计与实现
更新时间:2019-06-12 15:02:53
我们活着不能与草木同腐,不能醉生梦死,枉度人生,要有所作为。

——方志敏

本节主要讲解以下内容:

  • bird 基因算法设计
  • bird 基因计算其他属性
  • 根据 bird 基因控制展示

ForeverBird 相关 bird 的基因生成,通过基因计算属性与展示的关系如下图:

图片描述

由上图可知,bird 的基因有 32 字节,其中基因是通过智能合约生成的,而基因对应 bird 部分及展示是通过业务后台完成。本节接下来依次讲解设计思路。

bird 基因算法设计

bird 基因由智能合约生成,为了保证游戏的公平、公正,每个基因序列都是随机生成。目前的算法实现如下:

基因 = hash(time, id, salt)

bird 基因算法分析

基因是时间、id、salt 进行散列生成的序列,因此基因生成依赖于散列算法、时间戳、id 和 salt。下面依次讲解各个因素因子的选择:

  • 散列函数
    这里的散列函数是指单向散列函数,是指对明文进行映射得到固定长度的二进制串。使用单向散列函数可以快速计算 hash 值,但是通过 hash 值很难在有限时间内还原出明文。常见的散列函数有 md5、sha1、sha256 等。
    Solidity 提供的散列函数有 sha3sha256keccak256 等,ForeverBird 采用的散列函数是 keccak256,输出 256 位 hash 值(对应 Solidity 的 bytes32)。Solidity 提供了一些常用的数学和密码学函数,请参考 Solidity密码学函数

  • 时间戳
    使用时间戳是为了提高随机性,Solidity 提供了区块属性 now,标识区块时间戳 block.timestamp,要是只使用时间戳做 hash 的话,如果多个交易位于同一个区块则基因相同,而且矿工可以设置该时间戳,因此为了提供系统安全性,添加了随机因子 id 和 salt。

  • id
    id 是 bird 的 token id,使用 id 能够防止同一区块的多个交易生成相同基因,这里没有使用调用者以太坊地址 msg.sender,是为了防止同一玩家生成相同基因的两只 bird。

  • salt
    salt 是为了提高散列函数随机性,防止玩家计算出 bird 基因规则。该 salt 值需要保密不能泄露。

bird 基因生成算法实现

基因生成算法函数为 _generateGenes(),该函数有两个返回值,一个是 bird token id,另一个是bird 基因序列 genes。

// 生成基因序列
function _generateGenes(uint64 _birthTime)
    internal
    returns(uint64 id, bytes32 genes)
{
    // 获取 tokenid
    uint64 _id = totalBirds + 1;
    totalBirds += 1;
    bytes32 _genes = keccak256(_birthTime, _id, salt);
    return (_id, _genes);
}

bird 基因计算属性

由上一节介绍可知,bird 具有重量、经验值、力量、速度、等级等属性。ForeverBird 设定每只 bird 初始值规则如下:

  • 重量:1000 盎司
  • 经验值:0
  • 级别:0
  • 力量:5~15
  • 速度:5~15

其中力量和速度都是与基因相关的范围为 5~15 的随机数。力量取决于基因的第一个字节,速度取决于基因的第二个字节。因为力量和速度都是 5~15 的随机值,抽象公共函数 _getNumber(byte b) 获取随机数。_createBird(...) 函数为生成 bird。

创建 bird 的智能合约代码如下,写好合约之后,可以在 Remix 平台编译、运行,先调用 testCreateBird 创建新的 bird,然后调用 getBirdInfo(...) 查询是否成功。

pragma solidity ^0.4.21;

contract BirdManager {
    // 定义 bird 结构体
    struct Bird {
        bytes32 genes;
        uint64 id;
        uint64 birthTime;
        address owner;
        uint64 weight;
        uint32 exp;
        uint32 power;
        uint32 speed;
        uint32 level;
        uint64 eatFruitTime;
    }
    // 总宠物数量
    uint64 public totalBirds = 0;
    // 盐值
    string salt = "forever_bird";
    // <tokenid:Bird>
    mapping(uint64 => Bird) internal birdsMap;
   
    // 生成基因序列
    function _generateGenes(uint64 _birthTime)
        internal 
        returns(uint64 id, bytes32 genes)
    {
        // 获取 tokenid
        uint64 _id = totalBirds + 1;
        totalBirds += 1;
        bytes32 _genes = keccak256(_birthTime, _id, salt);
        return (_id, _genes);
    }

    // 获取 5~15 的随机值
    function _getNumber(byte b)
    internal pure
    returns (uint8)
    {
        uint8 p = (uint8(b & 0x0f));
        if(p < 5) {
            p = p + 5;
        }
        return p;
    }

    // 创建 bird 对象
    function _createBird(address _ownerAddress)
    internal
	returns (uint64 id)
    {
        // 获取 tokenid
        uint64 _id;
        bytes32 _genes;
        uint64 _birthTime = (uint64)(now);
        (_id, _genes) = _generateGenes(_birthTime);
        Bird memory _bird = Bird({
            id : _id,
            owner: _ownerAddress,
            genes : _genes,
            birthTime : _birthTime,
            weight : 1000,
            exp : 0,
            power : _getNumber(_genes[0]),
            speed : _getNumber(_genes[1]),
            level : 0,
            eatFruitTime: 0
            });
        birdsMap[_id] = _bird;
		return _id;
    }

    // 测试创建 bird 对象
    function testCreateBird()
    public
    {
        _createBird(msg.sender);
    }
    // 查询 bird 数据,请参考上一章内容
    function getBirdInfo(uint64 _id)
    ... ...
}

根据 bird 基因控制展示

通过智能合约生成 128 位(32 字节)基因序列,业务后台需要根据这 32 字节生成独一无二的图片。基因如何控制 bird 展示呢?ForeverBird 的图片生成思路及准则如下:

  1. bird 划分固定数量部位,如形状、鸟冠、头部、翅膀、颜色等
  2. 每一个部位准备若干展示图案,所有的部位图案能够组合成一只完整的 bird
  3. 基因用于控制部位显示
  4. 每一个部位使用基因序列的特定位置控制,如基因序列的第一个字节控制形状图案选择,第二个字节控制鸟冠图案选择等
  5. 所有基因都能够影响 bird 显示

下面来依次分析以上思路及准则。

bird 划分固定数量部位

bird 包含很多部位,按照整体来分包含头部、上体、下体。其中头部可以细分顶冠、眼睛、顶纹、上嘴、下嘴等;上体可以细分为背、初级飞羽、次级飞羽、三级飞羽、翼尖等;下体可以细分为腿、爪、腹等。具体的 bird 部位说明如下图:

图片描述

ForeverBird 使用卡通的 bird 展示,简化了 bird 部位,使用固定体型,配置整体颜色、腹部、爪、冠、尾、翅膀、嘴和眼睛八个部位。

部位形成完整 bird

ForeverBird 中 bird 采用 svg 矢量图,矢量图具有结构简单、体积小、不失真等优点。svg 使用 XML 定义图像,格式文件易读取,可以使用编辑器编辑。为了实现部位替换,首先制作一个 bird 模板文件,设置 bird 形状,添加各个部位占位符。bird 模板文件 bird_base.svg 如下:

<svg viewBox="0 0 560 508" xmlns="http://www.w3.org/2000/svg">
    <g>
        <rect fill="none" width="560" height="508" />
		<ellipse opacity="0.1" cx="261" cy="443" rx="151.749" ry="43.289"/>
    </g>
  
	<g id="body">
        <defs>
            <clipPath id="shape">
			<path fill="none" d="..." />
            </clipPath>
        </defs>
        <g clip-path="url(#shape)">
            <rect fill="$bird_color" width="640" height="480" />
        </g>
    </g>
	<g id="fur">
		#bird_fur
	</g>		
	<g id="foot">
		#foot
	</g>
	<g id="crest">
		#crest
	</g>
	<g id="tail">
		#tail
	</g>
	<g id="wing">
		#wing
	</g>
	<g id="mouth">
		#mouth
	</g>
	<g id="eye">
		#eye
	</g>
</svg>

bird 模板中 #... 标识用于占位,生成图片逻辑中会替换为特定部位矢量图。

ForeverBird 项目中针对每个部位创建了一个文件夹,每个文件夹下保存了各个部位的 svg 图片,例如 tail 的 svg 图存放于文件夹 tail 中,每个文件命名为 tail_xx.svg,比如 tail_00.svg、tail_01.svg,_xx 表示图片序号。

基因用于控制部位显示

基因序列有 32 字节,ForeverBird 项目中基因控制 8 个部位属性,因此基因的每 4 字节控制一个部位展示,4 字节最多可以控制 2^32 种选择,最大总图片空间为(2^32)^8。ForeverBird 每个部位设置了 16 张图片,因此,基因序列的 4 字节用于筛选 16 张图,总图片空间 16^8
基因映射为具体部位 svg 的逻辑如下:

  • 基因序列分为 8 份,连续 4 字节控制一个部位展示,即 0~3, 4~7, …, 28~31
  • 4 字节基因映射到整数空间 [0,15] 作为序号,利用部位和序号作为图片索引
  • 使用 JSON 文件配置索引与图片路径映射
    根据以上逻辑可以通过基因序列映射到 8 个图片的路径,然后替换模板中占位符即可生成完整的bird 矢量图。

本节主要讲解了基因生成算法,以及基于基因生成属性和图片的思路,本章后续小节会给出讲解代码实现。

}
立即订阅 ¥ 49.90

你正在阅读课程试读内容,订阅后解锁课程全部内容

千学不如一看,千看不如一练

手机
阅读

扫一扫 手机阅读

区块链游戏 ForeverBird 开发实战
立即订阅 ¥ 49.90

举报

0/150
提交
取消
意见反馈 邀请有奖 帮助中心 APP下载
官方微信