プリコネRはキャラコンプにいくら必要か検証してみた

タイトルの通り正式タイトル「プリンセスコネクト!Re:Dive」は、一体いくらお金を払えば登場するキャラクター全員をコンプリートできるのでしょうか?さすがに、実際にお金を払って体当たりで確認する訳にいかないため、検証用のプログラムを作成し、シミュレーションしてみました。

一応補足です。このゲーム内には無償ジュエル獲得や、ガチャ以外からのキャラクター獲得の方法も用意されています。ただし、ここではあくまでキャラクターをお金を払ってガチャを引くのみに絞って検証します。したがって、攻略に関係するような情報は乗っていません。

2018年4月25日キャラクター数を最新版に改定

2018年6月03日キャラクター数を最新版に改定

仕様の確認

まずは仕様の確認です。

最近は当たる確率がゲーム内に出るようになっていて確率自体は簡単に確認できます。

10連ガチャの仕様は、確認したところ以下のようです。また、単発のガチャは1~9キャラ目の計算と同じです。

  • 1~9キャラ目
    • ★★★:2%
    • ★★:18%
    • ★:80%
  • 10キャラ目
    • ★★★:2%
    • ★★:98%

キャラクターの出現はランク内で均等みたいです。キャラクターの人数は2018年3月3日現在以下の通りです。

  • キャラクター
    • ★★★が14人15人 17人で均等に出現する
    • ★★が15人, 17人, 18人で均等に出現する
    • ★が10人で均等に出現する

で、お金まわりは、こんな感じに設定されています。

  • 課金回りの仕様
    • 1ジュエル2円(割引を考慮しない)
    • 10連ガチャは1500ジュエル、=3000円
    • 通常ガチャは150ジュエル、=300円

通常ガチャ(ピックアップではない)は、★★★キャラは2%を14人15人, 17人で均等に割って、0.1176%のような計算方法になっているようです。プログラム的にはまず★のランクを決定し、そのランク内でランダムにキャラが出現するとしてよさそうです。若干この時点で結果が薄く見えている感じす。

ピックアップガチャについて

★3キャラクターの排出率2%は変わらず、その中で0.1176%が0.7%に上がることをピックアップと呼んでいるようです。ピックアップ対象外の★3キャラクターの出現率が下がっているので総合的にはコンプしにくいため今回の検討からは外しています。(★3自体が出やすくなっているのではないです。確率表示してるとはいえ誤解を生む原因ですね

結果

では、シミュレーション結果です。

10連ガチャを10万人にコンプするまで引いてもらう

とりあえず10万人に引いてもらいました。以下が結果です。

f:id:Takachan:20180603200913p:plain

過去のグラフと比べると山が低くなっています。また、最低回数が増加し、当選最大回数も大きく右のほうに伸びているのが見て取れます。

  • 最小回数:58回(17万4000円)
  • 最大回数:1218回(365万4000円)
  • 平均回数:290回(87万円)
  • 中央値:272回(81万6000円)

前回調査時から、★3のキャラクターが2人増えたため最小回数が、6回、中央値が41回増えています。★3が一人増えるごとに20回分回す回数が増えるという結果になっていますが、よく考えればキャラ一人当たりの確率が1%(本当はもっと低いですが)なので順当といえます。

最多当選となる山の頂上は237回で487人が当選しています。

★3のキャラクターが追加された影響で、全体的には1.1倍程度金額が増しています。

確率的には4-5回やればコンプする人が出る可能性ありますが、数千億分の1程度なのでたったの10万回では出ていません。

10連ガチャを10万人にコンプするまで引いてもらう(過去データ)

以下、以前(キョウカ、イリヤ実装以前)の結果です。

f:id:Takachan:20180425235216p:plain

  • 最小回数:52回(15万6000円)
  • 最大回数:1057回(317万1000円)
  • 平均回数:247回(74万1000円)
  • 中央値:230回(69万円)

最多当選となる山の頂上は204回で576人が当選しています。

以下、以前(ジュン実装時点)の結果です。

f:id:Takachan:20180303172954p:plain

  • 最小回数:38回(11万4000円)
  • 最大回数:950回(285万円)
  • 平均回数:226回(67万8000円)
  • 中央値:210回(63万円)

最多当選となる山の頂上は182回で605人が当選しています。

通常ガチャを50万人にコンプするまで引いてもらう

今度は通常ガチャのみでで、どくらい引いたらコンプするのか引いてもらいました。今度は50万人でチャレンジします。

f:id:Takachan:20180603214653p:plain

  • 最小回数:472回(14万1600円)
  • 最大回数:14700回(441万円)
  • 平均回数:2897回(86万9100円)
  • 中央値:2711回(81万3300円)

最多当選となる山の頂上は263回で2599人が当選しています。

この結果から、通常ガチャでも10連ガチャでもフルコンプまでの平均額がほぼ同じ結果が得られました。

10連ガチャと通常ガチャで★3のキャラの出現率が同じなのでこのような結果になったと思われます。特に10キャラ目ボーナスは比較的獲得しやすい★2のキャラクターの出現率がアップするだで、★3の出現率が変わらないため、回数に影響しなかったと考えられます。

このことからコンプリートを目指したい方は通常ガチャでも問題ないと思います。ただし重複キャラクターが魔法石?に交換されるので魔法石の数は違いが出ると思われます。

通常ガチャを50万人にコンプするまで引いてもらう(過去データ)

以下、以前(キョウカ、イリヤ実装以前)の結果です。

f:id:Takachan:20180426004214p:plain

  • 最小回数:427回(12万8100円)
  • 最大回数:11013回(330万3900円)
  • 平均回数:2463回(73万8900円)
  • 中央値:2299回(68万9700円)

最多当選となる山の頂上は2138回で312人が当選しています。

以下、以前(ジュン実装時点)の結果です。

f:id:Takachan:20180303175652p:plain

  • 最小回数:374回(11万2200円)
  • 最大回数:11189回(335万6700円)
  • 平均回数:2252回(67万5600円)
  • 中央値:2102回(63万600円)

最多当選となる山の頂上は1848回で320人が当選しています。

まとめ

なんとなく感じていましたが、かなり結構お金がかかりますね。

このゲームかわいい女の子がなんとアニメーションで動きます。内容も大変丁寧かつリッチに作りこまれているの印象です。ただし、キャラクター毎にシナリオが存在するため全てを余すところなく見たいと思うとSteamでビッグタイトルがN百本買えてしまう程度の金銭が必要になることが分かりました。

中にはシナリオが雑なキャラもいるみたいですが…

まとめ(2)

もう少し踏み込むと、このゲーム、

  • ★3キャラクター排出率が2%固定(これは体感ではかなり出にくい)のガチャの設置(ピックアップも同じ
  • ★3キャラクター個々の出現率は1回のガチャ当たりで0.1176% ≒ 850分の1は体感では「絶対に出ない」水準
  • ガチャ以外の高レアリティキャラクター獲得の代替手段の敷居が高めの数値設定
  • 各種消費財への課金機能(スタミナ、間接的に、マナ・石類)
  • 強化に必要なアイテムドロップもかなり確率が低い(=スタミナ必要)
  • ソーシャル要素(他プレイヤーとの戦闘)は課金額に比例*1

などなど、国内ソーシャルゲーム業界でありがちな、ソシャゲの論理を前面に押し出して、比較的多額の課金額を要求する仕組みがゲーム全体に隙無く実装されていて結構エグいです。

また、ガチャで目的のキャラが出ずに熱くなってお金をつぎ込んでも出ないものは『絶対に出ない』(2%~0.1176%というのは本当にそういう数字です)ため、熱くなりすぎないようにプレイする必要があると思います。最近は人数が増えてきたので特にそうです。

というか、1回300円で、上限のない850分の1のガチャを引かせて他人と競わせるというのはかなりインパクトが高いです。

検証プログラムの説明

ここからは検証プログラムのコードの解説です。興味ある人だけご確認ください。

まずはキャラクターの情報をプログラムに起こします。ここは簡単にランクごとにリストを作って名前を管理します。

/// <summary>
/// キャラクターのデータを保持するデータクラス
/// </summary>
public static class GameData
{
    /// <summary>
    /// ★★★のキャラクターのリスト
    /// </summary>
    private static IList<string> _HighRarityList { get; } = new List<string>()
    {
        "★★★アンナ",
        "★★★マホ",
        "★★★リノ",
        "★★★ハツネ",
        "★★★イオ",
        "★★★サレン",
        "★★★ノゾミ",
        "★★★ニノン",
        "★★★アキノ",
        "★★★マコト",
        "★★★シズル",
        "★★★モニカ",
        "★★★ジータ",
        "★★★ジュン",
        "★★★アリサ", // 2018.03.31 Add
        "★★★キョウカ", // 2018.04.30 Add
        "★★★イリヤ" // 2018.05.31 Add
    };

    /// <summary>
    /// ★★のキャラクターリスト
    /// </summary>
    private static IList<string> _MiddleRarityList { get; } = new List<string>()
    {
        "★★アカリ",
        "★★ミヤコ",
    // 以下略

    /// <summary>
    /// ランクに応じたリストを取得
    /// </summary>
    public static IList<string> GetList(int rank)
    {
        switch (rank)
        {
            case 1:
                return _LowRarityList;
            case 2:
                return _MiddleRarityList;
            case 3:
                return _HighRarityList;
            default:
                throw new NotSupportedException($"This rank is not suported = ({rank})");
        }
    }
}

次にガチャ本体です。1~9回目と10回目のガチャを別のクラスにして実装しています。

/// <summary>
/// ガチャを引く行為を表す抽象クラス
/// </summary>
public abstract class AbstractGachaSimulation
{
    protected Random Rand { get; } = new Random();

    /// <summary>
    /// ガチャを1回引いて名前を取得する
    /// </summary>
    public string RollGacha()
    {
        IList<string> list = GameData.GetList(this.GetRank());
        return list[this.Rand.Next(0, list.Count)];
    }

    /// <summary>
    /// キャラクターのランクを取得します。1~3で星の数に対応
    /// </summary>
    protected abstract int GetRank();
}

/// <summary>
/// 10連ガチャの10回目のガチャを表す
/// </summary>
public class SpecialGachaSimulation : AbstractGachaSimulation
{
    //
    // 5億回試行してこんな感じの確率分布
    //
    // ★の個数=0, 0%
    // ★★の個数 = 489897599, 97.9795198%
    // ★★★の個数 = 10102401, 2.0204802%
    //
    protected override int GetRank()
    {
        return this.Rand.Next(1, 100) - 1 >= 98 - 1 ? 3 : 2;
    }
}

/// <summary>
/// 普通のガチャ・10連ガチャの1~9回目のガチャを表す
/// </summary>
public class NormalGachaSimulation : AbstractGachaSimulation
{
    //
    // 10億回試行してこんな感じの確率分布
    //
    // ★の個数=797984891, 79.7984891%
    // ★★の個数 = 181808654, 18.1808654 %
    // ★★★の個数 = 20206455, 2.0206455 %
    //
    protected override int GetRank()
    {
        int rand = this.Rand.Next(1, 100) - 1;

        if (rand < 80 - 1)
        {
            return 1;
        }

        if(rand >= 80 -1 && rand < 98 - 1)
        {
            return 2;
        }

        return 3;
    }
}

最後に検証プログラムです。指定した人数がキャラクターをコンプリートするまでガチャを引き続け、最後に結果を出力します。

internal class AppMain
{
    // ガチャオブジェクトを作成しておく
    private static NormalGachaSimulation normalGacha = new NormalGachaSimulation();
    private static SpecialGachaSimulation spGacha = new SpecialGachaSimulation();

    public static void Main(string[] args)
    {
        // 結果を集計するテーブル
        var table = new SortedDictionary<int, int>();
        for(int i = 0; i < 1; i++) // 何人で引くか
        {
            int cnt = rollGachaUntilComplete();

            if (!table.ContainsKey(cnt))
            {
                table[cnt] = 0;
            }
            table[cnt]++;
        }

        // 結果をCSVに書き出す
        using (FileStream file = File.Create(@"d:\result.csv"))
        using (var sw = new StreamWriter(file))
        {
            foreach (KeyValuePair<int, int> item in table)
            {
                sw.WriteLine(item.Key + ", " + item.Value);
            }
        }
    }

    private static int rollGachaUntilComplete()
    {
        // 取得したキャラクターを記憶しておくテーブル
        var acquiredCharacterTable = new Dictionary<string, int>();

        // テーブルを全員当選ゼロ回で初期化
        foreach (string item in GameData.GetList(1))
        {
            acquiredCharacterTable[item] = 0;
        }

        foreach (string item in GameData.GetList(2))
        {
            acquiredCharacterTable[item] = 0;
        }

        foreach (string item in GameData.GetList(3))
        {
            acquiredCharacterTable[item] = 0;
        }

        // ガチャを引いた回数
        int gachaCnt = 1;

        while (true)
        {
            // 10連ガチャを引く
            for (int i = 0; i < 9; i++)
            {
                acquiredCharacterTable[normalGacha.RollGacha()]++; // 1~9回目
            }

            acquiredCharacterTable[spGacha.RollGacha()]++; // 10回目

            // 結果を確認して全員獲得出来たら終了
            if (!isCompleted(acquiredCharacterTable))
            {
                gachaCnt++;
                continue;
            }

            break;
        }
        writeResult(gachaCnt, acquiredCharacterTable);
        return gachaCnt;
    }

    // 全員獲得できたか確認する
    private static bool isCompleted(Dictionary<string, int> table)
    {
        foreach (KeyValuePair<string, int> pair in table)
        {
            if (pair.Value == 0)
            {

                return false;
            }
        }

        return true;
    }

    // 結果を表示する
    private static void writeResult(int cnt, Dictionary<string, int> table)
    {
        Console.WriteLine($"ガチャを引いた回数 = {cnt}回, {cnt * 3000}円");
        Console.WriteLine("獲得したキャラクター");
        foreach (KeyValuePair<string, int> pair in table)
        {
            Console.WriteLine($"{pair.Key} : {pair.Value}");
        }

        Console.ReadLine();
    }
}

このコードを実行するとこんな感じの出力になります。1人がコンプするまでの結果です。

この人はちょっと運が悪かったみたいですね。これだけ回しても★3のイリヤが1体って・・・

ガチャを引いた回数 = 468回, 140万4000円
獲得したキャラクター
★ヒヨリ : 349
★レイ : 330
★ミソギ : 337
★クルミ : 323
★ヨリ : 363
★スズメ : 350
★ユカリ : 300
★アオイ : 356
★ミサキ : 360
★リマ : 338
★★アカリ : 59
★★ミヤコ : 73
★★ユキ : 63
★★スズナ : 54
★★カオリ : 79
★★ミミ : 76
★★アヤネ : 67
★★エリコ : 63
★★シノブ : 60
★★マヒル : 66
★★シオリ : 75
★★チカ : 70
★★クウカ : 63
★★タマキ : 64
★★ミフユ : 71
★★ミツキ : 76
★★リン : 69
★★ミサト : 47
★★★アンナ : 6
★★★マホ : 4
★★★リノ : 5
★★★ハツネ : 4
★★★イオ : 8
★★★サレン : 3
★★★ノゾミ : 7
★★★ニノン : 1
★★★アキノ : 6
★★★マコト : 4
★★★シズル : 2
★★★モニカ : 7
★★★ジータ : 5
★★★ジュン : 9
★★★アリサ : 4
★★★キョウカ : 3
★★★イリヤ : 1

本番ではこれを複数人で引て結果を集計しています。

コード全体

昔書いたガチャの記事のリンクと今回の検証コードの全体を以下に置いておきます。

takachan.hatenablog.com

using System;
using System.Collections.Generic;
using System.IO;

namespace PcredGachaSim
{
    internal class AppMain
    {
        // ガチャオブジェクトを作成しておく
        private static NormalGachaSimulation normalGacha = new NormalGachaSimulation();
        private static SpecialGachaSimulation spGacha = new SpecialGachaSimulation();

        public static void Main(string[] args)
        {
            // 結果を集計するテーブル
            var table = new SortedDictionary<int, int>();
            for (int i = 0; i < 500000; i++)
            {
                int cnt = rollGachaUntilComplete();

                if (!table.ContainsKey(cnt))
                {
                    table[cnt] = 0;
                }
                table[cnt]++;
            }

            // 結果をCSVに書き出す
            using (FileStream file = File.Create(@"d:\result_1.csv"))
            using (var sw = new StreamWriter(file))
            {
                foreach (KeyValuePair<int, int> item in table)
                {
                    sw.WriteLine(item.Key + ", " + item.Value);
                }
            }

            // 結果をCSVに書き出す
            using (FileStream file = File.Create(@"d:\result_2.csv"))
            using (var sw = new StreamWriter(file))
            {
                foreach (KeyValuePair<int, int> pair in table)
                {
                    for (int i = 0; i < pair.Value; i++)
                    {
                        sw.WriteLine(pair.Key);
                    }
                }
            }
        }

        private static int rollGachaUntilComplete()
        {
            // 取得したキャラクターを記憶しておくテーブル
            var acquiredCharacterTable = new Dictionary<string, int>();

            // テーブルを全員当選ゼロ回で初期化
            foreach (string item in GameData.GetList(1))
            {
                acquiredCharacterTable[item] = 0;
            }

            foreach (string item in GameData.GetList(2))
            {
                acquiredCharacterTable[item] = 0;
            }

            foreach (string item in GameData.GetList(3))
            {
                acquiredCharacterTable[item] = 0;
            }

            // ガチャを引いた回数
            int gachaCnt = 1;

            while (true)
            {
                //// 通常ガチャ版
                acquiredCharacterTable[normalGacha.RollGacha()]++;

                //// 10連ガチャ版
                //for (int i = 0; i < 9; i++)
                //{
                //    acquiredCharacterTable[normalGacha.RollGacha()]++; // 1~9回目
                //}

                //acquiredCharacterTable[spGacha.RollGacha()]++; // 10回目

                // 結果を確認して全員獲得出来たら終了
                if (!isCompleted(acquiredCharacterTable))
                {
                    gachaCnt++;
                    continue;
                }

                break;
            }

            //writeResult(gachaCnt, acquiredCharacterTable);

            return gachaCnt;
        }

        // 全員獲得できたか確認する
        private static bool isCompleted(Dictionary<string, int> table)
        {
            foreach (KeyValuePair<string, int> pair in table)
            {
                if (pair.Value == 0)
                {

                    return false;
                }
            }

            return true;
        }

        // 結果を表示する
        private static void writeResult(int cnt, Dictionary<string, int> table)
        {
            Console.WriteLine($"ガチャを引いた回数 = {cnt}回, {cnt * 3000}円");
            Console.WriteLine("獲得したキャラクター");
            foreach (KeyValuePair<string, int> pair in table)
            {
                Console.WriteLine($"{pair.Key} : {pair.Value}");
            }

            Console.ReadLine();
        }
    }

    /// <summary>
    /// キャラクターのデータを保持するデータクラス
    /// </summary>
    public static class GameData
    {
        /// <summary>
        /// ★★★のキャラクターのリスト
        /// </summary>
        private static IList<string> _HighRarityList { get; } = new List<string>()
        {
            "★★★アンナ",
            "★★★マホ",
            "★★★リノ",
            "★★★ハツネ",
            "★★★イオ",
            "★★★サレン",
            "★★★ノゾミ",
            "★★★ニノン",
            "★★★アキノ",
            "★★★マコト",
            "★★★シズル",
            "★★★モニカ",
            "★★★ジータ",
            "★★★ジュン",
            "★★★アリサ", // 2018.03.31 Add
            "★★★キョウカ", // 2018.04.30 Add
            "★★★イリヤ" // 2018.05.31 Add
        };

        /// <summary>
        /// ★★のキャラクターリスト
        /// </summary>
        private static IList<string> _MiddleRarityList { get; } = new List<string>()
        {
            "★★アカリ",
            "★★ミヤコ",
            "★★ユキ",
            "★★スズナ",
            "★★カオリ",
            "★★ミミ",
            "★★アヤネ", // Add
            "★★エリコ",
            "★★シノブ",
            "★★マヒル",
            "★★シオリ",
            "★★チカ",
            "★★クウカ",
            "★★タマキ",
            "★★ミフユ",
            "★★ミツキ",
            "★★リン", // 2018.03.15 Add
            "★★ミサト" // 2018.05.15(?) Add
        };

        /// <summary>
        /// ★のキャラクターリスト
        /// </summary>
        private static IList<string> _LowRarityList { get; } = new List<string>()
        {
            "★ヒヨリ",
            "★レイ",
            "★ミソギ",
            "★クルミ",
            "★ヨリ",
            "★スズメ",
            "★ユカリ",
            "★アオイ",
            "★ミサキ",
            "★リマ",
        };

        /// <summary>
        /// ランクに応じたリストを取得
        /// </summary>
        public static IList<string> GetList(int rank)
        {
            switch (rank)
            {
                case 1:
                    return _LowRarityList;
                case 2:
                    return _MiddleRarityList;
                case 3:
                    return _HighRarityList;
                default:
                    throw new NotSupportedException($"This rank is not suported = ({rank})");
            }
        }
    }

    /// <summary>
    /// ガチャを引く行為を表す抽象クラス
    /// </summary>
    public abstract class AbstractGachaSimulation
    {
        protected Random Rand { get; } = new Random();

        /// <summary>
        /// ガチャを1回引いて名前を取得する
        /// </summary>
        public string RollGacha()
        {
            IList<string> list = GameData.GetList(this.GetRank());
            return list[this.Rand.Next(0, list.Count)];
        }

        /// <summary>
        /// キャラクターのランクを取得します。1~3で星の数に対応
        /// </summary>
        protected abstract int GetRank();
    }

    /// <summary>
    /// 10連ガチャの10回目のガチャを表す
    /// </summary>
    public class SpecialGachaSimulation : AbstractGachaSimulation
    {
        //
        // 5億回試行してこんな感じの確率分布
        //
        // ★の個数=0, 0%
        // ★★の個数 = 489897599, 97.9795198%
        // ★★★の個数 = 10102401, 2.0204802%
        //
        protected override int GetRank()
        {
            return this.Rand.Next(1, 100) - 1 >= 98 - 1 ? 3 : 2;
        }
    }

    /// <summary>
    /// 普通のガチャ・10連ガチャの1~9回目のガチャを表す
    /// </summary>
    public class NormalGachaSimulation : AbstractGachaSimulation
    {
        //
        // 10億回試行してこんな感じの確率分布
        //
        // ★の個数=797984891, 79.7984891%
        // ★★の個数 = 181808654, 18.1808654 %
        // ★★★の個数 = 20206455, 2.0206455 %
        //
        protected override int GetRank()
        {
            int rand = this.Rand.Next(1, 100) - 1;

            if (rand < 80 - 1)
            {
                return 1;
            }

            if (rand >= 80 - 1 && rand < 98 - 1)
            {
                return 2;
            }

            return 3;
        }
    }
}

*1:プレイ時間ではないです。今後はプレイ期間も関係してくると思います。