見出し画像

ゲームのレーティングシステムをDartで再現してみた【Dart】

今回はゲームなどで採用されているレーティングシステムをDartで簡潔に再現してみたので紹介します。これからオリジナルアプリやゲームでレーティング又はランキングシステムを開発しようとしている方やシステムに興味のある方の役に立てば幸いです。

レーティングシステムとは

レーティングとは競技や試合の参加者の実力を数値化するためのシステムで、チェスなどで使われるシステムです。最近ではテレビゲームのスプラトゥーンやほかのAPEX LegendsやCall Of DutyなどのPCゲームなどでもレーティングシステムが導入されています。

いつものようにDart Padでコードを書いていきます。

DartでPlayerを作成

Dartクラスでnameとratingのプロパティを含んだクラスを作成します。今回はプレイヤー同士のレーティングに大きな差を作りたくないため、1000までのランダムな整数をratingの項目に代入します。

import 'dart:math';
void main() {}

class Player {
  String name;
  int rating;

  Player(this.name, this.rating);

  // ランダムなレーティングを生成するメソッド
  static int generateRandomRating() {
    Random random = Random();
    return random.nextInt(1000); // 1000までのランダムな整数を生成
  }

  // プレイヤーの情報を文字列として表現するメソッド
  @override
  String toString() {
    return 'Player: $name, Rating: $rating';
  }
}

プレーヤーを作成して一覧をprintする

10人のプレーヤーを作成してprintします。

import 'dart:math';
void main() {
  // プレイヤーのリストを生成する
  List<Player> players = List.generate(10,
      (index) => Player('Player${index + 1}', Player.generateRandomRating()));
  List<Player> matchedPlayers = [];

  // プレイヤーの一覧を表示する
  print('--- Players ---');
  players.forEach((player) => print(player));
  print('---------------------------------------------');
}
...

レーティング用のメソッドを作成

今回の試合はプレイヤー同士が1v1で競技しているという形で進めます。
今回はイロレーティングシステムを採用しています。イロレーティングに関する詳細は参考記事をご覧ください。

...

class EloRatingSystem {
  static const int kFactor = 32;

  static double expectedScore(int rating1, int rating2) {
    return 1 / (1 + pow(10, ((rating2 - rating1) / 400)));
  }

  static void updateRatings(Player winner, Player loser) {
    double expectedWinnerScore = expectedScore(winner.rating, loser.rating);
    double expectedLoserScore = expectedScore(loser.rating, winner.rating);

    int winnerNewRating =
        (winner.rating + kFactor * (1 - expectedWinnerScore)).round();
    int loserNewRating =
        (loser.rating + kFactor * (0 - expectedLoserScore)).round();

    winner.rating = winnerNewRating;
    loser.rating = loserNewRating;
  }
}

各メソッドの説明

  1. kFactor: イロレーティングのK値として知られる定数です。この値は、試合の結果がどれだけレーティングに影響を与えるかを表す値です。通常、32が使用されますが、競技やゲームによって異なる場合があります。

  2. expectedScore: プレイヤーが勝利するかどうかの期待されるスコアを計算します。このスコアは、プレイヤーのレーティングに基づいて、そのプレイヤーが勝利する確率を表します。

  3. updateRatings: 試合の結果に基づいてプレイヤーのレーティングを更新します。まず、勝者と敗者の期待されるスコアを計算し、実際のスコアとの差を使用して新しいレーティングを計算します。勝者のレーティングは増加し、敗者のレーティングは減少します。

マッチングを行う

10人のプレーヤーがいるので合計5試合行われます。1試合ごとに勝者は1人目で敗者は2人目のプレーヤーという形で進めます。

void main() {
  // プレイヤーのリストを生成する
  List<Player> players = List.generate(10,
      (index) => Player('Player${index + 1}', Player.generateRandomRating()));
  List<Player> matchedPlayers = [];

  // プレイヤーの一覧を表示する
  print('--- Players ---');
  players.forEach((player) => print(player));
  print('---------------------------------------------');

  // プレイヤーリストから2人ずつマッチングして新しいリストに追加する
  for (int i = 0; i < players.length; i += 2) {
    if (i + 1 < players.length) {
      Player player1 = players[i];
      Player player2 = players[i + 1];
      // マッチングされた2人のプレイヤーを新しいリストに追加
      matchedPlayers.add(player1);
      matchedPlayers.add(player2);
      // ここで2人のプレイヤーが1 vs 1で対戦する処理を行う

      print('${player1.name} vs ${player2.name}');
      print('${player1.name} wins!');

      print('\nBefore match:');
      print('${player1.name}: rating: ${player1.rating}');
      print('${player2.name}: rating: ${player2.rating}');
      // Player 1 wins against Player 2
      EloRatingSystem.updateRatings(player1, player2);

      print('\nAfter match:');
      print('${player1.name}: rating: ${player1.rating}');
      print('${player2.name}: rating: ${player2.rating}');
      print('---------------------------------------------');
    }
  }
}

プリント結果

--- Players ---
Player: Player1, Rating: 384
Player: Player2, Rating: 691
Player: Player3, Rating: 93
Player: Player4, Rating: 568
Player: Player5, Rating: 731
Player: Player6, Rating: 730
Player: Player7, Rating: 221
Player: Player8, Rating: 113
Player: Player9, Rating: 947
Player: Player10, Rating: 770
---------------------------------------------
Player1 vs Player2
Player1 wins!

Before match:
Player1: rating: 384
Player2: 691

After match:
Player1: 411
Player2: 664
---------------------------------------------
Player3 vs Player4
Player3 wins!

Before match:
Player3: rating: 93
Player4: 568

After match:
Player3: 123
Player4: 538
---------------------------------------------
Player5 vs Player6
Player5 wins!

Before match:
Player5: rating: 731
Player6: 730

After match:
Player5: 747
Player6: 714
---------------------------------------------
Player7 vs Player8
Player7 wins!

Before match:
Player7: rating: 221
Player8: 113

After match:
Player7: 232
Player8: 102
---------------------------------------------
Player9 vs Player10
Player9 wins!

Before match:
Player9: rating: 947
Player10: 770

After match:
Player9: 955
Player10: 762
---------------------------------------------

結果を見てみるとレーティングの変動を確認できます。イロレーティングの特徴によりプレーヤー両者のレーティングの差が大きい場合はレーティング変動率が大きく、レーティングの差が小さいときは変動率が小さくなっています。ただし、勝者のレーティングが敗者のレーティングより高い場合はレーティングがほとんど変動しないか数値が小さくなっている場合が多いです。

全コード共有

import 'dart:math';

void main() {
  // プレイヤーのリストを生成する
  List<Player> players = List.generate(10,
      (index) => Player('Player${index + 1}', Player.generateRandomRating()));
  List<Player> matchedPlayers = [];

  // プレイヤーの一覧を表示する
  print('--- Players ---');
  players.forEach((player) => print(player));
  print('---------------------------------------------');

  // プレイヤーリストから2人ずつマッチングして新しいリストに追加する
  for (int i = 0; i < players.length; i += 2) {
    if (i + 1 < players.length) {
      Player player1 = players[i];
      Player player2 = players[i + 1];
      // マッチングされた2人のプレイヤーを新しいリストに追加
      matchedPlayers.add(player1);
      matchedPlayers.add(player2);
      // ここで2人のプレイヤーが1 vs 1で対戦する処理を行う

      print('${player1.name} vs ${player2.name}');
      print('${player1.name} wins!');

      print('\nBefore match:');
      print('${player1.name}: rating: ${player1.rating}');
      print('${player2.name}: rating: ${player2.rating}');
      // Player 1 wins against Player 2
      EloRatingSystem.updateRatings(player1, player2);

      print('\nAfter match:');
      print('${player1.name}: rating: ${player1.rating}');
      print('${player2.name}: rating: ${player2.rating}');
      print('---------------------------------------------');
    }
  }
}

class Player {
  String name;
  int rating;

  Player(this.name, this.rating);

  // ランダムなレーティングを生成するメソッド
  static int generateRandomRating() {
    Random random = Random();
    return random.nextInt(1000); // 1000までのランダムな整数を生成
  }

  // プレイヤーの情報を文字列として表現するメソッド
  @override
  String toString() {
    return 'Player: $name, Rating: $rating';
  }
}

class EloRatingSystem {
  static const int kFactor = 32;

  static double expectedScore(int rating1, int rating2) {
    return 1 / (1 + pow(10, ((rating2 - rating1) / 400)));
  }

  static void updateRatings(Player winner, Player loser) {
    double expectedWinnerScore = expectedScore(winner.rating, loser.rating);
    double expectedLoserScore = expectedScore(loser.rating, winner.rating);

    int winnerNewRating =
        (winner.rating + kFactor * (1 - expectedWinnerScore)).round();
    int loserNewRating =
        (loser.rating + kFactor * (0 - expectedLoserScore)).round();

    winner.rating = winnerNewRating;
    loser.rating = loserNewRating;
  }
}

まとめ

チェスやテレビゲームで採用されているレーティングシステムですが、このようにプログラミングを通して理解するのも面白いですね。もしオリジナルゲームを開発するときにレーティングシステムを作成するときはこのような形でレーティングシステムを開発するのも良いかもしれませんね。

初心者から始めるプログラミングスクールの紹介

我々Flutterラボは、大阪の梅田にあるコワーキングスペース『ONthe UMEDA』の料金プランとしてモバイルアプリ開発が学べるプログラミングスクールを運営しております。
オンラインではなく対面で学びたい方におすすめです。
※オンラインをご希望の方はFlutterラボのオンラインスクールをおすすめします。

以下のような方に適したプログラミングスクールです。

  • 大阪でFlutterを学びたい方

  • オフラインで現役エンジニアに教わりたい方

  • プログラミングの基礎から学びたい方

  • アプリを開発してみたい方

  • 初心者からスマホアプリをリリースしたい方

スタンダードコース

Flutter学習用のカリキュラムに合わせて、プログラミングの基礎からアプリ開発の応用まで学べるコースです。

プロコース

ご自身で開発したいアプリを、設計からリリースまですべてサポートするコースです。
ぜひ気軽にお問い合わせください。オフラインで受講ご希望の方はこちらからお問い合わせください。

この記事が気に入ったらサポートをしてみませんか?