Python 实现 2048 游戏(三):面向对象重构与AI模拟
专栏系列 : Python 实现 2048 游戏(共3篇)
实验环境 : 华为云 FlexusX | Ubuntu 24.04 | Python 3.12.3
知识点: Python 类和函数的使用、面向对象编程、枚举类型、状态机模式
一、前言
前两篇我们用函数式编程实现了完整的 2048 游戏。本篇将使用**面向对象编程(OOP)**方法重构整个项目,对比两种实现方式的优劣,并通过 100 局 AI 模拟验证重构后代码的正确性。
完整代码仓库
本系列所有实验代码已上传至 Gitee:
🔗 Gitee 仓库: https://gitee.com/LiaCin/python-learning-codes
bash
# 运行本篇实验代码
cd python-learning-codes/game2048-lab
python 2048_05_oop.py # OOP版本
python 2048_04_simulate.py # AI模拟对比
二、为什么需要面向对象重构?
函数式实现的问题:
python
# 函数式: 状态散落在各处
board = create_board()
score = 0
move_count = 0
state = 'PLAYING'
# 每次操作都要传递所有状态
board, score, changed = do_move(board, 'left', score)
if changed:
add_random_tile(board)
if is_game_over(board):
state = 'OVER'
面向对象的优势:
python
# OOP: 状态封装在对象内部
game = Game2048()
game.move(Direction.LEFT) # 自动处理一切
print(game.state) # 状态自动维护
核心改进:
- 封装: 棋盘、分数、状态都封装在对象内部
- 自包含: 不需要外部维护散落变量
- 可扩展: 通过继承添加新功能
- 类型安全 : 使用
Enum约束合法值
三、OOP 架构设计
3.1 类图结构
┌─────────────────┐ ┌─────────────────┐
│ Direction │ │ GameState │
│ (Enum) │ │ (Enum) │
├─────────────────┤ ├─────────────────┤
│ UP │ │ PLAYING │
│ DOWN │ │ WON │
│ LEFT │ │ OVER │
│ RIGHT │ │ │
└─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────┐
│ MatrixUtil │ │ RowMerger │
│ (静态工具类) │ │ (行合并器) │
├─────────────────┤ ├─────────────────┤
│ +transpose() │ │ +merge(row) │
│ +reverse() │ │ │
│ +equal() │ │ │
└─────────────────┘ └─────────────────┘
┌─────────────────────────────────────────┐
│ Board (棋盘) │
├─────────────────────────────────────────┤
│ - grid: list[list[int]] │
│ - size: int │
│ - merger: RowMerger │
├─────────────────────────────────────────┤
│ +add_random_tile() │
│ +move(direction) → int │
│ +can_merge() → bool │
│ +is_game_over() → bool │
│ +clone() → Board │
│ +max_tile() → int │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Game2048 (游戏控制器) │
├─────────────────────────────────────────┤
│ - board: Board │
│ - score: int │
│ - best_score: int │
│ - move_count: int │
│ - state: GameState │
├─────────────────────────────────────────┤
│ +move(direction) → bool │
│ +reset() │
│ +get_stats() → dict │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ SimpleAI (AI玩家) │
├─────────────────────────────────────────┤
│ - priorities: list[Direction] │
├─────────────────────────────────────────┤
│ +choose(board) → Direction │
└─────────────────────────────────────────┘
3.2 设计原则
| 原则 | 体现 |
|---|---|
| 单一职责 | Board 只管棋盘逻辑,Game2048 管游戏流程,SimpleAI 管决策 |
| 封装 | 棋盘数据 grid 为私有,通过方法访问 |
| 开闭原则 | SimpleAI 可通过继承扩展为更高级的AI |
| 枚举约束 | Direction 和 GameState 用 Enum 限制合法值 |
四、逐类实现
4.1 枚举类型
python
from enum import Enum
class Direction(Enum):
"""移动方向枚举"""
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
class GameState(Enum):
"""游戏状态枚举 (状态机)"""
PLAYING = 'PLAYING'
WON = 'WON'
OVER = 'OVER'
知识点 : Python
enum.Enum创建枚举类型,确保方向和状态只能是预定义值,避免字符串拼写错误。
4.2 矩阵工具类
python
class MatrixUtil:
"""矩阵操作工具类 (全静态方法)"""
@staticmethod
def transpose(matrix):
"""矩阵转置"""
return [list(row) for row in zip(*matrix)]
@staticmethod
def reverse(matrix):
"""矩阵逆转"""
return [row[::-1] for row in matrix]
@staticmethod
def equal(m1, m2):
"""判断两个矩阵是否相等"""
return all(m1[i] == m2[i] for i in range(len(m1)))
设计决策: 矩阵操作是纯函数,不持有状态,因此用静态方法组织在一起,既保持OOP结构又不引入不必要的实例化。
4.3 行合并器
python
class RowMerger:
"""行合并处理器"""
def __init__(self, size=4):
self.size = size
def merge(self, row):
"""将一行向左合并, 返回 (合并后的行, 得分)"""
compressed = [x for x in row if x != 0]
merged = []
score = 0
skip = False
for i in range(len(compressed)):
if skip:
skip = False
continue
if i + 1 < len(compressed) and compressed[i] == compressed[i + 1]:
merged.append(compressed[i] * 2)
score += compressed[i] * 2
skip = True
else:
merged.append(compressed[i])
merged += [0] * (self.size - len(merged))
return merged, score
4.4 棋盘类
python
class Board:
"""2048游戏棋盘"""
def __init__(self, size=4):
self.size = size
self.grid = self._create_empty()
self.merger = RowMerger(size)
def _create_empty(self):
"""创建空棋盘"""
return [[0] * self.size for _ in range(self.size)]
def clone(self):
"""深拷贝棋盘 (用于AI模拟)"""
new_board = Board(self.size)
new_board.grid = [row[:] for row in self.grid]
return new_board
def get_empty_cells(self):
"""获取所有空格子坐标"""
return [(i, j) for i in range(self.size)
for j in range(self.size) if self.grid[i][j] == 0]
def add_random_tile(self):
"""随机添加数字 (90%为2, 10%为4)"""
empty = self.get_empty_cells()
if not empty:
return None
i, j = random.choice(empty)
self.grid[i][j] = 4 if random.random() < 0.1 else 2
return (i, j, self.grid[i][j])
def move(self, direction):
"""执行移动, 返回得分"""
if direction == Direction.LEFT:
return self._move_left()
elif direction == Direction.RIGHT:
return self._move_right()
elif direction == Direction.UP:
return self._move_up()
elif direction == Direction.DOWN:
return self._move_down()
return 0
def _move_left(self):
score = 0
for i in range(self.size):
self.grid[i], s = self.merger.merge(self.grid[i])
score += s
return score
def _move_right(self):
self.grid = MatrixUtil.reverse(self.grid)
score = self._move_left()
self.grid = MatrixUtil.reverse(self.grid)
return score
def _move_up(self):
self.grid = MatrixUtil.transpose(self.grid)
score = self._move_left()
self.grid = MatrixUtil.transpose(self.grid)
return score
def _move_down(self):
self.grid = MatrixUtil.reverse(MatrixUtil.transpose(self.grid))
score = self._move_left()
self.grid = MatrixUtil.transpose(MatrixUtil.reverse(self.grid))
return score
def can_merge(self):
"""是否还能合并"""
for i in range(self.size):
for j in range(self.size - 1):
if self.grid[i][j] == self.grid[i][j+1] and self.grid[i][j] != 0:
return True
for j in range(self.size):
for i in range(self.size - 1):
if self.grid[i][j] == self.grid[i+1][j] and self.grid[i][j] != 0:
return True
return False
def has_empty(self):
return bool(self.get_empty_cells())
def is_full(self):
return not self.has_empty()
def max_tile(self):
return max(max(row) for row in self.grid)
def has_tile(self, target):
return any(target in row for row in self.grid)
def is_game_over(self):
return self.is_full() and not self.can_merge()
def __str__(self):
"""格式化输出棋盘"""
lines = [" +" + "------+" * self.size]
for row in self.grid:
line = " |"
for cell in row:
if cell == 0:
line += f" {'':>4} |"
else:
line += f" {cell:>4} |"
lines.append(line)
lines.append(" +" + "------+" * self.size)
return "\n".join(lines)
OOP亮点:
clone()方法支持AI在不影响真实棋盘的情况下模拟移动__str__()魔术方法让棋盘可以直接print()输出- 所有移动操作通过
move(direction)统一入口
4.5 游戏控制器
python
class Game2048:
"""2048游戏主控制器 (状态机)"""
TARGET = 2048
def __init__(self, size=4):
self.board = Board(size)
self.score = 0
self.best_score = 0
self.move_count = 0
self.state = GameState.PLAYING
self._won_shown = False
self._init_game()
def _init_game(self):
"""初始化游戏"""
self.board.add_random_tile()
self.board.add_random_tile()
def reset(self):
"""重置游戏"""
self.best_score = max(self.best_score, self.score)
self.board = Board(self.board.size)
self.score = 0
self.move_count = 0
self.state = GameState.PLAYING
self._won_shown = False
self._init_game()
def move(self, direction):
"""执行一次移动"""
if self.state == GameState.OVER:
return False
old_grid = [row[:] for row in self.board.grid]
gain = self.board.move(direction)
changed = old_grid != self.board.grid
if changed:
self.score += gain
self.move_count += 1
self.board.add_random_tile()
self._update_state()
return changed
def _update_state(self):
"""更新游戏状态 (状态机转换)"""
if self.board.has_tile(self.TARGET) and not self._won_shown:
self.state = GameState.WON
self._won_shown = True
elif self.board.is_game_over():
self.state = GameState.OVER
self.best_score = max(self.best_score, self.score)
else:
self.state = GameState.PLAYING
def get_stats(self):
"""获取游戏统计"""
return {
'score': self.score,
'best': self.best_score,
'moves': self.move_count,
'max_tile': self.board.max_tile(),
'state': self.state.value,
'won': self.board.has_tile(self.TARGET)
}
def __str__(self):
stats = self.get_stats()
header = (f" Score: {stats['score']} | Best: {stats['best']} | "
f"Moves: {stats['moves']} | Max: {stats['max_tile']} | "
f"State: {stats['state']}")
return header + "\n" + str(self.board)
状态机封装 : _update_state() 方法在每次移动后自动检查并转换游戏状态,外部无需关心状态管理。
4.6 AI玩家
python
class SimpleAI:
"""简单AI: 贪心策略"""
def __init__(self, priorities=None):
self.priorities = priorities or [
Direction.DOWN, Direction.RIGHT, Direction.LEFT, Direction.UP
]
def choose(self, board):
"""选择最佳移动方向"""
best_dir = None
best_score = -1
for direction in self.priorities:
test_board = board.clone() # 克隆棋盘进行模拟
gain = test_board.move(direction)
if test_board.grid != board.grid:
if gain > best_score:
best_score = gain
best_dir = direction
return best_dir
关键 :
board.clone()让AI可以在不影响真实游戏状态的情况下模拟所有方向,选择最优解。这是OOP封装带来的直接好处。
五、上机验证
5.1 单局测试
python
# 创建游戏
random.seed(2024)
game = Game2048()
ai = SimpleAI()
print(game)
# 模拟10步
for i in range(10):
direction = ai.choose(game.board)
if direction is None:
print(f"\n 无可用移动, 游戏结束!")
break
changed = game.move(direction)
print(f"\n 第{game.move_count}步: {direction.value:>5} 得分: {game.score}")
运行结果:
=============================================
面向对象 2048 游戏 - 测试运行
=============================================
Score: 0 | Best: 0 | Moves: 0 | Max: 2 | State: PLAYING
+------+------+------+------+
| | | | |
+------+------+------+------+
| | | | |
+------+------+------+------+
| | 2 | | |
+------+------+------+------+
| | | | 2 |
+------+------+------+------+
第1步: down 得分: 0
第2步: down 得分: 4
第3步: right 得分: 4
第4步: right 得分: 8
第5步: down 得分: 16
第6步: down 得分: 20
第7步: down 得分: 20
第8步: down 得分: 24
第9步: down 得分: 28
第10步: right 得分: 36
=============================================
10步后状态:
Score: 36 | Best: 0 | Moves: 10 | Max: 8 | State: PLAYING
+------+------+------+------+
| | | | 2 |
+------+------+------+------+
| | | | |
+------+------+------+------+
| | | | 2 |
+------+------+------+------+
| | 4 | 8 | 8 |
+------+------+------+------+
验证: OOP版与函数式版使用相同随机种子,输出完全一致,证明重构正确。
5.2 100局批量统计
python
results = []
for game_num in range(100):
random.seed(game_num * 7 + 13)
g = Game2048()
bot = SimpleAI()
while g.move_count < 500:
if g.state == GameState.OVER:
break
direction = bot.choose(g.board)
if direction is None:
break
g.move(direction)
results.append(g.get_stats())
运行结果:
=============================================
OOP版 100局模拟统计
=============================================
胜利局数: 0/100 (0%)
平均得分: 3132
最高得分: 7276
最低得分: 800
平均步数: 262
平均最大数字: 239
最大数字分布:
64: 4局 ( 4.0%) ####
128: 37局 ( 37.0%) #####################################
256: 44局 ( 44.0%) ############################################
512: 15局 ( 15.0%) ###############
验证: OOP版100局统计与函数式版完全一致(平均得分3132,最高7276),证明重构无损逻辑正确性。
六、函数式 vs 面向对象对比
6.1 代码对比
函数式调用:
python
# 需要手动维护所有状态
board = create_board()
score = 0
move_count = 0
state = 'PLAYING'
add_random_tile(board)
add_random_tile(board)
# 每次移动需要手动处理
old_board = [row[:] for row in board]
board, gain = move_left(board)
if board != old_board:
score += gain
move_count += 1
add_random_tile(board)
if is_game_over(board):
state = 'OVER'
elif has_won(board):
state = 'WON'
OOP调用:
python
# 状态自动管理
game = Game2048()
game.move(Direction.LEFT) # 自动处理一切: 移动、加分、添数字、状态更新
print(game.state) # GameState.PLAYING
print(game.score) # 自动维护
6.2 全面对比
维度 | 函数式 | 面向对象
-----------+---------------------------+--------------------------
代码组织 | 函数散落, 状态通过参数传递 | 类封装, 状态内部管理
状态管理 | 全局变量或参数传递 | 实例属性, 自包含
可扩展性 | 添加功能需修改多处 | 继承/多态, 易扩展
代码复用 | 函数级别复用 | 类级别复用, 支持继承
可测试性 | 需模拟全局状态 | 实例化即可测试
可读性 | 流程式, 逻辑分散 | 对象模型, 职责清晰
类型安全 | 无类型约束 | 可用类型注解, Enum约束
6.3 扩展示例:自定义AI
OOP的继承特性让扩展变得简单:
python
class SmartAI(SimpleAI):
"""更聪明的AI: 考虑空格数量"""
def choose(self, board):
best_dir = None
best_eval = -1
for direction in self.priorities:
test_board = board.clone()
gain = test_board.move(direction)
if test_board.grid != board.grid:
# 评估: 得分 + 空格数 * 10
empty_count = len(test_board.get_empty_cells())
eval_score = gain + empty_count * 10
if eval_score > best_eval:
best_eval = eval_score
best_dir = direction
return best_dir
函数式实现要做到同样的扩展,需要修改多处代码或引入全局配置。
七、Python 类和函数的知识要点
7.1 类的定义与使用
python
class MyClass:
"""类文档字符串"""
class_var = "类变量" # 所有实例共享
def __init__(self, param):
"""构造函数"""
self.instance_var = param # 实例变量
def instance_method(self):
"""实例方法"""
return self.instance_var
@staticmethod
def static_method():
"""静态方法 (不需要实例)"""
return "static"
@classmethod
def class_method(cls):
"""类方法"""
return cls.class_var
def __str__(self):
"""字符串表示"""
return f"MyClass({self.instance_var})"
7.2 Enum 枚举
python
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# 使用
c = Color.RED
print(c.name) # "RED"
print(c.value) # 1
print(c == Color.RED) # True
7.3 列表推导式
python
# 基本形式
[x for x in range(10)] # [0, 1, 2, ..., 9]
[x**2 for x in range(5)] # [0, 1, 4, 9, 16]
[x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
# 嵌套
[[0 for _ in range(4)] for _ in range(4)] # 4x4全零矩阵
# 带条件
[(i, j) for i in range(4) for j in range(4) if board[i][j] == 0]
7.4 匿名函数
python
# 基本语法
lambda params: expression
# 示例
square = lambda x: x ** 2
print(square(5)) # 25
# 在排序中使用
rows = [[2, 0], [4, 4], [0, 2]]
rows.sort(key=lambda r: sum(r), reverse=True)
# [[4, 4], [2, 0], [0, 2]]
八、如何运行完整游戏
8.1 SSH登录服务器
bash
ssh root@113.44.141.61
# 密码: 1qaz@WSX
8.2 运行curses版游戏
bash
cd /root/game2048_lab
python3 03_curses_game.py
8.3 运行自动模拟
bash
# 函数式模拟
python3 04_simulate.py
# OOP版模拟
python3 05_oop.py
8.4 服务器文件清单
/root/game2048_lab/
├── 01_matrix_ops.py # 矩阵操作实验 (133行)
├── 02_board_logic.py # 棋盘逻辑实验 (271行)
├── 03_curses_game.py # curses完整游戏 (286行)
├── 04_simulate.py # 自动模拟脚本 (295行)
└── 05_oop.py # OOP重构版本 (378行)
总计 1363行
九、本篇总结
| 知识点 | 内容 |
|---|---|
| Enum 枚举 | Direction、GameState 类型约束 |
| 类的定义 | __init__、实例方法、静态方法、__str__ |
| 封装 | 棋盘数据和方法封装在 Board 类中 |
| 状态机 | Game2048._update_state() 自动状态转换 |
| 克隆模式 | Board.clone() 支持AI模拟 |
| 继承扩展 | SimpleAI → SmartAI 无侵入扩展 |
| 函数式 vs OOP | 7个维度全面对比 |
| Python语法 | 列表推导式、lambda、类方法、魔术方法 |
十、系列总结
三篇博客知识点回顾
| 篇 | 主题 | 核心知识点 |
|---|---|---|
| 一 | 核心数学与棋盘逻辑 | 矩阵转置/逆转、列表推导式、lambda、行合并算法、四方向移动、游戏状态判断、collections |
| 二 | curses终端图形界面 | curses库使用、颜色系统、状态机、键位映射、界面绘制、自动模拟、AI策略、批量统计 |
| 三 | 面向对象重构 | Enum枚举、类封装、状态机模式、克隆模式、继承扩展、函数式vs OOP对比 |
实验任务完成情况
| 实验任务 | 状态 |
|---|---|
| 梳理游戏主逻辑 | ✅ 状态机驱动的主循环 |
| 处理用户输入 | ✅ 方向键 + WASD 双模式 |
| 实现矩阵转置与矩阵逆转 | ✅ transpose/reverse 函数 |
| 创建棋盘 | ✅ create_board / Board.init |
| 实现棋盘相关操作 | ✅ 移动/合并/添加随机数字/状态判断 |
| 绘制游戏界面 | ✅ curses 终端图形界面 |
| 完成主逻辑 | ✅ 函数式 + OOP 双版本 |
| 运行游戏查看效果 | ✅ 自动模拟验证 |
| 使用面向对象的方法重构代码 | ✅ 5个类完整重构 |
代码统计
| 文件 | 行数 | 说明 |
|---|---|---|
| 01_matrix_ops.py | 133 | 矩阵操作实验 |
| 02_board_logic.py | 271 | 棋盘逻辑实验 |
| 03_curses_game.py | 286 | curses完整游戏 |
| 04_simulate.py | 295 | 自动模拟脚本 |
| 05_oop.py | 378 | OOP重构版本 |
| 总计 | 1363 | 完整实验代码 |
完整源码 : 所有脚本位于服务器
/root/game2048_lab/目录专栏导航:
- [Python 实现 2048 游戏(一):核心数学与棋盘逻辑](#Python 实现 2048 游戏(一):核心数学与棋盘逻辑)
- [Python 实现 2048 游戏(二):curses终端图形界面与完整游戏](#Python 实现 2048 游戏(二):curses终端图形界面与完整游戏)
- [Python 实现 2048 游戏(三):面向对象重构与AI模拟](#Python 实现 2048 游戏(三):面向对象重构与AI模拟) ← 当前