山本  洋介

山本 洋介

1642397640

Solidity、Web3、Vue.jsを使用してブロックチェーンゲームを作成する

この記事では、以下を使用してイーサリアムパブリックブロックチェーンを使用して分散型ゲームを作成するためのステップバイステップのアプローチを見ていきます。

  • ヘルメット
  • 堅牢性
  • Vue.js

次のパートではフロントエンド側に焦点を当てますが、最初に、Solidity側とそれをRinkebyにデプロイする方法について簡単に説明しますtestnet

これは私の最初の記事であり、Web3とVue.jsについても注意が必要なため、情報が不足しているように感じました。

 

始める前に、このプロジェクトを作成したビルドスペースの功績を称えたいと思います。追加したのはVue.jsの部分です。このスペースに不慣れな方は、ぜひチェックしてみてください!彼らは最高の学習ツールとコミュニティを持っています!

したがって、始める前に、このスペースで始めたばかりの場合に本当に必要になるものについて話しましょう。

  • MetaMaskをインストールし、Chromeで拡張機能を有効にする必要があります
  • Metamaskに関する基本的な知識
  • Solidityに関する基本的な知識
  • JavaScriptとVue.jsに関する知識。

今日構築するもの

buildspaceキャラクターをミントしてボスと戦うことができるブロックチェーンベースのゲーム(に触発された)を構築します!

堅牢性

Solidityの初心者の方は、buildspaceをフォローすることをお勧めします。

私たちのスマートコントラクトは、キャラクターを作成し、選択したキャラクターをミントし、それでボスと戦うことを可能にします!簡単でしょ?

これが私たちのMyEpicGame.solスマートコントラクトです:

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "./libraries/Base64.sol";

import "hardhat/console.sol";

contract MyEpicGame is ERC721 {
    struct CharacterAttributes {
        uint256 characterIndex;
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    struct BigBoss {
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    BigBoss public bigBoss;

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    CharacterAttributes[] defaultCharacters;

    mapping(uint256 => CharacterAttributes) public nftHolderAttributes;

    mapping(address => uint256) public nftHolders;

    event CharacterNFTMinted(
        address sender,
        uint256 tokenId,
        uint256 characterIndex
    );
    event AttackComplete(uint256 newBossHp, uint256 newPlayerHp);

    constructor(
        string[] memory characterNames,
        string[] memory characterImageURIs,
        uint256[] memory characterHp,
        uint256[] memory characterAttackDmg,
        string memory bossName,
        string memory bossImageURI,
        uint256 bossHp,
        uint256 bossAttackDamage
    ) ERC721("Heroes", "HERO") {
        for (uint256 i = 0; i < characterNames.length; i += 1) {
            defaultCharacters.push(
                CharacterAttributes({
                    characterIndex: i,
                    name: characterNames[i],
                    imageURI: characterImageURIs[i],
                    hp: characterHp[i],
                    maxHp: characterHp[i],
                    attackDamage: characterAttackDmg[i]
                })
            );

            CharacterAttributes memory c = defaultCharacters[i];
            console.log(
                "Done initializing %s w/ HP %s, img %s",
                c.name,
                c.hp,
                c.imageURI
            );
        }

        bigBoss = BigBoss({
            name: bossName,
            imageURI: bossImageURI,
            hp: bossHp,
            maxHp: bossHp,
            attackDamage: bossAttackDamage
        });

        console.log(
            "Done initializing boss %s w/ HP %s, img %s",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.imageURI
        );

        _tokenIds.increment();
    }

    function mintCharacterNFT(uint256 _characterIndex) external {
        uint256 newItemId = _tokenIds.current();

        _safeMint(msg.sender, newItemId);

        nftHolderAttributes[newItemId] = CharacterAttributes({
            characterIndex: _characterIndex,
            name: defaultCharacters[_characterIndex].name,
            imageURI: defaultCharacters[_characterIndex].imageURI,
            hp: defaultCharacters[_characterIndex].hp,
            maxHp: defaultCharacters[_characterIndex].hp,
            attackDamage: defaultCharacters[_characterIndex].attackDamage
        });

        console.log(
            "Minted NFT w/ tokenId %s and characterIndex %s",
            newItemId,
            _characterIndex
        );

        nftHolders[msg.sender] = newItemId;

        _tokenIds.increment();
        emit CharacterNFTMinted(msg.sender, newItemId, _characterIndex);
    }

    function attackBoss() public {
        uint256 nftTokenIdOfPlayer = nftHolders[msg.sender];
        CharacterAttributes storage player = nftHolderAttributes[
            nftTokenIdOfPlayer
        ];
        console.log(
            "\nPlayer w/ character %s about to attack. Has %s HP and %s AD",
            player.name,
            player.hp,
            player.attackDamage
        );
        console.log(
            "Boss %s has %s HP and %s AD",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.attackDamage
        );

        require(player.hp > 0, "Error: character must have HP to attack boss.");
        require(bigBoss.hp > 0, "Error: boss must have HP to attack boss.");

        if (bigBoss.hp < player.attackDamage) {
            bigBoss.hp = 0;
        } else {
            bigBoss.hp = bigBoss.hp - player.attackDamage;
        }

        if (player.hp < bigBoss.attackDamage) {
            player.hp = 0;
        } else {
            player.hp = player.hp - bigBoss.attackDamage;
        }

        console.log("Boss attacked player. New player hp: %s\n", player.hp);
        emit AttackComplete(bigBoss.hp, player.hp);
    }

    function checkIfUserHasNFT()
        public
        view
        returns (CharacterAttributes memory)
    {
        uint256 userNftTokenId = nftHolders[msg.sender];
        if (userNftTokenId > 0) {
            return nftHolderAttributes[userNftTokenId];
        } else {
            CharacterAttributes memory emptyStruct;
            return emptyStruct;
        }
    }

    function getAllDefaultCharacters()
        public
        view
        returns (CharacterAttributes[] memory)
    {
        return defaultCharacters;
    }

    function getBigBoss() public view returns (BigBoss memory) {
        return bigBoss;
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        CharacterAttributes memory charAttributes = nftHolderAttributes[
            _tokenId
        ];

        string memory strHp = Strings.toString(charAttributes.hp);
        string memory strMaxHp = Strings.toString(charAttributes.maxHp);
        string memory strAttackDamage = Strings.toString(
            charAttributes.attackDamage
        );

        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "',
                        charAttributes.name,
                        " -- NFT #: ",
                        Strings.toString(_tokenId),
                        '", "description": "This is an NFT that lets people play in the game Metaverse Slayer!", "image": "',
                        charAttributes.imageURI,
                        '", "attributes": [ { "trait_type": "Health Points", "value": ',
                        strHp,
                        ', "max_value":',
                        strMaxHp,
                        '}, { "trait_type": "Attack Damage", "value": ',
                        strAttackDamage,
                        "} ]}"
                    )
                )
            )
        );

        string memory output = string(
            abi.encodePacked("data:application/json;base64,", json)
        );

        return output;
    }
}

ファイルについては、ここでBase64.sol見つけることができます。

これは基本的に、任意のデータをBase64文字列にエンコードできるようにするいくつかのヘルパー関数を提供します。これは、データの一部を文字列にエンコードするための標準的な方法です。

テスト

デプロイする前。契約をテストして、使用できることを確認する必要があります。

ルートディレクトリにという新しいフォルダを作成します。このフォルダーには、クライアント側とイーサリアムの両方のテストを含めることができます。test

フォルダー内にtest、という名前の新しいJSファイルを追加しますtest.js。このファイルには、1つのファイルに契約テストが含まれます。あなたはあなた自身を作成することができます、私は簡単なテストファイルを作成します:

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyEpicGame", function () {
  let gameContract;
  before(async () => {
    const gameContractFactory = await ethers.getContractFactory("MyEpicGame");
    gameContract = await gameContractFactory.deploy(
      ["Leo", "Aang", "Pikachu"],
      [
        "<https://i.imgur.com/pKd5Sdk.png>",
        "<https://i.imgur.com/xVu4vFL.png>",
        "<https://i.imgur.com/WMB6g9u.png>",
      ],
      [100, 200, 300],
      [100, 50, 25],
      "Elon Musk",
      "<https://i.imgur.com/AksR0tt.png>",
      10000,
      50
    );
    await gameContract.deployed();
  });
  it("Should have 3 default characters", async () => {
    let characters = await gameContract.getAllDefaultCharacters();
    expect(characters.length).to.equal(3);
  });
  it("Should have a boss", async () => {
		let boss = await gameContract.getBigBoss();
		expect(boss.name).to.equal("Elon Musk");
	});
});

テストを実行するには:

npx hardhat test

デプロイ(Rinkebyテストネットワークに)

deploy.jsハードハットプロジェクトのscriptsフォルダーに新しいファイルを作成しましょう。そして、その中にこのコードを入れてください。

これにより、コンストラクターから3つのデフォルトキャラクターとボスが作成されます。

const main = async () => {
  const gameContractFactory = await hre.ethers.getContractFactory("MyEpicGame");
  const gameContract = await gameContractFactory.deploy(
    ["Leo", "Aang", "Pikachu"],
    [
      "<https://i.imgur.com/pKd5Sdk.png>",
      "<https://i.imgur.com/xVu4vFL.png>",
      "<https://i.imgur.com/u7T87A6.png>",
    ],
    [100, 200, 300],
    [100, 50, 25],
    "Elon Musk",
    "<https://i.imgur.com/AksR0tt.png>",
    10000,
    50
  );
  await gameContract.deployed();
  console.log("Contract deployed to:", gameContract.address);
};
const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};
runMain();

コントラクトをデプロイするには、次のコマンドを実行します。

npx hardhat run scripts/deploy.js --network rinkeby

そして、Solidityの部分は終わりです。次に、フロントエンドインターフェイスを作成して対話する必要があります。

フロントエンドVue.js

ここではCSSを共有しません。GitHubリポジトリで自由に確認してください。

プロジェクトを作成することから始めましょう:

vue create frontend
cd frontend

Web3の相互作用にはエーテルを使用し、状態管理にはVuexを使用します。それらをインストールする方法は次のとおりです。

npm install --save vuex ethers

これで、プロジェクトを開始する準備が整いました。フロントエンドアプリを作成するために実行する手順について説明しましょう。

  • ユーザーのウォレットを接続します
  • キャラクターを選ぶ
  • 上司を攻撃する

ウォレットを接続する

ユーザーがアプリを操作するには、Metamaskがインストールされ、Rinkebyネットワークが選択されている必要があります。しかし、最後の部分でそれを処理します。

テンプレートは次のようになりますApp.vue。接続ボタンを使用すると、メタマスクでプロンプトが開き、アプリがユーザーが受け入れるトランザクションを要求できるようになります。

<template>
  <div class="app" id="app">
    <div class="container mx-auto">
      <div class="header-container">
        <p class="header gradient-text">⚔️ Metaverse Slayer ⚔️</p>
        <p class="sub-text">Team up to protect the Metaverse!</p>
        <div class="connect-wallet-container">
          <img
            src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
            alt="Monty Python Gif"
          />
          <button class="cta-button connect-wallet-button" @click="connect">
            Connect Wallet To Get Started
          </button>
        </div>
      </div>
      <div class="footer-container">
        <img
          alt="Twitter Logo"
          class="twitter-logo"
          src="./assets/twitter-logo.svg"
        />
        <a
          class="footer-text"
          :href="twitter_link"
          target="_blank"
          rel="noreferrer"
          >built by @{{ twitter_handle }}</a
        >
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      twitter_handle: "zouln96",
      twitter_link: "<https://twitter.com/zouln96>",
    };
  },
	methods: {
    async connect() {
      await this.$store.dispatch("connect", true);
    },
  },
	async mounted() {
    await this.$store.dispatch("connect", false);
  },
};
</script>

接続ボタンには、ストア(Vuex)にアクションをディスパッチするクリックイベントがあります。これについては後で説明します。ここでは、ストアの構造を見てみましょう。

import Vue from "vue";
import Vuex from "vuex";
import { ethers } from "ethers";
import MyEpicGame from "../utils/MyEpicGame.json";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    account: null,
    error: null,
    mining: false,
    characterNFT: null,
    characters: [],
    boss: null,
    attackState: null,
    contract_address: "0x91b5483e35EC485C68FF33f0ACfD51a26F3F1EcA",
  },
  getters: {
    account: (state) => state.account,
    error: (state) => state.error,
    mining: (state) => state.mining,
    characterNFT: (state) => state.characterNFT,
    characters: (state) => state.characters,
    boss: (state) => state.boss,
    attackState: (state) => state.attackState,
  },
  mutations: {
    setAccount(state, account) {
      state.account = account;
    },
    setError(state, error) {
      state.error = error;
    },
    setMining(state, mining) {
      state.mining = mining;
    },
    setCharacterNFT(state, characterNFT) {
      state.characterNFT = characterNFT;
    },
    setCharacters(state, characters) {
      state.characters = characters;
    },
    setBoss(state, boss) {
      state.boss = boss;
    },
    setAttackState(state, attackState) {
      state.attackState = attackState;
    },
  },
  actions: {},
});

状態オブジェクトには、次の属性があります。

  • account:接続されたアカウントが保存される場所
  • error:エラーを表示する
  • mining:トランザクションがマイニングされているかどうかを確認するブール値
  • characterNFT:選択した文字が保存される場所
  • characters:デフォルトの文字が保存される場所
  • boss:私たちのキャラクターと戦うボス
  • attackState:ボスを攻撃すると、トランザクションがマイニングされている間に状態が変化します
  • contract_address:契約をRinkebyネットワークにデプロイしたときに返されたアドレス。

MyEpicGame.jsonまた、コントラクトをデプロイした後、ビルドからインポートすることを忘れないでください。ブロックチェーン内のコントラクトを使用したweb3呼び出しに必要になります。

州のゲッターとセッター(ミューテーション)を作成しました。それでは、私たちの行動に取り掛かりましょう。

まず、前に説明したconnectアクションがあります。これについては、次のように説明します。

actions: {	
  async connect({ commit, dispatch }, connect) {
      try {
        const { ethereum } = window;
        if (!ethereum) {
          commit("setError", "Metamask not installed!");
          return;
        }
        if (!(await dispatch("checkIfConnected")) && connect) {
          await dispatch("requestAccess");
        }
        await dispatch("checkNetwork");
      } catch (error) {
        console.log(error);
        commit("setError", "Account request refused.");
      }
    },
    async checkNetwork({ commit, dispatch }) {
      let chainId = await ethereum.request({ method: "eth_chainId" });
      const rinkebyChainId = "0x4";
      if (chainId !== rinkebyChainId) {
        if (!(await dispatch("switchNetwork"))) {
          commit(
            "setError",
            "You are not connected to the Rinkeby Test Network!"
          );
        }
      }
    },
    async switchNetwork() {
      try {
        await ethereum.request({
          method: "wallet_switchEthereumChain",
          params: [{ chainId: "0x4" }],
        });
        return 1;
      } catch (switchError) {
        return 0;
      }
    },
    async checkIfConnected({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({ method: "eth_accounts" });
      if (accounts.length !== 0) {
        commit("setAccount", accounts[0]);
        return 1;
      } else {
        return 0;
      }
    },
    async requestAccess({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({
        method: "eth_requestAccounts",
      });
      commit("setAccount", accounts[0]);
    },
}

まず、Metamaskがインストールされているかどうかをここで確認します。

const { ethereum } = window;
if (!ethereum) {
  commit("setError", "Metamask not installed!");
  return;
}

すべて問題がない場合は、ユーザーがアプリにMetamaskへのアクセスを既に許可しているかどうかを確認し、アカウントを接続する必要があります。許可されていない場合は、見つかったアカウントの数である0が返されます。つまり、ユーザーにアクセスを要求する必要があります。

if (!(await dispatch("checkIfConnected")) && connect) {
	await dispatch("requestAccess");
}

注:connect変数は、クリックされたボタンであるか、実際にそれを呼び出しているマウントされた関数であるかを知るのに役立ちます

選択したネットワークを確認した後、それがRinkebyネットワークでない場合は、変更要求を送信します。

await dispatch("checkNetwork");

アカウントが見つかったら、アカウントをミューテーションにコミットして、次の状態で保存します。

// in checkIfConnected action
commit("setAccount", accounts[0]);

接続アクションは以上です。

次に、ユーザーがスマートコントラクトから選択できるデフォルトの文字を取得するアクションを作成します。

async getCharacters({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const charactersTxn = await connectedContract.getAllDefaultCharacters();
    const characters = charactersTxn.map((characterData) =>
      transformCharacterData(characterData)
    );
    commit("setCharacters", characters);
  } catch (error) {
    console.log(error);
  }
},

コントラクトから関数を呼び出すには、そのためのアクションも作成してコントラクトをフェッチし、それを返す必要があります。プロバイダー、契約abi、および署名者を提供します。

async getContract({ state }) {
  try {
    const { ethereum } = window;
    const provider = new ethers.providers.Web3Provider(ethereum);
    const signer = provider.getSigner();
    const connectedContract = new ethers.Contract(
      state.contract_address,
      MyEpicGame.abi,
      signer
    );
    return connectedContract;
  } catch (error) {
    console.log(error);
    console.log("connected contract not found");
    return null;
  }
},

次に、デフォルトの文字を返すスマートコントラクトの関数を呼び出し、文字データをJavaScriptで使用可能なオブジェクトに変換する関数を使用して、それぞれにマップします。

const charactersTxn = await connectedContract.getAllDefaultCharacters();
const characters = charactersTxn.map((characterData) =>
  transformCharacterData(characterData)
);

この関数は、 Vuex.StoretransformCharacterDataの初期化の上に追加されます。を読み取り可能な数値に変換します。hpattackDamagebigNumber

const transformCharacterData = (characterData) => {
  return {
    name: characterData.name,
    imageURI: characterData.imageURI,
    hp: characterData.hp.toNumber(),
    maxHp: characterData.maxHp.toNumber(),
    attackDamage: characterData.attackDamage.toNumber(),
  };
};

App.vue次に、ビューに戻ってビューを設定し、と呼ばれるコンポーネントを作成しましょうSelectCharacter

App.vueユーザーがウォレットに接続すると、ストアにアカウントが保存され、以前に取得したデフォルトからキャラクターを選択できるように、を変更します。

接続divホルダーにを追加v-ifし、ビューに文字選択コンポーネントを追加します。

<div class="connect-wallet-container" v-if="!account">
  <img
    src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
    alt="Monty Python Gif"
  />
  <button class="cta-button connect-wallet-button" @click="connect">
    Connect Wallet To Get Started
  </button>
</div>
<select-character v-else-if="account" />

アカウントの場合、実際にはストアから返されるのは計算変数です。

computed: {
  account() {
    return this.$store.getters.account;
  },
}

SelectCharacter私たちのコンポーネントに来る:

<template>
  <div class="select-character-container">
    <h2 class="mt-5">Mint Your Hero. Choose wisely.</h2>
    <div v-if="characters.length && !minting" class="character-grid">
      <div
        class="character-item cursor-pointer mt-10"
        :key="character.name"
        v-for="(character, index) in characters"
      >
        <div class="name-container">
          <p>{{ character.name }}</p>
        </div>
        <img :src="character.imageURI" :alt="character.name" />
        <button
          type="button"
          class="character-mint-button"
          @click="mintCharacterNFTAction(index)"
        >
          {{ `Mint ${character.name}` }}
        </button>
      </div>
    </div>
    <div class="loading" v-else>
      <div class="indicator">
        <loading-indicator />
        <p>Minting In Progress...</p>
      </div>
      <img
        src="<https://media2.giphy.com/media/61tYloUgq1eOk/giphy.gif?cid=ecf05e47dg95zbpabxhmhaksvoy8h526f96k4em0ndvx078s&rid=giphy.gif&ct=g>"
        alt="Minting loading indicator"
      />
    </div>
  </div>
</template>

<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  data() {
    return {
      minting: false,
    };
  },
  components: {
    LoadingIndicator,
  },
  methods: {
    async mintCharacterNFTAction(index) {
      if (this.minting) return;
      this.minting = true;
      await this.$store.dispatch("mintCharacterNFT", index);
      this.minting = false;
    },
  },
  async mounted() {
    this.minting = true;
    await this.$store.dispatch("getCharacters");
    this.minting = false;
  },
  computed: {
    characters() {
      return this.$store.getters.characters;
    },
  },
};
</script>

コンポーネントがマウントされたら、をフェッチdefaultCharactersしてビューに表示する必要があります。

アイテムごとに、選択したまたはインデックスmintCharacterNFTに基づいて呼び出されるミントアクションをストアにディスパッチするクリックイベントがあります。characterIdこのアクションをストアに追加しましょう:

async mintCharacterNFT({ commit, dispatch }, characterId) {
  try {
    const connectedContract = await dispatch("getContract");
    const mintTxn = await connectedContract.mintCharacterNFT(characterId);
    await mintTxn.wait();
  } catch (error) {
    console.log(error);
  }
},

前と同じように、ミンティングを担当するスマートコントラクト関数を呼び出します。

しかし、ここに問題があります、私たちは私たちのミントキャラクターを私たちの状態に設定しませんでしたか?スマートコントラクトでの機能を覚えていれば、キャラクターがミントされたらイベントが発生しますので、ご安心くださいCharacterNFTMinted

ですから、私たちが今しなければならないことは、そのイベントを聞いて、そこからキャラクターを設定することです。イベントリスナーを設定するアクションを作成しましょう。

async setupEventListeners({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    if (!connectedContract) return;
    connectedContract.on(
      "CharacterNFTMinted",
      async (from, tokenId, characterIndex) => {
        console.log(
          `CharacterNFTMinted - sender: ${from} tokenId: ${tokenId.toNumber()} characterIndex: ${characterIndex.toNumber()}`
        );
        const characterNFT = await connectedContract.checkIfUserHasNFT();
        console.log(characterNFT);
        commit("setCharacterNFT", transformCharacterData(characterNFT));
        alert(
          `Your NFT is all done -- see it here: <https://testnets.opensea.io/assets/$>{
            state.contract_address
          }/${tokenId.toNumber()}`
        );
      }
    );

  } catch (error) {
    console.log(error);
  }
},

web3でイベントを聞くには、を使用しcontract.on("event_name", callback)ます。

イベント内で、この関数で選択されたユーザーNFTをチェックし、checkIfUserHasNFTそれを自分の状態にコミットします。ユーザーがNFTリンクを表示したい場合、アラートは単なる追加情報です。では、このアクションはどこで呼び出されるべきだと思いますか?

checkNetworkディスパッチの下の接続アクションに追加します。

await dispatch("setupEventListeners");
await dispatch("fetchNFTMetadata");

また、ユーザーがアプリにアクセスしたときに、ユーザーがすでにNFTを持っているかどうかを確認する別のアクションを追加しましょう。

async fetchNFTMetadata({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const txn = await connectedContract.checkIfUserHasNFT();
    if (txn.name) {
      commit("setCharacterNFT", transformCharacterData(txn));
    }
  } catch (error) {
    console.log(error);
  }
},

このアクションはイベントとほとんど同じですが、呼び出されたときにのみチェックしてください。

これでキャラクターの選択が完了しました。戻ってApp.vue、ボスと戦うためにアリーナをセットアップしましょう。で呼び出されたselect-characterの子を変更するApp.vue必要があります。ユーザーがすでにNFTを選択している場合は、アリーナに直接移動する必要があります。

<select-character v-else-if="account && !characterNFT" />
<arena v-else-if="account && characterNFT" />

characterNFT variableは、accountのような計算変数です。

characterNFT() {
  return this.$store.getters.characterNFT;
},

コンポーネントを作成しましょうArena

<template>
  <div class="arena-container">
    <div class="boss-container" v-if="boss">
      <div :class="`boss-content ${attackState}`">
        <h2>🔥 {{ boss.name }} 🔥</h2>
        <div class="image-content">
          <img :src="boss.imageURI" :alt="`Boss ${boss.name}`" />
          <div class="health-bar">
            <progress :value="boss.hp" :max="boss.maxHp" />
            <p>{{ `${boss.hp} / ${boss.maxHp} HP` }}</p>
          </div>
        </div>
      </div>
      <div class="attack-container">
        <button class="cta-button" @click="attackAction">
          {{ `💥 Attack ${boss.name}` }}
        </button>
        <div class="loading-indicator" v-if="attackState === 'attacking'">
          <LoadingIndicator />
          <p>Attacking ⚔️</p>
        </div>
      </div>
    </div>
    <div class="players-container" v-if="characterNFT">
      <div class="player-container">
        <h2>Your Character</h2>
        <div class="player">
          <div class="image-content">
            <h2>{{ characterNFT.name }}</h2>
            <img
              :src="characterNFT.imageURI"
              :alt="`Character
            ${characterNFT.name}`"
            />
            <div class="health-bar">
              <progress :value="characterNFT.hp" :max="characterNFT.maxHp" />
              <p>{{ `${characterNFT.hp} / ${characterNFT.maxHp} HP` }}</p>
            </div>
          </div>
          <div class="stats">
            <h4>{{ `⚔️ Attack Damage: ${characterNFT.attackDamage}` }}</h4>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  components: {
    LoadingIndicator,
  },
  methods: {
    async attackAction() {
      await this.$store.dispatch("attackBoss");
    },
  },
  async mounted() {
    await this.$store.dispatch("fetchBoss");
  },
  computed: {
    boss() {
      return this.$store.getters.boss;
    },
    characterNFT() {
      return this.$store.getters.characterNFT;
    },
    attackState() {
      return this.$store.getters.attackState;
    },
  },
};
</script>

このコンポーネントがマウントされると、ボスをフェッチするアクションと、攻撃ボタンがクリックされたときに別のアクションを呼び出します。ここで、attackState(攻撃/ヒット)の間で変更が行われます。

async fetchBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const bossTxn = await connectedContract.getBigBoss();
    commit("setBoss", transformCharacterData(bossTxn));
  } catch (error) {
    console.log(error);
  }
},
async attackBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    commit("setAttackState", "attacking");
    console.log("Attacking boss...");
    const attackTxn = await connectedContract.attackBoss();
    await attackTxn.wait();
    console.log("attackTxn:", attackTxn);
    commit("setAttackState", "hit");
  } catch (error) {
    console.error("Error attacking boss:", error);
    setAttackState("");
  }
},

attackCompleteそして、私たちの行動の中で私たちのイベントを忘れないでくださいsetupEventListeners、これは上司とプレーヤーを更新しますhp

connectedContract.on(
  "AttackComplete",
  async (newBossHp, newPlayerHp) => {
    console.log(
      `AttackComplete: Boss Hp: ${newBossHp} Player Hp: ${newPlayerHp}`
    );
    let boss = state.boss;
    boss.hp = newBossHp;
    commit("setBoss", boss);    let character = state.characterNFT;
    character.hp = newPlayerHp;
    commit("setCharacterNFT", character);
  }
);

この読み込みインジケーターコンポーネントを追加して、UXを向上させることができます。

<template>
  <div class="lds-ring">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</template><script>
export default {};
</script>

これで、Vue.jsを使用して最初のweb3ゲームが完了しました。vercel私と同じように無料でホストできます。

これが私のアプリと完全なソースコードのGitHubリポジトリです。

このプロジェクトの作成を支援するためのビルドスペースへの大きな叫び声!

プロジェクトを完了するために、このNFTも入手しました。

ハッピーコーディング!

リンク:https ://betterprogramming.pub/create-a-blockchain-game-with-solidity-web3-and-vue-js-c75eed4b49a6

#vuejs  #web3  #solidity 

What is GEEK

Buddha Community

Solidity、Web3、Vue.jsを使用してブロックチェーンゲームを作成する
Luna  Mosciski

Luna Mosciski

1600583123

8 Popular Websites That Use The Vue.JS Framework

In this article, we are going to list out the most popular websites using Vue JS as their frontend framework.

Vue JS is one of those elite progressive JavaScript frameworks that has huge demand in the web development industry. Many popular websites are developed using Vue in their frontend development because of its imperative features.

This framework was created by Evan You and still it is maintained by his private team members. Vue is of course an open-source framework which is based on MVVM concept (Model-view view-Model) and used extensively in building sublime user-interfaces and also considered a prime choice for developing single-page heavy applications.

Released in February 2014, Vue JS has gained 64,828 stars on Github, making it very popular in recent times.

Evan used Angular JS on many operations while working for Google and integrated many features in Vue to cover the flaws of Angular.

“I figured, what if I could just extract the part that I really liked about Angular and build something really lightweight." - Evan You

#vuejs #vue #vue-with-laravel #vue-top-story #vue-3 #build-vue-frontend #vue-in-laravel #vue.js

山本  洋介

山本 洋介

1642397640

Solidity、Web3、Vue.jsを使用してブロックチェーンゲームを作成する

この記事では、以下を使用してイーサリアムパブリックブロックチェーンを使用して分散型ゲームを作成するためのステップバイステップのアプローチを見ていきます。

  • ヘルメット
  • 堅牢性
  • Vue.js

次のパートではフロントエンド側に焦点を当てますが、最初に、Solidity側とそれをRinkebyにデプロイする方法について簡単に説明しますtestnet

これは私の最初の記事であり、Web3とVue.jsについても注意が必要なため、情報が不足しているように感じました。

 

始める前に、このプロジェクトを作成したビルドスペースの功績を称えたいと思います。追加したのはVue.jsの部分です。このスペースに不慣れな方は、ぜひチェックしてみてください!彼らは最高の学習ツールとコミュニティを持っています!

したがって、始める前に、このスペースで始めたばかりの場合に本当に必要になるものについて話しましょう。

  • MetaMaskをインストールし、Chromeで拡張機能を有効にする必要があります
  • Metamaskに関する基本的な知識
  • Solidityに関する基本的な知識
  • JavaScriptとVue.jsに関する知識。

今日構築するもの

buildspaceキャラクターをミントしてボスと戦うことができるブロックチェーンベースのゲーム(に触発された)を構築します!

堅牢性

Solidityの初心者の方は、buildspaceをフォローすることをお勧めします。

私たちのスマートコントラクトは、キャラクターを作成し、選択したキャラクターをミントし、それでボスと戦うことを可能にします!簡単でしょ?

これが私たちのMyEpicGame.solスマートコントラクトです:

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "./libraries/Base64.sol";

import "hardhat/console.sol";

contract MyEpicGame is ERC721 {
    struct CharacterAttributes {
        uint256 characterIndex;
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    struct BigBoss {
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    BigBoss public bigBoss;

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    CharacterAttributes[] defaultCharacters;

    mapping(uint256 => CharacterAttributes) public nftHolderAttributes;

    mapping(address => uint256) public nftHolders;

    event CharacterNFTMinted(
        address sender,
        uint256 tokenId,
        uint256 characterIndex
    );
    event AttackComplete(uint256 newBossHp, uint256 newPlayerHp);

    constructor(
        string[] memory characterNames,
        string[] memory characterImageURIs,
        uint256[] memory characterHp,
        uint256[] memory characterAttackDmg,
        string memory bossName,
        string memory bossImageURI,
        uint256 bossHp,
        uint256 bossAttackDamage
    ) ERC721("Heroes", "HERO") {
        for (uint256 i = 0; i < characterNames.length; i += 1) {
            defaultCharacters.push(
                CharacterAttributes({
                    characterIndex: i,
                    name: characterNames[i],
                    imageURI: characterImageURIs[i],
                    hp: characterHp[i],
                    maxHp: characterHp[i],
                    attackDamage: characterAttackDmg[i]
                })
            );

            CharacterAttributes memory c = defaultCharacters[i];
            console.log(
                "Done initializing %s w/ HP %s, img %s",
                c.name,
                c.hp,
                c.imageURI
            );
        }

        bigBoss = BigBoss({
            name: bossName,
            imageURI: bossImageURI,
            hp: bossHp,
            maxHp: bossHp,
            attackDamage: bossAttackDamage
        });

        console.log(
            "Done initializing boss %s w/ HP %s, img %s",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.imageURI
        );

        _tokenIds.increment();
    }

    function mintCharacterNFT(uint256 _characterIndex) external {
        uint256 newItemId = _tokenIds.current();

        _safeMint(msg.sender, newItemId);

        nftHolderAttributes[newItemId] = CharacterAttributes({
            characterIndex: _characterIndex,
            name: defaultCharacters[_characterIndex].name,
            imageURI: defaultCharacters[_characterIndex].imageURI,
            hp: defaultCharacters[_characterIndex].hp,
            maxHp: defaultCharacters[_characterIndex].hp,
            attackDamage: defaultCharacters[_characterIndex].attackDamage
        });

        console.log(
            "Minted NFT w/ tokenId %s and characterIndex %s",
            newItemId,
            _characterIndex
        );

        nftHolders[msg.sender] = newItemId;

        _tokenIds.increment();
        emit CharacterNFTMinted(msg.sender, newItemId, _characterIndex);
    }

    function attackBoss() public {
        uint256 nftTokenIdOfPlayer = nftHolders[msg.sender];
        CharacterAttributes storage player = nftHolderAttributes[
            nftTokenIdOfPlayer
        ];
        console.log(
            "\nPlayer w/ character %s about to attack. Has %s HP and %s AD",
            player.name,
            player.hp,
            player.attackDamage
        );
        console.log(
            "Boss %s has %s HP and %s AD",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.attackDamage
        );

        require(player.hp > 0, "Error: character must have HP to attack boss.");
        require(bigBoss.hp > 0, "Error: boss must have HP to attack boss.");

        if (bigBoss.hp < player.attackDamage) {
            bigBoss.hp = 0;
        } else {
            bigBoss.hp = bigBoss.hp - player.attackDamage;
        }

        if (player.hp < bigBoss.attackDamage) {
            player.hp = 0;
        } else {
            player.hp = player.hp - bigBoss.attackDamage;
        }

        console.log("Boss attacked player. New player hp: %s\n", player.hp);
        emit AttackComplete(bigBoss.hp, player.hp);
    }

    function checkIfUserHasNFT()
        public
        view
        returns (CharacterAttributes memory)
    {
        uint256 userNftTokenId = nftHolders[msg.sender];
        if (userNftTokenId > 0) {
            return nftHolderAttributes[userNftTokenId];
        } else {
            CharacterAttributes memory emptyStruct;
            return emptyStruct;
        }
    }

    function getAllDefaultCharacters()
        public
        view
        returns (CharacterAttributes[] memory)
    {
        return defaultCharacters;
    }

    function getBigBoss() public view returns (BigBoss memory) {
        return bigBoss;
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        CharacterAttributes memory charAttributes = nftHolderAttributes[
            _tokenId
        ];

        string memory strHp = Strings.toString(charAttributes.hp);
        string memory strMaxHp = Strings.toString(charAttributes.maxHp);
        string memory strAttackDamage = Strings.toString(
            charAttributes.attackDamage
        );

        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "',
                        charAttributes.name,
                        " -- NFT #: ",
                        Strings.toString(_tokenId),
                        '", "description": "This is an NFT that lets people play in the game Metaverse Slayer!", "image": "',
                        charAttributes.imageURI,
                        '", "attributes": [ { "trait_type": "Health Points", "value": ',
                        strHp,
                        ', "max_value":',
                        strMaxHp,
                        '}, { "trait_type": "Attack Damage", "value": ',
                        strAttackDamage,
                        "} ]}"
                    )
                )
            )
        );

        string memory output = string(
            abi.encodePacked("data:application/json;base64,", json)
        );

        return output;
    }
}

ファイルについては、ここでBase64.sol見つけることができます。

これは基本的に、任意のデータをBase64文字列にエンコードできるようにするいくつかのヘルパー関数を提供します。これは、データの一部を文字列にエンコードするための標準的な方法です。

テスト

デプロイする前。契約をテストして、使用できることを確認する必要があります。

ルートディレクトリにという新しいフォルダを作成します。このフォルダーには、クライアント側とイーサリアムの両方のテストを含めることができます。test

フォルダー内にtest、という名前の新しいJSファイルを追加しますtest.js。このファイルには、1つのファイルに契約テストが含まれます。あなたはあなた自身を作成することができます、私は簡単なテストファイルを作成します:

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyEpicGame", function () {
  let gameContract;
  before(async () => {
    const gameContractFactory = await ethers.getContractFactory("MyEpicGame");
    gameContract = await gameContractFactory.deploy(
      ["Leo", "Aang", "Pikachu"],
      [
        "<https://i.imgur.com/pKd5Sdk.png>",
        "<https://i.imgur.com/xVu4vFL.png>",
        "<https://i.imgur.com/WMB6g9u.png>",
      ],
      [100, 200, 300],
      [100, 50, 25],
      "Elon Musk",
      "<https://i.imgur.com/AksR0tt.png>",
      10000,
      50
    );
    await gameContract.deployed();
  });
  it("Should have 3 default characters", async () => {
    let characters = await gameContract.getAllDefaultCharacters();
    expect(characters.length).to.equal(3);
  });
  it("Should have a boss", async () => {
		let boss = await gameContract.getBigBoss();
		expect(boss.name).to.equal("Elon Musk");
	});
});

テストを実行するには:

npx hardhat test

デプロイ(Rinkebyテストネットワークに)

deploy.jsハードハットプロジェクトのscriptsフォルダーに新しいファイルを作成しましょう。そして、その中にこのコードを入れてください。

これにより、コンストラクターから3つのデフォルトキャラクターとボスが作成されます。

const main = async () => {
  const gameContractFactory = await hre.ethers.getContractFactory("MyEpicGame");
  const gameContract = await gameContractFactory.deploy(
    ["Leo", "Aang", "Pikachu"],
    [
      "<https://i.imgur.com/pKd5Sdk.png>",
      "<https://i.imgur.com/xVu4vFL.png>",
      "<https://i.imgur.com/u7T87A6.png>",
    ],
    [100, 200, 300],
    [100, 50, 25],
    "Elon Musk",
    "<https://i.imgur.com/AksR0tt.png>",
    10000,
    50
  );
  await gameContract.deployed();
  console.log("Contract deployed to:", gameContract.address);
};
const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};
runMain();

コントラクトをデプロイするには、次のコマンドを実行します。

npx hardhat run scripts/deploy.js --network rinkeby

そして、Solidityの部分は終わりです。次に、フロントエンドインターフェイスを作成して対話する必要があります。

フロントエンドVue.js

ここではCSSを共有しません。GitHubリポジトリで自由に確認してください。

プロジェクトを作成することから始めましょう:

vue create frontend
cd frontend

Web3の相互作用にはエーテルを使用し、状態管理にはVuexを使用します。それらをインストールする方法は次のとおりです。

npm install --save vuex ethers

これで、プロジェクトを開始する準備が整いました。フロントエンドアプリを作成するために実行する手順について説明しましょう。

  • ユーザーのウォレットを接続します
  • キャラクターを選ぶ
  • 上司を攻撃する

ウォレットを接続する

ユーザーがアプリを操作するには、Metamaskがインストールされ、Rinkebyネットワークが選択されている必要があります。しかし、最後の部分でそれを処理します。

テンプレートは次のようになりますApp.vue。接続ボタンを使用すると、メタマスクでプロンプトが開き、アプリがユーザーが受け入れるトランザクションを要求できるようになります。

<template>
  <div class="app" id="app">
    <div class="container mx-auto">
      <div class="header-container">
        <p class="header gradient-text">⚔️ Metaverse Slayer ⚔️</p>
        <p class="sub-text">Team up to protect the Metaverse!</p>
        <div class="connect-wallet-container">
          <img
            src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
            alt="Monty Python Gif"
          />
          <button class="cta-button connect-wallet-button" @click="connect">
            Connect Wallet To Get Started
          </button>
        </div>
      </div>
      <div class="footer-container">
        <img
          alt="Twitter Logo"
          class="twitter-logo"
          src="./assets/twitter-logo.svg"
        />
        <a
          class="footer-text"
          :href="twitter_link"
          target="_blank"
          rel="noreferrer"
          >built by @{{ twitter_handle }}</a
        >
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      twitter_handle: "zouln96",
      twitter_link: "<https://twitter.com/zouln96>",
    };
  },
	methods: {
    async connect() {
      await this.$store.dispatch("connect", true);
    },
  },
	async mounted() {
    await this.$store.dispatch("connect", false);
  },
};
</script>

接続ボタンには、ストア(Vuex)にアクションをディスパッチするクリックイベントがあります。これについては後で説明します。ここでは、ストアの構造を見てみましょう。

import Vue from "vue";
import Vuex from "vuex";
import { ethers } from "ethers";
import MyEpicGame from "../utils/MyEpicGame.json";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    account: null,
    error: null,
    mining: false,
    characterNFT: null,
    characters: [],
    boss: null,
    attackState: null,
    contract_address: "0x91b5483e35EC485C68FF33f0ACfD51a26F3F1EcA",
  },
  getters: {
    account: (state) => state.account,
    error: (state) => state.error,
    mining: (state) => state.mining,
    characterNFT: (state) => state.characterNFT,
    characters: (state) => state.characters,
    boss: (state) => state.boss,
    attackState: (state) => state.attackState,
  },
  mutations: {
    setAccount(state, account) {
      state.account = account;
    },
    setError(state, error) {
      state.error = error;
    },
    setMining(state, mining) {
      state.mining = mining;
    },
    setCharacterNFT(state, characterNFT) {
      state.characterNFT = characterNFT;
    },
    setCharacters(state, characters) {
      state.characters = characters;
    },
    setBoss(state, boss) {
      state.boss = boss;
    },
    setAttackState(state, attackState) {
      state.attackState = attackState;
    },
  },
  actions: {},
});

状態オブジェクトには、次の属性があります。

  • account:接続されたアカウントが保存される場所
  • error:エラーを表示する
  • mining:トランザクションがマイニングされているかどうかを確認するブール値
  • characterNFT:選択した文字が保存される場所
  • characters:デフォルトの文字が保存される場所
  • boss:私たちのキャラクターと戦うボス
  • attackState:ボスを攻撃すると、トランザクションがマイニングされている間に状態が変化します
  • contract_address:契約をRinkebyネットワークにデプロイしたときに返されたアドレス。

MyEpicGame.jsonまた、コントラクトをデプロイした後、ビルドからインポートすることを忘れないでください。ブロックチェーン内のコントラクトを使用したweb3呼び出しに必要になります。

州のゲッターとセッター(ミューテーション)を作成しました。それでは、私たちの行動に取り掛かりましょう。

まず、前に説明したconnectアクションがあります。これについては、次のように説明します。

actions: {	
  async connect({ commit, dispatch }, connect) {
      try {
        const { ethereum } = window;
        if (!ethereum) {
          commit("setError", "Metamask not installed!");
          return;
        }
        if (!(await dispatch("checkIfConnected")) && connect) {
          await dispatch("requestAccess");
        }
        await dispatch("checkNetwork");
      } catch (error) {
        console.log(error);
        commit("setError", "Account request refused.");
      }
    },
    async checkNetwork({ commit, dispatch }) {
      let chainId = await ethereum.request({ method: "eth_chainId" });
      const rinkebyChainId = "0x4";
      if (chainId !== rinkebyChainId) {
        if (!(await dispatch("switchNetwork"))) {
          commit(
            "setError",
            "You are not connected to the Rinkeby Test Network!"
          );
        }
      }
    },
    async switchNetwork() {
      try {
        await ethereum.request({
          method: "wallet_switchEthereumChain",
          params: [{ chainId: "0x4" }],
        });
        return 1;
      } catch (switchError) {
        return 0;
      }
    },
    async checkIfConnected({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({ method: "eth_accounts" });
      if (accounts.length !== 0) {
        commit("setAccount", accounts[0]);
        return 1;
      } else {
        return 0;
      }
    },
    async requestAccess({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({
        method: "eth_requestAccounts",
      });
      commit("setAccount", accounts[0]);
    },
}

まず、Metamaskがインストールされているかどうかをここで確認します。

const { ethereum } = window;
if (!ethereum) {
  commit("setError", "Metamask not installed!");
  return;
}

すべて問題がない場合は、ユーザーがアプリにMetamaskへのアクセスを既に許可しているかどうかを確認し、アカウントを接続する必要があります。許可されていない場合は、見つかったアカウントの数である0が返されます。つまり、ユーザーにアクセスを要求する必要があります。

if (!(await dispatch("checkIfConnected")) && connect) {
	await dispatch("requestAccess");
}

注:connect変数は、クリックされたボタンであるか、実際にそれを呼び出しているマウントされた関数であるかを知るのに役立ちます

選択したネットワークを確認した後、それがRinkebyネットワークでない場合は、変更要求を送信します。

await dispatch("checkNetwork");

アカウントが見つかったら、アカウントをミューテーションにコミットして、次の状態で保存します。

// in checkIfConnected action
commit("setAccount", accounts[0]);

接続アクションは以上です。

次に、ユーザーがスマートコントラクトから選択できるデフォルトの文字を取得するアクションを作成します。

async getCharacters({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const charactersTxn = await connectedContract.getAllDefaultCharacters();
    const characters = charactersTxn.map((characterData) =>
      transformCharacterData(characterData)
    );
    commit("setCharacters", characters);
  } catch (error) {
    console.log(error);
  }
},

コントラクトから関数を呼び出すには、そのためのアクションも作成してコントラクトをフェッチし、それを返す必要があります。プロバイダー、契約abi、および署名者を提供します。

async getContract({ state }) {
  try {
    const { ethereum } = window;
    const provider = new ethers.providers.Web3Provider(ethereum);
    const signer = provider.getSigner();
    const connectedContract = new ethers.Contract(
      state.contract_address,
      MyEpicGame.abi,
      signer
    );
    return connectedContract;
  } catch (error) {
    console.log(error);
    console.log("connected contract not found");
    return null;
  }
},

次に、デフォルトの文字を返すスマートコントラクトの関数を呼び出し、文字データをJavaScriptで使用可能なオブジェクトに変換する関数を使用して、それぞれにマップします。

const charactersTxn = await connectedContract.getAllDefaultCharacters();
const characters = charactersTxn.map((characterData) =>
  transformCharacterData(characterData)
);

この関数は、 Vuex.StoretransformCharacterDataの初期化の上に追加されます。を読み取り可能な数値に変換します。hpattackDamagebigNumber

const transformCharacterData = (characterData) => {
  return {
    name: characterData.name,
    imageURI: characterData.imageURI,
    hp: characterData.hp.toNumber(),
    maxHp: characterData.maxHp.toNumber(),
    attackDamage: characterData.attackDamage.toNumber(),
  };
};

App.vue次に、ビューに戻ってビューを設定し、と呼ばれるコンポーネントを作成しましょうSelectCharacter

App.vueユーザーがウォレットに接続すると、ストアにアカウントが保存され、以前に取得したデフォルトからキャラクターを選択できるように、を変更します。

接続divホルダーにを追加v-ifし、ビューに文字選択コンポーネントを追加します。

<div class="connect-wallet-container" v-if="!account">
  <img
    src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
    alt="Monty Python Gif"
  />
  <button class="cta-button connect-wallet-button" @click="connect">
    Connect Wallet To Get Started
  </button>
</div>
<select-character v-else-if="account" />

アカウントの場合、実際にはストアから返されるのは計算変数です。

computed: {
  account() {
    return this.$store.getters.account;
  },
}

SelectCharacter私たちのコンポーネントに来る:

<template>
  <div class="select-character-container">
    <h2 class="mt-5">Mint Your Hero. Choose wisely.</h2>
    <div v-if="characters.length && !minting" class="character-grid">
      <div
        class="character-item cursor-pointer mt-10"
        :key="character.name"
        v-for="(character, index) in characters"
      >
        <div class="name-container">
          <p>{{ character.name }}</p>
        </div>
        <img :src="character.imageURI" :alt="character.name" />
        <button
          type="button"
          class="character-mint-button"
          @click="mintCharacterNFTAction(index)"
        >
          {{ `Mint ${character.name}` }}
        </button>
      </div>
    </div>
    <div class="loading" v-else>
      <div class="indicator">
        <loading-indicator />
        <p>Minting In Progress...</p>
      </div>
      <img
        src="<https://media2.giphy.com/media/61tYloUgq1eOk/giphy.gif?cid=ecf05e47dg95zbpabxhmhaksvoy8h526f96k4em0ndvx078s&rid=giphy.gif&ct=g>"
        alt="Minting loading indicator"
      />
    </div>
  </div>
</template>

<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  data() {
    return {
      minting: false,
    };
  },
  components: {
    LoadingIndicator,
  },
  methods: {
    async mintCharacterNFTAction(index) {
      if (this.minting) return;
      this.minting = true;
      await this.$store.dispatch("mintCharacterNFT", index);
      this.minting = false;
    },
  },
  async mounted() {
    this.minting = true;
    await this.$store.dispatch("getCharacters");
    this.minting = false;
  },
  computed: {
    characters() {
      return this.$store.getters.characters;
    },
  },
};
</script>

コンポーネントがマウントされたら、をフェッチdefaultCharactersしてビューに表示する必要があります。

アイテムごとに、選択したまたはインデックスmintCharacterNFTに基づいて呼び出されるミントアクションをストアにディスパッチするクリックイベントがあります。characterIdこのアクションをストアに追加しましょう:

async mintCharacterNFT({ commit, dispatch }, characterId) {
  try {
    const connectedContract = await dispatch("getContract");
    const mintTxn = await connectedContract.mintCharacterNFT(characterId);
    await mintTxn.wait();
  } catch (error) {
    console.log(error);
  }
},

前と同じように、ミンティングを担当するスマートコントラクト関数を呼び出します。

しかし、ここに問題があります、私たちは私たちのミントキャラクターを私たちの状態に設定しませんでしたか?スマートコントラクトでの機能を覚えていれば、キャラクターがミントされたらイベントが発生しますので、ご安心くださいCharacterNFTMinted

ですから、私たちが今しなければならないことは、そのイベントを聞いて、そこからキャラクターを設定することです。イベントリスナーを設定するアクションを作成しましょう。

async setupEventListeners({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    if (!connectedContract) return;
    connectedContract.on(
      "CharacterNFTMinted",
      async (from, tokenId, characterIndex) => {
        console.log(
          `CharacterNFTMinted - sender: ${from} tokenId: ${tokenId.toNumber()} characterIndex: ${characterIndex.toNumber()}`
        );
        const characterNFT = await connectedContract.checkIfUserHasNFT();
        console.log(characterNFT);
        commit("setCharacterNFT", transformCharacterData(characterNFT));
        alert(
          `Your NFT is all done -- see it here: <https://testnets.opensea.io/assets/$>{
            state.contract_address
          }/${tokenId.toNumber()}`
        );
      }
    );

  } catch (error) {
    console.log(error);
  }
},

web3でイベントを聞くには、を使用しcontract.on("event_name", callback)ます。

イベント内で、この関数で選択されたユーザーNFTをチェックし、checkIfUserHasNFTそれを自分の状態にコミットします。ユーザーがNFTリンクを表示したい場合、アラートは単なる追加情報です。では、このアクションはどこで呼び出されるべきだと思いますか?

checkNetworkディスパッチの下の接続アクションに追加します。

await dispatch("setupEventListeners");
await dispatch("fetchNFTMetadata");

また、ユーザーがアプリにアクセスしたときに、ユーザーがすでにNFTを持っているかどうかを確認する別のアクションを追加しましょう。

async fetchNFTMetadata({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const txn = await connectedContract.checkIfUserHasNFT();
    if (txn.name) {
      commit("setCharacterNFT", transformCharacterData(txn));
    }
  } catch (error) {
    console.log(error);
  }
},

このアクションはイベントとほとんど同じですが、呼び出されたときにのみチェックしてください。

これでキャラクターの選択が完了しました。戻ってApp.vue、ボスと戦うためにアリーナをセットアップしましょう。で呼び出されたselect-characterの子を変更するApp.vue必要があります。ユーザーがすでにNFTを選択している場合は、アリーナに直接移動する必要があります。

<select-character v-else-if="account && !characterNFT" />
<arena v-else-if="account && characterNFT" />

characterNFT variableは、accountのような計算変数です。

characterNFT() {
  return this.$store.getters.characterNFT;
},

コンポーネントを作成しましょうArena

<template>
  <div class="arena-container">
    <div class="boss-container" v-if="boss">
      <div :class="`boss-content ${attackState}`">
        <h2>🔥 {{ boss.name }} 🔥</h2>
        <div class="image-content">
          <img :src="boss.imageURI" :alt="`Boss ${boss.name}`" />
          <div class="health-bar">
            <progress :value="boss.hp" :max="boss.maxHp" />
            <p>{{ `${boss.hp} / ${boss.maxHp} HP` }}</p>
          </div>
        </div>
      </div>
      <div class="attack-container">
        <button class="cta-button" @click="attackAction">
          {{ `💥 Attack ${boss.name}` }}
        </button>
        <div class="loading-indicator" v-if="attackState === 'attacking'">
          <LoadingIndicator />
          <p>Attacking ⚔️</p>
        </div>
      </div>
    </div>
    <div class="players-container" v-if="characterNFT">
      <div class="player-container">
        <h2>Your Character</h2>
        <div class="player">
          <div class="image-content">
            <h2>{{ characterNFT.name }}</h2>
            <img
              :src="characterNFT.imageURI"
              :alt="`Character
            ${characterNFT.name}`"
            />
            <div class="health-bar">
              <progress :value="characterNFT.hp" :max="characterNFT.maxHp" />
              <p>{{ `${characterNFT.hp} / ${characterNFT.maxHp} HP` }}</p>
            </div>
          </div>
          <div class="stats">
            <h4>{{ `⚔️ Attack Damage: ${characterNFT.attackDamage}` }}</h4>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  components: {
    LoadingIndicator,
  },
  methods: {
    async attackAction() {
      await this.$store.dispatch("attackBoss");
    },
  },
  async mounted() {
    await this.$store.dispatch("fetchBoss");
  },
  computed: {
    boss() {
      return this.$store.getters.boss;
    },
    characterNFT() {
      return this.$store.getters.characterNFT;
    },
    attackState() {
      return this.$store.getters.attackState;
    },
  },
};
</script>

このコンポーネントがマウントされると、ボスをフェッチするアクションと、攻撃ボタンがクリックされたときに別のアクションを呼び出します。ここで、attackState(攻撃/ヒット)の間で変更が行われます。

async fetchBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const bossTxn = await connectedContract.getBigBoss();
    commit("setBoss", transformCharacterData(bossTxn));
  } catch (error) {
    console.log(error);
  }
},
async attackBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    commit("setAttackState", "attacking");
    console.log("Attacking boss...");
    const attackTxn = await connectedContract.attackBoss();
    await attackTxn.wait();
    console.log("attackTxn:", attackTxn);
    commit("setAttackState", "hit");
  } catch (error) {
    console.error("Error attacking boss:", error);
    setAttackState("");
  }
},

attackCompleteそして、私たちの行動の中で私たちのイベントを忘れないでくださいsetupEventListeners、これは上司とプレーヤーを更新しますhp

connectedContract.on(
  "AttackComplete",
  async (newBossHp, newPlayerHp) => {
    console.log(
      `AttackComplete: Boss Hp: ${newBossHp} Player Hp: ${newPlayerHp}`
    );
    let boss = state.boss;
    boss.hp = newBossHp;
    commit("setBoss", boss);    let character = state.characterNFT;
    character.hp = newPlayerHp;
    commit("setCharacterNFT", character);
  }
);

この読み込みインジケーターコンポーネントを追加して、UXを向上させることができます。

<template>
  <div class="lds-ring">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</template><script>
export default {};
</script>

これで、Vue.jsを使用して最初のweb3ゲームが完了しました。vercel私と同じように無料でホストできます。

これが私のアプリと完全なソースコードのGitHubリポジトリです。

このプロジェクトの作成を支援するためのビルドスペースへの大きな叫び声!

プロジェクトを完了するために、このNFTも入手しました。

ハッピーコーディング!

リンク:https ://betterprogramming.pub/create-a-blockchain-game-with-solidity-web3-and-vue-js-c75eed4b49a6

#vuejs  #web3  #solidity 

Teresa  Bosco

Teresa Bosco

1598685221

Vue File Upload Using vue-dropzone Tutorial

In this tutorial, I will show you how to upload a file in Vue using vue-dropzone library. For this example, I am using Vue.js 3.0. First, we will install the Vue.js using Vue CLI, and then we install the vue-dropzone library. Then configure it, and we are ready to accept the file. DropzoneJS is an open source library that provides drag and drops file uploads with image previews. DropzoneJS is lightweight doesn’t depend on any other library (like jQuery) and is  highly customizable. The  vue-dropzone is a vue component implemented on top of Dropzone.js. Let us start Vue File Upload Using vue-dropzone Tutorial.

Vue File Upload Using vue-dropzone

First, install the Vue using Vue CLI.

#vue #vue-dropzone #vue.js #dropzone.js #dropzonejs #vue cli

Aria Barnes

Aria Barnes

1625232484

Why is Vue JS the most Preferred Choice for Responsive Web Application Development?

For more than two decades, JavaScript has facilitated businesses to develop responsive web applications for their customers. Used both client and server-side, JavaScript enables you to bring dynamics to pages through expanded functionality and real-time modifications.

Did you know!

According to a web development survey 2020, JavaScript is the most used language for the 8th year, with 67.7% of people choosing it. With this came up several javascript frameworks for frontend, backend development, or even testing.

And one such framework is Vue.Js. It is used to build simple projects and can also be advanced to create sophisticated apps using state-of-the-art tools. Beyond that, some other solid reasons give Vuejs a thumbs up for responsive web application development.

Want to know them? Then follow this blog until the end. Through this article, I will describe all the reasons and benefits of Vue js development. So, stay tuned.

Vue.Js - A Brief Introduction

Released in the year 2014 for public use, Vue.Js is an open-source JavaScript framework used to create UIs and single-page applications. It has over 77.4 million likes on Github for creating intuitive web interfaces.

The recent version is Vue.js 2.6, and is the second most preferred framework according to Stack Overflow Developer Survey 2019.

Every Vue.js development company is widely using the framework across the world for responsive web application development. It is centered around the view layer, provides a lot of functionality for the view layer, and builds single-page web applications.

Some most astonishing stats about Vue.Js:

• Vue was ranked #2 in the Front End JavaScript Framework rankings in the State of JS 2019 survey by developers.

• Approximately 427k to 693k sites are built with Vue js, according to Wappalyzer and BuiltWith statistics of June 2020.

• According to the State of JS 2019 survey, 40.5% of JavaScript developers are currently using Vue, while 34.5% have shown keen interest in using it in the future.

• In Stack Overflow's Developer Survey 2020, Vue was ranked the 3rd most popular front-end JavaScript framework.

Why is Vue.Js so popular?

• High-speed run-time performance
• Vue.Js uses a virtual DOM.
• The main focus is on the core library, while the collaborating libraries handle other features such as global state management and routing.
• Vue.JS provides responsive visual components.

Top 7 Reasons to Choose Vue JS for Web Application Development

Vue js development has certain benefits, which will encourage you to use it in your projects. For example, Vue.js is similar to Angular and React in many aspects, and it continues to enjoy increasing popularity compared to other frameworks.

The framework is only 20 kilobytes in size, making it easy for you to download files instantly. Vue.js easily beats other frameworks when it comes to loading times and usage.

Take a look at the compelling advantages of using Vue.Js for web app development.

#1 Simple Integration

Vue.Js is popular because it allows you to integrate Vue.js into other frameworks such as React, enabling you to customize the project as per your needs and requirements.

It helps you build apps with Vue.js from scratch and introduce Vue.js elements into their existing apps. Due to its ease of integration, Vue.js is becoming a popular choice for web development as it can be used with various existing web applications.

You can feel free to include Vue.js CDN and start using it. Most third-party Vue components and libraries are additionally accessible and supported with the Vue.js CDN.

You don't need to set up node and npm to start using Vue.js. This implies that it helps develop new web applications, just like modifying previous applications.

The diversity of components allows you to create different types of web applications and replace existing frameworks. In addition, you can also choose to hire Vue js developers to use the technology to experiment with many other JavaScript applications.

#2 Easy to Understand

One of the main reasons for the growing popularity of Vue.Js is that the framework is straightforward to understand for individuals. This means that you can easily add Vue.Js to your web projects.

Also, Vue.Js has a well-defined architecture for storing your data with life-cycle and custom methods. Vue.Js also provides additional features such as watchers, directives, and computed properties, making it extremely easy to build modern apps and web applications with ease.

Another significant advantage of using the Vue.Js framework is that it makes it easy to build small and large-scale web applications in the shortest amount of time.

#3 Well-defined Ecosystem

The VueJS ecosystem is vibrant and well-defined, allowing Vue.Js development company to switch users to VueJS over other frameworks for web app development.

Without spending hours, you can easily find solutions to your problems. Furthermore, VueJs lets you choose only the building blocks you need.

Although the main focus of Vue is the view layer, with the help of Vue Router, Vue Test Utils, Vuex, and Vue CLI, you can find solutions and recommendations for frequently occurring problems.

The problems fall into these categories, and hence it becomes easy for programmers to get started with coding right away and not waste time figuring out how to use these tools.

The Vue ecosystem is easy to customize and scales between a library and a framework. Compared to other frameworks, its development speed is excellent, and it can also integrate different projects. This is the reason why most website development companies also prefer the Vue.Js ecosystem over others.

#4 Flexibility

Another benefit of going with Vue.Js for web app development needs is flexibility. Vue.Js provides an excellent level of flexibility. And makes it easier for web app development companies to write their templates in HTML, JavaScript, or pure JavaScript using virtual nodes.

Another significant benefit of using Vue.Js is that it makes it easier for developers to work with tools like templating engines, CSS preprocessors, and type checking tools like TypeScript.

#5 Two-Way Communication

Vue.Js is an excellent option for you because it encourages two-way communication. This has become possible with the MVVM architecture to handle HTML blocks. In this way, Vue.Js is very similar to Angular.Js, making it easier to handle HTML blocks as well.

With Vue.Js, two-way data binding is straightforward. This means that any changes made by the developer to the UI are passed to the data, and the changes made to the data are reflected in the UI.

This is also one reason why Vue.Js is also known as reactive because it can react to changes made to the data. This sets it apart from other libraries such as React.Js, which are designed to support only one-way communication.

#6 Detailed Documentation

One essential thing is well-defined documentation that helps you understand the required mechanism and build your application with ease. It shows all the options offered by the framework and related best practice examples.

Vue has excellent docs, and its API references are one of the best in the industry. They are well written, clear, and accessible in dealing with everything you need to know to build a Vue application.

Besides, the documentation at Vue.js is constantly improved and updated. It also includes a simple introductory guide and an excellent overview of the API. Perhaps, this is one of the most detailed documentation available for this type of language.

#7 Large Community Support

Support for the platform is impressive. In 2018, support continued to impress as every question was answered diligently. Over 6,200 problems were solved with an average resolution time of just six hours.

To support the community, there are frequent release cycles of updated information. Furthermore, the community continues to grow and develop with backend support from developers.



Wrapping Up

VueJS is an incredible choice for responsive web app development. Since it is lightweight and user-friendly, it builds a fast and integrated web application. The capabilities and potential of VueJS for web app development are extensive.

While Vuejs is simple to get started with, using it to build scalable web apps requires professionalism. Hence, you can approach a top Vue js development company in India to develop high-performing web apps.

Equipped with all the above features, it doesn't matter whether you want to build a small concept app or a full-fledged web app; Vue.Js is the most performant you can rely on.

Original source

 

#vue js development company #vue js development company in india #vue js development company india #vue js development services #vue js development #vue js development companies

Sofia Kelly

Sofia Kelly

1578061020

10 Best Vue Icon Component For Your Vue.js App

Icons are the vital element of the user interface of the product enabling successful and effective interaction with it. In this article, I will collect 10 Vue icon component to bring more interactivity, better UI design to your Vue application.

1. Animated SweetAlert Icons for Vue

A clean and simple Vue wrapper for SweetAlert’s fantastic status icons. This wrapper is intended for users who are interested in just the icons. For the standard SweetAlert modal with all of its bells and whistles, you should probably use Vue-SweetAlert 2

Animated SweetAlert Icons for Vue

Demo: https://vue-sweetalert-icons.netlify.com/

Download: https://github.com/JorgenVatle/vue-sweetalert-icons/archive/master.zip

2. vue-svg-transition

Create 2-state, SVG-powered animated icons.

vue-svg-transition

Demo: https://codesandbox.io/s/6v20q76xwr

Download: https://github.com/kai-oswald/vue-svg-transition/archive/master.zip

3. Vue-Awesome

Awesome SVG icon component for Vue.js, with built-in Font Awesome icons.

Vue-Awesome

Demo: https://justineo.github.io/vue-awesome/demo/

Download: https://github.com/Justineo/vue-awesome/archive/master.zip

4. vue-transitioning-result-icon

Transitioning Result Icon for Vue.js

A scalable result icon (SVG) that transitions the state change, that is the SVG shape change is transitioned as well as the color. Demonstration can be found here.

A transitioning (color and SVG) result icon (error or success) for Vue.

vue-transitioning-result-icon

Demo: https://transitioning-result-icon.dexmo-hq.com/

Download: https://github.com/dexmo007/vue-transitioning-result-icon/archive/master.zip

5. vue-zondicons

Easily add Zondicon icons to your vue web project.

vue-zondicons

Demo: http://www.zondicons.com/icons.html

Download: https://github.com/TerryMooreII/vue-zondicons/archive/master.zip

6. vicon

Vicon is an simple iconfont componenet for vue.

iconfont
iconfont is a Vector Icon Management & Communication Platform made by Alimama MUX.

vicon

Download: https://github.com/Lt0/vicon/archive/master.zip

7. vue-svgicon

A tool to create svg icon components. (vue 2.x)

vue-svgicon

Demo: https://mmf-fe.github.io/vue-svgicon/v3/

Download: https://github.com/MMF-FE/vue-svgicon/archive/master.zip

8. vue-material-design-icons

This library is a collection of Vue single-file components to render Material Design Icons, sourced from the MaterialDesign project. It also includes some CSS that helps make the scaling of the icons a little easier.

vue-material-design-icons

Demo: https://gitlab.com/robcresswell/vue-material-design-icons

Download: https://gitlab.com/robcresswell/vue-material-design-icons/tree/master

9. vue-ionicons

Vue Icon Set Components from Ionic Team

Design Icons, sourced from the Ionicons project.

vue-ionicons

Demo: https://mazipan.github.io/vue-ionicons/

Download: https://github.com/mazipan/vue-ionicons/archive/master.zip

10. vue-ico

Dead easy, Google Material Icons for Vue.

This package’s aim is to get icons into your Vue.js project as quick as possible, at the cost of all the bells and whistles.

vue-ico

Demo: https://material.io/resources/icons/?style=baseline

Download: https://github.com/paulcollett/vue-ico/archive/master.zip

I hope you like them!

#vue #vue-icon #icon-component #vue-js #vue-app