diff --git a/src/GomokuGame.java b/src/GomokuGame.java new file mode 100644 index 0000000..72177ec --- /dev/null +++ b/src/GomokuGame.java @@ -0,0 +1,712 @@ +package fivepieces; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import javax.sound.sampled.*; +import java.util.Stack; + +public class GomokuGame extends JFrame{ + private static final int BOARD_SIZE = 15; +private static final int CELL_SIZE = 40; +private static final int MARGIN = 30; +private static final int PIECE_SIZE = 36; + +private int[][] board = new int[BOARD_SIZE][BOARD_SIZE]; // 0: empty, 1: black, 2: white +private boolean blackTurn = true; +private boolean gameOver = false; +private boolean aiMode = false; +private boolean playerIsBlack = true; // 玩家执黑先手 +private Clip backgroundMusic; +private Stack history = new Stack<>(); +private Stack redoStack = new Stack<>(); +private static final int AI_SEARCH_DEPTH = 3; // 搜索深度 +private static final int WIN_SCORE = 100000; // 获胜分数 +private static final int BLOCK_WIN_SCORE = 50000; // 阻止获胜分数 + + public GomokuGame() { + setTitle("五子棋游戏"); + setSize(BOARD_SIZE * CELL_SIZE + 2 * MARGIN, BOARD_SIZE * CELL_SIZE + 2 * MARGIN + 100); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLocationRelativeTo(null); + + // 加载背景音乐 + loadBackgroundMusic(); + + // 添加游戏面板 + add(new GamePanel(), BorderLayout.CENTER); + + // 添加控制面板 + JPanel controlPanel = new JPanel(new GridLayout(2, 3, 5, 5)); + + JButton restartButton = new JButton("重新开始"); + restartButton.addActionListener(e -> restartGame()); + + JButton musicButton = new JButton("背景音乐 开/关"); + musicButton.addActionListener(e -> toggleMusic()); + + JButton aiButton = new JButton("AI对战 开/关"); + aiButton.addActionListener(e -> toggleAIMode()); + + JButton saveButton = new JButton("存档"); + saveButton.addActionListener(e -> saveGame()); + + JButton loadButton = new JButton("读档"); + loadButton.addActionListener(e -> loadGame()); + + JButton undoButton = new JButton("悔棋"); + undoButton.addActionListener(e -> undoMove()); + + controlPanel.add(restartButton); + controlPanel.add(musicButton); + controlPanel.add(aiButton); + controlPanel.add(saveButton); + controlPanel.add(loadButton); + controlPanel.add(undoButton); + + add(controlPanel, BorderLayout.SOUTH); + + setVisible(true); +} + +private void loadBackgroundMusic() { + try { + // 注意:你需要准备一个background.wav文件放在项目根目录下 + // 或者修改为你的音乐文件路径 + AudioInputStream audioInputStream = AudioSystem.getAudioInputStream( + new File("one.wav").getAbsoluteFile()); + backgroundMusic = AudioSystem.getClip(); + backgroundMusic.open(audioInputStream); + backgroundMusic.loop(Clip.LOOP_CONTINUOUSLY); + } catch (Exception e) { + JOptionPane.showMessageDialog(this, "无法加载背景音乐: " + e.getMessage()); + } +} + +private void toggleMusic() { + if (backgroundMusic != null) { + if (backgroundMusic.isRunning()) { + backgroundMusic.stop(); + } else { + backgroundMusic.start(); + } + } +} + +private void toggleAIMode() { + aiMode = !aiMode; + if (aiMode) { + int option = JOptionPane.showOptionDialog(this, + "选择执子颜色", "AI对战", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, + null, new String[]{"执黑先手", "执白后手"}, "执黑先手"); + + playerIsBlack = (option == 0); + + // 如果AI先手,让它先走一步 + if (!playerIsBlack && aiMode) { + makeAIMove(); + } + } + restartGame(); +} + +private void saveGameState() { + // 深拷贝当前棋盘状态 + int[][] currentState = new int[BOARD_SIZE][BOARD_SIZE]; + for (int i = 0; i < BOARD_SIZE; i++) { + System.arraycopy(board[i], 0, currentState[i], 0, BOARD_SIZE); + } + history.push(currentState); + redoStack.clear(); // 新的操作后,重做栈清空 +} + +private void undoMove() { + if (history.size() > 1) { // 保留初始空棋盘状态 + redoStack.push(history.pop()); + board = history.peek(); + blackTurn = !blackTurn; + gameOver = false; + repaint(); + } else { + JOptionPane.showMessageDialog(this, "无法再悔棋了"); + } +} + +private void redoMove() { + if (!redoStack.isEmpty()) { + history.push(redoStack.pop()); + board = history.peek(); + blackTurn = !blackTurn; + repaint(); + } +} + +private void saveGame() { + try (ObjectOutputStream oos = new ObjectOutputStream( + new FileOutputStream("gomoku_save.dat"))) { + SaveData data = new SaveData(board, blackTurn, gameOver, aiMode, playerIsBlack); + oos.writeObject(data); + JOptionPane.showMessageDialog(this, "游戏已保存"); + } catch (IOException e) { + JOptionPane.showMessageDialog(this, "保存游戏失败: " + e.getMessage()); + } +} + +private void loadGame() { + try (ObjectInputStream ois = new ObjectInputStream( + new FileInputStream("gomoku_save.dat"))) { + SaveData data = (SaveData) ois.readObject(); + board = data.getBoard(); + blackTurn = data.isBlackTurn(); + gameOver = data.isGameOver(); + aiMode = data.isAiMode(); + playerIsBlack = data.isPlayerIsBlack(); + + // 重置历史栈 + history.clear(); + redoStack.clear(); + saveGameState(); // 保存当前状态到历史 + + repaint(); + JOptionPane.showMessageDialog(this, "游戏已加载"); + } catch (IOException | ClassNotFoundException e) { + JOptionPane.showMessageDialog(this, "加载游戏失败: " + e.getMessage()); + } +} + +private void restartGame() { + board = new int[BOARD_SIZE][BOARD_SIZE]; + blackTurn = true; + gameOver = false; + + // 重置历史栈 + history.clear(); + redoStack.clear(); + saveGameState(); // 保存初始状态 + + // 如果AI先手,让它先走一步 + if (aiMode && !playerIsBlack) { + makeAIMove(); + } + + repaint(); +} + +private void makeMove(int x, int y) { + if (gameOver) return; + + if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == 0) { + saveGameState(); + + board[x][y] = blackTurn ? 1 : 2; + + if (checkWin(x, y)) { + gameOver = true; + JOptionPane.showMessageDialog(GomokuGame.this, + (blackTurn ? "黑方" : "白方") + "获胜!"); + } + + blackTurn = !blackTurn; + repaint(); + + // AI模式且轮到AI下棋 + if (aiMode && !gameOver && ((playerIsBlack && !blackTurn) || (!playerIsBlack && blackTurn))) { + Timer timer = new Timer(500, e -> { + makeAIMove(); + ((Timer)e.getSource()).stop(); + }); + timer.setRepeats(false); + timer.start(); + } + } +} + +private void makeAIMove() { + // 使用MiniMax算法寻找最佳移动 + int[] bestMove = findBestMoveWithMiniMax(); + if (bestMove != null) { + makeMove(bestMove[0], bestMove[1]); + } +} + +private int[] findBestMoveWithMiniMax() { + int player = playerIsBlack ? 2 : 1; // AI的棋子颜色 + int opponent = playerIsBlack ? 1 : 2; // 玩家的棋子颜色 + + int bestScore = Integer.MIN_VALUE; + int[] bestMove = null; + + // 获取所有可能的移动 + java.util.List possibleMoves = generatePossibleMoves(); + + for (int[] move : possibleMoves) { + int x = move[0], y = move[1]; + + // 尝试这个移动 + board[x][y] = player; + + // 计算这个移动的分数 + int score = miniMax(AI_SEARCH_DEPTH - 1, false, player, opponent, + Integer.MIN_VALUE, Integer.MAX_VALUE); + + // 撤销移动 + board[x][y] = 0; + + // 更新最佳移动 + if (score > bestScore) { + bestScore = score; + bestMove = new int[]{x, y}; + } + } + + return bestMove; +} + +private int miniMax(int depth, boolean isMaximizing, int player, int opponent, + int alpha, int beta) { + // 检查游戏是否结束或达到最大深度 + if (depth == 0 || isGameOver()) { + return evaluateBoard(player, opponent); + } + + // 生成可能的移动 + java.util.List possibleMoves = generatePossibleMoves(); + + if (isMaximizing) { + int bestScore = Integer.MIN_VALUE; + for (int[] move : possibleMoves) { + int x = move[0], y = move[1]; + + // 尝试这个移动 + board[x][y] = player; + + // 递归调用 + int score = miniMax(depth - 1, false, player, opponent, alpha, beta); + + // 撤销移动 + board[x][y] = 0; + + // 更新最佳分数 + bestScore = Math.max(bestScore, score); + alpha = Math.max(alpha, bestScore); + + // Alpha-Beta剪枝 + if (beta <= alpha) { + break; + } + } + return bestScore; + } else { + int bestScore = Integer.MAX_VALUE; + for (int[] move : possibleMoves) { + int x = move[0], y = move[1]; + + // 尝试这个移动 + board[x][y] = opponent; + + // 递归调用 + int score = miniMax(depth - 1, true, player, opponent, alpha, beta); + + // 撤销移动 + board[x][y] = 0; + + // 更新最佳分数 + bestScore = Math.min(bestScore, score); + beta = Math.min(beta, bestScore); + + // Alpha-Beta剪枝 + if (beta <= alpha) { + break; + } + } + return bestScore; + } +} + +private java.util.List generatePossibleMoves() { + java.util.List moves = new java.util.ArrayList<>(); + + // 首先检查是否有紧急情况(立即获胜或阻止对手获胜) + int player = playerIsBlack ? 2 : 1; + int opponent = playerIsBlack ? 1 : 2; + + // 1. 检查AI是否可以立即获胜 + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == 0) { + board[i][j] = player; + if (checkWin(i, j)) { + board[i][j] = 0; + moves.clear(); + moves.add(new int[]{i, j}); + return moves; // 直接返回获胜位置 + } + board[i][j] = 0; + } + } + } + + // 2. 检查是否需要阻止玩家获胜 + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == 0) { + board[i][j] = opponent; + if (checkWin(i, j)) { + board[i][j] = 0; + moves.clear(); + moves.add(new int[]{i, j}); + return moves; // 直接返回阻止位置 + } + board[i][j] = 0; + } + } + } + + // 3. 生成所有可能的移动,但优先考虑靠近已有棋子的位置 + boolean[][] visited = new boolean[BOARD_SIZE][BOARD_SIZE]; + + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] != 0) { + // 检查周围的空位 + for (int dx = -2; dx <= 2; dx++) { + for (int dy = -2; dy <= 2; dy++) { + int x = i + dx; + int y = j + dy; + if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE + && board[x][y] == 0 && !visited[x][y]) { + moves.add(new int[]{x, y}); + visited[x][y] = true; + } + } + } + } + } + } + + // 如果没有找到附近的空位(游戏刚开始),返回中心区域的位置 + if (moves.isEmpty()) { + int center = BOARD_SIZE / 2; + for (int i = center - 2; i <= center + 2; i++) { + for (int j = center - 2; j <= center + 2; j++) { + if (i >= 0 && i < BOARD_SIZE && j >= 0 && j < BOARD_SIZE + && board[i][j] == 0) { + moves.add(new int[]{i, j}); + } + } + } + } + + return moves; +} + +private int evaluateBoard(int player, int opponent) { + int score = 0; + + // 评估所有可能的五元组 + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == player) { + score += evaluatePosition(i, j, player, opponent); + } else if (board[i][j] == opponent) { + score -= evaluatePosition(i, j, opponent, player); + } + } + } + + return score; +} + +private int evaluatePosition(int x, int y, int player, int opponent) { + int score = 0; + int[][] directions = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; + + for (int[] dir : directions) { + // 检查这个方向的棋型 + int playerCount = 1; // 当前棋子 + int openEnds = 0; + boolean blocked1 = false, blocked2 = false; + + // 正向检查 + for (int i = 1; i <= 4; i++) { + int nx = x + i * dir[0]; + int ny = y + i * dir[1]; + + if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE) { + blocked1 = true; + break; + } + + if (board[nx][ny] == player) { + playerCount++; + } else if (board[nx][ny] == opponent) { + blocked1 = true; + break; + } else { + openEnds++; + break; + } + } + + // 反向检查 + for (int i = 1; i <= 4; i++) { + int nx = x - i * dir[0]; + int ny = y - i * dir[1]; + + if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE) { + blocked2 = true; + break; + } + + if (board[nx][ny] == player) { + playerCount++; + } else if (board[nx][ny] == opponent) { + blocked2 = true; + break; + } else { + openEnds++; + break; + } + } + + // 根据棋型评分 + score += evaluatePattern(playerCount, openEnds); + } + + return score; +} + +private int evaluatePattern(int playerCount, int openEnds) { + if (playerCount >= 5) return WIN_SCORE; // 五连 + + if (openEnds == 0) return 0; // 被阻挡的两端 + + switch (playerCount) { + case 4: + return openEnds == 2 ? BLOCK_WIN_SCORE : 10000; // 活四或冲四 + case 3: + return openEnds == 2 ? 5000 : 1000; // 活三或眠三 + case 2: + return openEnds == 2 ? 200 : 50; // 活二或眠二 + case 1: + return openEnds == 2 ? 10 : 1; // 活一或眠一 + default: + return 0; + } +} + +private boolean isGameOver() { + // 检查棋盘是否已满 + boolean boardFull = true; + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == 0) { + boardFull = false; + break; + } + } + if (!boardFull) break; + } + + return boardFull; +} + + +private int[] findBestMove() { + int player = playerIsBlack ? 2 : 1; // AI的棋子颜色 + + // 1. 检查是否有可以立即获胜的位置 + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == 0) { + board[i][j] = player; + if (checkWin(i, j)) { + board[i][j] = 0; + return new int[]{i, j}; + } + board[i][j] = 0; + } + } + } + + // 2. 检查是否需要阻止玩家获胜 + int opponent = playerIsBlack ? 1 : 2; + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == 0) { + board[i][j] = opponent; + if (checkWin(i, j)) { + board[i][j] = 0; + return new int[]{i, j}; + } + board[i][j] = 0; + } + } + } + + // 3. 随机选择一个空位置 + java.util.List emptyCells = new java.util.ArrayList<>(); + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == 0) { + emptyCells.add(new int[]{i, j}); + } + } + } + + if (!emptyCells.isEmpty()) { + return emptyCells.get((int)(Math.random() * emptyCells.size())); + } + + return null; +} + +private boolean checkWin(int x, int y) { + int player = board[x][y]; + int[][] directions = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; + + for (int[] dir : directions) { + int count = 1; + + // 正向检查 + for (int i = 1; i <= 4; i++) { + int nx = x + i * dir[0]; + int ny = y + i * dir[1]; + if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] == player) { + count++; + } else { + break; + } + } + + // 反向检查 + for (int i = 1; i <= 4; i++) { + int nx = x - i * dir[0]; + int ny = y - i * dir[1]; + if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] == player) { + count++; + } else { + break; + } + } + + if (count >= 5) { + return true; + } + } + + return false; +} + +class GamePanel extends JPanel { + public GamePanel() { + setPreferredSize(new Dimension( + BOARD_SIZE * CELL_SIZE + 2 * MARGIN, + BOARD_SIZE * CELL_SIZE + 2 * MARGIN)); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (gameOver) return; + if (aiMode && ((playerIsBlack && !blackTurn) || (!playerIsBlack && blackTurn))) { + return; // AI回合,忽略玩家点击 + } + + int x = (e.getX() - MARGIN + CELL_SIZE / 2) / CELL_SIZE; + int y = (e.getY() - MARGIN + CELL_SIZE / 2) / CELL_SIZE; + + makeMove(x, y); + } + }); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + // 绘制棋盘背景 + g.setColor(new Color(220, 179, 92)); + g.fillRect(MARGIN, MARGIN, BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE); + + // 绘制网格线 + g.setColor(Color.BLACK); + for (int i = 0; i < BOARD_SIZE; i++) { + g.drawLine(MARGIN + i * CELL_SIZE, MARGIN, + MARGIN + i * CELL_SIZE, MARGIN + (BOARD_SIZE - 1) * CELL_SIZE); + g.drawLine(MARGIN, MARGIN + i * CELL_SIZE, + MARGIN + (BOARD_SIZE - 1) * CELL_SIZE, MARGIN + i * CELL_SIZE); + } + + // 绘制棋子 + for (int i = 0; i < BOARD_SIZE; i++) { + for (int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == 1) { + g.setColor(Color.BLACK); + g.fillOval(MARGIN + i * CELL_SIZE - PIECE_SIZE / 2, + MARGIN + j * CELL_SIZE - PIECE_SIZE / 2, + PIECE_SIZE, PIECE_SIZE); + } else if (board[i][j] == 2) { + g.setColor(Color.WHITE); + g.fillOval(MARGIN + i * CELL_SIZE - PIECE_SIZE / 2, + MARGIN + j * CELL_SIZE - PIECE_SIZE / 2, + PIECE_SIZE, PIECE_SIZE); + } + } + } + + // 显示当前回合和模式 + g.setColor(Color.BLACK); + g.setFont(new Font("Arial", Font.BOLD, 16)); + String modeText = aiMode ? (playerIsBlack ? "AI模式(玩家执黑)" : "AI模式(玩家执白)") : "双人对战模式"; + g.drawString("模式: " + modeText, MARGIN, MARGIN + BOARD_SIZE * CELL_SIZE + 20); + g.drawString("当前回合: " + (blackTurn ? "黑方" : "白方"), + MARGIN, MARGIN + BOARD_SIZE * CELL_SIZE + 40); + } +} + +// 用于游戏存档的序列化类 +static class SaveData implements Serializable { + private static final long serialVersionUID = 1L; + private int[][] board; + private boolean blackTurn; + private boolean gameOver; + private boolean aiMode; + private boolean playerIsBlack; + + public SaveData(int[][] board, boolean blackTurn, boolean gameOver, boolean aiMode, boolean playerIsBlack) { + this.board = new int[BOARD_SIZE][BOARD_SIZE]; + for (int i = 0; i < BOARD_SIZE; i++) { + System.arraycopy(board[i], 0, this.board[i], 0, BOARD_SIZE); + } + this.blackTurn = blackTurn; + this.gameOver = gameOver; + this.aiMode = aiMode; + this.playerIsBlack = playerIsBlack; + } + + public int[][] getBoard() { + return board; + } + + public boolean isBlackTurn() { + return blackTurn; + } + + public boolean isGameOver() { + return gameOver; + } + + public boolean isAiMode() { + return aiMode; + } + + public boolean isPlayerIsBlack() { + return playerIsBlack; + } +} + +public static void main(String[] args) { + SwingUtilities.invokeLater(() -> new GomokuGame()); +} +}