“JavaScript中國象棋程式” 這一系列教程將帶你從頭使用JavaScript編寫一箇中國象棋程式。希望通過這個系列,我們對博弈程式的演算法有一定的瞭解。同時,我們也將構建出一個不錯的中國象棋程式。
程式的最終效果點選這裡檢視。
我學習中國象棋程式的歷程
為什麼選擇JavaScript
專案初衷
這個教程難學嗎?
這一節我們設計圖形介面,顯示初始化棋局。當點選某棋子時,彈窗提示所點選的具體棋子。效果如下:
1.1、棋盤表示
中國象棋有10行9列,很自然地想到可以用10×9矩陣表示棋盤。事實上,我們使用16×16矩陣來表示一個擴充了的虛擬棋盤。
如上圖所示,灰色部分為真實棋盤,置於虛擬棋盤之中。這麼做可以快速判斷棋子是否走出邊界。例如象沿田字走,如果走到真實棋盤之外的虛擬棋盤中,說明走法不合法。
容易想到使用二維陣列表示16×16矩陣,這樣棋盤上的一個位置需要兩個變數表示。一個走法包括起點和終點,就需要四個變數。如果使用長度為256的一維陣列表示,一個位置只需一個變數,這就可以減少計算量。因此用一維陣列表示16×16矩陣。
一維矩陣和二維矩陣之間的轉換也很簡單:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 將二維矩陣轉換為一維矩陣 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結果相同(可參考篇文章)。
再使用一個輔助陣列,標識虛擬棋盤中,哪些位置屬於真實棋盤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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, ]; |
要判斷某位置是否在真實棋盤,可使用函式:
1 2 3 |
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),這是為了與國際象棋相對應。如果沒有棋子,則用數字表示出相鄰連續的空位數。中國象棋共有十行,每行都用一個字串表示,行間使用正斜槓分割。例如:
9表示:第二行都是空格。
(2)、綠色區域,表示輪到哪一方走子,“w”表示紅方,“b”表示黑方。(沒有用r表示紅方,我想也是為了與國際象棋對應吧,畢竟國際象棋是黑白兩色。)
(3)、深紫色區域,在中國象棋中沒有意義,始終用“-”表示。
(4)、紫紅色區域,在中國象棋中沒有意義,始終用“-”表示。
(5)、藍色區域,表示雙方沒有吃子的走棋步數(半回合數),通常該值達到120就要判和(六十回合自然限著),一旦形成局面的上一步是吃子,這裡就標記“0”。
(6)、棕色區域,表示當前的回合數。
我們的程式就是使用FEN串初始化棋局的,這就涉及到了將FEN串轉化為一維棋局陣列。暫時不考慮哪方走子,只解析紅色部分,虛擬碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 將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位置。