<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      簡單五子棋對戰(zhàn)(AI生成)

      簡單五子棋對戰(zhàn)(AI生成)


      一、目標(biāo)

      通過AI,生成五子棋游戲的代碼,并理解代碼含義。

      二、包結(jié)構(gòu)


      gomoku/
      ├── Main.java                       // 程序入口
      ├── model/                          // 數(shù)據(jù)模型層
      │   ├── Chessboard.java             // 棋盤數(shù)據(jù)模型
      │   ├── Player.java                 // 玩家抽象類
      │   ├── HumanPlayer.java            // 人類玩家
      │   ├── AIPlayer.java               // AI玩家
      │   ├── GameRecord.java             // 儲存游戲數(shù)據(jù)
      │   └── RankList.java               // 排行榜對話框
      ├── view/                   .       // 視圖層
      │   ├── MainFrame.java              // 主窗口
      │   ├── ChessboardPanel.java        // 棋盤繪制面板
      │   ├── MenuPanel.java              // 菜單面板
      │   ├── TimerPanel.java             // 計(jì)時器面板
      │   ├── BoardPreview.java           // 預(yù)覽棋盤對話框
      │   ├── TowPlayerInputDialog.java   // 人人對戰(zhàn)玩家輸入對話框
      │   ├── UsernameDialog.java         // 人機(jī)對戰(zhàn)玩家輸入對話框
      │   └── RankDialog.java             // 排行榜對話框
      └── util/                           // 工具類
          └── GameUtils.java              // 游戲工具類
      

      三、代碼設(shè)計(jì)與講解

      3.1 Main.java

      點(diǎn)擊查看代碼
      package gomoku;
      
      import gomoku.view.MainFrame;
      import javax.swing.SwingUtilities;
      
      public class Main {
          public static void main(String[] args) {
              // 在事件調(diào)度線程中啟動GUI
              SwingUtilities.invokeLater(() -> {
                  MainFrame mainFrame = new MainFrame("五子棋");
                  mainFrame.setVisible(true); // 必須調(diào)用此方法,窗口才能顯示
              });
          }
      }
      
      • 使用多線程來確保程序的同時運(yùn)行性

      • 使用SwingUtilities.invokeLater()來確保每一步操作在正確的線程上安全地執(zhí)行

      3.2 Chessboard.java

      點(diǎn)擊查看代碼
      package gomoku.model;
      
      public class Chessboard {
          // 棋子狀態(tài)常量
          public static final int EMPTY = 0;
          public static final int BLACK = 1;
          public static final int WHITE = 2;
          
          private int[][] board;       // 棋盤數(shù)組
          private int size;            // 棋盤大小(如15x15)
          private int currentPlayer;   // 當(dāng)前落子玩家(默認(rèn)黑棋先行)
          private int lastRow = -1;    // 最后落子的行
          private int lastCol = -1;    // 最后落子的列
          private boolean isGameOver;  // 游戲是否結(jié)束
          private int winner;          // 獲勝者(EMPTY表示未分勝負(fù))
      
          // 構(gòu)造方法:初始化指定大小的棋盤
          public Chessboard(int size) {
              this.size = size;
              this.board = new int[size][size];
              this.currentPlayer = BLACK; // 黑棋先行
              this.isGameOver = false;
              this.winner = EMPTY;
          }
      
          // 落子方法:在指定位置放置當(dāng)前玩家的棋子(成功返回true,失敗返回false)
          public boolean placePiece(int row, int col) {
              // 校驗(yàn)條件:游戲未結(jié)束 + 位置合法 + 該位置為空
              if (isGameOver || row < 0 || row >= size || col < 0 || col >= size || board[row][col] != EMPTY) {
                  return false; // 落子失敗
              }
              
              // 放置棋子
              board[row][col] = currentPlayer;
              this.lastRow = row;
              this.lastCol = col;
              
              // 檢查是否獲勝
              if (checkWin(row, col)) {
                  isGameOver = true;
                  winner = currentPlayer;
              }
              
              return true; // 落子成功
          }
      
          // 檢查指定位置落子后是否獲勝(五子連珠判定)
          public boolean checkWin(int row, int col) {
              int player = board[row][col];
              if (player == EMPTY) return false;
      
              // 方向數(shù)組:上、下、左、右、左上、右下、右上、左下(共4組對向)
              int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}, {1, -1}};
              
              for (int i = 0; i < 4; i++) {
                  int count = 1; // 當(dāng)前位置已占1個棋子
                  int[] dir1 = dirs[2*i];   // 第一個方向(如向上)
                  int[] dir2 = dirs[2*i +1];// 對向方向(如向下)
                  
                  // 檢查第一個方向
                  int r = row + dir1[0];
                  int c = col + dir1[1];
                  while (r >= 0 && r < size && c >= 0 && c < size && board[r][c] == player) {
                      count++;
                      r += dir1[0];
                      c += dir1[1];
                  }
                  
                  // 檢查對向方向
                  r = row + dir2[0];
                  c = col + dir2[1];
                  while (r >= 0 && r < size && c >= 0 && c < size && board[r][c] == player) {
                      count++;
                      r += dir2[0];
                      c += dir2[1];
                  }
                  
                  // 連續(xù)5個及以上則獲勝
                  if (count >= 5) return true;
              }
              return false;
          }
      
          // 切換當(dāng)前玩家(黑→白,白→黑)- 僅在游戲未結(jié)束時有效
          public void switchPlayer() {
              if (!isGameOver) {
                  currentPlayer = (currentPlayer == BLACK) ? WHITE : BLACK;
              }
          }
      
          // 重置棋盤(清空所有棋子,恢復(fù)初始狀態(tài))
          public void reset() {
              for (int i = 0; i < size; i++) {
                  for (int j = 0; j < size; j++) {
                      board[i][j] = EMPTY;
                  }
              }
              currentPlayer = BLACK;
              lastRow = -1;
              lastCol = -1;
              isGameOver = false;
              winner = EMPTY;
          }
      
          // ------------------- Getter和Setter方法 -------------------
          public int getLastRow() {
              return lastRow;
          }
      
          public int getLastCol() {
              return lastCol;
          }
      
          public void setLastMove(int row, int col) {
              this.lastRow = row;
              this.lastCol = col;
          }
      
          public int[][] getBoard() {
              return board;
          }
      
          public int getSize() {
              return size;
          }
      
          public int getCurrentPlayer() {
              return currentPlayer;
          }
      
          public int getPiece(int row, int col) {
              if (row >= 0 && row < size && col >= 0 && col < size) {
                  return board[row][col];
              }
              return -1;
          }
      
          public boolean isGameOver() {
              return isGameOver;
          }
      
          public int getWinner() {
              return winner;
          }
      
          // 手動設(shè)置游戲結(jié)束狀態(tài)(用于特殊場景)
          public void setGameOver(boolean gameOver) {
              isGameOver = gameOver;
          }
      }
      
      
      • 該代碼用于更新棋盤、判斷棋子位置的合法性、檢測是否勝利、切換玩家、重置棋盤

      • 使用構(gòu)造函數(shù)來初始化棋盤大小和棋子的樣式

      • 使用多種判斷方法,使代碼更加簡潔

      3.3 Player.java

      點(diǎn)擊查看代碼
      package gomoku.model;
      
      public abstract class Player {
          protected int color;
          protected Chessboard chessboard;
          
          public Player(int color, Chessboard chessboard) {
              this.color = color;
              this.chessboard = chessboard;
          }
          
          public int getColor() {
              return color;
          }
          
          // 抽象方法:下棋
          public abstract void makeMove();
          
          // 設(shè)置落子后的回調(diào)接口
          public interface MoveListener {
              void onMoveMade
              (int row, int col);
          }
      }
      
      • 玩家抽象類,設(shè)定玩家下棋的行為

      • 設(shè)置接口MoveListener(響應(yīng)事件),監(jiān)聽玩家下棋棋子的位置(監(jiān)聽成功之后,判斷勝負(fù),刷新面板,切換回合)

      • 設(shè)置落子后的回調(diào)接口時默認(rèn)隱藏了public abstract修飾符

      3.4 HumanPlayer.java

      點(diǎn)擊查看代碼
      package gomoku.model;
      
      public class HumanPlayer extends Player {
          private MoveListener listener;
          
          public HumanPlayer(int color, Chessboard chessboard, MoveListener listener) {
              super(color, chessboard);
              this.listener = listener;
          }
          
          @Override
          public void makeMove() {
              // 人類玩家通過界面點(diǎn)擊落子,此處空實(shí)現(xiàn)
          }
          
          // 處理人類玩家的點(diǎn)擊
          public void handleClick(int row, int col) {
              if (chessboard.getPiece(row, col) == Chessboard.EMPTY && 
                  chessboard.getCurrentPlayer() == color) {
                  
                  if (chessboard.placePiece(row, col)) {
                      listener.onMoveMade(row, col);
                  }
              }
          }
      }
      
      • 聲明了MoveListener listener,可以保存外部傳入的回調(diào)“通信渠道”,使之持有任何 MoveListener 接口的實(shí)現(xiàn)類的對象(監(jiān)聽位置)

      • 人類對戰(zhàn)時背后邏輯判斷

      • 人類玩家的落子觸發(fā)方式是 “鼠標(biāo)點(diǎn)擊”(被動觸發(fā)),而 makeMove() 是抽象父類Player定義的 “主動落子方法”(用于 AI 主動找位置落子),所以是空實(shí)現(xiàn);而畫圖會在ChessboardPanel中實(shí)現(xiàn)

      • handleClick()判斷落子的合法性,并監(jiān)聽落子位置

      3.5 AIPlayer.java

      點(diǎn)擊查看代碼
      package gomoku.model;
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Random;
      import javax.swing.SwingUtilities;
      
      public class AIPlayer {
          private int color; // AI的棋子顏色(黑/白)
          private Chessboard chessboard;
          private MoveListener listener; // 落子后的回調(diào)接口
          private Random random; // 隨機(jī)數(shù)生成器
          private int delayMillis = 800; // 延遲時間,單位:毫秒(可調(diào)整)
      
          // 構(gòu)造方法:接收顏色、棋盤和回調(diào)
          public AIPlayer(int color, Chessboard chessboard, MoveListener listener) {
              this.color = color;
              this.chessboard = chessboard;
              this.listener = listener;
              this.random = new Random(); // 初始化隨機(jī)數(shù)生成器
          }
      
          // 帶延遲的AI隨機(jī)落子邏輯
          public void makeMove() {
              // 校驗(yàn):游戲未結(jié)束且當(dāng)前是AI回合
              if (!chessboard.isGameOver() && chessboard.getCurrentPlayer() == color) {
                  // 使用新線程實(shí)現(xiàn)延遲,避免阻塞UI
                  new Thread(() -> {
                      try {
                          // AI"思考"延遲
                          Thread.sleep(delayMillis);
                          
                          // 在UI線程中執(zhí)行落子操作(Swing要求UI操作在事件調(diào)度線程中執(zhí)行)
                          SwingUtilities.invokeLater(() -> {
                              // 獲取所有空位置
                              List<int[]> emptyPositions = findAllEmptyPositions();
                              
                              if (!emptyPositions.isEmpty()) {
                                  // 從空位置中隨機(jī)選擇一個
                                  int randomIndex = random.nextInt(emptyPositions.size());
                                  int[] randomPos = emptyPositions.get(randomIndex);
                                  int row = randomPos[0];
                                  int col = randomPos[1];
                                  
                                  // 落子并通知回調(diào)
                                  if (chessboard.placePiece(row, col)) {
                                      listener.onMoveMade(row, col);
                                  }
                              }
                          });
                      } catch (InterruptedException e) {
                          // 處理中斷異常(恢復(fù)中斷狀態(tài))
                          Thread.currentThread().interrupt();
                          e.printStackTrace();
                      }
                  }).start();
              }
          }
      
          // 查找棋盤上所有的空位置
          private List<int[]> findAllEmptyPositions() {
              List<int[]> emptyPositions = new ArrayList<>();
              int size = chessboard.getSize();
              
              for (int i = 0; i < size; i++) {
                  for (int j = 0; j < size; j++) {
                      if (chessboard.getPiece(i, j) == Chessboard.EMPTY) {
                          emptyPositions.add(new int[]{i, j});
                      }
                  }
              }
              
              return emptyPositions;
          }
      
          // 設(shè)置延遲時間(允許外部調(diào)整)
          public void setDelayMillis(int millis) {
              if (millis > 0) {
                  this.delayMillis = millis;
              }
          }
      
          // 落子回調(diào)接口
          public interface MoveListener {
              void onMoveMade(int row, int col);
          }
      }
          
      
      • 創(chuàng)建新的線程對象,避免 AI 延遲落子阻塞 UI 線程

      • Thread.sleep(800) 阻塞面板800ns,營造一種AI在思考的感覺

      • 使用List來存儲空為位置,并使用random,在空位置中隨機(jī)生成落子,可以修改此處代碼,使機(jī)器的落子更加復(fù)雜

      3.6 GameRecord.java

      點(diǎn)擊查看代碼
      package gomoku.model;
      
      import java.time.Duration;
      import java.time.LocalDateTime;
      
      /**
       * 游戲記錄模型,存儲單局游戲的信息,包括用戶名和棋盤狀態(tài)
       */
      public class GameRecord {
          private String gameType; // "人機(jī)對戰(zhàn)" 或 "人人對戰(zhàn)"
          private LocalDateTime startTime; // 開始時間
          private LocalDateTime endTime; // 結(jié)束時間
          private String winner; // 獲勝方 ("黑棋" 或 "白棋")
          private Duration duration; // 游戲時長
          private String username; // 玩家用戶名
          private int[][] boardState; // 結(jié)束時的棋盤狀態(tài)
          private int boardSize; // 棋盤大小
      
          public GameRecord(String gameType, int boardSize) {
              this.gameType = gameType;
              this.startTime = LocalDateTime.now();
              this.boardSize = boardSize;
          }
      
          // 結(jié)束游戲并計(jì)算時長,保存棋盤狀態(tài)
          public void finishGame(String winner, int[][] boardState, String username) {
              this.endTime = LocalDateTime.now();
              this.winner = winner;
              this.duration = Duration.between(startTime, endTime);
              this.username = username;
              // 深拷貝棋盤狀態(tài)
              this.boardState = new int[boardSize][boardSize];
              for (int i = 0; i < boardSize; i++) {
                  System.arraycopy(boardState[i], 0, this.boardState[i], 0, boardSize);
              }
          }
      
          // 獲取時長的字符串表示 (mm:ss)
          public String getDurationStr() {
              long seconds = duration.getSeconds();
              long minutes = seconds / 60;
              seconds %= 60;
              return String.format("%02d:%02d", minutes, seconds);
          }
      
          // getter方法
          public String getGameType() { return gameType; }
          public String getWinner() { return winner; }
          public Duration getDuration() { return duration; }
          public LocalDateTime getStartTime() { return startTime; }
          public String getUsername() { return username; }
          public int[][] getBoardState() { return boardState; }
          public int getBoardSize() { return boardSize; }
      }
      
      
      • 結(jié)束游戲時,創(chuàng)建獨(dú)立的棋盤副本,記錄落子位置、時間和勝者

      • 對原數(shù)組的每一行,使用System.arraycopy來把原數(shù)組每一行元素直接復(fù)制到新數(shù)組的對應(yīng)行

      3.7 RankList.java

      點(diǎn)擊查看代碼
      package gomoku.model;
      
      import java.util.ArrayList;
      import java.util.Comparator;
      import java.util.List;
      import java.util.stream.Collectors;
      
      /**
       * 排行榜數(shù)據(jù)管理類,負(fù)責(zé)記錄和排序游戲記錄
       */
      public class RankList {
          private static RankList instance; // 單例模式
          private List<GameRecord> allRecords; // 所有記錄
      
          private RankList() {
              allRecords = new ArrayList<>();
          }
      
          // 單例獲取
          public static synchronized RankList getInstance() {
              if (instance == null) {
                  instance = new RankList();
              }
              return instance;
          }
      
          // 添加記錄
          public void addRecord(GameRecord record) {
              allRecords.add(record);
          }
      
          // 獲取人機(jī)對戰(zhàn)排行榜 (按時長升序,即最快獲勝)
          public List<GameRecord> getAiRankList() {
              return allRecords.stream()
                      .filter(r -> r.getGameType().equals("人機(jī)對戰(zhàn)"))
                      .sorted(Comparator.comparing(GameRecord::getDuration))
                      .collect(Collectors.toList());
          }
      
          // 獲取人人對戰(zhàn)排行榜
          public List<GameRecord> getPvpRankList() {
              return allRecords.stream()
                      .filter(r -> r.getGameType().equals("人人對戰(zhàn)"))
                      .sorted(Comparator.comparing(GameRecord::getDuration))
                      .collect(Collectors.toList());
          }
      
          // 獲取總排行榜
          public List<GameRecord> getTotalRankList() {
              return allRecords.stream()
                      .sorted(Comparator.comparing(GameRecord::getDuration))
                      .collect(Collectors.toList());
          }
      }
      
      
      • 使用List來儲存記錄每一局對戰(zhàn)數(shù)據(jù),添加到排行榜上

      • 使用Stream流+Collection來比較對局時間,進(jìn)行排序

      3.8 MainFrame.java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import gomoku.model.Chessboard;
      import gomoku.model.AIPlayer;
      import gomoku.model.HumanPlayer;
      import gomoku.model.GameRecord;
      import gomoku.model.RankList;
      import gomoku.util.GameUtils;
      
      import javax.swing.*;
      import java.awt.*;
      import java.awt.event.MouseAdapter;
      import java.awt.event.MouseEvent;
      
      public class MainFrame extends JFrame {
          // 全局樣式配置(統(tǒng)一管理,方便修改)
          private static final Color BG_COLOR = new Color(245, 240, 230); // 米白色背景
          private static final Color BOARD_COLOR = new Color(210, 180, 140); // 木質(zhì)棋盤色
          private static final Color BUTTON_COLOR = new Color(139, 69, 19); // 棕色按鈕
          private static final Color BUTTON_TEXT_COLOR = Color.WHITE; // 按鈕文字白
          private static final Font MAIN_FONT = new Font("微軟雅黑", Font.PLAIN, 14);
          private static final Font TITLE_FONT = new Font("微軟雅黑", Font.BOLD, 18);
      
          // 新增:調(diào)整窗口和棋盤尺寸,確保足夠顯示
          private static final int MAIN_WINDOW_WIDTH = 900;
          private static final int MAIN_WINDOW_HEIGHT = 850; // 增加高度避免壓縮
          private static final int BOARD_MARGIN = 25; // 棋盤邊緣留白
      
          private Chessboard chessboard;
          private ChessboardPanel chessPanel;
          private MenuPanel menuPanel;
          private TimerPanel timerPanel;
          private HumanPlayer humanPlayer1;
          private HumanPlayer humanPlayer2;
          private AIPlayer aiPlayer;
          private boolean isVsAI;
          private boolean gameStarted;
          private GameRecord currentRecord;
      
          public MainFrame(String title) {
              super(title);
              // 基礎(chǔ)窗口配置
              setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              setSize(MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT); // 使用調(diào)整后的高度
              setLocationRelativeTo(null);
              setResizable(false);
              getContentPane().setBackground(BG_COLOR);
      
              initComponents();
              setupLayout();
              setupListeners();
          }
      
          private void initComponents() {
              menuPanel = new MenuPanel();
              timerPanel = new TimerPanel();
      
              chessboard = new Chessboard(15);
              chessPanel = new ChessboardPanel(chessboard);
              // 關(guān)鍵:設(shè)置棋盤面板的首選尺寸和最小尺寸
              chessPanel.setPreferredSize(new Dimension(600, 600));
              chessPanel.setMinimumSize(new Dimension(500, 500)); // 防止被壓縮
              chessPanel.setBackground(BG_COLOR);
          }
      
          private void setupLayout() {
              JPanel mainPanel = new JPanel(new BorderLayout(0, 20));
              mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
              mainPanel.setBackground(BG_COLOR);
      
              mainPanel.add(menuPanel, BorderLayout.NORTH);
              
              // 中間區(qū)域:使用面板包裹棋盤,確保居中且不被壓縮
              JPanel boardContainer = new JPanel(new FlowLayout(FlowLayout.CENTER));
              boardContainer.setBackground(BG_COLOR);
              // 為容器添加最小尺寸約束
              boardContainer.setMinimumSize(new Dimension(600, 600));
              boardContainer.add(chessPanel);
              mainPanel.add(boardContainer, BorderLayout.CENTER);
      
              mainPanel.add(timerPanel, BorderLayout.SOUTH);
      
              this.setContentPane(mainPanel);
          }
      
          private void setupListeners() {
              if (menuPanel == null) {
                  menuPanel = new MenuPanel();
                  System.err.println("menuPanel為空,已重新初始化");
              }
      
              menuPanel.setMenuListener(new MenuPanel.MenuActionListener() {
                  @Override
                  public void onPvpSelected() {
                      System.out.println("監(jiān)聽器收到:人人對戰(zhàn)");
                      startPVPGame();
                  }
      
                  @Override
                  public void onPvaiSelected() {
                      System.out.println("監(jiān)聽器收到:人機(jī)對戰(zhàn)");
                      startPVAGame();
                  }
      
                  @Override
                  public void onRestartSelected() {
                      System.out.println("監(jiān)聽器收到:重新開始");
                      restartGame();
                  }
      
                  @Override
                  public void onRankSelected() {
                      System.out.println("監(jiān)聽器收到:排行榜");
                      new RankDialog(MainFrame.this).setVisible(true);
                  }
      
                  @Override
                  public void onExitSelected() {
                      System.out.println("監(jiān)聽器收到:退出");
                      System.exit(0);
                  }
              });
      
              chessPanel.addMouseListener(new MouseAdapter() {
                  @Override
                  public void mouseClicked(MouseEvent e) {
                      if (!gameStarted) return;
      
                      // 優(yōu)化:動態(tài)計(jì)算棋盤坐標(biāo),適配實(shí)際面板尺寸
                      int panelSize = Math.min(chessPanel.getWidth(), chessPanel.getHeight());
                      int cellSize = (panelSize - 2 * BOARD_MARGIN) / 14;
                      int startX = (chessPanel.getWidth() - panelSize) / 2 + BOARD_MARGIN;
                      int startY = (chessPanel.getHeight() - panelSize) / 2 + BOARD_MARGIN;
      
                      int col = (e.getX() - startX + cellSize/2) / cellSize;
                      int row = (e.getY() - startY + cellSize/2) / cellSize;
      
                      if (row >= 0 && row < 15 && col >= 0 && col < 15) {
                          if (isVsAI) {
                              humanPlayer1.handleClick(row, col);
                          } else {
                              if (chessboard.getCurrentPlayer() == Chessboard.BLACK) {
                                  humanPlayer1.handleClick(row, col);
                              } else {
                                  humanPlayer2.handleClick(row, col);
                              }
                          }
                      }
                  }
              });
          }
      
          private void startPVPGame() {
              isVsAI = false;
              chessboard.reset();
              currentRecord = new GameRecord("人人對戰(zhàn)", 15);
              timerPanel.reset();
              timerPanel.start();
      
              humanPlayer1 = new HumanPlayer(Chessboard.BLACK, chessboard, this::onMoveMade);
              humanPlayer2 = new HumanPlayer(Chessboard.WHITE, chessboard, this::onMoveMade);
      
              chessPanel.repaint();
              menuPanel.updateStatus("人人對戰(zhàn) · 黑方回合");
              gameStarted = true;
              System.out.println("已進(jìn)入人人對戰(zhàn)模式");
          }
      
          private void startPVAGame() {
              isVsAI = true;
              chessboard.reset();
              currentRecord = new GameRecord("人機(jī)對戰(zhàn)", 15);
              timerPanel.reset();
              timerPanel.start();
      
              humanPlayer1 = new HumanPlayer(Chessboard.BLACK, chessboard, this::onMoveMade);
              aiPlayer = new AIPlayer(Chessboard.WHITE, chessboard, this::onMoveMade);
              aiPlayer.setDelayMillis(800);
      
              chessPanel.repaint();
              menuPanel.updateStatus("人機(jī)對戰(zhàn) · 您(黑棋)回合");
              gameStarted = true;
              System.out.println("已進(jìn)入人機(jī)對戰(zhàn)模式");
          }
      
          private void restartGame() {
              timerPanel.stop();
              if (isVsAI) {
                  startPVAGame();
              } else {
                  startPVPGame();
              }
          }
      
          private void onMoveMade(int row, int col) {
              chessboard.setLastMove(row, col);
              chessPanel.repaint();
      
              if (chessboard.checkWin(row, col)) {
                  timerPanel.stop();
                  String winner = chessboard.getCurrentPlayer() == Chessboard.BLACK ? "黑棋" : "白棋";
                  menuPanel.updateStatus(winner + "獲勝!");
                  gameStarted = false;
      
                  if (isVsAI) {
                      String username = new UsernameDialog(this).showDialog();
                      currentRecord.finishGame(winner, chessboard.getBoard(), username);
                      JOptionPane.showMessageDialog(this, 
                              "恭喜!" + username + "(" + winner + ")獲勝!\n用時:" + currentRecord.getDurationStr(),
                              "游戲結(jié)束", JOptionPane.INFORMATION_MESSAGE);
                  } else {
                      TwoPlayerInputDialog inputDialog = new TwoPlayerInputDialog(this, winner);
                      String[] usernames = inputDialog.showDialog();
                      String combinedName = usernames[0] + "|" + usernames[1];
                      currentRecord.finishGame(winner, chessboard.getBoard(), combinedName);
                      JOptionPane.showMessageDialog(this, 
                              "恭喜!" + (winner.equals("黑棋") ? usernames[0] : usernames[1]) + "獲勝!\n用時:" + currentRecord.getDurationStr(),
                              "游戲結(jié)束", JOptionPane.INFORMATION_MESSAGE);
                  }
      
                  RankList.getInstance().addRecord(currentRecord);
                  return;
              }
      
              if (GameUtils.isDraw(chessboard)) {
                  timerPanel.stop();
                  menuPanel.updateStatus("平局!");
                  gameStarted = false;
                  JOptionPane.showMessageDialog(this, "本局戰(zhàn)平!", "游戲結(jié)束", JOptionPane.INFORMATION_MESSAGE);
                  return;
              }
      
              chessboard.switchPlayer();
              if (isVsAI) {
                  menuPanel.updateStatus("人機(jī)對戰(zhàn) · AI(白棋)思考中...");
                  aiPlayer.makeMove();
                  menuPanel.updateStatus("人機(jī)對戰(zhàn) · 您(黑棋)回合");
              } else {
                  String currentPlayer = chessboard.getCurrentPlayer() == Chessboard.BLACK ? "黑方" : "白方";
                  menuPanel.updateStatus("人人對戰(zhàn) · " + currentPlayer + "回合");
              }
          }
      
          // 優(yōu)化棋盤繪制邏輯,確保完整顯示
          private class ChessboardPanel extends JPanel {
              private Chessboard chessboard;
      
              public ChessboardPanel(Chessboard chessboard) {
                  this.chessboard = chessboard;
              }
      
              @Override
              protected void paintComponent(Graphics g) {
                  super.paintComponent(g);
                  Graphics2D g2d = (Graphics2D) g;
                  g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      
                  // 關(guān)鍵:動態(tài)計(jì)算繪制區(qū)域,適配面板實(shí)際大小
                  int panelSize = Math.min(getWidth(), getHeight());
                  int drawAreaSize = panelSize - 2 * BOARD_MARGIN;
                  int cellSize = drawAreaSize / 14; // 15x15棋盤有14個間隔
                  int startX = (getWidth() - panelSize) / 2 + BOARD_MARGIN;
                  int startY = (getHeight() - panelSize) / 2 + BOARD_MARGIN;
      
                  // 繪制棋盤陰影
                  g2d.setColor(new Color(0, 0, 0, 20));
                  g2d.fillRoundRect(startX - 5, startY - 5, drawAreaSize + 10, drawAreaSize + 10, 8, 8);
      
                  // 繪制棋盤邊框
                  g2d.setColor(new Color(160, 82, 45));
                  g2d.drawRoundRect(startX, startY, drawAreaSize, drawAreaSize, 8, 8);
      
                  // 繪制棋盤底色
                  g2d.setColor(BOARD_COLOR);
                  g2d.fillRoundRect(startX + 2, startY + 2, drawAreaSize - 4, drawAreaSize - 4, 6, 6);
      
                  // 繪制網(wǎng)格
                  drawBoardGrid(g2d, startX, startY, cellSize);
      
                  // 繪制棋子
                  drawChessPieces(g2d, startX, startY, cellSize);
              }
      
              // 繪制棋盤網(wǎng)格(使用動態(tài)計(jì)算的參數(shù))
              private void drawBoardGrid(Graphics2D g2d, int startX, int startY, int cellSize) {
                  g2d.setColor(Color.BLACK);
                  g2d.setStroke(new BasicStroke(1.2f));
      
                  // 畫橫線和豎線
                  for (int i = 0; i < 15; i++) {
                      // 橫線
                      g2d.drawLine(startX, startY + i * cellSize, startX + 14 * cellSize, startY + i * cellSize);
                      // 豎線
                      g2d.drawLine(startX + i * cellSize, startY, startX + i * cellSize, startY + 14 * cellSize);
                  }
      
                  // 繪制天元和星位
                  int[] starPoints = {3, 7, 11};
                  g2d.setStroke(new BasicStroke(1f));
                  for (int x : starPoints) {
                      for (int y : starPoints) {
                          int px = startX + x * cellSize;
                          int py = startY + y * cellSize;
                          g2d.fillOval(px - 4, py - 4, 8, 8);
                      }
                  }
              }
      
              // 繪制棋子(使用動態(tài)計(jì)算的參數(shù))
              private void drawChessPieces(Graphics2D g2d, int startX, int startY, int cellSize) {
                  int chessSize = cellSize - 4;
                  int lastRow = chessboard.getLastRow();
                  int lastCol = chessboard.getLastCol();
      
                  for (int i = 0; i < 15; i++) {
                      for (int j = 0; j < 15; j++) {
                          if (chessboard.getBoard()[i][j] != Chessboard.EMPTY) {
                              int px = startX + j * cellSize;
                              int py = startY + i * cellSize;
      
                              // 棋子陰影
                              if (chessboard.getBoard()[i][j] == Chessboard.BLACK) {
                                  g2d.setColor(new Color(0, 0, 0, 30));
                              } else {
                                  g2d.setColor(new Color(0, 0, 0, 15));
                              }
                              g2d.fillOval(px - chessSize/2 + 2, py - chessSize/2 + 2, chessSize, chessSize);
      
                              // 棋子本體
                              if (chessboard.getBoard()[i][j] == Chessboard.BLACK) {
                                  g2d.setColor(Color.BLACK);
                              } else {
                                  g2d.setColor(Color.WHITE);
                                  g2d.drawOval(px - chessSize/2, py - chessSize/2, chessSize, chessSize);
                              }
                              g2d.fillOval(px - chessSize/2, py - chessSize/2, chessSize, chessSize);
      
                              // 最后一步標(biāo)記
                              if (lastRow != -1 && lastCol != -1 && i == lastRow && j == lastCol) {
                                  g2d.setColor(new Color(255, 0, 0, 80));
                                  g2d.setStroke(new BasicStroke(2f));
                                  g2d.drawOval(px - chessSize/2 - 2, py - chessSize/2 - 2, chessSize + 4, chessSize + 4);
                              }
                          }
                      }
                  }
              }
          }
      
          public static void main(String[] args) {
              try {
                  UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
              SwingUtilities.invokeLater(() -> {
                  new MainFrame("五子棋 - 優(yōu)化版").setVisible(true);
              });
          }
      }
      
      
      • MainFrame繪制圖形界面面板

      • initComponents()初始化面板,可以在這里修改面板大小

      • 使用邊界布局,添加組件,在圖形界面按動時,通過監(jiān)聽功能,運(yùn)行內(nèi)在邏輯

      • 兩種模式:人人對戰(zhàn)、人機(jī)對戰(zhàn)

      3.9 ChessboardPanel.java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import gomoku.model.Chessboard;
      import javax.swing.*;
      import java.awt.*;
      import java.awt.event.MouseAdapter;
      import java.awt.event.MouseEvent;
      
      public class ChessboardPanel extends JPanel {
          private static final int CELL_SIZE = 30;  // 每個格子的大小
          private static final int MARGIN = 20;     // 邊距
          private Chessboard chessboard;
          private ClickListener clickListener;
          private int lastRow = -1;
          private int lastCol = -1;
      
          // 新增方法:設(shè)置最后落子位置
          public void setLastMove(int row, int col) {
              this.lastRow = row;
              this.lastCol = col;
              repaint();
          }
      
          @Override
          protected void paintComponent(Graphics g) {
              super.paintComponent(g);
              Graphics2D g2d = (Graphics2D) g;
              g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
              drawChessboard(g2d);
              drawPieces(g2d);
              drawLastMoveMarker(g2d); // 繪制最后落子標(biāo)記
          }
      
          // 新增方法:繪制最后落子標(biāo)記(紅色圓點(diǎn))
          private void drawLastMoveMarker(Graphics2D g) {
              if (lastRow != -1 && lastCol != -1) {
                  int x = MARGIN + lastCol * CELL_SIZE;
                  int y = MARGIN + lastRow * CELL_SIZE;
                  g.setColor(Color.RED);
                  g.fillOval(x - 4, y - 4, 8, 8);
              }
          }
      
          
          public ChessboardPanel(Chessboard chessboard) {
              this.chessboard = chessboard;
              setPreferredSize(new Dimension(
                  chessboard.getSize() * CELL_SIZE + MARGIN * 2,
                  chessboard.getSize() * CELL_SIZE + MARGIN * 2
              ));
              setBackground(Color.WHITE);
              
              addMouseListener(new MouseAdapter() {
                  @Override
                  public void mouseClicked(MouseEvent e) {
                      if (clickListener != null) {
                          // 計(jì)算點(diǎn)擊位置對應(yīng)的棋盤坐標(biāo)
                          int row = (e.getY() - MARGIN + CELL_SIZE / 2) / CELL_SIZE;
                          int col = (e.getX() - MARGIN + CELL_SIZE / 2) / CELL_SIZE;
                          
                          if (row >= 0 && row < chessboard.getSize() && 
                              col >= 0 && col < chessboard.getSize()) {
                              clickListener.onClick(row, col);
                          }
                      }
                  }
              });
          }
          
         
          // 繪制棋盤
          private void drawChessboard(Graphics g) {
              int size = chessboard.getSize();
              
              // 繪制網(wǎng)格線
              g.setColor(Color.BLACK);
              for (int i = 0; i < size; i++) {
                  // 橫線
                  g.drawLine(MARGIN, MARGIN + i * CELL_SIZE,
                            MARGIN + (size - 1) * CELL_SIZE, MARGIN + i * CELL_SIZE);
                  // 豎線
                  g.drawLine(MARGIN + i * CELL_SIZE, MARGIN,
                            MARGIN + i * CELL_SIZE, MARGIN + (size - 1) * CELL_SIZE);
              }
              
              // 繪制天元和星位
              int[] stars = {3, 7, 11};  // 15x15棋盤的星位坐標(biāo)
              for (int x : stars) {
                  for (int y : stars) {
                      drawDot(g, MARGIN + x * CELL_SIZE, MARGIN + y * CELL_SIZE, 5);
                  }
              }
          }
          
          // 繪制棋子
          private void drawPieces(Graphics g) {
              int size = chessboard.getSize();
              int[][] board = chessboard.getBoard();
              
              for (int i = 0; i < size; i++) {
                  for (int j = 0; j < size; j++) {
                      if (board[i][j] != Chessboard.EMPTY) {
                          int x = MARGIN + j * CELL_SIZE;
                          int y = MARGIN + i * CELL_SIZE;
                          
                          // 繪制棋子
                          if (board[i][j] == Chessboard.BLACK) {
                              g.setColor(Color.BLACK);
                          } else {
                              g.setColor(Color.WHITE);
                              g.drawOval(x - CELL_SIZE / 2, y - CELL_SIZE / 2, 
                                        CELL_SIZE, CELL_SIZE);
                          }
                          g.fillOval(x - CELL_SIZE / 2, y - CELL_SIZE / 2, 
                                    CELL_SIZE, CELL_SIZE);
                          
                          // 繪制棋子邊框
                          g.setColor(Color.BLACK);
                          g.drawOval(x - CELL_SIZE / 2, y - CELL_SIZE / 2, 
                                    CELL_SIZE, CELL_SIZE);
                      }
                  }
              }
          }
          
          // 繪制小圓點(diǎn)(用于星位)
          private void drawDot(Graphics g, int x, int y, int radius) {
              g.fillOval(x - radius / 2, y - radius / 2, radius, radius);
          }
          
          // 設(shè)置點(diǎn)擊監(jiān)聽器
          public void setClickListener(ClickListener listener) {
              this.clickListener = listener;
          }
          
          // 點(diǎn)擊監(jiān)聽器接口
          @FunctionalInterface
          public interface ClickListener {
              void onClick(int row, int col);
          }
        
      }
      
      • 繪制落子后的圖形界面(棋盤、棋子),對于最后落子加個紅色邊框

      • 落子成功后,MainFrame 調(diào)用 setLastMove 更新最后落子位置,并調(diào)用 repaint()

      3.10 MenuPanel.java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import javax.swing.*;
      import java.awt.*;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      
      public class MenuPanel extends JPanel {
          // 樣式常量(保持不變)
          private static final Color BUTTON_COLOR = new Color(139, 69, 19);
          private static final Color BUTTON_TEXT_COLOR = Color.WHITE;
          private static final Color HOVER_COLOR = new Color(160, 82, 45);
          private static final Color PRESSED_COLOR = new Color(101, 67, 33);
          private static final Font MAIN_FONT = new Font("微軟雅黑", Font.PLAIN, 14);
          private static final int BUTTON_WIDTH = 110; // 縮小按鈕寬度(原120→110,減少總寬度)
          private static final int BUTTON_HEIGHT = 35;
          private static final int CORNER_RADIUS = 8;
      
          private JLabel statusLabel;
          private MenuActionListener listener;
      
          // 監(jiān)聽器接口(保持不變)
          public interface MenuActionListener {
              void onPvpSelected();
              void onPvaiSelected();
              void onRestartSelected();
              void onRankSelected();
              void onExitSelected();
          }
      
          // 構(gòu)造方法(核心修改:解決布局?jǐn)D壓問題)
          public MenuPanel() {
              // 1. 改用BoxLayout(垂直布局):先放按鈕行,再放狀態(tài)標(biāo)簽,避免橫向擠壓
              setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
              setBackground(new Color(245, 240, 230));
              
              // 2. 強(qiáng)制面板最小尺寸(確保不會被壓縮到0)
              setMinimumSize(new Dimension(850, 120));
              setPreferredSize(new Dimension(850, 120));
      
              // 3. 按鈕容器(橫向排列,用BoxLayout確保按鈕不換行且居中)
              JPanel buttonContainer = new JPanel();
              buttonContainer.setLayout(new BoxLayout(buttonContainer, BoxLayout.X_AXIS));
              buttonContainer.setBackground(new Color(245, 240, 230));
              
              // 按鈕間添加固定間距(原15→10,進(jìn)一步減少總寬度)
              int gap = 10;
              buttonContainer.add(Box.createHorizontalGlue()); // 左側(cè)空白(自動填充,使按鈕居中)
              buttonContainer.add(createStyledButton("人人對戰(zhàn)"));
              buttonContainer.add(Box.createRigidArea(new Dimension(gap, 0))); // 按鈕間距
              buttonContainer.add(createStyledButton("人機(jī)對戰(zhàn)"));
              buttonContainer.add(Box.createRigidArea(new Dimension(gap, 0)));
              buttonContainer.add(createStyledButton("重新開始"));
              buttonContainer.add(Box.createRigidArea(new Dimension(gap, 0)));
              buttonContainer.add(createStyledButton("排行榜"));
              buttonContainer.add(Box.createRigidArea(new Dimension(gap, 0)));
              buttonContainer.add(createStyledButton("退出游戲"));
              buttonContainer.add(Box.createHorizontalGlue()); // 右側(cè)空白(自動填充)
      
              // 4. 添加按鈕容器到面板(垂直方向間距10px)
              add(Box.createVerticalStrut(10)); // 頂部空白
              add(buttonContainer);
              add(Box.createVerticalStrut(10)); // 按鈕和標(biāo)簽間距
      
              // 5. 狀態(tài)標(biāo)簽(保持不變,但調(diào)整寬度適配面板)
              statusLabel = new JLabel("請選擇對戰(zhàn)模式", SwingConstants.CENTER);
              statusLabel.setFont(MAIN_FONT);
              statusLabel.setForeground(BUTTON_COLOR);
              statusLabel.setPreferredSize(new Dimension(800, 30));
              add(statusLabel);
      
              // 調(diào)試:確認(rèn)組件已添加(運(yùn)行后看控制臺)
              System.out.println("MenuPanel組件數(shù):" + getComponentCount());
              System.out.println("按鈕數(shù):" + buttonContainer.getComponentCount());
          }
      
          // 按鈕創(chuàng)建方法(保持不變,僅寬度已在常量中調(diào)整)
          private JButton createStyledButton(String text) {
              JButton button = new JButton(text);
              button.setFont(MAIN_FONT);
              button.setForeground(BUTTON_TEXT_COLOR);
              button.setBackground(BUTTON_COLOR);
              button.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
              button.setBorderPainted(false);
              button.setFocusPainted(false);
              button.setContentAreaFilled(false);
              button.setRolloverEnabled(true);
      
              // 自定義圓角UI(保持不變)
              button.setUI(new javax.swing.plaf.basic.BasicButtonUI() {
                  @Override
                  public void paint(Graphics g, JComponent c) {
                      AbstractButton b = (AbstractButton) c;
                      Graphics2D g2 = (Graphics2D) g.create();
                      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      
                      // 狀態(tài)色判斷
                      if (b.getModel().isPressed()) {
                          g2.setColor(PRESSED_COLOR);
                      } else if (b.getModel().isRollover()) {
                          g2.setColor(HOVER_COLOR);
                      } else {
                          g2.setColor(b.getBackground());
                      }
      
                      // 繪制圓角背景
                      g2.fillRoundRect(0, 0, b.getWidth(), b.getHeight(), CORNER_RADIUS, CORNER_RADIUS);
                      g2.dispose();
                      super.paint(g, c);
                  }
      
                  @Override
                  protected void paintFocus(Graphics g, AbstractButton b, Rectangle viewRect, Rectangle textRect, Rectangle iconRect) {}
              });
      
              // 按鈕點(diǎn)擊事件(保持不變)
              button.addActionListener(new ActionListener() {
                  @Override
                  public void actionPerformed(ActionEvent e) {
                      if ("人人對戰(zhàn)".equals(text) && listener != null) {
                          listener.onPvpSelected();
                      } else if ("人機(jī)對戰(zhàn)".equals(text) && listener != null) {
                          listener.onPvaiSelected();
                      } else if ("重新開始".equals(text) && listener != null) {
                          listener.onRestartSelected();
                      } else if ("排行榜".equals(text) && listener != null) {
                          listener.onRankSelected();
                      } else if ("退出游戲".equals(text) && listener != null) {
                          listener.onExitSelected();
                      }
                  }
              });
      
              return button;
          }
      
          // 設(shè)置監(jiān)聽器(保持不變)
          public void setMenuListener(MenuActionListener listener) {
              this.listener = listener;
          }
      
          // 更新狀態(tài)(保持不變)
          public void updateStatus(String text) {
              if (statusLabel != null) {
                  statusLabel.setText(text);
              }
          }
      }
      
      • 繪制最上部的菜單欄

      • 通過按鈕來實(shí)現(xiàn)人人對戰(zhàn)/人機(jī)對戰(zhàn)

      • 使用多種方法,增強(qiáng)了封裝性

      3.11 TimerPanel .java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import javax.swing.*;
      import java.awt.*;
      import java.util.Timer;
      import java.util.TimerTask;
      
      public class TimerPanel extends JPanel {
          private JLabel timerLabel;
          private Timer timer;
          private int seconds = 0;
      
          public TimerPanel() {
              setBackground(new Color(245, 240, 230));
              timerLabel = new JLabel("游戲時間:00:00", SwingConstants.CENTER);
              timerLabel.setFont(new Font("微軟雅黑", Font.PLAIN, 14));
              timerLabel.setForeground(new Color(139, 69, 19));
              add(timerLabel);
          }
      
          // 開始計(jì)時
          public void start() {
              if (timer == null) {
                  timer = new Timer();
                  timer.scheduleAtFixedRate(new TimerTask() {
                      @Override
                      public void run() {
                          seconds++;
                          int minutes = seconds / 60;
                          int secs = seconds % 60;
                          timerLabel.setText(String.format("游戲時間:%02d:%02d", minutes, secs));
                      }
                  }, 1000, 1000);
              }
          }
      
          // 重置計(jì)時
          public void reset() {
              if (timer != null) {
                  timer.cancel();
                  timer = null;
              }
              seconds = 0;
              timerLabel.setText("游戲時間:00:00");
          }
      
          // 停止計(jì)時
          public void stop() {
              if (timer != null) {
                  timer.cancel();
                  timer = null;
              }
          }
      }
      
      • 記錄游戲時長,并使用timerLabel.setText(String.format("游戲時間:%02d:%02d", minutes, secs))來規(guī)范化展示

      3.12 BoardPreview.java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import gomoku.model.GameRecord;
      import gomoku.model.Chessboard;
      
      import javax.swing.*;
      import java.awt.*;
      
      /**
       * 棋盤預(yù)覽對話框,用于顯示歷史棋局
       */
      public class BoardPreviewDialog extends JDialog {
          private GameRecord record;
          private int cellSize = 20; // 預(yù)覽棋盤的單元格大小
          private int margin = 10;   // 邊距
      
          // 正確的構(gòu)造函數(shù),接受父窗口和游戲記錄
          public BoardPreviewDialog(Dialog parent, GameRecord record) {
              super(parent, "棋局預(yù)覽", true);
              this.record = record;
              initComponents();
              setupLayout();
              pack();
              setLocationRelativeTo(parent);
          }
      
          private void initComponents() {
              // 創(chuàng)建信息面板顯示游戲詳情
          	JPanel infoPanel = new JPanel(new GridLayout(4, 2, 10, 5));
              infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
              
              infoPanel.add(new JLabel("玩家:"));
              infoPanel.add(new JLabel(record.getUsername()));
              
              infoPanel.add(new JLabel("游戲類型:"));
              infoPanel.add(new JLabel(record.getGameType()));
              
              infoPanel.add(new JLabel("獲勝方:"));
              infoPanel.add(new JLabel(record.getWinner()));
              
              infoPanel.add(new JLabel("用時:"));
              infoPanel.add(new JLabel(record.getDurationStr()));
      
              // 創(chuàng)建棋盤預(yù)覽面板
              JPanel boardPanel = new JPanel() {
                  @Override
                  protected void paintComponent(Graphics g) {
                      super.paintComponent(g);
                      drawPreviewBoard(g);
                  }
                  
                  @Override
                  public Dimension getPreferredSize() {
                      int size = record.getBoardSize() * cellSize + margin * 2;
                      return new Dimension(size, size);
                  }
              };
              boardPanel.setBackground(Color.WHITE);
      
              // 添加關(guān)閉按鈕
              JButton closeBtn = new JButton("關(guān)閉");
              closeBtn.addActionListener(e -> dispose());
              JPanel buttonPanel = new JPanel();
              buttonPanel.add(closeBtn);
      
              // 組裝界面
              setLayout(new BorderLayout(10, 10));
              add(infoPanel, BorderLayout.NORTH);
              add(new JScrollPane(boardPanel), BorderLayout.CENTER);
              add(buttonPanel, BorderLayout.SOUTH);
          }
      
          private void setupLayout() {
              setResizable(false);
          }
      
          // 繪制預(yù)覽棋盤
          private void drawPreviewBoard(Graphics g) {
              int[][] board = record.getBoardState();
              int size = record.getBoardSize();
              
              // 繪制網(wǎng)格
              g.setColor(Color.BLACK);
              for (int i = 0; i < size; i++) {
                  // 橫線
                  g.drawLine(margin, margin + i * cellSize,
                          margin + (size - 1) * cellSize, margin + i * cellSize);
                  // 豎線
                  g.drawLine(margin + i * cellSize, margin,
                          margin + i * cellSize, margin + (size - 1) * cellSize);
              }
              
              // 繪制棋子
              for (int i = 0; i < size; i++) {
                  for (int j = 0; j < size; j++) {
                      if (board[i][j] != Chessboard.EMPTY) {
                          int x = margin + j * cellSize;
                          int y = margin + i * cellSize;
                          
                          if (board[i][j] == Chessboard.BLACK) {
                              g.setColor(Color.BLACK);
                          } else {
                              g.setColor(Color.WHITE);
                              g.drawOval(x - cellSize / 2, y - cellSize / 2, 
                                      cellSize, cellSize);
                          }
                          g.fillOval(x - cellSize / 2, y - cellSize / 2, 
                                  cellSize, cellSize);
                          
                          // 繪制邊框
                          g.setColor(Color.BLACK);
                          g.drawOval(x - cellSize / 2, y - cellSize / 2, 
                                  cellSize, cellSize);
                      }
                  }
              }
          }
      }
      
      • 使用邊界布局

      • 通過點(diǎn)擊,調(diào)用GameRecord展示歷史數(shù)據(jù)

      3.13 TowPlayerInputDialog .java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import javax.swing.*;
      import java.awt.*;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      
      /**
       * 雙人對戰(zhàn)結(jié)束時輸入兩個玩家用戶名的對話框
       */
      public class TwoPlayerInputDialog extends JDialog {
          private JTextField blackPlayerField;
          private JTextField whitePlayerField;
          private String[] result; // 存儲兩個玩家的用戶名,索引0為黑方,1為白方
          private String winnerColor; // 獲勝方顏色("黑棋"或"白棋")
      
          public TwoPlayerInputDialog(Frame parent, String winnerColor) {
              super(parent, "游戲結(jié)束 - 輸入玩家信息", true);
              this.winnerColor = winnerColor;
              this.result = new String[2];
              initComponents();
              setupLayout();
              pack();
              setLocationRelativeTo(parent);
          }
      
          private void initComponents() {
              // 黑方玩家輸入
              JLabel blackLabel = new JLabel("黑方玩家:");
              blackPlayerField = new JTextField(15);
              blackPlayerField.setText("黑方玩家");
      
              // 白方玩家輸入
              JLabel whiteLabel = new JLabel("白方玩家:");
              whitePlayerField = new JTextField(15);
              whitePlayerField.setText("白方玩家");
      
              // 獲勝信息提示
              JLabel winnerLabel = new JLabel("<html><font color='red'>" + winnerColor + "獲勝!</font></html>");
              winnerLabel.setFont(new Font("SimHei", Font.BOLD, 14));
      
              // 按鈕
              JButton confirmBtn = new JButton("確認(rèn)");
              confirmBtn.addActionListener(new ActionListener() {
                  @Override
                  public void actionPerformed(ActionEvent e) {
                      result[0] = getPlayerName(blackPlayerField.getText(), "黑方玩家");
                      result[1] = getPlayerName(whitePlayerField.getText(), "白方玩家");
                      dispose();
                  }
              });
      
              // 布局
              JPanel panel = new JPanel(new GridBagLayout());
              GridBagConstraints gbc = new GridBagConstraints();
              gbc.insets = new Insets(5, 5, 5, 5);
              gbc.anchor = GridBagConstraints.WEST;
      
              // 獲勝信息行
              gbc.gridx = 0;
              gbc.gridy = 0;
              gbc.gridwidth = 2;
              panel.add(winnerLabel, gbc);
      
              // 重置網(wǎng)格寬度
              gbc.gridwidth = 1;
              
              // 黑方玩家行
              gbc.gridx = 0;
              gbc.gridy = 1;
              panel.add(blackLabel, gbc);
              gbc.gridx = 1;
              panel.add(blackPlayerField, gbc);
      
              // 白方玩家行
              gbc.gridx = 0;
              gbc.gridy = 2;
              panel.add(whiteLabel, gbc);
              gbc.gridx = 1;
              panel.add(whitePlayerField, gbc);
      
              // 按鈕行
              gbc.gridx = 0;
              gbc.gridy = 3;
              gbc.gridwidth = 2;
              gbc.anchor = GridBagConstraints.CENTER;
              panel.add(confirmBtn, gbc);
      
              add(panel);
          }
      
          private void setupLayout() {
              setResizable(false);
          }
      
          /**
           * 獲取玩家輸入的用戶名,為空時使用默認(rèn)值
           */
          private String getPlayerName(String input, String defaultName) {
              String name = input.trim();
              return name.isEmpty() ? defaultName : name;
          }
      
          /**
           * 顯示對話框并返回結(jié)果
           * @return 長度為2的數(shù)組,[0]是黑方用戶名,[1]是白方用戶名
           */
          public String[] showDialog() {
              setVisible(true);
              return result;
          }
      }
      
      
      • 雙人對戰(zhàn)結(jié)束時輸入兩個玩家用戶名的對話框,使用String[]來存儲用戶名稱

      • 使用邊界布局

      • 最開始默認(rèn)顯示黑方玩家/白方玩家,可以在文本框中輸入不同用戶名

      • 使用GridBagLayout自由調(diào)整界面布局,使界面布局更加美觀

      3.14 UsernameDialog.java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import javax.swing.*;
      import java.awt.*;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      
      /**
       * 游戲結(jié)束后輸入用戶名的對話框
       */
      public class UsernameDialog extends JDialog {
          private JTextField usernameField;
          private String username;
          private boolean confirmed;
      
          public UsernameDialog(Frame parent) {
              super(parent, "游戲結(jié)束", true);
              initComponents();
              setSize(300, 150);
              setLocationRelativeTo(parent);
          }
      
          private void initComponents() {
              JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10));
              panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
      
              // 提示標(biāo)簽
              JLabel label = new JLabel("請輸入您的用戶名:");
              panel.add(label);
      
              // 輸入框
              usernameField = new JTextField();
              usernameField.setText("玩家1"); // 默認(rèn)用戶名
              panel.add(usernameField);
      
              // 按鈕面板
              JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
              JButton confirmButton = new JButton("確認(rèn)");
              confirmButton.addActionListener(new ActionListener() {
                  @Override
                  public void actionPerformed(ActionEvent e) {
                      username = usernameField.getText().trim();
                      if (username.isEmpty()) {
                          username = "匿名玩家";
                      }
                      confirmed = true;
                      dispose();
                  }
              });
              buttonPanel.add(confirmButton);
      
              // 布局設(shè)置
              getContentPane().setLayout(new BorderLayout());
              getContentPane().add(panel, BorderLayout.CENTER);
              getContentPane().add(buttonPanel, BorderLayout.SOUTH);
          }
      
          // 顯示對話框并返回用戶名
          public String showDialog() {
              setVisible(true);
              return confirmed ? username : "匿名玩家";
          }
      }
      
      
      • 人機(jī)模式下的玩家名稱輸入框,默認(rèn)顯示玩家1

      • 邊界布局

      • 使用setVisible(true)來鎖定界面,直到點(diǎn)擊確定/取消才結(jié)束

      • 使用confirmed來標(biāo)記,判斷返回的用戶名

      3.15 RankDialog .java

      點(diǎn)擊查看代碼
      package gomoku.view;
      
      import gomoku.model.GameRecord;
      import gomoku.model.RankList;
      
      import javax.swing.*;
      import javax.swing.table.*;
      import java.awt.*;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      import java.awt.event.MouseEvent;
      import java.util.List;
      
      public class RankDialog extends JDialog {
          private JTabbedPane tabbedPane;
          private RankList rankList;
          // 添加序列化版本號解決第一個警告
          private static final long serialVersionUID = 1L;
      
          public RankDialog(Frame owner) {
              super(owner, "游戲排行榜", true);
              rankList = RankList.getInstance();
              initComponents();
              setupLayout();
              pack();
              setLocationRelativeTo(owner);
          }
      
          private void initComponents() {
              tabbedPane = new JTabbedPane();
              tabbedPane.addTab("人機(jī)對戰(zhàn)", createRankTable("人機(jī)對戰(zhàn)"));
              tabbedPane.addTab("人人對戰(zhàn)", createRankTable("人人對戰(zhàn)"));
              tabbedPane.addTab("總排行榜", createRankTable("全部"));
          }
      
          private void setupLayout() {
              setLayout(new BorderLayout());
              add(tabbedPane, BorderLayout.CENTER);
              
              JPanel buttonPanel = new JPanel();
              JButton closeBtn = new JButton("關(guān)閉");
              closeBtn.addActionListener(e -> dispose());
              buttonPanel.add(closeBtn);
              add(buttonPanel, BorderLayout.SOUTH);
          }
      
          private JComponent createRankTable(String type) {
              // 修正數(shù)組初始化方式
              String[] columns = type.equals("人人對戰(zhàn)") ? 
                  new String[]{"排名", "黑方玩家", "白方玩家", "獲勝方", "時長", "日期", "操作"} :
                  new String[]{"排名", "玩家", "游戲類型", "獲勝方", "時長", "日期", "操作"};
              
              // 確保變量名唯一,不重復(fù)
              DefaultTableModel tableModel = new DefaultTableModel(columns, 0) {
                  // 為內(nèi)部類添加序列化版本號
                  private static final long serialVersionUID = 2L;
                  
                  @Override
                  public boolean isCellEditable(int row, int column) {
                      return false;
                  }
                  
                  @Override
                  public Class<?> getColumnClass(int column) {
                      return (column == columns.length - 1) ? JButton.class : Object.class;
                  }
              };
      
              List<GameRecord> records;
              switch (type) {
                  case "人機(jī)對戰(zhàn)":
                      records = rankList.getAiRankList();
                      break;
                  case "人人對戰(zhàn)":
                      records = rankList.getPvpRankList();
                      break;
                  default:
                      records = rankList.getTotalRankList();
                      break;
              }
      
              for (int i = 0; i < records.size(); i++) {
                  GameRecord record = records.get(i);
                  JButton viewButton = new JButton("查看棋局");
                  final int index = i;
      
                  viewButton.addActionListener(e -> {
                      new BoardPreviewDialog(RankDialog.this, records.get(index)).setVisible(true);
                  });
      
                  Object[] row;
                  if (type.equals("人人對戰(zhàn)")) {
                      String[] players = record.getUsername().split("\\|");
                      String blackPlayer = players.length > 0 ? players[0] : "黑方玩家";
                      String whitePlayer = players.length > 1 ? players[1] : "白方玩家";
                      
                      row = new Object[]{
                              i + 1,
                              blackPlayer,
                              whitePlayer,
                              record.getWinner(),
                              record.getDurationStr(),
                              record.getStartTime().toLocalDate().toString(),
                              viewButton
                      };
                  } else {
                      row = new Object[]{
                              i + 1,
                              record.getUsername(),
                              record.getGameType(),
                              record.getWinner(),
                              record.getDurationStr(),
                              record.getStartTime().toLocalDate().toString(),
                              viewButton
                      };
                  }
                  tableModel.addRow(row); // 使用修改后的變量名
              }
      
              JTable table = new JTable(tableModel) { // 使用修改后的變量名
                  private static final long serialVersionUID = 3L;
                  
                  @Override
                  public TableCellRenderer getCellRenderer(int row, int column) {
                      if (column == columns.length - 1) {
                          return new DefaultTableCellRenderer() {
                              private static final long serialVersionUID = 4L;
                              
                              @Override
                              public Component getTableCellRendererComponent(JTable table, Object value,
                                                                            boolean isSelected, boolean hasFocus,
                                                                            int row, int column) {
                                  if (value instanceof JButton) {
                                      JButton btn = (JButton) value;
                                      if (isSelected) {
                                          btn.setBackground(table.getSelectionBackground());
                                          btn.setForeground(table.getSelectionForeground());
                                      } else {
                                          btn.setBackground(table.getBackground());
                                          btn.setForeground(table.getForeground());
                                      }
                                      return btn;
                                  }
                                  return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                              }
                          };
                      }
                      return super.getCellRenderer(row, column);
                  }
      
                  @Override
                  protected void processMouseEvent(MouseEvent e) {
                      int clickRow = rowAtPoint(e.getPoint());
                      int clickCol = columnAtPoint(e.getPoint());
      
                      if (clickRow != -1 && clickCol == columns.length - 1 && e.getButton() == MouseEvent.BUTTON1) {
                          Object cellValue = getModel().getValueAt(clickRow, clickCol);
                          if (cellValue instanceof JButton) {
                              ((JButton) cellValue).doClick();
                              return;
                          }
                      }
                      super.processMouseEvent(e);
                  }
              };
      
              // 設(shè)置列寬
              TableColumnModel columnModel = table.getColumnModel();
              if (type.equals("人人對戰(zhàn)")) {
                  columnModel.getColumn(0).setPreferredWidth(50);
                  columnModel.getColumn(1).setPreferredWidth(100);
                  columnModel.getColumn(2).setPreferredWidth(100);
                  columnModel.getColumn(3).setPreferredWidth(80);
                  columnModel.getColumn(4).setPreferredWidth(80);
                  columnModel.getColumn(5).setPreferredWidth(120);
                  columnModel.getColumn(6).setPreferredWidth(100);
              } else {
                  columnModel.getColumn(0).setPreferredWidth(50);
                  columnModel.getColumn(1).setPreferredWidth(100);
                  columnModel.getColumn(2).setPreferredWidth(100);
                  columnModel.getColumn(3).setPreferredWidth(80);
                  columnModel.getColumn(4).setPreferredWidth(80);
                  columnModel.getColumn(5).setPreferredWidth(120);
                  columnModel.getColumn(6).setPreferredWidth(100);
              }
      
              table.getTableHeader().setResizingAllowed(false);
              table.setRowHeight(30);
              table.setFocusable(false);
              
              return new JScrollPane(table);
          }
      }
      
      • 展示排行榜

      3.16 GameUtils.java

      點(diǎn)擊查看代碼
      package gomoku.util;
      
      import gomoku.model.Chessboard;
      
      public class GameUtils {
          // 檢查是否平局
          public static boolean isDraw(Chessboard chessboard) {
              int size = chessboard.getSize();
              for (int i = 0; i < size; i++) {
                  for (int j = 0; j < size; j++) {
                      if (chessboard.getPiece(i, j) == Chessboard.EMPTY) {
                          return false;  // 還有空位,不是平局
                      }
                  }
              }
              return true;  // 棋盤已滿,平局
          }
      }
      
      • 通過檢驗(yàn)棋盤上是否還有空格(調(diào)用chessboard.getPiece)來判斷平局

      運(yùn)行結(jié)果展示

      1.界面展示

      6316df4e500c9b87057fcbc41ed7f76b

      2.人機(jī)對戰(zhàn)過程展示

      e33dfa7999016bd754fdf9306a3d5b42

      3.人機(jī)對戰(zhàn)對話框展示

      b7a90f753e2ac32676da732a23e591d8

      4.人機(jī)對戰(zhàn)勝利界面展示

      3b09c463cbcc84c1a477183281c7122f

      5.人人對戰(zhàn)過程展示

      d5fb75f0e8e053476e6c542b44b7550c

      6.人人對戰(zhàn)勝利界面展示

      08f4f09a8011a7f1c1c988baa1b84426

      7.排行榜展示

      f57758b57f6b04fa1a72c052cceccba2
      e760c59b74d31f5d5e3fdcf69dab3205
      e760c59b74d31f5d5e3fdcf69dab3205
      532e34b46d9dc0490fb7cafa2792c793

      8.對戰(zhàn)記錄展示

      04e727f7677eb1a024d84912727e7eb4

      總結(jié)

      • 該游戲采用邊界布局,主要同通過監(jiān)聽的方式來判斷落子的位置,在根據(jù)其落子的合法性來判斷是否刷新頁面,在頁面上生成該落子。
      • 人機(jī)對戰(zhàn)中AI的落子是隨機(jī)生成的,模式較為簡單,可以優(yōu)化其落子算法,使游戲趣味性更加豐富。
      posted @ 2025-11-03 23:08  穗和  閱讀(9)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 久久99久国产精品66| 69人妻精品中文字幕| 尤物tv国产精品看片在线| 国产午夜亚洲精品一区| 精品国产粉嫩一区二区三区| 日本欧美大码a在线观看| 无码国内精品久久人妻蜜桃| 怡红院一区二区三区在线| 999国产精品999久久久久久| 午夜精品久久久久久久久| 人人爽人人爽人人片a免费| 亚洲熟妇国产熟妇肥婆| 一区二区不卡99精品日韩| 久久综合97丁香色香蕉| 日夜啪啪一区二区三区| 欧美成本人视频免费播放 | 开心一区二区三区激情| 亚洲av成人免费在线| 国产免费播放一区二区三区| 国产精品香蕉在线观看不卡| 开心久久综合激情五月天 | 中文字幕有码无码AV| 亚洲中文字幕无码专区| 视频一区视频二区中文字幕 | 国产不卡免费一区二区| 欧美性猛交xxxx乱大交丰满| 强伦姧人妻免费无码电影| 在线视频一区二区三区色 | 精品一区精品二区制服| 桂林市| 不卡一区二区三区四区视频| 最新偷拍一区二区三区| AV最新高清无码专区| 黑人强伦姧人妻久久| 伊人久久大香线蕉av五月天| 老色鬼永久精品网站| 7m精品福利视频导航| 中文字幕精品亚洲字幕成| 国产午精品午夜福利757视频播放 国产午夜亚洲精品国产成人 | 久久99国产精一区二区三区! | 九九久久自然熟的香蕉图片|