Python 实现 2048 游戏(三):面向对象重构与AI模拟

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
枚举约束 DirectionGameState 用 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 枚举 DirectionGameState 类型约束
类的定义 __init__、实例方法、静态方法、__str__
封装 棋盘数据和方法封装在 Board 类中
状态机 Game2048._update_state() 自动状态转换
克隆模式 Board.clone() 支持AI模拟
继承扩展 SimpleAISmartAI 无侵入扩展
函数式 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模拟) ← 当前
相关推荐
可以飞的话2 小时前
五、数据处理1
python
鱼毓屿御2 小时前
Python 装饰器与函数调用机制(复习笔记 · 2026-07-07)
开发语言·python
AC赳赳老秦2 小时前
采购专员自动化:OpenClaw 自动比价、生成询价单、跟踪供应商报价进度实战指南
开发语言·数据库·人工智能·python·自动化·github·openclaw
ednO8nqdR2 小时前
Python小游戏制作:如何实现可配置的跨分辨率界面布局
python·plotly·django·virtualenv·scikit-learn·fastapi·pygame
林泽毅2 小时前
Qwen3.5 DPO 模型对齐实战(pytrio训练版)
人工智能·python·算法
荣码2 小时前
LangGraph多步工作流:Agent不够用的时候,我踩了4个坑
java·python
曲幽3 小时前
从卡顿到丝滑:FastAPI 调用外部 API 的正确姿势(httpx 实战)
python·fastapi·web·async·httpx·client·await·requests·aiohttp
不瘦80斤不改名3 小时前
HalfCart:基于LBS的1\.5km本地拼单系统全栈实践与踩坑复盘
python·docker·typescript·pycharm
深盾科技_Virbox3 小时前
软件交付保护如何从加壳走向代码虚拟化
java·python·安全