来源 | Medium

作者 | Sam Benemerito

编译 | 王国玺

出品 | 区块链大本营(blockchain_camp)

不知道多少人和营长一样,在结束一天的工作后,就想瘫在沙发上玩玩游戏,一扫一天的疲惫。不过现在的游戏都越来越氪金了,就像玩家们常说的:不充钱你怎么能变强!!!

但要说氪金,很少有哪个游戏能比得上区块链游戏,因为区块链上的游戏都建立在一笔笔真实的加密货币交易之上。

就拿最火的去中心化游戏加密猫来说,其刚上线时一只加密猫能卖到上千元人民币,最贵的一只竟卖到了 125673 个以太币,在当时约合人民币 2.6 亿元(太可怕了)。

当时最贵的一只加密猫

不禁让人直呼:玩不起,玩不起。

玩不起又想玩怎么办?

手把手开发一款区块链游戏,不就OK了,何况又那么简单,10分钟就够了

这是一款怎样的 DApp?

从现在开始,我们将构建一个类似于加密猫的简单 DApp,我们称之为加密毒蛇

你可能会问为什么是蛇呢,但为什么不能是蛇呢?Python 翻译过来也是蛇呀。

在这个 DApp 中,你可以购买毒蛇和养殖毒蛇,应用页面是这样的:

加密毒蛇程序的页面

其功能列表如下:

  • 创造毒蛇 – 用户能够通过购买或让两条毒蛇交配来创造新的毒蛇。

  • 查看毒蛇的详细信息 – 用户应该能够查看自己拥有的毒蛇的详细信息并进行显示。

创建加密毒蛇,你至少需要用到以下工具

  • 智能合约相关:

  • 智能合约编程语言 Solidity

  • 开源智能合约开发环境 Remix

  • 用于调试和测试智能合约的 Metamask

  • 前端技术相关:

  • 用于与本地以太坊节点进行通信的 js 库 Web3.js

  • 构建用户界面的渐进式框架 Vue.js

  • 用于创建 vue 项目的 Vue-cli

  • 前端框架 Boostrap-vue

此外,你还需要使用 Git 来克隆一些代码模板。

在进行编程之前,这里先假定你已经对以下的知识有了基本的了解。

  • 了解智能合约及其编程语言 Solidity 。

  • 了解使用 MetaMask 进行智能合约的调试和测试。

  • 了解使用 Remix IDE 进行智能合约的编译和部署。

当然了,如果你对这一些知识还有欠缺,也不用担心,本文会详细介绍每一个细节。

深入了解 ERC-721 标准

ERC-721 是一个用来描述如何在以太坊区块链上制造不可替代 token 的标准。满足 ERC-721 标准的每个 token 或其他数字资产都是唯一的,这意味着它不等同于任何其他 token。你可以把它们想象成一种特殊的,独一无二的收藏品。(如果你已了解 ERC-721,可选择跳过这一部分

ERC-721 标准的接口

根据标准的官方文档,ERC-721 规定了智能合约用以管理、持有和交易唯一 token 必须实现的最小接口:

  • balanceOf(_owner) - 返回特定所有者 _owner 钱包中的 token 余额。

  • ownerOf(_tokenId) - 返回特定 token 所有者的钱包地址。

  • totalSupply() - 返回创建的 token 总数。

  • transfer(_to,_tokenId) - 将 _tokenId 标记的 token 从发送者的钱包转移到指定接收者的钱包。

  • takeOwnership(_tokenId) - 声明给定 ID 的 token 的所有权。

  • approve(_to,_tokenId) - 批准另一个地址来声明给定 ID 的 token 所有权。

此外,它还定义了两个事件:Transfer 和 Approval。当 token 从一个钱包转移到另一个钱包时,会触发转移事件。另一方面,当一个账户批准另一个账户声明他拥有的某个 token 的所有权时,会触发批准事件。

OpenZeppelin 的 ERC-721 token 实现

OpenZeppelin 是一款开源智能合约开发框架,它提供了可重复使用的智能合约模板,包括 ERC-20 和 ERC-721 等标准 token 的实现模板。在这里,我们将导入他们的 ERC-721 token 实现,这样我们就不必从头开始编写它,避免了重复造轮子。

创建智能合约

此 DApp 是基于以太坊平台开发,所以需要使用 Solidity 语言发行 token。

首先,在开源智能合约开发环境 Remix 中,创建一个名为 ViperToken.sol 的新文件并在其中添加以下代码:

1// We will be using Solidity version 0.5.3
2pragma solidity 0.5.3;
3// Importing OpenZeppelin's ERC-721 Implementation
4import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol";
5// Importing OpenZeppelin's SafeMath Implementation
6import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/math/SafeMath.sol";
7
8
9contract ViperToken is ERC721Full {
10    using SafeMath for uint256;
11    // This struct will be used to represent one viper
12    struct Viper {
13        uint8 genes;
14        uint256 matronId;
15        uint256 sireId;
16    }
17
18    // List of existing vipers
19    Viper[] public vipers;
20
21    // Event that will be emitted whenever a new viper is created
22    event Birth(
23        address owner,
24        uint256 viperId,
25        uint256 matronId,
26        uint256 sireId,
27        uint8 genes
28    );
29
30    // Initializing an ERC-721 Token named 'Vipers' with a symbol 'VPR'
31    constructor() ERC721Full("Vipers", "VPR") public {
32    }
33
34    // Fallback function
35    function() external payable {
36    }
37
38    /** @dev Function to determine a viper's characteristics.
39      * @param matron ID of viper's matron (one parent)
40      * @param sire ID of viper's sire (other parent)
41      * @return The viper's genes in the form of uint8
42      */
43    function generateViperGenes(
44        uint256 matron,
45        uint256 sire
46    )
47        internal
48        pure
49        returns (uint8)
50    {
51        return uint8(matron.add(sire)) % 6 + 1;
52    }
53
54    /** @dev Function to create a new viper
55      * @param matron ID of new viper's matron (one parent)
56      * @param sire ID of new viper's sire (other parent)
57      * @param viperOwner Address of new viper's owner
58      * @return The new viper's ID
59      */
60    function createViper(
61        uint256 matron,
62        uint256 sire,
63        address viperOwner
64    )
65        internal
66        returns (uint)
67    {
68        require(viperOwner != address(0));
69        uint8 newGenes = generateViperGenes(matron, sire);
70        Viper memory newViper = Viper({
71            genes: newGenes,
72            matronId: matron,
73            sireId: sire
74        });
75        uint256 newViperId = vipers.push(newViper).sub(1);
76        super._mint(viperOwner, newViperId);
77        emit Birth(
78            viperOwner,
79            newViperId,
80            newViper.matronId,
81            newViper.sireId,
82            newViper.genes
83        );
84        return newViperId;
85    }
86
87    /** @dev Function to allow user to buy a new viper (calls createViper())
88      * @return The new viper's ID
89      */
90    function buyViper() external payable returns (uint256) {
91        require(msg.value == 0.02 ether);
92        return createViper(0, 0, msg.sender);
93    }
94
95    /** @dev Function to breed 2 vipers to create a new one
96      * @param matronId ID of new viper's matron (one parent)
97      * @param sireId ID of new viper's sire (other parent)
98      * @return The new viper's ID
99      */
100    function breedVipers(uint256 matronId, uint256 sireId) external payable returns (uint256) {
101        require(msg.value == 0.05 ether);
102        return createViper(matronId, sireId, msg.sender);
103    }
104
105    /** @dev Function to retrieve a specific viper's details.
106      * @param viperId ID of the viper who's details will be retrieved
107      * @return An array, [viper's ID, viper's genes, matron's ID, sire's ID]
108      */
109    function getViperDetails(uint256 viperId) external view returns (uint256, uint8, uint256, uint256) {
110        Viper storage viper = vipers[viperId];
111        return (viperId, viper.genes, viper.matronId, viper.sireId);
112    }
113
114    /** @dev Function to get a list of owned vipers' IDs
115      * @return A uint array which contains IDs of all owned vipers
116      */
117    function ownedVipers() external view returns(uint256[] memory) {
118        uint256 viperCount = balanceOf(msg.sender);
119        if (viperCount == 0) {
120            return new uint256[](0);
121        } else {
122            uint256[] memory result = new uint256[](viperCount);
123            uint256 totalVipers = vipers.length;
124            uint256 resultIndex = 0;
125            uint256 viperId = 0;
126            while (viperId < totalVipers) {
127                if (ownerOf(viperId) == msg.sender) {
128                    result[resultIndex] = viperId;
129                    resultIndex = resultIndex.add(1);
130                }
131                viperId = viperId.add(1);
132            }
133            return result;
134        }
135    }
136}

由 GitHub 托管的 ViperToken.sol

在上面的代码中,我们套用了 OpenZeppelin 的 ERC-721 token 实现,并在其中添加了自定义函数。此外,还在代码中导入了 SafeMath 库以避免出现整数的上溢和下溢漏洞(去年四月份,美图的 BEC token 就因为存在整数溢出漏洞,一夜间被黑客盗取了64亿)。

接下来,详细介绍一下代码的功能:

首先,函数 generateViperGenes 用来确定新出生毒蛇的基因,主要用在两条毒蛇繁育新毒蛇时(结合其双亲的基因)。我们选择了一种非常简单的实现方式,在这种情况下,我们使用1到6这几个简单的数字来确定新出生毒蛇的长相。当然了,你也可以修改代码,创造出更多独具特色的毒蛇。

然后,我们实现了一个 createViper 函数,这个函数用来创建一条新的毒蛇,并通过传入函数的参数“ viperOwner ”指定该新创建毒蛇的所有者。

函数 buyViper 是一个可以接受以太币付款的函数( payable function ),函数中调用了我们之前定义的 createViper 函数。Payable 这个标记是一个修饰符,用于指出此函数在执行时可以接收以太币付款。相信你能从这行明摆着就是要钱的代码中看出来:

1require(msg.value == 0.02 ether);

代码中的 require 语句会检查是否满足给定的条件,并在不满足条件时报错。在 Solidity 语言中,msg.value 语句用来存储调用该函数的用户发送的以太币数量。

在上面的代码中,我们要求用户支付 0.02 个以太币来购买一条随机分配的毒蛇。在这里,你就是上帝,你可以随意修改这个金额,如果你愿意的话,甚至还可以删除这条语句,这样每个用户都可以访问这个函数来免费得到一条毒蛇。

函数 breedVipers 在调用时代价更为昂贵,它需要用户支付 0.05 个以太币,不过这并不是为了蒙骗小白,因为该函数允许用户根据两条毒蛇父母的基因创建一条新的毒蛇,也就是说可以让用户得到一条高身价的毒蛇。

当然了,我们还需要定义一个能够查看毒蛇详细信息的函数。函数 getViperDetails 在被调用时会返回给定毒蛇的基因和父母 ID 之类的细节。

最后是一个 ownVipers 函数,该函数可以返回调用者拥有的毒蛇的 ID 列表。具体的实现是通过 msg.sender 这个语句,它用来标记调用函数的用户的地址。

现在,所有的函数都已定义完毕,接下来,我们需要编译 ViperToken 智能合约,这里需要注意的是,请在 Remix 界面的右侧选择编译器的版本,指定版本为 0.5.3 + commit.10d17f24 ,因为我们使用的是 0.5.3 版本的 Solidity 语言,完成后我们就可以将智能合约部署到以太坊 Ropsten 测试网络上了。

这里我们编译并部署了 ViperToken 智能合约,由于操作步骤较多,你最好检查一下每一步是否都已正确完成。

如果所有的步骤都没有出错,智能合约得到了正确的部署,你会看到如下页面:

智能合约编译成功的页面


智能合约已被成功部署并加入到“已部署智能合约”中

构建 Web 应用程序

到这里,智能合约就可以正常运行了,但如果没有一个与之交互的 Web 应用程序,那么运行起来就只能看到一些数字。为了让这个 DApp 更有意思,接下来营长带你制作一个简单的 Web 应用程序。

配置开发环境

构建 Web 应用程序首先需要配置开发环境,在这里废话不多说,让我们直接切入正题。让我们在终端(或者Windows中的命令提示符/ Powershell)中执行以下操作来克隆 GitHub 上的代码模板:

1# Cloning the boilerplate from GitHub
2git clone -b boilerplate --single-branch https://github.com/openberry-ac/cryptovipers.git
3# Navigating to the directory and installing packages
4cd cryptovipers
5npm install
6# Installing Web3
7npm install -s web3@1.0.0-beta.37
8# To run the app
9npm run dev

克隆 GitHub 上的代码模板

这可能需要几分钟的执行时间,操作完成后,你的 Web 应用程序就运行起来了。

你可以在浏览器中访问 http:// localhost:8080 看到它,应用程序界面长这样:

加密毒蛇的页面

连接到智能合约实例

为了使我们的 Web 应用程序能够与先前部署的智能合约进行交互,这里我们会用到与本地以太坊节点进行通信的 js 库 web3.js 。在配置开发环境时我们已经安装好了软件包,现在我们要在“ contracts”文件夹下名为 web3.js 的文件中加入如下代码来调用它:

1import Web3 from 'web3';
2
3const getWeb3 = () => new Promise((resolve) => {
4  window.addEventListener('load', () => {
5    let currentWeb3;
6
7    if (window.ethereum) {
8      currentWeb3 = new Web3(window.ethereum);
9      try {
10        // Request account access if needed
11        window.ethereum.enable();
12        // Acccounts now exposed
13        resolve(currentWeb3);
14      } catch (error) {
15        // User denied account access...
16        alert('Please allow access for the app to work');
17      }
18    } else if (window.web3) {
19      window.web3 = new Web3(web3.currentProvider);
20      // Acccounts always exposed
21      resolve(currentWeb3);
22    } else {
23      console.log('Non-Ethereum browser detected. You should consider trying MetaMask!');
24    }
25  });
26});
27
28export default getWeb3;

由 GitHub 托管的 web3.js

上面的代码加载了 MetaMask 浏览器扩展初始化的 web3 实例,稍后我们将用它来和先前部署的智能合约进行交互。

在这个过程中你可能会遇到 MetaMask 的弹出窗口,它会要求获取访问权限。这是因为我们在代码中加入了 ethereum.enable(),所以在 Web 应用程序请求访问帐户时窗口就会自动弹出,这时你只需要像下图一样点击“Connect”按钮即可:

MetaMask 的弹出窗口

现在,我们需要让这个 Web 应用程序连接到智能合约的 ABI (Application Binary Interface,应用程序二进制接口)上。要获取 ABI 我们需要返回到 Remix 的界面,转到 Compile 选项卡,然后单击 Details 按钮旁边的 ABI 按钮,具体操作如下图所示:

点击 ABI 按钮来复制智能合约的 ABI

获取了 ABI 后,在 contract 文件夹下打开名为 abi.js 的文件,然后将 ABI 粘贴为变量 contractAbi 的值,如下所示:

1const contractAbi = // PASTE ABI CODE HERE
2
3export default contractAbi;

由 GitHub 托管的 abi.js

文件中应该会有一个示例代码,遇到问题时你可以随时参考它。然后,我们还必须指定智能合约实例的地址,你需要回到 Remix 界面的 Deploy 选项卡,然后单击已部署智能合约上的复制图标,具体操作如下图所示:

点击“复制”按钮复制智能合约实例地址

在 src 文件夹下找到的 App.vue ,打开并将你的智能合约地址粘贴到第 86 行,作为变量 contractAddress 的值(文件中也应该有注释,你可以随时参考这些注释):

1const contractAddress = ''; // Right here!
2// Ex: const contractAddress = '0xf59c4c3c79071d3e11034a9344789bd3';

定义实现的方法

现在你可能会注意到用户界面一切都正常,但点击按钮却不起任何作用。这是因为我们还没有定义 Web 应用程序的功能,接下来我们就开动。首先返回到 App.vue 文件中,转到第 116 行,你可以在其中查到所有的方法,但所有方法的实现都只包含一个输出方法名的 console.log()。

Web 应用程序的第一个功能是购买毒蛇。让我们用以下代码修改 buyViper()方法:

1buyViper() {
2  this.isLoading = true;
3  this.contractInstance.methods.buyViper().send({
4    from: this.account,
5    value: web3.toWei(0.02, 'ether'),
6  }).then((receipt) => {
7    this.addViperFromReceipt(receipt);
8    this.isLoading = false;
9  }).catch((err) => {
10    console.log(err, 'err');
11    this.isLoading = false;
12  });
13},

由 GitHub 托管的 App.vue

还记得我们在智能合约中的规定么?购买一条毒蛇我们需要收取 0.02 个以太币的费用,因此在代码中我们需要同时发送我们的帐户信息和 0.02 个以太币来进行支付。然后我们就可以调用智能合约中的 buyViper()函数,该函数会返回新生成毒蛇的详细信息,紧接着我们将这些细节保存在 vipers 数组中。

接下来,修改繁殖毒蛇的功能 breedVipers():

1breedVipers() {
2  this.isLoading = true;
3  this.contractInstance.methods.breedVipers(this.matron, this.sire).send({
4    from: this.account,
5    value: web3.toWei(0.05, 'ether'),
6  }).then((receipt) => {
7    this.addViperFromReceipt(receipt);
8    this.isLoading = false;
9  }).catch((err) => {
10    console.log(err, 'err');
11    this.isLoading = false;
12  });
13},

由 GitHub 托管的 App.vue

在智能合约中,我们规定了用户需要支付 0.05 个以太币才能访问繁殖毒蛇的函数,同时,在调用 breedVipers 函数时需要传入两个整数参数, matron (该新出生毒蛇的母亲)和 sire (父亲),所以在上面的代码中我们将两个整数参数传递给函数。这个函数在执行后会返回新出生毒蛇的详细信息,我们需要将其保存到 vipers 数组中。

最后,定义检索自己拥有毒蛇的详细信息的方法,称为 getVipers():

1getVipers() {
2  this.isLoading = true;
3  this.contractInstance.methods.ownedVipers().call({
4    from: this.account,
5  }).then((receipt) => {
6    for (let i = 0; i < receipt.length; i += 1) {
7      this.contractInstance.methods.getViperDetails(receipt[i]).call({
8        from: this.account,
9      }).then((viper) => {
10        this.vipers.push({
11          id: viper[0],
12          genes: viper[1],
13          matron: viper[2],
14          sire: viper[3],
15          url: vipersMap[viper[1]],
16        });
17      }).catch((err) => {
18        console.log(err, 'err');
19      });
20    }
21    this.isLoading = false;
22  }).catch((err) => {
23    console.log(err, 'err');
24    this.isLoading = false;
25  });
26},

由 GitHub 托管的 App.vue

想要获取你所拥有毒蛇的详细信息,你可以采用以下两种方法:ownedVipers 方法和 getViperDetails()方法。第一种方法能获取存有我们毒蛇详细信息的数组,而第二方法能获取每条毒蛇的详细信息。在获取每条毒蛇的详细信息后,我们将其保存到 vipers 数组中。

恭喜你,完成了所有的开发工作!

加密毒蛇游戏的玩法示范:花费 0.02 个以太币购买一条新的毒蛇(编号为16 ),然后让编号为15和编号为16的毒蛇繁殖一条毒蛇宝宝(编号为17),恭喜你,你做到了!

写在最后

刚刚我们开发了一个类似于加密猫的 DApp!同时,也学习了如何创建自己的 ERC-721 标准实现,并定义了 DApp 的自定义函数,学习了如何使用 Vue.js 开发项目,并创建了一个简单的 Web 应用程序。

如果你觉得这样太简单了,还想挑战一下自己,这是我的一点建议:

首先,你可以锻炼一下自己的 token 开发能力,在本文中我们直接使用了现成的 ERC-721 token 代码,你可以试着自己从头来开始编写,在这个过程中你可以参考开源智能合约开发框架 OpenZeppelin 的实现或以太坊 ERC-721 token 的标准规范。

或者,你可能试着扩展一下我们已经完成的工作,在这个去中心化应用程序中添加更多的功能,就比如说加入与其他用户进行毒蛇交易的功能,这也是一个不错的主意。

老铁们,学到干货了吗?

 

报名 | EOS智能合约与数据库开发

16岁保送北大、麻省理工博士、

EOS黑客松全球总决赛前三名

5月8日晚,精彩技术公开课与您不见不散!

推荐阅读:

  • 从BSV下架风波, 揭秘构造区块链分叉的测试方案 | 技术头条

  • 太可怕了! 五一外出还敢连Wi-Fi ?

  • 精华篇 | 王嘉平:突破不可能三角「异步共识组Monoxide」(附PPT)

  • 20k~80k,蚂蚁金服等大厂招人啦!想赶上这波人才荒,你要掌握这些...

  • 《互联网人叹气图鉴》

  • 硬核粉丝 | 清华双胞胎“YCY Dance Now”杀进超越杯编程大赛决赛

  • 异构计算=未来?一文带你秒懂3大主流异构

  • 他说:当一个程序员决定告别996,什么都有可能发生!

老铁在看了吗??

服! 买不起2.6亿一只的加密猫, 他用10分钟生了一窝!相关推荐

  1. 美国夫妇用数学算法买彩票赢1.74亿元——网友:现在学数学还来得及吗?

    用数学算法买彩票赢1.74亿元.一男子发现了彩票的漏洞,十几年来他一共获利2700万美元(约合人民币1.74亿元).官方经过调查以后表示这是合法有效的. 美国一位老爷爷曾是数学系学生,他偶然间看到一份 ...

  2. 互联网晚报 | 8月25日 星期三 | 拼多多年度活跃买家数达8.5亿;钉钉推出首个数字工牌产品“钉工牌”;小度科技完成B轮融资...

    今日看点 ✦ 百度旗下小度科技完成B轮融资,投后估值51亿美元 ✦ vivo总投资49.28亿元建智慧终端总部,人才房已开始动工 ✦ 钉钉推出首个数字工牌产品"钉工牌",整合工作身 ...

  3. 蚂蚁金服ATEC技术探索大会造访伦敦,只为讲好一个普惠金融愿景

    小蚂蚁说: 英国当地时间6月11日,一年一度为期一周的伦敦科技周(London Tech Week)正式拉开帷幕.作为伦敦科技周的重要组成部分,蚂蚁金服ATEC技术探索大会在伦敦科技周首日在威斯敏斯特 ...

  4. 腾讯宣布捐赠1亿元驰援河南;苹果回应iPhone 安全隐患;贝索斯完成10分钟太空之旅|极客头条...

    「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧. 整理 | 梦依丹 出品 | CSDN(ID:CSDNnews ...

  5. 极客日报:腾讯宣布捐赠1亿元驰援河南;苹果回应iPhone 安全隐患;贝索斯完成10分钟太空之旅

    一分钟速览新闻点! "搜狗输入法"重新上架各大安卓应用商店 腾讯宣布向河南捐款1亿元 用于采购救灾物质 华为注册自动驾驶 Autoware 商标 微信内测客服功能 腾讯起诉山寨微信 ...

  6. 亿级流量系统多级缓存架构10 阿里云实战

    亿级流量系统多级缓存架构10 阿里云实战 域名购买解析 域名的价值 域名的价值就像商标一样,体现在你注册一个域名之后,别人就不可以再注册这个域名了,域名的稀缺性就产生了.域名可以买卖,一个域名我注册了 ...

  7. echart 饼图每一块间隙_花10分钟做酱香饼,简单方便又美味,酥脆咸香比买的还要好吃...

    花10分钟做酱香饼,简单方便又美味,酥脆咸香比买的还要好吃 前几年,在单位附近有一家只做酱香饼的小店,刚开张的时候排很长队,我为了一块酱香饼排队半小时.后来买酱香饼的人少了,我三天两头买一块酱香饼解馋 ...

  8. Java练习习题,百钱买百鸡问题,用100文钱买鸡,公鸡5文钱一只,母鸡3文钱一只,小鸡3只1文钱

    需求说明: 用100文钱买鸡,公鸡5文钱一只,母鸡3文钱一只,小鸡3只1文钱,要求公鸡.母鸡.小鸡都必须要有,刚好用完100文钱,公鸡.母鸡.小鸡的数量之和也是100. public class te ...

  9. 第一道题目:一个美国人在菜市场上做生意。第一次,8美元买了一只鸡,9美元卖掉了;第二次,10美元买了同样的一只鸡,11美元又卖掉了。

    第一道题目:一个美国人在菜市场上做生意.第一次,8美元买了一只鸡,9美元卖掉了:第二次,10美元买了同样的一只鸡,11美元又卖掉了.那么,这个美国人到底是亏了,还是赚了?如果亏了,应该是亏多少?如果赚 ...

最新文章

  1. Java学习总结:58(Collections工具类)
  2. 视频编解码器 2020-比赛开始!
  3. 2012网页设计趋势(下)
  4. 生活中常见的电器,他们的工作原理你知道吗?
  5. JBPM中文乱码的一种解决方法
  6. 【Express】—post传递参数
  7. Python with 结构
  8. ziplist之详细分析
  9. 梅花传播业大展:Focussend将精准营销融入个性化邮件
  10. 【11月29】PF 粒子滤波的多维运动模型代码
  11. 全网首发:解决办法,/bin/ant: 1: cd: can‘t cd to /bin/../share/ant/bin/..
  12. EasyUI管理后台模板(附源码)
  13. 让 Chrome 57 支持迅雷精简版
  14. 【校内模拟】八云蓝(线段树)(大力分类讨论)
  15. 如何用原生js获取非行间样式
  16. pdf加密文件怎么加密
  17. ROS1/2 C++ talker.cpp 对比案例
  18. Linux 创建一个简单的私有CA、发证、吊销证书
  19. java 与 汇编_清华大学出版社-图书详情-《汇编语言与计算机体系结构——使用C++和Java》...
  20. 巴菲特午餐终局谜题何时揭晓,中标者是不是孙宇晨?

热门文章

  1. Python(x,y)下载安装及基本操作
  2. 容联云聚客SCRM如何破SCRM保险应用困局
  3. QTextEdit设计的几个知识点
  4. 如何从后面截取字符串 String.slice()
  5. 为 TDesignBlazor 添加暗黑模式
  6. ROS机器人Diego 1#制作(十四)机械臂的控制---arduino驱动
  7. uvm 糖果爱好者 subscriber调用parent方法解读
  8. 债券交易的方式及流程
  9. [Hadoop]Hadoop Archives
  10. 如何快速写作论文初稿?