• 《数据结构与算法分析: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 }
  • 相关阅读:
    Ehcache(04)——设置缓存的大小
    Ehcache(03)——Ehcache中储存缓存的方式
    Ehcache(02)——ehcache.xml简介
    Ehcache(01)——简介、基本操作
    linux 下开机自动启动tomcat服务
    Git_常用链接
    Android_listview点击失效
    Android_Drawable Bitmap Canvas Paint之间区别[转]
    Android_广播
    Notification(Notification的通知栏常驻、Notification的各种样式、Notification点击无效)
  • 原文地址:https://www.cnblogs.com/zhuli19901106/p/3832592.html
Copyright © 2020-2023  润新知