40+ 行 JS 代碼打造你的 2048 遊戲 AI

2048 遊戲是什麼?

2048 遊戲以下圖所示,它由一個 4*4 共 16 個方塊組成。玩家能夠經過「上下左右」四個方向操縱方塊滑動,滑動時兩個相鄰且數值相同的方塊會合並,新的方塊,數值爲二者之和。當遊戲裏任意方塊的數值達到 2048,即爲勝利。git

2048

咱們將使用「蒙特卡洛方法」來打造 2048 AI。github

蒙特卡洛方法是什麼?

有不少問題,數學公式很複雜,甚至短期內找不到數學公式。好比下面的不規則形狀的面積。算法

monte3.gif

咱們能夠經過一種「統計模擬」手段,在實踐上獲得上述不規則形狀面積的近似值。作法就是:1)在正方形裏生成許多位置隨機的點;2)統計在不規則圖形內的點的數量;3)計算步驟2獲得的數量跟總數的比值;4)用正方形的面積乘以步驟三獲得的比值,就是不規則形狀面積的近似值。編程

上述作法,就是一個典型的蒙特卡洛方法。當咱們生成的隨機點數量足夠大時,咱們獲得的近似值跟理論計算值就愈加接近,偏差愈加小。以下圖所示,求正方形裏的扇形面積的蒙特卡洛方法的模擬過程:數組

monte-carlo-method

上面兩幅圖,只是蒙特卡洛方法的兩個應用而已。事實上,蒙特卡洛方法的適用範圍很廣,任何可模擬和統計的比例分佈,均可以使用蒙特卡洛方法來模擬。好比檢測硬幣構造上是否足夠均衡。微信

理論上,拋硬幣的正反面機率是同樣的,各50%。然而,實際工藝上,作不到絕對均勻,總有誤差。要想知道這個誤差,是偏向正面,仍是偏向反面,可使用蒙特卡洛方法。不斷地拋硬幣,而後統計正反面所佔的比例,當拋硬幣的次數是無限大時,這個比例就反映了硬幣的均勻性。現實中,咱們作不到無限次拋硬幣,因此只能在某個偏差範圍內,獲得硬幣的均勻性評估。性能

總而言之,蒙特卡洛方法,在實踐上給予咱們這種便利:咱們能夠用模擬和統計,代替數學公式的運算過程,獲得跟理論值相近的解。優化

蒙特卡洛方法和 2048 遊戲

咱們能夠把蒙特卡洛方法,應用在 2048 遊戲上。ui

對於 2048 遊戲的任意狀態,都有「上下左右」四個方向能夠選擇;雖然有時往某個方向走了之後,不會改變盤面的狀態,但也是遊戲支持的走法,並不會被判輸,因此也是一個可選項。3d

這「上下左右」,哪一個方向好,哪一個方向壞,它們各自的勝率是多少?咱們都不知道,但咱們知道,客觀上它們是有一種分佈存在的。把它們四個的勝率加起來,一定等於 100%。

能夠把這個「上下左右」想象成一個四面骰子,並且是不均勻的四面骰子;或者把它們想象成一個正方向被分紅四塊,並且是不均等的四塊。咱們有「2048 公式」能夠套用嗎?咱們能直接計算出每個方向的勝率面積佔比嗎?我不是數學家,我沒有找到,但我知道蒙特卡洛方法,能夠估測出近似解。因此來試試吧。

蒙特卡洛方法的極端情形,等價於暴力窮舉,把四個方向,以及四個方向以後的四個方向,以及四個方向以後的四個方向的四個方向,每個排列組合都走一遍,知道輸或者贏;而後統計一下走「上下左右」時每一個的勝利次數,跟總次數相除,就獲得勝率了。

暴力窮舉太粗暴?不要緊。模擬 400 次,可能準確率就達到 90% 呢,剩下的無限次,或許只是把 90% 的準確率提到到 100% 罷了。

蒙特卡洛方法代碼

按照蒙特卡洛方法的描述。

第一步,先寫一個類,有 run 方法,run 方法接受一個參數 iterations,表示模擬多少次,simulate 方法就是模擬。

模擬完畢以後,getBestAction 獲取分數最高的那個 action 動做。

mcm

simulate 方法怎麼寫呢?就是不斷地隨機選一個方向,走到死。board.getActions 方法要在勝利或者失敗時,返回空數組,表示玩家在遊戲裏沒有任何有效動做能夠作了。這樣 while 死循環就能夠獲得釋放。

board.doAction 應該是讓遊戲進入下一個狀態。若是遊戲步驟是無限的,那麼咱們須要控制一下一次模擬的時間長短,或者 doAction 的次數,對於 2048 等非無限步驟遊戲來講,這一步倒能夠省略。

mcm

模擬時,須要 board.clone 複製一個,避免影響到當前遊戲的狀態。若是咱們拿不到遊戲模擬器,蒙特卡洛方法就沒有那麼方便地派上用場。

path 數組變量,記錄了咱們此次模擬的 action 序列。

當咱們一次模擬走到死以後,就把當前第一個 action 和本次模擬的結果(勝負01或者得分 score),存到統計表裏累計。爲何是第一個action?由於咱們的目的就是找到當前遊戲的下一步動做,因此模擬的第一步動做,對應的就是咱們實際上要作的下一部動做。

mcm

最後一個方法 updateStatistic,就是咱們更新統計表了。它的實現也很簡單,就是判斷一下這個動做是否已經存在,存在就累計,不存在就建立。

不知道你是否注意到,咱們的代碼裏,並無 2048 限定的內容,而是在操做一個 board,以及 clone, getActions, doAction, getResult 等高度抽象的方法?

沒錯,咱們剛纔實現的蒙特卡洛方法,不是爲 2048 定製的,它可使用在不一樣的棋盤遊戲、視頻遊戲或者跟步驟序列相關的遊戲裏。只要寫一個適配器,把遊戲狀態和動做導出到 clone, getActions, doAction, getResult 等接口便可。

2048 遊戲適配蒙特卡洛方法

只須要很簡短的幾行代碼,就能夠提供讓 2048 board 實例的方法,適配咱們所實現的「蒙特卡洛方法類」。

mcm

在 getActions 裏,判斷 2048 board 當前是否勝利(hasWon)或者失敗(hasLost),若是是,就返回空數組,若是不是,就返回 [0, 1, 2, 3] 數組表示「上下左右」。

getResult 返回結果就是,先記錄模擬前的分數 board.score 爲 startScore,在模擬後,getResult 時,把當前的 board.score - startScore,就獲得本次模擬的掙到的實際分數。

doAction 方法裏簡單地調用 board.move 移動方向。爲何要抽象成 doAction,而非 doMove 呢?由於有些遊戲的動做,不侷限於移動啊,因此 move 太具體了,action 更抽象,能夠表示更多可能的動做。

mcm

寫完適配器以後,就能夠輸出一個方法 getBestAction,只要把當前 2048 board 輸入進來,就用蒙特卡洛方法模擬 400 次,而後返回統計上得分最高的那個 action,做爲下一個 action。

每走一步都跑一下蒙特卡洛方法,雖然重複走了不少次,但不要緊,只要性能跟得上,重複就重複吧,重複帶來更多的模擬次數,也意味着更準確擬合了理論上的面積分布。

若是 400 次模擬,準確度不夠,能夠增長到 800 次, 2000 次,總有一個數量級,能夠達到滿意的結果。

勝利的結果

下圖是在我機器上模擬後,成功抵達 2048 的截圖。你也能夠在本身機器上看一下這個過程。固然,最好你能夠動手實現一下蒙特卡洛方法的算法,加固印象。

mcm

點擊這裏,訪問 DEMO點擊這裏,訪問源代碼

請關注個人微信公衆號。有機會,咱們再介紹基於蒙特卡洛方法的「蒙特卡洛樹搜索(MCTS)」,它實際上是蒙特卡洛方法在編程上的結構優化,本質仍是蒙特卡洛方法。

weixin
相關文章
相關標籤/搜索