• 沒有找到結果。

資料結構相關研究

BitBoard 是一種高效率的資料結構[5],概念是用一串位元來表示棋盤上單 一兵種的狀態,用 1 與 0 表示棋盤上指定的位置有無該兵種,西洋棋棋盤剛好 是 8×8=64 格,與 64 位元 CPU 相符,目前 BitBoard 已經廣泛使用於西洋棋上。

圖 2.1 西洋棋初始盤面 圖 2.2 黑方小兵所佔的位置 圖 2.1 為一個西洋棋初始盤面的棋盤,圖 2.2 為圖 2.1 中黑方的小兵所佔的 位置,一共有 64 格,每格與圖 2.1 的對應位置一樣。黑色格子為有黑方小兵的 位置,白色格子為沒有,使用一個 64Bits 的無負號整數來儲存圖 2.2 的資訊,

黑色格子用 1 代表,白色格子用 0 代表,64 格之中越靠近右上角的部分為越低 的位元,在二進位中越靠近右邊。圖 2.2 的內容用二進位來表示的話為:

0000 0000 0000 0000 0000 0000 0000 0000

總共有 64 位數,很長很難看清楚,所以之後一律使用十六進位表示,圖 2.2 的 內容用十六進位表示的話為:0x000000000000FF00。

一個變數只能存一種兵種,西洋棋黑白雙方各有六種兵種:國王(King)、

皇后(Queen)、城堡(Rook)、主教(Bishop)、騎士(Knight)、小兵(Pawn),所以需 要:K、Q、R、B、N、P 存黑方的子、k、q、r、b、n、p 存白方的子,以下為 圖 2.1 中各兵種所存的資訊:

K = 0x0000000000000008 k = 0x0800000000000000 Q = 0x0000000000000010 q = 0x0100000000000000 R = 0x0000000000000081 r = 0x8100000000000000 B = 0x0000000000000024 b = 0x2400000000000000 N = 0x0000000000000042 n = 0x4200000000000000 P = 0x000000000000FF00 p = 0x00FF000000000000

表 2.1 西洋棋初始盤面各兵種資訊

有了這些資訊就可以用邏輯運算很快的產生其他必要資訊,例如想要取得 盤面上所有黑子的位置(存入變數 black),只需將黑方的六種兵種做 OR 的運算:

black = K | Q | R | B | N | P 即可,以表 2.1 為例,black = 0x000000000000FFFF,

所有白子的位置則為:white = k | q | r | b | n | p,white = 0xFFFF000000000000,

棋盤上有子位置為 occupied = black | white,所有空格 empty = ~(black | white)。

棋盤本身也有一些固定的資訊會常使用到,例如棋盤上指定的列或行,以 下為兩個範例,這裡的列由下往上數(rank1~rank8),行由左往右數(filea~fileh):

圖 2.3 列遮罩範例 rank2 圖 2.4 行遮罩範例 fileb 各列各行的資訊如表 2.2,以下皆為常數:

Rank1 = 0XFF00000000000000 filea = 0x8080808080808080 Rank2 = 0X00FF000000000000 fileb = 0x4040404040404040 Rank3 = 0X0000FF0000000000 filec = 0x2020202020202020 Rank4 = 0X000000FF00000000 filed = 0x1010101010101010 Rank5 = 0X00000000FF000000 filee = 0x0808080808080808 Rank6 = 0X0000000000FF0000 filef = 0x0404040404040404 Rank7 = 0X000000000000FF00 fileg = 0x0202020202020202 Rank8 = 0X00000000000000FF fileh = 0x0101010101010101

表 2.2 西洋棋行列遮罩資訊

第二節 西洋棋 BitBoard 產生小兵走步

先介紹產生黑方小兵候選著手的方法,小兵第一步可以向前走一格或兩格,

之後每次都只能向前走一格,但是若斜前方有敵方的子則可以吃掉對方,這裡 先介紹比較重要的吃子部分再介紹走子部分。

當小兵往右斜前方移動吃子時,他的位元會左移七格,使用位元左移運算 子「<<」計算,這裡稱呼它為「位元偏移量」。圖 2.5 中左邊的黑色小兵,原本 的 位 置 為 0x0000200000000000 若 吃 右 斜 前 方 的 子 就 會 變 成

0x0000200000000000 << 7 得到 0x0010000000000000,第一次看到這樣的計算 可能會覺得很複雜,可以看圖 2.6 的位元對照表,0 代表二進位中最右邊的位數,

數字越大越左邊,黑色小兵原本在 45 號位置,向右斜前方移動後變到 52 號位 置,數字增加了 7,這樣就能了解為什麼要使用「<< 7」了,能很簡單的推導 出,吃左斜前方的子會變成「<< 9」。

7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24 39 38 37 36 35 34 33 32 47 46 45 44 43 42 41 40 55 54 53 52 51 50 49 48 63 62 61 60 59 58 57 56

圖 2.5 小兵吃子示意圖 圖 2.6 位元對照表

產生小兵吃子步的方法為,若小兵右斜前方或左斜前方的位置是白子即可吃:

PawnCaptures(P) = ( P << 7 | P << 9 ) & white

這裡有一個非常重要的關鍵:若小兵在邊界(圖 2.5 紅色框框部分),要如何 不讓他出界?圖 2.5 右邊的黑色小兵(40 號位置)若使用「<< 7」計算的話就會 走到錯誤的位置 47 號,所以上一段的程式碼要修正為,若小兵在最右邊那行

(fileh)則不用判斷右前方,左邊也做相同修正,經過精簡修正後的程式碼如下:

PawnCaptures(P) = (((P& ~fileh)<<7 | (P&~filea) <<9)) & white

而小兵走子步的部分,由於小兵只會向前走,走到底時會變成其他棋子,

所以不用考慮邊界問題,只要記得小兵第一次可以選擇走一步或兩步就好:

PawnMoves(P) = (P<<8 | (P&rank7)<<16) & empty

要注意的是,黑方和白方的小兵走步方向是不同的,白方需要再另外寫一次。

第三節 西洋棋 BitBoard 產生國王走步

圖 2.7 國王走步示意圖

接著是較為複雜的國王,國王的移動範圍為周圍八個位置,圖 2.7 標示了

國王移動範圍裡,每個位置的位元偏移量,以下為範例函式:

KingMoves(K) =

(K & ~rank8) >> 8 | (K & ~rank1) << 8 | (K & ~fileh) >> 1 | (K & ~filea) << 1 | (K & ~(rank8 | filea)) >> 7 | (K & ~(rank8 | fileh)) >> 9 |

(K & ~(rank1 | fileh)) << 7 | (K & ~(rank1 | filea)) << 9

產生國王走步的函式,參數 K 為國王的位置。

先考慮向上一格「>>8」的位置,國王只有在 rank8(最上面一列)時走這步 會出界,用(x & ~rank8) >> 8 來避免這個出界問題,國王在 rank8 時就不能向上 走了。相對位置為「<<8」的 rank1,再來就是右邊一格的「>>1」在 fileh(最右 邊一行),和相對的「<<1」在 filea。

接下來是對角的四個位置,國王在最上面一列和最左邊一行時,走左上角 的「>>7」都會出界,所以當國王在(rank8 | filea)時都不能走「>>7」,將邏輯整 理好後就變成(x & ~(rank8 | filea)) >> 7,以此類推可以找出另外三個角落的邏 輯,將八個結果 OR 起來就可以產生出國王的所有走步了。

第四節 西洋棋 BitBoard 產生騎士走步

圖 2.8 騎士走步示意圖

騎士走步處理方法跟國王走步很像,如下:

KnightMoves(K) =

(K & ~(rank1 | filegh)) << 6 | (K & ~(rank8 | filegh)) >> 10 | (K & ~(rank1 | fileab)) << 10 | (K & ~(rank8 | fileab)) >> 6 | (K & ~(rank12 | fileh)) << 15 | (K & ~(rank12 | filea)) << 17 | (K & ~(rank78 | fileh)) >> 17 | (K & ~(rank78 | filea)) >> 15 |

產生騎士走步的函式,參數 K 為騎士的位置。

filegh = fileg | fileh 為最右邊兩行,其他以此類推。大致上跟國王的差不多,

只是騎士橫向或直向可以跳兩格,所以邊界處理變兩層。西洋棋其他子能依以 上三種方法推出來。

第三章 搜尋演算法相關研究

相關文件