Bacon.js でカタンのサイコロをシミュレーションしてみた


# 概要
Bacon.js を用いて、カタンのサイコロをシミュレーションし、サイコロの目に偏りがあるのかを検証しました。36回をひとまとめとし、各出目（2〜12）が平均確率以上の頻度でどの程度出るのかを確認します。

注: 特に[カタン](http://ja.wikipedia.org/wiki/%E3%82%AB%E3%82%BF%E3%83%B3%E3%81%AE%E9%96%8B%E6%8B%93%E8%80%85%E3%81%9F%E3%81%A1)について知らなくても、支障ありません。Bacon.js に興味ある方は、入門編としてわりと役に立つと思います。


# 準備

## サイコロ

Bacon.js の EventStream.fromPoll メソッドを用いて、100msec に1回サイコロを振ります。

``` JavaScript
function rollDie() {
    return Math.ceil(Math.random() * 6);
}


function rollTwoDice() {
    return rollDie() + rollDie();
}


var diceStream = Bacon.fromPoll(100, function() {
    return new Bacon.Next(rollTwoDice());
});
```

## 試行回数をカウント

withStateMachine を用いて試行回数をカウントします。

``` JavaScript
var counterStream = diceStream.withStateMachine(0, function(sum, event) {
    if (event.hasValue()) {
        sum = sum + 1;
        return [sum, [new Bacon.Next(sum)]];
    }
});
```

## 36回をひとまとめに
出目の偏りを見るために、連続する36回ごとにひとまとめにします。

- 空の配列を用意
- event を受け取ったら後ろに追加
- 36件より多くなったら、古いものから削除

このようにして、最大36の長さの配列を subscriber に渡します。

``` JavaScript
var RANGE = 36;

var bufferedStream = diceStream.withStateMachine(new Array(), function(sum, event) {
    if (event.hasValue()) {
        sum.push(event.value());
        if (sum.length > RANGE) {
            sum.shift();
        }
        return [sum, [new Bacon.Next(sum)]];
    }
});
```

## 出現確率を計算

最新の出目の（過去36回内での）出現回数と、理論上の出現確率との比を subscriber へ返します。ただし、36回貯まるまでの間は、比率を返さないようにします（ややイケてない）。

``` JavaScript
var PRODUCTIVITY = {
    2: 1/36,
    3: 2/36,
    4: 3/36,
    5: 4/36,
    6: 5/36,
    7: 6/36,
    8: 5/36,
    9: 4/36,
    10: 3/36,
    11: 2/36,
    12: 1/36
};

var productivityStream = bufferedStream.map(function (value) {
    var current = value[value.length - 1];
    var prod = PRODUCTIVITY[current];
    var num = value.filter(function (x) {return x == current;}).length;
    var rate = (num / RANGE) / prod;
    if (value.length < RANGE) {
        return [num, undefined];
    } else {
        return [num, rate];
    }
});
```

## 出現確率の確率を評価

出現確率の偏りに応じて、メッセージを表示します。

|比率|メッセージ|
|---|---------|
|3倍以上|ｷﾀｧ━━━(　´∀｀)━━━━!!!|
|2倍以上|ｷﾀ(･∀･)ｺﾚ!!|
|0.5~2倍|表示なし|
|0.5倍以下|∑(￣Д￣；)なぬぅっ！！|

``` JavaScript
var productivityStateStream = productivityStream.map(function (value) {
    if (value[1] === undefined) {
        return "";
    }
    if (value[1] >= 3.0) {
        return "ｷﾀｧ━━━(　´∀｀)━━━━!!!";
    } else if (value[1] >= 2.0) {
        return "ｷﾀ(･∀･)ｺﾚ!!";
    } else if (value[1] > 0.5) {
        return "";
    } else {
        return "∑(￣Д￣；)なぬぅっ！！";
    }
});

```

# 実行
最後に、これらの EventStream を結合して画面に表示します。event の同期を取るのに combineTemplate が便利です。

``` JavaScript
    var $result = $('#result');
    Bacon.combineTemplate({
        'dice': diceStream,
        'counter': counterStream,
        'productivity': productivityStream,
        'state': productivityStateStream
    }).onValue(function(value) {
        var val = value.counter + ": " + value.dice + " (" + value.productivity[0] + ") " + value.state;
        $result.append($('<li>').text(val));
    });

```

# ソースコードと実行結果
ソースコードはこちらです。

- https://github.com/ledmonster/joking.bacon.catan

実行結果はここで見れます。

- http://ledmonster.github.io/joking.bacon.catan/ (1000件で打ち止め)

# 参考
- [カタンの流れを検証してみた](http://www.slideshare.net/ledmonster/ss-38441555)