- 博弈理论
- Bash Game: 一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
- 先手留(m+1)的倍数个取胜
- 题目
- SDUTACM集训队第二次选拔赛 B – 哈士奇“下棋” //if(n%(m+1)==0) return first defeat;
- Wythoff Game:两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
- 面对非奇异局势,设奇异局势,先手必胜
- 判断奇异局势:ak = [k(√5+1)/2],bk= ak+k(k=0,1,2…n)
- 算法: a[k] = (int) ((b[k] – a[k])*1.618)//int:强制转换–不大于这个数值的最大整数
- 题目
- hdu-1527 取石子游戏 //if(ak == (int)(bk – ak)*(sqrt(5.0) + 1) / 2)return first defeat;
- Nimm Game:三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
- 取胜算法:设置奇异局势,则a(+)b(+)c = 0//(+)为按位模2加 || xor
- sort(a,b,c)// a< b<c
- c=a+b
- a(+)b(+)c = a(+)b (+) (a(+)b) = (a(+) a) (+) (b(+)b) = 0 (+) 0 =0
- 判断:把每堆物品数全部异或起来,如果得到的值为0,那么先手必败即:int temp = 0;temp ^= allN; if (temp == 0) return first defeat;
- 取胜算法:设置奇异局势,则a(+)b(+)c = 0//(+)为按位模2加 || xor
- Fibonacci Nim:一堆个数为n(n>=2)的石子,游戏双方轮流取石子,
1)先手不能在第一次把所有的石子取完,至少取1颗;
2)之后每次可以取的石子数至少为1,至多为对手刚取的石子数的2倍。
约定取走最后一个石子的人为赢家。
- n为fibonacci数,先手必败
- 阶梯Nim
- Bash Game: 一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
- 黑白棋博弈算法实现(极大极小+alpha beta剪枝)
- 游戏规则
-
/** * 实现游戏规则 * 1.判断某一步是否合法 * 2.获取所有合法走步 * 3.走一步 --翻转敌方棋子 * 4.统计两方棋子个数 */public class Rule { /** * 某步落子是否在棋盘边界内 * @param x 落子位置横坐标 * @param y 落子位置纵坐标 * @return 合法性 */ public static boolean isLegal(int x,int y) { return x >= 0 && x < 8 && y >= 0 && y < 8; } /** * 1.判断在某个位置落子是否合法 -是否与己方棋子在某方向上夹住敌方棋子 * @param chessBoard 当前棋盘 * @param position 某位置 * @param player 玩家标识 * @return 合法性 */ public static boolean isLegalMove(int[][] chessBoard, Position position, int player) { int x = position.getX(); //待搜索的棋子位置以x,y坐标表示 int y = position.getY(); //不在棋盘范围 或者该位子已经有棋子了 直接返回非法 if (!isLegal(x,y) || chessBoard[x][y] != Constant.NULL.getValue()) return false; //取当前棋子的对手棋子颜色 int enemyPlayer = player == Constant.BLACK.getValue() ? Constant.WHITE.getValue() : Constant.BLACK.getValue(); //遍历八个方向 int dirx,diry;//坐标轴x,y 取值范围 -1 0 1 for (dirx = -1; dirx <= 1 ; dirx++) { for (diry = -1; diry <= 1 ; diry++) { if (dirx == 0 && diry == 0) //原点不搜 continue; int willGoX = x + dirx; int willGoY = y + diry; //从此方向的移动的下一步棋在棋盘上 且与当前棋子颜色相反 if (isLegal(willGoX,willGoY) && chessBoard[willGoX][willGoY] == enemyPlayer) { //在该方向继续搜索,直到找到己方棋子颜色为止 for (int i = willGoX + dirx,j = willGoY + diry;isLegal(i,j);i+= dirx,j+= diry) { if (chessBoard[i][j] == enemyPlayer) { //是敌方棋子,继续搜 continue; } else if (chessBoard[i][j] == player) { //己方棋子,返回true return true; }else { //为空 则不合法 跳出对该方向的搜索 break; } } } } } //八个方向都没找到合法位置 return false; } /** * 2.获取所有合法走步 * @param chessBoard 当前棋盘 * @param player 玩家标识 * @return 当前玩家所有合法走步 */ public List<Position> getAllLegalMoves(int[][] chessBoard,int player) { List<Position> legalMoves = new ArrayList<>(); for (int i = 0; i < chessBoard.length; i++) { for (int j = 0; j < chessBoard[0].length; j++) { if (chessBoard[i][j] == Constant.NULL.getValue() && isLegalMove(chessBoard,new Position(i,j),player)){ legalMoves.add(new Position(i,j)); } } } return legalMoves; } /** * 3.走一步,翻转敌方被夹住棋子 * @param board 当前棋盘 * @param position 将走步的位置 * @param player 玩家标识 * @return 翻转后的新棋盘 */ public int[][] move(int[][] board, Position position,int player) { //拷贝当前棋盘到新棋盘进行翻转 int[][] newBoard = new int[board.length][board[0].length]; for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { newBoard[i][j] = board[i][j]; } } int x = position.getX(); int y = position.getY(); //搜索当前位置八个方向,找到直线上被本方夹住的棋子 翻转 //取当前棋子的对手棋子颜色 int enemyPlayer = player == Constant.BLACK.getValue() ? Constant.WHITE.getValue() : Constant.BLACK.getValue(); //遍历八个方向 int dirx,diry;//坐标轴x,y 取值范围 -1 0 1 for (dirx = -1; dirx <= 1 ; dirx++) { for (diry = -1; diry <= 1 ; diry++) { if (dirx == 0 && diry == 0) //原点不搜 continue; int willGoX = x + dirx; int willGoY = y + diry; //从此方向的移动的下一步棋在棋盘上 且与当前棋子颜色相反 if (isLegal(willGoX,willGoY) && board[willGoX][willGoY] == enemyPlayer) { //在该方向继续搜索,直到找到己方棋子颜色为止 for (int i = willGoX + dirx,j = willGoY + diry;isLegal(i,j);i+= dirx,j+= diry) { if (board[i][j] == enemyPlayer) { //是敌方棋子,继续搜 continue; } else if (board[i][j] == player) { //己方棋子 //将从x,y 到 i,j 沿线棋子全部置为己方棋子颜色 setBoardColor(newBoard,x,y,i,j,player); break; }else { //为空 则不合法 跳出对该方向的搜索 break; } } } } } return newBoard; } /** * 将从x,y 到 i,j 沿线所夹敌方棋子全部置为己方棋子颜色 * @param newBoard * @param x * @param y * @param i * @param j * @param player */ private void setBoardColor(int[][] newBoard, int x, int y, int i, int j, int player) { //判断i,j 相对 x,y 的方向 //上 if (i == x && j > y) { //将沿上方向所夹对手棋子颜色置为己方棋子颜色 for (int k = y + 1 ; k < j; k++) { newBoard[x][k] = player; } } //右上 if (i > x && j > y) { for (int k = x + 1; k < i; k++) { for (int l = y + 1; l < j; l++) { newBoard[k][l] = player; } } } //右 if (i > x && j == y) { for (int k = x + 1; k < i; k++) { newBoard[k][y] = player; } } //右下 if (i > x && j < y) { for (int k = x + 1; k < i; k++) { for (int l = y - 1; l > j ; l--) { newBoard[k][l] = player; } } } //下 if (i == x && j < y) { for (int k = y - 1; k > j; k--) { newBoard[x][k] = player; } } //左下 if (i < x && j < y) { for (int k = x - 1; k > i; k--) { for (int l = y - 1; l > j ; l--) { newBoard[k][l] = player; } } } //左 if (i < x && j == y) { for (int k = x - 1; k > i; k--) { newBoard[k][y] = player; } } //左上 if (i < x && j > y) { for (int k = x - 1; k > i ; k--) { for (int l = y + 1; l < y; l++) { newBoard[k][l] = player; } } } } /** * 4.统计棋子个数 * @param board 当前棋盘 * @param player 玩家标识 * @return 玩家棋子个数 */ private int countPiece(int[][] board,int player) { int count = 0; for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { if (board[i][j] == player) { count++; } } } return count; } }
-
- 实搜索算法
/** * 极大层搜索方法 * @param chessBoard 当前棋盘 * @param depth 博弈树搜索深度 * @param alpha 极大层更新值 * @param beta 极小层更新值 * @param player 玩家标识 * @return score 和 position的对象 */ private static MiniMaxResult max(int[][] chessBoard,int depth,int alpha,int beta,int player) { Rule rule = new Rule(); //深度为0 返回当前局面评分 if (depth == 0) return new MiniMaxResult(rule.evaluate(chessBoard,player),null); //获取当前玩家所有可行走步 List<Position> legalMovesMe = rule.getAllLegalMoves(chessBoard,player); //取对手棋子 int enemyPlayer = player == Constant.BLACK.getValue() ? Constant.WHITE.getValue() : Constant.BLACK.getValue(); //终局:双方均没有步可走 返回当前局面评分 if (legalMovesMe.size() == 0){ if (rule.getAllLegalMoves(chessBoard,enemyPlayer).size() == 0){ return new MiniMaxResult(rule.evaluate(chessBoard,player),null); } //单方无处可走 调用min递归 return min(chessBoard,depth,alpha,beta,enemyPlayer); } //拷贝当前棋盘到新棋盘 int[][] temp = new int[chessBoard.length][chessBoard[0].length]; for (int i = 0; i < chessBoard.length; i++) { for (int j = 0; j < chessBoard[0].length; j++) { temp[i][j] = chessBoard[i][j]; } } int best = Integer.MIN_VALUE; //当前max节点维护的一个最佳值,调用min方法的alpha = alpha 和best的较大的那个 Position position = null; //正常情况有步可走,遍历每个合法走步 //if alpha > beta 剪枝 break //否则走步递归 for (int i = 0; i < legalMovesMe.size(); i++) { alpha = Math.max(best,alpha); if (alpha >= beta) { break; //剪枝 } //开始走步 rule.move(temp,legalMovesMe.get(i),player); int value = min(chessBoard,depth - 1,Math.max(best,alpha),beta,enemyPlayer).getScore(); if (value > best) { best = value; position = legalMovesMe.get(i); //取分高的走步作为下一步走步 } //拷贝当前走步所得棋盘 for (int p = 0; p < chessBoard.length; p++) { for (int j = 0; j < chessBoard[0].length; j++) { chessBoard[p][j] = temp[p][j]; } } } return new MiniMaxResult(best,position); } private static MiniMaxResult min(int[][] chessBoard, int depth, int alpha, int beta, int player) { Rule rule = new Rule(); if (depth == 0) { return new MiniMaxResult(rule.evaluate(chessBoard,player),null); } List<Position> legalMovesMe = rule.getAllLegalMoves(chessBoard,player); //取对手棋子 int enemyPlayer = player == Constant.BLACK.getValue() ? Constant.WHITE.getValue() : Constant.BLACK.getValue(); if (legalMovesMe.size() == 0) { if (rule.getAllLegalMoves(chessBoard,enemyPlayer).size() == 0) { return new MiniMaxResult(rule.evaluate(chessBoard,player),null); } return max(chessBoard,depth,alpha,beta,enemyPlayer); } //拷贝当前棋盘到新棋盘 int[][] temp = new int[chessBoard.length][chessBoard[0].length]; for (int i = 0; i < chessBoard.length; i++) { for (int j = 0; j < chessBoard[0].length; j++) { temp[i][j] = chessBoard[i][j]; } } int best = Integer.MAX_VALUE; Position position = null; for (int i = 0; i < legalMovesMe.size(); i++) { beta = Math.min(best,beta); //beta取当前值和beta中较小的一个 if (alpha >= beta) { break; } rule.move(temp,legalMovesMe.get(i),player); int value = max(chessBoard,depth-1,alpha,Math.min(best,beta),enemyPlayer).getScore(); if (value < best) { best = value; position = legalMovesMe.get(i); } //拷贝当前走步所得棋盘 for (int p = 0; p < chessBoard.length; p++) { for (int j = 0; j < chessBoard[0].length; j++) { chessBoard[p][j] = temp[p][j]; } } } return new MiniMaxResult(best,position); }
- 评估函数
/** * 黑白棋评估函数 考虑评估因素: * 棋子个数 * 边角 赋边界位置权重 5 2 1 * 行动力 棋子合法下一步合法走步数量 * 稳定度 * @param chessBoard * @param player * @return */ public int evaluate(int[][] chessBoard,int player) { int playerScore = 0; int enemyScore = 0; //取对手棋子 int enemyPlayer = player == Constant.BLACK.getValue() ? Constant.WHITE.getValue() : Constant.BLACK.getValue(); /** * 棋子个数 */ for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if (chessBoard[i][j] == player) { playerScore += 1; } else if (chessBoard[i][j] == enemyPlayer) { enemyScore += 1; } } } /** * 边角 */ 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] == player) { playerScore += 5; } else if (chessBoard[i][j] == enemyPlayer) { enemyScore += 5; } } else if (i == 0 || i == 7 || j == 0 || j == 7) { if (chessBoard[i][j] == player) { playerScore += 2; } else if (chessBoard[i][j] == enemyPlayer) { enemyScore += 2; } } else { if (chessBoard[i][j] == player) { playerScore += 1; } else if (chessBoard[i][j] == enemyPlayer) { enemyScore += 1; } } } } /** * 稳定度 */ for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { int weight[] = new int[] { 2, 4, 6, 10, 15 }; if (chessBoard[i][j] == player) { playerScore += weight[getStabilizationDegree(chessBoard, new Position(i, j))]; } else if (chessBoard[i][j] == enemyPlayer) { enemyScore += weight[getStabilizationDegree(chessBoard, new Position(i, j))]; } } } /** * 行动力 */ playerScore += getAllLegalMoves(chessBoard, player).size(); enemyScore += getAllLegalMoves(chessBoard, enemyPlayer).size(); return playerScore - enemyScore; } /** * 计算某位置处 player棋子稳定度 * 若player 某一方向直到不为空(包括边界)的两个位置 左-右 上-下 左上-右下 右上-左下 的某一个位置,至少有一个出界 || 两个均为敌方棋子 稳定度加一 * * @param board 当前棋盘 * @param position 当前棋子坐标 * @return 棋子稳定度 */ private static int getStabilizationDegree(int[][] board, Position position) { int player = board[position.getY()][position.getY()]; //敌方棋子 int enemy = player == Constant.BLACK.getValue() ? Constant.WHITE.getValue() : Constant.BLACK.getValue(); 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] = position.getX(); col[0] = col[1] = position.getY(); for (int i = 0; i < 2; i++) { while (Rule.isLegal(row[i] + drow[k][i], col[i] + dcol[k][i]) && board[row[i] + drow[k][i]][col[i] + dcol[k][i]] == player) { 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 (board[row[0] + drow[k][0]][col[0] + dcol[k][0]] == enemy && board[row[1] + drow[k][1]][col[1] + dcol[k][1]] == enemy) { degree += 1; } } return degree; }
-
参考实现:GitHub – huoxin4415/black-white-ai: 黑白棋AI
- 游戏规则