古法编程: Python OOP 井字棋游戏

4 阅读3分钟

井字棋游戏 (Tic-Tac-Toe)

场景描述

井字棋(Tic-Tac-Toe)是一款经典的两人对弈游戏,在3×3的棋盘上进行。玩家轮流放置自己的标记(X或O),率先在横、竖或斜方向连成一线者获胜。如果棋盘填满仍未分出胜负,则为平局。

本项目使用Python面向对象编程实现了一个完整的井字棋游戏,包含以下特性:

  • 清晰的代码结构,分离了游戏逻辑和界面展示
  • 输入验证,确保玩家只能选择空位
  • 自动判断胜负和平局
  • 友好的用户界面,显示当前棋盘状态

流程图

┌─────────────────────────────────────────────────────────────┐
│                      游戏开始                                 │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
              ┌────────────────────────┐
              │   显示欢迎信息           │
              │ "Welcome to tic-tac-toe"│
              └────────────┬───────────┘
                           │
                           ▼
              ┌────────────────────────┐
              │  初始化棋盘 (TTTBoard)   │
              │  current_player = X    │
              │  next_player = O       │
              └────────────┬───────────┘
                           │
                           ▼
              ┌────────────────────────┐
              │      显示棋盘            │
              │   (调用 __str__)        │
              └────────────┬───────────┘
                           │
              ┌────────────▼───────────┐
              │                        │
              │   ┌──────────────────┐ │
              │   │ 提示当前玩家输入   │ │◄───┐
              │   │ "X/O请选择移动"   │ │    │
              │   └────────┬─────────┘ │    │
              │            │           │    │
              │   ┌────────▼─────────┐ │    │
              │   │ 读取用户输入 (1-9)│ │    │
              │   └────────┬─────────┘ │    │
              │            │           │    │
              │   ┌────────▼─────────┐ │    │
              │   │ is_valid_space?  │─────┼──── 无效
              │   └────────┬─────────┘ │    │
              │            │           │    │
              │            │ 有效      │    │
              │   ┌────────▼─────────┐ │    │
              │   │ update_board()   │ │    │
              │   │ 放置标记 (X或O)   │ │    │
              │   └────────┬─────────┘ │    │
              └────────────┼───────────┘    │
                           │                │
                           ▼                │
              ┌────────────────────────┐    │
              │   is_winner(current)?  │    │
              └────────────┬───────────┘    │
                           │                │
              ┌────────────┼────────────┐   │
              │            │            │   │
            是│            │否          │   │
              ▼            │            ▼   │
   ┌──────────────┐   ┌─────▼─────┐  ┌──────────────┐
   │ 显示获胜信息  │   │is_board   │  │              │
   │ "X 获胜!"   │   │ _full()?  │  │              │
   └──────┬───────┘   └─────┬─────┘  │              │
          │                 │        │              │
          │         ┌───────┼───────┐│              │
          │        是│       │否     ││              │
          │         ▼       ▼       ││              │
          │   ┌──────────┐ ┌────────▼───────────┐  │
          │   │显示平局  │ │ 交换玩家           │  │
          │   │"平局"   │ │ current, next =    │  │
          │   └────┬────┘ │ next, current      │  │
          │        │      └────────┬───────────┘  │
          │        │               │              │
          │        │               └──────────────┘
          │        │                      │
          └────────┼──────────────────────┘
                   │
                   ▼
              ┌──────────┐
              │ 显示"游戏结束"│
              └─────┬────┘
                   │
                   ▼
              ┌──────────┐
              │  游戏结束  │
              └──────────┘

完整的游戏过程示例

Welcome to tic-tac-toe

             | |   1 2 3
            -+-+-
             | |   4 5 6
            -+-+-
             | |   7 8 9
            
X请选择移动: (1-9)
> 1

            X| |   1 2 3
            -+-+-
             | |   4 5 6
            -+-+-
             | |   7 8 9
            
O请选择移动: (1-9)
> 2

            X|O|   1 2 3
            -+-+-
             | |   4 5 6
            -+-+-
             | |   7 8 9
            
X请选择移动: (1-9)
> 3

            X|O|X  1 2 3
            -+-+-
             | |   4 5 6
            -+-+-
             | |   7 8 9
            
O请选择移动: (1-9)
> 6

            X|O|X  1 2 3
            -+-+-
             | |O  4 5 6
            -+-+-
             | |   7 8 9
            
X请选择移动: (1-9)
> 8

            X|O|X  1 2 3
            -+-+-
             | |O  4 5 6
            -+-+-
             |X|   7 8 9
            
O请选择移动: (1-9)
> 7

            X|O|X  1 2 3
            -+-+-
             | |O  4 5 6
            -+-+-
            O|X|   7 8 9
            
X请选择移动: (1-9)
> 9

            X|O|X  1 2 3
            -+-+-
             | |O  4 5 6
            -+-+-
            O|X|X  7 8 9
            
O请选择移动: (1-9)
> 4

            X|O|X  1 2 3
            -+-+-
            O| |O  4 5 6
            -+-+-
            O|X|X  7 8 9
            
X请选择移动: (1-9)
> 5

            X|O|X  1 2 3
            -+-+-
            O|X|O  4 5 6
            -+-+-
            O|X|X  7 8 9
            
X 获胜!
游戏结束

在这个例子中,玩家X通过占据第5个位置(中心位置),完成了从左上到右下的对角线连线,从而赢得了比赛。

核心代码实现

1. 游戏棋盘类 (tictactoe_oop.py)

# 井字棋棋盘字典key
ALL_SPACES = [str(i) for i in range(1, 10)]
# 字符串常量
X, O, BLANK = 'X', 'O', ' '


class TTTBoard:
    """井字棋游戏"""
    def __init__(self):
        """初始化棋盘"""
        self._spaces = {space: BLANK for space in ALL_SPACES}

    def is_valid_space(self, space) -> bool:
        """判断是否为有效的移动
        Args:
            space: 要检查的位置,应该是 '1' 到 '9' 的字符串
        Returns:
            bool: 如果该位置为空则返回 True,否则返回 False
        """
        return space in ALL_SPACES and self._spaces[space] == BLANK

    def is_winner(self, player):
        """判断玩家是否获胜"""
        s, p = self._spaces, player  # 简写

        # 检查3行,3列,2对角线上的标记
        return (s['1'] == s['2'] == s['3'] == p or
                s['4'] == s['5'] == s['6'] == p or
                s['7'] == s['8'] == s['9'] == p or
                s['1'] == s['4'] == s['7'] == p or
                s['2'] == s['5'] == s['8'] == p or
                s['3'] == s['6'] == s['9'] == p or
                s['1'] == s['5'] == s['9'] == p or
                s['3'] == s['5'] == s['7'] == p)

    def is_board_full(self) -> bool:
        """判断棋盘是否已满
         Returns:
            bool: 如果棋盘已满返回 True,否则返回 False
        """
        return not any(space == BLANK for space in self._spaces.values())

    def update_board(self, space, player):
        """更新棋盘
        Args:
            space: 要更新的位置,应该是 '1' 到 '9' 的字符串
            player: 要更新位置的玩家,应该是 'X' 或 'O'
        """
        self._spaces[space] = player

    def __str__(self):
        """返回棋盘字符串"""
        return f"""
            {self._spaces['1']}|{self._spaces['2']}|{self._spaces['3']}  1 2 3
            -+-+-
            {self._spaces['4']}|{self._spaces['5']}|{self._spaces['6']}  4 5 6
            -+-+-
            {self._spaces['7']}|{self._spaces['8']}|{self._spaces['9']}  7 8 9
            """

    __repr__ = __str__

2. 游戏主程序 (app.py)

from tictactoe_oop import (TTTBoard, X, O)


def run():
    print("Welcome to tic-tac-toe")
    board = TTTBoard()
    current_player, next_player = X, O  # X先行,O后行

    while True:
        print(board)
        move = None
        while not board.is_valid_space(move):
            print(f"{current_player}请选择移动: (1-9)")
            move = input("> ")

        board.update_board(move, current_player)  # 执行移动

        # 检查游戏是否结束
        if board.is_winner(current_player):  # 首先检查一方是否获胜
            print(board)
            print(f"{current_player} 获胜!")
            break
        elif board.is_board_full():  # 检查棋盘是否已满
            print(board)
            print("平局")
            break

        current_player, next_player = next_player, current_player  # 交换玩家
    print("游戏结束")


if __name__ == '__main__':
    run()