經歷了一連串的實作與調整,我們的 Tic-Tac-Toe(井字棋) Swing 遊戲專案終於正式劃下完美的句點!
從最開始建立一個空無一物的黑色視窗,到加入 3x3 網格面板、實作兩位玩家的回合交替、四種方向的連線勝負判斷,最後再加上平局機制與勝利/平手高亮特效,我們成功完成了一款功能完整且質感極佳的桌面小遊戲。
在這篇完結篇中,我們將重頭梳理整個專案的核心架構,並提供最終完整的原始碼,方便大家對照與學習!
資料結構與狀態控管 :
全方位勝負與平局判定 :
在 checkWinner() 方法中,我們實作了四種連線檢查:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TicTacToe {
// 視窗尺寸設定
int boardWidth = 600;
int boardHeight = 650; // 50px 用於頂部的文字面板
// Swing UI 元件
JFrame frame = new JFrame("Tic-Tac-Toe");
JLabel textLabel = new JLabel();
JPanel textPanel = new JPanel();
JPanel boardPanel = new JPanel();
// 遊戲狀態變數
JButton[][] board = new JButton[3][3];
String playerX = "X";
String playerO = "O";
String currentPlayer = playerX;
boolean gameOver = false;
int turns = 0;
TicTacToe(){
// 1. 初始化主視窗
frame.setVisible(true);
frame.setSize(boardWidth, boardHeight);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
// 2. 初始化頂部文字標籤與面板
textLabel.setBackground(Color.darkGray);
textLabel.setForeground(Color.white);
textLabel.setFont(new Font("Arial", Font.BOLD, 50));
textLabel.setHorizontalAlignment(JLabel.CENTER);
textLabel.setText("Tic-Tac-Toe");
textLabel.setOpaque(true);
textPanel.setLayout(new BorderLayout());
textPanel.add(textLabel);
frame.add(textPanel, BorderLayout.NORTH);
// 3. 初始化 3x3 網格棋盤面板
boardPanel.setLayout(new GridLayout(3, 3));
boardPanel.setBackground(Color.darkGray);
frame.add(boardPanel);
// 4. 建立 9 個按鈕並綁定事件監聽器
for (int r = 0; r < 3; r++){
for (int c = 0; c < 3; c++){
JButton tile = new JButton();
board[r][c] = tile;
boardPanel.add(tile);
tile.setBackground(Color.darkGray);
tile.setForeground(Color.white);
tile.setFont(new Font("Arial", Font.BOLD, 120));
tile.setFocusable(false);
// 按鈕點擊監聽
tile.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
if (gameOver) return; // 遊戲結束阻斷點擊
JButton tile = (JButton)e.getSource();
// 確保該格子尚未下過
if (tile.getText() == ""){
tile.setText(currentPlayer);
turns++; // 增加總步數
checkWinner(); // 檢查勝負與平手
// 未結束時切換回合
if (!gameOver){
currentPlayer = currentPlayer == playerX ? playerO : playerX;
textLabel.setText(currentPlayer + "'s turn.");
}
}
}
});
}
}
}
// 勝負與平手檢查方法
void checkWinner() {
// 1. 水平方向檢查 (Horizontal)
for (int r = 0; r < 3; r++) {
if (board[r][0].getText() == "") continue;
if (board[r][0].getText() == board[r][1].getText() &&
board[r][1].getText() == board[r][2].getText()){
for (int i = 0; i < 3; i++) {
setWinner(board[r][i]);
}
gameOver = true;
return;
}
}
// 2. 垂直方向檢查 (Vertical)
for (int c = 0; c < 3; c++){
if (board[0][c].getText() == "") continue;
if (board[0][c].getText() == board[1][c].getText() &&
board[0][c].getText() == board[2][c].getText()){
for (int i = 0; i < 3; i++){
setWinner(board[i][c]);
}
gameOver = true;
return;
}
}
// 3. 主對角線檢查 (Diagonally)
if (board[0][0].getText() == board[1][1].getText() &&
board[1][1].getText() == board[2][2].getText() &&
board[0][0].getText() != "") {
for (int i = 0; i < 3; i++){
setWinner(board[i][i]);
}
gameOver = true;
return;
}
// 4. 反對角線檢查 (Anti-diagonally)
if (board[0][2].getText() == board[1][1].getText() &&
board[1][1].getText() == board[2][0].getText() &&
board[0][2].getText() != "") {
setWinner(board[0][2]);
setWinner(board[1][1]);
setWinner(board[2][0]);
gameOver = true;
return;
}
// 5. 平局檢查 (Tie)
if (turns == 9) {
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
setTie(board[r][c]);
}
}
gameOver = true;
}
}
// 獲勝視覺樣式
void setWinner(JButton tile) {
tile.setForeground(Color.green);
tile.setBackground(Color.gray);
textLabel.setText(currentPlayer + " is the winner!");
}
// 平手視覺樣式
void setTie(JButton tile) {
tile.setForeground(Color.orange);
tile.setBackground(Color.gray);
textLabel.setText("Tie!");
}
}