スポンサーリンク
※サイト運営にサーバーは必須です※
~ ロリポップ! はコスパのよい初心者向けサーバーです~
目次
はじめに
※目次用の記事:ギャンブルの賭け方の種類をまとめてみた
賭け方にはいくつか種類がある。
そのうちの一つに、2in1法というのがある。
この記事では、2in1法とはなにかを説明した後、プログラムによるシミュレーション結果を示す。
2in1法とは
数列を用意する。最初は、数字の1だけ
(1)
数列の左端と右端を足し合わせた数だけ賭ける
※ただし、最初は数列の中身は1つしかないので、1だけ賭ける
負けた場合:賭け金額を数列の右端に加える
勝った場合:数列の右端と左端を消す
数列の大きさが1より小さくなった場合は、ゲームをリセット。数列は(1)から再びスタートする。
※数列の大きさが1以下だと、右端と左端を足し合わせて、賭け金額を算出するという操作ができなくなる。
※数列は、賭けた金額を記録することに対応する
2in1法の具体例
ルールだけ、説明してもイメージがつかめないと思うので、具体例を挙げる。
勝負 | 数列 | 賭け金額 | 勝敗 | 利益 |
初期 | 1 | 1 | × | -1 |
× | 11 | 2 | × | -3 |
×× | 112 | 3 | × | -6 |
××× | 1123 | 4 | ○ | -2 |
×××○ | 12 | 3 | × | -5 |
×××○× | 123 | 4 | × | -9 |
×××○×× | 1234 | 5 | ○ | -4 |
×××○××○ | 23 | 5 | ○ | 1 |
×××○××○○ | なし | ゲーム終了 |
1回目の勝負にかける金額は1。
※ここで、勝てば、数列はリセットされ、再び1を賭ける。
もしも、負けたら、賭けた金額を数列の右端に加える
(1,1)
2回目に賭ける金額は、一番右端と左端を足し合わせた1+1=2
2回目負けると、賭けた金額を数列の右端に加える
(1,1,2)
3回目に賭ける金額は、一番右端と左端を足し合わせた1+2=3
3回目負けると、賭けた金額を数列の右端に加える
(1,1,2,3)
3回目に賭ける金額は、一番右端と左端を足し合わせた1+3=4
4回目勝つと、数列の右端と左端を消す
(1,1,2,3)
このような操作を数列が、完全に消えるか、1つ残るまで続ける。
2in1法の特徴
賭け方の動きは、モンテカルロ法とほぼ同じ。
2in1法は、最初の数列が(1)でモンテカルロ法は(1,2,3)の違いであると言ってもほぼ間違いはない。
※2in1法は、最初の1回目の数列の大きさが1という所が、若干イレギュラーな感じがある。後で紹介するプログラムを見れば、気が付くかもしれないが、初期の賭ける金額は、m_bet(賭け金額)=vec[0]+vec[size-1](数列の両端)でなくm_bet=vec[0] (数列の左端)と書いている
モンテカルロ法で指摘したことと被るが……
負けた場合、賭け金額を右端に加える。
これは、負けた金額を記憶しておくことに対応する。
勝った場合、右端と左端を消す。
これは、負けた時の金額を帳消しすることを意味する。
負けた時に、数字を付け加えるスピードが1だとしたら、勝った時は、数字を消すスピードは2とえる。
そういう意味で、1回の勝ちで、2回分の負けを帳消ししている。(たぶんこれが、2in1の名前の由来だろう)
数列が全部消えて、ゲームのセッションが終わった場合、最初の数列である(1)の分だけ、利益がでる。つまり、数列が完全に消えた時に、最初に持っていた金額よりも+1多い金額になる。
しかし、既に気づいている方もいると思うが、2in1法は、数列に1つ数字が残っている場合もゲームがリセットされる。
その場合、
1-(最後に残った数列の数字)
が手持ちの金額として増減する。
もしも(最後に残った数列の数字)が大きいと、ゲームがリセットされても損する。
※2in1法を、払い戻し金が3倍の時に、適用したい場合は、モンテカルロ法に習って、数列の一番左と一番右合わせて計4つを一気に消せばよい。
※もしも、ゲームがリセットされても、損することのない手法にしたいのなら、数列の大きさが1になっても、一番左に、新たに数字(例えば1)を加えて、ゲームを続行させるルールを加えれば可能である。例えば、最後の数列の数字が(7)だったら、左に1を加え、(1,7)にしてゲームを続けるようにする。もしくは、数列に何も加えず、最初の1回目のようにゲームを進める。つまり、(7)のままで、次の勝負7を賭ける。もしも、賭ける金額が膨らむのを嫌うのなら、モンテカルロ法(賭け)の欠点&改良手法の提案(c++)で指摘したように、数字を分解すればよい。7→(3,4)
2in1法の検証
以下では2種類のプログラムを紹介する。
言語はC++で作った。
※C言語では動かない。大きさの変わる配列を扱う必要があり、vectorという動的配列を取り扱っている。vector はC言語では使用できないので注意。
※このプログラムを組んだ人間は、大学時代に少しプログラムをかじった程度の戦闘能力しかない。
2in1法個別シミュレーション
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> #include <vector> using namespace std; // 名前空間指定 int main() { int i=0; int i_max=100000;//勝負回数 double w=2.0; //賭け金の変換率(2倍以上)(win) double p=50; //勝率(50%以下)(probability) double m_min=1;//最小の賭け金額(money) double m_ini=1000;//最初の手持ちの資金(money_initial) int c;//勝ち負け差のカウント(count) c=0;//初期化 //乱数(時間による変化) srand((unsigned)time(NULL)); //ファイルの出力準備 FILE *sf; sf = fopen("simulation.dat","w");//出力するファイルの名前を指定 //エラー if(sf==NULL){ printf("ファイルオープンエラー\n"); return -1; } //初期条件の表示 printf("#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); fprintf(sf,"#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); double m_now;//現在の資金 double m_bet;//賭け金 //初期化 m_now=m_ini;//最初は最初の手持ちの資金でスタート //m_bet=m_min;//賭け金は最小の賭け金額でスタート // vct(vector)を生成 int n; vector<int> vec; //初期化 vec.push_back(1); int size; size = (int)vec.size(); m_bet=vec[0]*m_min; for(i=1;i<=i_max;i++) { double r;//乱数(rand) r=100.0*rand()/(RAND_MAX+1.0);//0から100の乱数を出す printf("%d回目\t賭け金は%lf\t",i,m_bet); fprintf(sf,"%d回目\t賭け金は%lf\t",i,m_bet); printf("数列:");//配列の出力 fprintf(sf,"数列:"); for(n=0;n<size;n++){ printf("%d/",vec[n]); fprintf(sf,"%d/",vec[n]); } printf("\t"); fprintf(sf,"\t"); if (p<=r){//負けの場合 m_now-=m_bet; vec.push_back(m_bet/m_min); // 末尾にm_bet を追加 c-=1; printf("lose\t"); fprintf(sf,"lose\t"); } else{//勝ちの場合 m_now +=m_bet*(w-1.0); vec.erase(vec.begin()); vec.pop_back(); c+=1; printf("win\t"); fprintf(sf,"win\t"); } printf("勝敗差%d\t手持ちのお金は%lf",c,m_now); fprintf(sf,"勝敗差%d\t手持ちのお金は%lf",c,m_now); size = (int)vec.size();//sizeの大きさ再度確認 m_bet=(vec[0]+vec[size-1])*m_min; if(size<=1){ printf("\t数列リセット"); fprintf(sf,"\t数列リセット"); vec.clear(); vec.push_back(1); m_bet=vec[0]*m_min; size = (int)vec.size();//sizeの大きさ再度確認 } printf("\n"); fprintf(sf,"\n"); if(m_now<m_bet){//手持ちの金より賭けるお金が高い場合 printf("次の勝負に必要な金額は%lfです。%d回目でゲームを終了します\n",m_bet,i); fprintf(sf,"次の勝負に必要な金額は%lfです。%d回目でゲームを終了します\n",m_bet,i); break; } }//for(i)文の終わり //sfクローズ fclose(sf); return 0; }//プログラムの終わり |
プログラムで使用している変数の説明
※マーチンゲール法(2倍賭け)の破綻までのシミュレーションで紹介したプログラムと同じ変数の置き方をしている
i_max:勝負の回数
w:賭けたお金の戻る倍率を指定。ここでは2倍を指定
p:勝率を指定。ここでは1/2の確率なので50を指定。
m_min:かけ金を指定
m_ini:最初の資金を指定
※ここではm_min=1でm_ini=1000と、最小の賭け金の1000倍を所持していると考えている。
例えば、最小の賭け金が1000円(千円)だとするなら、手元の軍資金は、1000000円(百万円)。割と現実的な設定だと思われる。
r:乱数(0~100の乱数)
※乱数rが勝率pより下の数で収まるなら勝ちの判定がでる。
そして、勝負を続けていくうちに、負け続けることもあるだろう。
そしてついに、賭けしようにも手持ちのお金が足りなくなるかもしれない。
この場合、これ以上勝負ができなくなり、プログラムは終了する。(破綻判定)
つまり、借金はNG
c:トータルの勝ち負けの差がどのくらいあるか調べる用 。
実際にプログラムを走らせると以下のような結果となる
勝負回数i_maxは100000(10万回)までとする
#倍率は2.000000 勝率は50.000000 最小の賭け金は1.000000 手持ちのお金は1000.000000
1回目 賭け金は1.000000 数列:1/ win 勝敗差1 手持ちのお金は1001.000000 数列リセット
(省略)
28回目 賭け金は1.000000 数列:1/ win 勝敗差8 手持ちのお金は1016.000000 数列リセット
29回目 賭け金は1.000000 数列:1/ lose 勝敗差7 手持ちのお金は1015.000000
30回目 賭け金は2.000000 数列:1/1/ lose 勝敗差6 手持ちのお金は1013.000000
31回目 賭け金は3.000000 数列:1/1/2/ lose 勝敗差5 手持ちのお金は1010.000000
32回目 賭け金は4.000000 数列:1/1/2/3/ lose 勝敗差4 手持ちのお金は1006.000000
33回目 賭け金は5.000000 数列:1/1/2/3/4/ lose 勝敗差3 手持ちのお金は1001.000000
34回目 賭け金は6.000000 数列:1/1/2/3/4/5/ win 勝敗差4 手持ちのお金は1007.000000
35回目 賭け金は5.000000 数列:1/2/3/4/ lose 勝敗差3 手持ちのお金は1002.000000
36回目 賭け金は6.000000 数列:1/2/3/4/5/ lose 勝敗差2 手持ちのお金は996.000000
37回目 賭け金は7.000000 数列:1/2/3/4/5/6/ lose 勝敗差1 手持ちのお金は989.000000
38回目 賭け金は8.000000 数列:1/2/3/4/5/6/7/ lose 勝敗差0 手持ちのお金は981.000000
39回目 賭け金は9.000000 数列:1/2/3/4/5/6/7/8/ win 勝敗差1 手持ちのお金は990.000000
40回目 賭け金は9.000000 数列:2/3/4/5/6/7/ lose 勝敗差0 手持ちのお金は981.000000
41回目 賭け金は11.000000 数列:2/3/4/5/6/7/9/ win 勝敗差1 手持ちのお金は992.000000
42回目 賭け金は10.000000 数列:3/4/5/6/7/ lose 勝敗差0 手持ちのお金は982.000000
43回目 賭け金は13.000000 数列:3/4/5/6/7/10/ win 勝敗差1 手持ちのお金は995.000000
44回目 賭け金は11.000000 数列:4/5/6/7/ win 勝敗差2 手持ちのお金は1006.000000
45回目 賭け金は11.000000 数列:5/6/ win 勝敗差3 手持ちのお金は1017.000000 数列リセット
(省略)
17529回目 賭け金は551.000000 数列:43/56/69/92/115/138/161/184/207/250/293/336/379/422/465/508/ lose 勝敗差-3 手持ちのお金は106.000000
次の勝負に必要な金額は594.000000です。17529回目でゲームを終了します
28-45回目までの流れが、2in1法を理解するのにちょうどいいかなと思って、切り取っている。
28回目終了して、29回目最初の段階で持っている金額は1016。45回目で勝利して、数列がぴったり消えて、45回目が終わった段階で持っている金額は1017。最初の段階より+1される
※srand((unsigned)time(NULL));の部分で時間を参照した上で乱数を発生させている。そのため、実行するタイミングで結果が変化する。
2in1法の期待値シミュレーション
上のプログラムを何度も走らせて期待値や破綻する率をもとめたい
そのためのプログラムは以下のようになる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> #include <vector> using namespace std; // 名前空間指定 int main() { int k; int k_max=100000;//試行回数 int i=0; int i_max=100000;//勝負回数 double w=2.0; //賭け金の変換率(2倍以上)(win) double p=50; //勝率(50%以下)(probability) double m_min=1;//最小の賭け金額(money) double m_ini=1000;//最初の手持ちの資金(money_initial) int b;//破たん回数のカウント(Bankruptcy) b=0;//初期化 int i_sum;//勝負回数の合計 double m_sum;//最終的なお金の合計 i_sum=0;//初期化 m_sum=0.0; int c_sum;//勝負差の合計 c_sum=0;//初期化 //乱数(時間による変化) srand((unsigned)time(NULL)); //ファイルの出力準備 FILE *sf; sf = fopen("simulation.dat","w");//出力するファイルの名前を指定 //エラー if(sf==NULL){ printf("ファイルオープンエラー\n"); return -1; } //初期条件の表示 printf("#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); fprintf(sf,"#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); for(k=1;k<=k_max;k++) { int i_count;//勝負回数のカウント i_count=0; double m_now;//現在の資金 double m_bet;//賭け金 //初期化 m_now=m_ini;//最初は最初の手持ちの資金でスタート int c;//勝ち負け差のカウント(count) c=0;//初期化 // vct(vector)を生成 //int n; vector<int> vec; //初期化 vec.push_back(1); int size; size = (int)vec.size(); m_bet=vec[0]*m_min; for(i=1;i<=i_max;i++) { double r;//乱数(rand) r=100.0*rand()/(RAND_MAX+1.0);//0から100の乱数を出す if (p<=r){//負けの場合 m_now-=m_bet; vec.push_back(m_bet/m_min); // 末尾にm_bet を追加 c-=1; } else{//勝ちの場合 m_now +=m_bet*(w-1.0); vec.erase(vec.begin()); vec.pop_back(); c+=1; } i_count=i;//i_countを定義しないと破たんしない(セーフ)場合、iと値が1ずれてしまうため size = (int)vec.size();//sizeの大きさ再度確認 m_bet=(vec[0]+vec[size-1])*m_min; if(size<=1){ vec.clear(); vec.push_back(1); m_bet=vec[0]*m_min; size = (int)vec.size();//sizeの大きさ再度確認 } if(m_now<m_bet){//手持ちの金より賭けるお金が高い場合 break; } }//for(i)文の終わり if(i_count<i_max) { printf("%d番目%d回目で破たん\t手持ちのお金は%lf\n",k,i_count,m_now); fprintf(sf,"%d番目%d回目で破たん\t手持ちのお金は%lf\n",k,i_count,m_now); b=b+1; } else{ printf("%d番目\t%d回目までセーフ\t手持ちのお金は%lf\n",k,i_count,m_now); fprintf(sf,"%d番目\t%d回目までセーフ\t手持ちのお金は%lf\n",k,i_count,m_now); } i_sum=i_sum+i_count; m_sum=m_sum+m_now; c_sum=c_sum+c; }//for(k)文の終わり //破たん率 printf("勝負回数は%d\n",i_max); printf("試行回数は%d\n",k_max); printf("破たん回数は%d\n",b); fprintf(sf,"勝負回数は%d\n",i_max); fprintf(sf,"試行回数は%d\n",k_max); fprintf(sf,"破たん回数は%d\n",b); double b_pro= (double)b/(double)k_max*100.0;//(double)を抜くとb/k_maxがゼロと評価されてしまう printf("破たん率は%lf%\n",b_pro); fprintf(sf,"破たん率は%lf%\n",b_pro); double i_exp=(double)i_sum/(double)k_max; double m_exp=(double)m_sum/(double)k_max; printf("破たんするまでに行える勝負回数の期待値(i_exp)は%lf\n",i_exp); printf("破たんする直前で持っているお金の期待値(m_exp)は%lf\n",m_exp); fprintf(sf,"破たんするまでに行える勝負回数の期待値(i_exp)は%lf\n",i_exp); fprintf(sf,"破たんする直前で持っているお金の期待値(m_exp)は%lf\n",m_exp); double c_exp=(double)c_sum/(double)k_max; printf("破たんした時の勝負差の期待値は%lf\n",c_exp); fprintf(sf,"破たんした時の勝負差の期待値は%lf\n",c_exp); //sfクローズ fclose(sf); return 0; }//プログラムの終わり |
プログラムで使用している変数の説明
b_pro:破綻率
※ここでいう破綻率とは手持ちのお金が、賭けに耐えられない状況を意味する。借金してお金を用意することはできないとする。
i_exp: 破綻するまで何回勝負ができるかの期待値
m_exp: 勝負が終わった段階で持っているお金の期待値
k_max:試行回数。
※k_maxを大きくすればするほど、正確な期待値が求まる。
c_exp:勝敗差の期待値
以下では、k_max=100000、i_max=100000で指定。
試行回数10万回、勝負回数10万回。
勝負回数は100000
試行回数は100000
破たん回数は96618
破たん率は96.618000%
破たんするまでに行える勝負回数の期待値(i_exp)は12551.335270
破たんする直前で持っているお金の期待値(m_exp)は991.885580
破たんした時の勝負差の期待値は-0.452730
破綻率96.618%は、高いように見えるが、マーチンゲール法などの99%越えと比べるとだいぶましな数字である。
破綻するまでに行える勝負回数の期待値(i_exp)も12551回と1万回を超えしている。単純なモンテカルロ法よりは、破綻しにくく、長くゲームができる。
モンテカルロ法より破綻率が小さくなるのは、はっきりわからないが、以下の2つが原因の候補
- モンテカルロ法と比べて、数列がリセットされた時に、2in1法は、最終的な収支において損が出やすい(損が出やすいというのは、逆に考えると、損が出ることに対して、寛容であるという意味だ。損が出ることに対して寛容でない手法ほど、破綻しやすい)
- 最初に賭ける金額が、モンテカルロ法だと4(1+3)で、2in1法だと、1である。より小さな額から勝負ができる
ちなみに、2in1法の破綻率にもっとも近いのは、モンテカルロ法(賭け)の欠点&改良手法の提案(c++)の記事で提案した手法だ。
※その記事で提案した手法は、2in1法の初めの数列が(1)でなく(0,1)。また、数字が1個残った時に、分解する手順を加えただけの手法といえる。似たような結果になるのは、納得がいく。
まとめ
- 2in1法は、払い戻し2倍で使用されることを想定されている
- モンテカルロ法と似ている手法で、最初の数列が違うだけと捉えることができる
- 数列が1つも残らず、リセットされると、最終的な収支は+1される。ただし、数列の数字が1つ残ってゲームを終えた場合は、損をだすことがある。
- モンテカルロ法より、長くゲームが楽しめる
書籍の紹介
『ギャンブルの必勝法が本当に儲かるかプログラミングで検証してみた』は以下の疑問にお答えします。
●勝率が50%の場合、利益を生み出す必勝法は存在するか?
●勝率が60%の場合、どのように賭けるのが最適か?
必勝法と思われている手法を15種類紹介します。必勝法には、例えば、マーチンゲール法(負けた時に2倍賭ける手法)などがあります。これらの手法が本当に儲かるかプログラミングを使用して検証します。また、検証するために必要なプログラミングの知識(C#)も紹介しています。
ギャンブルの必勝法が本当に儲かるかプログラミングで検証してみた
関連記事
※目次用の記事:ギャンブルの賭け方の種類をまとめてみた
~ギャンブルに絶対儲かる必勝法があるのだろうか?~
私(サイト主)はこの疑問に対して非常に興味を持ち、プログラミングで検証してみました。
このサイトを応援してもいいかなと思う人はぜひとも購入を検討してみてください。
コメント