Dappsでゲームを作ってみる(Part.1) コントラクトの定義

木内智史之介(シャッチョー)
ミンカさんけっこんしてくださいおねがいします(ズザー SEGAさん、DIVAの筐体ください(ズザー

基礎的な勉強ばかりやっていてもつまらないので、ここは階段を数段飛ばして、実際にゲームを作っていきたいと思います。
なんとか形になるといいなw

どんなゲームを作るのか

コントラクトの定義に入る前に、まず、「どんなゲームを作るのか」を決めないと進むに進めません。
可能な限りシンプルなゲームにしたいので、「3Dボックスを自由に積めるゲーム」にしようと思います。

こんなイメージ

要件を抜き出すと

  • ボックスには「所有者」の概念がある
  • ボックスには「色」の概念がある
  • ボックスには「座標(x,y,z)」の概念がある

といったあたりでしょうか?
まずは、このあたりの要件をコントラクトで表現してみる事にします。

プロジェクトの作成

最初の一歩として、まずはtruffleのプロジェクトを作成します。

$ truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test

ネットワーク設定

dockerで立ち上げているganacheに対してデプロイされるように、ネットワーク設定を追加しておきます。
ganacheは、以前の記事 で用意したものと同じものが立ち上がっているとします。

truffle.js
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*",
},
},
};

これで、コントラクトを書く準備が整いました!

truffle.jsとtruffle-config.jsの違い

余談ですが、truffle init コマンドでは、設定ファイルとして truffle.jstruffle-config.js の二つが作成されます。
これって、何がどう違うんだろう?って思ったら、公式ドキュメントに書いてありました。

https://truffleframework.com/docs/truffle/reference/configuration#resolving-naming-conflicts-on-windows

どうも、Windowsだと、truffle.jsを実行ファイルと勘違いされる事があるようで、それを回避するための一つの方法として truffle-config.js にリネームしろ、とそういう事のようです。

Windows使わないので、truffle-config.js は削除とします(笑)

コントラクトの定義

BoxFactoryコントラクトの作成

まずは、truffleコマンドを通して、コントラクトの箱形を作成します。

$ truffle create contract BoxFactory

作成されたコントラクトは内容が空なので、今回の要件を満たせるように実装を加えます。

contracts/BoxFactory.sol
pragma solidity ^0.4.22;
contract BoxFactory {
struct Box {
address owner;
uint32 color; // FFFFFF = 16777215 なので、32あれば十分
int8 x; // +-127程度を確保
int8 y; // z座標は、まだ持たせない
}
// 全てのボックスを格納
Box[] public boxes;
/**
* boxを生成する
*/
function _createBox(address _owner, uint32 _color, int8 _x, int8 _y) private {
require(_checkCoordinate(_owner, _x, _y));
boxes.push(Box({
owner: _owner,
color: _color,
x: _x,
y: _y
}));
}
/**
* boxの生成機能を外部に公開
*/
function createBox(uint32 _color, int8 _x, int8 _y) external {
_createBox(msg.sender, _color, _x, _y);
}
/**
* 同じ所有者で、同座標のボックスがない判定
*/
function _checkCoordinate(address _owner, int8 _x, int8 _y) private view returns(bool) {
for (uint i = 0; i < boxes.length; i++) {
Box memory box = boxes[i];
if (box.owner == _owner && box.x == _x && box.y == _y) {
return false;
}
}
return true;
}
/**
* 自分が所有するボックスを全て取得
* structのarrayで返却する事ができないので、返し方を工夫するしかない...
*/
function getBoxes() external view returns(address[], uint32[], int8[], int8[]) {
uint boxCount = _getBoxCount(msg.sender);
address[] memory owners = new address[](boxCount);
uint32[] memory colors = new uint32[](boxCount);
int8[] memory x = new int8[](boxCount);
int8[] memory y = new int8[](boxCount);
uint counter = 0;
for (uint i = 0; i < boxes.length; i++) {
Box memory box = boxes[i];
if (box.owner == msg.sender) {
owners[counter] = box.owner;
colors[counter] = box.color;
x[counter] = box.x;
y[counter] = box.y;
counter++;
}
}
return (owners, colors, x, y);
}
/**
* 自分が所有するボックスの個数を取得
*/
function _getBoxCount(address _owner) private view returns(uint count) {
for (uint i = 0; i < boxes.length; i++) {
if (boxes[i].owner == _owner) {
count++;
}
}
}
/**
* 自分のボックス数を取得する機能を外部公開
*/
function getBoxCount() external view returns(uint count) {
return _getBoxCount(msg.sender);
}
}

必要そうな機能を、難しい事は考えずにざっと実装してみました!

z座標に関して

本当はz座標に関しても定義するべきなんですが、この後のクライアント実装がちょっと複雑になりそうだったので、 一旦ステイしています。

Solidityは、構造体を返却できない

どうも、現段階のsolidityでは構造体を返却する事ができないようです。
なので、getBoxes の返却内容が非常に残念な事になっています。

https://medium.com/coinmonks/solidity-tutorial-returning-structs-from-public-functions-e78e48efb378

このあたりの記事が参考になるのですが、これは、今後のバージョンアップでサポートされる事を期待したい…。

振り返り

今回から、truffle コマンドはdockerを通さずに実行するようにしました。
truffleコマンドは常に最新を使う、という精神で問題なさそうに感じたというのと、 truffleが何かプロジェクト固有のデータをストアする事がないからです。

次の記事では、今回定義したコントラクトに関するテストを書いてみたいと思います。