回溯法计算白色相簿2各结局概率

偶然灵光一闪想做个白2的各结局概率比较,看看闭着眼睛随机选择下哪个结局概率大,哪个结局概率小。

然而,在互联网上找了半天也没找到CC概率的分析文章,做coda篇概率分析的文章倒是不少1 2 3

找着找着我偶然看到一篇分析CC好感度变化的文章4。有了这些信息,我自己也能写个程序来分析CC的各条线概率。我决定选用1的基本框架,采用回溯法计算CC篇各结局的概率,具体代码:

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <functional>

using namespace std;
struct Parameters
{
    // 角色好感度
    int SETZUNA;
    int Chiaki;
    int Mari;
    int Koharu;

    // 剧情flag
    bool ChiakiTEflag;
    bool Chiakiflag;
    bool Mariflag;
    bool Koharuflag;

    // 当前选项
    int QuestionNum;
};

/*
千晶、麻理、小春end入线条件:
千晶、麻理、小春好感度>=3
千晶、麻理、小春flag开启
(上述情况需一一对应)

雪菜end入线条件:
雪菜好感度>=5
千晶、麻理、小春好感均>=1
千晶flag关闭
*/

void cc(const bool ChiakiTE)
{
    // 结局统计
    int NormalEnd = 0;
    int SETZUNAEnd = 0;
    int ChiakiNormalEnd = 0;
    int ChiakiEnd = 0;
    int MariEnd = 0;
    int KoharuEnd = 0;
    // 结局权重统计
    int NormalEndweight = 0;
    int SETZUNAEndweight = 0;
    int ChiakiNormalEndweight = 0;
    int ChiakiEndweight = 0;
    int MariEndweight = 0;
    int KoharuEndweight = 0;

    function<void(Parameters &, const int)> cc_dfs = [&](Parameters &params, const int cur_weight)
    {
        switch (params.QuestionNum)
        {
        case 1:
            if (ChiakiTE == false) // 一周目
            {
                params.QuestionNum += 1;
                params.Chiaki += 1;
                cc_dfs(params, cur_weight); // “和她在一起觉得很舒服”(千晶好感度+1)
                params.Chiaki -= 1;
                params.QuestionNum -= 1;
            }
            else
            {

                params.QuestionNum += 1;
                params.Chiaki += 1;
                cc_dfs(params, cur_weight / 2); // “和她在一起觉得很舒服”(千晶好感度+1)
                params.Chiaki -= 1;

                params.ChiakiTEflag = true;     // 开启千晶TE flag
                cc_dfs(params, cur_weight / 2); // “这家伙,难对付啊”(千晶TE flag开启)
                params.ChiakiTEflag = false;

                params.QuestionNum -= 1;
            }
            break;
        case 2:
            params.QuestionNum += 1;
            cc_dfs(params, cur_weight / 2); // “给她发短信”

            params.SETZUNA += 1;
            cc_dfs(params, cur_weight / 2); // “给她打电话”(雪菜好感度+1)
            params.SETZUNA -= 1;

            params.QuestionNum -= 1;
            break;
        case 3:
            params.QuestionNum += 1;
            cc_dfs(params, cur_weight / 2); // “拒绝”

            params.Mari += 1;
            cc_dfs(params, cur_weight / 2); // “接受”(麻理好感度+1)
            params.Mari -= 1;

            params.QuestionNum -= 1;
            break;
        case 4:
            params.QuestionNum += 1;
            cc_dfs(params, cur_weight / 2); // “目送她离去”

            params.Koharu += 1;
            cc_dfs(params, cur_weight / 2); // “送她回去”(小春好感度+1)
            params.Koharu -= 1;

            params.QuestionNum -= 1;
            break;
        case 5:
            params.QuestionNum += 1;
            cc_dfs(params, cur_weight / 2); // “把她叫起来”

            params.Chiaki += 1;
            cc_dfs(params, cur_weight / 2); // “让她睡在床上”(千晶好感度+1)
            params.Chiaki -= 1;

            params.QuestionNum -= 1;
            break;
        case 6:
            params.QuestionNum += 1;
            params.SETZUNA += 1;
            cc_dfs(params, cur_weight / 2); // “目送她离去”(雪菜好感度+1)
            params.SETZUNA -= 1;

            params.Koharu += 1;
            cc_dfs(params, cur_weight / 2); // “送她回去”(小春好感度+1)
            params.Koharu -= 1;

            params.QuestionNum -= 1;
            break;
        case 7:

            params.QuestionNum += 1;
            params.SETZUNA += 1;
            cc_dfs(params, cur_weight / 2); // “这不是那么简单的事情”(雪菜好感度+1)
            params.SETZUNA -= 1;

            params.Mari += 1;
            cc_dfs(params, cur_weight / 2); // “都是过去的事情了”(麻理好感度+1)
            params.Mari -= 1;

            params.QuestionNum -= 1;
            break;
        case 8:
            params.QuestionNum += 1;
            if (params.Mari < 2)
            {
                params.Mariflag = false;
                cc_dfs(params, cur_weight); // “知道了,就今天好了”(关闭麻理flag)
                params.Mariflag = true;
            }
            else
            {
                cc_dfs(params, cur_weight / 2); // “抱歉,明天再说吧”(麻理好感度2以下时不能选择该选项)

                params.Mariflag = false;
                cc_dfs(params, cur_weight / 2); // “知道了,就今天好了”(关闭麻理flag)
                params.Mariflag = true;
            }
            params.QuestionNum -= 1;
            break;
        case 9:
            params.QuestionNum += 1;

            params.SETZUNA += 1;
            params.Chiakiflag = false;
            cc_dfs(params, cur_weight / 2); // “今天就,回去吧”(雪菜好感度+1且关闭千晶flag)”
            params.Chiakiflag = true;
            params.SETZUNA -= 1;

            params.Chiaki += 1;
            if (params.Mari) // 若【麻理 flag】开启,则关闭【小春 flag】,跳过选择支10
            {
                params.QuestionNum += 1;
                params.Koharu = false;
                cc_dfs(params, cur_weight / 2); // “再稍微说一点话吧”(千晶好感度+1,若此时麻理flag开启则小春flag关闭)
                params.Koharu = true;
                params.QuestionNum -= 1;
            }
            else
            {
                cc_dfs(params, cur_weight / 2); // “再稍微说一点话吧”
            }
            params.Chiaki -= 1;

            params.QuestionNum -= 1;
            break;
        case 10:
            params.QuestionNum += 1;
            cc_dfs(params, cur_weight / 2); // “可以才怪”

            params.Koharu += 1;
            params.SETZUNA += 1;
            cc_dfs(params, cur_weight / 2); // “随便你吧”(小春雪菜好感各+1)
            params.Koharu -= 1;
            params.SETZUNA -= 1;

            params.QuestionNum -= 1;
            break;
        case 11:
            params.QuestionNum += 1;
            if (params.Chiakiflag == false)
            {
                params.SETZUNA += 1;
                cc_dfs(params, cur_weight); // “你果然还是来这里吧”(雪菜好感度+1)(关闭千晶flag)
                params.SETZUNA -= 1;
            }
            else
            {
                params.SETZUNA += 1;
                params.Chiakiflag = false;
                cc_dfs(params, cur_weight / 2); // “你果然还是来这里吧”(雪菜好感度+1)(关闭千晶flag)
                params.Chiakiflag = true;
                params.SETZUNA -= 1;

                params.Chiaki += 1;
                cc_dfs(params, cur_weight / 2); // “我现在就去你那边”(千晶好感度+1)(若千晶flag已关闭则不能选择该选项)
                params.Chiaki -= 1;
            }
            params.QuestionNum -= 1;
            break;
        case 12:
            params.QuestionNum += 1;
            if (params.Mariflag == false)
            {
                params.SETZUNA += 1;
                cc_dfs(params, cur_weight); // “那么,就要一本吧”(雪菜好感度+1)
                params.SETZUNA -= 1;
            }
            else
            {

                params.SETZUNA += 1;
                params.Mariflag = false;
                cc_dfs(params, cur_weight / 2); // “那么,就要一本吧”(雪菜好感度+1,麻理flag关闭)
                params.Mariflag = true;
                params.SETZUNA -= 1;

                params.Mari += 1;
                cc_dfs(params, cur_weight / 2); // “不需要”(麻理好感度+1,若之前麻理flag已关闭则不能选择该选项)
                params.Mari -= 1;
            }
            params.QuestionNum -= 1;
            break;
        case 13:
            params.QuestionNum += 1;
            if (params.Koharuflag == false)
            {
                cc_dfs(params, cur_weight); // “嘛,无所谓了”
            }
            else
            {
                cc_dfs(params, cur_weight / 2); // “嘛,无所谓了”

                params.Koharu += 1;
                cc_dfs(params, cur_weight / 2); // “难道说那家伙...”(小春好感度+1,若之前小春flag已经关闭则不能选择该选项)
                params.Koharu -= 1;
            }
            params.QuestionNum -= 1;
            break;
        case 14:
            if (params.Chiakiflag && params.Chiaki >= 3 && params.Mariflag && params.Mari >= 3)
            {
                // 这里能3选1
                params.QuestionNum += 1;
                cc_dfs(params, cur_weight / 3); // “那种事情,怎么能去考虑”(进入选择支15)
                params.QuestionNum -= 1;

                // “给千晶打电话”(千晶end)
                if (params.ChiakiTEflag)
                {
                    ChiakiEndweight += cur_weight / 3;
                    ChiakiEnd += 1;
                }
                else
                {
                    ChiakiNormalEnd += 1;
                    ChiakiNormalEndweight += cur_weight / 3;
                }

                // “在编辑部露面”(麻理end)
                MariEndweight += cur_weight / 3;
                MariEnd += 1;
            }
            else if (params.Chiakiflag && params.Chiaki >= 3)
            { // 双选题
                params.QuestionNum += 1;
                cc_dfs(params, cur_weight / 2); // “那种事情,怎么能去考虑”(进入选择支15)
                params.QuestionNum -= 1;

                // “给千晶打电话”(千晶end)
                if (params.ChiakiTEflag)
                {
                    ChiakiEndweight += cur_weight / 2;
                    ChiakiEnd += 1;
                }
                else
                {
                    ChiakiNormalEnd += 1;
                    ChiakiNormalEndweight += cur_weight / 2;
                }
            }
            else if (params.Mariflag && params.Mari >= 3)
            { // 双选题
                params.QuestionNum += 1;
                cc_dfs(params, cur_weight / 2); // “那种事情,怎么能去考虑”(进入选择支15)
                params.QuestionNum -= 1;

                // “在编辑部露面”(麻理end)
                MariEndweight += cur_weight / 2;
                MariEnd += 1;
            }
            else
            {
                // 单选题
                params.QuestionNum += 1;
                cc_dfs(params, cur_weight); // “那种事情,怎么能去考虑”(进入选择支15)
                params.QuestionNum -= 1;
            }
            break;
        case 15:
            if (params.Koharuflag && params.Koharu >= 3)
            {
                // “去打工”(小春end)
                KoharuEndweight += cur_weight / 2;
                KoharuEnd += 1;

                params.QuestionNum += 1;
                cc_dfs(params, cur_weight / 2); // “一直独自待着”(雪菜or滑雪end)
                params.QuestionNum -= 1;
            }
            else
            {
                // 单选题
                params.QuestionNum += 1;
                cc_dfs(params, cur_weight); // “一直独自待着”(雪菜or滑雪end)
                params.QuestionNum -= 1;
            }

            break;
        case 16: // 并不存在的16选,单纯用来判定雪菜线还是滑雪线
            if (params.SETZUNA >= 5 && params.Chiaki >= 1 && params.Mari >= 1 && params.Koharu >= 1 && params.Chiakiflag == false)
            {
                // 雪菜END
                SETZUNAEndweight += cur_weight;
                SETZUNAEnd += 1;
            }
            else
            {
                // 滑雪END
                NormalEndweight += cur_weight;
                NormalEnd += 1;
            }
            break;
        default:
            printf("error!!!");
            exit(-1);
        }
    };
    constexpr int sum_weight = (1 << 15) * 3; // 一共15个选项,14个选项里每个选项有2种选择,1个选项里有3种选择
    // 为确保那唯一的三选题选上后权重能被整除,该选项的权重设为6,其余选项权重设为2
    // 故总权重为6*2^14=98,304,即sum_weight=(1<<15)*3=98,304
    Parameters param({0, 0, 0, 0, false, true, true, true, 1});
    cc_dfs(param, sum_weight);
    printf("滑雪:%d 雪菜:%d 千晶NE:%d 千晶TE:%d 麻理:%d 小春:%d\n", NormalEnd,
           SETZUNAEnd,
           ChiakiNormalEnd,
           ChiakiEnd,
           MariEnd,
           KoharuEnd);
    printf("滑雪:%.4f%% ", (float)NormalEndweight * 100 / sum_weight);
    printf("雪菜:%.4f%% ", (float)SETZUNAEndweight * 100 / sum_weight);
    printf("千晶NE:%.4f%% ", (float)ChiakiNormalEndweight * 100 / sum_weight);
    printf("千晶TE:%.4f%% ", (float)ChiakiEndweight * 100 / sum_weight);
    printf("麻理:%.4f%% ", (float)MariEndweight * 100 / sum_weight);
    printf("小春:%.4f%%\n", (float)KoharuEndweight * 100 / sum_weight);

    return;
}

int main()
{
    printf("一周目\n");
    cc(false);
    printf("二周目\n");
    cc(true);
    return 0;
}

结果如下:
result
上述结果中,第一行代表每个结局拥有的个数(对于同样的结局有多少种不同的选法),第二行代表各结局的概率(需要注意的是概率的分布不是平均的,不能直接用结局个数的和作为分母)。因此,与1一样,我引入了权重机制。这里需要注意的是,CC中倒数第二个选项是三选一,因此我对1里的权重机制进行了修改,自上而下传递当前权重,到达结局则将当前权重分配给该结局。

然后,我们可以从1 2 3里知道,coda篇结局概率为:

雪菜TE 39.2578%
冬马TE 0.0244%
浮气线 30.3711%
腰斩线 30.3467%

要想进入coda,必须通关CC雪菜线。于是,我们以可以进入千晶TE的二周目为前提(不然千晶就没机会了),则白色相簿2各结局的概率分别为:

春哥雪菜关系不上不下 70.6095%(滑雪线64.4897%、千晶NE6.1198%)
春哥与雪菜在一起 12.3383%(雪菜TE、浮气线+不共戴天、腰斩线)
春哥与小春在一起 11.0596%(小春线)
春哥与千晶在一起 3.0599%(千晶TE)
春哥与麻理在一起 2.9297%(麻理线)
春哥与冬马在一起 0.0030%(冬马TE)


  1. 用递归的方式分析白色相簿2 coda篇各结局概率 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. 白色相簿2coda篇 闭眼乱选进各结局概率 ↩︎ ↩︎

  3. 白色相簿2 coda篇各结局概率分析 ↩︎ ↩︎

  4. 【白色相簿2】全选项及全线路攻略 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值