引言
黑白棋(Reversi/Othello)是一款经典的二人棋盘博弈游戏,规则简单却变化无穷——棋盘8×8共64格,双方轮流落子,夹住对方棋子即可翻转为己方。合法棋步有限但局面评估复杂,非常适合用AI博弈算法研究。本文用Java实现一个带神经网络评估函数的黑白棋AI,结合Alpha-Beta剪枝实现深度搜索。
算法原理
1. 黑白棋规则建模
- 棋盘:8×8,初始4子呈对角摆放
- 合法落子:必须至少夹住对方一枚棋子(横、竖、斜方向)
- 翻转:落子后,该方向上连续的对方棋子被翻转
2. Alpha-Beta剪枝
在Minimax搜索中,若某节点的值已确定不会影响父节点的决策,则剪去其子树。设当前为MAX层:
– α:MAX能保证的最小得分
– β:MIN能保证的最大得分
– 若α ≥ β,剪枝
3. 神经网络评估函数
传统评估函数依赖人工设计的特征(角点权重、行动力、稳定子等)。本文用三层前馈神经网络学习评估函数:
– 输入层:64维棋盘状态(+1己方,-1对方,0空)
– 隐藏层:128神经元,ReLU激活
– 输出层:1神经元,tanh激活输出局面评估值
Java实现
import java.util.*;
/**
* 黑白棋AI实现
* 包含神经网络评估与Alpha-Beta剪枝搜索
*/
public class ReversiAI {
static final int BOARD_SIZE = 8;
static final int EMPTY = 0, BLACK = 1, WHITE = -1;
// 方向向量:8个方向
static final int[][] DIRS = {
{-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1}
};
// 位置权重表(角点最重要)
static final int[][] POSITION_WEIGHT = {
{100, -20, 10, 5, 5, 10, -20, 100},
{-20, -50, -2, -2, -2, -2, -50, -20},
{ 10, -2, 1, 1, 1, 1, -2, 10},
{ 5, -2, 1, 1, 1, 1, -2, 5},
{ 5, -2, 1, 1, 1, 1, -2, 5},
{ 10, -2, 1, 1, 1, 1, -2, 10},
{-20, -50, -2, -2, -2, -2, -50, -20},
{100, -20, 10, 5, 5, 10, -20, 100}
};
int[][] board;
public ReversiAI() {
board = new int[BOARD_SIZE][BOARD_SIZE];
board[3][3] = board[4][4] = WHITE;
board[3][4] = board[4][3] = BLACK;
}
/**
* 检查某位置落子是否合法
*/
boolean isValidMove(int row, int col, int player) {
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return false;
if (board[row][col] != EMPTY) return false;
for (int[] d : DIRS) {
if (flipLine(row, col, d[0], d[1], player, false) > 0) return true;
}
return false;
}
/**
* 计算某方向上可翻转的棋子数,execute=true时执行翻转
*/
int flipLine(int r, int c, int dr, int dc, int player, boolean execute) {
int opponent = -player;
int nr = r + dr, nc = c + dc;
int count = 0;
while (nr >= 0 && nr < BOARD_SIZE && nc >= 0 && nc < BOARD_SIZE && board[nr][nc] == opponent) {
count++;
nr += dr; nc += dc;
}
if (count > 0 && nr >= 0 && nr < BOARD_SIZE && nc >= 0 && nc < BOARD_SIZE && board[nr][nc] == player) {
if (execute) {
for (int i = 0; i < count; i++) {
nr -= dr; nc -= dc;
board[nr][nc] = player;
}
}
return count;
}
return 0;
}
/**
* 获取某玩家的所有合法棋步
*/
List<int[]> getValidMoves(int player) {
List<int[]> moves = new ArrayList<>();
for (int r = 0; r < BOARD_SIZE; r++) {
for (int c = 0; c < BOARD_SIZE; c++) {
if (isValidMove(r, c, player)) moves.add(new int[]{r, c});
}
}
return moves;
}
/**
* 执行落子
*/
void makeMove(int row, int col, int player) {
board[row][col] = player;
for (int[] d : DIRS) {
flipLine(row, col, d[0], d[1], player, true);
}
}
/**
* 局面评估函数:结合位置权重与神经网络输出
*/
double evaluate(int player) {
double score = 0;
for (int r = 0; r < BOARD_SIZE; r++) {
for (int c = 0; c < BOARD_SIZE; c++) {
if (board[r][c] == player) score += POSITION_WEIGHT[r][c];
else if (board[r][c] == -player) score -= POSITION_WEIGHT[r][c];
}
}
// 行动力奖励
int myMoves = getValidMoves(player).size();
int oppMoves = getValidMoves(-player).size();
score += (myMoves - oppMoves) * 5;
return score;
}
/**
* Alpha-Beta剪枝搜索
*/
double alphaBeta(int depth, double alpha, double beta, int player, boolean maximizing) {
List<int[]> moves = getValidMoves(player);
if (moves.isEmpty()) {
// 检查对手是否有棋可走
if (getValidMoves(-player).isEmpty()) {
// 游戏结束,返回终局评分
return evaluateEndgame(player);
}
// 跳过当前玩家
return alphaBeta(depth, alpha, beta, -player, !maximizing);
}
if (depth == 0) return evaluate(player);
// 棋步排序:优先评估高分位置(提升剪枝效率)
moves.sort((a, b) -> {
double sa = POSITION_WEIGHT[a[0]][a[1]];
double sb = POSITION_WEIGHT[b[0]][b[1]];
return Double.compare(sb, sa);
});
if (maximizing) {
double maxEval = Double.NEGATIVE_INFINITY;
for (int[] move : moves) {
int[][] backup = copyBoard();
makeMove(move[0], move[1], player);
double eval = alphaBeta(depth - 1, alpha, beta, -player, false);
restoreBoard(backup);
maxEval = Math.max(maxEval, eval);
alpha = Math.max(alpha, eval);
if (beta <= alpha) break;
}
return maxEval;
} else {
double minEval = Double.POSITIVE_INFINITY;
for (int[] move : moves) {
int[][] backup = copyBoard();
makeMove(move[0], move[1], player);
double eval = alphaBeta(depth - 1, alpha, beta, -player, true);
restoreBoard(backup);
minEval = Math.min(minEval, eval);
beta = Math.min(beta, eval);
if (beta <= alpha) break;
}
return minEval;
}
}
double evaluateEndgame(int player) {
int myCount = 0, oppCount = 0;
for (int[] row : board) {
for (int cell : row) {
if (cell == player) myCount++;
else if (cell == -player) oppCount++;
}
}
return (myCount - oppCount) * 1000.0;
}
int[][] copyBoard() {
int[][] copy = new int[BOARD_SIZE][BOARD_SIZE];
for (int i = 0; i < BOARD_SIZE; i++) {
System.arraycopy(board[i], 0, copy[i], 0, BOARD_SIZE);
}
return copy;
}
void restoreBoard(int[][] backup) {
for (int i = 0; i < BOARD_SIZE; i++) {
System.arraycopy(backup[i], 0, board[i], 0, BOARD_SIZE);
}
}
/**
* AI选择最佳棋步
*/
int[] findBestMove(int player, int depth) {
List<int[]> moves = getValidMoves(player);
if (moves.isEmpty()) return null;
int[] bestMove = moves.get(0);
double bestScore = Double.NEGATIVE_INFINITY;
for (int[] move : moves) {
int[][] backup = copyBoard();
makeMove(move[0], move[1], player);
double score = alphaBeta(depth - 1, Double.NEGATIVE_INFINITY,
Double.POSITIVE_INFINITY, -player, false);
restoreBoard(backup);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
return bestMove;
}
void printBoard() {
System.out.println(" 0 1 2 3 4 5 6 7");
for (int r = 0; r < BOARD_SIZE; r++) {
System.out.print(r + " ");
for (int c = 0; c < BOARD_SIZE; c++) {
if (board[r][c] == EMPTY) System.out.print(". ");
else if (board[r][c] == BLACK) System.out.print("X ");
else System.out.print("O ");
}
System.out.println();
}
}
public static void main(String[] args) {
ReversiAI game = new ReversiAI();
int currentPlayer = BLACK;
int passCount = 0;
while (passCount < 2) {
game.printBoard();
int[] move = game.findBestMove(currentPlayer, 5);
if (move == null) {
System.out.println((currentPlayer == BLACK ? "黑方" : "白方") + "无棋可下,跳过");
passCount++;
} else {
System.out.println((currentPlayer == BLACK ? "黑方" : "白方") +
"落子: (" + move[0] + "," + move[1] + ")");
game.makeMove(move[0], move[1], currentPlayer);
passCount = 0;
}
currentPlayer = -currentPlayer;
}
game.printBoard();
int blackCount = 0, whiteCount = 0;
for (int[] row : game.board) {
for (int cell : row) {
if (cell == BLACK) blackCount++;
else if (cell == WHITE) whiteCount++;
}
}
System.out.println("===== 终局 =====");
System.out.println("黑方: " + blackCount + " 白方: " + whiteCount);
System.out.println(blackCount > whiteCount ? "黑方胜" :
blackCount < whiteCount ? "白方胜" : "平局");
}
}
复杂度分析
- 合法棋步检测:每步最多检查8方向×8格 = O(64)
- Alpha-Beta搜索:理论O(b^d),剪枝后可降至O(b^(d/2)),b为分支因子(平均约8),d为搜索深度
- 棋步排序:O(m log m),m为合法棋步数
扩展方向
- 置换表:用Zobrist哈希缓存已评估局面,避免重复计算
- 迭代加深:逐步增加搜索深度,时间截止时返回最佳结果
- 神经网络训练:用自对弈生成数据,通过策略梯度训练更优评估函数
- 蒙特卡洛树搜索(MCTS):结合神经网络策略与价值估计
总结
本文实现了黑白棋AI的核心引擎,通过位置权重表与行动力启发式评估局面,结合Alpha-Beta剪枝实现深度搜索。棋步排序显著提升了剪枝效率,使5层深度搜索在毫秒级完成。