Python 2048游戏代码实现(Pygame仿真带移动动画)

前些天发现没有博主用Pygame实现比较漂亮的2048游戏,于是就自己做了一版。功能及特色包括:全套经典配色和数字块图片、记分牌(分数和历史最高分)以及移动动画效果。效果图如下:

本文全套代码和资源在这里付费获取:2048游戏源码(Pygame仿真带移动动画)「恰饭需要,恳请各位支持!博主必定尽力为大家奉上最详尽的讲解文章。」

在这里插入图片描述

主函数

我把大部分功能的实现都封装为函数了,所以主函数里的内容还是比较简洁的:

def main():
	# 初始化部分
    global best_score
    pygame.init()     #初始化pyamge库
    images = load_images()     #载入数字块图片
    best_score = read_best()       #读入历史最高分数
    screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))     #新建窗口对象
    pygame.display.set_caption('2048')      #设置窗口标题
    map = init_map()       #初始化游戏地图二维列表
    map = add_randnum(map)        #随机加入一个新数字块
    draw_all(screen, map, images)        #绘制窗口

    # 主循环部分
    while True:
        for event in pygame.event.get():     # 获取事件(鼠标、键盘等)
            if event.type == pygame.QUIT:     # 如果按下关闭按钮
                pygame.quit()     # 退出pygame窗口
                sys.exit()     # 结束主循环
            if event.type == pygame.KEYDOWN:     #鼠标事件
                if event.key == pygame.K_UP or event.key == pygame.K_w:    #按下向上键或w键
                    map = move_up(screen, map, images)      #获取移动后的地图,函数中完成移动动画效果(下同)
                if event.key == pygame.K_DOWN or event.key == pygame.K_s:    #按下向下键或s键
                    map = move_down(screen, map, images)
                if event.key == pygame.K_LEFT or event.key == pygame.K_a:    #按下向左键或a键
                    map = move_left(screen, map, images)
                if event.key == pygame.K_RIGHT or event.key == pygame.K_d:    #按下向右键或d键
                    map = move_right(screen, map, images)

下面我把主程序里面用到的函数按顺序盘点一遍,方便大家参考借鉴。

载入图片:load_images()

将数字块图片都放在 block\ 文件夹下,图片 n.png 表示的是 2n 的数字块:

在这里插入图片描述
将图片用 pygame.image.load() 函数载入后存入列表,再使用 pygame.transform.scale() 函数调整图片大小以适应游戏界面。然后返回图片列表。

def load_images():
    images = [0]
    for i in range(1, 16):
        images.append(pygame.image.load("block/"+str(i)+".png"))
        images[i] = pygame.transform.scale(images[i], (int(BOLCK_WIDTH), int(BOLCK_WIDTH)))
    return images

读入最高分:read_best()

将最高分数存储在当前目录下的 best 文件,在载入游戏时读取。

def read_best():
    f = open('best', 'r')
    score = int(f.read())
    return score

初始化地图:load_images()

首先需要放出我定义的数字块类 numBlock(因为平常没怎么用过类与对象,所以可能这个类抽象得有点生涩,欢迎大家多多指教):

class numBlock:      #数字块类
    def __init__(self):
        self.num = 0         #数字
        self.rect = pygame.Rect(0, 0, BOLCK_WIDTH, BOLCK_WIDTH)       #所在矩形
    def set_position(self, x, y):        #根据数组下标设置矩形位置
        self.rect.x = GAMEBOX_LEFT+UNIT_WIDTH*(8*x+1)
        self.rect.y = GAMEBOX_TOP+UNIT_WIDTH*(8*y+1)

这个类里面有两个属性:数字块的数字和所在矩形。矩形使用的是 pygame 的 Rect 类,可以表示矩形的左上坐标和长宽。

另外还有一个函数 set_position(x, y) ,可以根据数字块的行列坐标将其移动到对应位置。


load_images() 函数就是用 numBlock 类生成了一个二维列表用来表示游戏地图:

def init_map():
    map = []
    for i in range(4):
        line = []
        for j in range(4):
            line.append(numBlock())
        map.append(line)
    return map

随机加入块:add_randnum(map)

随机选取地图上一个没有数字的位置,填入数字 1 或 2。需要注意,在我的数组里,n 表示的是 2n ,即随机加入的数字块是 2 或者 4。

def add_randnum(map):
    x = random.randint(0, 3)
    y = random.randint(0, 3)
    while map[y][x].num != 0:
        x = random.randint(0, 3)
        y = random.randint(0, 3)
    map[y][x].num = random.randint(1, 2)
    map[y][x].set_position(x, y)
    return map

绘制窗口:draw_all(screen, map, images)

我在这个函数里封装了三部分的绘制。首先先给出这个函数的定义:

def draw_all(screen, map, images):
    draw_bg(screen)
    draw_titlebar(screen)
    draw_blocks(screen, map, images)
    pygame.display.update()

看函数名就可以得知,这三部分分别是:绘制背景、绘制标题栏 和 绘制数字块。绘制完成后调用 pygame.display.update() 刷新窗口。

绘制背景

先填充背景色为浅黄色,然后绘制褐色游戏底盘,最后绘制八个浅褐色的数字框。相关颜色常量和位置常量的定义放在文章的最后。

def draw_bg(screen):
    screen.fill(YELLOW_LIGHT)
    gamebox_rect = pygame.Rect(GAMEBOX_LEFT, GAMEBOX_TOP, GAMEBOX_WIDTH, GAMEBOX_WIDTH)
    pygame.draw.rect(screen, BROWN_NORMAL, gamebox_rect, 0, 5)
    for i in range(4):
        for j in range(4):
            block_rect = pygame.Rect(GAMEBOX_LEFT+UNIT_WIDTH*(8*i+1), GAMEBOX_TOP+UNIT_WIDTH*(8*j+1), BOLCK_WIDTH, BOLCK_WIDTH)
            pygame.draw.rect(screen, BROWN_LIGHT, block_rect, 0, 5)

绘制标题栏

标题栏包括:2048 logo、记分牌底板、记分牌标题 和 记分牌数字。

def draw_titlebar(screen):
    #绘制2048标题
    title_font = pygame.font.Font('font/ClearSansBold.woff.ttf', TITLE_SIZE)
    title_text = title_font.render('2048', True, BROWN_DEEP)
    screen.blit(title_text, (GAMEBOX_LEFT, GAMEBOX_TOP/2-TITLE_SIZE*3/4))
    #绘制记分牌背板
    scoreboard_rect = pygame.Rect(WINDOW_WIDTH-GAMEBOX_LEFT-SCOREBOARD_WIDTH*15/7,
                                   GAMEBOX_TOP/2-SCOREBOARD_HEIGHT*3/4, SCOREBOARD_WIDTH, SCOREBOARD_HEIGHT)
    pygame.draw.rect(screen, BROWN_NORMAL, scoreboard_rect, 0, 3)
    bestboard_rect = pygame.Rect(WINDOW_WIDTH - GAMEBOX_LEFT - SCOREBOARD_WIDTH,
                                  GAMEBOX_TOP / 2 - SCOREBOARD_HEIGHT * 3 / 4, SCOREBOARD_WIDTH, SCOREBOARD_HEIGHT)
    pygame.draw.rect(screen, BROWN_NORMAL, bestboard_rect, 0, 3)
    #绘制记分牌标题文字
    scoretitle_font = pygame.font.SysFont('Arial', SCORETITLE_SIZE, True)
    scoretitle_text = scoretitle_font.render('SCORE', True, BROWN_MORE_LIGHT)
    scoretitle_rect = scoretitle_text.get_rect()
    scoretitle_rect.center = (WINDOW_WIDTH-GAMEBOX_LEFT-SCOREBOARD_WIDTH*(15/7-1/2), GAMEBOX_TOP/2-SCOREBOARD_HEIGHT/2)
    screen.blit(scoretitle_text, scoretitle_rect)

    besttitle_font = pygame.font.SysFont('Arial', SCORETITLE_SIZE, True)
    besttitle_text = besttitle_font.render('BEST', True, BROWN_MORE_LIGHT)
    besttitle_rect = besttitle_text.get_rect()
    besttitle_rect.center = (WINDOW_WIDTH-GAMEBOX_LEFT-SCOREBOARD_WIDTH/2, GAMEBOX_TOP/2-SCOREBOARD_HEIGHT/2)
    screen.blit(besttitle_text, besttitle_rect)

    #绘制分数
    global score, best_score

    score_font = pygame.font.Font('font/ClearSansBold.woff.ttf', SCORE_SIZE)
    score_text = score_font.render(str(score), True, WHITE)
    score_rect = score_text.get_rect()
    score_rect.center = (WINDOW_WIDTH - GAMEBOX_LEFT - SCOREBOARD_WIDTH * (15 / 7 - 1 / 2), GAMEBOX_TOP / 2 - SCOREBOARD_HEIGHT / 10)
    screen.blit(score_text, score_rect)

    best_font = pygame.font.Font('font/ClearSansBold.woff.ttf', SCORE_SIZE)
    best_text = best_font.render(str(best_score), True, WHITE)
    best_rect = best_text.get_rect()
    best_rect.center = (WINDOW_WIDTH - GAMEBOX_LEFT - SCOREBOARD_WIDTH / 2, GAMEBOX_TOP / 2 - SCOREBOARD_HEIGHT / 10)
    screen.blit(best_text, best_rect)

绘制数字块

遍历二维列表,如果数字不为0,则根据元素的 rect 所定义位置绘制出数字块。

def draw_blocks(screen, map, images):
    for i in range(4):
        for j in range(4):
            if map[i][j].num != 0:
                screen.blit(images[map[i][j].num], (map[i][j].rect.x,map[i][j].rect.y))

移动函数

按下方向键时的执行的程序我封装为了四个函数,这四个函数大概是这个游戏的代码的精髓吧。这是我根据自己的思路写的,如有问题请大家不吝指正。

下面仅以 move_up() 函数为例来讲解实现原理。其他三个方向的代码类似,只贴出完整代码,不做讲解。

向上移动:move_up(screen, map, images)

先贴上完整代码,然后再分块说明:

def move_up(screen, map, images):
    global score, best_score
    pre_map = copy.deepcopy(map)
    map = copy.deepcopy(map)
    move_step = [[0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0]]
    for j in range(4):
        k = 0
        pre_num = numBlock()
        for i in range(4):
            if map[i][j].num == 0:
                k += 1
            else:
                if map[i][j].num == pre_num.num:
                    pre_num.num += 1
                    score += pow(2, pre_num.num)
                    if best_score < score:
                        best_score = score
                        write_best(score)
                    map[i][j] = numBlock()
                    k += 1
                    move_step[i][j] = k
                else:
                    t = map[i][j]
                    map[i][j] = numBlock()
                    map[i - k][j] = t
                    map[i - k][j].set_position(j, i - k)
                    move_step[i][j] = k
                pre_num = map[i - k][j]
    is_moving = True
    k = 0
    while is_moving:
        is_moving = False
        k += 1
        for i in range(4):
            for j in range(4):
                if move_step[i][j] > 0.1:
                    is_moving = True
                    move_step[i][j] -= 1/10
                    pre_map[i][j].set_position(j, i-k*1/10)
        draw_all(screen, pre_map, images)

    if not equal_map(map, pre_map):
        add_randnum(map)
    draw_all(screen, map, images)
    return map

在这段程序的开头先引入全局变量 scorebest_score,因为每次移动完都需要计分。然后将地图用深拷贝 copy.deepcopy() 复制两份进行后续操作。初始化列表 move_step 用于存储每个数字块将要移动的格数,这个数组是为移动动画准备的。

	global score, best_score
    pre_map = copy.deepcopy(map)
    map = copy.deepcopy(map)
    move_step = [[0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0]]

这里是游戏规则的核心算法,用到一个双重循环来模拟获得出移动后的新地图。对于每一列,从上到下遍历这一列,用变量k表示当前数字块需要上移几格,然后上移。

过程中判断如果当前数字与前一个数字相同,则删除该块并给上一块升级,即合块。

当然,计分也是在这里完成的。每次合块后,增加的分数为合块后的数字。

    for j in range(4):
        k = 0
        pre_num = numBlock()
        for i in range(4):
            if map[i][j].num == 0:
                k += 1
            else:
                if map[i][j].num == pre_num.num:
                    pre_num.num += 1
                    score += pow(2, pre_num.num)
                    if best_score < score:
                        best_score = score
                        write_best(score)
                    map[i][j] = numBlock()
                    k += 1
                    move_step[i][j] = k
                else:
                    t = map[i][j]
                    map[i][j] = numBlock()
                    map[i - k][j] = t
                    map[i - k][j].set_position(j, i - k)
                    move_step[i][j] = k
                pre_num = map[i - k][j]

接着是完成移动动画,循环小步移动需要移动的数字块,每移动一小步使用之前定义的 draw_all() 函数刷新窗口,直至移动完成。

    is_moving = True
    k = 0
    while is_moving:
        is_moving = False
        k += 1
        for i in range(4):
            for j in range(4):
                if move_step[i][j] > 0.1:
                    is_moving = True
                    move_step[i][j] -= 1/10
                    pre_map[i][j].set_position(j, i-k*1/10)
        draw_all(screen, pre_map, images)

最后,判断移动地图是否相等,即移动前的地图是否可以移动。如果没有移动则不用增加新的随机数字块。

if not equal_map(map, pre_map):
        add_randnum(map)

这里的 equal_map(map, pre_map) 函数定义如下:

def equal_map(map1, map2):
    equal = True
    for i in range(4):
        for j in range(4):
            if map1[i][j].num != map2[i][j].num:
                equal = False
                break
    return equal

向下移动:move_down(screen, map, images)

def move_down(screen, map, images):
    global score, best_score
    pre_map = copy.deepcopy(map)
    map = copy.deepcopy(map)
    move_step = [[0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0]]
    for j in range(4):
        k = 0
        pre_num = numBlock()
        for i in range(3, -1, -1):
            if map[i][j].num == 0:
                k += 1
            else:
                if map[i][j].num == pre_num.num:
                    pre_num.num += 1
                    score += pow(2, pre_num.num)
                    if best_score < score:
                        best_score = score
                        write_best(score)
                    map[i][j] = numBlock()
                    k += 1
                    move_step[i][j] = k
                else:
                    t = map[i][j]
                    map[i][j] = numBlock()
                    map[i + k][j] = t
                    map[i + k][j].set_position(j, i + k)
                    move_step[i][j] = k
                pre_num = map[i + k][j]
    is_moving = True
    k = 0
    while is_moving:
        is_moving = False
        k += 1
        for i in range(4):
            for j in range(4):
                if move_step[i][j] > 0.1:
                    is_moving = True
                    move_step[i][j] -= 1/10
                    pre_map[i][j].set_position(j, i+k*1/10)
        draw_all(screen, pre_map, images)

    if not equal_map(map, pre_map):
        add_randnum(map)
    draw_all(screen, map, images)
    return map

向左移动:move_left(screen, map, images)

def move_left(screen, map, images):
    global score, best_score
    pre_map = copy.deepcopy(map)
    map = copy.deepcopy(map)
    move_step = [[0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0]]
    for i in range(4):
        k = 0
        pre_num = numBlock()
        for j in range(4):
            if map[i][j].num == 0:
                k += 1
            else:
                if map[i][j].num == pre_num.num:
                    pre_num.num += 1
                    score += pow(2, pre_num.num)
                    if best_score < score:
                        best_score = score
                        write_best(score)
                    map[i][j] = numBlock()
                    k += 1
                    move_step[i][j] = k
                else:
                    t = map[i][j]
                    map[i][j] = numBlock()
                    map[i][j - k] = t
                    map[i][j - k].set_position(j - k, i)
                    move_step[i][j] = k
                pre_num = map[i][j - k]
    is_moving = True
    k = 0
    while is_moving:
        is_moving = False
        k += 1
        for i in range(4):
            for j in range(4):
                if move_step[i][j] > 0.1:
                    is_moving = True
                    move_step[i][j] -= 1/10
                    pre_map[i][j].set_position(j-k*1/10, i)
        draw_all(screen, pre_map, images)

    if not equal_map(map, pre_map):
        add_randnum(map)
    draw_all(screen, map, images)
    return map

向右移动:move_right(screen, map, images)

def move_right(screen, map, images):
    global score, best_score
    pre_map = copy.deepcopy(map)
    map = copy.deepcopy(map)
    move_step = [[0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0]]
    for i in range(4):
        k = 0
        pre_num = numBlock()
        for j in range(3, -1, -1):
            if map[i][j].num == 0:
                k += 1
            else:
                if map[i][j].num == pre_num.num:
                    pre_num.num += 1
                    score += pow(2, pre_num.num)
                    if best_score < score:
                        best_score = score
                        write_best(score)
                    map[i][j] = numBlock()
                    k += 1
                    move_step[i][j] = k
                else:
                    t = map[i][j]
                    map[i][j] = numBlock()
                    map[i][j + k] = t
                    map[i][j + k].set_position(j + k, i)
                    move_step[i][j] = k
                pre_num = map[i][j + k]
    is_moving = True
    k = 0
    while is_moving:
        is_moving = False
        k += 1
        for i in range(4):
            for j in range(4):
                if move_step[i][j] > 0.1:
                    is_moving = True
                    move_step[i][j] -= 1/10
                    pre_map[i][j].set_position(j+k*1/10, i)
        draw_all(screen, pre_map, images)

    if not equal_map(map, pre_map):
        add_randnum(map)
    draw_all(screen, map, images)
    return map

附录:程序开头定义的常量

# 位置常量
WINDOW_WIDTH = 600
WINDOW_HEIGHT = 750

GAMEBOX_LEFT = 50
GAMEBOX_TOP = 200
GAMEBOX_WIDTH = 500

UNIT_WIDTH = GAMEBOX_WIDTH/33
BOLCK_WIDTH = UNIT_WIDTH*7

SCOREBOARD_WIDTH = 120
SCOREBOARD_HEIGHT = 55

# 字体大小
TITLE_SIZE = 60
SCORETITLE_SIZE = 14
SCORE_SIZE = 24

# 颜色常量
BROWN_DEEP = (119, 110, 101)
BROWN_NORMAL = (187, 173, 160)
BROWN_LIGHT = (205, 193, 180)
BROWN_MORE_LIGHT = (238, 228, 218)
YELLOW_LIGHT = (250, 248, 239)
WHITE = (255, 255, 255)
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

两只程序猿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值