Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 128 additions & 26 deletions src/main/java/AI/MinimaxAI.java
Original file line number Diff line number Diff line change
@@ -1,39 +1,141 @@
package AI;

import GameEngine.CheckWin;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MinimaxAI {

private static final int DEPTH = 4;
private static final int WIN_SCORE = 1_000_000;

public int[] makeMove(int[][] board) {
int[] bestMove = minimax(board, 5, true); // 使用MiniMax算法搜索最佳下棋位置,这里设定搜索深度为5
return bestMove;
int size = board.length;
// 在副本上搜索,避免搜索过程中影响界面渲染
int[][] copy = deepCopy(board, size);
int[] result = minimax(copy, size, DEPTH, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
int row = result[1], col = result[2];
// 兜底:如果没有候选点(棋盘为空),落中心
if (row == -1) { row = size / 2; col = size / 2; }
return new int[]{row, col};
}

/** 返回 [score, row, col] */
private int[] minimax(int[][] board, int size, int depth,
int alpha, int beta, boolean maximizing) {
if (CheckWin.checkWin(board, 2)) return new int[]{WIN_SCORE, -1, -1};
if (CheckWin.checkWin(board, 1)) return new int[]{-WIN_SCORE, -1, -1};
if (depth == 0 || CheckWin.isBoardFull(board))
return new int[]{evaluate(board, size), -1, -1};

List<int[]> candidates = getCandidates(board, size);
if (candidates.isEmpty()) return new int[]{evaluate(board, size), -1, -1};

int bestRow = candidates.get(0)[0];
int bestCol = candidates.get(0)[1];
int bestScore = maximizing ? Integer.MIN_VALUE : Integer.MAX_VALUE;

for (int[] move : candidates) {
int r = move[0], c = move[1];
board[r][c] = maximizing ? 2 : 1;
int score = minimax(board, size, depth - 1, alpha, beta, !maximizing)[0];
board[r][c] = 0;

if (maximizing) {
if (score > bestScore) { bestScore = score; bestRow = r; bestCol = c; }
alpha = Math.max(alpha, bestScore);
} else {
if (score < bestScore) { bestScore = score; bestRow = r; bestCol = c; }
beta = Math.min(beta, bestScore);
}
if (beta <= alpha) break; // Alpha-Beta 剪枝
}
return new int[]{bestScore, bestRow, bestCol};
}

private int[] minimax(int[][] board, int depth, boolean maximizingPlayer) {
// 在这里实现MiniMax算法,根据当前游戏状态计算最佳下棋位置
// 返回一个包含行和列的数组,例如 [row, col]

// 如果已经达到搜索深度,或者游戏结束,就返回当前局面的估值和下棋位置
// 否则,递归搜索子节点,根据最大化或最小化玩家选择合适的估值

// 示例代码,需要根据实际情况进行完善
int boardSize = board.length;
int[] bestMove = new int[]{-1, -1};
int bestValue = maximizingPlayer ? Integer.MIN_VALUE : Integer.MAX_VALUE;

for (int row = 0; row < boardSize; row++) {
for (int col = 0; col < boardSize; col++) {
if (board[row][col] == 0) {
board[row][col] = maximizingPlayer ? 2 : 1; // 假设AI是玩家2,对手是玩家1
int value = minimax(board, depth - 1, !maximizingPlayer)[0];
board[row][col] = 0; // 恢复原始状态

if ((maximizingPlayer && value > bestValue) || (!maximizingPlayer && value < bestValue)) {
bestValue = value;
bestMove[0] = row;
bestMove[1] = col;
/** 只考虑现有棋子周围 2 格内的空位作为候选点,大幅缩减搜索空间 */
private List<int[]> getCandidates(int[][] board, int size) {
Set<String> seen = new HashSet<>();
List<int[]> result = new ArrayList<>();
boolean hasAny = false;

for (int r = 0; r < size; r++) {
for (int c = 0; c < size; c++) {
if (board[r][c] == 0) continue;
hasAny = true;
for (int dr = -2; dr <= 2; dr++) {
for (int dc = -2; dc <= 2; dc++) {
int nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= size || nc < 0 || nc >= size) continue;
if (board[nr][nc] != 0) continue;
String key = nr + "," + nc;
if (seen.add(key)) result.add(new int[]{nr, nc});
}
}
}
}
if (!hasAny) result.add(new int[]{size / 2, size / 2});
return result;
}

/** 局面评估:AI(玩家2)得分 - 对手(玩家1)得分 */
private int evaluate(int[][] board, int size) {
return scoreForPlayer(board, size, 2) - scoreForPlayer(board, size, 1);
}

private int scoreForPlayer(int[][] board, int size, int player) {
int total = 0;
int[][] dirs = {{0, 1}, {1, 0}, {1, 1}, {1, -1}};
for (int r = 0; r < size; r++) {
for (int c = 0; c < size; c++) {
for (int[] d : dirs) {
total += scoreLine(board, size, r, c, d[0], d[1], player);
}
}
}
return total;
}

/** 对一条连线打分(只从起点开始,避免重复计算) */
private int scoreLine(int[][] board, int size,
int r, int c, int dr, int dc, int player) {
if (board[r][c] != player) return 0;
// 确认是连线的起点
int prevR = r - dr, prevC = c - dc;
if (prevR >= 0 && prevR < size && prevC >= 0 && prevC < size
&& board[prevR][prevC] == player) return 0;

// 统计连子数
int count = 0;
int cr = r, cc = c;
while (cr >= 0 && cr < size && cc >= 0 && cc < size && board[cr][cc] == player) {
count++;
cr += dr;
cc += dc;
}

// 计算两端是否开放
boolean openBefore = prevR >= 0 && prevR < size && prevC >= 0 && prevC < size
&& board[prevR][prevC] == 0;
boolean openAfter = cr >= 0 && cr < size && cc >= 0 && cc < size
&& board[cr][cc] == 0;
int openEnds = (openBefore ? 1 : 0) + (openAfter ? 1 : 0);

if (openEnds == 0) return 0;
if (count >= 5) return 100_000;
switch (count) {
case 4: return openEnds == 2 ? 10_000 : 1_000;
case 3: return openEnds == 2 ? 1_000 : 100;
case 2: return openEnds == 2 ? 100 : 10;
default: return openEnds == 2 ? 10 : 1;
}
}

return bestMove;
private int[][] deepCopy(int[][] src, int size) {
int[][] copy = new int[size][size];
for (int i = 0; i < size; i++) copy[i] = src[i].clone();
return copy;
}
}
Loading