“JavaScript中國象棋程序” 這一系列教程將帶你從頭使用JavaScript編寫一個中國象棋程序。這是教程的第1節。
這一節我們設計圖形界面,顯示初始化棋局。當點擊某棋子時,彈窗提示所點擊的具體棋子。效果如下:
1.1、棋盤表示
中國象棋有10行9列,很自然地想到可以用10×9矩陣表示棋盤。事實上,我們使用16×16矩陣來表示一個擴充了的虛擬棋盤。
如上圖所示,灰色部分為真實棋盤,置於虛擬棋盤之中。這么做可以快速判斷棋子是否走出邊界。例如象沿田字走,如果走到真實棋盤之外的虛擬棋盤中,說明走法不合法。
容易想到使用二維數組表示16×16矩陣,這樣棋盤上的一個位置需要兩個變量表示。一個走法包括起點和終點,就需要四個變量。如果使用長度為256的一維數組表示,一個位置只需一個變量,這就可以減少計算量。因此用一維數組表示16×16矩陣。
一維矩陣和二維矩陣之間的轉換也很簡單:
// 將二維矩陣轉換為一維矩陣 function COORD_XY(x, y) { return x + (y << 4); } // 根據一維矩陣,獲取二維矩陣行數 function RANK_Y(sq) { return sq >> 4; } // 根據一維矩陣,獲取二維矩陣列數 function FILE_X(sq) { return sq & 15; }
其中,sq & 15是通過位運算取余,與sq % 16結果相同(可參考篇文章)。
再使用一個輔助數組,標識虛擬棋盤中,哪些位置屬於真實棋盤:
var IN_BOARD_ = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 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, ];
要判斷某位置是否在真實棋盤,可使用函數:
function IN_BOARD(sq) { return IN_BOARD_[sq] != 0; }
1.2、棋子表示
使用整數表示棋子:
|
將 |
士 |
象 |
馬 |
車 |
炮 |
卒 |
紅方 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
黑方 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
棋子這樣表示,可以快速判斷某棋子屬於紅方還是黑方,如下表所示:
紅方棋子 |
黑方棋子 |
||
十進制 |
二進制 |
十進制 |
二進制 |
8 |
0000 1000 |
16 |
0001 0000 |
9 |
0000 1001 |
17 |
0001 0001 |
10 |
0000 1010 |
18 |
0001 0010 |
11 |
0000 1011 |
19 |
0001 0011 |
12 |
0000 1100 |
20 |
0001 0100 |
13 |
0000 1101 |
21 |
0001 0101 |
14 |
0000 1110 |
22 |
0001 0110 |
可以看出:
紅方棋子 & 8 = 1
黑方棋子 & 16 = 1
1.3、字符串表示局面
使用數組表示局面,程序處理起來很方便,但是再網上傳遞棋局很不方便。我們可以用一行字符串表示一個局面,這就是FEN格式串,一種使用ASCII碼字符描述國際象棋局面的標准,當然也可應用於中國象棋。中國象棋的初始局面可表示為:
rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1
(1)、紅色區域,表示棋盤布局,小寫表示黑方,大寫表示紅方。一個字母表示一個棋子,對應關系如下。
紅方 |
字母 |
黑方 |
字母 |
對應單詞 |
帥 |
K |
將 |
k |
king |
仕 |
A |
士 |
a |
advisor |
相 |
B |
象 |
b |
bishop |
馬 |
N |
馬 |
n |
knight |
車 |
R |
車 |
r |
rook |
炮 |
C |
炮 |
c |
cannon |
兵 |
P |
卒 |
p |
pawn |
至於為什么馬不用H(horse),象不用E(elephant),這是為了與國際象棋相對應。如果沒有棋子,則用數字表示出相鄰連續的空位數。中國象棋共有十行,每行都用一個字符串表示,行間使用正斜杠分割。例如:
rnbakabnr表示:
9表示:第二行都是空格。
1c5c1表示:
(2)、綠色區域,表示輪到哪一方走子,“w”表示紅方,“b”表示黑方。(沒有用r表示紅方,我想也是為了與國際象棋對應吧,畢竟國際象棋是黑白兩色。)
(3)、深紫色區域,在中國象棋中沒有意義,始終用“-”表示。
(4)、紫紅色區域,在中國象棋中沒有意義,始終用“-”表示。
(5)、藍色區域,表示雙方沒有吃子的走棋步數(半回合數),通常該值達到120就要判和(六十回合自然限着),一旦形成局面的上一步是吃子,這里就標記“0”。
(6)、棕色區域,表示當前的回合數。
我們的程序就是使用FEN串初始化棋局的,這就涉及到了將FEN串轉化為一維棋局數組。暫時不考慮哪方走子,只解析紅色部分,偽代碼如下:
// 將FEN串轉為一維數組 行變量 y = 3 列變量 x = 3 var c = FEN串第一個字符; while (c != " ") { if (c == "/") { // 換行 x = 3; y ++; if (y > 12) { break; } } else if (c >= "1" && c <= "9") { // 出現空位 列向量x增加c } else if (c >= "A" && c <= "Z") { // 紅方棋子 將字符表示的棋子轉換為整數,並放入數組x + (y << 4)的位置 } else if (c >= "a" && c <= "z") { 將字符表示的棋子轉換為整數,並放入數組x + (y << 4)的位置 } c = FEN串的下一個字符; }
1.4、棋盤前端設計思路
由於棋盤有90個交叉點,我們把棋盤划分為的90個小正方形區域,交叉點是小正方形的中心。每個區域都會定義一個img標簽。
上圖使用紅色方框,標識出了4個小正方形區域。
這些img標簽有兩個作用:
(1)、顯示棋子圖片
如果某個區域存在棋子,就會顯示相應的棋子圖片;否則,顯示一張透明圖片(也就是oo.gif)。
(2)、響應點擊事件
每個img標簽都會綁定onmousedown事件。點擊不同的img標簽時,會傳遞不同的參數給響應函數,這樣就知道點擊的具體是哪個區域了。
1.5、核心代碼說明
本節的代碼可以在 Github 下載,也可以直接clone
git clone -b step-1 https://github.com/Royhoo/write-a-chinesechess-program
程序中定義了兩個對象:Board和Position。Board表示一個棋盤,主要功能是初始化棋局,顯示棋盤、棋子,響應棋盤上的點擊事件。Position存儲了一維棋局數組,並定義了很多對該數組進行操作的方法。
Board對象實例化的代碼位於index.html中。
通過prototype屬性,我們為這兩個對象添加了很多的屬性和方法。
Board的主要屬性和方法:
(1)、pos
這是Position對象的一個實例。
(2)、flushBoard()
刷新棋盤,也就是重新顯示棋盤上的棋子。
(3)、drawSquare(sq)
顯示sq位置的棋子圖片。如果該位置沒棋子,則顯示一張透明的圖片。
(4)、clickSquare(sq_)
點擊棋盤的響應函數。點擊棋盤(棋子或者空位置),就會調用該函數。sq_是點擊的位置。
Position的主要屬性和方法:
(1)、squares
這就是一維棋局數組。
(2)、fromFen(fen)
通過FEN串初始化棋局,也就是將參數fen表示的棋局,轉化為一維棋局數組squares表示的棋局。
(3)、addPiece(sq, pc)
將棋子pc添加進棋局中的sp位置。