【创作灵感】

看到原神BWiki里有抽卡模拟器,便自己根据游戏内的描述用C语言编写了一个简单的模拟器,供参考使用。

作者注:

  • 普通C语言编程是没有图形化界面的,只看见原神两个字就点进来的请不要随便喷。
  • 因为三星、四星物品实在过多,对于这两种星级,程序只分4星角色、4星物品、3星这三类,不具体到名称,不然会导致程序代码大大增长且无实际意义。
  • 免费下载链接:C语言:原神角色UP池祈愿模拟器

目录

一、背景与描述

二、模块及介绍

1.祈愿基础概率的模拟

2.祈愿保底的模拟

3.历史记录的书写

4.历史记录页面实现翻页

5.修改已经抽取的数量

三、运行与展示

1.运行主界面

2.单抽

3.十连抽

4.查看历史纪录

5.修改垫抽数量

6.1 完整运行(单抽)

6.2 完整运行(十连抽)

四、代码与注释


一、背景与描述

原神BWiki里的抽卡模拟器:原神BWiki模拟器传送门

以4.2版本下半期UP祈愿-1为例:

对于这期UP池,我们有以下五星列表:

同时我们还要注意到以下这句话:

二、模块及介绍

1.祈愿基础概率的模拟

从上面图中我们可以得到小数点位数最多的就是概率为0.0255的4星武器和4星角色,也就是255/10000,为便于理解与阅读,这里我们不进行约分。

我们可以利用1-10000的一万个数字作为随机数生成的范围,让1-60为5星角色,61-315为4星角色(或物品),316-570为4星物品(或角色),剩下的就是3星物品与角色。这样处理就行了。

简单点描述就是:

srand(unsigned int)time(NULL));int NumberCode = rand()%10000 + 1;if (NumberCode >=1 && NumberCode =61 && NumberCode =316 && NumberCode <= 570) printf("这是4星物品");else printf("这是3星角色/物品");

2.祈愿保底的模拟

我们知道游戏中大保底为180次,即必中UP角色;小保底为90次,必定出五星角色,其中50%概率为UP角色。同时我们也知道每抽取10次必定出一个四星物品或角色,而且一旦出了抽到UP的情况,那么之前的抽取次数就会清空作废。

那么也就相当于N计数,每抽取一次就N++。当N为10的整数倍的时候就有保底。这个就没必要单独列出代码了。

3.历史记录的书写

这里的历史记录采用了类似于栈的结构,但也只是类似。这里就是把之前抽取的往后移,每个都往后移动一个位置,让出一个位置来写入新的记录。用代码简单表示就是:

char History[200][100];//200个储存空间int N = 20;//已经存了20条for (int i = N; i > 0; i--)strcpy(History[i], History[i - 1]);strcpy(History[0], "这里是新的抽卡记录(一条)");

4.历史记录页面实现翻页

为了模仿游戏中的一页五条历史记录,翻页的写法是不可避免的。

首先,根据上面的写法,我们知道History[0]是最近的历史记录,History[N – 1]则为最久的历史记录。所以我们第一页无疑需要从上到下输出History[0]到History[4],以此类推每一页的输出就是:

int page;//由用户决定大小for (int i = 0;i < 5; i++)printf("%s\n",History[(page - 1)*5 + i]);

5.修改已经抽取的数量

这个功能是方便大家根据自己的已垫次数进行模拟。其实只用把SmokedNumber更改就行了。

三、运行与展示

1.运行主界面

2.单抽

3.十连抽

4.查看历史纪录

5.修改垫抽数量

6.1 完整运行(单抽)

受限于gif图时间限制,下图为2.5倍速

6.2 完整运行(十连抽)

受限于gif图时间限制,下图为2.5倍速

四、代码与注释

#define _CRT_SECURE_NO_WARNINGS#include #include #include #include #include #include #include "unistd.h"///注意,该项目规定输出区域为第三行以后(不包括第三行),前两行用于常驻简介与目前大保底的抽数#define TimeInterval 25//预定的动画时间间隔#define ChangeColor_Purple printf("\033[35m")//将颜色换成紫色#define ChangeColor_Green_ printf("\033[32m")//将颜色换成绿色#define ChangeColor_Red___ printf("\033[31m")//将颜色换成红色#define ChangeColor_Yellow printf("\033[33m")//将颜色换成黄色#define ChangeColor_Whilt_ printf("\033[0m") //将颜色换成白色#define ChangeColor_Blue__ printf("\033[36m")//将颜色换成蓝色#define MoveTheLocation printf("\033[%d;%dH",4,0)//移动光标保证输出位置一样#define MoveToChangeTheNumber printf("\033[%d;%dH",2,NumberOutput_X)//移动光标保证输出位置一样#define HideCursor printf("\033[" />");ChangeColor_Green_; printf("Operation completed.Press [Enter] to continue...");//提示用户操作完毕ChangeColor_Whilt_;CharToContinue = getchar();do { CharToContinue = getchar(); } while (CharToContinue != '\n');//持续读取直到输入回车} while (1);return 0;}void InputTheOperate(){//用于简单介绍如何操作并要求用户输入操作///总共A、B、C、D三种操作,分别对应单抽、十连抽、查看历史记录、修改已垫抽数量CleanTheScreen();ChangeColor_Whilt_; Sleep(25); printf("\n");printf("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n");printf("┃Please input the Operate you want: ┃\n");printf("┃ A.Prayer B.Prayer * 10┃\n");printf("┃ C.HistoryD.Edit Smoked Number ┃\n");printf("┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n");printf("┃Personal blog homepage:\033[36msherrychou.blog.csdn.net\033[0m┃\n");printf("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");ChangeColor_Purple; printf(">");ChangeColor_Green_; printf("Please input A,B,C or D:");ChangeColor_Whilt_;char Operate = ' ';//储存用户需要进行的操作do { scanf("%c", &Operate); if (!(Operate == 'A' || Operate == 'B' || Operate == 'C' || Operate == 'D')){HideCursor; printf("\033[%d;%dH", 12, 26);ChangeColor_Whilt_; printf(" ");ShowCursor; printf("\033[%d;%dH", 12, 26);}} while (!(Operate == 'A' || Operate == 'B' || Operate == 'C' || Operate == 'D'));//持续读取直到输入A或B或C或Dswitch (Operate){case 'A'://单抽case 'B'://十连抽Prayer(Operate);//并列break;case 'C'://查看历史记录SearchTheHistory();break;case 'D'://修改垫抽次数EditTheSmokedNumber();break;}return;}void Prayer(char Oper){//进行祈愿并输出的函数srand((unsigned int)time(NULL));switch (Oper){case 'A'://进行单抽{Number_Big++;N_History++;int CodeNumber = rand() % 10000 + 1;/*对获得物品进行分段*1-60为5星角色(0.6%)*61-315为4星角色(2.55%)*316-570为4星物品(2.55%)*剩下的全是3星*/int Temp_1[1];if (Number_Big % 10 != 0)//没有保底的情况{if (CodeNumber >= 1 && CodeNumber = 61 && CodeNumber = 316 && CodeNumber <= 570)Temp_1[0] = 3;//四星物品else Temp_1[0] = 4;//三星}else {switch (Number_Big){case 90://小保底{Temp_1[0] = rand() % 2;//50%概率为up角色if (Temp_1[0] == 0) Number_Big = 0;//如果抽中了UP那么大保底累计抽数归零break;}case 180://大保底Temp_1[0] = Number_Big = 0;break;default://剩下的就是四星保底Temp_1[0] = rand() % 2 + 2;//50%概率为四星角色,得到2或3break;}}EditShowedSmokedNumber();Output(1, Temp_1);break;}case 'B'://进行十连抽{N_History += 10;int Temp_1[10];for (int i = 0; i = 1 && CodeNumber = 61 && CodeNumber = 316 && CodeNumber <= 570)Temp_1[i] = 3;//四星物品else Temp_1[i] = 4;//三星}else {switch (Number_Big){case 90://小保底{Temp_1[i] = rand() % 2;//50%概率为up角色if (Temp_1[i] == 0) Number_Big = 0;//如果抽中了UP那么大保底累计抽数归零break;}case 180://大保底Temp_1[i] = Number_Big = 0;break;default://剩下的就是四星保底Temp_1[i] = rand() % 2 + 2;//50%概率为四星角色,得到2或3break;}}}EditShowedSmokedNumber();Output(10, Temp_1);break;}}return;}void Output(int n, int Cod[10]){//输出动画与结果的函数///n为祈愿次数,cod[]储存的是抽中的编号,0为五星UP角色,1为五星常驻角色,2为四星角色,3为四星物品,4为三星CleanTheScreen();HideCursor;printf("\n");switch (n){case 1://单抽switch (Cod[0]){case 0:ChangeColor_Yellow;for (int i = 1; i  0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[33m*****\t%s", UPer_5_Stars);ChangeColor_Whilt_;break;case 1:ChangeColor_Yellow;for (int i = 1; i  0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[33m*****\t%s", FiveStars[FiveStarsNotUPer_Code]);ChangeColor_Whilt_;break;case 2:{ChangeColor_Purple;for (int i = 1; i  0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[35m****\t%s", Out);ChangeColor_Whilt_;break;}case 3:{ChangeColor_Purple;for (int i = 1; i  0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[35m****\t%s", Out);ChangeColor_Whilt_;break;}case 4:{ChangeColor_Blue__;for (int i = 1; i  0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[36m***\t%s", Out);break;}}break;case 10://十连抽{struct InformationForTenPrayer Inf[10];struct InformationForTenPrayer *pIFTP = NULL;int n_History = N_History - 10;//先写历史记录,避免顺序打乱不好写for (int j = 0; j  0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[33m*****\t%s", UPer_5_Stars);strcpy(pIFTP->Name, UPer_5_Stars);pIFTP->StarNumber = 5;break;case 1:srand((unsigned int)time(NULL));int FiveStarsNotUPer_Code = rand() % 7;for (int i = n_History - 1; i > 0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[33m*****\t%s", FiveStars[FiveStarsNotUPer_Code]);strcpy(pIFTP->Name, FiveStars[FiveStarsNotUPer_Code]);pIFTP->StarNumber = 5;break;case 2:strcpy(pIFTP->Name, "4 stars role");for (int i = n_History - 1; i > 0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[35m****\t%s", pIFTP->Name);pIFTP -> StarNumber= 4;break;case 3:strcpy(pIFTP->Name, "4 stars object");for (int i = n_History - 1; i > 0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[35m****\t%s", pIFTP->Name);pIFTP->StarNumber = 4;break;case 4:strcpy(pIFTP->Name, "3 stars object/role");for (int i = n_History - 1; i > 0; i--)strcpy(History[i], History[i - 1]);//把历史记录往后推sprintf(History[0], "\033[36m***\t%s", pIFTP->Name);pIFTP->StarNumber = 3;break;}}//到这里按顺序写完了历史记录//接下来根据抽到的星数进行降序排序,符合原神实际十连抽的最终页面struct InformationForTenPrayer Temp_Swap;//交换的中间值for (int i=0;i<9;i++)for (int j=0;j<9-i;j++)if (Inf[j].StarNumber < Inf[j + 1].StarNumber){Temp_Swap = Inf[j];Inf[j] = Inf[j + 1];Inf[j + 1] = Temp_Swap;}//冒泡排序printf("\n");for (int i = 0; i StarNumber){case 5:ChangeColor_Yellow; break;case 4:ChangeColor_Purple; break;case 3:ChangeColor_Blue__; break;}for (int j = 1; j StarNumber; j++){printf("*");Sleep(200);}Sleep(50);printf("\t:%s\n", pIFTP->Name);Sleep(100);ChangeColor_Whilt_;}break;}}ShowCursor;return;}void EditTheSmokedNumber(){//修改已垫抽次数的函数SmokedNumberInputWrong://如果输入的不是用户需要修改的,或者不符合要求,则跳回到这里CleanTheScreen();ChangeColor_Purple; printf(">");ChangeColor_Green_; printf("Please input the Smoked Number:");ChangeColor_Whilt_;while (scanf("%d", &Number_Big) == EOF) { }//如果读取失败(比如输入的不是数字)就一直循环if (Number_Big >= 0 && Number_Big ");ChangeColor_Green_; printf("Do you want to change the Smoked Number to %d ?(Y/N)", Number_Big);ChangeColor_Whilt_;char CharToNext = getchar();do { CharToNext = getchar(); } while (!(CharToNext == 'Y' || CharToNext == 'N'));//持续读取直到读取到Y或Nif (CharToNext == 'N') goto SmokedNumberInputWrong;else {EditShowedSmokedNumber();CleanTheScreen();}}else {//数据范围不符合要求ChangeColor_Purple; printf(">");ChangeColor_Red___; printf("The number is not allowed.\n");ChangeColor_Purple; printf(">");ChangeColor_Red___; printf("You should input between 0 to 179.Please try again.\n");ChangeColor_Purple; printf("\n>");ChangeColor_Green_; printf("Press [Enter] to continue...");char charToContinue = getchar();do { charToContinue = getchar(); } while (charToContinue != '\n');//持续读取直到输入回车goto SmokedNumberInputWrong;}return;}void EditShowedSmokedNumber(){//修改显示的已抽取数量的函数MoveToChangeTheNumber;printf(" ");MoveToChangeTheNumber;ChangeColor_Yellow; printf("%d", Number_Big);ChangeColor_Whilt_;MoveTheLocation;return;}void CleanTheScreen(void){//清空屏幕输出区域的函数HideCursor;MoveTheLocation;for (int i = 1; i <= 15; i++){for (int j = 1; j <= 100; j++)printf(" ");printf("\n");}ShowCursor;MoveTheLocation;}void Welcome(){//用于输出欢迎页面的函数,只调用一次HideCursor;//隐藏光标ChangeColor_Purple; printf("The Genshin Impact Lottery Limulator programmed by QJB.\n");ChangeColor_Whilt_; printf("Today's UPer is:");ChangeColor_Yellow; printf("%s", UPer_5_Stars);ChangeColor_Whilt_; printf("Smoked Number:");ChangeColor_Yellow; printf("%d\n", Number_Big);ChangeColor_Red___; printf("---------------------------------------------------------\n");ChangeColor_Whilt_;//以上构成顶部栏printf("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n");printf("┃Welcome to use The Genshin Impact Lottery Limulator┃\n");printf("┃ The application is programmed by QJB(Sherry Chou) ┃\n");printf("┃ Compiled according to the rules of Genshin Impact ┃\n");printf("┃ Please respect personal gain,don't repost at will ┃\n");printf("┃Personal blog homepage:\033[36msherrychou.blog.csdn.net\033[0m┃\n");printf("┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n");printf("┃ ┃\n");printf("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");//插入的标签已经输入完毕//做个进度条增强观感Sleep(500);ChangeColor_Yellow; printf("\033[%d;%dH", 11, 2);//移动光标到进度条开始位置for (int i = 1; i <= 55; i++){printf("█");Sleep(25);}ShowCursor;//显示光标return;}void SearchTheHistory(){//实现查看历史记录功能的函数///历史记录页面格式:输出区域的 第一行为历史记录页面标题,第二行进行空行,分离内容///第三行到第七行为五条历史记录(每页五条),第八行为当前页数与总页数,以及左右换页符///第九行是提示用户输入需要进行的操作CleanTheScreen();int StartLine = 6, page = 1, pageMax=(N_History - 1) / 5 + 1;HideCursor;ChangeColor_Whilt_; printf("The history record passage:");printf("\033[%d;%dH", 11, 0);//定位printf(" <>\n",page);ChangeColor_Purple; printf(">");ChangeColor_Green_; printf("[A]Flip to the previous page\n");//提示用户操作ChangeColor_Purple; printf(">");ChangeColor_Green_; printf("[B]Flip to the next page\n");//提示用户操作ChangeColor_Purple; printf(">");ChangeColor_Green_; printf("[C]Exit\n");//提示用户操作ChangeColor_Purple; printf(">");ChangeColor_Green_; printf("Your operation:");//提示用户操作ChangeColor_Whilt_;for (; StartLine =0){printf("\t%s", History[5 * (page - 1) + (StartLine - 6)]);}}printf("\033[%d;%dH", 15, 17); ShowCursor;//移动并显示光标char TheOperate;do {TheOperate = getchar();ChangeColor_Whilt_;do { TheOperate = getchar();if (!(TheOperate == 'A' || TheOperate == 'B' || TheOperate == 'C')){printf("\033[%d;%dH", 15, 17);printf(" ");printf("\033[%d;%dH", 15, 17);}} while (!(TheOperate == 'A' || TheOperate == 'B' || TheOperate == 'C'));HideCursor;//以上完成了对用户需要的操作的读取switch (TheOperate){case 'A'://翻到上一页HideCursor;if (page > 1){page--;StartLine = 6;History_CleanThePanel();for (; StartLine = 0){printf("\t%s", History[5 * (page - 1) + (StartLine - 6)]);}}printf("\033[%d;%dH", 11, 22);//定位ChangeColor_Whilt_; printf("%d", page);}printf("\033[%d;%dH", 15, 17);printf(" ");printf("\033[%d;%dH", 15, 17);ShowCursor;//移动并显示光标break;case 'B'://翻到下一页HideCursor;if (page <pageMax){page++;StartLine = 6;History_CleanThePanel();for (; StartLine = 0){printf("\t%s", History[5 * (page - 1) + (StartLine - 6)]);}}printf("\033[%d;%dH", 11, 22);//定位ChangeColor_Whilt_; printf("%d", page);}printf("\033[%d;%dH", 15, 17);printf(" ");printf("\033[%d;%dH", 15, 17);ShowCursor;//移动并显示光标break;case 'C'://退出历史记录查看goto OutThe_while_1_Cycle;}} while (1);OutThe_while_1_Cycle://跳出循环调到这里ChangeColor_Whilt_;ShowCursor;printf("\033[%d;%dH", 16, 0);return;}void History_CleanThePanel(){//用于清空历史记录输出区域的函数for (int i = 6; i <= 10; i++){printf("\033[%d;%dH", i, 0);for (int j = 1; j <= 60; j++)printf(" ");}return;}

unistd.h头文件:

#ifndef _UNISTD_H#define _UNISTD_H#include #include #endif