2014年3月12日 星期三

神魔之塔 世界的啃蝕者/飢餓的毒龍 龍隊零石通關


這是今天(3/12)錄的影片,為了錄一個完整的零石通關影片,不僅把手上所有的魔法石都用光了,總共還打到三隻毒龍,第一次是弄錯敵人技能吃了一石通關。第二次雖然零石通關,但是畫面有不小心被擋到。然後就一直失敗,一直吃石補體,最後沒石了只好等體力慢慢回復。然後等了幾個小時以後有體力再打,又失敗。再等幾個小時以後又有體力,又再打,才好不容易錄到一個完整的。不過就算雙異界當隊長,回復力還是很不夠,只能很有耐心地慢慢把王的血磨光。過程其實還滿無聊的,如果睡不著可以看看。

2014年3月10日 星期一

ToS2 搜尋演算法概論 (3)

前情提要:區域最佳解(Local Optimal Soluion)很麻煩。

前一篇文章最後面提到,分段作 DFS 很容易就會掉到 Local optimal solution ,這個情況又會因為搜尋的步數不足,加上缺乏對盤面潛力的精確評估方式而更加嚴重。事實上上述兩者都是可以再改進的地方。當然寫在前面也就表示我還沒有什麼好的想法。想要增加搜尋的步數,最直接的作法可能是將一定不會解出好盤面的路徑捨棄不搜尋,這樣就可以在同樣的時間裡面搜尋更多的步數。不過這樣的作法面臨的挑戰還是一樣,目前我並沒有辦法對於「未完成」的盤面,對其潛力作很好的評估。因為如果想要在還沒有走完以前,就先看出某一條路徑不會解出好盤面,那麼就得利用當下這個未完成的盤面,去作一個粗略的估計。但目前我並沒有好的評估方法。簡單來說,現在的搜尋方法被限制在,只能夠對於「已完成」的盤面進行評估。這當然是一個很大的限制,如果有人有對於怎麼突破這個限制的一些想法,非常歡迎提出來討論。

基於上述的限制,因為我只能對於已完成的盤面進行評估,所以只能很努力地把搜尋範圍裡面所有的路徑都走完,看看每一條路徑最後的盤面如何。這樣當然就很容易卡在 Local optimal solution 裡面。我的作法來自兩種不同的觀念,但最後的實作其實是一樣的。這個想法可以被視為是一種 Look ahead 或者是 Roll back 。你認為哪一種比較好理解,就從那種去理解吧。 Look ahead 的想法有點像是想要給未完成盤面一個評分的方式。雖然我要搜尋的範圍是 N 步,但走到 N 步得到當時的盤面 A 以後,我再多搜尋 M 步的範圍,看看現在決定的這條路徑,之後如果繼續走,是不是可能可以得到更好的盤面 B ,來當作是這條路徑的評分,也就是評估現在這條路徑的潛力。這樣的好處是,在很多的 N 步當中,我可以得到 N+M 步當中看起來最好的解。當然馬上就會有人講說,你這樣不就是把搜尋範圍從 N 步變成 N+M 步嗎?是的,不過不一樣的地方在於,我下一次搜尋會從 N 步開始搜,而非 N+M 步開始搜。這樣就不會把當時搜尋的 Local optimal solution ,也就是 N+M 步的盤面 B ,當作起點,導致任意移動符石可能打亂已經排好的符石。而是從 N 步的盤面 A 開始找解,找看看再下一個 N+M 步有沒有更好的解,而我知道至少有一個 N+M 步的盤面 B 不錯。只是再往下搜 N 步,再加上 M 步的 Look ahead ,有可能搜到 N+(N+M) 步會有比 B 更好的解。另外一個理解的方式是 Roll back ,也就是我一開始雖然搜尋 N+M 步,得到當時最好的盤面 B ,但是我不要從 N+M 步開始下一次的搜尋。理由也是一樣,這樣可能導致打亂已經排好的符石,反而得不到更好的解。因此我 Roll back 回到第 N 步時的盤面,再往下搜尋 N+(N+M) 步, Roll back 就可以避免卡在當時的 Local optimal solution 盤面 B 。因此,只要現在還沒有到達步數的上限,那麼下一階段的搜尋,就會 Roll back 回到這一段的第 N 步時的盤面 A 繼續往下搜尋,而非在該階段看到最佳盤面的 N+M 步時的盤面 B 開始繼續搜尋。最後,假使總夠搜尋 I 個階段,那麼總步數將會是 N * I + M 步。

另一個處理 Local optimal solution 問題的方法,概念上就相對簡單。這個想法是來自 GA 裡面基因庫的概念,雖然在很多個解當中,總是會有一個最好的,但如果我們只取「一個解」,那個解後續的發展不見得好。但如果我們同時保留「多個解」,這些目前看起來不錯的解,後續的發展通通不好的機會,就相對變低了。這樣就可以讓我們整套演算法跑完以後,不致於在某些情況下得到很差的解。因此,同樣是在分段 DFS 的架構底下,第一個階段跑完以後,我會保留 K 組當時看起來最佳的解(S1, N 步),這 K 組解各自對應到當時它們的盤面。然後從這 K 組解出發,再各自往下搜尋 N+M 步,又得到一大堆解。再從這一大堆解裡面找出 K 組最佳的解(S2, N+N 步) ,如此不斷重覆直到步數的上限。這裡的 K 越大,搜尋量也會呈現性增加,不過至少是線性的,不是指數增加。所以還算可以接受, K 大概取 8~16 效果就不錯了,會取 8 或 16 是因為我的處理器有八個 hardware thread ,剛好可以透過 openmp 平均分散工作量,平行化的部份會留到討論實作的地方再來講。

當然,這樣的作法實際上並沒有「解決」分段的 DFS 遇到 Local optimal solution 的問題,但至少表現出來的症狀已經大幅緩解,在多數的時候都可以把這些路徑不錯地串連起來,得到一個不錯的解,而且也把計算的時間壓在可以接受的範圍裡面。事實上,如果搭配一個良好的盤面評分系統,這個作法離解決 Local optimal solution 造成的問題,也是雖不中亦不遠矣。分段作 DFS 最大的困難在於,一段 DFS 結束以後,必須要選擇其中一條路徑作為下一段搜尋的起點,此時評分機制的角色就非常重要。先前有提過,我現在只能對已完成盤面作評估,但即使如此,還是可以對未完成的部份依照我們的喜好給一個粗略的分數。例如在兩個 combo 數相當的盤面,我們怎麼決定到底應該選擇哪一個當作起點,又或者我們問一個更具體的問題,如果在搜尋的步數裡面,無法完成拼圖盾,但我們的搜尋目標就是要滿足拼圖盾,在分段 DFS 的搜尋架構無法直接完成的工作,要怎麼樣透過評分機制來補足分段 DFS 的不足。寫到這裡,搜尋演算法的架構大致上是講完了,下一篇會開始討論分段 DFS 的好伙伴,也就是盤面評分的演算法。讓分段 DFS 沒辦法做的事情,由盤面評分演算法來補足。

2014年3月8日 星期六

ToS2 神魔之塔擬人轉珠/記憶的教誨/突破界限之門/追憶旅人 零石通關/示範影片


昨天晚上看到別的玩家在玩這關,然後看到「符石風化‧改」,就知道這東西實在麻煩。昨天晚上躺在床上先想了一會,今天再花了半天的時間,耗掉大約三條體力作測試和除錯,總算是把功能先給改出來了。上面就是通關的影片。雖然還有一點點瑕疵,不過因為是一次通關的關卡,所以改了也沒辦法再測,剩下的只能等到下次再有「符石風化‧改」的關卡時才能測試了。不過因為「符石風化‧改」的應對策略和原本程式架構不太合,所以硬是把功能塞進去以後,程式碼變得很亂,可能得等有時間再來重新整理一下,現在的寫法連我自己都覺得很難理解。

2014年3月4日 星期二

ToS2 搜尋演算法概論 (2)

前情提要:先產生最佳盤面再找路徑看起來也不是很方便

既然直接產生最佳盤面,再去找一條從初始盤面一直到最佳盤面的路徑,看起來也不是很容易做到,而且甚至也不容易設計演算法,想法自然就又回到直接作搜尋。但是先前也已經提到,直接以暴力法作搜尋, Search space 實在太大,根本不可能搜得完。所以勢必要縮短搜尋的步數。先用最直覺最簡單的作法來想,神魔之塔的盤面在考慮斜轉的情況底下,每一步都可以有九種選擇,分別是上、右上、右、右下、下、左下、左、左上以及停在當時的位置。在搜尋的過程當中,除了停下不走以外,在不考慮碰到邊界的情況下,其餘的選擇都可以再接下去作原本的這九種選擇,因此若將搜尋的總步數設為 N ,那麼搜尋的時間複雜度小於 9^N ,約比 8^N 略大一點。簡單來說,這個複雜度是指數成長的。因此我們只能選擇一個很小的 N ,目前我的程式選用的 N = 8 。這個數字取決於使用者能忍受計算一個盤面花多少的時間, N 每增加 1 ,計算時間就會變為八倍,因此就算有很好的電腦協助運算,這個 N 也沒辦法再增加多少。但在上一篇文章當中,我也提到,以目前 Bluestacks 的使用狀況來看,在五秒的轉珠時間裡面,大約可以允許最多 70 步。因此,將搜尋範圍從 8^70 ,縮小到只剩下 8^8 ,這樣找到一個好的解的機會自然大大降低。如果不去比較這個數字,光從概念去想,如果只允許移動 8 次,就算把所有的可能都考慮進來,要把整個盤面整理好還是很困難。直接縮小搜尋的範圍有一個非常明顯的缺點,那就是總步數不足,根本就不可能把盤面整理好。


其實這邊的想法和人在玩的時候是很像的,人在玩的時候事實上也是在腦中作搜尋,只是可能搭配更多經驗等綜合判斷,但決策的過程還是很相像。我不知道高手的思考方式是不是不一樣,還是只是記住一些特徵來加快搜尋的速度,但至少我自己在理解這個遊戲的時候,並沒有想到什麼特別的技巧。所以,為了要解決總步數不足的問題,最簡單的作法就是多做幾次搜尋,再把一段一段的路徑接起來。簡單的想法就是,先搜尋 8 步,找到長度為 8 的路徑當中最好的一條,並將當時的盤面記下。接著再從這個盤面出發,再搜尋 8 步,再找到長度為 8 的路徑當中最好的一條。只要把這兩條路徑接起來,就可以得到一條長度為 16 的路徑。這個作法大概是我白天被問到可不可以寫神魔之塔的 Bot ,然後搭車回家的路上就想得到的第一個解法。想法很簡單,也很實用,如果有興趣的人也可以自己直接實作這個作法,會得到「還不錯」的結果,但是它的缺點也會非常明顯,那就是它會很直接地被卡在 Local optimal solution 裡面。找出來的解其實不盡理想。


接下來我會舉兩個簡單的例子來表達這個概念,在這些例子裡面,我們先假設所謂最好的路徑就是可以形成最多 combo 的路徑。假設我們一開始有的盤面是這樣




盤面的左半邊看起來是比較容易形成 combo 的區域,所以在 8 步以內找到的最佳解可能是這樣




但是再往下搜尋 8 步,可能就找不到更多的 combo ,因為會破壞原本已經形成的 combo 。但事實上在 16 步之內,可以形成更多的 combo ,例如這樣



只要先把右上角整理好,再去走原本的路徑,就可以多得到 1 combo 。但先往右上角整理的走法,在 8 步以內可能沒有足夠的步數走到左半邊形成 4 combo ,因此這條路徑在搜尋第一個 8 步的時候,就會被捨棄。簡單地說,這種作法會捨棄很多一開始看起來不好,但實際上很好的走法。再舉另外一個例子,假設在第一個 8 步之後,可以把盤面整理成這樣



我們用眼睛看會覺得超棒,因為只要再動七步,就可以形成漂亮的 6 combo 。


但是這個盤面本身卻連 1 combo 都沒有,因此它也會被捨棄。在某些搜尋的問題裡面,困在 Local optimal solution 裡面,可能不是太嚴重,因為只要大部份的地方都得到不錯的解,那麼最後的解也不會比最佳解差太多。但在神魔之塔這樣的轉珠遊戲裡面,這個問題相對較為嚴重,因為每一次的移動都有副作用,導致在得到一個 Local optimal solution 以後,對於那些會拆掉現有的 combo 以追求更高 combo 的路徑,在下一次搜尋範圍之內,若是無法將原有的 combo 組合回來,那麼得到的解將比原本的更差,這些很有潛力的路徑就被捨棄了。

對於上述的狀況,有種極端的例子。假設在疊珠疊很多層的情況底下,走 9 步就可以完成整個疊珠,可以得到很高的 combo 數。但是在 8 步的時候,就是沒辦法將最下層啟動的符石擺好,導致這個盤面最後連 1 combo 都沒有。在這樣的情況底下,即使只差 1 步,還是無法求得這個很棒的解。那麼這樣的問題要怎麼解決呢?或許有些人會覺得,那我就多搜尋 1 步就可以了,這個想法很直覺,但沒有用。因為不管搜尋幾步,總是有那種差一兩步就得到好解的情況會發生,但每多搜尋 1 步,複雜度就變成原本的 8 倍,因此實際上搜尋的範圍就只能被限縮在短短的幾步之內。要跳出 Local optimal solution 也有一些現成的搜尋演算法可以用,例如 Simulated Annealing (SA ,模擬退火法)或者是 Genetic Algorithm (GA ,基因演算法),但這兩種很厲害的技巧(我認為發明的人真的很厲害,不過用這兩種演算法解問題倒不用什麼高深的學問),在轉珠問題裡面似乎也派不上用場。 SA 和 GA 的作法都是隨機變動一部份的參數來看看整體解是否能夠得到改進, GA 則是混合目前不錯的解的不同部份來得到一組新的解。如果今天要解的問題有某種「區域性」,也就是改變一部份的參數,影響只會在特定的範圍之內,那麼這種嘗試方式就很有用,我們可以透過累積部份的改進,來達到整體的改進。但轉珠問題只要走的路徑有 1 步不一樣,最後的結果可能就大不相同,特別是在疊珠當中,只要在前期中斷,後面所有的 combo 都沒辦法產生。因此透過 SA 或 GA 這種帶有亂數變動的演算法來試著跳出轉珠問題當中的 Local optimal solution ,似乎也不是很恰當。當然如果有對這兩個演算法有深入了解的人,有想到套用的方式,也很歡迎分享。不過在我目前的理解之下,我認為這種先進的演算法似乎不適合這個問題,所以最後還是回到比較傳統的作法。因為篇幅的原因,怎麼處理困在 Local optimal solution 的作法就下篇再講了。