JavaScript中國象棋程序(4) - 極大極小搜索算法


“JavaScript中國象棋程序” 這一系列教程將帶你從頭使用JavaScript編寫一個中國象棋程序。這是教程的第4節。

程序的最終效果點擊這里查看

這一系列共有9個部分:

0、JavaScript中國象棋程序(0)- 前言

 

上一節的程序,電腦是在隨機走棋,這樣太沒勁了。這一節我們的程序中加入極大極小搜索算法,這樣程序會稍微有點智商。不過棋力不高,也就是普通小學生的水平吧。

4.1、局面評估

局面評估,就是判斷局面對紅方(或黑方)的優勢,並把優勢量化。棋子價值可用以下不等式表達:

> 車 > 馬、炮 > 仕、相 > 兵

棋子價值可以簡單量化為:

10

20

20

40

45

90

1000

但是棋子價值是跟位置有關系的,比如兵在過河前價值很小,過河后價值大漲。在我們的程序中,兵的位置價值數組如下:

[	// 兵
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  9,  9,  9, 11, 13, 11,  9,  9,  9,  0,  0,  0,  0,
    0,  0,  0, 19, 24, 34, 42, 44, 42, 34, 24, 19,  0,  0,  0,  0,
    0,  0,  0, 19, 24, 32, 37, 37, 37, 32, 24, 19,  0,  0,  0,  0,
    0,  0,  0, 19, 23, 27, 29, 30, 29, 27, 23, 19,  0,  0,  0,  0,
    0,  0,  0, 14, 18, 20, 27, 29, 27, 20, 18, 14,  0,  0,  0,  0,
    0,  0,  0,  7,  0, 13,  0, 16,  0, 13,  0,  7,  0,  0,  0,  0,
    0,  0,  0,  7,  0,  7,  0, 15,  0,  7,  0,  7,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
]

在初始位置,中兵價值15,其他四個位置價值都是7。位於九宮的中心位置時,價值達到最高的44。這個數組肯定不是憑空想象出來的,應該是象棋百科全書網的前輩,經過無數次的試驗得到的。

帥的位置價值數組如下:

[	// 帥
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  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,  2,  2,  2,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0, 11, 15, 11,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  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、11、15僅僅表示了帥的位置分值,並不是說帥只值這個數。由於兵、帥的位置沒有重合,這兩個數組可以合並。

如此一來,每種棋子就都會有一個與絕對位置相關的價值數組,因此我們的程序里有一個常量數組PIECE_VALUE[7][256]。該數組只提供了紅方的位置分值。想獲得黑方的位置分值,使用前面介紹過的函數SQUARE_FLIP(sq)翻轉一下位置即可。此外,我們在Position對象中定義vlWhite和vlBlack兩個屬性,分別表示紅方和黑方棋子價值。每次調用addPiece(sq, pc, bDel)增刪棋子時,都會更新vlWhite和vlBlack。獲取紅方優勢的局面評估函數:

Position.prototype.evaluate = function() {
  var vl = this.vlWhite - this.vlBlack;
  return vl;
}

4.2、簡單的兩層搜索

 

圓形節點為紅方走棋的局面,方形節點為黑方走棋局面,紅色數字為局面估值(也就是紅方的優勢)。深度優先遍歷這個搜索樹。

1)、初始局面為A,該紅方走棋。紅方有B1、B2、B3三種走法。

2)、假設紅方選擇第一種走法,走到了局面B1。

3)、在局面B1,該黑方走棋。黑方有C1、C2、C3三種走法。

4)、C1、C2、C3是葉子節點,調用局面評估函數,算得局面估值(也就是紅方優勢)分別是8、10、15。

5)、回到局面B1,此時該黑方走棋。黑方后完后,紅方優勢越小,對黑方越有利。黑方自然會走到對自己最有利的C1局面。此時我們認為,B1局面的估值是8。

6)、同樣的方法,B2、B3的估值分別是5、10。

7)、回到初始局面A,紅方走棋。現在已經知道,選擇第一種走法,最終估值為8;選擇第二種走法,最終估值為5;選擇第三種走法,最終估值為10。估值就是紅方的優勢,紅方自然會選擇對自己優勢最大的走法,也就是走到局面B3。

8)、可知行棋路線為A -> B3 -> C7。

A點選擇估值最大的局面,稱為極大點;B1、B2、B3選擇估值最小的局面,稱為極小點。

4.3、極大點搜索算法

在程序中,定義一個全局變量,表示搜索深度:

var MINMAXDEPTH = 3;

也就是說,程序會搜索3層。當然你也可以改為4,這樣電腦的棋力會更強。當搜索深度超過4時,電腦會走得很慢很慢。根節點的層次是MINMAXDEPTH,每往下搜索一層,層次減1。當層次為0時,不再向下搜索,而是調用評估函數計算估值。

偽代碼如下:

// 極大點搜索
Search.prototype.maxSearch = function(depth) {
  如果depth等於0,調用評估函數並返回分值
  
  var vlBest = 負無窮;			// 初始最優值
  var mvs = 當前局面全部走法;		// 生成當前局面的所有走法
  var value = 0;
  for (var i = 0; i < mvs.length; i ++) {
	執行招法mvs[i]
	調用極小點搜索算法,深度設為depth-1,並將返回值賦給value
	撤銷招法mvs[i]
	
	if (value > vlBest) {		// 尋找最大估值
	  vlBest = value;
	  if (depth == MINMAXDEPTH) {	// 如果回到了根節點,需要記錄根節點的最佳走法
	    記錄根節點的最佳走法
	  }
	}	
  }  
  return vlBest;			// 返回當前節點的最優值
}

4.4、極小點搜索算法

極小點搜索這與極大點搜索很相似,但有3處不同:

1)、初始最優值為正無窮。

2)、遞歸調用的是極大點搜索算法。(而極大點搜索算法會遞歸調用極小點搜索算法)

3)、尋找的是最小估值。

4.5、極大極小搜索算法

if (該紅方走棋) {

  調用極大點搜索算法,深度為MINMAXDEPTH

} else {

  調用極小點搜索算法,深度為MINMAXDEPTH

}

4.6、核心代碼說明

本節的代碼可以在 Github 下載,也可以直接clone

git clone -b step-4 https://github.com/Royhoo/write-a-chinesechess-program

Board中新增或修改的主要屬性和方法:

1)、result

對局結果,有4種狀態。

0表示結果未知(正在戰斗中,這有在這一狀態下,程序才響應用戶的點擊事件)

1表示你贏了(也就是電腦輸了)

2表示和棋

3表示你輸了(也就是電腦贏了)

Position中新增或修改的主要屬性和方法:

1)、vlWhite

紅方所有棋子的價值

2)、vlBlack

黑方所有棋子的價值

3)、addPiece(sq, pc, bDel)

此方法增加了一項功能,就是在增減棋子時,更新vlWhite和vlBlack。

4)、checked()

判斷老將是否被對方攻擊。具有攻擊性的棋子是車、馬、炮、兵。我們要判斷對方的這四類棋子是否攻擊到了己方老將,以及是否將帥對臉。算法如下:

1、假設帥(將)是車,判斷它是否能吃到對方的車和將(帥)。如果能吃到對方的車,說明己方帥(將)被對方車攻擊;如果能吃到將(帥),說明存在將帥對臉。

2、假設帥(將)是炮,判斷它是否能吃到對方的炮。

3、假設帥(將)是馬,判斷它是否能吃到對方的馬,需要注意的是,帥(將)的馬腿用的數組是ADVISOR_DELTA,而不是KING_DELTA。

4、假設帥(將)是過河的兵(卒),判斷它是否能吃到對方的卒(兵)。

5)、makeMove()

改方法做了一些改進。如果移動棋子后,發現老將被對方攻擊,也就是說這步棋是去送死的,那么就要撤銷對棋子的移動,並返回false。

Search中新增或修改的主要屬性和方法:

1)、mvResult

這是搜索算法找到的最佳走法,隨后電腦就會執行這步棋。

2)、maxSearch()

極大點搜索算法

3)、minSearch()

極小點搜索算法

4)、maxMinSearch()

極大極小搜索算法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM