0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

How to develop a blockchain game. Day6 プレイヤー登録

Last updated at Posted at 2025-01-01

Previous << Day5 - 所持金表示
Next >> Day7 - ゲーム内通貨購入

Why Flow

FLOW(または$FLOW)トークンは、Flowネットワークのネイティブ通貨です。開発者およびユーザーは、FLOWを使用してネットワーク上で取引(transact)を行うことができます。開発者は、ピアツーピア決済(他人同士の決済)、サービス料金徴収、または消費者向け特典(rewards)のために、FLOWを直接アプリに統合することができます。

ということでやっていきます、猿でも分かるP2P(ピアツーピア)決済アプリ開発!

💡もし、エミュレータの起動方法やスマートコントラクトのデプロイについて操作に自信がない場合はこちらを参照してください。

プレイヤーResourceを作成する

リソースはタイトル保有数や戦績、実績の保有者であることを保証するものです。ゲームにはタイトルや戦績などさまざまな情報がありますので、それを保証してくれるものがリソースの存在です。また、リソースを通じて関数を呼び出すと自動的にリソースのIDが取得できるのも利点です。

Day3からHelloWorld.cdcsrc内にコピーしてきてファイル名をAwesomeCardGame.cdcに変更して、flow.jsonの以下の部分を新しいコントラクト名に変更します。

flow.json
  "contracts": {
    "AwesomeCardGame": "./AwesomeCardGame.cdc"
  },
   :
  "deployments": {
    "emulator": {
      "emulator-account": ["AwesomeCardGame"]
    }
  }

AwesomeCardGame.cdcを以下の内容に変更します。

AwesomeCardGame.cdc
import "FlowToken"
import "FungibleToken"

access(all) contract AwesomeCardGame {

  access(self) let playerList: {UInt: CyberScoreStruct}
  access(self) var totalPlayers: UInt
  access(all) let starterDeck: [UInt16]
  access(self) let FlowTokenVault: Capability<&{FungibleToken.Receiver}>
  access(self) let PlayerFlowTokenVault: {UInt: Capability<&{FungibleToken.Receiver}>}

  access(all) resource Player {
    access(all) let player_id: UInt
    access(all) let nickname: String

    init(nickname: String) {
      AwesomeCardGame.totalPlayers = AwesomeCardGame.totalPlayers + 1
      self.player_id = AwesomeCardGame.totalPlayers
      self.nickname = nickname

      AwesomeCardGame.playerList[self.player_id] = CyberScoreStruct(player_name: nickname)
    }
  }

  access(all) struct CyberScoreStruct {
    access(all) let player_name: String
    access(all) var score: [{UFix64: UInt8}]
    access(all) var win_count: UInt
    access(all) var loss_count: UInt
    access(all) var ranking_win_count: UInt
    access(all) var ranking_2nd_win_count: UInt
    access(all) var period_win_count: UInt
    access(all) var period_loss_count: UInt
    access(all) var cyber_energy: UInt8
    access(all) var balance: UFix64

    init(player_name: String) {
      self.player_name = player_name
      self.score = []
      self.win_count = 0
      self.loss_count = 0
      self.ranking_win_count = 0
      self.ranking_2nd_win_count = 0
      self.period_win_count = 0
      self.period_loss_count = 0
      self.cyber_energy = 0
      self.balance = 0.0
    }
  }

  access(all) fun createPlayer(nickname: String, flow_vault_receiver: Capability<&{FungibleToken.Receiver}>): @AwesomeCardGame.Player {
    let player <- create Player(nickname: nickname)

    if (AwesomeCardGame.PlayerFlowTokenVault[player.player_id] == nil) {
      AwesomeCardGame.PlayerFlowTokenVault[player.player_id] = flow_vault_receiver
    }
    return <- player
  }

  init() {
    self.FlowTokenVault = self.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
    self.PlayerFlowTokenVault = {}
    self.starterDeck = [1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
    self.totalPlayers = 0
    self.playerList = {}
  }
}

<&FlowToken.Receiver>ではなく、<&{FungibleToken.Receiver}>となっているのはReceiverがFungibleTokenインタフェース内で定義されているからです。インタフェースの型は、リソース内の複数のフィールド/関数に対してアクセス権を細かく設定することが出来ます。インタフェースの型はインターセクション{}で囲って作成します(これらはJavaなどで一般的です)。FlowTokenにはVaultの実装があり、そのVaultのインタフェースがFungibleTokenに存在するため、FlowToken.Vault内でアクセス出来る機能を絞る必要がある場合は<&{FungibleToken.Receiver}>のようになります。しかし、FlowToken.Vaultリソースである事には変わりはありません。

init内FlowTokenVaultはこのスマートコントラクトをデプロイしているAdminアカウントのFlow入金先で、createPlayer内PlayerFlowTokenVaultはゲームプレイヤーに対する賞金の入金先です。(あくまで入金先であり、FLOWの引き出しはインターセクション型により制限されて出来ません。)

entitlementによるアクセス制限の実装を忘れてました。entitlementについてはDay3を参照して下さい

Playerリソースをアカウントのストレージに保存する

Playerリソースは以下のコードでアカウントのストレージに保存します:

import "AwesomeCardGame"

transaction(nickname: String) {
  prepare(signer: auth(Storage, Capabilities) &Account) {
    let FlowTokenReceiver = signer.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)

    signer.storage.save(<- AwesomeCardGame.createPlayer(nickname: nickname, flow_vault_receiver: FlowTokenReceiver), to: /storage/AwesomeCardGamePlayer)
    let cap = signer.capabilities.storage.issue<&AwesomeCardGame.Player>(/storage/AwesomeCardGamePlayer)
    signer.capabilities.publish(cap, at: /public/AwesomeCardGamePlayer)
  }
  execute {
    log("success")
  }
}

storage.saveでリソースをアカウントストレージに保存し、その後Capabilityを作成します。Capabilityはリソース情報の取得に便利な機能です。リソース内の情報はリソース自体かCapabilityを通じてしか取得できませんが、リソース自体はアカウントのトランザクションでしか取得できないため、Capabilityとして公開することでjavascriptでどこからでも取得することが出来ます。

HTMLとロジックを書く

メイン画面のロジックを以下のように加筆します。

+page.svelte
<script>
import { config, authenticate, unauthenticate, currentUser } from '@onflow/fcl';
import { getBalance, isRegistered } from '../scripts';
import { createPlayer } from '../transactions'
import flowJSON from '../flow.json';
const network = 'emulator';
let walletUser;
let flowBalance;
let hasResource;
config({
  'flow.network': network,
  'accessNode.api': 'http://localhost:8888',
  'discovery.wallet': 'http://localhost:8701/fcl/authn',
}).load({ flowJSON });
currentUser.subscribe(async (user) => {
  walletUser = user;
  if (user.addr) {
    flowBalance = await getBalance(user.addr);
    hasResource = await isRegistered(user.addr);
  }
});
</script>
{#if !walletUser?.addr}
  <button onclick={authenticate}>ログイン</button>
{/if}
{#if walletUser?.addr}
  FLOW残高: {flowBalance}
  <button onclick={unauthenticate}>ログアウト</button>
  {#if !hasResource}
    Playerリソースが作成されていません
   <button onclick={() => createPlayer('ニックネームAAA')}>新規登録</button>
  {/if}
  {#if hasResource}
    Playerリソース作成済みです
  {/if}
{/if}

isRegisteredはリソースを保持済みか確認するメソッドです。リソースを保持済みなのに同じパスにリソースを保存しようとするとエラーになるため、このメソッドでそのエラーを防ぎます。

src/scripts.jsに以下を追記します。

scripts.js
import { query } from "@onflow/fcl";

export const isRegistered = async function (address) {
  const result = await query({
    cadence: `
    import "AwesomeCardGame"
    access(all) fun main(address: Address): &AwesomeCardGame.Player? {
        return getAccount(address).capabilities.get<&AwesomeCardGame.Player>(/public/AwesomeCardGamePlayer).borrow()
    }
    `,
    args: (arg, t) => [arg(address, t.Address)],
  });
  return result;
};

createPlayerはjsからリソース作成トランザクションを実行するためのメソッドです。

src/transactions.jsを新規作成し以下を入力します。

transactions.js
import { mutate, authz } from "@onflow/fcl";

export const createPlayer = async function (nickname) {
  const txId = await mutate({
    cadence: `
      import "AwesomeCardGame"
      import "FlowToken"
      import "FungibleToken"

      transaction(nickname: String) {
        prepare(signer: auth(Storage, Capabilities) &Account) {
          let FlowTokenReceiver = signer.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)

          signer.storage.save(<- AwesomeCardGame.createPlayer(nickname: nickname, flow_vault_receiver: FlowTokenReceiver), to: /storage/AwesomeCardGamePlayer)
          let cap = signer.capabilities.storage.issue<&AwesomeCardGame.Player>(/storage/AwesomeCardGamePlayer)
          signer.capabilities.publish(cap, at: /public/AwesomeCardGamePlayer)
        }
        execute {
          log("success")
        }
      }
    `,
    args: (arg, t) => [arg(nickname, t.String)],
    proposer: authz,
    payer: authz,
    authorizations: [authz],
    limit: 999,
  });
  console.log(txId);
  return txId;
};

トランザクションの最後でtxIdをコンソールログに出すようにしていますが、エミュレータを起動していると、そこにトランザクションのエラー情報も出力されます。

スマートコントラクトをデプロイする

エミュレータが起動している状態で、以下を実行します。

flow project deploy

もしデプロイした後に再デプロイしたい場合は、以下のコマンドを実行して一度コントラクトを削除します。

flow accounts remove-contract AwesomeCardGame

リソースを作成してアカウントのストレージに保存する

これでリソース作成/保存のロジックはすべて準備できましたので、http://localhost:5173/ にアクセスします。

結果:
スクリーンショット 2025-01-01 15.46.34.png

新規登録ボタンをクリックします。

スクリーンショット 2025-01-01 15.47.18.png
APPROVEをクリックします。

トランザクションが完了してisRegisteredメソッドを再び実行すると以下のように表示されます。

結果:
スクリーンショット 2025-01-01 16.20.14.png

リソースを保有しているか確認するメソッドは登録後に数秒単位で実行する必要があります。

これでゲーム賞金の入金先のスマートコントラクトへの保存とPlayerリソースの作成ができました。


この記事のソースコードはこちらにあります。


Previous << Day5 - 所持金表示

Flow BlockchainのCadence version1.0ドキュメント

Next >> Day7 - ゲーム内通貨購入

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?