Solidity入門基礎編【4】 – CryptoZombiesで学習 –

  • このエントリーをはてなブックマークに追加
  • LINEで送る

第3回に引き続きSolidity入門基礎編を進めていきます。

少しづつ難易度も上がって来てますが、頑張って取り組みましょう。

前回のはこちら。
Solidity入門基礎編【3】 – CryptoZombiesで学習 –

初めての方はこちらから。
Solidity入門基礎編【1】 – CryptoZombiesで学習 –

始めるまえにこちらへアクセスしてください。
https://cryptozombies.io/jp/lesson/4/chapter/1/

チャプター 1: Payable関数

チャプターの前に関数修飾詞についておさらい。

関数修飾詞とはざっくりいうと、関数にくっつけるとの関数の動きを制御できるというもの。

可視性修飾詞

public:外部から実行・参照可能、同じコントラクト内の関数、他のコントラクト内の関数から呼べる関数

external:外部から実行・参照可能、他のコントラクト内の関数からのみ呼べる関数(同じコントラクト内の関数からは呼べない)

internal:外部から実行・参照不可、他のコントラクト内の関数、同じコントラクト内の関数からのみ呼べる関数

private:外部から実行・参照不可、同じコントラクト内の関数からのみ呼べる関数(他のコントラクト内の関数からは呼べない)

状態修飾詞

view:読み取り専用、コントラクト内のデータを読み込む
pure:読み取り専用、コントラクト内のデータも読まない(関数内だけで完結)

カスタム修飾詞(って勝手に呼んでる)

modifier:自分でルールをつくれる(オーナーしか関数使えなくするとか、レベル制限を設けるとか)

そして今回追加で学ぶのが、payable修飾詞。

これはethを受け取るための関数修飾詞。solidity独特のものです。

目的:0.001 etherを支払うとゾンビのレベルを上げられる機能を実装

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  // levelUpに必要なetherを定義
  uint levelUpFee = 0.001 ether;
  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // levelUp関数を入力 どのゾンビのレベルを上げるのか情報が必要なためidをパラメータに設定
  function levelUp(uint _zombieId) external payable {
    // msg.valueは支払額のこと
    // 支払額が上記で設定した0.001 etherだったら処理が通る
    require(msg.value == levelUpFee);
    // ゾンビのレベルを1上げる
    zombies[_zombieId].level++;
  }

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}

チャプター 2: Withdraw関数

チャプター1ではコントラクトに対して送金をしました。

送金されたetherはコントラクトに貯まっている状態です。

それを引き出すための関数を学んでいきます。

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 引き出し用の関数
  function withdraw() external onlyOwner {
    // ユーザーから頂いたethを全額ownerに送金する
    // this.balanceはこのコントラクト内にある残高
    owner.transfer(this.balance);
  }

  // levelUpFeeをいつでも書き換えられるようにするための関数
  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}

チャプター 3: ゾンビ・バトル

このチャプターはコントラクト作成の復習回。

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {

}

チャプター 4: 乱数

これはレッスン1でもやったと思いますが、改めて。

目的:乱数を設定して、バトルの勝敗を決める機能の実装

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  // randNonceは1ずつ繰り上げていく数字 初期値として0を設定
  uint randNonce = 0;
  // ランダムな数字を返す関数
  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
  // now, msg.sender, randNonceの数字でハッシュ値を作り、_modulusで割った余りを返す
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }
}

チャプター 5: ゾンビが闘う

戦いの機能実装をします。

・自分のゾンビから一体を選び,攻撃する相手のゾンビを選ぶ
・攻撃するゾンビは勝率70%、守備するゾンビは30%の勝率
・全ゾンビ(攻撃するものも守備するものも)が、winCountとlossCountを持ち、これらはバトル結果次第で増えていく
・もし攻撃するゾンビが勝ったら、レベルアップして新たなゾンビを産む
・もし攻撃するゾンビが負けた場合は、何も起こらない(lossCountの増加を除く)
・勝っても負けても、攻撃するゾンビのクールダウン時間は開始される

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  // 勝率を設定
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  // 攻撃関数を宣言 引数には自分のゾンビと攻撃対象のゾンビIDを設定
  function attack(uint _zombieId, uint _targetId) external {
    
  }
}

チャプター 6: 共通ロジックのリファクタリング

requireを共通化していこうねというお話。

ゾンビを所有しているオーナーしか実行できないという制限を持つ関数が複数あるので、
共通化するためにmodifierでカスタム関数修飾詞をつくる。

pragma solidity ^0.4.19;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  KittyInterface kittyContract;

  // modifierで共通のrequireを宣言
  modifier ownerOf(uint _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    _;
  }

  function setKittyContractAddress(address _address) external onlyOwner {
    kittyContract = KittyInterface(_address);
  }

  function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(now + cooldownTime);
  }

  function _isReady(Zombie storage _zombie) internal view returns (bool) {
      return (_zombie.readyTime <= now);
  }

  // 関数定義にmodifier(ownerOf)を加える
  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
    // 重複するrequire文は削除する
    Zombie storage myZombie = zombies[_zombieId];
    require(_isReady(myZombie));
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    if (keccak256(_species) == keccak256("kitty")) {
      newDna = newDna - newDna % 100 + 99;
    }
    _createZombie("NoName", newDna);
    _triggerCooldown(myZombie);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }
}

チャプター 7: さらなるリファクタリング

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }

  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  // ownerOfを使うように修正
  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  // ownerOfを使うように修正
  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}

チャプター 8: Attackへ戻ろう!

ゾンビの攻撃用機能の実装に戻る。

先ほど宣言したownerOfも使っていきます。

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  // modifierをここに加える
  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    // Zombie構造体をつかって、自己所有のゾンビを設定する。
    Zombie storage myZombie = zombies[_zombieId];
  // Zombie構造体をつかって、敵のゾンビを設定する。
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
  }
}

チャプター 9: ゾンビの勝敗

pragma solidity ^0.4.19;

import "./ownable.sol";

contract ZombieFactory is Ownable {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
    uint cooldownTime = 1 days;

    struct Zombie {
      string name;
      uint dna;
      uint32 level;
      uint32 readyTime;
      uint16 winCount; // 勝利数
      uint16 lossCount; // 敗北数
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string _name, uint _dna) internal {
        // 0勝0敗を追加
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        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 {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}

チャプター 10: ゾンビの勝利

成績を更新する機能を実装していく。

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    // 70より下2桁の数字が小さいか
    if(rand <= attackVictoryProbability) {
        // 勝ったら勝ち数を増やす
        myZombie.winCount++;
        // ゾンビのレベルを上げる
        myZombie.level++;
        // 相手の負け数を増やす
        enemyZombie.lossCount++;
        feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    }
  }
}

チャプター 11: ゾンビの敗北

次は負けた場合の処理を追加。

目的:
・myZombieのlossCountを増やせ。

・enemyZombieのwinCountを増やせ。

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } else { // ここへ追加する
      myZombie.lossCount++;
      enemyZombie.winCount++;
    }
    _triggerCooldown(myZombie);
  }
}

次の講座はこちら。

Solidity入門基礎編【5】 – CryptoZombiesで学習 –

  • このエントリーをはてなブックマークに追加
  • LINEで送る

SNSでもご購読できます。

コメントを残す

*