2014年2月27日 星期四

ToS2 神魔之塔擬人轉珠/濾珠、存珠、儲珠 示範影片


昨天晚上實作了濾珠(或稱存珠、儲珠)的功能,也就是刻意保留特定種類的符石。目前效果還不是讓我很滿意,主要是因為裡面還是有因為天降導致要被存的符石被消掉的情況出現。當然,某些很極端的情況下,被天降消掉應該是無法避免的,但演算法最好能夠盡量最小化被天降消掉的機會,或者讓存好珠需要的回合越少越好。只不過越是要避免天降消掉要存的珠,就代表要保留越多的符石來隔開要存的珠與新掉下來的珠,這也代表能消的符石越少。目前我心裡面已經有一點想法,所以應該會再試著加以改進。就先把第一版的影片擺出來,也許有人會給我一些意見(不過我應該還是會先實作我已經想到的作法)。有趣的是,加上這個功能,在原有的程式碼之中,扣掉變數定義、初使化或給值以外,真正與演算法相關的程式碼只多了 57 行。

ToS2 搜尋演算法概論 (1)

前情提要:暴力搜尋跑不完

先前已經提過,要用暴力搜尋,把所有可以走的路徑都走過一遍來找最佳解,基本上是無法實作的。因為複雜度太大,在現在的電腦上面執行,不太可能跑得完。當然有量子電腦或者是大量的雲端計算,也許有機會。但前者的問題是我根本不知道它是怎麼運算的,當然也不會寫讓量子電腦計算的程式。而對於後者,我沒那麼多錢,所以也就不考慮了。在不考慮永遠超大量計算資源的情況底下,那麼減少計算量就是勢在必行的工作。我曾經想過不只一個作法,在這邊我會先介紹被我捨棄,最後沒有被實作的方法。倒不是我特別喜歡賣關子,而是因為我認為沒有失敗,或者每一個一開始被認為不可行的作法,在高度抽象的概念階段,都還是很有價值的。很多事光用想像,不可能真的知道孰優孰劣,因此不論是好的想法或差的想法,都有被紀錄的價值。

當我思考到透過搜尋去嘗試所有可能的路徑計算複雜度太高之後,在我腦海當中出現的念頭是,其實我們並不需要找到「所有的路徑」去得到最佳解,我們只需要找到「一條路徑」去得到最佳解,就可以了。好的,那什麼是「最佳解」呢?雖然在不同的情境之下,同一個盤面能產生的最佳解可能不一樣,例如追求有拼圖盾和沒有拼圖盾的情況下,最佳解就很有可能不一樣。但是,一旦消除符石的目標確定了以後,什麼樣的解是「最佳解」,事實上是很容易得到的。當然,「最佳解」可能也會有很多個,不過同樣地,我們還是只需要「其中一個」。為了要幫助大家理解,我應該要畫一些圖,但是我實在太懶得畫圖,所以就利用別人做的網頁吧。簡單舉個例子,如果我們有一個盤面像這樣,盤面上總共有三個火珠、六個木珠、五個水珠、四個光珠、七個暗珠、五個心珠。經過簡單的計算以後,我們知道這個盤面最大的 Combo 數是 8 Combo 。我們把能作出來的消除,依消除符石的數量從多到少排序並把同色錯開可以得到 5 水、 5 心、 4 暗、 4 光、 3 暗 、3 木 、 3 火、 3 木。接著試著把這些消除的串列塞進盤面裡面,大的先塞比較好塞,經過幾次的嘗試以後就可以得到這樣的盤面。雖然要很直覺得找到一個可以擺放的盤面可能有點困難,但如果同樣作搜尋,要找到一個稱得上是最佳解的盤面,絕對比找到一個 8 Combo 且能夠把所有符石通通消光光的盤面要簡單得多。

那麼,在有了啟始盤面以及最終的最佳解之後,下一個任務就是要找到一條從啟始盤面「通往」最佳解的「路徑」。如果這條路徑沒有任何限制,那麼這條路徑其實也不會太難找。我們可以先將在啟始盤面與最終最佳解當中同色的符石編號對應,也就是紀錄哪一個符石最後要移動到哪裡去,然後開始照著我們想要的來移動。讀到這裡,你可能會想說「慢著,哪有你想怎麼動就怎麼動這麼簡單,移動特定一個符石的時候可是會影響到其它符石的」。接下來我要證明的就是,「從任意盤面必定存在一條路徑可以移動成另一個任意盤面」。有自己設計過一些演算法並證明有效性的人讀到這裡,應該已經知道大概證明的方向。首先,我先假定這條路徑一開始拿起來的符石最後會落在盤面的最右上角。接著我會從左而右,從下而上,把指定要的符石移動到那個位置上。舉例而言,從初始盤面,要變成最終盤面,我必須要把一個水珠移到最左下角。為了節省時間,就用離左下角最近的水珠,也就是 (3, 3) 這個水珠,而我應該要拿起來的是最後擺在右上角的木珠,一樣假定是離右上角最近的木珠,就是 (6, 5) 這顆。那麼我的路徑會是這樣,接著我再去移動心珠過來擺,一樣找最近的,我的路徑會是這樣。最下面一列,由左而右分別是心珠和暗珠。然後,把最下一列排好的路徑是這樣。看到這裡,一定會有人說,要是能這樣轉,那我也會。的確,其實只要有無限的時間,慢慢轉一定可以把盤面轉成我們要的樣子。但是問題就在於「沒有無限的時間」。


所以,相對於先前提到的搜尋法(我隨便取個名字),因為計算複雜度太高導致於會根本「算不完」。最終盤面建構路徑法(還是一樣,名字是隨便取的,請不要放在心上),要找到一個最終盤面是最佳解不難,要找到一條路徑也不難。但是很可能根本走不完。反應快一點的人應該已經發現機會是什麼。雖然人的手有手速的限制,很可能無法在五秒之內轉完這麼複雜的路徑(剛剛排最下面一排就已經花掉 52 步,如果要排完整個盤面可能要 200 步以上)。但是如果是給程式去排,要在五秒之內送出一大堆的滑鼠訊號,絕對是做得到。想到這裡,我就滿懷著希望打開 Bluestacks (or Genymotion) ,想要試試看是不是我拼命狂送訊號,模擬器也會把這些訊號如實地傳遞給在其中執行的 App 。不過一試馬上就知道,完全不能用。雖然我不太確定背後的原理是什麼,但是模擬器或其中的 App 處理滑鼠訊號有它的極限間隔,在兩次處理的間隔時間之內的滑鼠動作,有可能會直接被忽略。舉個簡單的例子,假使我送出向左再向下的動作,但在左邊那格停留的時間不夠長,以至於沒有被偵測到,就已經往下的話,那麼動作實際上就會變成是直接往左下斜轉,而不是先往左再往下。


在經過很多次的實驗以後,我測到的數據是以我的電腦執行 Bluestacks ,這個間隔時間大約在 70ms 到 80ms 左右,如果兩次動作隔的時間比這個時間還要短,就有機會有動作會沒有被偵測到。這個數據代表的意義很明顯,即使是程式在玩這個遊戲,一樣有嚴格的步數上限。即使這個步數的上限可能隨著使用的模擬器的順暢度或實作方式有所不同,但可走的步數並不是「無限」的。當然,這並不代表「最終盤面建構路徑法」就不能用,只是它可能需要搭配一個可以找到最佳解,但是該最佳解與啟始盤面的路徑又不會太長的演算法。一旦我們增加了這些條件在裡面,那麼這個演算法就不再像剛剛示範的那麼簡單,裡面勢必也要作一些搜尋,進而增加它的運算複雜度。當然,這個「最終盤面建構路徑法」的複雜度一定還是遠比暴力搜尋求解要低得多。如果可走的步數不是壓在 70 步這麼短的步數裡面(剛剛光是排一列就已經花掉超過 50 步),這個演算法應該是大有可為。但是因為對步數的要求,對這個演算法而言其實相當嚴苛,所以最後在幾經思考之後,我還是決定暫時放棄這個演算法,還是試著從搜尋路徑的方向來著手。


因為文章有越寫越長的趨勢,所以這一篇就先停在這裡,免得有人一點進來看到旁邊的 scroll bar 就不想讀了。

2014年2月26日 星期三

ToS2 程式原始碼以及概論

首先,這個 Bot 的 VS 2012 方案名稱是 ToS2 ,所以我就沿用這個名稱, Git 的 repository 名稱也叫 ToS2 。或許有人會納悶,為什麼要叫 ToS"2" 呢?原因很簡單,因為這是我寫的第二個 ToS 的 Bot 。早先寫了第一版以後,也陸陸續續在上面加上許多最佳化的功能,但是效能上的瓶頸一直無法突破。最後在經過一段時間的思考以後,我認為要進一步加速程式的計算,必須要整個搜尋的架構改成現在這個架構。而從原本的程式改寫,要改動的地方太多,不如整個重寫。因此才又開了一個新的方案,把整個程式重寫一遍。因為我在命名上實在沒什麼創意,所以第一版叫 ToS ,第二版就叫 ToS2 。我也沒有另外去想一個很好聽的名字,所以以後這個程式就叫 ToS2 吧。如果有一天又面臨大改,應該就會叫 ToS3 。基本上應該不用擔心最後數字變得很大,因為我沒有那麼多時間。

最好的情況下,我應該要把 source code 好好整理以後再放出去,但有些朋友已經在跟我要 source code ,所以我想就先把現在的樣子放出去也無妨, GitHub 的 URL 是: https://github.com/sitos/ToS2 。就跟我以往寫的大部份程式一樣,我的習慣很差,以致於所有的 function 都擠在同一個檔案裡面。基於未來我如果去上班可能要跟別人合作,接下來我「應該」會將程式碼慢慢整理,把不同功能區塊的程式碼分到不同的檔案裡面,以方便閱讀、修改與討論。但如果對實作細節很感興趣的人,也可以現在就將程式碼下載回去看,只是很抱歉裡面現在幾乎沒有任何註解。有註解的地方都是提醒我那邊的寫法可能有問題的地方,對於了解程式碼的運作應該是一點幫助也沒有。


在這篇文章的最後,我略略講一下一個 Game Bot 大致上的流程和架構,給完全沒有相關經驗或背景的人了解。基本上 Game Bot 做的事就和一個在玩遊戲的人一樣,分為三大部份。第一、了解遊戲的狀態,第二、找尋最佳的策略,第三、反應。套用在神魔之塔這款遊戲來說的話,就是讀盤面、想路徑和動滑鼠。基本上讀盤面和動滑鼠,是相對「簡單」而「直覺」的部份。我所謂的簡單指的並不是人人都會,至少在我寫第一個 Game Bot 以前,我認為那是對我而言最麻煩的部份,因為它牽涉到很多系統的 API ,沒學過或沒查過,我就不會用。所謂的簡單指的是,一旦你會用了以後,幾乎所有的遊戲都可以用一樣的方式來獲取遊戲的資訊,用一樣的方式來操作遊戲。當然,這不代表只會一種方法就是夠用的,因為遊戲可能有反應時間的限制,可能有各種動畫干擾對遊戲狀態的判斷。因為太複雜的我也不會,這部份還有待學習充實。


至於中間的想路徑這段,是我認為最有挑戰性也最有趣的一部份。我做的第一個完整的 Game Bot 是給 Facebook 上的 Bejeweled Blitz 用的 Bot ,它的策略就相對簡單很多,如果有機會我重寫那個 Bot ,應該也可以和大家分享,在這邊先跳過。但神魔之塔或龍族拼圖的策略就相對難很多。有學過演算法的人應該就知道,這種問題基本上只要作 Search ,不管是 DFS 或 BFS 都可以,就一定可以找到最佳解。問題是, Search space 實在太大了。一個粗略的算法是,假設我們可以允許的步數是 50 步,每一步有 3 個方向可以選擇(不考慮上一步的反方向,也不考慮碰到邊界的情況),這樣總共有 3^50 步可以走。大致學過 Computer science 的人應該都知道,這通常算不太出來。所以暴力求最佳解基本上是不實用的,那麼就只能退而求其次,縮小搜尋的範圍,試著在搜尋的範圍裡面找到堪用的解。為了讓文章不要太長,我採用的搜尋方式留到下一篇文章再講。


最後,附上三四年前我做的 Bejeweled Blitz 的 Bot 的影片,其實當時最高的分數曾經有 3.8M ,我本來想要做到 4M 再收手,但當時卡在取點取色的效率不佳,最後未竟全功。等我學會新技巧的時候,沒空回頭去做。所以如果有機會的話,也許我會再把它改良一下。





ToS2 神魔之塔擬人轉珠/獅子宮/拼圖盾 示範影片



我知道已經有好幾位玩家做了自動轉珠,但我個人對於這裡面的搜尋演算法以及實作還是很有興趣。所以我也自己做了一套。一開始實在落後很多,不過在付出一段時間以後,我認為做出來的效果已經不錯。不管是搜尋速度,搜尋到的解,以及能夠處理的特殊條件,都做得不錯。特別是我認為我的搜尋核心設計得不錯,很容易可以加上一些新功能。

我在看網路上的其它影片時,發現大部份的功能大家做的都差不多。而我的程式相比之下,亮點可能就是我已經把解拼圖盾也一併實作完了。所以這裡的第一個影片,就先擺上解拼圖盾的示範影片。因為我很希望可以得到一些回饋,特別是技術上的回饋,所以我還在考慮要怎麼分享這個專案。目前還沒有很確定的想法。所以就只先放上影片。