2017年1月28日 星期六

學習編譯系統的心得

金字塔門檻

fig 1
fig 2
fig 3
fig 4
fig 5
fig 6
fig 7
fig 8
fig 9

fig 10
20170128 是 2017 的農曆新年 (大年初一), 就來點溫馨的學習文章好了。

在看了知乎《如何学习编译原理?》這個問題後, 我寫下這篇 (由於實名認證, 現在我已經無法在知乎 po 文), 這是個好問題, 光是發現怎麼學習編譯原理就花了不少時間, 也買了不少書, 但每本書的實作都不同, 讓學習更難了。希望這篇文章能幫助和我一樣想學習編譯器的朋友, 也紀錄著我自己的學習之路。

os 和 compiler 大概是資工本科功夫裡頭基本中的基本, 但基本可不代表他們很簡單, 一定有很多人想要掌握這兩塊, 也一定很多人失敗了, 我也曾經失敗幾次後再度挑戰, os 我已經略有小成, 編譯技術則剛開始。這篇紀錄我如何找到學習編譯原理的方法, 希望對有興趣的人有所幫助。

有人說編譯器很簡單, 有人說很難, 我是認為很難的那一派別, 也許每個人程度都不同, 所以認定方式也不同, 我之前完成了一個小型 os kernel, 但對我學習編譯器不太有幫助, 在開始的時候, 我還是覺得很難, 而就算是我完成了一個玩具型的 c 編譯器的現在, 我也還是覺得很難。

Re: [問卦] 有沒資工系畢業門坎 沒"自己的編譯器"這要求?

這篇回文也和編譯器相關, 很豐富。

真的有決心要學習編譯原理的話, 請從金字塔門檻圖一步步從底層爬上來, 所有的東西都手工打造, 絕對不要使用 flex/bison 之類的工具, 但如果是工作上的專案, 我是贊成使用這些工具的, 畢竟能減輕工作份量, 沒理由不使用的。

我似乎忽略了 debugger, debugger 嚴格來說不應歸類在編譯系統, 但實際上沒有 debugger 是很難除錯的, 要「全端」 (full stack) 地學習編譯系統, 應該要補上 debugger 的, 而 debugger 的難度, 我覺得應該是最難的。

在我最後更新本篇文章時 (20170609), 我來到了 assembler 階段, 真是令人開心, 只剩下 linker 了呢, 如果再完成 linker, 就可以完整體驗編譯原理了。當然, 在學習過程我簡化不少東西, 以下便是我的學習心得。

若以不那麼嚴謹的方式來說, 其實我已經會寫 compiler, assembler, linker, loader, 剩下 debugger。

買書一向是我的主要學習方式, fig 1 是一開始我擁有的書, 翻閱之後卻發現有一種無所適從的感覺, 每本書好像都寫的不錯 (我會過濾爛書, 但其實在繁體中文的世界沒什麼可以挑), 但又好像有什麼不足之處, 總之我沒學成。

fig 2 ~ fig 10 是我在進入簡體中文世界後所購得的書籍, 有了新武器後, 201604 我再次挑戰編譯器這個題目, 一樣感到困難, 沒有因為這些新武器而得到救贖。和學習寫 os kernel 完全不同, 我幾乎只靠 2 本書, 就可以寫出 os kernel 了, 但買了這麼多編譯器的書, 卻還是覺得困難, 找不到適合自己的書。

再繼續堅持下去後, 從中摸索如何學習編譯器就花了不少時間, 打定學習方式後, 慢慢有了進步。

學習重點一樣是簡化再簡化, 但要怎麼簡化就是學問了。

買的書中有些是實作 pascal (fig 1 右下那兩本), 有些是用 java 來實作編譯器, 但我想要用 c++ 來實作 c 語言編譯器, 其實是想寫 c++ 編譯器, 但 c++ 太複雜, 惦惦自己的斤兩, 退一步實作 c 編譯器好了。而選擇 java 實作的書是無奈之舉 (不是我沒注意到, 就是需要其內容), 我需要該書裡頭的知識, 所以只能認了, 事實上, 書單中只有一本是用 c++ 實作編譯器。

而買的書一定要有完整的範例程式, 絕對不要買理論型的書籍 (對, 就是在說那本), 對於初學者來說, 絕對不可能靠理解理論來完成編譯器, 編譯器是很難的程式, 在真實世界上, 也僅有少數人有能力寫出來。沒有一個範例在那邊讓我們參考,不是一個有效率的學習方式。

而事實上, 我發現書中對於講解程式碼的部份都很薄弱, 幾乎都要自己去追蹤這些程式碼, 才能看懂在寫什麼, 不怪作者們, 因為要怎麼講清楚這些程式碼還真的蠻難的。這應該是這類型的程式一般人很少寫過, 所以很陌生, 幸運的是, 努力追蹤這些程式碼一段時間後, 就會習慣了。

再來是要實作那個語言? 當然是實作你有在用或是喜歡的語言, 如果你喜歡 python, 那實作 python 絕對比實作你不會的 pascal 還能引起你的興趣, 我根本沒在用 pascal, 怎麼可能有興趣去實作 pascal。動力是很重要的, 因為跌跤的時間會佔了很大一部份, 沒有繼續前進的動力, 很容易就放棄了, 而興趣就是你源源不絕的動力來源。

再來是 c 很複雜, 有辦法全部實作她嗎? 可能可以, 但在 lexer 就得花上不少時間, parser 可能要更久, 像以下的宣告:

void (*f[10]) (int, int);
char (*(*x())[]) ();
char (*(*x[3])())[5];

要付出不少時間來實作, 而一開始的我, 也沒有能力可以處理這種複雜的 ebnf。

我的重點在實作整個編譯流程:
  1. lexer
  2. parser
  3. AST
  4. C preprocessor
  5. interpreter
  6. 輸出組合語言
  7. 實作 assembler
  8. 實作 linker
  9. 實作 virtual machine
  10. 實作 jit
  11. 實作 GC
這些我都想跑過一遍, 簡化這些是很重要的, 要怎麼縮短時間, 又能完整的走過一遍。編譯系統並不是只有編譯器一項, 還有很多很多的東西一起組合起來的, 大部份的書都只著重在編譯器上, 其實是遠遠不夠的, 這樣對於編譯系統的技術就會少了一些拼圖, 神功未成, 有了死角, 豈不可惜。

我喜歡研究微小的整體, 而不是某個部份的深入, 「麻雀雖小, 五臟俱全」, 可以用來形容我的學習風格。

所以我把 c 簡化成只實作:
  • if/else, while, function call
  • type 只支援 char, int
當然還有指標, c 語言沒實作指標還能算是 c 嗎? 簡化時有些可以省略, 有些不行, 指標就是不能省略的部份, 別小看這些簡化後的目標, 乍看之下很簡單, 一寫下去知道, 難度還是很高的。

所以最後實作的 simple c interpreter 是有支援指標的。疑, 不是要寫編譯器嗎? 怎麼變成直譯器了, 哎呀! 功力不夠, 只好先改變目標。Joel 的邊走邊開槍你沒讀過嗎? 不過就算是 interpreter 也是存在某種難度的。

再來是 lex, parser 都一定要自己實作, 絕對不用 flex, bison, 要練習怎麼可以偷懶呢? 這些都要自己做。但如果是工作上的專案, 我建議使用 lex, bison, 這會大幅減低程式的負擔, 也會提高整體的正確性, 更擁有方便的彈性。

再來我選擇要產生 AST, 不產生 AST 的作法也有, 但我要實作 AST, 這部份我在《两周自制脚本语言》獲得此技能, 雖然這本書是使用 java, 而我是用 c++, 但這本書的 java code 依然給了我很好的參考, 讓我得以突破 lexer, parser, AST 這三道關卡。

實作出 fig 21, fig 22 的 AST 時, 那種興奮感一定很多人都可以體會。我建議最好可以把 AST 具象化, 若不能很清楚的看到自己建立的 AST, 一定會覺得很模糊, 有一種我真的把 AST 建對了的疑惑。這邊可能會是第一個卡關的地方。tree 可是一個很難搞的資料結構, 要怎麼確定建立的 AST 是對的呢? 還有比把它印出來更可靠的方法了嗎?

再來的 interpreter 我就沒靠相關的 compiler 書籍了, 因為我在 SICP 4.1 中獲得了這個技能。

在 SICP 我實作了 4.1 的 scheme (一樣用 c++ 實作), 所以接下來的 eval 我靠自己的能力就可以完成。這可不是件容易的事情, 在這裡是可能會卡住的第二關。

實作 AST 可以怎麼開始呢? 因為總共有:

list 1
  1. 四則運算
  2. 使用變數
  3. if/else
  4. while
  5. function declare/definition
  6. function call
要處理。

為什麼要搞定這些, 因為有了這些結構, 實作出來的簡化 c 語言就不再是玩具, 而是真的可以用來寫程式, 你總不希望辛苦搞定的語言, 什麼都不能做吧, 為此目的我還實作出 printf, 讓這個 interpreter 更有實際功用, 當可以用 recursive 算出 5! 時, 超興奮 der。

parser 要怎麼進行才能減低學習負擔呢? 先從《四則運算》開始, 能把 1+2*(9-2) 的 AST 產生出來, 就已經邁進了一大步, 很有成就感。而且在這樣的拆解後, 難度下降, 也可以用下班後的少數時間來實作, 不需要一個很大塊很完整的時間來學習。而當完成《四則運算》後, 你就會尊敬每個平台上都有的計算機程式, 這可不是容易寫的程式哦! 我不知道寫了多少張的 A4 紙, 就只是為了描述這個《四則運算》的 AST, 到寫文章的這個時候, 我已經對 AST 有所感覺了。

再來就照 list 1 的順序慢慢實作, 我就是這樣把所有功能完成的。

我買的書籍中, 有些有很嚇人的理論和資料結構, 理論很難懂, 資料結構很難實作, 很有可能實作這些資料結構就得花不少時間, 但我沒有用到這些還是把玩具等級的 interpreter 做出來了。

記得你是來學習寫編譯程式, 不是來練習資料結構的。雖然我用的是 c++, 但也沒用到特別的東西, 只用到 if/else, while, function call, 一點 class, vector, stirng, map, 也沒用到多型, 但這樣的武器就已經足夠完成一個玩具版的練習了。在提醒一次, 記得你是來學習寫編譯程式, 不是來把玩 c++ 的。

你還要把時間花在精巧的 c++ 語言特性嗎? 不, 我喜歡把時間用在實作某個東西上, c++ 語言特性有他的好處, 但不一定是必須的, 你能寫出很酷的或是讓人迷惑的 c++ 程式碼, 但我可以寫編譯器, 而裡頭只用到 if/else, function call, while loop, 沒有什麼特別的 c++ 語法, 那個厲害呢? 吾 ... 都很厲害, 看怎麼選擇而己, 當然如果可以兩個都掌握那是最棒的。

雖然寫程式很有趣, 但世界上還有很多有趣的事情, 我希望能把時間也分點給這些有趣的東西, 要能同時掌握這兩項可能要很久之後了。

fig 21
fig 22

我目前走到了《產生 asm》這塊 (在我更新本文時, 我已經在寫組譯器, 可以讓 gnu ld link 出 elf 執行檔, 不過, 我在這裡停止開發了), 還有很多部份需要努力, 而且愈來愈難。我用一樣的方式來學習《產生 asm》, 一開始還是處理《四則運算》, 不過其難度比我想像中的要難很多, code generator 比 interpreter 難了 5 倍以上, 我刻意不看書上的實作, 完全自己想, 還真的有點吃不消, 方法可能不是最好, 但這可都是我自己想出來的哦! 這樣的成就感, 實在是令人滿足, 作夢都會笑。實在想不出方法, 再來查閱書籍。

在學生時代把這些完成是比較好的, 但我在想, 如果是學生時代的我, 有能力完成定下的這些目標嗎? 大概不行, 那時候可能沒這麼多的學習資訊, 我的程式功力也很差 (這當然不是說我現在就比較好), 直接挑戰編譯器可能太難了, 對於組合語言也不熟悉, 那怎麼可能寫得出 code generator 呢? 可能連 lexer, parser 的狀態機都能難倒我, 程式真的就像砍柴一樣, 一直砍, 一直砍, 就能找到訣竅。但如果在學生時代努力過, 就算沒成功, 也不是壞事。

把這些都簡化實作過一次, 就相當於走過一輪了, 有興趣的部份再去深入。

有些書是用 LLVM 來打造自己的編譯器, 我一樣建議真正要學習的朋友不要使用 LLVM, 這樣會漏掉一些拼圖, 但如果是工作上的專案, 我是很贊成使用的, 正確性高, 有最佳化的功能, 開發速度快。

不過學習最忌諱速度快, 真正專業的東西是沒有捷徑的, 只能像螞蟻一樣, 一次爬一小步, 慢慢的累積這些專業, 一旦掌握了好幾個部份, 再把他們組合起來, 擁有這些深厚的基礎, 就是自己的優勢所在。

現在有些速食課程, 號稱在短短時間就可以怎麼樣, 也許你真的在短時間完成了什麼, 但如果要繼續下去, 該花的時間還是要補回來的, 我不相信有什麼快速學習方法。『真正有用的知識, 都不是那麼容易獲得的。』

ref:
寫自己的程式語言 (For Rubyist) 一文, 作者是文組學生, 卻開發出自己的程式語言, 資工本科系還不見得可以作到這件事情, 真是不簡單。參考其《自學歷程 (1)》

2017年1月21日 星期六

[ios/android games] 超級英雄:武力對決 injustice: Gods Among Us, 2.16 無限金錢


20170728 補充 2.16 版, 無限金錢密技
20170724 2.16 版出來了, 有新的關卡 (第八大關) 和人物, 新的關卡是「排名 X」的敵人, 非常強, 不容易打。

我靠著吸血烏鴉 (排名I) 才破第八大關, 排名 I 雖然不算強, 但可以運用烏鴉的吸血能力, 大幅降低敵人生命, 再用強力人物來打敗他們即可輕鬆過關。

新的關卡和人物不是重點, 你想要有很多能量積分買人物吧? 也就是作弊拿到用不完的錢買人物, 以下影片透過 2.16 版的 bug, 讓玩家買人物時, 能量積分會回到沒買的數值。簡單說, 假設我有 1000000 能量積分, 我花了 600000 能量積分買了 Most Wanted Pack (萬眾矚目遊戲包), 剩下 400000, 按照以下步驟操作後, 會回復 1000000。



不過按照玩家回覆的結果看來, 有人可以, 有的人卻失敗了, 賣掉人物以此方法獲得的能量積分後, 立即回到主畫面, 砍掉目前的執行程式, 再重新執行一次, 這樣賣掉人物的能量積分才不會回到原本沒賣的數值。

以下便是這金錢無限的步驟, 但前提你得先到存到一筆不少的能量積分, 如果想買萬眾矚目遊戲包, 至少先要存 600000 才能購買, 否則你只能買你能購買得起的人物或是強化包, 對新手還是有點小門檻:

1-6 的關卡, 打 1-6-1, 成功擊敗敵人後, 到商店:
關掉 wifi
買東西
開啟 wifi
打 1-6-2, 成功擊敗敵人後, 花掉的能量積分會回復。

很刺激, 每次都很擔心錢 (能量積分) 會不會回復。

由於這算是作弊, 很可能會被系統封鎖, 無法打線上對戰, 請自己決定要不要使用。

20070511 injustice 2 出了, Pre-Register for Injustice 2 Mobile to Unlock Catwoman, 我也拿到 catwoman, 但我還是喜歡玩 injustice。

這個遊戲有很多版本, 我玩的是 ios/ipad/android 版本, 後來在 steam 特價攻勢下又買了 pc 的版本。

這是一個格鬥遊戲, 在平板上的操作很簡單, 就一直點螢幕就好了, 也不能控制人物前進後退, 也許你可能會覺得單調, 但其實還蠻有遊戲性的。兩指一同按下是防禦, 一直點螢幕是攻擊, 橫向滑動是另外一種攻擊。

pc 版的操作就相當複雜, 教學模式就把我搞的很亂, 太多組合技能, 記不住這些組合按鍵。以下介紹 mobile 版本。

以下介紹以 2.16 版本為主。




遊戲共有幾種模式:
  1. 一般遊戲關卡, 總共 8 大關
  2. 幸存者 - 可以得到特殊寶物
  3. 網路對戰 - 和網路其他人對打, 可得特殊寶物或是人物, 和幸存者拿到的寶物有所不同, 通常要打進前 5% 才有機會得到。
  4. 挑戰模式 - 得到某個角色。
  5. 突破戰 - 將角色等級往上再升級。
再進一步繼續前先介紹幾個名詞:
  • 能量積分 - 可以想成遊戲中的貨幣, 可以買東西。
  • 排名 - 可以想成等級, 這個值越高, 攻擊力越高, 得靠能量積分來購買, 若買了一個超人, 排名是 0, 再買一個超人, 排名會升到 1, 而不是有 2 個超人。最高到 VII。
  • 當前級別 - 可以想成等級, 這個值越高, 攻擊力越高, 可以靠戰鬥來提昇, 最高到 50。
  • 現實世界的金錢 - 這不用解釋吧!
排名升一級提升的攻擊力比較高, 由於能量積分很難大量累積, 所以比較難提昇。當前級別的提高對於攻擊力的提昇比較小。

這裡有個很技巧的升級方式, 我應該先升級「排名」, 還是「當前級別」? 你認為都一樣嗎? 讓數據來說話吧!



由上面幾張圖看來, 應該可以發現, "當前級別" 越高, "排名" 提昇一級增強的攻擊力和生命值是比較多的, 所以我現在幾乎都是打到 50 級才會升級 "排名"。

當前級別 46, 攻擊力增加
10164-6776 = 3388

當前級別 50, 攻擊力增加
10527-7018 = 3509

這遊戲有大點的螢幕會比較好玩, 本來玩免費遊戲都堅持不付錢的, 不過被自殺突擊隊 (Suicide Squad) 的小丑女 (harley quinn) 吸引, 花了 590nt 買了小丑女, 也順便通過之前打不過的關卡, 本來已經不想玩了, 但 harley quinn 加入後, 戰力大增, 後來又陸續打了不少關卡, 得到不少挑戰人物, 愈玩愈久。590 算是比較貴的角色, 能力越強大價錢越貴。

原本以為 harley quinn 是限量, 不過過了很久之後的挑戰賽還是有機會可以得到 harley quinn, 有點被耍了的感覺。



不過我發現用 "現實世界的金錢" 買的人物有個問題, "排名" 無法提昇, 就算有足夠的 "能量積分" 也沒用, 所以我現在都不想花 "現實世界的金錢" 買人物了。而用 "能量積分" 買到的某些角色也有類似問題, 大部分是挑戰賽的人物, 一定要等要你打完挑戰賽, 才可以提昇 "排名"。

建議註冊一個 WBID 帳號, 登入後, 每天會有一個獎勵, 會得到能量積分、人物, 不拿白不拿。


想看萬眾矚目遊戲包、阿卡姆遊戲包、裝備箱裡頭有什麼話, 可以參考以下影片, 看他這樣買還蠻過癮的。



裝備箱的東西不算好, 沒有必要購買, 玩「網路對戰」或是「幸存者模式」就會有機會得到。

「萬眾矚目遊戲包」對我來說, 最主要想要烏鴉、防化服末日, 看影片可以得知, 並不是那麼好得到, 得花不少能量積分才有機會。其他人物我覺得沒有特別喜歡, 若是買到蝙蝠俠和超人 (以下那兩個人物), 那就虧大了, 這 2 隻沒有特別厲害。如果是阿卡姆蝙蝠俠就還不錯。



來談談一般正常關卡, 第六關的敵人的 "排名" 都是 V (五), 我的 harley quinn (排名 0 - 標準) "當前級別" 升到 50 級也打不過, 還是得用 "能量積分" 升級 "排名", 所以要靠打關卡賺 "能量積分"。

另外有個問題, 我的 harley quinn 是花 "現實世界的金錢" 590 nt 買的, 前面提到會有無法透過 "能量積分" 升級 "排名" 的問題, 直到我在挑戰賽再次贏得 harley quinn 時, 我才可以用 "能量積分" 491000 來升級 harley quinn, 這樣花 590 nt 買 harley quinn 的感覺很不爽, 所以我現在不太喜歡在花 "現實世界的金錢" 買人物了。若是這個角色在商店中並沒有販賣, 只能一直維持在「排名 0/當前級別 50 級」 , 這樣的角色不管特殊能力在厲害, 都沒有用, 6, 7, 8 的關卡一定打不過。

不過自殺突擊隊的 harley quinn 真的很厲害, 第一能量的絕招有 3 段, 狠狠的打擊敵人, 網路對戰最怕遇到這隻。

挑戰人物需要打過 5 個關卡, 不是很容易, 因為需要銅級、銀級、金卡 3 種等級的人物, 而有些關卡還會限定需要的人才可參加, 我就因為沒有其他 3 個角色無法得到少年烏鴉, 少年烏鴉好像是還蠻厲害的角色, 沒得到蠻可惜的。

從第六大關就難倒我了, 後來等到 harley quinn 好不容易升級 "排名" 到達 16k 的攻擊力之後, 可以挑戰 6-7 的關卡, 這邊卡了很久, 終於在我配合 2 個小丑下, 破了這個關卡。

我使用的策略是, 使用 2 個小丑, 小丑的特殊能力是, 被打死之後會奪去敵人蠻大一部分的血量, 這 2 個小丑被打死後, 敵人的血量也快沒了, harley quinn 補上最後一刀即可, 輕輕鬆鬆。

這個遊戲賺的 "能量積分" 非常少, 要很辛苦的練功, 也才能集到一點 "能量積分" 購買升等品, 我之前是打 6-1 賺錢, 對我來說這關相當容易過, 大概每關 30 秒內都可以結束, 不過每個人狀況都不同, 也可以重複打別關來賺 "能量積分", 現在可以打 6-8, 這關敵人很強, 我的 harley quinn "排名" 升級到 V 的時候才打得過。

6-8 可獲得的 "能量積分"
1950
4620
4690
4760
4830

連線功能蠻爛的, 有時候會卡很久, 整個螢幕都不動, 只能重新開機, 連線的配對也很慢, 不過是個賺錢的好管道。我都盡量挑比較弱的人去打, 因為遇強則強, 遇到有特異功能的, 會很難打贏。

大部分的角色都帶有特殊能力, 例如生化人每次出場都會回復一點血量, 小丑被打死的話, 敵人會損失大量的血。

烏鴉則是會在血量剩下 20% 之內和敵人交換血量, 這招有夠賤, 網路對戰遇到這隻很棘手的。

Challenge Skip Cost Reference

Challenge Skip Cost Reference (跳過 5 關挑戰賽所需能量積分)
INJUSTICE: GODS AMONG US MOBILE FAN COMMUNITY·MONDAY, JULY 25, 2016
 The cost to skip is universal throughout all challenges, so this can be used for any future references.
 -----------------------------------------------
 Standard/Expert/Nightmare(The first 4 stages have same cost across the 3 difficulties) 
 Stage 1: 64,000
 Stage 2: 64,000 
 Stage 3: 120,000
 Stage 4: 120,000 

 The 5th stage has different costs depending on the difficulty:
 Standard Stage 5: 250,000
 Expert  Stage 5: 500,000
 Nightmare Stage 5: 800,000
 ---------------------------------------------------
 Total Skip Cost for All Difficulties Per Stage:
 Stage 1:   192,000
 Stage 2:   192,000
 Stage 3:   360,000
 Stage 4:   360,000
 Stage 5: 1,550,000
 -------------------------------------------------------
 Total Skip Cost for All Stages Per Difficulty
 Standard:    618,000
 Expert:         868,000
 Nightmare: 1,168,000
 -------------------------------------------------------
 Total Skip Cost For All Stages & Difficulties : 2,654,000
 -----------------------------------------------------     

 Thanks to macksoks  from the official forums for the information. 

跳過第五關挑戰賽所需能量積分
20000 * 4
30000 * 4
40000 * 6
60000 * 1

跳過第二關挑戰賽所需能量積分
1000 * 4
5000 * 4
10000 * 4

目前有個 "能量積分" 200000 就可以買到之前的挑戰人物, 我在這邊花了很多 "能量積分", 沒升級 harley quinn 有點可惜, 不過這個強化包有期限, 升級 harley quinn 就在晚一點了。



杰西卡也是厲害的角色, 她的組合技可以轟出一系列的攻擊, 也有機率是敵人昏迷, 而第二大攻擊可以和綠燈俠搭檔, 會再延續一次攻擊。



獲得 "重生烏鴉" 後, 如虎添翼, "重生烏鴉" 的奪取對手的能量太恐怖了, 只要對手的能量大於 "重生烏鴉" 出場時, 便會奪取對手的能量佔為己有, 然後便可以轟出 2 或 3 級的大絕招, 佔了很大的便宜。



這 3 隻角色是我目前最強大的, 有了這些角色之後, 終於打到了第七大關, 但 7-8 還是過不了, 全部都是 "排名" VII 的角色, 威力很強, 只有一隻 harley quinn 排名到 VII 也是不行的, 我暫時敗退在這關, 等待 "重生烏鴉" "排名" 提昇後, 再來挑戰。

中文翻譯有點怪怪的, 我將 ipad 設定為英文語系, 這樣就可以看到人物的英文名稱。



而意外得知 regime 是指什麼角色, 中文是 "政權" 的角色。

如果你懷疑有那個帳號作弊或是開外掛的話, 以下連結說明怎麼檢舉這些帳號。
HACKING/CHEATING, AND REPORTING, PLEASE READ [ New ]

fig 2 有兩個雷克思而且無法決定人物出場順序
20170211 (6) 我終於把 7-8 給破了, 使用 Harley Quinn VII/50, Raven VII/50, Jessica Cruz IV/50, 7-8 所有人物均是 VII/50 等級的角色, 又有一些關卡限制, 例如不能用第二絕招、血量減少 ... 一堆毛, 超級難打。

7-8 最後一關類似 6-8 的第一關, 只不過改成你所選用的角色, 但等級都是 VII/50, 我用了和 6-8 第一關同樣策略, 但是行不通, 對手的 Harley Quinn VII/50 比我的 Harley Quinn VII/50 攻擊力、血量都高出不少, 我最後選用 Harley Quinn VII/50, Raven VII/50 和一個銅級角色才破關。

坦白說 7-8 很難, 我一點都不想再挑戰一次。而 fig 2 這關很誇張, 有兩個雷克思而且無法決定人物出場順序還限制時間, 這 2 個雷克思會一直放第一絕招把你的能量消掉, 無法發出第一大絕招來對付敵人, 就算我有 raven, 由於無法決定其出場順序, 不見得可以吸收他們的能量, 很難打。他們的排名都是 VII, 但攻擊能力和血量幾乎都很高, 相當難纏的一關。



而過了這麼難的 7-8 竟然只給我 fig 3 這樣的爛卡。
fig 3. 過了 7-8 得到的卡片

升級到 X 的排名, 這個我本來不想玩的, 破了 7-8 我就想結束了, 不想花太多時間在這遊戲上, 不過還是誤打誤撞玩了這個升級關卡。



總過有 3 關要打, 最後一關的阿瑞思會有一擊斃命的絕招, 要點選到游標的中間才能躲過致命攻擊。打完後選一個可以突破排名 VII 的人物, 再來就是用能量積分去升級他。

不過每打一次只能解開一個等級, 要升級到排名 X 得要打 3 次, 而排名升到 X 之後, "當前級別"就可以繼續升到 60 級。

一旦升級到排名 X, 打 7-8 就沒有那麼難了。

挑戰人物模式, 完成 3 個等級
挑戰模式的惡夢等級終於可以完成, 不過青銅和白銀我是靠著能量積分過關的, 裡頭有些關卡太難打了, 大概要花 120000 能量積分, 比起 4x0000 還是便宜不少。

少年烏鴉惡夢等級
第三關
5000*2
10000*2
15000*2

第二關 - 需要冷霜殺手
1000*4
5000*4
10000*4

以下的作弊方式已經失效, 參考就好。


ref:

帳號轉換的問題:
https://community.wbgames.com/t5/forums/searchpage/tab/message?filter=location&q=+transfer+my+account+to+new+machine&location=category%3AInjustice
帳號在 ios 和 android 是不能相互轉換的, 很不爽。

injustice 2 官網討論區:
https://community.wbgames.com/t5/Injustice-2-Mobile/ct-p/injustice2mobile/interaction-style/forum

2017年1月13日 星期五

compiler [2.3] - 產生 if/else, while 的 AST

旦旦而學之,久而不怠焉,迄乎成。
這篇來得很晚, 在《compiler [1] - 四則運算》之後本來要發表這篇的, 不過程式碼總是比文件更新快, 再加上一些怠惰的因素在, 只寫了一小段, 最近才補完, 所以就變成現在才發表了。完成程式之後沒馬上把心得寫下來就會像這樣一直拖, 可是要把心得記錄下來又是一件累人的事情, 有怠惰之心是人之常情。

看過四則運算的 ast 之後, 應該很想知道其他結構化單元的 ast 會長什麼樣? 畢竟很多寫 AST 的文章, 幾乎只會把四則運算的 AST 秀出來, 其他結構單元的 AST 一樣很重要, 本篇文章要談的是 if/else statement AST, 再來還會有 function delcare/definition, funcion call AST, 這些都很重要, 一個都不能遺漏, 要不然在編譯器的學習上就少了一塊拼圖, 努力這麼久竟沒全功, 豈不可惜。

我做了一點修改, 把《两周自制脚本语言》的 EBNF 改成 c 的語法, 不過和 c 有點不同 if statement 的 {, } 一定要有。畢竟目標是以 c 為主, 若不能寫出自己喜歡的語言編譯器, 實在沒什麼動力。有些書用 pascal 為例子, 我一點都不想寫 pascal, 怎麼會有興趣去實作它的編譯器呢?

基本上 ebnf 寫的出來, 程式就寫的出來, 再經過幾次的實作後, 我慢慢掌握到訣竅了。就是 if 判斷式的字串比對程式碼, 還真的不難。

  • pop_token(): 會把目前的 token stream pop 出來
  • peek_token(): 不會把目前的 token stream pop 出來

概念很簡單, 是很重要, 一定要準備這樣的 2 個 function, parser 才會比較好寫。

比對一下 ebnf 和程式碼, 先不要管 expr(), block() 之類的, 會有一點具體的感覺。

ASTNode 的產生可能有點難度, 樹這樣的資料結構比較難理解與使用, 常常會跑錯 node, 而且沒有好的輸出來看這棵樹有沒有建對, 這是為什麼我想盡辦法要輸出整棵樹的原因。但弄懂之後就像打通任督二脈, 功力再上一層樓。

c_parser.cpp
 242 // block     : "{" [ statement ] { (";" | EOL) [ statement ] } "}"
 243 // modify - block     : "{" [ statement ] { ";" [ statement ] } "}"
 244 // modify 2 - block     : "{" { statement } "}"
 245 ASTNode* block()
 246 {
 247   Token token = peek_token(); 
 248 
 249   ASTNode *b = new ASTNode();
 250 
 251   if (token.str_ == "{")
 252   {
 253     pop_token();
 254   
 255     while(is_token("}") == false)
 256     {
 257       need = false;
 258       ASTNode *s = statement();
 259       need = true;
 260       if (s)
 261         b->add_child(s);
 262     }
 263 
 264 
 
 291     Token t = peek_token();
 292     if (t.str() == "}")
 293     {
 294       Token t = pop_token();
 295     }
 296     else
 297     {
 298       err("block: should '}'", t.str_);
 299     }
 300 
 301 
 302   }
 303   else
 304   {
 305     err("block: should '{'", token.str_);
 306   }
 307   return b;
 308 }

 455 /*
 456  * statement :   "if" expr block [ "else" block ] 
 457  *               | "while" expr block
 458  *               | simple
 459  */
 460 
 461 /*
 462  * modify - statement :   "if" "(" expr ")" block [ "else" block ] 
 463  *                        | "while" "(" expr ")" block
 464  *                        | "return" [expr]
 465  *                        | simple ";"
 466  */
 467 ASTNode* statement()
 468 {
 469   ASTNode *s_node = 0; // statement node
 470   Token token = peek_token(); 
 471 
 472   if (token.str_ == "if")
 473   {
 474     Token t = pop_token();
 475     t.ast_type_ = IF;
 476     s_node = new ASTNode(t);
 477 
 478     ASTNode *e = 0;
 479     t = peek_token(); 
 480     if (t.str_ == "(")
 481     {
 482       pop_token();
 483       e = expr();
 484     }
 485     else
 486     {
 487       err("statement: should '('", t.str_);
 488     }
 489     t = peek_token(); 
 490     if (t.str_ == ")")
 491     {
 492       pop_token();
 493     }
 494     else
 495     {
 496       err("statement: should ')'", t.str_);
 497     }
 498 
 499     ASTNode *then_b = block();
 500     then_b->set_token(then_block);
 501 
 502     s_node->add_child(e, then_b);
 503     //s_node->add_child(then_b->children());
 504 
 505     Token token = peek_token(); 
 506     if (token.str_ == "else")
 507     {
 508       Token t = pop_token();
 509       ASTNode *else_b = block();
 510       else_b->set_token(else_block);
 511       s_node->add_child(else_b);
 512     }
 519 
 520   }
 521   else if (token.str_ == "while")
 522        {
 523          Token t = pop_token();
 524          t.ast_type_ = WHILE;
 525          s_node = new ASTNode(t);
 526          ASTNode *e = 0;
 527          t = peek_token(); 
 528          if (t.str_ == "(")
 529          {
 530            pop_token();
 531            e = expr();
 532          }
 533          else
 534          {
 535            err("statement: should '('", t.str_);
 536          }
 537 
 538          t = peek_token(); 
 539          if (t.str_ == ")")
 540          {
 541            pop_token();
 542          }
 543          else
 544          {
 545            err("statement: should ')'", t.str_);
 546          }
 547 
 548          ASTNode *b = block();
 549          b->set_token(while_token);
 550          s_node->add_child(e, b);
 551        }
 552        else if (token.str_ == "return")
 553             {
 554               pop_token();
 555               cout << "xx return" << endl;
 556               s_node = new ASTNode(return_token);
 557               Token t = peek_token();
 558               if (t.str() == ";")
 559               { 
 560                 pop_token();
 561               }
 562               else
 563               { // expression
 564                 cout << "expr return" << endl;
 565                 ASTNode *e = 0;
 566                 e = expr();
 567                 if (e)
 568                   s_node->add_child(e);
 569 
 570                 if (is_token(";"))
 571                 {
 572                   pop_token();
 573                 }
 574                 else
 575                 {
 576                   Token t = peek_token();
 577                   err("statement|simple: should be ;", t.str());
 578                 }
 579               }
 580             }
 581             else // simple
 582             {
 583               s_node = simple();
 584               if (is_token(";"))
 585               {
 586                 pop_token();
 587               }
 588               else
 589               {
 590                 Token t = peek_token();
 591                 err("statement|simple: should be ;", t.str());
 592               }
 593             }
 594 
 595   return s_node;
 596 }

c_parser.cpp L472 ~ L520 就是在處理 if/else statement, 並產生 AST 中的 if/else node, 對照著 L462 的 ebnf 看, 簡單的不得了吧, 誰說 parser 很難的? 只要寫得出 ebnf, 幾乎就寫得出程式碼。不過自己改寫 enbf 要注意左遞迴的問題, 這也不是太難, 反正遇到左遞迴的話, 就會發現程式一直在 recursive 沒有結束的一天, 遇到就知道怎麼改了, 我沒遇到所以無法提供個例子, 請自己參閱相關書籍。

if 這部份就是要產生 if node, 特別的部份是 if 區塊和 if 條件式, if 條件式是 expression, 想成「四則運算」就好, 「四則運算」的 AST 很久之前就完成了, 沒問題, if 區塊剩下的 if statement 也可以想成 expression -> 又可以想成「四則運算」, 等於只要處理 if node 而已, 實在輕鬆, 真實世界當然不是這麼簡單, 不過這樣想的話, 似乎就沒那麼難了。

現在你知道為什麼要先處理「四則運算」了吧, 處處都是「四則運算」。

list 1. if/else AST
 1 int main()
 2 {
 3   int i,j;
 4 
 5   i=5;
 6   j= i + 2;
 7   if (i>1)
 8   {
 9     printf("i>1\n");
10   }
11   else
12   {
13     printf("i<=1\n");
14   }
15 
16   return 0;
17 }
18 
19                                                     root
20                                                      |
21                                                     prog
22                                               _______|________
23                                               |              |
24                                    main |int |func |global  main
25                                               |
26                                           func_body
27       ________________________________________|________________________________________
28       |                   |                   |                   |                   |
29      var                  =                   =                   if                return
30   ____|____              _|__                _|__     ____________|____________       |
31   |       |              |  |                |  |     |           |           |       0
32 i |int  j |int           i  5                j  +     >       then_block  else_block
33                                                _|__  _|__         |           |
34                                                |  |  |  |       printf      printf
35                                                i  2  i  1         |           |
36                                                                 i>1\n       i<=1\n

fig1 list 1 範例的 AST, if/else statement AST

如果 else 對你有點難的話, 不要實作 else 也無所謂, 用 if 就夠了, 難度又可以再減少一些。

while node 該怎麼建立呢? if node 會, while 就會, 這是一樣的, 只是關鍵字改成 while 而已嘛! 他們的不同在 eval 階段, if 的 eval 只要執行一次, 而 while 的 eval 可能要執行 0 次或是多次, 但 AST node 是很類似的。

學習的重點在精神, 不在完整, 不然光是要處理 int *********i, 就累死你了, 目前的程式是可以 parse int *********i, 但是無法 eval 它, 要怎麼紀錄這樣的型別, 難倒我。

2017年1月7日 星期六

[敗家] inkBook 8吋

20161031 請同學代買, 20161108 到貨,
20161224 在台灣收到 169 usd, 付了 5300 nt。
之前買了 sony prs t1 電子閱讀器, 可惜現在出了點問題, 只能閱讀英文電子書, 一放進中文書籍, 畫面就會黑掉, 無法正常閱讀, 我花了不少時間處理, 甚至連 root 都試過了, 還差點搞壞成為磚頭, 但是還是無法修好。

放棄修復後, 尋尋覓覓打算找 sony prs t1 的替代品。

6 吋實在太小了, 9.7/13.3 吋我又覺得太大, 而且價錢也貴, 後來得知有 8 吋的產品, 是在淘寶發現的。

PocketBook 电子书 电子阅读器 移动阅读设备 PB840 InkPad》, 事實上我也幾乎要買到它了, 只是有瑕疵, 後來以退貨結束, 加上我還沒搞懂特貨的運送, 只好另尋它法。而《PocketBook 电子书 电子阅读器 移动阅读设备 PB840 InkPad》 現在也停止販售了, 也許該慶幸沒買到。禍兮, 福之所倚; 福兮, 禍之所伏。

期間我打算購買 kindle, ruten 也有現貨, 但改機的部份實在讓我覺得麻煩, 我只想讀電子書, 不想搞什麼改機, 而 kindle 無法方便的閱讀 epub 檔案, 就是覺得不太實用, 我並非只看 amazon 自家的電子書。

想不到在網上亂找之後, 知道了美國亞馬遜網站竟然有賣 8 吋的電子書閱讀器, 真奇怪, 亞馬遜自己怎麼還在堅持 6 吋的, dxg 的 9 吋似乎不再重製了, 我看到有 2, 3 款, 其中包含 inkBook 8 這款, 我沒什麼要求, android, 可以插 sd card, 然後找了一台便宜的。

但是這台不寄送台灣, 請了同學代買, 等了兩個月終於到了我手上, 這兩個月間, 我又買了不少 drm-free 的電子書。

買的時候沒注意到有皮套, 所以就裸機使用了, 重量很輕。

官網: http://my-midia.com/en/inkbook-8-2/ 有相當多的資訊, 以下節錄 cpu 和 ram。

ram 512 MB 有點小, cpu 是 dual-core cortex a9 1.0G, 解析度: 1024×768 pixel (160 dpi), 都還可以, 如果只專注在看電子書, 而不是當平板用, 也是夠力。

android 是 4.0, 也還可以。

e-ink 最為人詬病的就是換頁時, 畫面會全閃爍的情形, 本台機器可以自己設定翻幾頁時才全部更新, 這時候就會來個全反擊, 阿, 不是, 是全閃爍。預設是 5 頁。
Screen8” E Ink Pearl™ (EPD, 16 shades of grey), 1024 x 768 px resolution (160 DPI), touch enabled (capacitive)
FrontlightYes (dimmable)
Dimensions198 x 144 x 8,4 mm, 258 g
Battery2800 mAh, Li-Ion Polymer
Built in memory8 GB
Wireless connectionYes, Wi-Fi 802.11 b/g/n
Portsmicro-USB 2.0, microSD cards support up to 32 GB
Processor and RAM memoryDual-Core Cortex A9 1.0GHz, 512 MB RAM
Supported formatsEPUB, PDF (reflow), MOBI (w/o DRM), TXT, FB2, HTML, RTF, CHM
美國亞馬遜連結: https://www.amazon.com/inkBOOK-Touchscreen-Display-reader-Android/dp/B01C48BYLY

同學就是從是從這個網址訂購, 8 吋果然大了不只那麼一點, 無法單手拿在手上, 掌握起來有點不便。

比起《PocketBook 电子书 电子阅读器 移动阅读设备 PB840 InkPad》, inkBook 8 當然更好, 有了 android 的自由度, 幾乎可裝任何需要的 app, 當然, 閱讀 epub 的 app 不需要太多, 就那些, 但我還裝了:
  • 多看
  • kindle
  • dropbox,
  • google 中文輸入法, 沒這個怎麼輸入中文
  • chrome
有些和看書無關, 但用來傳檔案會很方便。

inkBOOK 8, 8" E Ink Touchscreen Display e-book reader with Built-in Light, Wi-Fi -- includes Android App Store


Price: $199.00
Sale: $169.00 & FREE Shipping. Details
You Save: $30.00 (15%)
In Stock.

本來預期會有 10% 的稅, 不知道為什麼後來沒有, 所以就是 169 usd, 我付了 5300 nt。

fig 2 google 注音輸入法

android 的系統, 我對其中文閱讀、輸入的表現毫不擔心, 但其實和我想像的還是有點差距。

首先其 app market 不是 google market, 是他自己的 market - Midiapolis App Store, 有一些 android app 會找不到, 例如: google driver, 讀冊 ... 不過還好大部份還是有的, 不會太困擾。而 gmail app 無法正確登入, 也還好, 就用 chrome 來登入 gmail。

台灣有賣類似的:

博閱T80 8吋 電子書 閱讀器 Andriod系統 內建閱讀燈 可安裝開卷有益 漫畫島 預購

8吋螢幕 可聽音樂 多格式支援 Andriod4.2
一次付清特價 6800 元 
信用卡紅利折抵刷卡金 ( 接受20家銀行, 紅利折抵辦法 )
付款方式 (不限3萬) 
運費跨店購物車490免運費單店免運費

獲得現金積點:68 點 (1點 = 1元) 說明
     
800萬件商品適用 (說明)

價錢貴了一些, 早一點知道也許我會從這裡購買。

google app 的服務有問題我猜測是 google service 有所更動, inkBook 8 有新的 firmware 可以更新, 也許有所改善, 但我不想更新 firmware, 而且使用 chrome 也可以透過瀏覽器使用 google 的服務, 算是可以接受。

fig 4, 螢幕大小比一比

sd card 的使用也很乾脆, 都可以使用檔案管理程式了, 還怕有什麼電子檔案開不起來嗎? 和 sony t1 不同, sony t1 的 sd card 有個保護性很好的蓋子, 這個沒有, 直接就插在上方的 sd card 插槽, 沒有蓋子可以保護。

kindle app 讀取 amazon 買的書, 當然也都沒問題, 輕鬆下載來看, 沒理由被綁在 kindle 硬體上。中國亞馬遜的書籍, 我買了 "時間簡史" 的簡體中文版本, 看起來沒什麼問題。




kobo books app:
20170203 我買了 "臺灣史上最有梗的臺灣史", 因為有折扣, 只花了 96 nt, 其實我不想買有 drm 的電子書, 這只是我測試用, 買買看台灣繁體中文的電子書。




不喜歡的理由很多:
  • 帳號被盜怎麼辦
  • kobo 倒了怎麼辦
買實體書或是 drm-free 的版本完全不必要擔心這些問題。

這本實體書是直排的版本, 但電子書是橫排的版本, 我不是很在意橫排或是直排, 實際上, 我自己喜歡橫排多一些。

fig 5 ipad pdf

fig 6 ipad epub

fig 7 inkbook 8 pdf

fig 8 inkbook 8 pdf reflow

有些書籍開啟的速度很慢, 慢到以為當機沒反應, 我不確定是 app 的問題還是檔案本身就比較大, 得有點耐心。預設的 app 有 pdf reader 和 fbreader/preader epub reader, 我覺得還算好用, 沒什麼特別要求。

使用 grabmybooks 抓下來的電子書, 也不在需要做特殊的轉換才能正常顯示中文, 省下我不少麻煩, sonly t1 還得特別轉換才能正常顯示中文, 實在太繁瑣。

有了 sony t1 的電子書經驗, 就算是這台 8 吋, 我還是不習慣, 有些實體書的感覺, 電子書無法呈現, 特別是翻頁的手感, 電子書不見得有優勢, 電子書最適合的還是小說這種圖片較少, 從第一頁讀到最後一頁這樣的書籍, 我常看的書籍通常不是此類型, 都需要前後翻頁來對照, 電子墨水的閃爍和過慢的翻頁速度, 都不是好體驗。

而使用大尺寸螢幕的平板來看書也不見得就不好, 像是 pdf, 或是彩色、圖片較多的雜誌, 雖然不見得適合, 但若只是短時間閱讀 (10 分鐘內), 也還是可以派上用場。當然, 長時間的閱讀是完全不行的, 可別勉強自己, 眼睛會出問題的。

目前我是綜合這 2 種機器來閱讀電子書, 當然 e-ink 的閱讀器還是我的主力。

台灣推行電子書的努力我個人完全不看好, 連實體的 e-ink 電子閱讀器都買不到, 要怎麼發展電子書產業呢? 只有 app 在平板看書是不可能的, 書店老闆自己都沒試過嗎? 在平板電腦怎麼可能長時間閱讀, 若無法長時間閱讀, 你們那些電子書要賣誰呢?

inkbook 8 沒有實體翻頁按鍵, 個人覺得不太方便, 還是習慣有按鍵來翻頁, 雖然點擊螢幕的右邊、左邊就可以翻頁, 但還是不習慣, 而且有時候反應不靈敏, 當沒有翻頁時, 我不知道是我沒按到螢幕, 還是閱讀軟體自己鈍鈍的, 有時候再多碰螢幕幾次, 就多翻了好幾頁。內建的檔案總管很難用滑動來翻頁, 我老是翻不到在scroll bar 中間的電子書檔案, 使用起來感覺非常不愉快, 後來安裝了另外一個檔案管理器才好多了。

螢幕從 6 吋換到 8 吋覺得沒什麼太大的改變, 不好用的地方還是不好用, pdf 的閱讀效果依然不好, 參考 fig5 ~ fig 8, 可能需要 13.3 或是 9.7 吋的機型。

android 系統相對方便得多, 不用被綁在像是 kindle, kobo 之類的平台上, 要看什麼書就看什麼書, 方便不少。

而 drm-free 的電子書在這樣的系統上更是如魚得水, 不用擔心 kindle 無法開啟 epub, 得花功夫破解, 找一套看的順眼 epub reader 就搞定。

sony t1 因為是 android 特定系統, 無法順利使用 android 的好處, 中文 epub 需要做個轉換才能正確顯示 (ref: sony prs-t1 使用心得 (2) - epub)。inkbook8 完全沒這問題, 用 grab mybook 抓下來, 直接看就可以正確顯示中文的 epub。

來看看圖表的問題, fig 9 ~ 11 是分別在 ipad, inkbook 8吋上看圖表的效果, 只有 pdf 檔圖表的效果最好, 我在 inkbook 8吋看圖表, 並不是很清楚。

fig 9. in ipad 9.7 吋, mobi 轉成 epub
fig 10. in ipad 9.7 吋, 原始 pdf
fig 11. in ipad 9.7 吋 mobi

fig 12. 在 inkbook 8 吋 epub

如果有連上網路, 不論是使用閱讀 app 看書, 或是使用其他 app, 都會不定時跳出廣告, 很煩人。

另外會有翻頁不順的問題, 有時候碰了螢幕好幾次, 就是不翻頁。

預設的 epub reader 是 FB2 reader, Adobe reader, Adobe reader 在翻頁時有時候會放大字型, 干擾看書, 預設我使用 FB2 reader, 不過推薦使用 koreader 看 epub, 閱讀體驗比較好。

ref: