【创作灵感】
看到原神BWiki里有抽卡模拟器,便自己根据游戏内的描述用C语言编写了一个简单的模拟器,供参考使用。
作者注:
- 普通C语言编程是没有图形化界面的,只看见原神两个字就点进来的请不要随便喷。
- 因为三星、四星物品实在过多,对于这两种星级,程序只分4星角色、4星物品、3星这三类,不具体到名称,不然会导致程序代码大大增长且无实际意义。
- 免费下载链接:C语言:原神角色UP池祈愿模拟器
目录
一、背景与描述
原神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 <= 60) printf("这是5星角色");
else if (NumberCode >=61 && NumberCode <= 315) printf("这是4星角色");
else if (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 <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <Windows.h>
#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[?25l")//隐藏光标
#define ShowCursor printf("\033[?25h")//隐藏光标
int Number_Big = 0;//分别记录目前大保底抽了多少抽,90就是小保底,180是大保底
int N_History = 0;//记录总共可查询的历史记录数量(不一定等于Number_Big)
char UPer_5_Stars[25] = "Furina"; //五星Up角色的名称,此处为芙宁娜
char FiveStars[7][25] = {"Tighnari", "Dehya", "Keqing", "Mona", "Qiqi", "Diluc", "Jean"};
//分别对应提纳里、迪希雅、刻晴、莫娜、七七、迪卢克、琴
int NumberOutput_X;
char History[500][100];//最多可查询500条历史记录
struct InformationForTenPrayer {//用于储存十连抽的数据,便于排序显示
int StarNumber;
char Name[25];
};
void CleanTheScreen(void);
void InputTheOperate();
void Welcome();
void Prayer(char Oper);
void Output(int n, int Cod[10]);
void EditTheSmokedNumber();
void EditShowedSmokedNumber();
void SearchTheHistory();
void History_CleanThePanel();
int main(void)
{
NumberOutput_X = 1 + strlen(UPer_5_Stars) + 16 + 16 + 14;
Welcome();//输出欢迎页面与顶部栏
char CharToContinue;
do {
InputTheOperate();
ChangeColor_Purple; printf("\n>");
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.History D.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或D
switch (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 <= 60)
{
Temp_1[0] = rand() % 2;//50%概率为up角色
if (Temp_1[0] == 0) Number_Big = 0;//如果抽中了UP那么大保底累计抽数归零
}
else if (CodeNumber >= 61 && CodeNumber <= 315)
Temp_1[0] = 2;//四星角色
else if (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或3
break;
}
}
EditShowedSmokedNumber();
Output(1, Temp_1);
break;
}
case 'B'://进行十连抽
{
N_History += 10;
int Temp_1[10];
for (int i = 0; i < 10; i++)
{
Number_Big ++;
int CodeNumber = rand() % 10000 + 1;
if (Number_Big % 10 != 0)//没有保底的情况
{
if (CodeNumber >= 1 && CodeNumber <= 60)
{
Temp_1[i] = rand() % 2;//50%概率为up角色
if (Temp_1[i] == 0) Number_Big = 0;//如果抽中了UP那么大保底累计抽数归零
}
else if (CodeNumber >= 61 && CodeNumber <= 315)
Temp_1[i] = 2;//四星角色
else if (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或3
break;
}
}
}
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 <= 5; i++)
{
printf("*");
Sleep(200);
}
printf("\t:%s\n", UPer_5_Stars);
//接下来写历史记录
for (int i = N_History - 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 <= 5; i++)
{
printf("*");
Sleep(200);
}
srand((unsigned int)time(NULL));
int FiveStarsNotUPer_Code = rand() % 7;
printf("\t:%s\n", FiveStars[FiveStarsNotUPer_Code]);
//接下来写历史记录
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]);
ChangeColor_Whilt_;
break;
case 2:
{
ChangeColor_Purple;
for (int i = 1; i <= 4; i++)
{
printf("*");
Sleep(200);
}
char Out[25] = "4 stars role";
printf("\t:%s\n", Out);
//接下来写历史记录
for (int i = N_History - 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 <= 4; i++)
{
printf("*");
Sleep(200);
}
char Out[25] = "4 stars object";
printf("\t:%s\n", Out);
//接下来写历史记录
for (int i = N_History - 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 <= 3; i++)
{
printf("*");
Sleep(200);
}
char Out[25] = "3 stars object/role";
printf("\t:%s\n", Out);
//接下来写历史记录
for (int i = N_History - 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 < 10; j++)
{
pIFTP = &Inf[j];
n_History++;
switch (Cod[j])
{
case 0:
for (int i = n_History - 1; i > 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 < 10; i++)//输出了
{
pIFTP = &Inf[i];
switch (pIFTP->StarNumber)
{
case 5:ChangeColor_Yellow; break;
case 4:ChangeColor_Purple; break;
case 3:ChangeColor_Blue__; break;
}
for (int j = 1; j <= pIFTP->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 < 180)
{
ChangeColor_Purple; printf(">");
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或N
if (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(" << %d >>\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 <= 10; StartLine++)
{
printf("\033[%d;%dH", StartLine, 0);
if (5*(page - 1) + (StartLine - 6)>=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 <= 10; StartLine++)
{
printf("\033[%d;%dH", StartLine, 0);
if (5 * (page - 1) + (StartLine - 6) >= 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 <= 10; StartLine++)
{
printf("\033[%d;%dH", StartLine, 0);
if (5 * (page - 1) + (StartLine - 6) >= 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 <io.h>
#include <process.h>
#endif