Python 实现:从数学模型到完整控制台版《2048》游戏

Python 实现:从数学模型到完整控制台版《2048》游戏

本文将带你从数学建模角度 完整解析《2048》游戏的设计逻辑,结合 Python 实现代码,逐步还原游戏从状态表示、移动合并、胜负判断到控制循环的全过程。

导入库

python 复制代码
import random
import os
import msvcrt  # Windows下的键盘输入处理

1. 模块 1:状态表示与初始化

1.1 模块 1 - 数学模型

1.1.1 状态矩阵定义

定义游戏在时刻 t t t 的状态矩阵为:
S t = [ s 11 ( t ) s 12 ( t ) ... s 1 n ( t ) s 21 ( t ) s 22 ( t ) ... s 2 n ( t ) ⋮ ⋮ ⋱ ⋮ s n 1 ( t ) s n 2 ( t ) ... s n n ( t ) ] S_t = \begin{bmatrix} s_{11}(t) & s_{12}(t) & \dots & s_{1n}(t) \\ s_{21}(t) & s_{22}(t) & \dots & s_{2n}(t) \\ \vdots & \vdots & \ddots & \vdots \\ s_{n1}(t) & s_{n2}(t) & \dots & s_{nn}(t) \end{bmatrix} St= s11(t)s21(t)⋮sn1(t)s12(t)s22(t)⋮sn2(t)......⋱...s1n(t)s2n(t)⋮snn(t)

其中
s i j ( t ) ∈ { 0 , 2 , 4 , 8 , ...   } , i , j ∈ { 1 , 2 , 3 , 4 } s_{ij}(t) \in \{0, 2, 4, 8, \dots\}, \quad i, j \in \{1,2,3,4\} sij(t)∈{0,2,4,8,...},i,j∈{1,2,3,4}

表示第 i i i 行第 j j j 列方格中的数值。若 s i j ( t ) = 0 s_{ij}(t) = 0 sij(t)=0,则该格为空。

1.1.2 游戏全局状态

定义整体游戏状态为:
S t game = ( S t , score t , best_score t , won t , game_over t ) S_t^{\text{game}} = (S_t, \text{score}_t, \text{best\_score}_t, \text{won}_t, \text{game\_over}_t) Stgame=(St,scoret,best_scoret,wont,game_overt)

初始状态为:
S 0 game = ( S 0 ′ , 0 , 0 , False , False ) , S 0 ′ = S 0 + P 1 + P 2 S_0^{\text{game}} = (S_0', 0, 0, \text{False}, \text{False}), \quad S_0' = S_0 + P_1 + P_2 S0game=(S0′,0,0,False,False),S0′=S0+P1+P2

其中 P 1 , P 2 P_1, P_2 P1,P2 为随机生成的初始方块矩阵。

1.1.3 随机生成新方块

空格集合定义为:
E t = { ( i , j ) ∣ s i , j ( t ) = 0 } E_t = \{(i,j) \mid s_{i,j}(t) = 0\} Et={(i,j)∣si,j(t)=0}

在空格集合中随机选择位置:
( i ∗ , j ∗ ) = 随机选择 ( E t ) (i^*, j^*) = \text{随机选择}(E_t) (i∗,j∗)=随机选择(Et)

生成新方块值:
s i ∗ , j ∗ ( t + 1 ) = { 2 , 概率 0.9 4 , 概率 0.1 s_{i^*,j^*}(t+1) = \begin{cases} 2, & \text{概率 } 0.9 \\ 4, & \text{概率 } 0.1 \end{cases} si∗,j∗(t+1)={2,4,概率 0.9概率 0.1

1.2 模块 1 - 代码实现

下面展示该部分的 Python 实现,对应公式的每一个步骤:

python 复制代码
def __init__(self):
    """初始化游戏"""
    self.grid_size = 4
    self.grid = [[0 for _ in range(self.grid_size)] for _ in range(self.grid_size)]
    self.score = 0
    self.best_score = 0
    self.game_over = False
    self.won = False

    # 初始化游戏,添加两个方块
    for _ in range(2):
        self.add_new_tile()

def add_new_tile(self):
    """在随机空位置添加新方块(2或4)"""
    # 找出所有空白格坐标
    empty_cells = [(i, j) for i in range(self.grid_size)
                   for j in range(self.grid_size) if self.grid[i][j] == 0]
    if not empty_cells:
        return False

    # 随机选取空格
    i, j = random.choice(empty_cells)

    # 以90%概率生成2,10%概率生成4
    self.grid[i][j] = 2 if random.random() < 0.9 else 4
    return True

1.3 模块 1 - 逻辑说明

初始化阶段:

  • 创建一个 4 × 4 4\times4 4×4 的零矩阵;
  • 随机生成两个初始方块;
  • 初始化分数、最高分及游戏状态。

数学意义:

  • 游戏从空棋盘(零矩阵)开始,随机性使每局初始状态不同,形成状态空间的多样性。

1.4 初始状态可视化示例

初始状态可能如下(用矩阵形式):
S 0 ′ = [ 0 2 0 0 0 0 0 0 0 0 0 4 0 0 0 0 ] S_0' = \begin{bmatrix} 0 & 2 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 4 \\ 0 & 0 & 0 & 0 \end{bmatrix} S0′= 0000200000000040


2. 模块 2:方块移动与合并

2.1 模块 2 - 数学模型

2.1.1 单行压缩与合并

对一行向量 L = [ s 1 , s 2 , s 3 , s 4 ] L = [s_1, s_2, s_3, s_4] L=[s1,s2,s3,s4] 先去除零元素:
L ′ = [ s i ∣ s i ≠ 0 ] L' = [s_i \mid s_i \neq 0] L′=[si∣si=0]

合并规则:
s j ′ = { 2 s j , s j = s j + 1 s j , 否则 s_j' = \begin{cases} 2 s_j, & s_j = s_{j+1} \\ s_j, & \text{否则} \end{cases} sj′={2sj,sj,sj=sj+1否则

合并后删除重复元素,并在右端填充零:
L ′ ′ = 补零 ( L ′ ) L'' = \text{补零}(L') L′′=补零(L′)

分数更新为:
score t + 1 = score t + ∑ 合并的方块 s j \text{score}_{t+1} = \text{score}t + \sum{\text{合并的方块}} s_j scoret+1=scoret+合并的方块∑sj

2.1.2 四个方向的移动矩阵
  • 向左(Left):
    S t ′ = [ _ m e r g e _ l i n e ( R i ) ∣ R i ∈ S t ] S_t' = [\_merge\_line(R_i) \mid R_i \in S_t] St′=[_merge_line(Ri)∣Ri∈St]

  • 向右(Right):
    S t ′ = [ 反转 ( _ m e r g e _ l i n e ( 反转 ( R i ) ) ) ∣ R i ∈ S t ] S_t' = [\text{反转}(\_merge\_line(\text{反转}(R_i))) \mid R_i \in S_t] St′=[反转(_merge_line(反转(Ri)))∣Ri∈St]

  • 向上(Up):
    S t ′ = 转置 ( [ _ m e r g e _ l i n e ( R i ) ∣ R i ∈ 转置 ( S t ) ] ) S_t' = \text{转置}([\_merge\_line(R_i) \mid R_i \in \text{转置}(S_t)]) St′=转置([_merge_line(Ri)∣Ri∈转置(St)])

  • 向下(Down):
    S t ′ = 转置 ( [ 反转 ( _ m e r g e _ l i n e ( 反转 ( R i ) ) ) ∣ R i ∈ 转置 ( S t ) ] ) S_t' = \text{转置}([\text{反转}(\_merge\_line(\text{反转}(R_i))) \mid R_i \in \text{转置}(S_t)]) St′=转置([反转(_merge_line(反转(Ri)))∣Ri∈转置(St)])

2.2 模块 2 - 代码实现

python 复制代码
# ========= 移动与合并逻辑 =========
def _merge_line(self, line):
    """
    合并单行(核心算法)
    返回新行和是否发生移动的标志
    """
    original = list(line)
    # 1. 压缩非零元素
    new_line = [num for num in line if num != 0]
    
    # 2. 合并相邻相同数字
    i = 0
    while i < len(new_line) - 1:
        if new_line[i] == new_line[i + 1]:
            new_line[i] *= 2
            self.score += new_line[i]
            new_line.pop(i + 1)
            new_line.append(0)  # 补零
            i += 1
        i += 1
    
    # 3. 补零到原始长度
    new_line += [0] * (self.grid_size - len(new_line))
    
    # 4. 返回新行与是否移动
    return new_line, new_line != original

def _move(self, direction):
    """
    通用移动逻辑
    direction: 'up', 'down', 'left', 'right'
    """
    moved = False
    
    # 上/下移动需要先转置
    if direction in ('up', 'down'):
        self.grid = [list(row) for row in zip(*self.grid)]
    
    for i in range(self.grid_size):
        row = self.grid[i][::-1] if direction in ('right', 'down') else self.grid[i]
        new_row, has_moved = self._merge_line(row)
        if direction in ('right', 'down'):
            new_row.reverse()
        self.grid[i] = new_row
        moved = moved or has_moved
    
    # 上/下移动还原
    if direction in ('up', 'down'):
        self.grid = [list(row) for row in zip(*self.grid)]
    
    return moved

2.3 模块 2 - 逻辑说明

  1. 单行处理核心: _merge_line 统一完成压缩、合并、补零和得分更新。
  2. 方向抽象化: _move 根据方向决定是否需要行翻转或矩阵转置,从而只用一套逻辑处理四个方向。
  3. 移动标志: 每次移动会返回 moved=TrueFalse,用于判断是否生成新方块。

2.4 示意矩阵变化

假设某行初始状态:
R = [ 2 , 0 , 2 , 4 ] R = [2, 0, 2, 4] R=[2,0,2,4]

  • 压缩非零元素:

    2 , 2 , 4 \] \[2, 2, 4\] \[2,2,4

  • 合并:

    4 , 4 , 0 \] \[4, 4, 0\] \[4,4,0

  • 得分增加 4(2+2)

  • 补零完成最终行:

    4 , 4 , 0 , 0 \] \[4, 4, 0, 0\] \[4,4,0,0


3. 模块 3:胜负判断与游戏结束检测

3.1 模块 3 - 数学模型

3.1.1 胜利条件

若存在某格值为 2048 2048 2048,则胜利:
won t = { True , ∃ i , j : s i , j ( t ) = 2048 False , 否则 \text{won}t = \begin{cases} \text{True}, & \exists i,j: s{i,j}(t) = 2048 \\ \text{False}, & \text{否则} \end{cases} wont={True,False,∃i,j:si,j(t)=2048否则

3.1.2 游戏结束条件

若无法移动,则游戏结束。具体条件为:

  1. 棋盘无空格:
    ∀ i , j : s i , j ( t ) ≠ 0 \forall i,j: s_{i,j}(t) \neq 0 ∀i,j:si,j(t)=0

  2. 水平方向和垂直方向不存在相邻相同方块:

    ∀ i , j < 4 : s i , j ≠ s i , j + 1 且 s j , i ≠ s j + 1 , i \forall i,j<4: s_{i,j} \neq s_{i,j+1} \text{ 且 } s_{j,i} \neq s_{j+1,i} ∀i,j<4:si,j=si,j+1 且 sj,i=sj+1,i

若满足以上条件,则:
game_over t = True \text{game\_over}_t = \text{True} game_overt=True

3.1.3 重置游戏

更新最高分:

best_score t + 1 = max ⁡ ( best_score t , score t ) , score t + 1 = 0 \text{best\score}{t+1} = \max(\text{best\_score}_t, \text{score}t), \quad \text{score}{t+1} = 0 best_scoret+1=max(best_scoret,scoret),scoret+1=0

清空棋盘并重置状态:

S t + 1 = O 4 × 4 , won t + 1 = game_over t + 1 = False S_{t+1} = O_{4\times4}, \quad \text{won}_{t+1} = \text{game\over}{t+1} = \text{False} St+1=O4×4,wont+1=game_overt+1=False

随机生成两个初始方块:

S 0 ′ = S 0 + P 1 + P 2 S_0' = S_0 + P_1 + P_2 S0′=S0+P1+P2

3.2 模块 3 - 代码实现

python 复制代码
# ========= 状态检测 =========
def check_win(self):
    """检查是否有 2048 方块"""
    if any(2048 in row for row in self.grid):
        self.won = True

def check_game_over(self):
    """检查是否无法移动"""
    # 1. 检查是否有空格子
    if any(0 in row for row in self.grid):
        return False
    
    # 2. 检查水平和垂直方向是否有可合并方块
    for i in range(self.grid_size):
        for j in range(self.grid_size - 1):
            if self.grid[i][j] == self.grid[i][j + 1]:
                return False
            if self.grid[j][i] == self.grid[j + 1][i]:
                return False
    
    # 3. 无法移动,则游戏结束
    self.game_over = True
    return True

def reset_game(self):
    """重置游戏"""
    # 更新最高分
    self.best_score = max(self.best_score, self.score)
    self.score = 0
    self.game_over = self.won = False
    
    # 清空棋盘
    self.grid = [[0] * self.grid_size for _ in range(self.grid_size)]
    
    # 初始化两个方块
    for _ in range(2):
        self.add_new_tile()

3.3 模块 3 - 逻辑说明

  1. 胜利检测
    • 使用 any() 判断棋盘中是否存在 2048 方块。
    • 一旦出现,设置 self.won = True,但游戏可继续进行以追求更高分数。
  2. 游戏结束检测
    • 首先检查是否存在空格子,有空格子则游戏未结束。
    • 然后检查所有相邻行列是否有相同方块,有则可合并,游戏未结束。
    • 如果没有空格子且无法合并,则游戏结束。
  3. 重置游戏
    • 保存最高分。
    • 清空棋盘和分数,重置状态标志。
    • 随机生成两个新方块,重新开始游戏。

4. 模块 4:控制台显示与用户界面

4.1 模块 4 - 数学模型

4.1.1 棋盘显示矩阵

显示 ( G t ) = [ g 1 , 1 g 1 , 2 g 1 , 3 g 1 , 4 g 2 , 1 g 2 , 2 g 2 , 3 g 2 , 4 g 3 , 1 g 3 , 2 g 3 , 3 g 3 , 4 g 4 , 1 g 4 , 2 g 4 , 3 g 4 , 4 ] , g i , j = { s i , j ( t ) , s i , j ( t ) ≠ 0 空格 , s i , j ( t ) = 0 \text{显示}(G_t) = \begin{bmatrix} g_{1,1} & g_{1,2} & g_{1,3} & g_{1,4} \\ g_{2,1} & g_{2,2} & g_{2,3} & g_{2,4} \\ g_{3,1} & g_{3,2} & g_{3,3} & g_{3,4} \\ g_{4,1} & g_{4,2} & g_{4,3} & g_{4,4} \end{bmatrix}, \quad g_{i,j} = \begin{cases} s_{i,j}(t), & s_{i,j}(t) \neq 0 \\ 空格, & s_{i,j}(t) = 0 \end{cases} 显示(Gt)= g1,1g2,1g3,1g4,1g1,2g2,2g3,2g4,2g1,3g2,3g3,3g4,3g1,4g2,4g3,4g4,4 ,gi,j={si,j(t),空格,si,j(t)=0si,j(t)=0

4.1.2 操作与状态提示
  1. 清屏:
    清屏 ( )    ⟹    删除上一帧显示 \text{清屏}() \implies \text{删除上一帧显示} 清屏()⟹删除上一帧显示

  2. 分数显示:
    score t → 显示当前分数 , best_score t → 显示历史最高分 \text{score}_t \to \text{显示当前分数}, \quad \text{best\_score}_t \to \text{显示历史最高分} scoret→显示当前分数,best_scoret→显示历史最高分

  3. 棋盘行输出:
    输出行 ( R i ) = ∣ d i , 1 ∣ d i , 2 ∣ d i , 3 ∣ d i , 4 ∣ \text{输出行}(R_i) = | d_{i,1} | d_{i,2} | d_{i,3} | d_{i,4} | 输出行(Ri)=∣di,1∣di,2∣di,3∣di,4∣

  4. 状态输出:
    输出状态 ( won t , game_over t ) \text{输出状态}(\text{won}_t, \text{game\_over}_t) 输出状态(wont,game_overt)

该函数满足:

  • 若 won t = True \text{won}_t = \text{True} wont=True,显示"恭喜你赢了!"
  • 若 game_over t = True \text{game\_over}_t = \text{True} game_overt=True,显示"游戏结束!"
  • 否则显示当前棋盘与分数信息。

4.2 模块 4 - 代码实现

python 复制代码
def display_grid(self):
    """在控制台显示游戏网格和状态"""
    # 清屏
    os.system('cls' if os.name == 'nt' else 'clear')

    # 显示标题和分数
    print("=" * 30)
    print(" " * 10 + "2048 游戏" + " " * 10)
    print("=" * 30)
    print(f"分数: {self.score:<8}最高分: {self.best_score}")
    print("-" * 30)

    # 显示棋盘网格
    for row in self.grid:
        row_str = "|".join(f"{val:^6}" if val != 0 else " " * 6 for val in row)
        print(f"|{row_str}|")
        print("-" * 30)

    # 显示操作提示
    print("使用方向键或 WASD 移动方块,'Q' 退出游戏")

    # 显示游戏状态
    if self.won:
        print("恭喜你赢了!继续挑战更高分数!")
    elif self.game_over:
        print("游戏结束!按任意键重新开始")

4.3 模块 4 - 逻辑说明

  1. 清屏
    使用系统命令 cls (Windows) 或 clear (Linux/macOS) 清除之前输出。
  2. 显示标题与分数
    格式化输出当前分数和最高分,便于玩家查看。
  3. 显示棋盘网格
    • 遍历棋盘矩阵的每一行。
    • 使用固定宽度格式化每个方块值,使网格整齐。
    • 空方块显示为空格。
  4. 显示操作提示
    指示玩家使用方向键或 WASD 移动方块。
  5. 显示游戏状态提示
    根据 wongame_over 标志显示不同信息。

4.4 界面设计

txt 复制代码
==============================
          2048 游戏
==============================
分数: 0       最高分: 0
------------------------------
|      |  2   |      |      |
------------------------------
|      |      |      |      |
------------------------------
|      |      |      |  4   |
------------------------------
|      |      |      |      |
------------------------------
使用方向键或 WASD 移动方块,'Q' 退出游戏

5. 模块 5:主循环与用户交互

5.1 游戏主循环

在每一帧交互中,定义用户输入函数:
U t = get_key ( ) U_t = \text{get\_key}() Ut=get_key()

输入空间:
U t ∈ { up , down , left , right , quit } U_t \in \{\text{up}, \text{down}, \text{left}, \text{right}, \text{quit}\} Ut∈{up,down,left,right,quit}

状态转移函数为:
S t + 1 = f ( S t , U t ) S_{t+1} = f(S_t, U_t) St+1=f(St,Ut)

其中:

  • 若 U t = 方向 U_t = \text{方向} Ut=方向,则调用 _move(U_t)
  • 若棋盘状态变化,则调用 add_new_tile()
  • 若 U t = quit U_t = \text{quit} Ut=quit,则退出循环。

5.2 模块 5 - 代码实现

python 复制代码
def run(self):
    """运行游戏主循环"""
    while True:
        # 显示网格
        self.display_grid()  # 显示当前状态
        self.check_win()  # 检查是否赢了

        # 检查是否游戏结束
        if self.check_game_over():  # 如果游戏结束
            self.display_grid()  # 显示最终状态
            input("按回车键重新开始...")
            self.reset_game()  # 重置游戏
            continue

        # 获取用户输入
        key = self.get_key() 
        if key == 'quit':  # 如果用户选择退出
            print("感谢游玩!再见!")
            break

        if key in ('up', 'down', 'left', 'right'):  # 处理用户移动
            if self._move(key):  # 如果移动成功
                self.add_new_tile()  # 添加新方块

5.3 模块 5 - 逻辑说明

  1. 事件循环结构: 游戏逻辑以无限循环运行,每帧更新状态。
  2. 按键捕获机制: msvcrt.getch() 可监听方向键或 WASD 键,实现即时响应。
  3. 输入映射层: 将低级字节码输入(如 b'H')映射为方向命令,便于逻辑统一。
  4. 状态控制: 若本轮操作导致棋盘变化,则随机生成新方块并进入下一轮循环。

6. 模块 6:程序入口与启动

6.1 模块 6 - 代码实现

python 复制代码
import random
import os
import msvcrt  # Windows下的键盘输入处理

class Console2048:
    def __init__(self, grid_size=4):
        """初始化游戏"""

    def add_new_tile(self):
        """在随机空位置添加新方块(2或4)"""

    def display_grid(self):
        """在控制台显示游戏网格"""

    # ========= 移动与合并逻辑 =========
    def _merge_line(self, line):
        """合并单行(核心算法),返回新行与是否移动标志"""

    def _move(self, direction):
        """通用移动逻辑(方向: up/down/left/right)"""

    # ========= 状态检测 =========
    def check_win(self):
        """检查是否有 2048 方块"""

    def check_game_over(self):
        """检查是否无法移动"""

    def reset_game(self):
        """重置游戏"""

    # ========= 输入与控制 =========
    def get_key(self):
        """获取键盘输入"""

    # ========= 主循环 =========
    def run(self):
        """运行游戏主循环"""


if __name__ == "__main__":
    print("2048 控制台版游戏")
    print("使用方向键或 WASD 键移动方块,按 'Q' 退出游戏")
    input("按回车键开始游戏...")
    Console2048().run()

6.2 模块 6 - 逻辑说明

  1. 程序入口判断: 使用 Python 标准写法 if __name__ == "__main__" 保证独立运行。
  2. 初始化提示信息: 以交互方式提示玩家游戏说明。
  3. 实例化与运行: 调用类 Console2048run() 方法进入主循环,实现完整的控制台游戏体验。

7. 总结

本文以数学建模视角完整剖析了 2048 控制台版游戏 的程序逻辑。

从状态矩阵定义到用户交互主循环,构建了一个由五个核心方程驱动的有限状态系统:
{ S t + 1 = f ( S t , U t ) score t + 1 = score t + ∑ 合并值 won t = [ ∃ s i j = 2048 ] game_over t = [ ∀ s i j ≠ 0 ∧ 无相邻相等 ] E t = { ( i , j ) ∣ s i j = 0 } \begin{cases} S_{t+1} = f(S_t, U_t) \\ \text{score}_{t+1} = \text{score}_t + \sum \text{合并值} \\ \text{won}t = [\exists s{ij}=2048] \\ \text{game\over}t = [\forall s{ij}\neq 0 \land 无相邻相等] \\ E_t = \{(i,j)\mid s{ij}=0\} \end{cases} ⎩ ⎨ ⎧St+1=f(St,Ut)scoret+1=scoret+∑合并值wont=[∃sij=2048]game_overt=[∀sij=0∧无相邻相等]Et={(i,j)∣sij=0}

这一结构展示了如何用数学模型统一游戏逻辑与程序实现

  1. 状态矩阵映射游戏局势;
  2. 转移函数刻画玩家动作;
  3. 概率分布控制随机生成;
  4. 判定函数定义系统边界。
相关推荐
2401_841495642 小时前
【数据结构】基于BF算法的树种病毒检测
java·数据结构·c++·python·算法·字符串·模式匹配
蒙奇D索大2 小时前
【算法】递归算法实战:汉诺塔问题详解与代码实现
c语言·考研·算法·面试·改行学it
图灵信徒2 小时前
R语言绘图与可视化第六章总结
python·数据挖掘·数据分析·r语言
wanhengidc2 小时前
海外云手机是指什么
运维·服务器·游戏·智能手机·云计算
封奚泽优2 小时前
使用Labelme进行图像标注
开发语言·python·labelme
檐下翻书1732 小时前
智能医疗大模型在医生培训中的应用案例
python
一只鱼^_2 小时前
力扣第 474 场周赛
数据结构·算法·leetcode·贪心算法·逻辑回归·深度优先·启发式算法
叫我龙翔3 小时前
【数据结构】从零开始认识图论 --- 单源/多源最短路算法
数据结构·算法·图论
码界筑梦坊3 小时前
243-基于Django与VUE的笔记本电脑数据可视化分析系统
vue.js·python·信息可视化·数据分析·django·毕业设计·echarts