偶然灵光一闪想做个白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 ¶ms, 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;
}
结果如下:
上述结果中,第一行代表每个结局拥有的个数(对于同样的结局有多少种不同的选法),第二行代表各结局的概率(需要注意的是概率的分布不是平均的,不能直接用结局个数的和作为分母)。因此,与1一样,我引入了权重机制。这里需要注意的是,CC中倒数第二个选项是三选一,因此我对1里的权重机制进行了修改,自上而下传递当前权重,到达结局则将当前权重分配给该结局。
雪菜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)