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. Day12 勝敗と賞金送付、ランキング

Last updated at Posted at 2025-01-04

Previous << Day11 - ターンチェンジ

Why Flow

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

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

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

勝敗を判定する

Day11 ターンチェンジのturn_changeメソッドの最後のコメントを外します。

  /* judge the winner */
  self.judgeTheWinner(player_id: player_id)

10ターンが終わるときに勝敗を決するので、turn_changeの中に勝敗を判定するロジックを埋め込みます。

また、ランキングや、期間内のランキング報酬配布も、このロジックの中に埋め込みます。こうすることで、世界中で1万ゲーム行われるごとに500FLOWを1位に自動で配布する、と言ったことができます。

💡賞金の配布はそのきっかけとなるトランザクションが行われる時に行う

勝敗と賞金送付、ランキングを私は以下のように実装しました。

AwesomeCardGame.cdc
           :
  access(self) let rankingPeriod: UInt
  access(self) var rankingBattleCount: UInt
  access(self) var ranking1stWinningPlayerId: UInt
  access(self) var ranking2ndWinningPlayerId: UInt

           :

  access(all) struct CyberScoreStruct {
    access(contract) fun set_win_count(new_value: UInt) {
      self.win_count = new_value
    }
    access(contract) fun set_loss_count(new_value: UInt) {
      self.loss_count = new_value
    }
    access(contract) fun set_period_win_count(new_value: UInt) {
      self.period_win_count = new_value
    }
    access(contract) fun set_period_loss_count(new_value: UInt) {
      self.period_loss_count = new_value
    }
    access(contract) fun set_ranking_win_count(new_value: UInt) {
      self.ranking_win_count = new_value
    }
    access(contract) fun set_ranking_2nd_win_count(new_value: UInt) {
      self.ranking_2nd_win_count = new_value
    }
           :
  }
           :
  /*
  ** [Resource] Admin (Game Server Processing)
  */
  access(all) resource Admin {
           :

    access(all) fun judgeTheWinner(player_id: UInt) :Bool {
      pre {
        AwesomeCardGame.battleInfo[player_id] != nil : "This guy doesn't do match."
      }

      if let info = AwesomeCardGame.battleInfo[player_id] {
        if (info.turn > 10) {
          if (info.your_life > info.opponent_life || (info.your_life == info.opponent_life && info.is_first == false)) { /* Second Attack wins if lives are same. */
            let opponent = info.opponent
            AwesomeCardGame.battleInfo.remove(key: player_id)
            AwesomeCardGame.battleInfo.remove(key: opponent)
            if let cyberScore = AwesomeCardGame.playerList[player_id] {
              cyberScore.score.append({getCurrentBlock().timestamp: 1})
              cyberScore.set_win_count(new_value: cyberScore.win_count + 1)
              cyberScore.set_period_win_count(new_value: cyberScore.period_win_count + 1)
              AwesomeCardGame.playerList[player_id] = cyberScore
            }
            if let cyberScore = AwesomeCardGame.playerList[opponent] {
              cyberScore.score.append({getCurrentBlock().timestamp: 0})
              cyberScore.set_loss_count(new_value: cyberScore.loss_count + 1)
              cyberScore.set_period_loss_count(new_value: cyberScore.period_loss_count + 1)
              AwesomeCardGame.playerList[opponent] = cyberScore
            }
            AwesomeCardGame.playerMatchingInfo[player_id] = PlayerMatchingStruct()
            AwesomeCardGame.playerMatchingInfo[opponent] = PlayerMatchingStruct()
            /* Game Reward */
            let reward <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 0.5) as! @FlowToken.Vault
            AwesomeCardGame.PlayerFlowTokenVault[player_id]!.borrow()!.deposit(from: <- reward)
            self.rankingTotalling(playerid: player_id);
            return true
          } else {
            let opponent = info.opponent
            AwesomeCardGame.battleInfo.remove(key: player_id)
            AwesomeCardGame.battleInfo.remove(key: opponent)
            if let cyberScore = AwesomeCardGame.playerList[player_id] {
              cyberScore.score.append({getCurrentBlock().timestamp: 0})
              cyberScore.set_loss_count(new_value: cyberScore.loss_count + 1)
              cyberScore.set_period_loss_count(new_value: cyberScore.period_loss_count + 1)
              AwesomeCardGame.playerList[player_id] = cyberScore
            }
            if let cyberScore = AwesomeCardGame.playerList[opponent] {
              cyberScore.score.append({getCurrentBlock().timestamp: 1})
              cyberScore.set_win_count(new_value: cyberScore.win_count + 1)
              cyberScore.set_period_win_count(new_value: cyberScore.period_win_count + 1)
              AwesomeCardGame.playerList[opponent] = cyberScore
            }
            AwesomeCardGame.playerMatchingInfo[player_id] = PlayerMatchingStruct()
            AwesomeCardGame.playerMatchingInfo[opponent] = PlayerMatchingStruct()
            /* Game Reward */
            let reward <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 0.5) as! @FlowToken.Vault
            AwesomeCardGame.PlayerFlowTokenVault[opponent]!.borrow()!.deposit(from: <- reward)
            self.rankingTotalling(playerid: opponent);
            return true
          }
        } else if (info.turn == 10 && info.is_first_turn == false) { /* 10 turn and second attack */
          if (info.your_life <= info.opponent_life && info.is_first == true) { /* Lose if palyer is First Attack & life is less than opponent */
            let opponent = info.opponent
            AwesomeCardGame.battleInfo.remove(key: player_id)
            AwesomeCardGame.battleInfo.remove(key: opponent)
            if let cyberScore = AwesomeCardGame.playerList[player_id] {
              cyberScore.score.append({getCurrentBlock().timestamp: 0})
              cyberScore.set_loss_count(new_value: cyberScore.loss_count + 1)
              cyberScore.set_period_loss_count(new_value: cyberScore.period_loss_count + 1)
              AwesomeCardGame.playerList[player_id] = cyberScore
            }
            if let cyberScore = AwesomeCardGame.playerList[opponent] {
              cyberScore.score.append({getCurrentBlock().timestamp: 1})
              cyberScore.set_win_count(new_value: cyberScore.win_count + 1)
              cyberScore.set_period_win_count(new_value: cyberScore.period_win_count + 1)
              AwesomeCardGame.playerList[opponent] = cyberScore
            }
            AwesomeCardGame.playerMatchingInfo[player_id] = PlayerMatchingStruct()
            AwesomeCardGame.playerMatchingInfo[opponent] = PlayerMatchingStruct()
            /* Game Reward */
            let reward <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 0.5) as! @FlowToken.Vault
            AwesomeCardGame.PlayerFlowTokenVault[opponent]!.borrow()!.deposit(from: <- reward)
            self.rankingTotalling(playerid: opponent);
            return true
          } else if (info.your_life >= info.opponent_life && info.is_first == false) {// Win if palyer is Second Attack & life is more than opponent
            let opponent = info.opponent
            AwesomeCardGame.battleInfo.remove(key: player_id)
            AwesomeCardGame.battleInfo.remove(key: opponent)
            if let cyberScore = AwesomeCardGame.playerList[player_id] {
              cyberScore.score.append({getCurrentBlock().timestamp: 1})
              cyberScore.set_win_count(new_value: cyberScore.win_count + 1)
              cyberScore.set_period_win_count(new_value: cyberScore.period_win_count + 1)
              AwesomeCardGame.playerList[player_id] = cyberScore
            }
            if let cyberScore = AwesomeCardGame.playerList[opponent] {
              cyberScore.score.append({getCurrentBlock().timestamp: 0})
              cyberScore.set_loss_count(new_value: cyberScore.loss_count + 1)
              cyberScore.set_period_loss_count(new_value: cyberScore.period_loss_count + 1)
              AwesomeCardGame.playerList[opponent] = cyberScore
            }
            AwesomeCardGame.playerMatchingInfo[player_id] = PlayerMatchingStruct()
            AwesomeCardGame.playerMatchingInfo[opponent] = PlayerMatchingStruct()
            /* Game Reward */
            let reward <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 0.5) as! @FlowToken.Vault
            AwesomeCardGame.PlayerFlowTokenVault[player_id]!.borrow()!.deposit(from: <- reward)
            self.rankingTotalling(playerid: player_id);
            return true
          }
        }
        if (info.opponent_life == 0) {
          let opponent = info.opponent
          AwesomeCardGame.battleInfo.remove(key: player_id)
          AwesomeCardGame.battleInfo.remove(key: opponent)
          if let cyberScore = AwesomeCardGame.playerList[player_id] {
            cyberScore.score.append({getCurrentBlock().timestamp: 1})
            cyberScore.set_win_count(new_value: cyberScore.win_count + 1)
            cyberScore.set_period_win_count(new_value: cyberScore.period_win_count + 1)
            AwesomeCardGame.playerList[player_id] = cyberScore
          }
          if let cyberScore = AwesomeCardGame.playerList[opponent] {
            cyberScore.score.append({getCurrentBlock().timestamp: 0})
            cyberScore.set_loss_count(new_value: cyberScore.loss_count + 1)
            cyberScore.set_period_loss_count(new_value: cyberScore.period_loss_count + 1)
            AwesomeCardGame.playerList[opponent] = cyberScore
          }
          AwesomeCardGame.playerMatchingInfo[player_id] = PlayerMatchingStruct()
          AwesomeCardGame.playerMatchingInfo[opponent] = PlayerMatchingStruct()
          /* Game Reward */
          let reward <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 0.5) as! @FlowToken.Vault
          AwesomeCardGame.PlayerFlowTokenVault[player_id]!.borrow()!.deposit(from: <- reward)
          self.rankingTotalling(playerid: player_id);
          return true
        } else if (info.your_life == 0) {
          let opponent = info.opponent
          AwesomeCardGame.battleInfo.remove(key: player_id)
          AwesomeCardGame.battleInfo.remove(key: opponent)
          if let cyberScore = AwesomeCardGame.playerList[player_id] {
            cyberScore.score.append({getCurrentBlock().timestamp: 0})
            cyberScore.set_loss_count(new_value: cyberScore.loss_count + 1)
            cyberScore.set_period_loss_count(new_value: cyberScore.period_loss_count + 1)
            AwesomeCardGame.playerList[player_id] = cyberScore
          }
          if let cyberScore = AwesomeCardGame.playerList[opponent] {
            cyberScore.score.append({getCurrentBlock().timestamp: 1})
            cyberScore.set_win_count(new_value: cyberScore.win_count + 1)
            cyberScore.set_period_win_count(new_value: cyberScore.period_win_count + 1)
            AwesomeCardGame.playerList[opponent] = cyberScore
          }
          AwesomeCardGame.playerMatchingInfo[player_id] = PlayerMatchingStruct()
          AwesomeCardGame.playerMatchingInfo[opponent] = PlayerMatchingStruct()
          /* Game Reward */
          let reward <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 0.5) as! @FlowToken.Vault
          AwesomeCardGame.PlayerFlowTokenVault[opponent]!.borrow()!.deposit(from: <- reward)
          self.rankingTotalling(playerid: opponent);
          return true
        }
      }
      return false
    }

    /* Totalling Ranking values. */
    access(all) fun rankingTotalling(playerid: UInt) {
      AwesomeCardGame.rankingBattleCount = AwesomeCardGame.rankingBattleCount + 1;
      if let cyberScore = AwesomeCardGame.playerList[playerid] {
        /* When this game just started */
        if (AwesomeCardGame.ranking2ndWinningPlayerId == 0 || AwesomeCardGame.ranking1stWinningPlayerId == 0) {
          if (AwesomeCardGame.ranking1stWinningPlayerId == 0) {
            AwesomeCardGame.ranking1stWinningPlayerId = playerid;
          } else if(AwesomeCardGame.ranking2ndWinningPlayerId == 0) {
            AwesomeCardGame.ranking2ndWinningPlayerId = playerid;
          }
        } else {
          for player_id in AwesomeCardGame.playerList.keys {
            if let score = AwesomeCardGame.playerList[player_id] {
              if (score.win_count + score.loss_count > 0) {
                if (player_id != AwesomeCardGame.ranking2ndWinningPlayerId && player_id != AwesomeCardGame.ranking1stWinningPlayerId) {
                  if let rank2ndScore = AwesomeCardGame.playerList[AwesomeCardGame.ranking2ndWinningPlayerId] { /* If it's equal, first come first served. */
                    if (AwesomeCardGame.calcPoint(win_count: rank2ndScore.period_win_count, loss_count: rank2ndScore.period_loss_count) < AwesomeCardGame.calcPoint(win_count: cyberScore.period_win_count, loss_count: cyberScore.period_loss_count)) {
                      if let rank1stScore = AwesomeCardGame.playerList[AwesomeCardGame.ranking1stWinningPlayerId] {
                        if (AwesomeCardGame.calcPoint(win_count: rank1stScore.period_win_count, loss_count: rank1stScore.period_loss_count) < AwesomeCardGame.calcPoint(win_count: cyberScore.period_win_count, loss_count: cyberScore.period_loss_count)) {
                          AwesomeCardGame.ranking2ndWinningPlayerId = AwesomeCardGame.ranking1stWinningPlayerId;
                          AwesomeCardGame.ranking1stWinningPlayerId = player_id;
                        } else {
                          AwesomeCardGame.ranking2ndWinningPlayerId = player_id;
                        }
                      }
                    }
                  }
                } else if (player_id != AwesomeCardGame.ranking1stWinningPlayerId) {
                  if let rank1stScore = AwesomeCardGame.playerList[AwesomeCardGame.ranking1stWinningPlayerId] {
                    if (AwesomeCardGame.calcPoint(win_count: rank1stScore.period_win_count, loss_count: rank1stScore.period_loss_count) < AwesomeCardGame.calcPoint(win_count: cyberScore.period_win_count, loss_count: cyberScore.period_loss_count)) { /* If it's equal, first come first served. */
                      AwesomeCardGame.ranking2ndWinningPlayerId = AwesomeCardGame.ranking1stWinningPlayerId;
                      AwesomeCardGame.ranking1stWinningPlayerId = player_id;
                    }
                  }
                }
              }
            }
          }
        }
      }
      if (AwesomeCardGame.rankingBattleCount >= AwesomeCardGame.rankingPeriod) {
        /* Initialize the ranking win count. */
        for playerId in AwesomeCardGame.playerList.keys {
          if let score = AwesomeCardGame.playerList[playerId] {
            score.set_period_win_count(new_value: 0)
            score.set_period_loss_count(new_value: 0);
            AwesomeCardGame.playerList[playerId] = score; /* Save */
          }
        }
        /* Initialize the count. */
        AwesomeCardGame.rankingBattleCount = 0;
        /* Pay ranking reward(20 $FLOW) */
        if let rank1stScore = AwesomeCardGame.playerList[AwesomeCardGame.ranking1stWinningPlayerId] {
          rank1stScore.set_ranking_win_count(new_value: rank1stScore.ranking_win_count + 1)
          AwesomeCardGame.playerList[AwesomeCardGame.ranking1stWinningPlayerId] = rank1stScore; /* Save */
          let reward1st <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 20.0) as! @FlowToken.Vault
          AwesomeCardGame.PlayerFlowTokenVault[AwesomeCardGame.ranking1stWinningPlayerId]!.borrow()!.deposit(from: <- reward1st)
        }
        /* Pay ranking reward(10 $FLOW) */
        if let rank2ndScore = AwesomeCardGame.playerList[AwesomeCardGame.ranking2ndWinningPlayerId] {
          rank2ndScore.set_ranking_2nd_win_count(new_value: rank2ndScore.ranking_2nd_win_count + 1)
          AwesomeCardGame.playerList[AwesomeCardGame.ranking2ndWinningPlayerId] = rank2ndScore; // Save
          let reward1st <- AwesomeCardGame.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: /storage/flowTokenVault)!.withdraw(amount: 10.0) as! @FlowToken.Vault
          AwesomeCardGame.PlayerFlowTokenVault[AwesomeCardGame.ranking2ndWinningPlayerId]!.borrow()!.deposit(from: <- reward1st)
        }
      }
    }
  }
           :

  access(all) fun calcPoint(win_count: UInt, loss_count: UInt): UInt {
    if ((win_count + loss_count) > 25) {
      return UInt(UFix64(win_count) / UFix64(win_count + loss_count) * 50.0) + win_count;
    } else if ((win_count + loss_count) > 5) {
      return UInt(UFix64(win_count) / UFix64(win_count + loss_count) * 20.0) + win_count;
    } else {
      return UInt(UFix64(win_count) / UFix64(win_count + loss_count) * 10.0) + win_count;
    }
  }
           :

    self.rankingPeriod = 1000
    self.rankingBattleCount = 0
    self.ranking1stWinningPlayerId = 0
    self.ranking2ndWinningPlayerId = 0
  }
}

rankingPeriodrankingBattleCountは新しいプロパティの為、この場合、コントラクトのアップデートができません。エミュレータを再起動して、デプロイからやり直します。

ターンが制限ターンを超える

10ターンが終了した時点でライフの多い方か、同ライフの場合は後攻の勝ちになります。(カードゲームは先に攻撃できる方が有利な為)

10ターンまでターン交代をするのは大変なので、3ターンでやってみます。judgeTheWinnerの判定箇所を10から3に変更してコントラクトを更新します。

ゲームが終了するまでに必要なコマンドは以下:

/* 対戦相手のマッチング */
node ../backend/send_to_decide_who_to_play_against.js 1
node ../backend/send_to_decide_who_to_play_against.js 2

/* ゲーム開始 */
node ../backend/send_game_start.js 1 "[23,10,9,9]"
node ../backend/send_game_start.js 2 "[14,2,1,11]"

/* ターン交代 */
node ../backend/send_change_turn.js 1 "{}"
node ../backend/send_change_turn.js 2 "{}"
node ../backend/send_change_turn.js 1 "{}"
node ../backend/send_change_turn.js 2 "{}"
node ../backend/send_change_turn.js 1 "{}"

ここで決着がつくはずです同点であれば後攻の勝ちです

3ターン先攻まで進めました。
スクリーンショット 2025-01-04 13.50.26.png

写真のプレイヤーは後攻でFLOW残高が998.0あります。次にターン交代があると勝敗が決します。

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

ゲームが終了し、FLOWが998.5に増えています。(賞金は0.5FLOWです。)

ちなみにAdminは以下のように0.5FLOW減っています。

スクリーンショット 2025-01-04 13.57.43.png

ランキングで入賞する

ランキングは期間内のランキングと、全期間を通じてのランキングが存在します。期間内のランキングはその期間で1位か2位になると、賞金が20FLOW10FLOWが得られます。その期間はrankingPeriodで決めてあり、1000ゲームがセットされています。

全世界で1000ゲームごとにランキング上位者に対して賞金が即座に振り込まれる仕様です。

テストのために5試合毎のランキングにして確認します。

結果:
5試合目3ターン目のPlayer2:
スクリーンショット 2025-01-04 14.31.53.png

5試合目終了後
スクリーンショット 2025-01-04 14.33.01.png

ちょっとバグがあったみたいで、1位の賞金と2位の賞金どちらも受け取ってしまっていますが、30FLOWを受け取っています。

もちろん、5試合毎にこんなに賞金を払っていては、Adminは赤字になってしまいますが...
スクリーンショット 2025-01-04 14.40.45.png

30FLOWの赤字です。

以上です。

Conclusion:
Cadenceを使えば、簡単に賞金制ゲーム(ESports)を作れることが分かってもらえたと思います。

その他にもピアツーピア決済アプリを誰でも、それこそ子供でも一人で作ってお金を稼ぐことができる事を知ってもらいたかったです。


もっと高度なGraphQLによる対戦相手と情報を共有しながらトランザクションを実施したいケースの場合は、こちらが参考になります。(本は薄めですが、無駄を省いてピンポイントでコードを参照出来るようにする為です、レビュー滅茶甘めで付けていただけると喜びます😭)


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


Previous << Day11 - ターンチェンジ

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

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?