• 沒有找到結果。

電腦象棋程式Shark的設計與實作

N/A
N/A
Protected

Academic year: 2021

Share "電腦象棋程式Shark的設計與實作"

Copied!
93
0
0

加載中.... (立即查看全文)

全文

(1)國立臺灣師範大學 資訊工程研究所碩士論文. 指導教授: 林順喜博士. 電腦象棋程式 Shark 的設計與實作 The Design and Implementation of the Chinese Chess Program Shark. 研究生: 中華民國. 劉孟謙. 一百零三. 年. 撰 七. 月.

(2)

(3) 摘要. 電腦對局在人工智慧領域一直是一個非常吸引人的項目,而電腦象棋相關研 究發展遠不及國外西洋棋來的豐富,而象棋是我國國粹,因此我們希望針對電腦 象棋的領域進行深入的研究探討。本研究以網路上開源的象棋程式「象眼」為基 礎,設計改良開發成象棋程式 Shark,以供象棋棋手精進棋力、參與各項電腦對 局競賽,並期望能與人類大師抗衡。 下棋程式的棋力與搜尋演算法、審局函數息息相關,本研究主要針對這兩項 進行改良。我們以軟體工程的概念設計加強演算法的可靠性;研發新的資料結 構—BitBoard 大幅的增進了審局函數的效能,讓程式能搜尋得更深、且讓特殊棋 型的偵測以及複雜的長捉盤面處理可以非常有效率的實現;另外增強程式對於兵 卒的掌握以及使用 Material Table 建構更多程式對於中殘局的知識。 原始象眼估計其棋力約有六段,改良後的 Shark 與象眼對弈已有 80%的勝率, 並且於 2013 年 12 月參加 TAAI 電腦對局比賽獲得銅牌、經過再改良後 2014 年 6 月參加 TCGA 電腦對局比賽獲得銀牌,在眾多持續開發多年的象棋程式中擠進了 一席之地,估計其棋力已晉升至七段。其後我們將繼續開發改良,期望能更精進 棋力,向頂尖程式與人類大師看齊。 關鍵字:位棋盤、人工智慧、電腦象棋. i.

(4) ABSTRACT. Computer gaming is a very interesting subject in artificial intelligence. Compared with Chess, Chinese chess (XiangQi) has fewer researches. Since Chinese chess is the quintessence of Chinese culture, we decide to make a further study on the subject. Our Chinese chess program "Shark" is improved from "ElephantEye" - an open source Chinese chess program on the Internet. Shark has participated in the TAAI 2013 and TCGA 2014 computer game tournaments. The strength of a Chinese chess program depends on its search algorithms and evaluation functions. We attempt to improve the two parts. We apply software engineering principles to improve the reliability of the search algorithms, develop a new data structure - BitBoard to improve the performance of the evaluation functions, make our program able to search deeper, recognize patterns more efficient, and detect the perpetual chase well. Then we build Material Table to establish the knowledge of mid-to-end games. ElephantEye is ranked about 6 dan. So far Shark has won 80% of the games against ElephantEye. And it won the bronze medal in TAAI 2013, the silver medal in TCGA 2014. It is ranked about 7 dan. We will continuously improve our Shark program. The ultimate goal is to compete with the strongest program and the human masters.. Keywords: BitBoard, artificial intelligence, computer Chinese chess. ii.

(5) 致謝. 感謝指導教授林順喜博士,在這幾年的研究以及論文寫作上耐心及熱忱 的指導我,並且解決各式遇到的困難,教授總是有非常多的 Idea,並且不厭 其煩的與我討論各項細節,讓我獲益良多。 特別感謝實驗室的徐大開同學,也是我大學時認識的象棋社社長,象棋 棋力有三段,由於我本身象棋棋力不高,如果沒有他的大力幫助,我想我可 能無法完成這個研究,在此獻上萬分感謝之意。 感謝實驗室的學長姐,在畢業後還是在我比賽前最辛苦的日子回來幫我 加油打氣,給我心態上的調適,讓我能更專心投入在研發上。感謝陳志宏學 長常常提供我研究上以及論文上的技術支援。感謝實驗室的同學與學弟們的 支持與鼓勵。 最後感謝我的父母,提供我多年良好的求學環境,使我衣食無虞,並一 路支持著我,讓我能順利完成學業,在此僅以本論文以及成果獻給我最敬愛 的父母。. iii.

(6) 目錄 摘要 .................................................................................................................................. i ABSTRACT ....................................................................................................................ii 致謝 ................................................................................................................................iii 目錄 ................................................................................................................................ iv 圖目錄 ............................................................................................................................ vi 表目錄 ..........................................................................................................................viii 第1章 緒論 ............................................................................................................. 1 1.1 研究背景 ..................................................................................................... 1 1.2 1.3 第2章 2.1. 研究目的 ..................................................................................................... 2 研究意義 ..................................................................................................... 2 文獻探討 ..................................................................................................... 4 相關論文及程式介紹 ................................................................................. 4 2.1.1 象眼 ................................................................................................. 4 2.1.2 2.1.3 2.1.4. ELP .................................................................................................. 4 棋謀 ................................................................................................. 5 象棋世家 ......................................................................................... 5. 2.1.5 BrainChess ...................................................................................... 5 2.2 電腦對局理論 ............................................................................................. 6 2.3 演算法 ......................................................................................................... 8 2.3.1 Mini-Max Search............................................................................. 8 2.3.2 Alpha-Beta Search......................................................................... 10 2.3.3 PVS................................................................................................ 13 2.3.4 水平線效應、寧靜搜尋 ............................................................... 16 2.4 資料結構 ................................................................................................... 18 2.4.1 棋盤表示與走步生成 ................................................................... 18 2.4.2 Transposition Table ....................................................................... 20 2.5 審局函數 ................................................................................................... 22 2.6 後續發展 ................................................................................................... 23 第3章 方法與步驟 ............................................................................................... 24 3.1. 資料結構的改進 ....................................................................................... 24 3.1.1 適用於象棋的 BitBoard ............................................................... 24 3.1.2 BitBoard 的棋盤表示法 ............................................................... 26 3.1.3 3.1.4. 棋子表示法 ................................................................................... 27 Intrinsic 函數................................................................................. 29 iv.

(7) 3.1.5 3.1.6. BitBoard 的基本操作指令 ........................................................... 31 以 BitBoard 加速走步產生 .......................................................... 34. 3.1.7 Magic Hash ................................................................................... 39 3.2 演算法改進............................................................................................... 41 3.2.1 走步排序策略............................................................................... 41 3.2.2 Late move reduction (LMR) ......................................................... 44 3.2.3 PVS 的改良 .................................................................................. 46 3.2.4 控制格計算................................................................................... 47 3.2.5 長捉的偵測................................................................................... 49 3.3 審局函數改進........................................................................................... 56 3.3.1 子力價值....................................................................................... 56 3.3.2 3.3.3. 特殊棋型....................................................................................... 58 自由度........................................................................................... 63. 3.3.4 Material Table ............................................................................... 66 第4章 實驗結果與未來發展............................................................................... 69 參考文獻....................................................................................................................... 73 附錄 A – TAAI 2013 電腦對局比賽棋譜 .................................................................... 74 附錄 B – TCGA 2014 電腦對局比賽棋譜 .................................................................. 79. v.

(8) 圖目錄 圖1 圖2 圖3 圖4 圖5 圖6 圖7 圖8. 不同遊戲的狀態複雜度與遊戲樹複雜度【1】 ............................................... 3 目前已知最少子困斃最多子盤面,黑方所有子都在卻無合法走步 ............. 6 井字遊戲為例的遊戲狀態樹 ............................................................................. 7 Mini-Max 樹示意圖............................................................................................ 9 Alpha-Beta 樹示意圖 ........................................................................................ 11 輪黑,紅方在 8 步後能將死黑方 ................................................................... 18 範例盤面 ........................................................................................................... 19 BitFile 結構 ....................................................................................................... 20. 圖9 圖 10 圖 11 圖 12 圖 13 圖 14 圖 15 圖 16 圖 17 圖 18 圖 19. BitRank 結構 ..................................................................................................... 20 Transposition Table 示意圖 .............................................................................. 22 128 bits 象棋棋盤 BitBoard 示意圖 ................................................................. 25 初始盤面紅方各個棋子對應的 BitBoard 示意圖 .......................................... 27 MS1B、LS1B 示意圖 ...................................................................................... 30 棋盤合法位置的 BitBoard,O 部分表示該 bit 為 1 ...................................... 32 BitTable[43]的 BitBoard 示意圖 ...................................................................... 33 Col[3]的 BitBoard 示意圖 ................................................................................ 33 車的 Mask ......................................................................................................... 35 與 Occupied 做 AND 運算後的結果 ............................................................... 35 查表得到的走步資訊 ....................................................................................... 35. 圖 20 圖 21 圖 22 圖 23 圖 24 圖 25 圖 26 圖 27 圖 28 圖 29. 車的走子步 ....................................................................................................... 36 車的吃子步 ....................................................................................................... 36 馬與象其拐腳處的 Mask ................................................................................. 37 反向傌的 Mask ................................................................................................. 38 要檢查是否有傌的位置 ................................................................................... 38 AVX2 指令 PEXT 運作示意圖【11】 ............................................................ 40 Killer Heuristic (左)與 History Heuristic(右)比較 ........................................... 43 水平線效應 ....................................................................................................... 44 使用延伸策略後 ............................................................................................... 44 使用 LMR 後 .................................................................................................... 45. 圖 30 範例中局盤面 ................................................................................................... 47 圖 31 紅方的控制點 ................................................................................................... 48 圖 32 黑方的控制點 ................................................................................................... 48 圖 33 紅方受到保護的棋子 ....................................................................................... 49 圖 34 黑方受到紅方威脅的棋子 ............................................................................... 49 vi.

(9) 圖 35 紅俥長捉假根包,紅方不變作負 ................................................................... 51 圖 36 圖 37 圖 38 圖 39 圖 40 圖 41 圖 42 圖 43 圖 44 圖 45. 紅傌長捉真根包,雙方不變作和 ................................................................... 51 黑方長捉無根兵,不變判負 ........................................................................... 52 由於黑方車不能反吃,因此為紅方俥炮連捉黑方車,紅方不變作負 ....... 55 TCGA 2014 Shark 對 BrainChess:雙方走閒著,不變作和 ........................ 55 合法的 From 與 To 組合 .................................................................................. 58 不合法的 From 與 To 組合 .............................................................................. 58 各種栓鍊 ........................................................................................................... 62 車的自由度計算 ............................................................................................... 64 馬的自由度計算 ............................................................................................... 65 車兵勝士象全 ................................................................................................... 68. 圖 46 圖 47 圖 48 圖 49. 單車勝單缺士 ................................................................................................... 68 TAAI 2013 棋謀對 Shark 中局盤面 ............................................................... 70 陷阱佈局 ........................................................................................................... 71 陷阱佈局結果 ................................................................................................... 71. vii.

(10) 表目錄 表1 表2 表3 表4 表5 表6 表7 表8. NegaMax 演算法虛擬碼 .................................................................................. 10 Alpha-Beta 演算法的虛擬碼 ............................................................................ 12 PVS 演算法的虛擬碼 ....................................................................................... 15 棋子編號意義,最右邊為 bit 0 ....................................................................... 28 棋子編號方式 ................................................................................................... 28 Intrinsic 指令與傳統 C++指令比較:128 bits Shift 為例 .............................. 29 Intrinsic 指令與傳統 C++指令比較:LS1B 為例 .......................................... 30 MSB 的 C++程式碼【9】 ............................................................................... 31. 表9 表 10 表 11 表 12 表 13 表 14 表 15 表 16 表 17 表 18 表 19. BitBoard 基本指令 ........................................................................................... 32 檢查將軍的實作方法比較 ............................................................................... 39 不適合使用 LMR 的狀況定義 ........................................................................ 46 亞洲棋規長捉規則細節 ................................................................................... 50 判斷捉的虛擬碼 ............................................................................................... 53 車馬炮在開中殘局的價值 ............................................................................... 56 仕相對於對方的威脅下降而降低其價值 ....................................................... 57 各種狀態下兵的價值 ....................................................................................... 57 Material Table 編碼表 ....................................................................................... 67 TAAI 2013 電腦對局象棋比賽結果 ................................................................ 69 TCGA 2014 電腦對局象棋比賽結果 ............................................................. 71. viii.

(11) 第1章 緒論 1.1. 研究背景 在人工智慧的研究領域之中,電腦對局一直是十分重要的一部分。除了. 各式對抗類遊戲的人工智慧開發,同時也有各類益智遊戲的解題研究,許多 嶄新的演算法與資料結構也隨著這些研究不斷推出。並且隨著硬體效能與演 算法的改良,許多遊戲的人工智慧已經開始達到了職業玩家的水準,有些遊 戲甚至人類已經不是電腦的對手,相信未來這樣的趨勢將會越來越明顯。 象棋是中國的國粹之一,其歷史相當的悠久,從古至今棋手累積了大量 的棋譜與開中殘局的豐富知識,例如《橘中秘》、《梅花譜》等等,也有些特 殊棋型的衍生成語與棋諺,例如說馬後炮、飛象過河等,也顯示了象棋融入 了華人的生活中。甚至有些國外的學者也熱衷於象棋研究。 象棋屬於完全資訊、無隨機性遊戲,遊戲中所有資訊對對弈雙方都是已 知的,且沒有隨機成分,這類型的遊戲還有像是西洋棋、黑白棋、將棋等等, 因為盤面資訊都已知,因此對遊戲的理解與掌握將主導棋局的進行,雙方的 對弈比的就是硬實力了。目前國際電腦對局學會(ICGA,International Computer Game Association) 、 台 灣 電 腦 對 局 學 會 (TCGA , Taiwan Computer Game Association)以及中華民國人工智慧學會(TAAI,Taiwanese Association for Artificial Intelligence) 也都將象棋列為正式的比賽項目之一了。. 1.

(12) 1.2. 研究目的 象棋一直是華人愛玩的一種棋類,個個棋手不斷互相對弈,突破自我,. 追求更強的棋力,而我們的目的便是希望能設計出強大的象棋程式與人類頂 尖棋手抗衡,甚至超越人類冠軍。因為程式是不會老化的,希望人類能與強 大的程式對弈並且不斷精進棋力。 目前象棋的研究比起西洋棋來說發展不算完善,相關技術論文相對較少, 特別在專家知識的建構上比較欠缺,因此電腦象棋算是很有研究價值的題目, 我們嘗試改進電腦象棋在開中局的布局以及殘局觀念,並改善程式的效能以 求更強的棋力。. 1.3. 研究意義 以完全資訊雙人對弈遊戲的人工智慧來說,不外乎都是以搜尋樹的方式. 來展開遊戲狀態,程式從中選擇最佳的走步行之。隨著搜尋層數加深,其遊 戲樹搜尋節點數是以指數暴漲。圖 1 列出了各種遊戲其狀態複雜度以及遊戲 樹複雜度,其中象棋的遊戲樹複雜度為 10150,可說是相當高。 設計者不斷的利用各種演算法以及分支切捨技術試圖加速以求更深的搜 尋,各種新演算法也不斷的被提出。但一旦搜尋的層數到一定的深度,再增 加深度對於棋力的提升便十分有限,且所花的計算成本卻大幅上升,因此勢 必要將研發的重心轉向搜尋樹底層的審局函數,因為審局函數才是程式知識 的核心,引領程式走向勝利。我們將試圖改進審局函數的專業知識,配合資 2.

(13) 料結構設計高效益的審局函數。. 圖 1 不同遊戲的狀態複雜度與遊戲樹複雜度【1】. 3.

(14) 第2章 文獻探討 本章將介紹電腦對局的基礎知識與本研究參考的主要資源—象眼,以及 其特色。. 2.1. 相關論文及程式介紹. 2.1.1. 象眼. 電腦象棋在大陸的研究較台灣來的多且完整,因此本論文主要是參考塗 志堅撰寫的「電腦象棋的設計與實現」 【2】以及開源的象棋程式「ElephantEye (象眼)」【3】來進行改良。 象眼由象棋程式「夢入神蛋」參照塗志堅的論文改進研發而成。象眼在 大陸的聯眾、弈天等象棋對弈網站上作過測試,用等級分來衡量,聯眾網的 戰績在 2500 分左右,弈天網快棋的戰績在 2000 分左右,慢棋在 1500 分左 右。 2005 年 9 月象眼參加在台北舉行的第 10 屆 ICGA 電腦奧林匹克大賽 中國象棋組比賽,戰績是 7 勝 5 和 14 負,在 14 個程式中排名第 11,目前 在開源的程式中象眼在知識部分稍有不足,因此還沒有辦法向頂尖的程式看 齊。. 2.1.2. ELP 4.

(15) ELP 初名為「象棋明星」 ,由許舜欽教授領導的團隊研發,主要以組合語 言寫成,因此效能上非常好,是屬於審局簡單、偏向遊戲樹展開的程式,在 早期(2000 年左右)是國內首屈一指的程式。. 2.1.3. 棋謀. 棋謀為交通大學吳毅成教授與其博士生曾汶傑研發,棋謀總共開發了約 10 年左右。不斷經由分析棋譜中的關鍵走步改良審局以特殊棋型偵測,並且 完全平行化,在開中局可以搜尋至 20 層左右,在攻殺方面十分擅長。. 2.1.4. 象棋世家. 象棋世家為鄭明政所開發,鄭明政有象棋五段的棋力,因此在審局精確 度以及專家知識建構上可說是數一數二。 象棋世家由 2000 年開始研發,前後與許多棋手不斷測試改良,也參與了 各種比賽,近期(約 2006 年至今)的比賽幾乎都是以全勝之姿蟬連冠軍寶座, 在國內是無人能敵。現在象棋世家由東華大學顏士淨教授接手繼續研發改 良。. 2.1.5. BrainChess. BrainChess 由中研院博士後研究員陳柏年所開發,近期針對開局庫加入 了大量陷阱布局,使得棋力大幅上升。其未來發展不容小覷。 5.

(16) 2.2. 電腦對局理論 象棋由棋手雙方輪流走子,至一方王被吃掉或是無合法走步—無法解將. 或困斃(如圖 2)即結束棋局,因此遊戲狀態可以以雙方就當前盤面可能的走子 步一層一層的展開成一樹狀結構,樹上的每個節點代表著盤面狀態,根節點 為當前的盤面。程式分析這棵龐大的對局樹並且從中選出一個最佳的走步作 出行動,圖 3 是以井字遊戲中局盤面為例的遊戲狀態樹。. 圖 2 目前已知最少子困斃最多子盤面, 黑方所有子都在卻無合法走步. 6.

(17) 圖 3 井字遊戲為例的遊戲狀態樹 如果能完全展開整棵遊戲樹,則可以保證程式在所有盤面下都能做出最 佳應對,換句話說就是破解了這個遊戲,但破解不代表能讓自己立於不敗之 地,因為有可能在雙方都下出最佳走步的情況下有一方必敗,而井字遊戲的 結果為必和。 由圖 1 可知,象棋的狀態複雜度有 1048、對局樹複雜度有 10150,要展開 整棵樹在有限時間內是辦不到的,因此我們只能由根節點往下展開數層進行 分析。 在展開到設定深度後,我們必須要對盤面進行分析,以一個大略的數值 代表著盤面的局勢,即審局函數。審局函數的意義代表著假設這個狀態發展 到遊戲結束的話能獲得的分數近似值,也可以稱作是獲勝的可能性分數,其 7.

(18) 值越高代表著獲勝的可能性越高。. 演算法. 2.3. 本節將詳細介紹對局程式常用的演算法以及其原理。. 2.3.1. Mini-Max Search. 由有限深度展開的遊戲樹中,我們必須要在葉節點(也就是有限深度搜尋 樹中的最底層)呼叫審局函數進行局勢的評分,以評估剩餘至棋局結束大概的 局勢。以零和遊戲來說,分數對於我方正值代表優勢,負值代表劣勢。如果 雙方都是以最佳的走法下棋,則我方會想辦法讓局勢分提高,也就是 Max 方; 敵方會想辦法讓局勢分降低,也就是 Min 方。 Mini-Max Search(最小-最大搜尋法)就是基於這個準則模擬雙方最佳的走 步,也就是依據 Min 與 Max 的選擇走步策略不斷的遞迴由下往上回傳結果, 圖 4 為 Mini-Max Search 的示意圖,Min 節點會選擇局勢分最低的子節點、 Max 節點會選擇局勢分最高的子節點,由最底層的審局分數不斷經由 Min 與 Max 選擇並往上回傳分數,至根節點(當前盤面)時,此時的 Max 方選擇的分 支就是當前搜尋樹下得到的最佳走步,程式就會輸出此步。 Mini-Max Search 是深度優先的演算法,複雜度為指數 kd,k 為分支因數, 也就是每一手的平均合法走步數目,d 為搜尋樹展開的深度,是一個暴力的 搜尋演算法。 8.

(19) 圖 4 Mini-Max 樹示意圖. 而實際上對於雙方棋手的策略都是為了要提高己方的分數,並壓低對方 的分數,因此我們可以在搜尋樹的分數往上回傳時都做變號處理,把看分數 的角度變成當前節點的出手方,因而所有的節點都取 Max,這樣可以省去一 半的程式碼,提升可維護性,此類的變種稱作 NegaMax Search。 表 1 是 NegaMax 演算法虛擬碼,注意 Evaluate()永遠使用正號,表 示局勢分數以當前走子方的角度計算,而在遞迴呼叫的部分前面加了一個負 號,因為對於當前盤面來說,對方得正分就是我方得負分。. 9.

(20) int NegaMax(int depth) { if (depth == 0) return Evaluate(); //到達限制搜尋深度,回傳審局分數 int best_score = -INF; //最佳分數設為負無限 while (moves left) //嘗試當前盤面所有可能著法 { MakeMove(move); //執行著法 //減少一層的深度,遞迴演算盤面 int score = -NegaMax(depth-1); UndoMakeMove(move); if (score > best_score) best_score = score; //更新最佳分數 } return best_score; } 表 1 NegaMax 演算法虛擬碼. 2.3.2. Alpha-Beta Search. Alpha-Beta Search【4】 【5】是 Mini-Max Search 的改良,概念在於由於對 弈雙方都是選擇對自己分數最高的分支,基於這個特性,當搜尋中發現有一 些盤面回傳的分數過高時(也就是對於上一層對手來說過低時),便不需要再看 其它分支,因為上一層對手必定不會選擇這個走步。 舉圖 5 的搜尋樹為例,當 E 節點得到+5 的分數時,由於 B 節點是 Min 節點,因此可以保證 B 節點回傳的分數絕對不會大於+5,又 A 節點已經得到 +6 的分數,在根節點是 Max 節點的情況下,可以確保根節點絕對不會選擇 B 節點,因此 B 節點其下的子樹(F 節點開始)就都沒有必要搜尋了,這個切斷後 續搜尋的動作叫做 Cutoff。 10.

(21) 圖 5 Alpha-Beta 樹示意圖 我們在原始 Mini-Max Search 演算法中增加了 Alpha、Beta 兩個參數,分 別表示目前我方、敵方已經找到的最佳走步分數,修改後的虛擬碼如表 2 所 示。注意在遞迴呼叫的部分把 Alpha 與 Beta 做變號交換位置以適應 NegaMax 永遠以當前走子方為視角的原理。隨著搜尋的進行,Alpha 值可能越來越高, Alpha 與 Beta 所含括的範圍稱作 Alpha-Beta Window,這個 Window 限定了節 點的分數必須落在這個區間內,若有分數超過 Beta,則發生 Beta Cut,其後 所有分支都不用搜尋;如果所有分支分數都小於 Alpha,則會回傳小於 Alpha 的值(也就是大於上一層的 Beta),因此上一層便會發生 Beta Cut。. 11.

(22) int AlphaBeta(int depth, int alpha, int beta) { if (depth == 0) return Evaluate(); //到達限制搜尋深度,回傳審局分數 int best_score = -INF; //最佳分數設為負無限 while (moves left) //嘗試當前盤面所有可能著法 { MakeMove(move); //執行著法 //減少一層的深度,遞迴演算盤面 int score = -AlphaBeta(depth-1, -beta, -alpha); UndoMakeMove(move); if (score > best_score) { best_score = score; //更新最佳分數 if (score > beta) return score; //Beta Cut alpha = MAX(alpha, score); //更新 Alpha bound } } return best_score; } 表 2 Alpha-Beta 演算法的虛擬碼 仔細觀察圖 5 可以發現,我們是在確認 E 節點分數比 A 節點小才能捨去 其後的節點,因此像 A 節點這樣擁有較高分數的節點越早被發現則越有機會 砍掉更多的分支。整個 Alpha-Beta Search 運作的過程中便是使 Alpha 值不斷 提升、縮小 Window 的過程,因此越早發現最佳走步可以越早讓 Alpha-Beta Window 縮到最小,讓其後的節點更容易發生 Beta Cut,達到更高的效能。因 此 Alpha-Beta Search 比起 Mini-Max Search 的效能提升非常仰賴走步生成的排 序機制。 12.

(23) Alpha-Beta Search 在最佳狀況(即所有節點展開的第一個走步便是該節點 的最佳走步,其後的節點的第一個子節點搜尋完後便 Cut 掉後續所有節點), 其時間複雜度比起 Nega-Max Search 可以從 kd 降為 kd/2,因而大幅提升效能, 而實際狀況則介於兩者之間,畢竟電腦下棋就是一個排序走步的過程,如果 走步排序機制完美,便不需要搜尋了。. 2.3.3. PVS. 在 Alpha-Beta 搜尋樹中,若有一個路線從搜尋樹的葉節點到根節點都被 選為最佳的分支,則這條路線就被稱為 PV 路線。PVS (Principal Variation Search) 演算法【6】 ,是經由 Alpha-Beta 演算法選擇 PV 路線的特性改良而來。 在 Alpha-Beta 搜尋樹中,會有三種類型的節點,如下做介紹: 1.. Alpha 節點:Alpha 節點其下面的子節點回傳的分數都小於 Alpha 值,表 示這個節點對於要走子的一方局勢十分壞,該節點回傳的分數必定會超 過上一層節點的 Beta 值,並發生 Beta Cut,即對於要走子的一方有其它 更好的選擇而絕對不會讓盤面演變至此。又因為此節點必須搜尋其下所 有子節點以確認分數都小於 Alpha,因此又稱 All 節點。. 2.. Beta 節點:Beta 節點其子節點止少會有一個分支回傳大於 Beta 的分數, 表示盤面對要走棋的一方十分好,但上一層的對手有其它走步可以避開 這樣的盤面,因此進行 Beta Cut,其後的走步都不需要檢查了。又因為 此節點只要找到一個子節點回傳大於 Beta 的值即可發生 Beta Cut,因此 又稱 Cut 節點。 13.

(24) 3.. PV 節點:PV 節點的子節點至少有一個分數會大於 Alpha,並且沒有分 支的分數會大過 Beta,意即 PV 節點為程式預期雙方會出手的走步。PV 節點必須搜尋所有的子節點,Root 必為 PV 節點。 圖 5 中的 A、C、D 為 PV 節點、E 為 Alpha 節點、B 為 Beta 節點,實際. 上當 F 節點被 Cut 掉後,B 節點會回傳+5,然而這不影響整體搜尋的結果, Beta 節點表示的是一個 Upper Bound,也就是說 B 節點的子節點如果全部搜 尋完的話分數必定不會大於+5。 PVS 便是假設每個節點的第一個搜尋到的子節點必定是 PV 節點,意即 假設其後的子節點分數都不會比第一個好,因此對第一個子節點使用傳統的 Alpha-Beta Search,然後利用得到的分數 value 對其後的子節點使用 (value, value+1) 的 Window 參數進行搜尋,這是一種「猜測」 ,對於其後的節點只關 心分數是否會大於第一個分支,而不需要知道實際分數,這個 Window 又稱 為 Zero Window。如果測試後得到的分數小於 value+1,則表示猜對了,該分 支的分數不會比第一個分支好,且由於 Alpha-Beta Window 極小,因而 Cut 掉了大量的節點而大幅提升效能;如果得到的分數大於等於 value+1,則表示 猜錯了,該子節點的分數大於第一個子節點,因此必須花費額外的成本重新 以正常的 Window 搜尋這個子節點,以得到準確的分數,然後再度對其後的 子節點猜測分數,PVS 的虛擬碼如表 3 所示。. 14.

(25) int PVS(int depth, int alpha, int beta) { if (depth == 0) return Evaluate(); //到達限制搜尋深度,回傳審局分數 int best_score = -INF; //最佳分數設為負無限 while (moves left) //嘗試當前盤面所有可能著法 { MakeMove(move); int score;. //執行著法. if (is first move) //如果是第一個著法,正常計算 score = -PVS(depth-1, -beta, -alpha); else {. //使用(Alpha, Alpha+1)的 Zero Window score = -PVS(depth-1, -alpha-1, -alpha); //結果若大於 Alpha,則表示猜錯必須重新搜尋 //若大於 Beta 則發生 Beta Cut 不必再重新搜尋 if (score > alpha && score < beta) score = -PVS(depth-1, -beta, -alpha);. } UndoMakeMove(move); if (score > best_score) { best_score = score; //更新最佳分數 if (score > beta) return score; //Beta Cut alpha = MAX(alpha, score); //更新 Alpha bound } } return best_score; } 表 3 PVS 演算法的虛擬碼 與 Alpha-Beta Search 相同,如果走步排序完美(特別重要的是最佳走步必 須排在第一個),可以最大化的提升效能,因為當最高分的節點先被計算出後, 15.

(26) 其後的節點可以用 Zero Window 快速驗證搜尋過。通常來說,PVS 在同樣的 搜尋時間下可以提升 2~3 層搜尋深度,可說是非常可觀。 另外 PVS 也是非常適合平行化的搜尋演算法。在搜尋完第一個分支得到 分數後,便可以對其後的所有分支平行搜尋,因為其後分支都是使用 Zero Window,因此平行運算不會因為 Alpha 值不斷上升的影響而失去 Window 縮 小帶來的速度提升,只有在其中一個分支傳回大於等於 value+1 的分數(猜錯) 時,必須停止所有的 Thread 重新搜尋。本研究沒有對平行化部分進行更深的 探討。. 2.3.4. 水平線效應、寧靜搜尋. 如果在搜尋樹到達設定的層數便停止搜尋傳回審局分數,則在葉節點的 地方常常有可能是尚有吃子或被將軍(即明顯有對策)的盤面,而我們卻因為設 定的搜尋層數到了而被迫停止推演接下來的發展因而錯估情勢。例如說在葉 節點我方吃了對方一隻馬,因為到達搜尋設定層數因而沒有發現在下一步我 方反而有一隻車會被吃掉,程式因而認為這步非常好而錯判情勢。這種情形 稱作水平線效應 (Horizontal Effect)。 解決水平線效應的方法必須於一些特定盤面在基礎的層數外做額外的延 伸。最常見的延伸策略為「寧靜搜尋」 ,此法為展開盤面上所有的吃子走步至 盤面穩定才回傳審局分數,如此一來可以在複雜的連續吃子中準確的評估形 勢。 另外考慮一種情況是可能的對策為唯一或是走步自由度大減的情形,例 16.

(27) 如說解將步通常只會有少數幾步;又如換子走步,為走步選擇極少而且必須 應對的盤面,由於這類型的盤面通常其應對方式十分單調(意即任何對於象棋 稍微有點概念的人或程式看到這樣的盤面通常都會做出正確且單調的回應), 因此實際上在這個盤面的這一層的搜尋是沒有得到任何資訊的,此狀況會讓 搜尋樹展開相同層數下實際得到的資訊下降。在實際的搜尋樹中,Alpha-Beta 演算法會嘗試著拖延不好的事件至搜尋限制層數之外而回傳一個不是太差的 分數,這也是水平線效應的一種。舉例來說,如果有一個著法會導致你失去 一隻馬,Alpha-Beta 演算法會試圖找到一系列迫著迫使對方必須立刻應對(比 方說將軍或是捉車),進而把真正失去馬的盤面拖延至搜尋限制層數外而認為 盤面狀況不差,即使最終還是會失去馬。 圖 6 舉出了另一種緩著(當自己快要被將死時不斷的送子來阻擋),圖中黑 方已無力阻擋紅方車的殺棋,但在到達這個盤面之前的情況,在較淺層的搜 尋下卻有可能無法發現殺棋。 上述兩種類型的水平效應是比較不容易被發現的,也可以說是 Alpha-Beta 演算法的一大缺失,它會導致在某些路線上獲得的實際資訊量大 減,因此必須要針對這類型的分支單獨增加搜尋深度,以避免搜尋樹退化。. 17.

(28) 圖 6 輪黑,紅方在 8 步後能將死黑方. 資料結構. 2.4. 本章將介紹象棋常用的棋盤表示法以及 Transposition Table。. 2.4.1. 棋盤表示與走步生成. 棋盤表示通常以雙向的 Piece-Square Table 來記錄,該 Table 是兩個一維 陣列,分別記錄棋盤上的格子裡有什麼棋子以及各個棋子在棋盤上的位置, 象眼使用了 16×16 的 Square-Piece Table (Mail Box) 記錄棋盤,Piece-Square Table 以長度為 48 的一維陣列使用其中 16~47 記錄各個棋子所在位置,相同 的兵種有不一樣的 Index:例如兩隻紅仕擁有不同的 Index。 走步產生時可使用 Piece-Square Table 查出棋子的位置,再以 Square-Piece 18.

(29) Table 檢查棋盤狀態以決定每種棋子的合法走步。合法走步的計算通常是在程 式啟動時預先生成棋盤狀態對應棋子的走步並建表預存,往後計算時只須依 據棋子所在位置查詢相對應的表然後考慮盤面資訊如棋盤的空格以及吃子與 否即可省去大量計算時間。 而車炮長距離移動的走法需要循序的檢查棋盤被阻擋的狀態,炮要檢查 炮架、馬與象則需要檢查馬腿與象眼,都是比較花時間的檢查,而吃子必須 要判斷目標是否是對方的子,因此走步產生是程式中相當花時間的部分。 對於車與炮的走步產生,象眼使用了 BitFile、BitRank 的結構,如圖 7 的範例盤面,以及圖 8、圖 9 的 BitFile、BitRank 結構,每一縱列與橫列都是 一個 2 進位資料,每個格子代表一個 bit,bit 為 1 代表裡面佔有棋子,圖中以 黑色部分表示該 bit 為 1。 相. 帥. 俥. 仕. 相. 仕 炮 兵. 兵. 炮. 傌. 兵. 車. 馬 兵. 卒. 卒. 卒 士. 車. 象. 將. 圖 7 範例盤面. 19. 象.

(30) 圖 8 BitFile 結構. 圖 9 BitRank 結構. 當要產生車或炮的合法走步時,取出棋子所在位置所對應的 BitFile 與 BitRank,然後以此 2 進位資料為 Index 直接去查詢走步預生成表格,可以得 到走子步資訊,吃子步則需要檢查目標點是否為敵方棋子。. 2.4.2. Transposition Table. Transposition Table (同形表)是用來記錄已經搜尋過的盤面資訊,包含審 局分以及最佳走步等,可以在以後遇到相同盤面時直接查詢而省去重複搜尋 相同盤面的時間。 Transposition Table 一般是把盤面資訊經過 Zobrist Hash 得到一個 index 對應到紀錄區。Zobrist Hash 是一種快速產生 Hash Index 的算法,對於每一個 棋子在棋盤上的每一個位置都有一個代表的亂數與之對應。當給定一個盤面 時,把這些棋子-位置所對應的亂數全部經過 XOR 運算得到的結果即為其 Hash Index。Zobrist Hash 的優點是:利用 XOR 運算的可逆性,當盤面發生小 20.

(31) 變化時(走子、吃子),只需把走動的棋子其 From 與 To 兩個位置的亂數對當 前的 Hash Index 各做一次 XOR 運算即可得到新的 Hash Index,而吃子只需對 被吃子在原位的亂數多做一次 XOR 即可得到結果(即提取棋子),因為盤面發 生變化時不需要重新計算所有棋子,而且以 Index 查表是 O(1)的複雜度,因 此 Zobrist Hash 法效能非常高,特別適合下棋程式。 而如果有兩個不同盤面剛好擁有相同的 Index,會被分配到同一個 Table 的位置,稱作 Collision (碰撞)。我們因為沒有紀錄完整盤面資訊,因此會從 中取出錯誤的資料進而導致搜尋結果錯誤。改良的方法是加長 Zobrist Hash 中棋子-位置亂數的長度以降低 Collision Rate,多出來長度的部分當作盤面 資訊存入 Transposition Table 中做為驗證碼 (Verification code),當查詢其中資 料時必須比對驗證碼是否正確,如果驗證通過則假設其資訊正確,由於此時 Collision Rate 已經非常低,因此若還是 Collision 則忽略其造成影響,如果驗 1. 證碼是 32 位元,則 Collision Rate 可以降為原來的232;若驗證失敗則對於撞 到的位置必須選擇取代的策略,常用的策略有深度優先以及始終覆蓋兩種, 深度優先可以確保較準確的資訊會留下來;始終覆蓋的想法來自於相同的盤 面常常是近期搜尋過的盤面,因而希望保留最近期的資料。 而我們使用兩層的結構分別做深度優先的取代策略以及始終覆蓋的策略 【7】,這樣可以同時兼顧其準確度(深度優先)以及命中率(始終覆蓋),圖 10 為 Transposition Table 結構示意圖。 另外利用 Transposition Table 也可以檢測重複盤面,以利用長將或長捉判 負規則迫使對方產生禁著而必須變著,因而創造優勢,相關方法將在 3.2.5 節 21.

(32) 說明。 Index. 分數. 深度. 驗證碼. 0 1 2 … n 圖 10 Transposition Table 示意圖. 2.5. 審局函數 審局函數通常包含了子力價值、位置分、自由度、棋盤控制以及特殊棋. 型、王的安全性等等,把上述所有項目做線性結合所得的分數就是審局分數。 審局亦有靜態審局以及動態審局,靜態審局就是各個項目的評分標準在 棋局的進行都是固定的;動態審局則是評分標準會隨著棋局的局勢而有所調 整。例如說兵卒在過河前的價值是很低的,但過河後威力會大增,並且配合 其它攻擊子力可以造成更大的攻勢;炮在開中局的影響力會大過殘局等等。 很顯然動態審局是更準確的,但相對的設計上以及計算成本也高得多。 審局是在搜尋樹的最底層執行的,因此是下棋程式運行最花時間的部分, 審局函數的準確度與運算成本無法同時兼顧,審局函數運行過久會導致搜尋 層數的降低,如何取捨審局準度與搜尋層數也是一個困難的議題。 要加速審局函數必須要有好的資料結構配合,並且配合著專家知識結合 程式設計能力。審局函數涵蓋了下棋程式所有的知識,可以說是程式的核心,. 22.

(33) 是影響程式棋力最關鍵的部分,好的審局函數配合搜尋演算法才能引領程式 走向勝利。 象眼的審局函數包含了子力價值、位置分數、特殊棋型以及自由度,並 且在開始搜尋前預先判斷盤面情勢,包含開中殘局程度、雙方攻勢等,並對 上述評分項目的參數相應做調整,但因為攻勢與開中殘局程度會隨著搜尋進 行而變化,因此參數調整動作應該在葉節點審局時做調整,但因為調整成本 很高,因此在搜尋深度不深的情況下可以免強接受。. 2.6. 後續發展 與西洋棋發展多年的大量研究比較起來,象棋研究可以說是鳳毛麟角,. 因此除了參考西洋棋的發展外,象棋獨有的部分可以說是還有很多研究空間 的。例如說象棋特有的將帥九宮格、士象的防守、炮與馬的獨特性,以及開 中殘局的策略,與人類比較起來,電腦不容易判斷「勢」與「先」 ,人類玩家 常常會使出棄子求先的妙著,也就是長遠的布局。另外在殘局的策略對於不 同的棋型其目標都不相同,例如優勢方要盡早破士象、劣勢方要盡力守和而 不要一味地謀求無意義的進攻而失去良好的防守陣型,一般通用的審局函數 很難適用於所有殘局狀況。這些問題目前象棋研究尚未對此有所發展。. 23.

(34) 第3章 方法與步驟 程式的棋力主要由兩個因素控制:搜尋深度與審局準確度。加強這兩個 項目便可以提升程式的棋力。 搜尋深度的提升可以使用 Pruning 策略(砍搜尋樹分支或減少部分分支搜 尋層數)來減少搜尋的節點數或單純改善程式效能以求相同時間下能搜尋得 更深。審局準確度則需要象棋的專家知識與程式結合。我們將針對這兩個部 分進行改良以加強棋力,本章將詳述相關改進方法。 我們的實驗測試平台為 Intel Core i7 3970X @3.5GHz、DDR3-1600 64G。. 資料結構的改進. 3.1. 首先是針對速度上的提升,程式的效能瓶頸通常發生在走步產生以及審 局函數,我們必須要有相對應的資料結構搭配以改善效能。. 3.1.1. 適用於象棋的 BitBoard. 與迴圈處理一維陣列比較起來,電腦更善於一次進行大量的位元運算, 即邏輯指令。傳統產生一個盤面走子方的合法走步需要循序讀取與判讀棋盤 上的一些位置,十分的花時間。現在在西洋棋上已經被大量使用的一種新資 料結構叫做 BitBoard (位棋盤)【8】 ,以二進位資料中 bit 的 0 或 1 來表示棋盤 上的格子有無棋子,西洋棋棋盤有 64 個格子,正好與現今電腦能一次處理的. 24.

(35) 單位資料 64 bits 相符,因此每個棋子以一個 64 bits 的資料表示其位置,並相 互進行邏輯運算可以快速檢查棋盤以產生合法走步或其它應用。 我們將此概念應用在象棋棋盤上,但因為象棋棋盤有 90 個格子,無法直 接像西洋棋一樣簡單表示,象眼的解決方式為使用 BitFile 與 BitRank,對於 每一行與每一列都使用條狀的 bit 結構表示,可用於加速產生炮與車的走步(參 閱 2.4.1 節),但功能也僅限於此,其它棋種並不適用,無法加速審局函數計 算,而且必須隨著搜尋樹展開更新多筆資料,無法完全發揮效能。 以象棋 90 個棋盤位置以楚河漢界分邊,我們使用兩個 64 bits 共 128 bits 的結構來表示棋盤,如圖 11 所示。 [High] 64 bits. [Low] 64 bits. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 圖 11 128 bits 象棋棋盤 BitBoard 示意圖 25.

(36) 由高低位組成的 128 bits 結構使用其中 90 個 bits 表示棋盤 90 個位置。當 棋盤的格子中有棋子時,該結構對應的 bit 就會被設為 1,圖中也表示棋盤格 子的編號,0~63 為 Low、64~127 為 High。外圍為牆壁,用以快速處理邊界。 分成 Low 與 High 可以快速檢查棋子是否過河,如果是棋子位置編號只 需檢查第 6 個 bit 是否為 1;如果是 BitBoard 則只需檢查 Low 或 High 是否不 為零即可。. 3.1.2. BitBoard 的棋盤表示法. 圖 12 為初始盤面代表紅方所有棋種及 Occupied 的 BitBoard 表示, Occupied 為所有棋種的聯集,是走步生成的依據,將在 3.1.6 介紹。 這些 BitBoard 資訊將隨著搜尋演算法對棋局的演示而不斷的更新維護。 使用了此結構即取代 Piece-Square Table,但仍然保留 Square-Piece Table,因 此更新成本的增加只有在 Occupied 的部分。 在許多應用上我們需要得到棋子位置編號的訊息 (原始 Piece-Square Table 的功能),因此必須把 BitBoard 結構中的 bit 轉換成位置編號,需要使用 LS1B 指令,該指令將在 3.1.4 節介紹。. 26.

(37) 圖 12 初始盤面紅方各個棋子對應的 BitBoard 示意圖. 3.1.3. 棋子表示法. 表 4 表示棋子編號的表示法,0~2 bit 為棋子種類(Type),3~4 表示棋子所 屬方,01 表示紅方、10 表示黑方,00 表示空白。. 27.

(38) 表 5 列出了各個棋種對應的編號,其中 0~7 不使用,輪紅下棋時對應的 值(side)為 8,輪黑為 16。舉例來說,黑方炮的編號二進位表示為 10110,前 面 10 表示黑方,後面二進位 110 是十進位的 6-炮的編號。 其中棋子類型為 0 的部分為代表該方所有棋子聯集(Occupied)的編號,在 走步產生以及棋型偵測上佔有十分重要的地位,在後面章節會詳細介紹。. 黑. 紅. Type. 表 4 棋子編號意義,最右邊為 bit 0. 棋子類型. 0. 1. 2. 3. 4. 5. 6. 7. 輪紅=8. All. 帥. 仕. 相. 傌. 俥. 炮. 兵. 編號. 8. 9. 10. 11. 12. 13. 14. 15. 輪黑=16. All. 將. 士. 象. 馬. 車. 包. 卒. 編號. 16. 17. 18. 19. 20. 21. 22. 23. 表 5 棋子編號方式. 如此編號方式特別便於取出以及判斷棋種,例如要取出我方的炮只要使 用 side+6 即可,不必去判斷我方是紅方還是黑方;走步產生時也只需要使 用 side+kind 的組合便可以讓紅黑雙方共用走步產生程式碼,提升程式可 維護性;要取出對方的王時使用(side^0x18)+1,使用 XOR 翻轉代表 side 的 3~4 bit 並加上棋種編號即可快速取得;要檢查一個棋子 pc 是否是我方的 只要使用(side&pc)!=0 即可;檢查某一方 side 的棋子於棋盤位置 sq 是否 過河則使用(sq^(sd<<2))&0x40 快速測試。 28.

(39) 3.1.4. Intrinsic 函數. 如果要在兩個 64 位元的資料間進行操作可以使用 C++的傳統語法來達 成,但需要多個步驟因而導致效能的下降,這也是象棋完全使用 BitBoard 困 難的原因之一。而我們使用了 intrinsic 函數解決這個問題。 intrinsic 函數是編譯器提供的 CPU 低階指令呼叫方式。CPU 包含了多種 指令集,除了常用的 x86、x64 外還有 MMX、SSE、AVX 等。一般在編譯 C++ 的程式時,編譯器通常只會使用到極少種的 CPU 指令,實際上 CPU 提供了 大量的原生指令,通常可以做到一般 C++語法很難表達的操作,進而大量提 升效能。使用 intrinsic 函數便能以函數呼叫的方式使用這些指令,但必須 Compiler 要支援。 如果要在兩個 64 bits 的資料間做 shift 的指令,以 C++的語法要實現是十 分複雜的,表 6 表示從 B 往左 shift 40 位元到 A 所需要的指令比較。. 一般 C++語法. 使用 intrinsic 函數. A = (A << 40) | (B >> 64-40);. A = __shiftleft128(B, A, 40);. B <<= 40;. B <<= 40;. 表 6 Intrinsic 指令與傳統 C++指令比較:128 bits Shift 為例 一般 C++語法至少會產生 4 條 CPU 指令,而使用 intrinsic 卻只需要 2 條 CPU 指令,在指令延遲減少、資料相依性降低的情況下可以有效增加整體速. 29.

(40) 度。 另外要從 BitBoard 的「佔有」意義轉換成位置編號的訊息必須使用 MS1B、 LS1B (Most/Last Significant 1 bit-取得最高、最低位元為 1 的 Index),也是 BitBoard 最常使用的操作。以 LS1B 為例,傳統作法必須獨立出最低位元為 1 的資料,然後對該資料做 Log2 運算以取得該 bit 代表的編號,當然 Log 運算 十分慢,亦有作法使用 Magic Hash 查表以加速【9】 。但 LS1B 以及 MS1B 都 有對應的原生指令可以使用:_BitScanReverse64 與 _BitScanForward64,如 圖 13 所示。此例中 MS1B 為 60、LS1B 會得到 1。. reverse. forward. 0001001101000001011000101100110001011100010 圖 13 MS1B、LS1B 示意圖 使用 Magic Hash 的 C++語法. 使用 intrinsic 函數. t = X & -X; //取出 LSB. //使用 BSF 指令,一步完成. //計算 Magic Hash 的 Index. _BitScanForward64(&result, X);. hIndex = t * Magic Number >> S; //查表取得結果 result = HashTable[hIndex];. 表 7 Intrinsic 指令與傳統 C++指令比較:LS1B 為例 表 7 表示 LS1B 所需要的指令比較,使用 Magic Hash 的 C++語法至少會 產生 5 條 CPU 指令,且需要查詢記憶體資料,而使用 intrinsic 卻只需要 1 條 CPU 指令,而 BSF (BitScanForward) 指令只需要 3 個 CPU 週期,與一個乘法 運算相當,速度比傳統做法快上不少。再者這是 LS1B 的例子,取出 LSB 有. 30.

(41) 較快的做法 X & -X,若是 MSB 則運算更加繁雜,如表 8 所示,更別說還有 後面 Magic Hash 的計算,而使用 intrinsic 函數仍然是只要一個指令解決。 U64 MSB(U64 X) { x |= x >> 32; x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; x |= x >> 1; return (x >> 1) + 1; } 表 8 MSB 的 C++程式碼【9】. 如果能善用 intrinsic 函數,可以有效的利用原生硬體指令的高效能,而 且程式碼也較為簡潔易懂。而由於我們 BitBoard 中 High 與 Low 大部分的運 算都是獨立不相關的,因此另外尚有許多 SIMD (Single Instruction Multiple Data) 指令集可以利用,本篇不再多介紹。. 3.1.5. BitBoard 的基本操作指令. BitBoard 中常用的基本指令除了上一節介紹的 Shift、MS1B、LS1B 外, 還有 AND、OR、NOT、XOR 等邏輯指令以及 Population count、BitSet、BitClr 等。 邏輯指令 AND、OR、NOT、XOR 只需對 High 與 Low 分別作對應的運 算即可,BitSet、BitClr 則分別使用 OR 及 AND 指令進行;Population count 則使用 intrinsic 指令__popcnt64,分別對 High 與 Low 計算後相加,表 9 列出了 BitBoard 基本指令與實際 C++程式碼對應,其中因為棋盤只使用了中 間 90 bits,因此 NOT 指令使用棋盤中 90 bits 為 1 的數字做 XOR 運算,如圖 31.

(42) 14 所示。而 BitTable 記錄了只有一個 bit 為 1 的陣列,於 BitSet、BitClr 使用, 圖 15 為 BitTable[43]的 BitBoard 示意圖。 在本資料結構的設計中,BitBoard 相關的運算於 High 與 Low 的部分幾 乎沒有資料相依性,可以充分利用 CPU 的 Pipeline 發揮性能。. 基本指令. C++ 程式碼. AND、OR、XOR. A.High & B.High. 以 AND 為例. A.Low & B.Low. NOT. X.High ^= ~0xFFC0180300600C01 X.Low ^= ~0x80300600C01803FF. Population count BitSet(n)、BitClr(n) 以 BitSet 為例. __popcnt64(X.High) + __popcnt64(X.Low) X.High |= BitTable[n].High X.Low |= BitTable[n].Low 表 9 BitBoard 基本指令. 圖 14 棋盤合法位置的 BitBoard,O 部分表示該 bit 為 1. 32.

(43) 圖 15 BitTable[43]的 BitBoard 示意圖 另外在使用 BitBoard 於象棋中之前必須預先建立 BitTable、BitRow、 BitCol 陣列以利運行,其分別代表棋盤中每個格子、Row 以及 Column 的 Mask, 如圖 16 為 Col[3]之 BitBoard 示意圖。. 圖 16 Col[3]的 BitBoard 示意圖 截至目前,我們已經準備好象棋 BitBoard 的基本操作與資料了,接下來 便可以開始進行實際的應用,發揮其強大的威力。. 33.

(44) 3.1.6. 以 BitBoard 加速走步產生. 走步產生也是下棋程式中效能瓶頸的一部分,如果使用一維陣列則必須 循序地去檢查棋盤格子,相當花時間,而使用 BitBoard 便可以快速的檢查。 以圖 17 的範例盤面為例,若要產生車的走步,必須檢查其四個方向第一 個能碰到的棋子的位置,BitBoard 的使用方法為: 1.. LS1B 取出黑車的位置編號。. 2.. 查表取得該車十字方向的 Bit Mask。(圖 17) 十字 Mask 不包含棋盤邊緣的原因是因為邊緣是否有子不會影響以下流 程計算的結果。. 3.. 將這個 Mask 與棋盤的 Occupied 做 AND 運算。(圖 18). 4.. 做完 AND 運算的結果經過 Magic Hash 運算後得到一個 index,查表便 可以取得車能走到的位置。(圖 19). 5.. 其結果與「空白」的 BitBoard (即 Occupied 做 NOT 運算)做 AND 運算可 得走子步(圖 20). 6.. 其結果與對手的 Occupied 做 AND 運算可得吃子步(圖 21). 7.. 5.與 6.得到的結果以 LS1B 配合 BitClr 便可以取出合法走子的目標。. 34.

(45) 相. 帥. 俥. 仕. 兵. 兵. 車. 卒 卒. 士. 車. 象. 圖 17 車的 Mask. 象. 將. 象. 圖 18 與 Occupied 做 AND 運算後的結果 相. 帥. 俥. 仕. 相. 仕 炮 兵. 炮. 傌. 兵. 車. 馬 兵. 卒. 卒. 卒 士. 車. 馬. 卒. 象. 兵. 兵. 兵 卒. 將. 炮. 傌. 卒. 卒. 車. 兵. 馬 兵. 相. 炮 兵. 車. 仕. 仕. 炮. 傌. 士. 帥. 俥. 仕 炮. 兵. 相. 相. 象. 將. 象. 圖 19 查表得到的走步資訊. 35.

(46) 相. 帥. 俥. 仕. 相. 相. 俥. 仕 炮 兵. 兵. 傌. 兵 兵. 馬. 車. 車. 馬 卒. 卒. 卒 士. 象. 將. 兵. 兵 卒. 士. 炮. 傌. 卒. 卒. 相. 炮 兵. 兵. 仕. 仕. 炮. 車. 帥. 車. 象. 圖 20 車的走子步. 象. 將. 象. 圖 21 車的吃子步. 與傳統的走步生成比較起來,使用 BitBoard 可以化簡一切循序繁瑣的檢 查,比方說循序檢查馬腳的位置、炮架、是否是空格、吃到的是否是對方棋 子等等,只需要以 Mask 取出棋盤的部分資訊查表並作 AND 運算即可,AND 運算可以平行的大量處理逐格檢查的動作,而且全部都是邏輯運算完成,可 以省去大量的循序判斷式增進效能。 車炮的走子步規則完全相同,因此可以共用一張表,而吃子走步則必須 要查不一樣的表,但上述的流程是完全一樣的。 將帥、兵卒、士的走步規則只依據自身所在位置,與棋盤狀態無關,因 此只需根據自己所在位置查對應的表並跳至步驟 5 即可完成。 馬、象則需要檢查其馬腳處以及象眼處是否有棋子,因此在步驟 2 的部 分使用不同的 Mask,其餘步驟均相同,圖 22 為紅方傌與黑方象的 Mask 示 意圖。. 36.

(47) 相. 帥. 俥. 仕. 相. 仕 炮 兵. 兵. 炮. 傌. 兵. 車. 馬 兵. 卒. 卒. 卒 士. 車. 象. 將. 象. 圖 22 馬與象其拐腳處的 Mask 另外在搜尋過程中經常需要判斷是否被將軍,用來檢查走步合法性,因 為一個著法導致自己呈現被將軍狀態是不允許的。象棋中,若一方沒有合法 著法則做負,包含棋子全部被堵死(圖 2)或無法解將。傳統一維陣列必須一一 檢查對方合法著法中是否有一個著法的目標點為我方王,以及是否將帥對臉, 若對方合法著法有 40 步則需要做 40 次檢查,十分費時。而我們可以利用 BitBoard 快速做檢查。 比較基本的 BitBoard 用法是產生對方所有子的合法走步 BitBoard (如圖 21),然後將它與王的 BitBoard 做 AND 運算,若結果不為零,則表示被將軍, 注意這邊不需要把圖 21 的吃子步 BitBoard 以 LS1B 拆出來,因為我們不需要 知道目標位置的 Index,只需要知道它是否涵蓋了王的位置即可,因此 AND 運算可以快速達成此目標。此作法需要對對方能過河的兵種各做一次檢查以 及檢查將帥對臉。 進階的做法是反向檢查,我們可以把目標王假設成各種兵種,然後產生 37.

(48) 該兵種反向的吃子著法,若該反向吃子著法能吃到該兵種的棋子,則表示檢 查成立。如圖 23 所示,若要反向檢查將是否被紅傌將軍,我們可以 1.. 取得將在該位置反向傌的 Mask (圖 23)。. 2.. 以 Magic Hash 查表取得要檢查是否有傌的位置(圖 24). 3.. 與對方傌的 BitBoard 做 AND 運算,若不為零則表示檢查成立。. 如此做法對於紅方的兩隻傌從原本需要分別檢查兩隻傌變成只需要反向 檢查一次,因為是反向檢查,第 1 步驟傌的 Mask 會與正常傌的 Mask 不相同。 而相應地,對於炮、兵都是一樣的處理方式,對於兵原本要做五次檢查也是 可以一次就完成,加速不少。 相. 帥. 俥. 仕. 相. 相. 帥. 俥. 仕. 仕. 相. 仕. 炮. 炮 兵. 兵. 兵 兵. 馬. 馬. 兵. 兵 傌. 傌. 炮. 兵. 傌 傌. 象. 將. 士. 炮. 象. 圖 23 反向傌的 Mask. 兵. 象. 將. 士. 象. 圖 24 要檢查是否有傌的位置. 比較特別的是,將帥對臉的檢查可以與車的檢查同時進行, 因為其本質 是完全相同的,步驟如上所示,在步驟 3 的部分改成對紅俥與帥 OR 在一起 的 BitBoard 做一次 AND 運算即完成。 38.

(49) 表 10 比較各種做法需要的檢查運算次數,上述進階作法能最大化發揮 BitBoard 的優勢。 檢查運算次數. 說明. 傳統作法. 約 40 次. 相當於一手的平均分支因數. 基本作法. 12 次. 帥、車馬炮各 2、兵 5 隻. 進階作法. 4次. 帥車、馬、炮、兵各 1 次. 表 10 檢查將軍的實作方法比較. 3.1.7. Magic Hash. 3.1.6 節中我們提到了各種 Mask,Mask 與各種 BitBoard 做 AND 運算的 實質意義是「取得 Mask 對應在 BitBoard 上的資訊」 ,這些取得的資訊將用於 走步產生等用途。BitBoard 與 Mask 做 AND 運算的結果會讓我們想要取得的 資訊分布在 Mask 對應的一些 bits 上,我們必須把這些 bits (部分棋盤資訊) 「濃 縮」成一個唯一的 index 以查表,這個動作稱作 Magic Hash,其公式為 (Value.H * Magic Number1 ^ Value.L * Magic Number2) >> Constant. 其中 Value 為 Mask 與棋盤資訊做 AND 運算的結果,兩個 Magic Number 與 Constant 為 64 bits 的數值,其配對可以把在定義域範圍內的所有 Value 唯 一的對應到一個 index,其中 Mask 的 Population count 以 64 減去後即為 Constant 的值,以圖 17 的車在該位置為例,其 Mask 的 Population count 為 13 (代表我們想取得棋盤中 13 個位置的資訊)、Constant 值為 51,因此對應的 Table 大小為 213 個元素。以車為例,車在棋盤上不同位置都有不同的 Magic Number 與 Constant 的配對。. 39.

(50) Magic Number 必須由試誤法不斷尋找,Constant 決定了對應域的 Table 要開多大,由於只要 Value 唯一的對應到 Table 上即可,因此可以開比較大的 Table,以減少對應域碰撞的機率。雖然比較浪費空間,但相對的 Magic Number 會比較容易找到。 而近期 Intel 發展了 AVX2 指令集,其中包含了 PEXT (位抽取)指令,其 作用如圖 25 所示,利用原始資料與要抽取 bits 的 Mask,可以直接將其進行 濃縮,產生最後的結果。這個動作與本節講的 Magic Hash 不謀而合,把盤面 資訊與十字的 Mask 做 PEXT 運算可以直接得到最終的 index,省去了 AND 運算以及 Magic Hash 公式運算的步驟,如此更能提升效能,並且省去了尋找 Magic Number 的麻煩。但由於需要最新一代的 CPU 才有 AVX2 指令集,我 們為了讓程式維持在不同電腦硬體間的良好相容性,因此沒有採用 PEXT 指 令。. 圖 25 AVX2 指令 PEXT 運作示意圖【11】. 40.

(51) 演算法改進. 3.2. 演算法是下棋程式中運作最為複雜的部份,雖然各種新算法與資料結構 不斷被提出來,但在各種算法與資料結構間的複雜交互作用下,時常會藏匿 一些錯誤或非預期的情況,我們到目前為止還很難深入瞭解所有細節(2.3.4 節提到的水平線效應就是一例;與 Transposition Table 的交互作用產生的錯誤 更是不好解決),因此我們針對一些觀察出來的問題嘗試尋找改進方案。. 走步排序策略. 3.2.1. 以 Alpha-Beta 為基礎的演算法十分仰賴好的走步排序策略來提升砍分支 的效率(參閱 2.3.2),象眼實作的走步排序策略共有 4 個,以下按照順位表示: 1.. 記錄在 Transposition Table 中的最佳著法. 2.. 優良的吃子走步,以 MvvLva 排序. 3.. 2 個殺手著法 (Killer Heuristic). 4.. 非吃子步與較差的吃子走步,以 History Heuristic 排序. Transposition Table 中的最佳著法是之前搜尋過當前盤面時記錄下的最佳 著法,當對同一個盤面有更深的搜尋需求時,該著法理論上有非常大的機率 仍然是最佳著法,因此優先順位最高,並且 PVS 的運作(2.3.3 節)非常仰賴這 個策略。 MvvLva 是 Most valuable victim / Last valuable attacker 的縮寫,表示用最. 41.

(52) 小價值的子吃到最大價值的子應該要最優先,並同時考慮了如果被吃的目標 是有保護的,則應該還要多考慮攻擊子會被反吃的因素。另外我們實驗發現, Mvv 是比 Lva 重要的,因此先以 Mvv 做排序,內部再以 Lva 排序,以此策略 改良後,在實際對戰盤面上約可以減少 10%以上的搜尋節點數。而一些乍看 之下不好的吃子著法(例如用車吃一個有保護的馬)則留至走子步一起考慮。 接下來是殺手著法,也就是 Killer Heuristic,該結構記錄了在同樣深度的 兄弟節點中造成 Beta Cut 的走子步,殺手的意義即在此。這個策略假設在相 同搜尋深度下盤面通常是比較接近的,因此如果有一個著法在其兄弟盤面中 造成 Beta Cut,則在當前盤面也造成 Beta Cut 的機率是比較高的,因此也要 優先嘗試。但由於記錄這個著法的兄弟盤面不會完全與當前盤面一樣,因此 著法有可能是非法步,需要先檢查著法是否合法。而殺手著法只記錄走子步, 因為吃子走步會讓盤面發生巨大變化,這與 Killer Heuristic 的最初概念是相 違背的。 最後是走子步與不好的吃子步,這部分以 History Heuristic 排序,History Heuristic 假設在一定的層數內、棋盤的「勢」不會發生太大的變化,因此在 不同的深度下,其造成 Beta Cut 的著法仍然有可能是當前盤面好的著法,並 以此依據進行排序剩下的走步。一般 History Heuristic 記錄的是一個著法 (From、To) 造成 Beta Cut 的次數,深度越深的記錄權重越高,然後在其它節 點產生出的走步便參照該權重表進行排序。而基於與 Killer Heuristic 相似的 前提假設,Hitsory Heuristic 一樣只記錄走子步。 圖 26 是 Killer Heuristic 與 History Heuristic 示意圖,Killer Heuristic 運作 42.

(53) 在相同深度的兄弟節點中;History Heuristic 則運作在不同深度的節點中。該 例中,若[馬2進3]造成 Beta Cut,則在兄弟節點與不同深度的節點中該走步 排序的優先權會提高。. 圖 26 Killer Heuristic (左)與 History Heuristic(右)比較 而我們發現,象棋的棋子在著法的 From、To 之間沒有絕對的關聯性。 例如說車與炮的著法在只有 From 與 To (棋子位置變化)的資訊下是無法分辨 的,顯然的這兩個兵種走一樣的著法造成的影響相差甚遠。 因此我們改良了 History Heuristic 的策略,從 From、To 改成 Pieces、To, 記錄了什麼棋子移動到了什麼位置,如此一來便可以精確的記錄該走步對盤 面的「勢」造成了什麼影響,更符合 History Heuristic 的精神,以此策略在實 戰盤面中能減少約 7%搜尋節點數,因為 History Heuristic 是順位比較低的走 步排序策略因此效能增加沒有 MvvLva 來的多。 43.

(54) 3.2.2. Late move reduction (LMR). 相較於吃子步,走子步對於盤面的影響是比較沒這麼劇烈的,在 2.3.4 節 中,我們討論到了水平線效應會讓搜尋樹在含有迫著或緩著的分支路線得到 的資訊量下降,因而造成棋力不穩定,通常配合延伸搜尋深度的作法以讓含 有迫著或緩著的分支能得到相對應的資訊補償,然而延伸深度會大幅增加計 算成本,且延伸的量不足的情況下無法完全補足減少的資訊量。 圖 27、圖 28 為水平效應以及使用延伸策略後的影響示意圖,左三角形 為搜尋樹、右三角形為獲得的資訊量,水平線效應就是在某些含有迫著或緩 著的盤面上實際獲得的資訊量是比較少的,而圖 28 使用了延伸策略後可以緩 解部分分支資訊量的不足,但會造成搜尋樹的暴漲。. 圖 27 水平線效應. 圖 28 使用延伸策略後. 44.

(55) 因此,我們再使用了與延伸深度相反的作法:減少較平靜的盤面搜尋深 度,目的是要盡量維持搜尋樹中所有路線獲得的資訊量一致,並以此空出來 的計算時間再使整體搜尋樹的平均深度加深以提升整體棋力。. 圖 29 使用 LMR 後 較平靜的盤面通常是走子步所造成,而走子步通常在走步排序順位是比 較後面的,這也是 Late move reduction【10】名稱的由來。 圖 29 為使用 LMR 後的影響示意圖,對於較平靜的盤面減少搜尋深度後, 盡可能的讓搜尋樹在各個分支獲得的資訊量是接近的,相對的整體資訊量會 下降,但因為 LMR 減少了部分分支的搜尋深度,因此實驗顯示在相同的時間 下可以加深約 2 層的整體搜尋深度,補足整體資訊量下降的問題。 LMR 另一個層面的意思是,對於走步排序較後面的分支認為比較不重要 (不太可能會成為 PV 路線,即不太可能大於當前的 Alpha 值),因此如果發現 被 LMR 減少搜尋深度的分支成為 PV 路線時應該要以完整的深度重新搜尋以 得到更準確的分數。. 45.

(56) LMR 並不是所有情況下都適用,為了維持各個分支獲得資訊量的平衡, 我們必須根據盤面狀況或走步類型作出選擇,表 11 定義了不適合使用 LMR 的走步類型: 規則. 說明. 吃子步. 吃子步屬於迫著,因為對方通常必須反吃回 來,選擇單純,資訊量不足. 將軍步或解將步. 屬於迫著與緩著,資訊量不足,不宜使用. 剩餘搜尋深度小於 3. 剩餘資訊量已經很低,不宜再刪減. 兵卒的走步. 兵卒走步屬於比較緩慢的走步,常常容易得 不到資訊,因此不使用 LMR. 盤面狀態相當危險. 若王的安全性危急,則不能使用 LMR,因為 容易錯判情勢. 表 11 不適合使用 LMR 的狀況定義. 3.2.3. PVS 的改良. 承 2.3.3 節,PVS 演算法假設每個節點的第一個子節點一定是 PV 節點, 因此對第一個子節點總是使用完整的 Alpha-Beta Window 搜尋。然而我們觀 察發現,在 PVS 樹中,Alpha 節點與 Beta 節點是交互存在的,意即 Alpha 節 點的父節點一定是 Beta 節點,因此 Alpha 節點的第一個子節點一定會回傳小 於 Alpha 值的分數,因此我們對於 Alpha 節點的第一個子節點便可以使用 (Alpha, Alpha+1) 的 Zero Window 去猜測並驗證他不會超過 Alpha,而不需要 用完整的 Alpha-Beta Window,從而加速其運行。 參閱 2.3.3 節的 PVS 虛擬碼,改良後的修正只要把斷走步是否是排序中 第一個順位的判斷刪除掉即可。 然而在搜尋完這個節點之前,我們無從得知當前節點的類型,因此我們 46.

(57) 永遠猜測當前節點是 Alpha 節點,始終以 Zero Window 搜尋第一個子節點。 由於 Alpha 節點在搜尋樹中佔了絕大部分的比例,因此永遠都猜 Alpha 節點 其命中率是高的,實驗顯示,使用此方法可以減少約 5%的搜尋節點數。. 3.2.4. 控制格計算. 在象棋的審局函數中,棋子的控制格是一項非常重要的資訊,其中可以 延伸包括保護與威脅關係、自由度計算、王的安全性等等,這些應用將在 3.3 節做介紹,我們這節介紹控制格如何產生。 有了 BitBoard 的資料結構後,我們對於棋盤的資訊處理從循序處理變成 單步處理,利用了平行位元指令的優勢,這些方法對於控制格計算更是再適 合不過了。. 圖 30 範例中局盤面 47.

(58) 而控制格的產生十分簡單,只要對於所有棋子產生其吃子範圍的 BitBoard (不需要用 LS1B 拆出來),然後通通用 OR 運算結合即可,而且這些 中間產生吃子步的 BitBoard 資訊更可以直接利用來產生 From、To 配對的實 際走子表示,當然其中只有炮的走子步需要重新計算,因為炮的走子與吃子 是不同的。如圖 30 是一個中局盤面,分析其黑方的控制格與紅方的控制格可 得到如圖 31 與圖 32 的 BitBoard 資訊。. 車. 象. 士. 將. 士. 馬 卒. 象. 士. 將. 士. 馬. 馬. 卒 包. 車. 車. 包. 馬. 卒. 卒. 卒 包. 象. 俥. 車. 包. 卒. 象. 俥. 兵. 兵 傌. 卒. 炮. 仕. 帥. 兵 傌. 炮. 傌 相. 兵. 兵. 炮. 相. 相. 圖 31 紅方的控制點. 仕. 帥. 兵 炮. 傌. 俥 仕. 卒. 俥 仕. 相. 圖 32 黑方的控制點. 有了這些資訊後,要分析諸如黑方 2 路線的包被紅方俥拴住或是紅方窩 心馬的出口被黑方過河卒控制住都變得容易。而要檢查一個棋子或區域是否 被保護或威脅,則只要把棋子或一個區域的 BitBoard 對己方(保護)或敵方(威 脅)控制點的 BitBoard 做 AND 運算即可立即得到結果,如圖 33、圖 34 所示。. 48.

(59) 車. 象. 士. 將. 士. 馬 卒. 象. 士. 將. 士. 馬. 馬. 卒 包. 車. 車. 包. 馬. 卒. 卒. 卒 包. 象. 俥. 車. 包. 卒. 象. 俥. 兵. 兵 傌. 卒. 炮. 仕. 帥. 兵 傌. 炮. 傌 相. 兵. 兵. 炮. 相. 相. 圖 33 紅方受到保護的棋子. 仕. 帥. 兵 炮. 傌. 俥 仕. 卒. 俥 仕. 相. 圖 34 黑方受到紅方威脅的棋子. 當然這些只是初步的資料,以圖 32 為例,黑方包在 2 路線紅方陣地上有 控制區域,但實際上如果黑包打出去的話黑車會被紅俥吃掉,這類情況都還 是需要仰賴搜尋演算法去進一步分析。 由於控制格需要產生雙方的吃子走步 BitBoard,而只有當前走子方的吃 子步資訊資訊可以利用於走步產生,因此計算控制格的成本稍高,但其應用 價值可以彌補這一點。. 3.2.5. 長捉的偵測. 在象棋的對戰中,棋規也是一個相當重要的因素,在高手過招時,如果 能善用棋規去限制對手,便有可能達到意想不到的效果。 在台灣的象棋比賽使用的是亞洲棋規,包含了長將、長捉等禁手,並且 其中長將嚴重性大於長捉、在雙方不違例或雙方違反同一棋規而盤面循環時 49.

參考文獻

相關文件

method void setInt(int j) function char backSpace() function char doubleQuote() function char newLine() }. Class

此極限便是觀察分子或者分母誰「跑得比較快」。若是分子 趨近無窮大的速度快很多,則極限為無窮大 ,若是分母快很 多,則極限便是

[r]

這次的實驗課也分成兩個禮拜完成,在實驗過程中我們幾乎都很順利完成了課堂上要達到的目標

營建工程系 不限系科 工業工程與管理系 不限系科 應用化學系 不限系科 環境工程與管理系 不限系科 工業設計系 不限系科. 景觀及都市設計系

◦ 金屬介電層 (inter-metal dielectric, IMD) 是介於兩 個金屬層中間,就像兩個導電的金屬或是兩條鄰 近的金屬線之間的絕緣薄膜,並以階梯覆蓋 (step

強制轉型:把 profit轉換成double的型態

int main(int argc, char** argv).