图片 18

人工智能期末复习笔记2018-01-03

黑白棋

黑白棋,又叫苹果棋,最早流行于西方国家。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。黑白棋非常易于上手,但精通则需要考虑许多因素,比如角边这样的特殊位置、稳定度、行动力等。本游戏取名为黑白棋大师,提供了8种难度等级的选择,从菜鸟、新手、入门、棋手到棋士、大师、宗师、棋圣,助你不断提升棋力。

本文将着重介绍黑白棋实现过程中用到的算法。

对博弈树的理解 简单而言就是对每一步可能的结果进行分析
之后对当前步骤的下一步的所有可能结果进行分析而创建的树

对抗搜索

  • 博弈问题
    博弈问题穷举有时可以获得必胜结果,但是稍微复杂一点的问题就很难穷举了。
  • 极大极小过程
  • alpha-beta剪枝
  • Monte-Carlo博弈

黑白棋游戏规则

游戏规则见黑白棋大师中的截图。

图片 1

专业表示极大极小博弈树:极大极小博弈树是因描绘这种结构的一种简单算法而得名。我们来对ttt游戏的结果分配一下分值。如果叉(X)获胜,则分值为1。如果圈(O)获胜,则分值为-1。现在,叉将试图获得最大化的分值,而圈将试图最小化分值。于是,第一位研究此问题的研究者决定把游戏者叉命名为max,并且把游戏者圈命名为min。因此,这个完整的数据结构就被命名为极大(Max)极小(Min)博弈树。

极大极小过程

极大极小过程的想法非常直白,即:我方走步,则选择极大值;对方走步,则选择极小值。
极大极小过程是博弈问题的一个最基本的决策模式,alpha-beta剪枝法是建立在极大极小过程基础上的。


黑白棋大师游戏截图

游戏启动界面。

图片 2

游戏过程中的一个截图。

图片 3

开新局时的选项,选择先后手以及AI的水平。

图片 4

例如:图片 5

alpha-beta剪枝法

假如A和B博弈,不妨令局面估值函数为A的利益,那么A追求节点值极大,B追求节点值极小。
定义:极大节点的下界为alpha,极小节点的上界为beta。
剪枝:后辈节点的beta<=祖先节点的alpha,alpha剪枝;后辈节点的alpha>=祖先节点的beta,beta剪枝。
MyRemark
这个地方可能会给出一个树然后考手写求解过程,一个比较简单的手写方法是,先把每一层是极大还是极小标出来,然后标注这一行写alpha还是beta,之后从最后一层开始向上推。正常情况下一定是alpha小于beta的。如果出现alpha大于beta,就从中间剪开。这时候,被剪开上面是谁,就是谁剪枝。
实际实现的时候会发现,在ab剪枝法中,局面评价函数是非常重要的。深蓝能够依靠这个算法实现,很大程度上是因为成功构造出了一个局面评价函数。
在做大作业的时候,实在是没想出来合适的局面评价函数,效果特别差,所以最后用了蒙特卡洛。不过alpha-beta剪枝的效率真的是很好,现在想想假如用蒙特卡洛的结果训练神经网络,把这个作为估值函数,这样就相当于没有时间限制,算是一种cheat的方式吧(摊手)。


几个关键的类

对于博弈树的创立过程需要对于任意种可能结果设定权重:例如黑白棋中设立以下几种权重“

Monte-Carlo模拟

蒙特卡洛方法就很简单,就是不断地试,然后选择最优的。
主要就是UCT算法。每次模拟,假如没有拓展就拓展,每次拓展走子的时候,都选择UCB1最大的点走子。
最终选择被模拟次数最多的节点作为最佳走步。
AlphaGo用的是Monte-Carlo,显然是因为围棋比象棋复杂太多,局面估值函数实在是不好做。
AlphaGo在实际做的时候有以下几个操作:

  • 利用策略网络缩小搜索范围
  • 将估值网络的结果结合到信心上限的计算中
  • 一个节点被模拟一定的次数之后才扩展(这个还是挺重要
  • 最终选择模拟次数最多的节点作为最佳走步
    这个部分做了一个大作业,可能就不考了吧(天真脸)

Rule

Rule类实现游戏规则相关的方法,包括

  1. 判断某一步是否合法
  2. 获取所有的合法走步
  3. 走一步并翻转敌方棋子
  4. 统计两方棋子个数

连三 100分

Algorithm

Algorithm类实现极小极大算法,包括

  1. 局面评估函数,对当前局面打分,越高对max越有利,越低对min越有利
  2. min()方法
  3. max()方法
  4. 获得一个好的走步

双连二 50分

ReversiView

ReversiView继承自SurfaceView,实现棋盘的界面,在该类定义棋盘界面的绘制、更新等操作。

平局 0分

RenderThread

RenderThread继承自Thread,是控制ReversiView以一定fps更新、重绘界面的线程。

不分胜负 1

具体实现

其中如果评分时不分胜负则还会继续搜索,直到找到其他三种状态、

棋盘表示

byte[][]二维数组存储棋盘,-1表示有黑子,1表示有白子,0表示棋格为空

利用博弈树时,由于结果可能很多 所以需要进行必要剪枝,算法思路如下:

游戏规则类Rule的实现

提供几个关于游戏规则的静态方法。

  1. min电脑AI下棋时,如果考虑步数为0,则代表直接返回当前棋盘估值w(值越大代表对max越有优势,越小则代表对min越有优势,w=maxW-minW)。

判断某一个位置是否位于棋盘内

public static boolean isLegal(int row, int col) {
    return row >= 0 && row < 8 && col >= 0 && col < 8;
}
  1. 如果考虑步数为N,先获取min电脑可以下棋的位置steps。

  2. 对于可以下棋的一步step,电脑AI下棋到step的第row行,第column列。

  3. 如果这时候min电脑已经赢了,则把棋盘回退一步,返回棋盘估值和下棋位置,不用再考虑其他走法了。

  4. 否则,min需要在每一种走法里面,选择一种走法,令max人类走N-1步之后,自己的优势保持最大(即w值最小)。

  5. 什么是alpha-beta剪枝呢?就是如果max人类当前一种走法1至少可以获取alpha优势,而另一种走法2,min电脑的一步棋则可能让人类获取比alpha更小的优势,那么max人类肯定不会选择走法2,所以计算在计算min电脑的走法时,min电脑的其他走法就不用再计算了。

  6. 最后min电脑经过steps.length种走法对比之后,选择w值最小的一种走法,把棋盘回退一步,并返回棋盘估值和下棋位置。

  7. max走法类似,人类会选择w值最大的走法下棋,所以max函数和min函数分别代表人和AI下棋,互相递归调用,直到递归到步数为0时返回N步之后的估值。

判断某一方在某个位置落子是否合法

即判断该子是否能与己方棋子在某个方向上夹住敌方棋子。

public static boolean isLegalMove(byte[][] chessBoard, Move move, byte chessColor) {
        int i, j, dirx, diry, row = move.row, col = move.col;
        if (!isLegal(row, col) || chessBoard[row][col] != Constant.NULL)
            return false;
        for (dirx = -1; dirx < 2; dirx++) {
            for (diry = -1; diry < 2; diry++) {
                if (dirx == 0 && diry == 0) continue;
                int x = col + dirx, y = row + diry;
                if (isLegal(y, x) && chessBoard[y][x] == (-chessColor)) {
                    for (i = row + diry * 2, j = col + dirx * 2; isLegal(i, j); i += diry, j += dirx) {
                        if (chessBoard[i][j] == (-chessColor)) {
                            continue;
                        } else if (chessBoard[i][j] == chessColor) {
                            return true;
                        } else {
                            break;
                        }
                    }
                }
            }
        }
        return false;
}

如果一棵树博弈树的每个内部结点的第一个子结点都返回最优的解,那么称这棵树是良序的.对一个良序的博弈树,Alpha-Beta算法会修剪一些无必要搜索的子树,修剪之后的树就称为最小树(minimal
tree,或者critical
tree).当博弈树是良序的时候,Alpha-Beta算法所需要搜索子树都包含在这个最小树中.按照上述结点的分类方法,最小树中的所有结点的类型都是已定的,因为那些类型为undefined的结点都会被剪枝.

某一方走一步子

将各个方向上被翻转的棋子的颜色改变,并返回这些棋子在棋盘的位置,方便显示翻转动画。

public static List<Move> move(byte[][] chessBoard, Move move, byte chessColor) {
    int row = move.row;
    int col = move.col;
    int i, j, temp, m, n, dirx, diry;
    List<Move> moves = new ArrayList<Move>();
    for (dirx = -1; dirx < 2; dirx++) {
        for (diry = -1; diry < 2; diry++) {
            if (dirx == 0 && diry == 0)
                continue;
            temp = 0;
            int x = col + dirx, y = row + diry;
            if (isLegal(y, x) && chessBoard[y][x] == (-chessColor)) {
                temp++;
                for (i = row + diry * 2, j = col + dirx * 2; isLegal(i, j); i += diry, j += dirx) {
                    if (chessBoard[i][j] == (-chessColor)) {
                        temp++;
                        continue;
                    } else if (chessBoard[i][j] == chessColor) {
                        for (m = row + diry, n = col + dirx; m <= row + temp && m >= row - temp && n <= col + temp
                                && n >= col - temp; m += diry, n += dirx) {
                            chessBoard[m][n] = chessColor;
                            moves.add(new Move(m, n));
                        }
                        break;
                    } else
                        break;
                }
            }
        }
    }
    chessBoard[row][col] = chessColor;
    return moves;
}

图片 6

获取某一方当前全部合法的落子位置

public static List<Move> getLegalMoves(byte[][] chessBoard, byte chessColor) {
    List<Move> moves = new ArrayList<Move>();
    Move move = null;
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 8; col++) {
            move = new Move(row, col);
            if (Rule.isLegalMove(chessBoard, move, chessColor)) {
                moves.add(move);
            }
        }
    }
    return moves;
}

均匀博弈树及其最小树

统计玩家和AI的棋子个数

public static Statistic analyse(byte[][] chessBoard, byte playerColor) {

    int PLAYER = 0;
    int AI = 0;
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            if (chessBoard[i][j] == playerColor)
                PLAYER += 1;
            else if (chessBoard[i][j] == (byte)-playerColor)
                AI += 1;
        }
    }
    return new Statistic(PLAYER, AI);
}

如果一个博弈树的所有内部结点(interior
node)具有相同的分支因子,且所有的根结点到叶结点的深度相同,那么该搜索树就是一个均匀的(uniform).对于一个深度为d的均匀良序博弈树的的最小树,从根结点到叶结点,经历了d个边.设第i个边是其父结点的第图片 7个分支.将所有图片 8按照“.”连接起来形成一个串:图片 9.设图片 10为该串中第一个大于1的值.如果不存在图片 11,即所有的图片 12都为1,则该叶结点为PV结点.如果图片 13存在,那么图片 14对应边的子结点一定为CUT结点.这时如果d

游戏算法类Algorithm的实现

  • j是偶数,那么该串对应的叶结点为CUT结点,否则,如果d –
    j是奇数,该叶结点为ALL结点.

极大过程和极小过程

这两个过程的函数形式为:

private static MinimaxResult max(byte[][] chessBoard, int depth, int alpha, int beta, byte chessColor, int difficulty);
private static MinimaxResult min(byte[][] chessBoard, int depth, int alpha, int beta, byte chessColor, int difficulty);

chessBoard为棋盘;depth为博弈树搜索深度;alpha和beta用于alpha-beta剪枝,在max方法中alpha不断更新为局面评分的较大值,在min方法中beta不断更新为局面评分的较小值,当alpha
>=
beta时就进行剪枝;chessColor表示棋子颜色;difficulty表示游戏难度,对应于不同的AI水平。

由于黑子先行,黑子总是调用max()方法,白子调用min()方法。

下面以极大过程为例。

如果深度为0,只要返回当前局面评分即可。如果双方均没有步可走,表示已经达到最终局面,返回该局面评分。如果仅单方无处可走,调用min递归即可。

正常情况下有步可走,遍历每个合法的走步,如果alpha大于等于beta,剪枝直接break,否则走步并递归。

best是当前max节点维护的一个最佳值,调用的min方法的alpha是取得alpha和best的较大值。

private static MinimaxResult max(byte[][] chessBoard, int depth, int alpha, int beta, byte chessColor, int difficulty) {
    if (depth == 0) {
        return new MinimaxResult(evaluate(chessBoard, difficulty), null);
    }
    List<Move> legalMovesMe = Rule.getLegalMoves(chessBoard, chessColor);
    if (legalMovesMe.size() == 0) {
        if (Rule.getLegalMoves(chessBoard, (byte)-chessColor).size() == 0) {
            return new MinimaxResult(evaluate(chessBoard, difficulty), null);
        }
        return min(chessBoard, depth, alpha, beta, (byte)-chessColor, difficulty);
    }
    byte[][] tmp = new byte[8][8];
    Util.copyBinaryArray(chessBoard, tmp);
    int best = Integer.MIN_VALUE;
    Move move = null;

    for (int i = 0; i < legalMovesMe.size(); i++) {
        alpha = Math.max(best, alpha);
        if(alpha >= beta){
            break;
        }
        Rule.move(chessBoard, legalMovesMe.get(i), chessColor);
        int value = min(chessBoard, depth - 1, Math.max(best, alpha), beta, (byte)-chessColor, difficulty).mark;
        if (value > best) {
            best = value;
            move = legalMovesMe.get(i);
        }
        Util.copyBinaryArray(tmp, chessBoard);
    }
    return new MinimaxResult(best, move);
}

private static MinimaxResult min(byte[][] chessBoard, int depth, int alpha, int beta, byte chessColor, int difficulty) {
    if (depth == 0) {
        return new MinimaxResult(evaluate(chessBoard, difficulty), null);
    }
    List<Move> legalMovesMe = Rule.getLegalMoves(chessBoard, chessColor);
    if (legalMovesMe.size() == 0) {
        if (Rule.getLegalMoves(chessBoard, (byte)-chessColor).size() == 0) {
            return new MinimaxResult(evaluate(chessBoard, difficulty), null);
        }
        return max(chessBoard, depth, alpha, beta, (byte)-chessColor, difficulty);
    }
    byte[][] tmp = new byte[8][8];
    Util.copyBinaryArray(chessBoard, tmp);
    int best = Integer.MAX_VALUE;
    Move move = null;

    for (int i = 0; i < legalMovesMe.size(); i++) {
        beta = Math.min(best, beta);
        if(alpha >= beta){
            break;
        }
        Rule.move(chessBoard, legalMovesMe.get(i), chessColor);
        int value = max(chessBoard, depth - 1, alpha, Math.min(best, beta), (byte)-chessColor, difficulty).mark;
        if (value < best) {
            best = value;
            move = legalMovesMe.get(i);
        }
        Util.copyBinaryArray(tmp, chessBoard);
    }
    return new MinimaxResult(best, move);
}

对一个CUT类型的叶结点,它对应的串存在着性质:对所有i,如果d –
j是偶数,则图片 15为1.这种串(除了全1的串)和CUT叶结点一一对应.这种串的个数为图片 16,故此CUT叶结点的个数也为图片 17.

alpha-beta剪枝原理

先解释下alpha和beta的物理含义,alpha表示max节点迄今为止的最佳局面评分,beta表示min节点迄今为止的最佳局面评分。

举个例子见下图(数值为虚构),假设深度是两层,每个结点有两行数字,上方的两个数分别是alpha和beta,表示作为参数传到该层的alpha和beta。下方的数表示了该节点best的更新过程。

图片 18

看图中第一个红色的叉号,该位置处会更新beta为正无穷和2的较小值,即2,导致alpha大于等于beta成立,发生剪枝,对应于min方法中相应位置处的break操作。

同样,对一个ALL类型的叶结点,它对应的串存在着性质:对所有i,如果d –
j是奇数,那么为1.这样的串(除了全1的串)和ALL叶结点一一对应.这样的串的个数为图片 19,所以ALL叶结点的个数为图片 20.再加上PV叶结点,一个最小树包含的叶结点个数为图片 21,这也是在均匀博弈树中Alpha-Beta算法搜需要搜索的最少叶结点个数.

获得AI计算出的最佳走步

该方法用于AI走步以及提示功能。

public static Move getGoodMove(byte[][] chessBoard, int depth, byte chessColor, int difficulty) {
        if (chessColor == Constant.BLACK)
            return max(chessBoard, depth, Integer.MIN_VALUE, Integer.MAX_VALUE, chessColor, difficulty).move;
        else
            return min(chessBoard, depth, Integer.MIN_VALUE, Integer.MAX_VALUE, chessColor, difficulty).move;
}

参考博弈树建立代码。

局面评估函数

局面评估函数决定了AI水平的高低。对应于不同的AI等级,设计了不同的评估函数。

菜鸟级别只关注棋子个数,新手、入门、棋手3个级别不仅关注棋子的个数,而且关注特殊位置的棋子(边、角),棋士和大师级别在棋子个数、边角之外还考虑了行动力,即对方下轮可选的下子位置的个数,宗师和棋圣考虑稳定度和行动力。稳定度将在下一小节介绍。

private static int evaluate(byte[][] chessBoard, int difficulty) {
        int whiteEvaluate = 0;
        int blackEvaluate = 0;
        switch (difficulty) {
        case 1:
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    if (chessBoard[i][j] == WHITE) {
                        whiteEvaluate += 1;
                    } else if (chessBoard[i][j] == BLACK) {
                        blackEvaluate += 1;
                    }
                }
            }
            break;
        case 2:
        case 3:
        case 4:
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    if ((i == 0 || i == 7) && (j == 0 || j == 7)) {
                        if (chessBoard[i][j] == WHITE) {
                            whiteEvaluate += 5;
                        } else if (chessBoard[i][j] == BLACK) {
                            blackEvaluate += 5;
                        }
                    } else if (i == 0 || i == 7 || j == 0 || j == 7) {
                        if (chessBoard[i][j] == WHITE) {
                            whiteEvaluate += 2;
                        } else if (chessBoard[i][j] == BLACK) {
                            blackEvaluate += 2;
                        }
                    } else {
                        if (chessBoard[i][j] == WHITE) {
                            whiteEvaluate += 1;
                        } else if (chessBoard[i][j] == BLACK) {
                            blackEvaluate += 1;
                        }
                    }
                }
            }
            break;
        case 5:
        case 6:
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    if ((i == 0 || i == 7) && (j == 0 || j == 7)) {
                        if (chessBoard[i][j] == WHITE) {
                            whiteEvaluate += 5;
                        } else if (chessBoard[i][j] == BLACK) {
                            blackEvaluate += 5;
                        }
                    } else if (i == 0 || i == 7 || j == 0 || j == 7) {
                        if (chessBoard[i][j] == WHITE) {
                            whiteEvaluate += 2;
                        } else if (chessBoard[i][j] == BLACK) {
                            blackEvaluate += 2;
                        }
                    } else {
                        if (chessBoard[i][j] == WHITE) {
                            whiteEvaluate += 1;
                        } else if (chessBoard[i][j] == BLACK) {
                            blackEvaluate += 1;
                        }
                    }
                }
            }
            blackEvaluate = blackEvaluate * 2 + Rule.getLegalMoves(chessBoard, BLACK).size();
            whiteEvaluate = whiteEvaluate * 2 + Rule.getLegalMoves(chessBoard, WHITE).size();
            break;
        case 7:
        case 8:
            /**
             * 稳定度
             */
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    int weight[] = new int[] { 2, 4, 6, 10, 15 };
                    if (chessBoard[i][j] == WHITE) {
                        whiteEvaluate += weight[getStabilizationDegree(chessBoard, new Move(i, j))];
                    } else if (chessBoard[i][j] == BLACK) {
                        blackEvaluate += weight[getStabilizationDegree(chessBoard, new Move(i, j))];
                    }
                }
            }
            /**
             * 行动力
             */
            blackEvaluate += Rule.getLegalMoves(chessBoard, BLACK).size();
            whiteEvaluate += Rule.getLegalMoves(chessBoard, WHITE).size();
            break;
        }
        return blackEvaluate - whiteEvaluate;
}
int gameState(char _board[9])
{
    int state;
    static int table[][3] = 
    {
        {0, 1, 2},
         {3, 4, 5},
          {6, 7, 8},
         {0, 3, 6},
          {1, 4, 7},
          {2, 5, 8},
          {0, 4, 8},
          {2, 4, 6},
      };
    char chess = _board[0];
     for (char i = 1; i < 9; ++i)
      {
         chess &= _board[i];
     }
      bool isFull = 0 != chess;
     bool isFind = false;
    for (int i = 0; i < sizeof(table) / sizeof(int[3]); ++i)
    {
        chess = _board[table[i][0]];
          int j;
        for (j = 1; j < 3; ++j)
            if (_board[table[i][j]] != chess)
                break;
            if (chess != empty && j == 3)
            {
                isFind = true;        
                break;
            }
        }
        if (isFind)
            //got win or lose
            state = chess == o ? WIN : LOSE;
        else
        {
            if (isFull)
                //all position has been set without win or lose
                return DRAW;
            else
            {
                //finds[0] -> 'o', finds[1] -> 'x'
                int finds[2] = {0, };
                for (int i = 0; i < sizeof(table) / sizeof(int[3]); ++i)
                {
                    bool findEmpty = false;
                    chess = 0xff;
                    int j;
                    for (j = 0; j < 3; ++j)
                        if (_board[table[i][j]] == empty && !findEmpty)
                            findEmpty = true;
                        else
                         chess &= _board[table[i][j]];
                if ((chess == o || chess == x) && findEmpty)
                {
                    isFind = true;        
                    if (o == chess)
                         ++finds[0];
                     else
                        ++finds[1];
                 }
            }
             if (finds[0] > 1 && finds[1] < 1)
                 //2 'o' has been founded twice in row, column or diagonal direction
                 state = -(INFINITY / 2) * finds[0];
             else if (finds[1] > 1 && finds[0] < 1)
                 //2 'x' has been founded twice in row, column or diagonal direction
                 state = INFINITY / 2 * finds[1];
             else
                 //need to search more.
                 state = INPROGRESS;
         }
    }
     return state;
 }

稳定度计算

我们知道,在黑白棋中,棋盘四角的位置一旦占据是不可能再被翻转的,因此这几个位置上的子必然是稳定子,而边上的子只有可能沿边的方向被翻转,稳定的程度高于中间的位置上的子。

因此,试图给每个子定义一个稳定度,描述该子不被翻转的稳定程度。

一共有四个方向,即左-右,上-下,左上-右下,右上-左下。举个例子,下面代码中的 (drow[0][0], dcol[0][0])表示向左移动一个单位的向量,(drow[0][1], dcol[0][1])表示向右移动一个单位的向量。

对于棋盘中某个子的位置,向左找到第一个不是该颜色的位置(可以是出界),再向右找到第一个不是该颜色的位置(可以是出界),如果这两个位置至少有一个出界,或者两个均为敌方棋子,稳定度加1。

对于另外三个方向作同样操作。可以看到,角上的棋子的稳定度必然为4,其他位置则根据具体情况并不恒定不变。

private static int getStabilizationDegree(byte[][] chessBoard, Move move) {
        int chessColor = chessBoard[move.row][move.col];
        int drow[][], dcol[][];
        int row[] = new int[2], col[] = new int[2];
        int degree = 0;

        drow = new int[][] { { 0, 0 }, { -1, 1 }, { -1, 1 }, { 1, -1 } };
        dcol = new int[][] { { -1, 1 }, { 0, 0 }, { -1, 1 }, { -1, 1 } };

        for (int k = 0; k < 4; k++) {
            row[0] = row[1] = move.row;
            col[0] = col[1] = move.col;
            for (int i = 0; i < 2; i++) {
                while (Rule.isLegal(row[i] + drow[k][i], col[i] + dcol[k][i])
                        && chessBoard[row[i] + drow[k][i]][col[i] + dcol[k][i]] == chessColor) {
                    row[i] += drow[k][i];
                    col[i] += dcol[k][i];
                }
            }
            if (!Rule.isLegal(row[0] + drow[k][0], col[0] + dcol[k][0])
                    || !Rule.isLegal(row[1] + drow[k][1], col[1] + dcol[k][1])) {
                degree += 1;
            } else if (chessBoard[row[0] + drow[k][0]][col[0] + dcol[k][0]] == (-chessColor)
                    && chessBoard[row[1] + drow[k][1]][col[1] + dcol[k][1]] == (-chessColor)) {
                degree += 1;
            }
        }
        return degree;
}

 

项目开源

完整项目已经开源到github上。github项目主页: Reversi

发表评论

电子邮件地址不会被公开。 必填项已用*标注