背景
最近RUSTを勉強していて、ある程度Syntaxなどを覚えたので、手を動かしながら進もうと思いました。やはりはじめは自分がよく知っている機械学習の分野からやることにした。それの結果、今回はある問題にMonte Carlo法で近似してみます。
問題設定 1
二つの6面サイコロを振って、両方とも同じ面が出れば5スコアをもらえる、それ以外の場合は1スコア引かれる。初期は1000スコアを持っている。質問①:勝てる確率はいくら?質問②:平均収益はいくら?
分析
この問題は結構簡単で計算ができるんですが、あえて先に簡単なルールで実装して、RUSTで問題なければ、難易度上げて遊ぼうと思います。
先に計算方法で結果を出すと勝てる確率は$P=(6/36)$ ,
出た目 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
1 | +5 | -1 | -1 | -1 | -1 | -1 |
2 | -1 | +5 | -1 | -1 | -1 | -1 |
3 | -1 | -1 | +5 | -1 | -1 | -1 |
4 | -1 | -1 | -1 | +5 | -1 | -1 |
5 | -1 | -1 | -1 | -1 | +5 | -1 |
6 | -1 | -1 | -1 | -1 | -1 | +5 |
そして期待収益は
$ E[X]=[5\times {\frac {1}{6}}+[-1\times {\frac {5}{6}}]]=0.0$
ま~、この金額設定だと収益はゼロということです。こう設定したのもプログラムをテストするためであって、いいじゃないですか。
実装
先にcargo使ってRustパッケージを作る
cargo new mc_dice --bin
そして先にサイコロを作っていく
dice.rs
use rand::Rng;//乱数生成のために
//二つのサイコロを手に入れる
pub struct Dices {
pub first_dice: u8,
pub second_dice: u8,
}
//初期化
//初期化は定数で設定しちゃった、何かもっといい方があるはず
impl Dices {
pub fn new() -> Dices {
Dices {
first_dice: 1u8,
second_dice: 1u8,
}
}
}
//サイコロを振る
impl Dices {
pub fn roll(&mut self) {
let mut rng = rand::thread_rng();
self.first_dice = rng.gen_range(1..=6);
self.second_dice = rng.gen_range(1..=6);
}
}
そしてゲームルールを作っていく
game.rs
use crate::dice::Dices;
pub fn black_box_1(dices: &Dices) -> bool {
if dices.first_dice == dices.second_dice {
return true;
} else {
return false;
}
}
TODO: なんで関数をblack_box_1
にしたか、その考えは後に記入します。
それでゲーム開始:
main.rs
mod dice;
mod game;
use crate::{dice::Dices, game::black_box_1};
fn main() {
let num_simulations: usize = 10000;//Play回数
let bet: i32 = 1;//毎回Play時に使う金額
let mut win_probability: Vec<f32> = Vec::new();//勝てる可能性
let mut end_balance: Vec<i32> = Vec::new();//毎回収益金額
let mut overall_win_probability = 0.0;//全体的な勝てる可能性
let mut overall_end_balance = 0;//全体的な収益金額
let max_num_rolls = 999;//一回Playでサイコロを振る回数
for i in 0..num_simulations {
let min: i32;
let max: i32;
let mut dices: Dices = Dices::new();//毎回Play時サイコロを初期化する、しなくていいけど
let mut cash: Vec<i32> = Vec::new();
cash.push(1000);//毎回Play時1000$課金する
let mut num_rolls: Vec<i32> = Vec::new();
num_rolls.push(0);
let mut num_wins = 0;
let mut _probability: f32 = 0.0;
while num_rolls.last().unwrap() < &max_num_rolls {
dices.roll();
if black_box_1(&dices) {//ルールによって判断する
num_wins += 1;
cash.push(cash.last().unwrap() + bet * 5);//勝った
} else {
cash.push(cash.last().unwrap() - bet);
}
num_rolls.push(num_rolls.last().unwrap() + 1);//負けた
}
_probability = num_wins as f32 / num_rolls.len() as f32;
win_probability.push(_probability);
end_balance.push(*cash.last().unwrap());
if cash.last().unwrap() <= &0 {//お金がなければ、出っていけ~~~
println!("You lost all your money!");
break;
}
}
overall_win_probability = win_probability.iter().sum::<f32>() / win_probability.len() as f32;
overall_end_balance = end_balance.iter().sum::<i32>() / end_balance.len() as i32;
println!("Over all probability of winning: {:?}", overall_win_probability);
println!("Final balance: {:?}", overall_end_balance);
}
まだ、RUSTにそこまで慣れていないので、ちょっと見ずらいコードになっている、だが今後いつか戻ってこの記事を見た時を楽しみにしています、((´∀`*))ヶラヶラ
結果 1
予測通り, 収益は0に近い値が出る。
Over all probability of winning: 0.16651642
Final balance: 1000
ここまで、RUSTのテストでした。それではちょっとゲームルールを変えて遊びます。
問題設定 2
二つの6面サイコロを振って、両方とも同じ面が出って、二つを足し合わせてある2~11の間でランダム選択された値より大きければ勝利と判断される、そして4スコアをもらえるそれ以外の場合は1スコア引かれる。初期は1000スコアを持っている。質問①:このゲームの勝てる確率はいくら?質問②:平均収益はいくら?
分析
…いらないでしょう~~
実装
pub fn black_box_2(dices: &Dices) -> bool {
let mut rng = rand::thread_rng();
let x_num: u8 = rng.gen_range(2..=11);
if dices.first_dice == dices.second_dice {
if dices.first_dice + dices.second_dice >= x_num {
return true;
} else {
return false;
}
} else {
return false;
}
}
結果
Over all probability of winning: 0.09698783
Final balance: 582
結論
ギャンブルはやばい~~ではなくて、RUSTはやばい~pythonだったらかんたんでできるなのに。ま~でもRUSTも面白いところはある。今後も続けられるそう~~
この例ではMCが何が強いかちょっとわかりづらいかな、でも計算コストがPFlopsで表現する今の時代ではやはりMCみたいな離散かできて、計算力がすべてみたいなアルゴリズムがいいかもしれない。