iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Software Development

軟體開發養成計畫:以小程式實作深化開發能力系列 第 12

[Day12]從紙上遊戲到程式實作:Python 版井字遊戲

  • 分享至 

  • xImage
  •  

開場

還記得小時候我們一定玩過的井字遊戲嗎?在課本裡、在白紙上、在沙坑中...都可以看到他的身影,而這次我們用VS Code Python把它做成小程式,它出現在自己的電腦吧!

1.井字遊戲介紹 & 為什麼適合練習寫成小程式

先回顧一下井字遊戲,英文稱為 Tic-Tac-Toe,說不定大家更常聽到的的是它通俗的名字-- "OOXX"。
這是一種簡單的兩人對弈遊戲,遊戲玩法是在一個 3x3 的格子棋盤上進行,兩位玩家輪流下棋,一位玩家使用 "X" 作為標記,另一位則使用 "O",連線可以是水平、垂直或對角線,而最快連成一條線者獲勝。如果整個棋盤的格子都被填滿了,但沒有任何玩家連線成功,那麼這場遊戲就是平手。

那我們在寫井字遊戲程式的過程可以學到甚麼呢?

(1)鍛鍊基礎資料結構

這個遊戲會讓我們使用到二維列表2D list來代表棋盤,可以藉由學習存取、修改列表中的元素,並利用巢狀迴圈nested loops來遍歷棋盤,進行狀態的檢查。
這是偏更複雜程式的基礎,對跟我一樣的新手打好底子非常有幫助。

(2)訓練條件判斷和迴圈

遊戲運行的部分離開不了while迴圈,while將控制遊戲進行,直到有人獲勝或平手為止。
此外,if/else/elif在這個程式中也會大量的被運用到來對應各種情況,像是:

  • 檢查玩家輸入的座標是否正確。
  • 判斷是否有玩家連線獲勝。
  • 判斷是否棋盤已滿達成平手。

這些都是程式設計中最核心的邏輯控制。

(3)建立函式和程式模組化概念

在做這項主題時會將程式的不同功能拆分成獨立的函式Functions,讓程式碼更清晰易讀。
例如說,我們可以建立一個函式來「顯示棋盤」、一個函式來「檢查勝負」,以及一個函式來「處理玩家輸入」。

2.基本程式碼片段

(1)初始化(init)

class TicTacToe:
    def __init__(self, master):
        self.master = master
        self.master.title("井字遊戲 (Tic-Tac-Toe)")
        
        self.current_player = 'X'
        self.board = [['' for _ in range(3)] for _ in range(3)]
        self.buttons = [[None for _ in range(3)] for _ in range(3)]
        
        self.create_widgets()

這是類別創建時會自動執行的函式,用來初始化遊戲的所有核心變數。

  • self.master:代表主視窗,是所有 UI 元件的父容器。
  • self.current_player:追蹤目前輪到誰下棋。
  • self.board:一個 3x3 的二維列表,用來表示遊戲棋盤的後台資料。
  • self.buttons:一個 3x3 的二維列表,用來存放前端畫面上的 9 個按鈕物件。
  • self.create_widgets():在這裡呼叫,用來建立遊戲的所有圖形介面。

(2)建立介面 (create_widgets)

def create_widgets(self):
        self.label = tk.Label(...)
        self.board_frame = tk.Frame(...)
        
        for row in range(3):
            for col in range(3):
                button = tk.Button(self.board_frame, ...)
                button.grid(...)
                self.buttons[row][col] = button
                
        self.restart_button = tk.Button(...)

負責建立所有使用者介面(UI)元件,並將它們放置在視窗上。

  • tk.Frame:我們創建了一個獨立的 Frame 物件,並將其背景設為藍色。這樣所有按鈕都放在這個 Frame 裡,形成一個帶有藍色邊框的九宮格。
  • 按鈕迴圈:這段程式碼用兩層 for 迴圈來動態建立 9 個按鈕,並將它們以 3x3 的網格排列。
  • command=lambda:每個按鈕都綁定一個 button_click 函式。lambda 的作用是確保函式在被點擊時才執行,同時將按鈕的行和列當作參數傳遞進去。

(3)處理點擊事件(button_click)

def button_click(self, row, col):
        if self.board[row][col] != '':
            return
            
        self.board[row][col] = self.current_player
        self.buttons[row][col].config(text=self.current_player)
        
        if self.check_winner(self.board):
        elif self.check_draw(self.board):
        else:
            self.current_player = 'O' if self.current_player == 'X' else 'X'

這是遊戲的核心邏輯,當玩家點擊任何一個按鈕時,這個函式就會被觸發。

  • 首先會檢查點擊的格子是否已經被佔用。如果是,直接跳出函式。
  • 將後台的 self.board 列表和前端的 self.buttons 視覺顯示都更新為當前玩家的符號。
  • 每次下完棋後,立即呼叫 check_winnercheck_draw 函式來判斷遊戲是否結束。
  • 如果遊戲沒有結束,就將 self.current_player 從 'X' 切換到 'O'。

(4)檢查勝負 (check_winner & check_draw)

def check_winner(self, board):
        return False
        
    def check_draw(self, board):
        return False

這兩個函式負責遊戲的結束判斷,它們只會檢查棋盤的當前狀態,並回傳 True 或 False。

  • check_winner:邏輯上包含了所有連成三線的可能性(三條水平、三條垂直、兩條對角線)。
  • check_draw:只要在棋盤上找到任何一個空位,就表示遊戲還沒有平手。

(5)重啟遊戲 (restart_game)

def restart_game(self):
        self.restart_button.config(state=tk.DISABLED)

當玩家點擊「再玩一次」按鈕時,這個函式會將所有遊戲狀態重設到初始狀態,為新的一局做準備。

  • 除了重設 boardcurrent_player,它還會將所有按鈕的文字清空,並重新禁用「再玩一次」按鈕,讓遊戲循環能夠重新開始。

3.成果展示

23
24

4.學習心得

我們小時候訓練思考所玩的遊戲——「OOXX」,還真是含有著極大的深意呀。
這次的井字遊戲練習,雖然只是個簡單的小專案,但實際動手後才發現,它遠比我想像的更有挑戰性。從玩家輸入、棋盤更新到勝負判斷,每個步驟環環相扣。

過程中我體會到,程式設計不只是把功能堆疊起來,而是要有系統地思考邏輯,確保每一步都能正確銜接。尤其在勝負判斷的部分,看似簡單的三條直線、三條橫線和兩條斜線,實際寫起來卻需要仔細檢查,避免漏掉任何情況。

同時我也發現,錯誤訊息或是小 bug 其實是最好的老師,每次修正的過程,都讓我更理解程式的運作方式,雖然花了不少時間,但完成後看到遊戲能順利跑起來,心裡的成就感比預期的還要大。
之後的幾天也會繼續保持這股動力寫小程式的!


上一篇
[Day11]實戰設計模式:掌握程式架構的基石——單例模式與 MVC
下一篇
[Day13]Debug 技巧入門:程式出錯了該怎麼辦?
系列文
軟體開發養成計畫:以小程式實作深化開發能力17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言