Java咖啡館(12): 終結者

文章來源:電腦愛好者 作者:Gary Chan

  通過各種試驗,科學家發現自然界中許多被認爲聰明的動物的智慧仍然無法與人類幼兒時期的相比擬。而每天面對的計算機,雖然使得人類的生產力發生翻天覆地變化,但冰冷的它,是否有一天能夠模仿甚至達到人類的智慧呢?

計算機的智慧

  如何定義人類的智慧,即使最睿智的哲學家也只能望洋興嘆。不過,我們不妨由淺入深。如果一向自信十足的你在網上與網友對弈楚漢時,遇到一個與你殺得難解難分甚至略勝一籌的高手,當你英雄惜英雄,邀請那高人紅茶館小聚一番,卻被告知那是某公司象棋程序2.0而不是一個真人在下棋時,你一定會由衷發出讚歎——計算機真是聰明啊!

  所以,從休閒的棋牌遊戲入手,是探究計算機智慧有益的第一步。實際上,早在1950年,Claude Shannon和Alan Turing已編寫過下棋的程序,前者是信息論的創始人,而以後者名字命名的圖靈獎更是計算機科學中的最高獎項。

  當時的工作是基於兩個玩家互相博弈的基礎上的。計算機科學家們把棋局的狀態看作結點,把從當前棋局走一步而變化出來的新的棋局當做結點的子結點,如果把原始棋局看作樹根,就可以把棋局的所有變化演化成一棵倒置的樹來,我們稱之爲遊戲樹。這樣,便可以從最底部的結果來倒推,從而決定走對自己最有利的一步棋。

  遊戲樹的想法很直觀,但這棵樹實在太龐大了。比如西洋跳棋,完整的遊戲樹將達到10的40次方個結點的量級,假設一臺計算機每秒能處理300個結點,將會花費10的21次方個世紀來構建這棵遊戲樹!而國際象棋更加複雜,按照每步平均35個可供選擇的下法,平均每個玩家下50步棋,國際象棋的遊戲樹將會達到35的100次方的量級。盛行於中日韓三國的圍棋更是比國際象棋複雜得多。

  由於時間限制而遊戲樹幾近無限,我們的可行方法不外乎兩個字——砍樹,也就是在遊戲樹的構建過程中只計算那些感興趣的結點,而不管那些不怎麼有意思的結點,結果相當於只生成了部分的遊戲樹,這樣便可以把計算時間控制住。仔細想想,下棋也是這樣的,當別人將軍的時,你自然只考慮如何解圍,而不是如何去喫對方的子。當然,如何砍樹並且砍得好,那是一門大學問。

教計算機玩Tic-Tac-Toe

  相對於西洋跳棋、國際象棋和圍棋,我們的Tic-Tac-Toe遊戲可要簡單得多,雖然手動構造遊戲樹也不是不可行,但那樣的做法實在太遜了啦!我們要砍樹!

  這裏給大家介紹的方法是MiniMax算法。所謂MiniMax算法,就是從當前棋局出發生成一顆n層深的遊戲樹,從樹根開始輪流給每層結點賦予Max和Min的稱號,並且按照一定的估計函數給最底層的結點賦值。然後,從下向上依次給每個結點賦值,若一個結點是Max結點,則把它子結點中的最大值賦給它,若是Min結點,則把它子結點中的最小值賦給它。如此這般,便可以得知樹根選擇哪個子結點才能得到最大效益。

  實際,估計函數是用來限制遊戲樹深度的。這裏介紹的估計函數是自己可能贏棋的通道數減去對手可能贏棋的通道數。以圖1舉例,假設計算機執圓圈,則圓圈可能贏的通道數是4個,而大叉可能贏的通道數只有2個,所以棋局對應的估計值=4-2=2(見圖1)。

  下面我們手動模仿計算機執大叉在開局時進行MiniMax算法的演算過程,讓大家有個直觀的理解。其中,MiniMax算法的估計函數就是上文介紹的,遊戲樹的深度爲三層(見圖2)。


  最底層的結點的賦值都是根據估計函數得到的。在Min層,每個值都是底層結點中值的最小值,這表示充滿智慧的對手可能使自己落到的最窘迫的下場。當Min層生成以後,計算機自然知道Min最右邊的哪個結點是最划算的,無論對手如何走,自己走正中的那一格總會佔有相當的優勢。

Tic-Tac-Toe加強版

  上一版的Tic-Tac-Toe智商肯定無法令人滿意,既然有了MiniMax算法,爲什麼不動手實現一下呢?

  在Eclipse中打開上回建立的項目。首先在TicTacToe類中加入估計函數evalauate()。這個估計函數與上文介紹的估計函數略有不同,這裏通過線性函數考慮了更多的因素,強調了三連通棋局的重要性,以及提高了兩連通棋局的地位。通過這個估計函數,讀者朋友應該能夠體會到砍樹也是很有藝術性的,對於複雜的棋類,估計函數幾乎決定了程序棋力的高下。源代碼如下:

/**
* 估計函數。
* @param board 要被估計的棋盤
* @return 估計值
*/
private int evalauate(int[] board) {
int x2 = 0, o2 = 0, x1 = 0, o1 = 0;

for (int i = 0; i < lines.length; i++) {
int[] line = lines[i];
int sum = 0;
for (int j = 0; j < line.length; j++) {
sum += board[line[j]];
}
switch (sum) {
case 3:
// 圓圈聯通
return -100;
case 12:
// 大叉聯通
return 100;
case 8:
x2++;
break;
case 4:
x1++;
break;
case 2:
o2++;
break;
case 1:
o1++;
break;
}
}
return 3 * (x2 - o2) + x1 - o1;
}


  下面列出了應用MiniMax算法的模仿專業棋力的professional()函數,請各位朋友參考註釋閱讀:

/**
* 擁有職業選手棋力的機器人下一步棋。
*/
public void professional() {
ArrayList blanks = new ArrayList();
// 獲取當前棋局所有可以下的位置
for (int i = 0; i < 9; i++) {
if (board[i] == 0) {
blanks.add(new Integer(i));
}
}
// 若已經沒有空格可以下了便和局
if (blanks.size() == 0) {
message = "平局";
gameover = true;
return;
}
// 利用MiniMax算法找出計算機最可能贏的一步棋
// 枚舉Min的每一種可能下法
int minBoards[][] = new int[blanks.size()][9];
int max = -100, best = 0;
for (int i = 0; i < minBoards.length; i++) {
// 複製當前棋局
int[] minBoard = (int[]) board.clone();
// 測試下第一步棋
minBoard[((Integer) (blanks.get(i))).intValue()] = CROSS;
minBoards[i] = minBoard;
// 剩下的空格就是Max的落子範圍
ArrayList maxBlanks = (ArrayList) blanks.clone();
maxBlanks.remove(i);
// 根據當前的測試棋局枚舉Max的每一種可能下法
int min = 100;
for (int j = 0; j < maxBlanks.size(); j++) {
// 複製當前棋局
int[] maxBoard = (int[]) minBoard.clone();
// 測試下第一步棋
maxBoard[((Integer) (maxBlanks.get(j))).intValue()] = NOUGHT;
// Min將取這些Max的取值中最小者
int value = evalauate(maxBoard);
if (value < min) {
min = value;
}
}
if (min > max) {
max = min;
best = i;
}
}
board = minBoards[best];
}


  最後記得把mouseReleased()中的amateur();代碼改成professional(),這樣加強版的遊戲就算完成了。運行一下,計算機是不是已經威風八面了?

Tic-Tac-Toe終結版

  由於篇幅所限,以上代碼還有很多可以改進之處。

  首先,Tic-Tac-Toe遊戲棋盤非常有特色,用中學數學的術語來講是中央對稱的,所以在預測下一步可行的位置時,可以利用這個特性,只考慮其中幾種情況即可。

  其次,估計函數還可以進一步優化,有興趣的朋友可以在網上搜索一下,這方面的論文也不在少數。

  最後,MiniMax是一個經典的限制深度的算法,實際上,還可以在MiniMax的基礎上限制樹的廣度,那就是Alpha-Beta算法。比如,我們以如圖3所示爲例。

  我們將分別計算K、L和M的值。很顯然,K的值是1。但是計算L的時候,當我們發現D的值是-1的時候,我們可以馬上砍掉L這個結點了,因爲對於根節點,K的值肯定比L要大。這樣便進一步節省了搜索空間。

Just Do It

  請嘗試根據以上提示改進程序,完成一個更優秀的遊戲。

結束語

  小店到今天終於又告一段落。我們在這四回的文章中介紹了Java中的Applet技術,可以做出許多匪夷所思的效果。此外,我們在最後還爲大家講解了一些人工智能的技術,可以發現,當Java與AI結合後,可以迸發出很強大的能量,若再加上一些美工和音效,製作出商業軟件也不是什麼難事。

  多謝大家的關照與捧場,希望大家能夠繼續支持我們大家的Java咖啡館!  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章