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<int[][]> history = new Stack<>();
private Stack<int[][]> 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<int[]> 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<int[]> 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<int[]> generatePossibleMoves() {
    java.util.List<int[]> 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<int[]> 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());
}
}
