python_2048_game

第 1 章 项目概述与设计思想

“好的开始是成功的一半” —— 理解项目整体设计再深入代码


📋 本章内容


🎮 为什么选择 2048

完美的学习项目

2048 是一个理想的学习项目,原因如下:

特点 说明 学习价值
规则简单 4 个方向滑动,相同数字合并 逻辑清晰,易于理解
状态有限 4x4 网格,16 个位置 数据结构简单
算法适中 滑动 + 合并,有挑战性但不复杂 锻炼算法思维
可扩展 可添加动画、AI、多人模式 支持渐进式学习
可视化 界面直观,效果即时可见 学习成就感强

对比其他学习项目

❌ 太简单:计算器、猜数字
   → 学不到架构设计

❌ 太复杂:3D 游戏、网络应用
   → 容易迷失在细节中

✅ 刚刚好:2048
   → 有挑战但可完成,能学到完整项目结构

📖 游戏简介

游戏规则

  1. 目标:合并方块,达到 2048
  2. 操作:上下左右滑动所有方块
  3. 合并:相同数字碰撞时合并(2+2=4, 4+4=8…)
  4. 生成:每次移动后随机生成新方块(2 或 4)
  5. 结束:网格填满且无法合并时游戏结束

游戏示例

初始状态:
┌────┬────┬────┬────┐
│    │    │ 2  │    │
├────┼────┼────┼────┤
│    │    │    │    │
├────┼────┼────┼────┤
│    │ 2  │    │    │
├────┼────┼────┼────┤
│    │    │    │ 4  │
└────┴────┴────┴────┘

向左滑动后:
┌────┬────┬────┬────┐
│ 2  │    │    │    │
├────┼────┼────┼────┤
│    │    │    │    │
├────┼────┼────┼────┤
│ 2  │    │    │    │
├────┼────┼────┼────┤
│ 4  │    │    │    │
└────┴────┴────┴────┘
(新方块随机生成)

🛠️ 技术选型

为什么用这些技术?

1. Python 3.14+

优点:
✅ 语法简洁,适合初学者
✅ 丰富的生态系统
✅ 跨平台支持
✅ 类型注解支持(代码更清晰)

2. Textual(TUI 框架)

什么是 Textual?
→ 用 Python 构建终端用户界面的框架
→ 类似网页开发(HTML → Widgets, CSS → CSS, JS → Python)

为什么选 Textual?
✅ 现代化设计(比 curses 易用)
✅ 支持事件驱动
✅ 丰富的组件库
✅ 自动处理终端差异

3. uv(项目管理)

什么是 uv?
→ 用 Rust 编写的超快 Python 包管理器

为什么选 uv?
✅ 速度极快(比 pip 快 10-100 倍)
✅ 自动管理虚拟环境
✅ 依赖锁定(确保一致性)
✅ 现代工作流

技术栈总览

┌─────────────────────────────────────────┐
│              用户界面层                  │
│              Textual 8.0+               │
├─────────────────────────────────────────┤
│              业务逻辑层                  │
│              Python 3.14+               │
├─────────────────────────────────────────┤
│              项目管理层                  │
│              uv + pyproject.toml        │
└─────────────────────────────────────────┘

🏗️ 分层设计思想

核心概念:关注点分离

什么是关注点分离(Separation of Concerns)?

把不同的功能分开,让每个部分只做一件事。

生活中的例子:

❌ 不好的设计:瑞士军刀式厨房
   → 切菜、炒菜、洗碗都在同一个台面上
   → 混乱、难以维护

✅ 好的设计:功能分区厨房
   → 切菜区、烹饪区、清洗区分开
   → 清晰、高效、易维护

本项目的三层架构

┌─────────────────────────────────────────────────────────────┐
│                    表示层 (Presentation Layer)               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  app.py  - 应用入口,事件处理                        │   │
│  │  ui.py   - 界面组件,视觉渲染                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                          ↓ ↑                                │
│                    (只调用,不修改)                        │
│                          ↓ ↑                                │
├─────────────────────────────────────────────────────────────┤
│                    业务层 (Business Layer)                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  game.py - 游戏规则,移动逻辑,状态管理              │   │
│  └─────────────────────────────────────────────────────┘   │
│                          ↓ ↑                                │
│                    (只依赖接口)                           │
│                          ↓ ↑                                │
├─────────────────────────────────────────────────────────────┤
│                    数据层 (Data Layer)                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  models.py - 数据结构,存储,序列化                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                          ↓ ↑                                │
│                    (纯数据,无逻辑)                       │
│                          ↓ ↑                                │
├─────────────────────────────────────────────────────────────┤
│                    支撑层 (Support Layer)                    │
│  ┌──────────────┐              ┌──────────────┐            │
│  │  config.py   │              │   utils.py   │            │
│  │  (配置常量)   │              │  (工具函数)   │            │
│  └──────────────┘              └──────────────┘            │
└─────────────────────────────────────────────────────────────┘

为什么要分层?

好处 1:可测试性

# ❌ 不分层:UI 和逻辑混在一起
def move_up():
    # 移动逻辑
    # UI 更新
    # 动画播放
    # ... 难以单独测试

# ✅ 分层:业务逻辑可独立测试
def test_move_up():
    game = Game()
    game.move("up")  # 纯逻辑,不依赖 UI
    assert game.score == 100

好处 2:可替换性

# 想改成 Web 界面?只需替换表示层!
# 业务层和数据层完全不用改

# Textual TUI
from textual.app import App
class GameApp(App): ...

# Web (Flask)
from flask import Flask
app = Flask(__name__)
@app.route("/")
def game_page(): ...

# 两者共用同一个 Game 类!

好处 3:可维护性

问题:分数计算错了

❌ 不分层:要在几百行代码里找
✅ 分层:直接去 game.py 的 move() 方法

问题:界面颜色要改
❌ 不分层:可能影响游戏逻辑
✅ 分层:只改 ui.py 的 CSS

📊 模块职责表

模块 层级 职责 依赖其他模块
config.py 支撑层 定义常量
utils.py 支撑层 工具函数
models.py 数据层 数据结构 config
game.py 业务层 游戏逻辑 models, config
ui.py 表示层 界面组件 game, models
app.py 表示层 应用入口 game, ui

依赖规则:


🎯 设计原则

1. 单一职责原则 (SRP)

每个类/函数只做一件事

# ❌ 不好:一个函数做太多事
def move_and_update_and_save():
    # 移动逻辑
    # UI 更新
    # 保存到文件

# ✅ 好:每个函数职责单一
def move(direction): ...
def update_ui(): ...
def save_game(): ...

2. 开闭原则 (OCP)

对扩展开放,对修改关闭

# ✅ 好设计:添加新方向不用改现有代码
DIRECTIONS = ["up", "down", "left", "right"]

def move(direction):
    if direction not in DIRECTIONS:
        raise ValueError(...)
    # 处理移动

# 要添加"对角线"移动?
# → 扩展 DIRECTIONS 列表即可

3. 配置驱动

把可能变化的值提取为常量

# ❌ 魔法数字
for i in range(4):  # 4 是什么?
    for j in range(4):
        ...

# ✅ 配置常量
GRID_SIZE = 4
for i in range(GRID_SIZE):
    for j in range(GRID_SIZE):
        ...

🔍 本章学习路线

阅读顺序

1. 理解 2048 游戏规则(5 分钟)
   → 玩几局手机游戏

2. 理解分层设计(15 分钟)
   → 画出三层架构图

3. 认识技术栈(10 分钟)
   → 了解 Textual、uv 是什么

4. 运行项目(10 分钟)
   → 确保环境配置正确

实践任务

# 任务 1:运行游戏
cd ~/Work/open_learn/python/game_2048
uv run game-2048

# 任务 2:修改配置
# 编辑 config.py,把 GRID_SIZE 改成 5
# 运行游戏,观察变化

# 任务 3:阅读代码
# 打开 game.py,找到 move() 函数
# 尝试理解它的调用链

检查清单


❓ 常见问题

Q1: 为什么不用 Pygame?

A: Pygame 适合图形游戏,但 2048 是纯逻辑游戏。Textual 更轻量,且能学习终端 UI 开发(实用技能)。

Q2: 分层会不会太复杂?

A: 初期可能觉得多,但随着项目变大,分层的优势会越来越明显。这是专业开发的标配。

Q3: 我能跳过理论直接写代码吗?

A: 可以,但建议先理解设计思想。磨刀不误砍柴工!


📚 延伸阅读


下一章: 第 2 章 项目结构与模块化

🐧 理解设计,再动手写代码!