|
|
|
@ -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<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());
|
|
|
|
|
}
|
|
|
|
|
}
|