【细节呈现】用Python编写2048游戏(命令行版)

本篇博文围绕使用Python开发热门游戏2048 GAME(命令行版本)

代码未做任何优化(原生且随意)、全程以面向过程MVC的设计思想为主、开发环境是Ubuntu系统下的Pycharm

2048是我学习Python过程中的一个作业,直入正题——

一、了解游戏

1. 介绍

2048》是一款单人在线和移动端游戏,由19岁的意大利人Gabriele Cirulli于2014年3月开发。游戏任务是在一个网格上滑动小方块来进行组合,直到形成一个带有有数字2048的方块(来源:维基百科

2. 玩法规则

  1. 通过方向键让方块整体上下左右移动
  2. 如果两个带有相同数字的方块在移动中碰撞,则它们会相加合并为一个新方块
  3. 每次出现方块移动时,都会有一个值为2或者4的新方块出现
  4. 初始开局时,4*4的方块,随机2个方块赋值2或者4
  5. 其中所出现的数字都是2的幂,2,4,8,16…

二、MVC设计

Model:无
View:终端界面(有时间再研究一下pyQt),打印二维列表,输入输出控制
Controller:二维列表-矩阵、数据控制、上下左右操作、计分机制、方块合并处理等等

三、核心函数

通过观察游戏界面,可知数据由二维数组(线性代数–方阵)存储,将上图映射到如下代码:

source = [
    [0, 0, 0, 0],
    [0, 0, 2, 2],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]

通过玩法规则第1条可知,方向键上下左右移动,四种移动必然存在相似的操作,祁天暄讲师通过分析向左移动来书写后续代码,我这里也会以向左移动来分析如何写后续的代码。

在这里插入图片描述

[0, 0, 2, 2]

取上述一行,按左方向键移动后,可以看到两个方块2持续左移(如果左边还有非0的方块,那么就会顶住该非0的方块),然后相撞变成方块4,因为有方块移动,所以随机挑选一个方块0进行填充成2(不限于当前行,也可能发生在其他行):
在这里插入图片描述

本行规律:

[0, 0, 2, 2] >发生滑动> [2, 2, 0, 0] >相等相撞求和> [4, 0, 0, 0] >随机填充> [4, 0, 0, 2]

经过多局游戏结合上方的规律,可以得知以下规律:

[0, 0, 2, 2] >发生滑动> [2, 2, 0, 0] >相等相撞求和> [4, 0, 0, 0] >随机填充> [4, 0, 0, 2]
[4, 0, 2, 2] >发生滑动> [4, 2, 2, 0] >相等相撞求和> [4, 4, 0, 0] >随机填充> [4, 4, 0, 4]
[4, 4, 2, 2] >发生滑动,不动> [4, 4, 2, 2] >相等相撞求和> [8, 4, 0, 0] >随机填充> [4, 0, 0, 2]

1. 滑动处理

发生滑动的环节,可以得出一个规律,有0在非0元素的前方则必滑动,否则不动

[0, 0, 2, 2] >发生滑动> [2, 2, 0, 0]
[4, 0, 2, 2] >发生滑动> [4, 2, 2, 0]
[4, 4, 2, 2] >发生滑动,不动> [4, 4, 2, 2]

所以此处构造一个zero_to_end函数,功能就是将0移至末尾处,并保持非0元素应有的顺序,先看一下中规中矩的方式(冒泡式的移动,时间复杂度较高):

def zero_to_end(list_data):
    for i in range(3, -1, -1):
        for j in range(i):
            if list_data[j] == 0:
                list_data[j], list_data[j + 1] = list_data[j + 1], list_data[j]

再看一下另一种写法(采用该函数,时间复杂度为O(n)):

def zero_to_end(list_data):
    """
    重排序函数(核心算法)
    非0元素移至最前(保持顺序),0元素移至最后,充当中间人处理列表的角色
    :param list_data: list 一维列表
    :return: None
    """
    for i in range(3, -1, -1):
        if not list_data[i]:
            del list_data[i]
            list_data.append(0)

2. 相等相撞求和

经上一函数,每一行列表都被处理成:若干非0元素有序在前,若干0元素在后

[2, 2, 0, 0] >相等相撞求和> [4, 0, 0, 0]
[4, 2, 2, 0] >相等相撞求和> [4, 4, 0, 0]
[4, 4, 2, 2] >相等相撞求和> [8, 4, 0, 0]

相等相撞求和这个过程肯定要统一函数处理,增加复用性,因此需要详细拆分该流程的细节:

[4, 2, 2, 0]
如果第1个元素等==2个元素:
    则第1个元素 +2个元素,并赋给第1个元素的位置
    删除第2个元素
    末尾追加一个0
[4, 2, 2, 0]
如果第2个元素==3个元素(符合条件)
    则第2个元素 +3个元素,并赋给第2个元素的位置[4, 4, 2, 0]
    删除第3个元素[4, 2, 0]
    末尾追加一个0[4, 2, 0, 0]
[4, 2, 0, 0]
如果第3个元素==4个元素
    则第3个元素 +4个元素,并赋给第3个元素的位置
    删除第4个元素
    末尾追加一个0

也就是说,相邻且相等的两个元素相加,应赋值给前方位置的元素,然后删除后方位置的元素,删除了一个,肯定还要凑回去的,根据游戏规则,补0即可

其实上方的逻辑还可以进行优化,当检测到当前位置的元素为0时,直接打断循环即可(因为已经被zero_to_end函数处理过了),封装成merge_single函数如下:

def merge_single(list_data):
    """
    合并元素函数(核心算法)
    重排序后,左边两个相邻相同的非0元素相加,后方补0,并加分(可diy)
    如果两个相邻的元素不同或者为0,则不做其他操作
    :param list_data: list 一维列表
    :return: None
    """
    zero_to_end(list_data)  # 处理一维列表
    for i in range(3):
        if list_data[i] == 0: break  # 检测到当前位置为0,后方就不管了,直接打断
        if list_data[i] == list_data[i + 1]:
            list_data[i] *= 2  # 等价于 += list_data[i + 1]
            del list_data[i + 1]  # 删除 [i + 1]位置的元素
            list_data.append(0)  # 补0

有点不太放心,放一条数据进行测试:

[4, 4, 2, 2] >i = 0,相加> [8, 4, 2, 2] >删除i + 1位置> [8, 2, 2] >0> [8, 2, 2, 0] 
>循环结束第1次,i = 1,又相加> [8, 4, 2, 0] >又删除i + 1位置> [8, 4, 0] >又补0> [8, 4, 0, 0]
> 循环结束第2次,i = 2,发现是0,直接跳出循环 

3. 随机填充

玩法规则第3条,当游戏中,发生方块滑动时,在0元素区域随机抽取一个位置,随机赋值2或者4。

因此,先定义全局变量,一个存2和4的元组,通过random模块实现随机索引获取2或者4。

random_tuple = (2, 4)  # 初始添加的值、移动时添加的值

通过while循环不断寻找随机方格,直到发现该方格存储0,那么该方格将被赋予新值。

def random_site():
    """
    随机填充0元素函数(非核心)
    随机挑选0元素的位置,进行随机填充random_list中的任意一个元素
    可通过增删改变random_list中的元素,从而影响到随机填充的数字
    :return: None
    """
    random_list_len = len(random_tuple)
    while True:
        x = random.randint(0, 3)
        y = random.randint(0, 3)
        if after_source[x][y] == 0:
            after_source[x][y] = random_tuple[random.randint(0, random_list_len - 1)]
            break

四、附加功能函数

通过以上三步,成功的完成了2048的核心功能,接下来逐一部署2048的初始化、游戏操作、用户操作、打印等等函数。

1. 初始数据和矩阵比较

构造游戏初始数据,以全局变量表示:

score = 0  # 初始分数,后续累加即可
source = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]

单纯的一个source表示数据可能还不够,我构造了两个4*4的矩阵,分别命名为before_source和after_source:

before_source = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]  # 操作前的矩阵
after_source = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]  # 操作后的矩阵(当前打印的矩阵)

这两个矩阵,用于用户操作前的一个比较(后台比较),假设用户进行左向移动,那么移动前的数据传给before_source,移动后(即每一行调用merge_single函数后)的数据传给after_source,after_source才是用户需要看的,两者在后台进行比较后,倘若不同,则说明发生了“方块移动”,那么就要调用随机填充的函数,如果相同,说明没有方块滑动,则不可随机填充。假如对以下的数据发起左移操作后,是不存在元素移动的,即不会调用随机填充:

[
    [4, 0, 0, 0],
    [0, 0, 0, 0],
    [2, 4, 2, 0],
    [8, 2, 0, 0]
]

根据上述分析,构造比较函数compare_matrix:

def compare_matrix():
    """
    二维数组比较
    操作前后的二维数组(矩阵)进行比较
    如果不相等,说明有元素可移动,当移动时调用random_site()函数
    """
    if not (before_source == after_source):
        random_site()

2. 矩阵数据打印

每次执行完移动操作后(无论是上下还是左右),肯定都要反馈给用户数据界面,因此需要构造打印矩阵的函数:

def print_list():
    """打印游戏过程中必看的矩阵信息"""
    for single_list in after_source:
        print(single_list)

3. main入口(框架搭建)

调用程序总该需要一个入口,构造main函数。

循环开始阶段,通过global关键字操作全局变量before_source,此处需要注意:应使用深拷贝(需要导入copy模块),将当前的数据拷贝给before_source,如果采取浅拷贝,操作after_source后,before_source也会跟随变化,这样就导致before_source恒等于after_source。

上下左右以input输入(w 、s、a、d)来进行移动,n表示主动认输,q表示退出游戏。

每次执行完移动操作后,都需要进行反馈数据界面,所以循环末尾需要调用print_list函数和打印当前分数:

def main():
    """程序入口:初始化 + 输入 + 输出"""
    while True:
        global before_source
        before_source = copy.deepcopy(after_source)
        key = input("键入:")
        if key == "a": pass
        if key == "d": pass
        if key == "w": pass
        if key == "s": pass
        if key == "n": pass
        if key == "q": break  
        print_list()
            
            
main()  # 调用main函数,即正常游戏的入口

当准备输入时,手动中止程序,会发现很烦人的红色报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlpgZPVR-1674483797911)(/home/ronan/.config/Typora/typora-user-images/image-20230123213355607.png)]

因此加上try和except简单的处理一下:

def main():
    """程序入口:初始化 + 输入 + 输出"""
    while True:
        try:
            key = input("键入:")
            global before_source
            before_source = copy.deepcopy(after_source)
            if key == "a": pass
            if key == "d": pass
            if key == "w": pass
            if key == "s": pass
            if key == "n": pass
            if key == "q": break
            print(f"当前分数:{score}")
            print_list()
        except KeyboardInterrupt:
            break
        
            
            
main()  # 调用main函数,即正常游戏的入口

4. 实现认输功能

构建forfeit函数,打印最终分数后,人为抛出KeyboardInterrupt异常,直接调到except执行break打断循环(为了不在打印最终得分后,执行后续两条语句):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqFS4KKo-1674483797912)(/home/ronan/.config/Typora/typora-user-images/image-20230123214632123.png)]

def forfeit():
    """认输"""
    print(f"玩家已认输,最终得分:{score}")
    raise KeyboardInterrupt

5. 加分机制

每发生方块碰撞合并后,相加数字即为当局加分,比如初始为0,两个相邻的方块2碰撞合并后变成方块4,当前分数+4,即分数为4。

在merge_single函数中(参考2.2的函数),list_data.append(0)语句后添加下述代码:

global score
score += list_data[i]

即:

def merge_single(list_data):
    """
    合并元素函数(核心算法)
    重排序后,左边两个相邻相同的非0元素相加,后方补0,并加分(可diy)
    如果两个相邻的元素不同或者为0,则不做其他操作
    :param list_data: list 一维列表
    :return: None
    """
    zero_to_end(list_data)
    for i in range(3):
        if list_data[i] == 0: break
        if list_data[i] == list_data[i + 1]:
            list_data[i] *= 2
            del list_data[i + 1]
            list_data.append(0)
            global score
            score += list_data[i]

6. 游戏初始化

为了让游戏设置得灵活一点,增加全局变量init_count,表示初始方块需赋值2或者4的个数,通常为2。

init_count = 2  # 初始值的个数


def init():
    """
    游戏初始化
    :return: None
    """
    print(f"""当前分数:{score}\n操作方式:q退出 n认输
       w(上)
a(左)  s(下)  d(右)""")
    for i in range(init_count):  # 随机生成init_count个初始值
        random_site()
    print_list()

7. 各方向移动操作

首先要明确,核心函数中第2节的相等相撞合并函数,仅仅针对一维列表,而整个游戏以二维列表为主,因此需要复用该代码:

def merge():
    """合并操作,详见merge_single()函数"""
    for i in range(4):
        merge_single(after_source[i])

接下来逐一分析,左右上下操作如何实现…

(1)左(基础操作)

调用merge函数,完成滑动合并,然后比较前后矩阵相等来决定是否调用随机填充(即调用compare_matrix函数)

def left():
    """向左操作"""
    merge()

(2)右

最暴力无脑的办法就是复制上述函数代码,然后更改,但是我一直写这些简单清晰的函数,就是为了复用,所以这里应该想办法复用merge等代码,我以向左操作为基础,仅看merge函数,假设每一行都逆转,再进行向左的核心操作,再逆转回去,不就可以了吗,比如:

[0, 2, 2, 4]向右移动操作后变成[0, 0, 4, 4]
[0, 2, 2, 4] >逆转> [4, 2, 2, 0] >merge> [4, 4, 0, 0] >逆转> [0, 0, 4, 4]

因此代码如下:

def reverse():
    """逆转2048二维列表中的每一行一维列表"""
    for i in range(4):
        after_source[i].reverse()
def right():
    """向右操作"""
    reverse()
    merge()
    reverse()

(3)上

同样为了复用,以向左操作为基础,仅看merge函数,假设进行矩阵转置,然后调用merge操作,再转置,比如:

[0, 2, 0, 0]
[4, 2, 0, 0]
[0, 0, 4, 0]
[4, 0, 0, 0]
转置后
[0, 4, 0, 4]
[2, 2, 0, 0]
[0, 0, 4, 0]
[0, 0, 0, 0]
调用merge后
[8, 0, 0, 0]
[4, 0, 0, 0]
[4, 0, 0, 0]
[0, 0, 0, 0]
再转置回来,得到结果
[8, 4, 4, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]

因此代码如下(3种转置均可):

def transposition():
    """二维列表转置(矩阵转置)"""
    for x in range(4):
        for y in range(x, 4):
            after_source[x][y], after_source[y][x] = after_source[y][x], after_source[x][y]

def transposition():
    """二维列表转置(矩阵转置)"""
    new_map = [list(item) for item in zip(*after_source)]
    after_source.clear()
    after_source.extend(new_map)

def transposition():
    """二维列表转置(矩阵转置)"""
    new_map = [list(item) for item in zip(*after_source)]
    after_source[:] = new_map
def up():
    """向上操作"""
    transposition()
    merge()
    transposition()

(3)下

根据上移和右移操作所得的灵感,同样是以左移为基础操作。假设进行矩阵转置,逆转后,调用merge操作,再逆转,再转置,比如:

[0, 2, 0, 0]
[4, 2, 0, 0]
[0, 0, 4, 0]
[4, 0, 0, 0]
转置后
[0, 4, 0, 4]
[2, 2, 0, 0]
[0, 0, 4, 0]
[0, 0, 0, 0]
逆转每一行后
[4, 0, 4, 0]
[0, 0, 2, 2]
[0, 4, 0, 0]
[0, 0, 0, 0]
调用merge后
[8, 0, 0, 0]
[4, 0, 0, 0]
[4, 0, 0, 0]
[0, 0, 0, 0]
再逆转回来
[0, 0, 0, 8]
[0, 0, 0, 4]
[0, 0, 0, 4]
[0, 0, 0, 0]
再转置回来,得到结果
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[8, 4, 4, 0]

因此代码如下:

def down():
    """向下操作"""
    transposition()
    reverse()
    merge()
    reverse()
    transposition()

五、最终代码

"""
    2048 GAME
"""
import random
import copy

score = 0  # 分数
init_count = 2  # 初始值的个数
before_source = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]  # 操作前的矩阵
after_source = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]  # 操作后的矩阵(当前打印的矩阵)
random_tuple = (2, 4)  # 初始添加的值、移动时添加的值


# Controller层
def zero_to_end(list_data):
    """
    重排序函数(核心算法)
    非0元素移至最前(保持顺序),0元素移至最后,充当中间人处理列表的角色
    :param list_data: list 一维列表
    :return: None
    """
    for i in range(3, -1, -1):
        if not list_data[i]:
            del list_data[i]
            list_data.append(0)


def merge_single(list_data):
    """
    合并元素函数(核心算法)
    重排序后,左边两个相邻相同的非0元素相加,后方补0,并加分(可diy)
    如果两个相邻的元素不同或者为0,则不做其他操作
    :param list_data: list 一维列表
    :return: None
    """
    zero_to_end(list_data)
    for i in range(3):
        if list_data[i] == 0: break
        if list_data[i] == list_data[i + 1]:
            list_data[i] *= 2
            del list_data[i + 1]
            list_data.append(0)
            global score
            score += list_data[i]


def random_site():
    """
    随机填充0元素函数(非核心)
    随机挑选0元素的位置,进行随机填充random_list中的任意一个元素
    可通过增删改变random_list中的元素,从而影响到随机填充的数字
    :return: None
    """
    random_list_len = len(random_tuple)
    while True:
        x = random.randint(0, 3)
        y = random.randint(0, 3)
        if after_source[x][y] == 0:
            after_source[x][y] = random_tuple[random.randint(0, random_list_len - 1)]
            break


def merge():
    """合并操作,详见merge_single()函数"""
    for i in range(4):
        merge_single(after_source[i])


def reverse():
    """逆转2048二维列表中的每一行一维列表"""
    for i in range(4):
        after_source[i].reverse()


def transposition():
    """二维列表转置(矩阵转置)"""
    for x in range(4):
        for y in range(x, 4):
            after_source[x][y], after_source[y][x] = after_source[y][x], after_source[x][y]


def compare_matrix():
    """
    二维数组比较
    操作前后的二维数组(矩阵)进行比较
    如果不相等,说明有元素可移动,当移动时调用random_site()函数
    """
    if not (before_source == after_source):
        random_site()


def left():
    """向左操作"""
    merge()


def right():
    """向右操作"""
    reverse()
    merge()
    reverse()


def up():
    """向上操作"""
    transposition()
    merge()
    transposition()


def down():
    """向下操作"""
    transposition()
    reverse()
    merge()
    reverse()
    transposition()


# View层
def init():
    """
    游戏初始化
    :return: None
    """
    print(f"""当前分数:{score}\n操作方式:q退出 n认输
       w(上)
a(左)  s(下)  d(右)""")
    for i in range(init_count):  # 随机生成init_count个初始值
        random_site()
    print_list()


def print_list():
    """打印游戏过程中必看的矩阵信息"""
    for single_list in after_source:
        print(single_list)


def forfeit():
    """认输"""
    print(f"玩家已认输,最终得分:{score}")
    raise KeyboardInterrupt


def main():
    """程序入口:初始化 + 输入 + 输出"""
    init()
    while True:
        try:
            global before_source
            before_source = copy.deepcopy(after_source)
            key = input("键入:")
            if key == "a": left()
            if key == "d": right()
            if key == "w": up()
            if key == "s": down()
            if key == "n": forfeit()
            if key == "q": break
            compare_matrix()
            print(f"当前分数:{score}")
            print_list()
        except KeyboardInterrupt:
            break


main()

  • 0
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在 Python 中模拟 3D 气旋需要使用一些数学知识,如果您是一名科学家或工程师,那么您应该已经具备了相关的知识。 可以使用 NumPy 和 Matplotlib 等 Python 科学计算库来编写代码。NumPy 可以用于处理多维数组,而 Matplotlib 可以用于创建图形和可视化数据。 代码的细节取决于您对气旋模型的定义以及您希望展示的信息,但是一般来说,代码可以通过以下步骤实现: 1. 定义气旋模型的参数,如半径、旋转速度等。 2. 使用 NumPy 创建一个多维数组来存储气旋的位置数据。 3. 对气旋的位置进行模拟,并更新数组中的数据。 4. 使用 Matplotlib 对数据进行可视化,创建 3D 图形来展示气旋的变化。 如果您是一名初学者,编写一个简单的 3D 气旋模型可能需要一定的代码编写经验,但是可以通过查阅教程和参考代码来学习。 ### 回答2: 要用Python编写3D气旋模型,首先需要使用相关的库和工具来实现。 一种常用的Python库是matplotlib,它可以用来绘制2D和3D图形。可以使用其中的plot_surface函数来创建气旋的表面图,并使用color参数来设置不同的颜色来表示气旋的不同部分。 另外,还可以使用NumPy库来进行数学运算和数组处理。通过定义气旋的方程,可以使用NumPy库中的函数来计算出气旋的各个点的坐标和高度。 在编写代码时,可以先创建一个空的3D图,并设置合适的角度和视图。然后,根据气旋的方程计算出每个点的坐标和高度,并使用plot_surface函数将这些点连接起来,形成一个完整的气旋模型。 为了增强视觉效果,可以为气旋模型添加一些细节,例如使用圆柱体来表示旋涡的核心或旋转的云团,使用颜色渐变来表示不同地区或高度的气压差异等等。 最后,在完成模型的绘制后,可以通过调整视图和旋转角度,以便观察模型的各个部分和细节。 总之,使用Python编写3D气旋模型需要结合matplotlib和NumPy等库,根据气旋的方程计算坐标和高度,并使用plot_surface函数将这些点连接起来,形成一个完整的模型。通过增加细节和调整视图,可以得到更加真实和逼真的气旋效果。 ### 回答3: 编写3D气旋模型的Python程序可以通过以下步骤实现: 1. 导入必要的库:首先要导入Python库,如NumPy、Matplotlib和Mayavi,以便处理数学运算、数据可视化和绘图。 2. 生成气旋模型的数据:使用公式或算法生成气旋的数据。可以使用数学函数来生成气旋特征如位置、大小、旋转速度和方向等信息。将这些数据存储在适当的数据结构中,如数组或矩阵。 3. 创建3D场景:使用Mayavi库中的函数创建一个3D场景,并添加一个3D坐标轴和网格,以便在场景中显示气旋模型。 4. 绘制气旋:使用Matplotlib的绘图函数或Mayavi的绘图函数,在3D场景中绘制气旋。根据气旋模型的数据,在场景中添加一个或多个球体或圆盘,代表气旋的位置、大小和形状。 5. 设置场景参数:调整场景的显示参数,如相机视角、光照效果和颜色映射。可以使用Mayavi的函数来设置这些参数,以使气旋模型在场景中呈现出逼真且易于理解的效果。 6. 渲染和显示模型:使用Mayavi库中的函数渲染和显示3D模型。将生成的3D场景保存为图像文件或直接在窗口中显示。 7. 添加交互功能(可选):如果需要,可以使用Python库中的事件处理函数和用户界面元素,为模型添加交互功能。例如,允许用户通过鼠标或键盘控制气旋的运动或改变气旋的参数。 通过上述步骤,可以编写一个用Python实现的3D气旋模型程序。根据具体需求和特定的气旋模型算法,可以进一步调整和扩展程序。在编写过程中,适当的注释和文档可以帮助代码的可读性和理解性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顾平安6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值