一個二維平面上有一羣NPC,每一回合能夠隨機向上/下/左/右任一方向走1步,有單位碰撞體積(NPC位置不能重合)javascript
規則就這麼簡單,初始狀況下這羣NPC是被人工均勻分佈在二維平面上的,運行N個回合後發現全部NPC都集中在了左下角。。怎麼會這樣,說好的隨機呢?java
現有的實現是這樣的:數組
根據NPC的當前位置判斷獲得能夠去的位置,把結果存放在一維數組arr裏dom
P.S.上/下/左/右最多4個點(周圍空蕩蕩的),最少0個點(被圍起來了)wordpress
生成[0, arr.length – 1]內的一個隨機數,做爲目標位置索引值index函數
// 生成隨機數[min, max] w.Util.rand = function(min, max) { return Math.round(Math.random() * (max - min) + min); }
控制NPC移動到arr[index]的位置測試
邏輯應該是沒有問題的,但是爲何運行結果是NPC都跑到左下角開會去了呢?等等,爲何是左下而不是其它角角?.net
由於實現第一步的時候是按照上 -> 下 -> 左 -> 右的順序判斷的,最後都去了左下角,說明向上和向右的機率過小了(生成隨機數的函數rand是沒問題的,確實能獲得[min, max]的數)3d
問題的根源是Math.random()不給力,生成[0, 1)之間的小數,取到0和靠近1的值機率很小,因此rand()函數生成的隨機數取到min和mix的機率也很小,向上/向右的可能性也就小了blog
既然取到min和max的機率很小,中間機率比較均勻,那好辦,切掉這兩個值就行了。具體實現以下:
function randEx(min, max) { var num; var maxEx = max + 2; // 擴大範圍到[min, max + 2],引入兩個多餘值替換min/max do{ num = Math.round(Math.random() * (maxEx - min) + min); num--; } while (num < min || num > max); // 範圍不對,繼續循環 return num; }
值得一提的是上面的加2減1比較巧妙,可以剛好排除min和max
JS中Math.random()返回值的機率差別可能比你想象的要大些,在實際應用中是不可接受的
測試代碼以下:
// 生成隨機數[min, max] w.Util.rand = function(min, max) { return Math.round(Math.random() * (max - min) + min); } // 生成更隨機的隨機數[min, max] function randEx(min, max) { var num; var maxEx = max + 2; // 擴大範圍到[min, max + 2],引入兩個多餘值替換min/max do{ num = Math.round(Math.random() * (maxEx - min) + min); num--; } while (num < min || num > max); // 範圍不對,繼續循環 return num; } function testRand(times, fun) { var arr = [1, 2, 3, 4]; var count = []; var randVal; for (var i = 0; i < times; i++) { // 獲取[0, 3]的隨機數 randVal = fun(0, arr.length - 1); // 記錄次數 if (typeof count[randVal] !== "number") { count[randVal] = 0; } count[randVal]++; } console.log(count); } console.log("100 times"); testRand(100, w.Util.rand); // 以前的實現 testRand(100, randEx); // 改進過的實現 console.log("1000 times"); testRand(1000, w.Util.rand); // 以前的實現 testRand(1000, randEx); // 改進過的實現 console.log("10000 times"); testRand(10000, w.Util.rand); // 以前的實現 testRand(10000, randEx); // 改進過的實現 console.log("100000 times"); testRand(100000, w.Util.rand); // 以前的實現 testRand(100000, randEx); // 改進過的實現
運行結果以下:
看到了吧,效果仍是很不錯的
理論上Java的Math.random(),C#的next可能也存在這個問題,這裏就沒有必要驗證了,由於randEx函數的思想(加2減1,嫌效果很差還能夠加4減二、加6減3……直到滿意爲止)是通用的