井字棋游戏 (Tic-Tac-Toe)
本项目使用Python面向对象编程实现了一个完整的井字棋游戏,包含以下特性:
- OOP面向对象的封装,继承
- 多继承的MRO
- 输入验证,确保玩家只能选择空位
- 自动判断胜负和平局
- 友好的用户界面,显示当前棋盘状态
【MRO super链分析】
场景描述
井字棋(Tic-Tac-Toe)是一款经典的两人对弈游戏,在3×3的棋盘上进行。玩家轮流放置自己的标记(X或O),率先在横、竖或斜方向连成一线者获胜。如果棋盘填满仍未分出胜负,则为平局。
流程图
┌─────────────────────────────────────────────────────────────┐
│ 游戏开始 │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌────────────────────────┐
│ 显示欢迎信息 │
│ "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()
添加Mini棋盘MiniBoard
添加一种简约化的棋盘
X请选择移动: (1-9)
> 9
..X 1 2 3
.OX 4 5 6
.OX 7 8 9
X 获胜!
游戏结束
所有的功能逻辑都不变,只需要通过继承,重写展示的方法即可,扩展棋盘展示
class MiniBoard(TTTBoard):
"""小棋盘"""
def __str__(self):
s = {
space: '.' if val == BLANK else val
for space, val in self._spaces.items()
}
"""返回棋盘字符串"""
return f"""
{s['1']}{s['2']}{s['3']} 1 2 3
{s['4']}{s['5']}{s['6']} 4 5 6
{s['7']}{s['8']}{s['9']} 7 8 9
"""
__repr__ = __str__
添加提示Hint
打印棋盘的时候,如果判断下一步有人会获胜就给出提示
X请选择移动: (1-9)
> 5
X|O| 1 2 3
-+-+-
|X| 4 5 6
-+-+-
| | 7 8 9
X的下一步能获胜
O请选择移动: (1-9)
class HintBoard(TTTBoard):
"""添加说明X和O是否只差一步胜利"""
def __str__(self):
msgs = [super().__str__()]
if self._judge_next_win(X):
msgs.append(f"{X}的下一步能获胜")
if self._judge_next_win(O):
msgs.append(f"{O}的下一步能获胜")
return '\n'.join(msgs)
def _judge_next_win(self, player):
"""判断玩家在下一步是否能获胜(通过模拟尝试每个空位)
Args:
player: 要检查的玩家,应该是 'X' 或 'O'
Returns:
bool: 如果玩家能在下一步获胜返回 True,否则返回 False
"""
origin_space = self._spaces.copy() # 保存原始棋盘状态用于恢复
is_win = False
for space in self._spaces.keys():
if self._spaces[space] == BLANK: # 只检查空位
super().update_board(space, player) # 模拟在该位置落子
if self.is_winner(player):
is_win = True
break
super().update_board(space, BLANK) # 恢复该位置为空位,尝试下一个位置
self._spaces = origin_space # 恢复原始棋盘状态
return is_win
给mini棋盘也加上提示(MRO技术)
效果如下:
X请选择移动: (1-9)
> 9
XO. 1 2 3
... 4 5 6
..X 7 8 9
X的下一步能获胜
O请选择移动: (1-9)
我们创建一个新类,继承HintBoard,MiniBoard就可以了,只需要有这个类,里面的不需要写任何的新功能
class HybridBoard(HintBoard,MiniBoard):
"""mini棋盘拥有提示功能"""
pass
【MRO super链分析】
在app.py中将MiniBoard替换成HintBoard就好了,其底层是使用mro技术。
if input("是否使用小棋盘?(y/n): ").lower() == 'y':
# board = MiniBoard()
board = HybridBoard()
else:
board = HintBoard()