ブロックチェーンゲームの作り方6 プレイヤー登録

[Previous << 5 - 所持金表示](https://qiita.com/flowcadence/items/8ff1ed3535bc368f7c68)
[Next >> 7 - ゲーム内通貨購入](https://qiita.com/flowcadence/items/2ef6bb711f8e1446814d)

https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a#%E9%96%8B%E7%99%BA%E6%96%B9%E6%B3%95

:::note
**何故WebフレームワークにSvelteを選んだのか？**

ReactやNext.js、Nuxt、Vueは特定の外部ライブラリを使用したい時に設定ファイルに設定が必要です。でも待ってください、Node.jsにはそのような設定ファイルがなくても[fclを使用できました](https://qiita.com/flowcadence/items/27ef976c2f2493146dd6#non-custodial-account)。設定ファイルはWebフレームワーク側の都合なんですね。Svelteにはこの設定が必要ありません。この方が本当のfclの開発上の制約は何なのか、を知ることができます。
また、Svelteは次世代Webフレームワークの筆頭でもあります。

今回はゲームのプレイヤーをスマートコントラクトにトランザクションで登録します。同時にCapabilityを使って情報を取り出します!
:::

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

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

:::note warn
💡もし、エミュレータの起動方法やスマートコントラクトのデプロイについて操作に自信がない場合は[こちら](https://qiita.com/flowcadence/items/8cc25249cda37364cac1)を参照してください。
:::

#### プレイヤーResourceを作成する

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

Day3から`HelloWorld.cdc`を`src`内にコピーしてきてファイル名を`AwesomeCardGame.cdc`に変更して、`flow.json`の以下の部分を新しいコントラクト名に変更します。
```ruby:flow.json
  "contracts": {
    "AwesomeCardGame": "./AwesomeCardGame.cdc"
  },
   :
  "deployments": {
    "emulator": {
      "emulator-account": ["AwesomeCardGame"]
    }
  }
```
`AwesomeCardGame.cdc`を以下の内容に変更します。
```ruby: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インタフェース内で定義されているからです。インタフェースの型はリソース内のフィールド/関数に対して、アクセス権を細かく設定することができます。インタフェースの型は[インターセクション`{}`](https://qiita.com/flowcadence/items/582db1a97e279dfe033f)で囲って作成します(これらはJava言語などで一般的です)。FlowTokenには[Vaultの実装](https://github.com/onflow/flow-core-contracts/blob/master/contracts/FlowToken.cdc#L37C31-L37C46)があり、そのVaultのインタフェースが[FungibleTokenに存在する](https://github.com/onflow/flow-ft/blob/master/contracts/FungibleToken.cdc#L167)ため、FlowToken.Vault内で機能のアクセスを制限する必要がある場合は`<&{FungibleToken.Receiver}>`のように[インターセクション](https://qiita.com/flowcadence/items/582db1a97e279dfe033f)(インタフェースの型)を使用します。しかし、FlowToken.Vaultリソースである事には変わりありません。

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

*あっ、entitlementの実装を忘れてる。。Day7でentitlementを使用します。entitlementについては[Day3を参照して下さい](https://qiita.com/flowcadence/items/2911220eb06a20beea14)。entitlementはVersion1.0の新機能です。私もさほど詳しくないのですが、インタフェースよりも直感的にアクセス制限を実装できるので、その目的で導入されたようです。2024年夏まではインターセクションでアクセス制限するしかなかったです。entitlementの導入によって書くコード量がずっと減りました。*

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

`Player`リソースは以下のコードで作成してアカウントのストレージに保存します:
```ruby
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")
  }
}
```
`storage.save`でリソースをアカウントストレージに保存し、その後`Capability`を作成します。`Capability`はリソース情報の取得に便利な機能です。リソース内の情報はリソース自体か`Capability`を通じてしか取得できませんが、リソース自体はアカウントのトランザクションでしか取得できないため、`Capability`として公開することでjavascriptでどこからでも取得することが出来ます。

#### HTMLとロジックを書く

メイン画面のロジックを以下のように加筆します。
```ruby:+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`に以下を追記します。
```ruby: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`メソッドを呼び出す箇所の実装をします。`createPlayer`はjsから[リソース作成トランザクション](#playerリソースをアカウントのストレージに保存する)を実行するためのメソッドです。

`src/transactions.js`を新規作成し以下を入力します。
```ruby: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;
};
```

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

エミュレータが起動している状態で、以下を実行します。
```ruby
flow project deploy
```
もしデプロイした後に再デプロイしたい場合は、以下のコマンドを実行して一度コントラクトを削除します。
```ruby
flow accounts remove-contract AwesomeCardGame
```
#### リソースを作成してアカウントのストレージに保存する

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

結果:
![スクリーンショット 2025-01-01 15.46.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912821/5a6d883b-304b-3082-cc7f-8067a00ea8ab.png)

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

![スクリーンショット 2025-01-01 15.47.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912821/a50b269c-bfe3-6842-8024-79f90c657100.png)
APPROVEをクリックします。

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

結果:
![スクリーンショット 2025-01-01 16.20.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912821/7b662ae0-5e12-4b7a-4509-9d2ee5b66deb.png)

:::note
リソースを保有しているか確認するメソッドは登録後に数秒単位で実行する必要があります。
:::
これでゲームの賞金の入金先をスマートコントラクトに保存することと`Player`リソースの作成の両方ができました。(ユーザーはトランザクションを躊躇するのでなるべくまとめて行うのです)
***

この記事のソースコードは[こちら](https://github.com/temt-ceo/awesome-cardgame)にあります。

***
<b>[Previous << 5 - 所持金表示](https://qiita.com/flowcadence/items/8ff1ed3535bc368f7c68)
</b>

[Flow blockchain / Cadence version1.0ドキュメント](https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a#%E9%96%8B%E7%99%BA%E6%96%B9%E6%B3%95)

<b>[Next >> 7 - ゲーム内通貨購入](https://qiita.com/flowcadence/items/2ef6bb711f8e1446814d)</b>