“JavaScript中國象棋程式” 這一系列教程將帶你從頭使用JavaScript編寫一箇中國象棋程式。這是教程的第2節。
程式的最終效果點選這裡檢視。
這一節介紹棋子的走法。改進之後的程式,可以連續走動紅方棋子,但要符合象棋規則。黑方(電腦方)暫時還不能回應。
2.1、走法表示
一個走法包括起點和終點,分別用sqSrc和pcDst表示一維棋局陣列中的起點和終點。很容易想到,使用陣列[sqSrc, pcDst]表示一個走法。但程式將來會處理大量的走法,使用陣列表示走法太浪費記憶體。由於sqSrc和pcDst都是不超過255的整數(因為一維棋盤陣列的大小是256),可以將sqSrc和pcDst壓縮到一個整數中,演算法如下:
1 2 3 |
function MOVE(sqSrc, sqDst) { return sqSrc + (sqDst << 8); } |
如果想從壓縮後的整數中,重新獲取起點和終點,可分別使用以下函式:
1 2 3 4 5 6 7 8 9 |
// 獲取走法的起點 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 就說明終止位置有本方棋子,走法不合法。
另外,我們設定七個全域性變數:
1 2 3 4 5 6 7 |
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位於九宮
可使用一個輔助陣列,標識出所有合法的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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, ]; |
判斷是否在九宮,使用函式
1 2 3 |
function IN_FORT(sq) { return IN_FORT_[sq] != 0; } |
(2)、pcDst – sqSrc等於-16、-1、1、16其中的一個。
設定一個陣列16×32的一維陣列
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 |
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。校驗函式如下
1 2 3 |
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。檢驗函式如下
1 2 3 |
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。判斷函式如下:
1 2 3 4 |
// 如果從起點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。檢驗函式如下
1 2 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)、對應馬腳的位置沒有棋子
我們使用一個新的輔助陣列,來判斷馬的走法是否合法。
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 |
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函式類似)
判斷函式如下:
1 2 3 4 5 |
// 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。可用如下函式得到兵(卒)向前一步的位置:
1 2 3 4 |
// 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。