diff --git a/src/main/java/AI/MinimaxAI.java b/src/main/java/AI/MinimaxAI.java index a644c55..10d9e59 100644 --- a/src/main/java/AI/MinimaxAI.java +++ b/src/main/java/AI/MinimaxAI.java @@ -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 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 getCandidates(int[][] board, int size) { + Set seen = new HashSet<>(); + List 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; } } diff --git a/src/main/java/GUI/GomokuGameGUI.java b/src/main/java/GUI/GomokuGameGUI.java index 0971c05..899d3e6 100644 --- a/src/main/java/GUI/GomokuGameGUI.java +++ b/src/main/java/GUI/GomokuGameGUI.java @@ -1,175 +1,277 @@ package GUI; +import GameEngine.CheckWin; +import GameEngine.GameEngine; +import GameCtl.CodeUploadHandler; + import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import AI.MinimaxAI; -import AI.RandomAI; -import GameCtl.CodeUploadHandler; -import GameCtl.resetGame; -import GameEngine.GameEngine; +import java.awt.event.*; public class GomokuGameGUI extends JFrame { - private Timer gameTimer; // 游戏循环的计时器 - private JPanel boardPanel; // 棋盘面板 - private int[][] boardState = new int[20][20]; - private RandomAI randomAI; - private MinimaxAI minimaxAI; - private boolean currentPlayerIsRandomAI = true; // 当前下棋的是随机AI + // ── 棋盘尺寸常量 ──────────────────────────────────────────────────────────── + private static final int SIZE = GameEngine.BOARD_SIZE; // 15 + private static final int CELL = 36; // 格距(px) + private static final int PAD = 30; // 边距(px) + private static final int BOARD_PX = CELL * (SIZE - 1) + PAD * 2; // 棋盘面板像素宽高 + + // ── 游戏状态 ───────────────────────────────────────────────────────────────── + private final GameEngine engine = new GameEngine(); + private boolean gameRunning = false; + private boolean humanVsAi = false; // false = AI对战,true = 人机对战 + private boolean waitingHuman = false; // 人机模式下等待玩家落子 + // ── UI 组件 ────────────────────────────────────────────────────────────────── + private JPanel boardPanel; + private JLabel statusLabel; + private Timer aiTimer; // AI对战模式:每步间隔 + + // ── 构造 ────────────────────────────────────────────────────────────────────── public GomokuGameGUI() { - // 初始化棋盘状态 - for (int row = 0; row < 20; row++) { - for (int col = 0; col < 20; col++) { - boardState[row][col] = 0; // 0表示空 - } - } - // 初始化主界面 - initializeUI(); - // 创建并设置菜单 - createMenuBar(); - // 为主界面添加一个棋盘面板 - createBoardPanel(); - - randomAI = new RandomAI(); - minimaxAI = new MinimaxAI(); - - // 创建游戏循环计时器,每隔一段时间触发一次游戏逻辑 - gameTimer = new Timer(1000, new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - // 游戏逻辑 - startGame(); + initWindow(); + buildMenu(); + buildBoard(); + buildStatus(); + + // AI 对战计时器,每 600ms 走一步 + aiTimer = new Timer(600, e -> { + if (!engine.isGameOver()) { + engine.makeAiMove(); + boardPanel.repaint(); + if (engine.isGameOver()) { + aiTimer.stop(); + gameRunning = false; + showResult(); + } } }); - gameTimer.setRepeats(true); // 设置为重复触发 - gameTimer.setCoalesce(true); // 如果计时器的触发事件被阻塞,确保只触发一个事件 } - private void initializeUI() { + // ── 窗口初始化 ──────────────────────────────────────────────────────────────── + private void initWindow() { setTitle("五子棋游戏"); - setSize(500, 500); - setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - // 加载图标并设置为窗口图标 + setResizable(false); ImageIcon icon = new ImageIcon("src/asset/favicon/favicon.png"); setIconImage(icon.getImage()); - } //标题以及图标 + } + + // ── 菜单栏 ──────────────────────────────────────────────────────────────────── + private void buildMenu() { + JMenuBar bar = new JMenuBar(); + setJMenuBar(bar); + + // 文件 + JMenu file = new JMenu("文件"); + bar.add(file); + JMenuItem upload = new JMenuItem("上传用户代码"); + file.add(upload); + upload.addActionListener(e -> CodeUploadHandler.handleUploadCode()); - private void createMenuBar() { - JMenuBar menuBar = new JMenuBar(); - setJMenuBar(menuBar); + // 游戏控制 + JMenu ctrl = new JMenu("游戏控制"); + bar.add(ctrl); - JMenu fileMenu = new JMenu("文件"); - menuBar.add(fileMenu); + JMenuItem aiVsAi = new JMenuItem("AI 对战模式"); + ctrl.add(aiVsAi); + aiVsAi.addActionListener(e -> { humanVsAi = false; startGame(); }); - JMenuItem uploadCodeSubMenu = new JMenuItem("上传用户代码"); - fileMenu.add(uploadCodeSubMenu); - uploadCodeSubMenu.addActionListener(e -> CodeUploadHandler.handleUploadCode()); + JMenuItem humanVsAiItem = new JMenuItem("人机对战模式(你执黑)"); + ctrl.add(humanVsAiItem); + humanVsAiItem.addActionListener(e -> { humanVsAi = true; startGame(); }); + + ctrl.addSeparator(); + JMenuItem reset = new JMenuItem("重置游戏"); + ctrl.add(reset); + reset.addActionListener(e -> startGame()); + + // 退出 + JMenu exit = new JMenu("退出"); + bar.add(exit); + JMenuItem exitItem = new JMenuItem("退出游戏"); + exit.add(exitItem); + exitItem.addActionListener(e -> System.exit(0)); + } - JMenu startMenu = new JMenu("游戏控制"); // 修正此处为小写startMenu - menuBar.add(startMenu); + // ── 棋盘面板 ────────────────────────────────────────────────────────────────── + private void buildBoard() { + boardPanel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + render((Graphics2D) g); + } + }; + boardPanel.setPreferredSize(new Dimension(BOARD_PX, BOARD_PX)); + boardPanel.setBackground(new Color(219, 175, 84)); // 木色 - JMenuItem startMenuItem = new JMenuItem("开始游戏"); - startMenu.add(startMenuItem); - startMenuItem.addActionListener(new ActionListener() { + boardPanel.addMouseListener(new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { - // 启动游戏 - startGame(); + public void mouseClicked(MouseEvent e) { + if (humanVsAi && waitingHuman && gameRunning) { + handleClick(e.getX(), e.getY()); + } } }); - JMenuItem resetGameMenuItem = new JMenuItem("重置游戏"); - startMenu.add(resetGameMenuItem); - resetGameMenuItem.addActionListener(e -> new resetGame()); + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(boardPanel, BorderLayout.CENTER); + } - JMenu exitMenu = new JMenu("退出"); - menuBar.add(exitMenu); + private void buildStatus() { + statusLabel = new JLabel("请从「游戏控制」菜单选择模式开始", SwingConstants.CENTER); + statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); + statusLabel.setBorder(BorderFactory.createEmptyBorder(6, 0, 6, 0)); + getContentPane().add(statusLabel, BorderLayout.SOUTH); - JMenuItem exitMenuItem = new JMenuItem("退出"); - exitMenu.add(exitMenuItem); - exitMenuItem.addActionListener(e -> System.exit(0)); + pack(); + setLocationRelativeTo(null); } + // ── 开始 / 重置 ─────────────────────────────────────────────────────────────── private void startGame() { - // 创建游戏引擎并开始游戏 - GameEngine gameEngine = new GameEngine(boardState, currentPlayerIsRandomAI); - gameEngine.makeMove(); - - // 更新界面 - repaint(); // 重绘棋盘 - - // 检查游戏是否结束 - if (isGameOver()) { - // 游戏结束,处理游戏结果(你需要实现这部分逻辑) -// handleGameResult(); - gameTimer.stop(); // 停止游戏循环 + aiTimer.stop(); + engine.reset(); + gameRunning = true; + waitingHuman = false; + boardPanel.repaint(); + + if (!humanVsAi) { + // AI vs AI + setStatus("AI 对战中:随机AI(黑) vs Minimax AI(白)"); + aiTimer.start(); + } else { + // Human vs AI + waitingHuman = true; + setStatus("人机对战 ─ 你的回合(黑棋),请点击落子"); } } + // ── 人机落子处理 ────────────────────────────────────────────────────────────── + private void handleClick(int mouseX, int mouseY) { + // 将像素坐标映射到最近的交叉点 + int col = Math.round((float)(mouseX - PAD) / CELL); + int row = Math.round((float)(mouseY - PAD) / CELL); - private void createBoardPanel() { - boardPanel = new JPanel() { + if (!engine.placeMove(row, col)) return; // 位置无效 + + boardPanel.repaint(); + + if (engine.isGameOver()) { + gameRunning = false; + waitingHuman = false; + showResult(); + return; + } + + // 轮到 AI(Minimax,玩家2) + waitingHuman = false; + setStatus("AI 思考中…"); + + SwingWorker worker = new SwingWorker() { @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); + protected int[] doInBackground() { + return engine.computeMinimaxMove(); + } - int boardSize = 20; // 棋盘大小,这里假设是20*20 - int cellSize = 21; // 每个格子的大小 - - // 绘制棋盘 - for (int row = 0; row < boardSize; row++) { - for (int col = 0; col < boardSize; col++) { - int x = col * cellSize; - int y = row * cellSize; - - // 绘制格子边框 - g.setColor(Color.BLACK); - g.drawRect(x, y, cellSize, cellSize); - - // 绘制棋子 - if (boardState[row][col] == 1) { - g.setColor(Color.RED); // 玩家1的棋子颜色 - g.fillOval(x, y, cellSize, cellSize); - } else if (boardState[row][col] == 2) { - g.setColor(Color.BLUE); // 玩家2的棋子颜色 - g.fillOval(x, y, cellSize, cellSize); - } + @Override + protected void done() { + try { + int[] aiMove = get(); + engine.placeMove(aiMove[0], aiMove[1]); + boardPanel.repaint(); + + if (engine.isGameOver()) { + gameRunning = false; + waitingHuman = false; + showResult(); + } else { + waitingHuman = true; + setStatus("你的回合(黑棋),请点击落子"); } + } catch (Exception ex) { + ex.printStackTrace(); } } }; - boardPanel.setPreferredSize(new Dimension(500, 500)); + worker.execute(); + } + + // ── 渲染棋盘 ────────────────────────────────────────────────────────────────── + private void render(Graphics2D g) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 画格线 + g.setColor(new Color(100, 70, 20)); + g.setStroke(new BasicStroke(1f)); + for (int i = 0; i < SIZE; i++) { + int x = PAD + i * CELL; + int y = PAD + i * CELL; + g.drawLine(x, PAD, x, PAD + (SIZE - 1) * CELL); + g.drawLine(PAD, y, PAD + (SIZE - 1) * CELL, y); + } + + // 画星位(天元 + 四星) + g.setColor(new Color(80, 50, 10)); + for (int[] pt : new int[][]{{3,3},{3,11},{7,7},{11,3},{11,11}}) { + int px = PAD + pt[1] * CELL; + int py = PAD + pt[0] * CELL; + g.fillOval(px - 4, py - 4, 8, 8); + } - Container contentPane = getContentPane(); - contentPane.setLayout(new BorderLayout()); - contentPane.add(boardPanel, BorderLayout.CENTER); + // 画棋子 + int[][] board = engine.getBoardState(); + int r = CELL / 2 - 2; // 棋子半径 + for (int row = 0; row < SIZE; row++) { + for (int col = 0; col < SIZE; col++) { + if (board[row][col] == 0) continue; + int px = PAD + col * CELL; + int py = PAD + row * CELL; + + if (board[row][col] == 1) { + // 黑棋 + g.setPaint(new GradientPaint(px - r, py - r, Color.GRAY, + px + r, py + r, Color.BLACK)); + g.fillOval(px - r, py - r, r * 2, r * 2); + g.setColor(new Color(40, 40, 40)); + g.drawOval(px - r, py - r, r * 2, r * 2); + } else { + // 白棋 + g.setPaint(new GradientPaint(px - r, py - r, Color.WHITE, + px + r, py + r, new Color(200, 200, 200))); + g.fillOval(px - r, py - r, r * 2, r * 2); + g.setColor(new Color(130, 130, 130)); + g.drawOval(px - r, py - r, r * 2, r * 2); + } + } + } } - // 根据落子位置更新棋盘状态 - private void makeMove(int row, int col, int player) { - boardState[row][col] = player; - // 进行其他游戏状态更新,如判断胜负 + // ── 游戏结果弹窗 ───────────────────────────────────────────────────────────── + private void showResult() { + String msg; + int w = engine.getWinner(); + if (w == 1) { + msg = humanVsAi ? "恭喜,你赢了!🎉" : "随机 AI(黑棋)获胜!"; + } else if (w == 2) { + msg = humanVsAi ? "AI 获胜,再接再厉!" : "Minimax AI(白棋)获胜!"; + } else { + msg = "平局!"; + } + setStatus("游戏结束 ─ " + msg); + JOptionPane.showMessageDialog(this, msg, "游戏结束", JOptionPane.INFORMATION_MESSAGE); } - // 检查游戏是否结束 - private boolean isGameOver() { - // 实现游戏结束条件的判断逻辑 - // 如果游戏结束,返回true;否则,返回false - return false; + private void setStatus(String text) { + statusLabel.setText(text); } + // ── 入口 ───────────────────────────────────────────────────────────────────── public static void main(String[] args) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - GomokuGameGUI game = new GomokuGameGUI(); - game.setVisible(true); - } + SwingUtilities.invokeLater(() -> { + GomokuGameGUI game = new GomokuGameGUI(); + game.setVisible(true); }); } } diff --git a/src/main/java/GameCtl/gameStart.java b/src/main/java/GameCtl/gameStart.java index 609c00d..09f4a47 100644 --- a/src/main/java/GameCtl/gameStart.java +++ b/src/main/java/GameCtl/gameStart.java @@ -1,5 +1,13 @@ package GameCtl; +import GUI.GomokuGameGUI; +import javax.swing.SwingUtilities; + public class gameStart { -// 实现开始游戏的逻辑 + public static void launch() { + SwingUtilities.invokeLater(() -> { + GomokuGameGUI game = new GomokuGameGUI(); + game.setVisible(true); + }); + } } diff --git a/src/main/java/GameCtl/resetGame.java b/src/main/java/GameCtl/resetGame.java index fdf5457..0bd0e9d 100644 --- a/src/main/java/GameCtl/resetGame.java +++ b/src/main/java/GameCtl/resetGame.java @@ -1,7 +1,9 @@ package GameCtl; - +/** + * 重置逻辑已内联到 GomokuGameGUI#startGame(), + * 保留此类以维持原有包结构。 + */ public class resetGame { - // 实现重置游戏的逻辑 - + // 由 GomokuGameGUI 直接调用 engine.reset(),此类无需额外实现 } diff --git a/src/main/java/GameEngine/CheckWin.java b/src/main/java/GameEngine/CheckWin.java index 686f250..640ab2d 100644 --- a/src/main/java/GameEngine/CheckWin.java +++ b/src/main/java/GameEngine/CheckWin.java @@ -2,81 +2,41 @@ public class CheckWin { - private int[][] board; // 棋盘状态,0表示空,1表示玩家1,2表示玩家2 - - - private boolean isValidMove(int row, int col) { - // 检查是否合法的下子位置 - return board[row][col] == 0; // 0表示空位置 - } - - private boolean checkWin(int player) { - int boardSize = 15; // 棋盘大小,这里假设是15x15 - - // 检查水平方向 - for (int row = 0; row < boardSize; row++) { - for (int col = 0; col <= boardSize - 5; col++) { - boolean win = true; - for (int i = 0; i < 5; i++) { - if (board[row][col + i] != player) { - win = false; - break; + public static boolean checkWin(int[][] board, int player) { + int size = board.length; + int[][] dirs = {{0, 1}, {1, 0}, {1, 1}, {1, -1}}; + + for (int r = 0; r < size; r++) { + for (int c = 0; c < size; c++) { + if (board[r][c] != player) continue; + for (int[] d : dirs) { + int dr = d[0], dc = d[1]; + // 只从每条连线的起点开始计数,避免重复 + int prevR = r - dr, prevC = c - dc; + if (prevR >= 0 && prevR < size && prevC >= 0 && prevC < size + && board[prevR][prevC] == player) continue; + // 统计连续棋子数 + 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; } - } - if (win) { - return true; - } - } - } - - // 检查垂直方向 - for (int row = 0; row <= boardSize - 5; row++) { - for (int col = 0; col < boardSize; col++) { - boolean win = true; - for (int i = 0; i < 5; i++) { - if (board[row + i][col] != player) { - win = false; - break; - } - } - if (win) { - return true; - } - } - } - - // 检查正对角线方向 - for (int row = 0; row <= boardSize - 5; row++) { - for (int col = 0; col <= boardSize - 5; col++) { - boolean win = true; - for (int i = 0; i < 5; i++) { - if (board[row + i][col + i] != player) { - win = false; - break; - } - } - if (win) { - return true; + if (count >= 5) return true; } } } + return false; + } - // 检查反对角线方向 - for (int row = 0; row <= boardSize - 5; row++) { - for (int col = 4; col < boardSize; col++) { - boolean win = true; - for (int i = 0; i < 5; i++) { - if (board[row + i][col - i] != player) { - win = false; - break; - } - } - if (win) { - return true; - } + public static boolean isBoardFull(int[][] board) { + for (int[] row : board) { + for (int cell : row) { + if (cell == 0) return false; } } - - return false; + return true; } } diff --git a/src/main/java/GameEngine/GameEngine.java b/src/main/java/GameEngine/GameEngine.java index a085b66..6c875ca 100644 --- a/src/main/java/GameEngine/GameEngine.java +++ b/src/main/java/GameEngine/GameEngine.java @@ -4,60 +4,71 @@ import AI.RandomAI; public class GameEngine { + + public static final int BOARD_SIZE = 15; + private int[][] boardState; - private boolean currentPlayerIsRandomAI; - private RandomAI randomAI; - private MinimaxAI minimaxAI; + private int currentPlayer; // 1 = 随机AI / 黑棋,2 = Minimax AI / 白棋 + private int winner; // 0=进行中,1=玩家1胜,2=玩家2胜 + private final RandomAI randomAI; + private final MinimaxAI minimaxAI; - public GameEngine(int[][] boardState, boolean currentPlayerIsRandomAI) { - this.boardState = boardState; - this.currentPlayerIsRandomAI = currentPlayerIsRandomAI; + public GameEngine() { + boardState = new int[BOARD_SIZE][BOARD_SIZE]; + currentPlayer = 1; + winner = 0; randomAI = new RandomAI(); minimaxAI = new MinimaxAI(); } - public void makeMove() { - int[] move; - - if (currentPlayerIsRandomAI) { - move = randomAI.makeMove(boardState); - } else { - move = minimaxAI.makeMove(boardState); - } + public int[][] getBoardState() { return boardState; } + public int getCurrentPlayer() { return currentPlayer; } + public int getWinner() { return winner; } - int row = move[0]; - int col = move[1]; - - // 根据落子位置更新棋盘状态 - boardState[row][col] = currentPlayerIsRandomAI ? 1 : 2; - - // 切换下一个玩家 - currentPlayerIsRandomAI = !currentPlayerIsRandomAI; + public boolean isGameOver() { + return winner != 0 || CheckWin.isBoardFull(boardState); + } - // 更新界面(这部分需要你实现) - updateUI(); + /** + * 在指定位置落子(当前玩家)。 + * 返回 true 表示落子成功,false 表示位置无效或游戏已结束。 + */ + public boolean placeMove(int row, int col) { + if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return false; + if (boardState[row][col] != 0 || isGameOver()) return false; - // 检查游戏是否结束 - if (isGameOver()) { - // 游戏结束,显示胜利者或平局信息(这部分需要你实现) - showGameResult(); + boardState[row][col] = currentPlayer; + if (CheckWin.checkWin(boardState, currentPlayer)) { + winner = currentPlayer; } + currentPlayer = (currentPlayer == 1) ? 2 : 1; + return true; } - // 添加一个方法,用于检查游戏是否结束 - private boolean isGameOver() { - // 实现游戏结束条件的判断逻辑 - // 如果游戏结束,返回true;否则,返回false - return false; // 你需要根据五子棋的规则来实现这部分逻辑 + /** + * AI 自动落子(AI vs AI 模式使用): + * 玩家1 → RandomAI,玩家2 → MinimaxAI。 + * 返回落子坐标 [row, col],游戏已结束时返回 null。 + */ + public int[] makeAiMove() { + if (isGameOver()) return null; + int[] move = (currentPlayer == 1) + ? randomAI.makeMove(boardState) + : minimaxAI.makeMove(boardState); + placeMove(move[0], move[1]); + return move; } - // 添加一个方法,用于更新界面 - private void updateUI() { - // 在棋盘上更新棋子状态(这部分需要你实现) + /** + * 仅计算 Minimax AI 的最佳落点,不实际落子(供人机模式 SwingWorker 使用)。 + */ + public int[] computeMinimaxMove() { + return minimaxAI.makeMove(boardState); } - // 添加一个方法,用于显示游戏结果 - private void showGameResult() { - // 在界面上显示胜利者或平局信息(这部分需要你实现) + public void reset() { + boardState = new int[BOARD_SIZE][BOARD_SIZE]; + currentPlayer = 1; + winner = 0; } }