アニマロッタ6 アニマと星の物語　スターダストワンダーチャンスの突破率について求める

#### 目的
現在のチャンスカード、微チャンスカードを含めた全てのカード種の突破率を求めたい。
2023.09.18追記：アニマランドで追加されたカードにも対応しました！（多分4種類だけ?)

# スターダストワンダーチャンスの概要
・8球1ゲームで抽選
・1球で1～25を抽選。**1ゲーム間で重複はしない**
・ビンゴカードに書かれた数字が3ゲーム以内に**全て埋まれば**成功
・1枠に2つ数字がある時はどちらかに入ればok
・失敗時はビンゴカードのライン数に応じた配当を獲得(今回は計算外)


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614546/941ecc8c-38ab-0294-9a8c-e2610c7fc22d.png" width=70%>

画像は[公式サイト](https://p.eagate.573.jp/game/lotta/an6/game.html)より


# 実装
これまでのワンダーチャンス(以下WC)と同様、Python3.7を使用し、各パターンについて100万回調べる。
ただし確認できている時点で14パターン存在するため、毎回100回のワンダーを行うのは厳しい。
そこでワンダーチャンスの**ボール抽選結果を固定し**、入力の配置が成功するかを判定する。流れとしては次のようになる。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614546/38b3ad48-a91c-1122-e1c5-660d7c5afeab.png" width=80%>

そのため、プログラムAを1回実行してからプログラムBを実行すれば入力データを変更しない限りは**毎回同じ結果**を得るようになる。


# 実行結果
ムーンライトワンダーチャンスとサンシャインワンダーチャンスの結果も記載すると長くなってしまうため、ここではSTDWCの結果のみを記載する。
(見やすくするために画像にしました！）
![std.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614546/1380c0c7-7fd5-8fe5-0cb1-14152e4c1986.png)



# 考察
ここの部分はメダルゲーマー向けの内容となります。ご了承ください。

#### 思ったより突破率が高い
MLWCの突破率は仮に3ゲーム最高カードが出たとして36.2%、SSWCの最高カードは突破率13.3%なので今回のSTDWCでは突破率が最低8.5%か90.7%と幅広いが、チャンスカードと判定できるカードが思ったより多かったことがわかった。

#### 1マス空き=最高カードでは無い
結果を見る前の予想とては1マス空き(2数字)と1マス空き(1数字)が突破率ツートップとなると思っていた。しかし全パターンを調べてみたところ、1マス空き(1数字)の上には3マス空き(全2数字)が上位、しかも74%と高確率であった。このようになった理由としては組み合わせの量が考えられる。1マス空き(1数字)の場合は**指定された一つの数字に入れる**一方で、3マス空き(各2数字)の場合は**指定された8パターンのうち一つを成功する**という意味になるため3マス空き(各2数字)でも74%という高確率になったのでは無いかと考えられる。


# Q&A
気になった質問をこちらへ
#### Q.9マス空き(全部1数字)の突破率は?
**A.2.0%**
期待が一切できないね。


#### Q.じゃあ9マス空き(全部2数字)の突破率は?
**A.37.2%**
ところでこのカードになると突破方法がなんと512通りもあるよ！めんどくさいね！(プログラム1回実行するのにこれだけ5分以上かかっている。)

#### コンプリートワンダーチャンスは？
11.6%みたいです。

# まとめ

まとめるとスターダストワンダーチャンスは:
・チャンスカードの**パターンが多い**
・**5マス以下**かつ**2数字のマスが存在**するなら**チャンスカード**
・数字2このマスが**多いほど有利**
・1マス空き(1数字)≠最高カード
といった感じです。

ほとんどの場合は6マス空きor5マス空きの1数字のみとかですが、2数字のマスが多数出てきたら期待しましょう。


#### 謝辞
今回の記事を作成するにあたり、ワンダーチャンスの画像、配置パターンのサンプルを提供して頂いた: Jさん、Kさん、Nさん、Sさん、Tさんの5名の方々、ありがとうございます。(イニシャル、アルファ順)


#### 重要なお願い
もし上記結果に存在しないカードパターン(5マス空きの1数字4マス、2数字1マスなど)が発見できた場合はコメントに画像つき(URLリンクなど)で報告をお願いしたいです。ただし**今作の**ワンダーチャンスに限ります。


## 関連リンク
[ムーンライトワンダーチャンスの突破率はこちら](https://qiita.com/TEA4933/items/61602956972910d9c423)
[サンシャインワンダーチャンスの突破率はこちら](https://qiita.com/TEA4933/items/ac014c89fb45da984729)

# 設計
ここからはプログラマー向けの記事となります。また、**この章以降はPCでの閲覧を推奨します。**

他に作成したムーンライトワンダーチャンス(MLWC)とサンシャインワンダーチャンス(以下SSWC)と比べると実装レベルは簡単である。しかし、これまでワンダーチャンスは全パターンで100万回以上の実験を行っていることから工夫して実装したい。

そこで2つのファイルを作成し:
1. ワンダーチャンス中に**どのポケットに入ったかを記録する**ファイル
2. 1のファイルをもとにワンダーチャンスが**成功した回数を調べる**ファイル
を作成する。
#### 1 ゲームログ・ファイルの作成
今回のゲームに関して必要な情報は3ゲームを通してどの数字に入賞したかであって、どの数字に順番に入賞したかは関係ない。そのためRandomのsampleを使って0～24の中から重複しない数字8つを抽出する。

```python:pick_up.py
 for i in range(3):
        total.extend(r.sample(range(25),k=8))
    t = list(set(total))
```
これによってtには0～24が3ゲーム間抽出され、tを結果をみると次のような結果になる。

```python:pick_up_result.py
 t = [0, 1, 2, 3, 4, 5, 8, 9, 11, 12, 14, 16, 18, 19, 21, 22, 24]
```

これを100万回行い、全ての結果をテキストファイルに出力する。作成したファイルの中身は次のようになっている。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614546/1adea5cf-64e5-9bb2-dc2b-6bf6a7373826.png" width=40%>


#### 2 ワンダーチャンス成功を判定するファイル
続いてワンダーチャンスが指定された数字入力でどれほど成功したかの判定を作成する。ファイルの読み込み方法は別記事でも多々存在するのでここでは省略する。まずは必要な数値入力を用意する。今回は1数字入力と2数字入力がある下の画像を入力例として使用する。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614546/126118a9-6a4d-3306-b9cb-5a5ed73db9d9.png" width=30%>
上記画像をNさんから使用許可を頂きました、ありがとうございます。
変数は1数字で必要なものをsingledataの1次元配列、2数字必要なマスを2次元配列として次のような入力を行う。

```python.sample_input.py
singledata = [10,18,19] #数字1個の入力
doubledtata = [[3,11],[6,8],[12,24]] #数字2個の入力
#注意:乱数で選ばれる数値は0～24に対し物理上では1～25ですが今回は問題ないためそのまま入力します。
```
#### ワンダーチャンスの全成功パターン用意
2数字マスがある場合、ワンダーチャンスの成功方法は複数通りある。それを今回はbit演算でどの数字を選ぶかを選択する。今回の上記配置では3マス2数字があるので合計8パターンある。
![bit type.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614546/caf6cb79-2fca-659b-71e1-4be74cdc49be.png)
bitの全パターン変換方法は数え上げを使用し、関数で処理を行う。

```python.addsubdigit.py
def addsubdigit(a):
    p = len(a)-1
    a[p]+=1
    while a[p] == 2:
        a[p] = 0
        p-=1
        a[p]+=1
    return a
```

上記関数を利用し、取得したbit値によって取得する数値を格納すれば良い。

```python.create_allpattern.py
digit = len(doubledtata)
checktype = []
if digit:
    dbdigit = [0 for i in range(digit)] #bitを桁数分用意
    dbdigit[-1] -= 1
    while True: #全ビット数が1になるまで
        ds = []
        dbdigit = addsubdigit(dbdigit) #数え上げ
        for i in range(digit): #現在の桁表示をもとにx2マスでどっちの数字を取るか選択
            ds.append(doubledtata[i][dbdigit[i]])
        checktype.append(ds + singledata) #x1マスと選択したx2マスを結合、確認にぶち込み
        if dbdigit == [1 for i in range(digit)]:break #ここがdo-whileにあたる
    for i in checktype: print(i)
else:checktype.append(singledata)
```
これで変数checktypeには今回の場合8種類の1次元配列が格納されていることになる。

#### 入力データの成功判定
最後に入力データが判定する配列内にあるかをみる。配列の中に指定された数値が**全て**あれば成功ではあるものの、配列では順番が存在するため、[0,1]は[1,0,2]の中に含まれない問題が発生してしまう。そこで配列を集合に変換し、**部分集合であれば**成功となる。アニマロッタの100万ゲームデータはdataに格納されているため、for文で回してlinedata内にあれば成功で良い。なお8パターンある内1パターンでも成功すれば良いため、成功したらbreakを行う。

```python:check.py
for linedata in data:
    for i in checktype:
        if set(i) <= set(linedata): #部分集合なら(つまり指定された数値全部が含まれているなら)
            succeed+=1
            break
```

#### 出力結果
上記内容を組み合わせて実行すると次のようになった。

```result
[3, 6, 12, 10, 18, 19]
[3, 6, 24, 10, 18, 19]
[3, 8, 12, 10, 18, 19]
[3, 8, 24, 10, 18, 19]
[11, 6, 12, 10, 18, 19]
[11, 6, 24, 10, 18, 19]
[11, 8, 12, 10, 18, 19]
[11, 8, 24, 10, 18, 19]

Open spaces 6 Singe: 3 Double: 3
216862 21.6862
1000000
```

実行してみたところ、結果は21.68%で四捨五入を行うと21.7%と表の結果になった。


# プログラム全体のソースコード
### 1.3ゲームでどこに入賞したかをファイルに作るソースコード

```python:stardustwonder_creator.py
import random as r
fi =[]
for loop in range(1000000):
    if (loop + 1) % 5000 == 0: print("\r", loop + 1, end="")
    total = []
    for i in range(3):
        total.extend(r.sample(range(25),k=8))
    t = list(set(total))
    fi.append(t)

with open("anima_std_3gamedata.txt", mode='w') as f:
    for i in fi:
        tex = []
        for k in range(len(i)):
            tex.append(str(i[k]))
            if k != len(i)-1:
                tex.append(" ")
        tep = ''.join(tex)
        f.write( ''.join([str(tep), "\n"]))
f.close()

```
### 2.ワンダーチャンスが成功したかを判定するソースコード

```python:stardustwonder_checker.py
path = 'anima_std_3gamedata.txt'

data = []
print("Reading data...")
loop = 0
with open(path) as f:
    l = f.readlines()
    #print(type(l))
    #print(l)
for i in l:
    loop+=1
    if (loop + 1) % 50000 == 0: print("\r", loop + 1, end="")
    i.replace("\n","")
    da = i.split()
    data.append(list(map(int,da)))
f.close()

def addsubdigit(a):
    p = len(a)-1
    a[p]+=1
    while a[p] == 2:
        a[p] = 0
        p-=1
        a[p]+=1
    return a

print()
singledata = [10,18,19] #数字1個の入力
doubledtata = [[3,11],[6,8],[12,24]] #数字2個の入力
digit = len(doubledtata)
succeed = 0
loop = 0
checktype = []
if digit:
    dbdigit = [0 for i in range(digit)] #bitを桁数分用意
    dbdigit[-1] -= 1
    while True: #全ビット数が1になるまで
        ds = []
        dbdigit = addsubdigit(dbdigit) #数え上げ
        for i in range(digit): #現在の桁表示をもとにx2マスでどっちの数字を取るか選択
            ds.append(doubledtata[i][dbdigit[i]])
        checktype.append(ds + singledata) #x1マスと選択したx2マスを結合、確認にぶち込み
        if dbdigit == [1 for i in range(digit)]:break #ここがdo-whileにあたる
    for i in checktype: print(i)
else:checktype.append(singledata)

print()
print("Checking data...")
for linedata in data:
    loop+=1
    if (loop + 1) % 10000 == 0: print("\r", loop + 1,succeed, end="") #経過数表記
    for i in checktype:
        if set(i) <= set(linedata): #部分集合なら(つまり指定された数値全部が含まれているなら)
            succeed+=1
            break

print()
print("Open spaces",len(singledata)+digit,"Singe:",len(singledata),"Double:",digit)
print(succeed,succeed/len(data)*100)
print(len(data))
```