《數據結構與算法分析:C語言描述》復習——第十章“算法設計技巧”——Minimax策略


2014.07.08 20:53

簡介:

  Minimax策略描述的是二人在輪流操作的博弈中,盡力使自己的利益最大化(Max),使對手利益最小化(Min)的一種策略。

  這樣的游戲有很多種,其中最典型的就是雙人棋牌類游戲:中國象棋、五子棋、撲克牌等等。

  這樣的游戲的特點是:

    1. 兩人交替操作,一方先開始

    2. 兩人的操作互相獨立,沒有協作

  游戲的種類實在太多,生活中處處是游戲。

  “游戲”和“博弈”的英語釋義都是“game”,所以我們其實可以認為理論上它們是一回事,只不過一個通俗,一個深奧。

圖示:

  本次我們使用書上給出的一個非常簡單的棋牌游戲“三連棋”作為例子,來解釋Minimax策略。

  如圖:

  

  給定一個3X3的棋盤,兩人輪流在上面畫“O”和“X”,誰能先將三個相同符號連成一條線(橫豎斜皆可),就算贏。

  本次的代碼會實現這個游戲的AI,允許你和程序來玩這個游戲。運行程序后,你會發現不論你怎么下棋,你都贏不了的。

  因為這個游戲是公平的,沒有必勝策略,因此電腦程序總能和你打成平局。

  如果你不好好下棋,當然也會輸掉。

  

  下面我們開始討論Minimax策略的理論部分。

  下面是一棵樹(分叉數量我們不關心):

  

  上面這棵樹叫“博弈樹”,英文是game tree。其中Max層和Min層交替出現。關於這棵樹有四點需要解釋:

    1. Max層表示輪到我操作,期望我的利益最大化,所以標記為Max層

    2. Min層表示輪到對手操作,期望我的利益最小化,所以標記為Min層

    3. 節點中有一個數值,這個莫名其妙的數值是本章最難理解的東西——利益

    4. 樹的底層總是葉子節點,此處每個葉子節點都表示游戲的一種結局,因為游戲結束了,樹才不會繼續延伸下去

  利益是什么?金錢,高考分數,身高體重,房子面積。總之說多了都是淚。

  游戲中的“利益”不能用一個“贏”字來概括,否則你無法着手去分析贏的辦法。

  此處的利益,應該是具有多個參數的一個函數F(...),函數值越大,你離“贏”就越近。

  

  比如下象棋,“贏”的定義是吃掉對手的“將”或者“帥”。所以你不能將“對手的將是否存在”作為利益的判斷條件。

  如果你手上現在有7個棋子,而對手只剩2個,那么很有可能你會贏,因此將雙方棋子的數量差作為“利益”的標准,可能會更好。

  同樣是象棋棋子,“車”的攻擊力巨大,“馬”的行動靈活,“士”的防御至關重要,因此不同的棋子為你帶來的潛在利益各不相同。

  正是由於實際的游戲如此復雜,想用一個包含了很多參數的函數F(...)來表達利益才顯得非常困難。

  對於中國象棋之類的游戲,這個估值函數可以復雜到讓人吐血,也可以很簡單。至於用戶能看到的區別,就是“電腦厲害死了”和“電腦弱爆了”。

  

  至此,你要隨時記住一個游戲中的兩個條件:

    1. 贏的標准是什么(走象棋,吃掉對方的將帥)

    2. 為了達到“贏”,我應該朝什么方向努力(走象棋,努力吃掉對方的棋子)

  

  那么三連棋呢:

    1. 贏的標准,三個棋子連成一條線

    2. 為了達到“贏”,你應該盡力把自己的棋子連在一起

  

  至此我已經不知道該怎么繼續講了,因為我思考到這兒之后就直接開始編寫代碼了。

  現在你可以運行下面的代碼,試試和電腦下棋,並單步調試觀察運行過程。

  如果你要自己編碼實現這個程序,請記住一條原則:你如果贏了電腦,那程序就是錯的。

  在閱讀代碼的過程中,請嘗試理解這個程序中是如何定義“利益”的。

實現:

 感謝 @無聊的豆子君 指出代碼中的錯誤。

  1 // Optimization for Minimax game strategy, using Alpha-Beta Pruning.
  2 // You can watch over the 'function_call_count' variable.
  3 #include <iostream>
  4 #include <vector>
  5 using namespace std;
  6 
  7 int function_call_count;
  8 
  9 bool computerWin(const vector<int> &board)
 10 {
 11     int i, j;
 12     
 13     for (i = 0; i < 3; ++i) {
 14         for (j = 0; j < 3; ++j) {
 15             if (board[i * 3 + j] != -1) {
 16                 break;
 17             }
 18         }
 19         if (j == 3) {
 20             return true;
 21         }
 22     }
 23     
 24     for (i = 0; i < 3; ++i) {
 25         for (j = 0; j < 3; ++j) {
 26             if (board[j * 3 + i] != -1) {
 27                 break;
 28             }
 29         }
 30         if (j == 3) {
 31             return true;
 32         }
 33     }
 34     
 35     if (board[0] == board[4] && board[4] == board[8] && board[8] == -1) {
 36         return true;
 37     }
 38     
 39     if (board[2] == board[4] && board[4] == board[6] && board[6] == -1) {
 40         return true;
 41     }
 42     
 43     return false;
 44 }
 45 
 46 bool humanWin(const vector<int> &board)
 47 {
 48     int i, j;
 49     
 50     for (i = 0; i < 3; ++i) {
 51         for (j = 0; j < 3; ++j) {
 52             if (board[i * 3 + j] != 1) {
 53                 break;
 54             }
 55         }
 56         if (j == 3) {
 57             return true;
 58         }
 59     }
 60     
 61     for (i = 0; i < 3; ++i) {
 62         for (j = 0; j < 3; ++j) {
 63             if (board[j * 3 + i] != 1) {
 64                 break;
 65             }
 66         }
 67         if (j == 3) {
 68             return true;
 69         }
 70     }
 71     
 72     if (board[0] == board[4] && board[4] == board[8] && board[8] == 1) {
 73         return true;
 74     }
 75     
 76     if (board[2] == board[4] && board[4] == board[6] && board[6] == 1) {
 77         return true;
 78     }
 79     
 80     return false;
 81 }
 82 
 83 bool fullBoard(const vector<int> &board)
 84 {
 85     for (int i = 0; i < 9; ++i) {
 86         if (board[i] == 0) {
 87             return false;
 88         }
 89     }
 90     
 91     return true;
 92 }
 93 
 94 void findComputerMove(vector<int> &board, int &best_move, int &result, 
 95     int alpha, int beta)
 96 {
 97     void findHumanMove(vector<int> &, int &, int &, int, int);
 98     int dc, i, response;
 99     
100     ++function_call_count;
101     best_move = -1;
102 
103     if (fullBoard(board)) {
104         result = 0;
105         return;
106     }
107     
108     if (humanWin(board)) {
109         result = 1;
110         return;
111     }
112 
113     if (computerWin(board)) {
114         result = -1;
115         return;
116     }
117     
118     result = alpha;
119     for (i = 0; i < 9 && result > beta; ++i) {
120         if (board[i] != 0) {
121             continue;
122         }
123         board[i] = -1;
124         findHumanMove(board, dc, response, result, beta);
125         board[i] = 0;
126         
127         if (best_move == -1 || response < result) {
128             result = response;
129             best_move = i;
130         }
131     }
132 }
133 
134 void findHumanMove(vector<int> &board, int &best_move, int &result, int alpha, 
135     int beta)
136 {
137     void findComputerMove(vector<int> &, int &, int &, int, int);
138     int dc, i, response;
139     
140     ++function_call_count;
141     best_move = -1;
142 
143     if (fullBoard(board)) {
144         result = 0;
145         return;
146     }
147     
148     if (computerWin(board)) {
149         result = -1;
150         return;
151     }
152 
153     if (humanWin(board)) {
154         result = 1;
155         return;
156     }
157     
158     result = beta;
159     for (i = 0; i < 9 && result < alpha; ++i) {
160         if (board[i] != 0) {
161             continue;
162         }
163         board[i] = 1;
164         findComputerMove(board, dc, response, alpha, result);
165         board[i] = 0;
166         
167         if (best_move == -1 || response > result) {
168             result = response;
169             best_move = i;
170         }
171     }
172 }
173 
174 void printBoard(const vector<int> &board)
175 {
176     cout << "  1 2 3" << endl;
177     int i, j;
178     
179     for (i = 0; i < 3; ++i) {
180         cout << i + 1;
181         for (j = 0; j < 3; ++j) {
182             cout << ' ';
183             switch(board[i * 3 + j]) {
184             case -1:
185                 cout << 'X';
186                 break;
187             case 0:
188                 cout << '.';
189                 break;
190             case 1:
191                 cout << 'O';
192                 break;
193             }
194         }
195         cout << endl;
196     }
197 }
198 
199 int main()
200 {
201     vector<int> board;
202     int n;
203     int result;
204     
205     board.resize(9, 0);
206     while (cin >> n) {
207         if (n < 0 || n >= 9 || board[n]) {
208             cout << "Invalid move" << endl;
209             continue;
210         }
211 
212         board[n] = 1;
213         printBoard(board);
214         if (humanWin(board)) {
215             cout << "You win." << endl;
216             break;
217         }
218         
219         if (fullBoard(board)) {
220             cout << "Draw." << endl;
221             break;
222         }
223         
224         result = 1;
225         function_call_count = 0;
226         findComputerMove(board, n, result, 1, -1);
227         cout << "Number of function calls: " << function_call_count << endl;
228         board[n] = -1;
229         printBoard(board);
230         if (computerWin(board)) {
231             cout << "Computer win." << endl;
232             break;
233         }
234         
235         if (fullBoard(board)) {
236             cout << "Draw." << endl;
237             break;
238         }
239     }
240     
241     return 0;
242 }

 

 

 


免責聲明!

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



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