🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

4回答

4492閲覧

設定した排出率に従ったガチャを作成したいのです

SEKI817291

総合スコア5

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2020/01/02 02:27

前提・実現したいこと

設定した排出率に従った、ソーシャルゲームのガチャ(のシミュレータ)を作成しています。
ラインナップはtxtファイルに保存されています。
初めての質問なので、見にくい部分があると思いますがご容赦ください。

発生している問題

先日、知恵袋で回答して頂いた方法を採用しました。次のとおりです。 1. 各キャラクタに排出率をポイントとして割り振る 2. 全キャラクタのポイントの合計値 total を求める 3. total 未満の乱数 random を生成する 4. リストの順番に random から各キャラクタのポイントを次々と引いてゆく 5. random が0以下になったときのキャラクタを排出する この方法を自分なりにプログラムコードで書き起こしてみましたが、同じ排出率に設定したキャラクタ同士で 排出率が異なっています。 どうすれば排出率に従うでしょうか。

ガチャのラインナップは次のとおりです。 数値はポイントです。このポイントを排出率(%)にしたいです。
Dantalion 1.50 Lune 1.50 Silvi 1.50 Paimon 1.50 Fenrir-wiz 1.50 Yog-sothose 1.50 Madou 1.50 Echidna-SALA 1.50 JakotsuHime 1.50 Leeche 1.50 HakuMu 1.50 Zera 1.50 Kuzankoh 1.50 Salene 1.50 Valkyrie-CIEL 1.50 Zeus-GIGA 1.50 Athena-NON 1.50 GaranDoushi 1.50 Fagan-RAI 1.50 Velois 1.50 Chutenmaru 1.50 Xerog-CORE 1.50 Minerva 6.70 Frey 6.70 Ison&Isuna 6.70 Andromeda 6.70 Bastet 6.70 Parvati 6.70 Venus 6.70 Ganesha 6.70 Tsukuyomi 6.70 Haku 6.70

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4#include <time.h> 5#define EXIT_TARGET 0 6#define TIMES 1000000 7 8 typedef struct MonsterBox{ 9 char name[32]; 10 double prb; 11 12 struct MonsterBox *next; 13 } LIST; 14 15 LIST *head = NULL; 16 LIST *tail = NULL; 17 void freeList(LIST *p){ 18 if(p == NULL){ 19 return; 20 } 21 freeList(p -> next); 22 free(p); 23 } 24 25LIST *getNewNode(void){ 26 LIST *newNode = NULL; 27 newNode = (LIST *)malloc(sizeof(LIST)); 28 29 if(newNode == NULL){ 30 puts("Out of memory."); 31 freeList(head); 32 exit(EXIT_FAILURE); 33 } 34 return newNode; 35} 36 37void inputList(char name[], double prb){ 38 LIST *p = NULL; 39 p = getNewNode(); 40 p -> prb = prb; 41 strcpy(p -> name, name); 42 if(head == NULL){ 43 head = p; 44 tail = head; 45 46 return; 47 } 48 49 tail -> next = p; 50 tail = p; 51 52 return; 53} 54 55int searchMonster(char target[]){ 56 57 LIST *p = NULL; 58 59 p = head; 60 while(p != NULL && strcmp(p -> name, target) != 0){ 61 p = p -> next; 62 } 63 if(p != NULL){ 64 return 0; 65 } 66 return 1; 67} 68 69 70int main(int argc, char **argv){ 71 72 FILE *fp = NULL; 73 char name[32]; 74 double prb = 0; 75 LIST *p = NULL; 76 double random = 0.0; 77 double total = 0.0; 78 int counter = 0; 79 int flag = 0; 80 int i = 0; 81 double avg = 0.0; 82 83 srand(time(NULL)); 84 85 if(argc != 3){ 86 printf("Usage: %s fileName.txt monsterName\n", argv[0]); 87 exit(EXIT_FAILURE); 88 } 89 if((fp = fopen(argv[1], "r")) == NULL){ 90 printf("Failed to open %s\n",argv[1]); 91 exit(EXIT_FAILURE); 92 } 93 while(fscanf(fp, "%31s %lf", name, &prb) == 2){ 94 inputList(name, prb); 95 } 96 97 flag = searchMonster(argv[2]); 98 // printf("flag = %d\n",flag); 99 if(flag == 1){ 100 printf("Not found %s.\n", argv[2]); 101 fclose(fp); 102 freeList(head); 103 exit(EXIT_FAILURE); 104 } 105 106 for(p = head; p != NULL; p = p -> next){ 107 total += (p -> prb); 108 } 109 // printf("%lf\n",total); 110 111 for(i = 0; i < TIMES; i++){ 112 p = head; 113 random = (double)(rand() % (int)total); 114 while(1){ 115 if(p -> next == NULL){ 116 p = head; 117 } 118 else{ 119 random -= p -> prb; 120 if(random <= 0){ 121 //puts(p -> name); 122 if(strcmp(p -> name, argv[2]) == 0){ 123 // printf("%d\n",counter); 124 counter++; 125 } 126 break; 127 } 128 p = p -> next; 129 } 130 } 131 } 132 133 avg = (double)counter / (double)TIMES; 134 135 printf("排出率: %lf% 排出体数: %d体\n", avg * 100, counter); 136 137 fclose(fp); 138 freeList(head); 139 return 0; 140 141}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

y_waiwai

2020/01/02 02:34

排出率が異なるとはどういった方法で確認されたんでしょうか
SEKI817291

2020/01/02 02:36

実際にこのプログラムを実行して確かめました。 そもそも、このプログラムが間違っているかもしれませんが。
majiponi

2020/01/02 03:01

排出率の違いは、以下の2つの原因で起きます。 1. rand関数自体の偏り。 2. 偶然(統計的に有意な差でない) 1000万回や1億回でも差が出ますか?
SEKI817291

2020/01/02 04:45

他の回答者様がrand()関数はよくないというので、Xorshiftというものを用いたところ、同じ排出率にせってキャラクタ同士はほとんど同じ排出率で排出されるようになりました。
guest

回答4

0

ベストアンサー

0:Dantalion 1.50

1:Lune 1.50
2:Silvi 1.50
3:Paimon 1.50
4:Fenrir-wiz 1.50
...

  1. int slot[1000] を用意し
  2. その先頭から順に15個に0を、15個に1を...n番目の排出率x10個にnを代入する
  3. 0~999の乱数rを生成し、slot[r]を排出する。

...ってのが確実かも。

[追記]
(2)のあと、slot[1000]をシャッフルして slot[0]から順に取り出せば「福引ガラガラ」方式に。

投稿2020/01/02 02:42

編集2020/01/02 10:00
episteme

総合スコア16612

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SEKI817291

2020/01/02 05:08

ありがとうございます。 そちらの方法でもやってみたいと思います。
episteme

2020/01/02 09:57

これがベストアンサー? なんで?
guest

0

確率はあくまで可能性であって,「〇回やったら必ず決めた割合になる」とは言えないと思いますが.

もし確率通り, 例えば10%で出るとなっていたら10回に1回は必ず出るようにするのでしたら, 福引のガラガラのように「出したモノは以降抽選から外す」ようにする等が必要かと思います.

以下は, ファイル読み込みやパラメータ(argv)によるモンスターの指定などは省いて(固定にして)います.

c

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4 5typedef struct data { 6 char name[32]; 7 int probability; //整数化のため10倍値 8} Data; 9Data data[] = { 10 {"Dantalion", 15}, 11 {"Lune", 15}, 12 {"Silvi", 15}, 13 {"Paimon", 15}, 14 {"Fenrir-wiz", 15}, 15 {"Yog-sothose", 15}, 16 {"Madou", 15}, 17 {"Echidna-SALA", 15}, 18 {"JakotsuHime", 15}, 19 {"Leeche", 15}, 20 {"HakuMu", 15}, 21 {"Zera", 15}, 22 {"Kuzankoh", 15}, 23 {"Salene", 15}, 24 {"Valkyrie-CIEL",15}, 25 {"Zeus-GIGA", 15}, 26 {"Athena-NON", 15}, 27 {"GaranDoushi", 15}, 28 {"Fagan-RAI", 15}, 29 {"Velois", 15}, 30 {"Chutenmaru", 15}, 31 {"Xerog-CORE", 15}, 32 {"Minerva", 67}, 33 {"Frey", 67}, 34 {"Ison&Isuna", 67}, 35 {"Andromeda", 67}, 36 {"Bastet", 67}, 37 {"Parvati", 67}, 38 {"Venus", 67}, 39 {"Ganesha", 67}, 40 {"Tsukuyomi", 67}, 41 {"Haku", 67}, 42 {"", -1} //EOD 43}; 44 45#define TIMES 1000000 46 47int initLotteryArray(int *lotteryArray, int total) { 48 int i, j, k; 49 50 for(i=0, k=0; data[i].probability>0; i++) { 51 for(j=0; j<data[i].probability; j++, k++) lotteryArray[k] = i; 52 } 53 return total; 54} 55 56char *getMonster(int index, int *lotteryArray, int *lotteryCounts, int total) { 57 int i; 58 char *monster; 59 60 monster = data[lotteryArray[index]].name; 61 if(--(*lotteryCounts) <= 0) { 62 *lotteryCounts = initLotteryArray(lotteryArray, total); 63 } else { 64 //前詰め 65 for(i=index; i<*lotteryCounts; i++) lotteryArray[i] = lotteryArray[i+1]; 66 } 67 return monster; 68} 69 70int getRandomInt(int lotteryCounts) { 71 return rand() % lotteryCounts; 72} 73 74int main() { 75 int i, total = 0, index; 76 int *lotteryArray; 77 int lotteryCounts; 78 char *monster; 79 int counter = 0; 80 double avg; 81 82 for(i=0; data[i].probability>0; i++) total += data[i].probability; 83 84 lotteryArray = (int *)malloc(sizeof(int)*total); 85 lotteryCounts = initLotteryArray(lotteryArray, total); 86 87 for(i=0; i<TIMES; i++){ 88 index = getRandomInt(lotteryCounts); 89 monster = getMonster(index, lotteryArray, &lotteryCounts, total); 90 if(strcmp(monster, "Athena-NON") == 0) counter ++; 91 } 92 93 free(lotteryArray); 94 95 avg = (double)counter / (double)TIMES; 96 printf("排出率: %lf% 排出体数: %d体\n", avg * 100, counter); 97 98 return 0; 99}

なお, このコードでは整数化の為に "100分の1.5" 等ではなく "1000分の15" 等となっています.
つまり, 100 回やっても 1.5 回出るかどうかは分からないが 1000 回やれば必ず 15 回出ます.

投稿2020/01/02 04:37

編集2020/01/02 07:36
jimbe

総合スコア13202

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SEKI817291

2020/01/02 04:41

設定した排出率よりも明らかに異なっていたので。
jimbe

2020/01/02 05:44

「明らかに異なっている」という結論の出し方そのものが間違ってはいませんか, ということです. 確率が"100分の1"だからといって100回に1回"必ず"出るわけではありません. 100万回に1万回出るのでも100分の1で, つまり99万回連続外れでもその後1万回連続出れば"100分の1"と言えるでしょう. もしそのように"設定"出来ていたとして, それを例えば10万回回しても出ず(=0%)「明らかに異なっている」と言ってしまっているのではないでしょうか. 逆に"100分の1"と設定したら必ず100回に1回出るようにしたいということであれば, 回答に書きました通り, ガラガラのようにする方法があるのでは...と思いました.
guest

0

回答ではないです
ガチャの公平性を担保するのはなかなか難しく、Qiita で度々公平性に踏み込んだ記事を見かけます。

まとめがあったので、紹介しておきます。
公平なガチャシステムのまとめ

シミュレータであればあまり関係ないので、ただの参考ですけど。

投稿2020/01/02 03:45

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

コードちゃんど読んでいませんが、rand、特に (double)(rand() % (int)total) は辞めたほうが良いです。

詳細はグーグル先生に譲りますが、この生成方法は偏った乱数を生成します。簡単に実験出来ます。
stdlibのrandも嫌われています

以下に書かれている((double)rand()+1.0)/((double)RAND_MAX+2.0); と XorShiftを組み合わせる方法が簡単そうです

http://www.sat.t.u-tokyo.ac.jp/~omi/random_variables_generation.html#Uniform
https://ja.wikipedia.org/wiki/Xorshift

投稿2020/01/02 03:19

maai

総合スコア463

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SEKI817291

2020/01/02 04:47

Xorshiftを用いたら、同じ排出率のキャラクタ同士に排出体数の差が小さくなりました。 しかし、設定した排出通りになりません。1.50%に設定したキャラクタがおよそ1.00%程度で排出されてしまいます。 知恵袋で回答いただいた方法自体に問題があるのでしょうか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問