“JavaScript中國象棋程序” 這一系列教程將帶你從頭使用JavaScript編寫一個中國象棋程序。這是教程的第2節。
這一節介紹棋子的走法。改進之后的程序,可以連續走動紅方棋子,但要符合象棋規則。黑方(電腦方)暫時還不能回應。
2.1、走法表示
一個走法包括起點和終點,分別用sqSrc和pcDst表示一維棋局數組中的起點和終點。很容易想到,使用數組[sqSrc, pcDst]表示一個走法。但程序將來會處理大量的走法,使用數組表示走法太浪費內存。由於sqSrc和pcDst都是不超過255的整數(因為一維棋盤數組的大小是256),可以將sqSrc和pcDst壓縮到一個整數中,算法如下:
function MOVE(sqSrc, sqDst) { return sqSrc + (sqDst << 8); }
如果想從壓縮后的整數中,重新獲取起點和終點,可分別使用以下函數:
// 獲取走法的起點 function SRC(mv) { return mv & 255; } // 獲取走法的終點 function DST(mv) { return mv >> 8; }
2.2、判斷目標位置是否有本方棋子
如果目標位置有本方棋子,那么該走法肯定是不合法的。
設置一個變量pcSelfSide。走棋方為紅方時pcSelfSide=8,走棋方為黑方時pcSelfSide=16。在上一節提到過:
紅方棋子 & 8 = 1
黑方棋子 & 16 = 1
(終點棋子 & pcSelfSide) != 0 就說明終止位置有本方棋子,走法不合法。
另外,我們設置七個全局變量:
var PIECE_KING = 0; // 將 var PIECE_ADVISOR = 1; // 士 var PIECE_BISHOP = 2; // 象 var PIECE_KNIGHT = 3; // 馬 var PIECE_ROOK = 4; // 車 var PIECE_CANNON = 5; // 炮 var PIECE_PAWN = 6; // 卒
來對棋子編號。例如,只要滿足:
棋子數值 - pcSelfSide = PIECE_KING
那么這個棋子就是將(帥)。使用同樣的方法,可以判斷出其他六種棋子。
2.3、校驗將(帥)的走法
將的走法有四個方向,如下圖所示:
在一維數組中,1、2、3、4幾個方向的起點和終點分別滿足以下等式:
pcDst = sqSrc - 16
pcDst = sqSrc - 1
pcDst = sqSrc + 1
pcDst = sqSrc + 16
將(帥)的走法需要滿足下面這兩個條件:
(1)、終點sqSrc位於九宮
可使用一個輔助數組,標識出所有合法的位置。
var IN_FORT_ = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
判斷是否在九宮,使用函數
function IN_FORT(sq) { return IN_FORT_[sq] != 0; }
(2)、pcDst - sqSrc等於-16、-1、1、16其中的一個。
設置一個數組16×32的一維數組
var LEGAL_SPAN = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
在該數組中,(-16 + 256)、(-1 + 256)、(1 + 256)、(16 + 256)四個位置是1,其他位置都不是1。校驗函數如下
function KING_SPAN(sqSrc, sqDst) { return LEGAL_SPAN[sqDst - sqSrc + 256] = = 1; }
2.4、校驗士的走法
士的走法也是四個方向,如下所示
士的4個方向分別滿足以下等式:
pcDst = sqSrc - 17
pcDst = sqSrc - 15
pcDst = sqSrc + 15
pcDst = sqSrc + 17
士的走法需要滿足下面這兩個條件:
(1)、終點sqSrc位於九宮
這與將(帥)的判斷方法相同
(2)、pcDst - sqSrc等於-17、-15、15、17其中的一個
在上面提到的輔助數組LEGAL_SPAN中,(-17 + 256)、(-15 + 256)、(15 + 256)、(17 + 256)四個位置是2,其他位置都不是2。檢驗函數如下
function ADVISOR_SPAN(sqSrc, sqDst) { return LEGAL_SPAN[sqDst - sqSrc + 256] == 2; }
2.5、校驗象的走法
象的四個走法如下圖所示,黑三角是相應的象眼位置
象的4個方向分別滿足以下等式:
pcDst = sqSrc - 34
pcDst = sqSrc - 30
pcDst = sqSrc + 30
pcDst = sqSrc + 34
象的走法需要滿足下面三個條件:
(1)、象不能過河
在程序中,棋局被表示為大小為256的一維數組,一半棋盤位於0到127,另一半位於128到255。
128的二進制是1000 0000,右起第八位是1。128到255這些數的二進制,右起第8位都是1;0到127這些數的二進制,右起第8位都是0。因此,如果象沒過河,也就是pcDst和sqSrc位於相同的一半棋盤,那么
(sqSrc ^ sqDst)二進制的右起第八位是0,((sqSrc ^ sqDst) & 256)等於0。判斷函數如下:
// 如果從起點sqSrc到終點sqDst沒有過河,則返回true;否則返回false function SAME_HALF(sqSrc, sqDst) { return ((sqSrc ^ sqDst) & 0x80) == 0; }
(2)、pcDst - sqSrc等於-34、-30、34、30其中的一個
在上面提到的輔助數組LEGAL_SPAN中,(-34 + 256)、(-30 + 256)、(30 + 256)、(34 + 256)四個位置是3,其他位置都不是3。檢驗函數如下
function BISHOP_SPAN(sqSrc, sqDst) { return LEGAL_SPAN[sqDst - sqSrc + 256] == 3; }
(3)、象眼無棋子
象眼位於sqSrc和sqDst的中點,判斷(sqSrc + sqDst)/2 的位置是否有棋子即可。
2.6、校驗馬的走法
馬的8個走法如下圖所示,黑三角是相應的馬腳位置
馬的8個方向滿足以下等式:
pcDst = sqSrc - 33
pcDst = sqSrc - 31
pcDst = sqSrc - 18
pcDst = sqSrc + 14
pcDst = sqSrc - 14
pcDst = sqSrc + 18
pcDst = sqSrc + 31
pcDst = sqSrc + 33
對應的馬腳分別位於:
sqSrc - 16
sqSrc - 16
sqSrc - 1
sqSrc - 1
sqSrc + 1
sqSrc + 1
sqSrc + 16
sqSrc + 16
馬的走法需要滿足下面兩個條件:
(1)、pcDst - sqSrc等於-33、-31、-18、14、-14、18、31、33其中的一個
(2)、對應馬腳的位置沒有棋子
我們使用一個新的輔助數組,來判斷馬的走法是否合法。
var KNIGHT_PIN_ = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-16, 0,-16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
該數組在(-33 + 256)、(-31 + 256)、(-18 + 256)、(14 + 256)、(-14 + 256)、(18 + 256)、(31 + 256)、(33 + 256)這8個位置分別存放了-1、-1、-16、-16、1、1、16、16,其他位置都是0。
設置變量sqPin滿足:
sqPin = sqSrc + KNIGHT_PIN_[sqDst - sqSrc + 256]
如果馬的走法滿足條件(1),那么spPin就是馬腳的位置;否則sqPin = sqSrc。
因此馬的走法合法,只需要滿足sqPin != sqSrc並且sqPin位置無棋子。
2.7、校驗車、炮的走法
車的走法如下圖所示:
沿着四條直線走,每個方向可一直向前走,直到:
(1)、走出棋盤
(2)、碰到本方棋子
(3)、吃掉對方棋子
例如方向1,每走一步都是在起點基礎上-1;方向2是在起點基礎上-16;方向3是在起點基礎上+1;方向4是在起點基礎上+16。
炮與車的行棋規則類似,也可以沿一個方向一直向前走,不過遇到棋子時,要越過去(也就是翻山)。翻山后,炮只能吃對方棋子,不能落到空位置。
具體校驗算法可參看代碼。
2.8、校驗卒(兵)的走法
紅兵的走法如下圖所示:
過河前,只能向前走。過河后,可以左右走。
(1)、判斷是否過河
以紅方為例,紅方是向上走。如果紅方過河,則會走到棋盤0到127的位置,此時所處位置的二進制表示,右起第8位是0。(因為128的二進制是1000 0000,這與之前講過的SAME_HALF函數類似)
判斷函數如下:
// sp是棋子位置,sd是走棋方(紅方0,黑方1)。如果該位置已過河,則返回true;否則返回false。 function AWAY_HALF(sq, sd) { return (sq & 0x80) == (sd << 7); }
如果兵已經過河,是可以左右走的,滿足下面條件的走法一定合法:
AWAY_HALF(sq, sd) && (sqDst == sqSrc - 1 || sqDst == sqSrc + 1)
(2)、判斷兵(卒)是否是向前走了一步
紅兵向前走一步是sqSrc - 16,黑卒向前走一步是sqSrc + 16。可用如下函數得到兵(卒)向前一步的位置:
// sp是棋子位置,sd是走棋方(紅方0,黑方1)。返回兵(卒)向前走一步的位置。 function SQUARE_FORWARD(sq, sd) { return sq - 16 + (sd << 5); }
因此,只要sqDst = = SQUARE_FORWARD(sq, sd),說明兵(卒)是向前走了一步,走法合法。
2.9、核心代碼說明
本節的代碼可以在 Github 下載,也可以直接clone
git clone -b step-2 https://github.com/Royhoo/write-a-chinesechess-program
Board中新增或修改的主要屬性和方法
(1)、sqSelected
當前被選中棋子的位置。
(2)、clickSquare(sq_)
用戶點擊棋盤時,我們分兩種情況進行討論:
1、當前棋盤上沒有棋子被選中
也就是說sqSelected為0。如果點擊的是己方棋子,那么直接選中該子
sqSelected = 點擊的位置
2、當前棋盤上有棋子被選中
也就是說sqSelected不為0。如果這次點擊的仍然是己方棋子,這說明用戶重新選擇了要走的棋子,
sqSelected = 新的點擊的位置
如果這次點擊的是對方棋子,或者是一個空位置,這說明用戶是在走棋。起點是原來選中的位置,終點是當前選中的位置。
(3)、addMove(mv)
判斷一步棋是否合法,如果合法,就執行這步棋。
Position中新增或修改的主要屬性和方法
(1)、legalMove(mv)
判斷步驟是否合法。合法返回true,非法返回false。