该教程来自 CryptoZombies
网址:https://cryptozombies.io/zh/course/
CryptoZombies 是个在编游戏的过程中学习 Solidity 智能协议语言的互动教程。编游戏的同时学习以太坊的智能协议。关键是它免费。

僵尸工厂用于生产僵尸,每一个僵尸由头部基因、眼部基因、上衣基因、皮肤基因、眼色基因、衣服眼色基因组成,每个基因数值不一样,呈现的基因外形会有差异,通过这6个基因的组合,从而生产大量形状不一样的僵尸。

8356281049284737

这个数字的不同部分会对应僵尸的不同的特点,每个基因是都是由一个16位的整数组成。
例如:前两位数字是 83, 是计算僵尸的头型,我们做83 % 7 + 1 = 7 运算, 此僵尸将被赋予第七类头型。(求余7是因为程序只设计了7中头型)

1. 合约

从最基本的开始入手:
Solidity 的代码都包裹在合约里面. 一份合约就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点.

一份名为 HelloWorld 的空合约如下:

contract HelloWorld {}

版本指令
所有的 Solidity 源码都必须冠以 version pragma — 标明 Solidity 编译器的版本. 以避免将来新的编译器可能破坏你的代码。

例如: pragma solidity ^0.4.19; (当前 Solidity 的最新版本是 0.4.19).
综上所述, 下面就是一个最基本的合约 — 每次建立一个新的项目时的第一段代码:

pragma solidity ^0.4.19;contract HelloWorld {}

2. 状态变量和整数

我们已经为我们的合约做了一个外壳, 下面学习 Solidity 中如何使用变量。

状态变量是被永久地保存在合约中。也就是说它们被写入以太币区块链中. 想象成写入一个数据库。

例子:

contract Example {// 这个无符号整数将会永久的被保存在区块链中uint myUnsignedInteger = 100;
}

在上面的例子中,定义 myUnsignedInteger 为 uint 类型,并赋值100。

无符号整数: uint
uint 无符号数据类型, 指其值不能是负数,对于有符号的整数存在名为 int 的数据类型。

注: Solidity中, uint 实际上是 uint256代名词, 一个256位的无符号整数。你也可以定义位数少的uints — uint8, uint16, uint32, 等…… 但一般来讲你愿意使用简单的 uint, 除非在某些特殊情况下,这我们后面会讲。

3. 数学运算

在 Solidity 中,数学运算很直观明了,与其它程序设计语言相同:

  • 加法: x + y
  • 减法: x - y
  • 乘法: x * y
  • 除法: x / y
  • 求余: x % y (例如, 13 % 53, 因为13除以5,余3)
    Solidity 还支持 乘方操作 (如:x 的 y次方) // 例如: 5 ** 2 = 25
uint x = 5 ** 2; // equal to 5^2 = 25

4.结构体

有时你需要更复杂的数据类型,Solidity 提供了 结构体:

struct Person {uint age;string name;
}

结构体允许你生成一个更复杂的数据类型,它有多个属性。

注:我们刚刚引进了一个新类型, string。 字符串用于保存任意长度的 UTF-8 编码数据。 如:string greeting = "Hello world!"

5. 数组

如果你想建立一个集合,可以用 数组这样的数据类型. Solidity 支持两种数组: 静态数组动态数组:

// 固定长度为2的静态数组:
uint[2] fixedArray;
// 固定长度为5的string类型的静态数组:
string[5] stringArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;

你也可以建立一个 结构体类型的数组 例如,上面提到的Person:

Person[] people; // dynamic Array, we can keep adding to it

记住:状态变量被永久保存在区块链中。所以在你的合约中创建动态数组来保存成结构的数据是非常有意义的。

公共数组
你可以定义 public 数组, Solidity 会自动创建 getter 方法. 语法如下:

Person[] public people;

其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式。

6. 定义函数

在 Solidity 中函数定义的句法如下:

function eatHamburgers(string _name, uint _amount) {}

这是一个名为 eatHamburgers 的函数,它接受两个参数:一个 string类型的 和 一个 uint类型的。现在函数内部还是空的。

注:: 习惯上函数里的变量都是以(_)开头 (但不是硬性规定) 以区别全局变量。我们整个教程都会沿用这个习惯。

我们的函数定义如下:

eatHamburgers("vitalik", 100);

7. 使用结构体和数组

创建新的结构体
还记得上面的 Person 结构吗?

struct Person {uint age;string name;
}Person[] public people;

现在我们学习创建新的 Person 结构,然后把它加入到名为 people 的数组中.

// 创建一个新的Person:
Person satoshi = Person(172, "Satoshi");// 将新创建的satoshi添加进people数组:
people.push(satoshi);

你也可以两步并一步,用一行代码更简洁:

people.push(Person(16, "Vitalik"));
注:array.push() 在数组的 尾部 加入新元素 ,所以元素在数组中的顺序就是我们添加的顺序, 如:

uint[] numbers;
numbers.push(5);
numbers.push(10);
numbers.push(15);
// numbers is now equal to [5, 10, 15]

8. 私有 / 公共函数

Solidity 定义的函数的属性默认为公共。 这就意味着任何一方 (或其它合约) 都可以调用你合约里的函数。

显然,不是什么时候都需要这样,而且这样的合约易于受到攻击。 所以将自己的函数定义为私有是一个好的编程习惯,只有当你需要外部世界调用它时才将它设置为公共

如何定义一个私有的函数呢?

uint[] numbers;function _addToArray(uint _number) private {numbers.push(_number);
}

这意味着只有我们合约中的其它函数才能够调用这个函数,给 numbers 数组添加新成员。

可以看到,在函数名字后面使用关键字 private 即可。和函数的参数类似,私有函数的名字用(_)起始。

9. 函数的更多属性

返回值

要想函数返回一个数值,按如下定义:

string greeting = "What's up dog";function sayHello() public returns (string) {return greeting;
}

Solidity 里,函数的定义里可包含返回值的数据类型(如本例中 string)。

函数的修饰符

上面的函数实际上没有改变 Solidity 里的状态,即,它没有改变任何值或者写任何东西。

这种情况下我们可以把函数定义为 view, 意味着它只能读取数据不能更改数据:

function sayHello() public view returns (string) {}

Solidity 还支持pure 函数, 表明这个函数甚至都不访问应用里的数据,例如:

function _multiply(uint a, uint b) private pure returns (uint) {return a * b;
}

这个函数甚至都不读取应用里的状态 — 它的返回值完全取决于它的输入参数,在这种情况下我们把函数定义为 pure.

注:可能很难记住何时把函数标记为 pure/view。 幸运的是, Solidity 编辑器会给出提示,提醒你使用这些修饰符。

10. Keccak256 和 类型转换

如何让 _generateRandomDna 函数返回一个全(半) 随机的 uint?

Ethereum 内部有一个散列函数keccak256,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。

这在 Ethereum 中有很多应用,但是现在我们只是用它造一个伪随机数。

例子:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaab");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256("aaaac");

显而易见,输入字符串只改变了一个字母,输出就已经天壤之别了。

注: 在区块链中安全地产生一个随机数是一个很难的问题, 本例的方法不安全,但是在我们的Zombie DNA算法里不是那么重要,已经很好地满足我们的需要了。

类型转换

有时你需要变换数据类型。例如:

uint8 a = 5;
uint b = 6;
// 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 我们需要将 b 转换为 uint8:
uint8 c = a * uint8(b);

上面, a * b 返回类型是 uint, 但是当我们尝试用 uint8 类型接收时, 就会造成潜在的错误。如果把它的数据类型转换为 uint8, 就可以了,编译器也不会出错。

11. 事件

我们的合约几乎就要完成了!让我们加上一个事件.

事件 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。

例子:

// 这里建立事件
event IntegersAdded(uint x, uint y, uint result);function add(uint _x, uint _y) public {uint result = _x + _y;//触发事件,通知appIntegersAdded(_x, _y, result);return result;
}

你的 app 前端可以监听这个事件。JavaScript 实现如下:

YourContract.IntegersAdded(function(error, result) { // 干些事
}

这里介绍了Solidity的一部分基础语法,如果要掌握更多的Solidity基础知识,可以参考区块链技术-智能合约Solidity编程语言。

下面就将本节教程 搭建僵尸工厂 的完整代码以及言语解释写下。
这里应用了上面介绍的所有Solidity语法知识。

pragma solidity ^0.4.19;
contract ZombieFactory {event NewZombie(uint zombieId, string name, uint dna);uint dnaDigits = 16;uint dnaModulus = 10 ** dnaDigits;struct Zombie {string name;uint dna;}Zombie[] public zombies;function _createZombie(string _name, uint _dna) private {uint id = zombies.push(Zombie(_name, _dna)) - 1;NewZombie(id, _name, _dna);}function _generateRandomDna(string _str) private view returns (uint) {uint rand = uint(keccak256(_str));return rand % dnaModulus;}function createRandomZombie(string _name) public {uint randDna = _generateRandomDna(_name);_createZombie(_name, randDna);}}

这段合约代码是一个僵尸工厂。
咱从最开始解读这个合约。
1. 第一行,这个合约基于0.4.19这个版本的编译器
2. 第二行,建立一个合约 ZombieFactory
3. 第四行,定义一个 事件 叫做 NewZombie。 它有3个参数:zombieId(uint), name (string), 和 dna (uint)。
4. 第六行,僵尸DNA将由一个十六位数字组成,所以定义 dnaDigitsuint 数据类型, 并赋值 16。
5. 第七行,为了保证我们的僵尸的DNA只含有16个字符,我们先造一个uint数据,让它等于10^16。这样一来以后我们可以用模运算符 % 把一个整数变成16位。所以,建立一个uint类型的变量,名字叫dnaModulus, 令其等于 10dnaDigits 次方.
6. 第九行至第十二行,在我们的程序中,我们将创建一些僵尸!每个僵尸将拥有多个属性,所以建立一个struct 命名为 Zombie,里面包含了两个属性name (类型为string), 和 dna (类型为uint)。
7. 第十四行,为了把一个僵尸部队保存在我们的APP里,并且能够让其它APP看到这些僵尸,我们创建一个数据类型为 Zombie 的结构体数组,用 public 修饰,命名为:zombies.
8. 第十六行,建立一个私有函数 _createZombie。 它有两个参数:_name (类型为string), 和_dna (类型为uint)
9. 第十七行,在函数体里新创建一个 Zombie, 然后把它加入 zombies 数组中。 新创建的僵尸的 namedna,来自于函数的参数。加入数组后,因为array.push() 返回数组的长度,类型是uint。所以可以把它当做 zombie 的 id或者索引
10. 第十八行,生成事件NewZombie,并将 zombie 的id ,name,dna传送出去。
11. 第二十一行,我们需要建立一个帮助函数,它根据一个字符串随机生成一个DNA数据。所以,创建一个 private 函数,命名为 _generateRandomDna。它只接收一个输入变量 _str (类型 string), 返回一个 uint 类型的数值。此函数只读取我们合约中的一些变量,所以标记为view。
12. 第二十二行,取 _strkeccak256 散列值生成一个伪随机十六进制数,类型转换为 uint, 最后保存在类型为 uint 名为 rand 的变量中。
13. 第二十三行,我们只想让我们的DNA的长度为16位 (还记得 dnaModulus?)。所以第二行代码应该 return 上面计算的数值对 dnaModulus 求余数(%)
14. 第二十六行,创建一个 public 函数,命名为createRandomZombie. 它将被传入一个变量 _name (数据类型是 string)。
15. 第二十七行,调用_generateRandomDna 函数,传入 _name 参数, 结果保存在一个类型为 uint的变量里,命名为 randDna
16. 第二十八行,调用_createZombie 函数, 传入参数:_namerandDna

我们的 Solidity 合约完工了! 现在我们要写一段 JavaScript 前端代码来调用这个合约。

以太坊有一个 JavaScript 库,名为Web3.js

在后面的课程里,我们会进一步地教你如何安装一个合约,如何设置Web3.js。 但是现在我们通过一段代码来了解 Web3.js 是如何和我们发布的合约交互的吧。

如果下面的代码你不能全都理解,不用担心。

// 下面是调用合约的方式:
var abi = /* abi是由编译器生成的 */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 发布之后在以太坊上生成的合约地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 能访问公共的函数以及事件// 某个监听文本输入的监听器:
$("#ourButton").click(function(e) {var name = $("#nameInput").val()//调用合约的 `createRandomZombie` 函数:ZombieFactory.createRandomZombie(name)
})// 监听 `NewZombie` 事件, 并且更新UI
var event = ZombieFactory.NewZombie(function(error, result) {if (error) returngenerateZombie(result.zombieId, result.name, result.dna)
})// 获取 Zombie 的 dna, 更新图像
function generateZombie(id, name, dna) {let dnaStr = String(dna)// 如果dna少于16位,在它前面用0补上while (dnaStr.length < 16)dnaStr = "0" + dnaStrlet zombieDetails = {// 前两位数构成头部.我们可能有7种头部, 所以 % 7// 得到的数在0-6,再加上1,数的范围变成1-7// 通过这样计算:headChoice: dnaStr.substring(0, 2) % 7 + 1,// 我们得到的图片名称从head1.png 到 head7.png// 接下来的两位数构成眼睛, 眼睛变化就对11取模:eyeChoice: dnaStr.substring(2, 4) % 11 + 1,// 再接下来的两位数构成衣服,衣服变化就对6取模:shirtChoice: dnaStr.substring(4, 6) % 6 + 1,//最后6位控制颜色. 用css选择器: hue-rotate来更新// 360度:skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),zombieName: name,zombieDescription: "A Level 1 CryptoZombie",}return zombieDetails
}

我们的 JavaScript 所做的就是获取由zombieDetails 产生的数据, 并且利用浏览器里的 JavaScript 神奇功能 (我们用 Vue.js),置换出图像以及使用CSS过滤器。在后面的课程中,你可以看到全部的代码。

编写区块链游戏学智能合约 教程1:搭建僵尸工厂相关推荐

  1. 编写区块链游戏学智能合约 教程2:僵尸攻击人类

    该教程来自 CryptoZombies 网址:https://cryptozombies.io/zh/course/ CryptoZombies 是个在编游戏的过程中学习 Solidity 智能协议语 ...

  2. 区块链游戏FOMO3D智能合约核心分析

    最近做一个区块链的项目,需要彻底分析FOMO3D的智能合约,顺便熟悉一下区块链的开发流程. 首先为了能跑FOMO3D的智能合约我尝试了truffle+galanche,对我来说不太理想,我就自己用py ...

  3. 分享实录|区块链技术与智能合约入门(开发实例)

    2019独角兽企业重金招聘Python工程师标准>>> 1 什么是区块链 1.1白话讲解区块链 现在区块链特别火,可能大家都听说过区块链,听说过比特币,那到底什么是区块链? 前几天和 ...

  4. 【区块链DAPP】智能合约概述

    智能合约概述 智能合约是运行在区块链公链上的一种代码,该代码由Solidity编写,并通过区块链的智能合约虚拟机来执行,以达到对区块链编程的目标.可以将区块链公联理解为操作系统,Solidity是编写 ...

  5. 区块链中的智能合约是什么?

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. "智能合约是一套以数字形式定义的承诺,承诺控制着数字资产并包含了合约参与者约定的权利和义务,由计算机系统自动执 ...

  6. 行走在区块链上的智能合约

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 我和你打一个赌,我赌明天是雨天,你赌是晴天,赌注100大洋.假设明天是晴天,然后你跑过来管我要100大洋的赌金,我装疯卖 ...

  7. 区块链中的智能合约(Smart Contract)

    1994年,法律学者.密码学家Nick Szabo认识到智能合约的去中心化分类账的应用.他理论上认为,这些合同可以用代码编写,可以在系统上存储和复制,并由构成区块链的计算机网络进行监督.这些智能合约也 ...

  8. 区块链 Fisco bcos 智能合约(19)-区块链性能腾飞:基于DAG的并行交易执行引擎PTE

    在区块链世界中,交易是组成事务的基本单元. 交易吞吐量很大程度上能限制或拓宽区块链业务的适用场景,愈高的吞吐量,意味着区块链能够支持愈广的适用范围和愈大的用户规模. 当前,反映交易吞吐量的TPS(Tr ...

  9. 区块链中的“智能合约”有何应用?

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 如刺金般闪耀的区块链时代,投资者的热潮还将持续升温,与此同时金融的大佬已经开始注意到区块链应用落地场景的实现,在金融界实 ...

  10. tron区块链php对接,Tron区块链技术 - Tron智能合约概述

    Tron区块链技术:多年来,  以太坊  一直是分散世界中开发智能合约的主流平台之一.然而,最近TRON作为一个准备面对以太坊的竞争平台在分散网络中崛起. TRON区块链技术是什么? Tron区块链是 ...

最新文章

  1. “搞垮” 微博服务器?每天上亿条用户推送是如何做到的
  2. c语言gcno文件位置,makefile - 具有覆盖率的CMake Ninja将gcno文件放在根二进制目录中 - 堆栈内存溢出...
  3. 解决 Iframe跨域session 丢失问题
  4. java并发 设计模式,Java并发——设计模式
  5. 【错误记录】应用运行 CPU 占用率达到 90% ( 使用 CPU Profiler 监控应用运行情况 )
  6. TCP对应的应用层协议之FTP /SMTP / HTTP
  7. RTSP播放器开发过程中需要考虑哪些关键因素
  8. Docker如何删除一个镜像
  9. 在Ubuntu上安装mongoDB
  10. 图像同态滤波 python实现_8图像增强
  11. 《工业设计史》第十章:战后重建与设计
  12. GoodUP:智协云店通+BitCOO的4WiN全球互贸链 | 翼次元空间
  13. 涉密计算机怎么更新补丁,当需要将病毒库、系统补丁程序等导入到涉密信息系统时采用什么方式...
  14. 格律诗的基本知识【一小时学会写格律诗】
  15. 计算几何(二) by邓俊辉老师
  16. 计算机语言有几进制,一个字节由几个二进制位组成(计算机系统有什么两部分组成)...
  17. 为自己选一个网络硬盘
  18. 从写下第1行代码到拿下谷歌百万年薪 ,我是如何在8个月内做到的?
  19. 如何禁用计算机的网络适配器,怎么禁用网络适配器?禁用和卸载网络适配器的步骤...
  20. 分类评价指标 F值 详解 | Micro F1 Macro F1 Weight F1

热门文章

  1. Unity资源加载以及释放
  2. QTP(Quick Test Professional)安装详细教程
  3. mysql 判断语句_mysql条件判断语句讲解
  4. 开源网店系统Javashop 发布3.0beta版
  5. 一元线性回归及案例(Python)
  6. DJ-ZBS2漏电继电器
  7. viper4android安装方法,安卓音效神器ViPER4Android_FX安装教程
  8. 014游移方位惯导系统力学编排公式推导
  9. word字体放大后只显示一半_word字体显示不全或是显示一半怎么回事如何解决
  10. VoxelMorph运行时遇到的问题