猫史档案馆


【Python作品分享】2048【作品秀】

用户:射手座的丁丁然射手座的丁丁然查看:0 回复:1 评论:0 创建时间:2021-01-29T17:17:37


【作品展示】

center_image

 

【作品介绍】

完整版2048(附带注释哟)

需要pygame

致谢:码高少儿编程

 

【作品源代码】

#引入各种模块----------------------------
import pygame
import time
import sys
import random
from pygame.locals import *
import threading  # 多线程模块

#定义常量,存储界面尺寸-------------------
SIZE = 4  # 2048是4*4的小格,所以尺寸为4
LENGTH = 130  # 每个小格,边长长度为130
SCORE_HEIGHT = 130  # 计分区所占高度为130

#第1次添加程序开始------------------------------
#用元组定义背景颜色和每个数字对应的方块颜色-------
DEFAULT = (205, 193, 180)  # 背景颜色
C_FONT = (120, 111, 102)  # 字体颜色
C_2 = (238, 228, 218)  # 数字2方块色
C_4 = (237, 224, 200)  # 以下都是对应的方块色
C_8 = (242, 177, 121)  # 全部采用RGB颜色混合
C_16 = (245, 149, 99)
C_32 = (246, 124, 95)
C_喵 = (246, 94, 59)
C_128 = (237, 207, 114)
C_256 = (237, 204, 98)
C_512 = (237, 200, 80)
C_1024 = (237, 197, 63)
C_2048 = (225, 187, 0)
#第1次添加程序结束--------------------------------

#第2次添加程序开始--------------------------------
#定义一个函数,用来选取对应数字的颜色---------------


def get_color(n):  # 参数是数字n,依靠n来选取对应颜色
    n_t = 0  # 设定一个变量,用来确定颜色列表的第几个位置
    for i in range(1, 12):  # 因为2到2048一共11个数字
        #所以range(1,12)
        if n >> i == 1:  # 因为2到2048这几个数字全是2的次方
            #所以利用二进制右移,移1次变成1的话
            #说明是第一个数字,也就是2
            n_t = i  # 以此类推,移几次表示是第几个数字
    #接下来定义一个列表,按顺序把背景色、数字色、字体色放进去(第一个是背景色,最后一个是字体)
    color = [DEFAULT, C_2, C_4, C_8, C_16, C_32, C_喵,
             C_128, C_256, C_512, C_1024, C_2048, C_FONT]
    return color[n_t]  # 根据前面的计算结果,从列表里取出对应的方块色
#第2次添加程序结束----------------------------------------------------------------------

#第3次添加程序开始-----------------------------------------------------------------
#定义一个类,用于画面内容控制--------------------------------------------------------


class Map:  # 这个类取名叫Map(注意首字母大写),表示整个画面内的内容
    def __init__(self, size):  # 初始化函数,参数是方格的尺寸
        self.size = size  # 把传入的参数,给类的size变量
        #接下来创建一个二维数组,这里使用了列表生成式,第一个[0 for i in range(size)]
        #表示创建一个有四个元素(size我们之前定义了4)的列表,里面的每一个元素都是0
        #第二个以此参考,就是再创建一个4元素列表,每个元素都是刚才创建的第一个列表
        #这样的话,就形成了4*4二维数组,每个数值都是0(如果看不懂,可以用循环嵌套创建)
        self.map = [[0 for i in range(size)] for i in range(size)]
        self.score = 0  # 创建用于计分的变量
        self.is_move = 0  # 创建用于判断是否移动的变量
        self.add()  # 随机添加一个数字2或4,这个add函数随后就定义
        self.add()  # 再添加一个,因为起始的时候应该有两个数字

    def add(self):  # 定义一个添加函数,专门用来随机添加数字2和4
        #注意,在前面引入random模块,因为马上要用随机功能
        while True:  # 在没有成功随机出数字之前,应该一直循环,所以True
            pos = random.randint(0, self.size*self.size-1)  # 0-15(含15)中随机出一个数
            #因为4*4=16
            flag = self.map[pos//self.size][pos % self.size]  # 取得这个数字对应位置的数值
            #这里是4*4个格子,第一个//地板除,取得整除的整数部分,表示第几排,第二个取余%
            #表示取得整除之后的余数,也就是那一排的第几个(这个比较绕,好好画一下想想)
            if flag == 0:  # 如果这个位置的数是0,那说明这个位置可以给新数,不然就要再循环
                num = random.randint(0, 3)  # 0-3中随机出一个数,用来决定要添加2还是4
                n = 2  # 默认新数是2
                if num == 0:  # 如果出现的随机数是0(概率4分之1),那就添加4
                    n = 4
                self.map[pos//self.size][pos % self.size] = n  # 把新数给这个位置
                self.score += n  # 每次生成都要加对应的分数
                break  # 完成之后就可以中断循环了
#第3次添加程序结束----------------------------------------------------------------------

#第6次添加程序开始,注意缩进,这部分仍然属于上面的Map类------------------------------------
#添加一个判定失败的函数,注意缩进--------------------------------------------------------
    def failed(self):
        for i in self.map:  # 利用循环嵌套来访问每一个元素
            for j in i:  # 只要有0,说明还有空位,就不算失败
                if j == 0:
                    return False  # false表示没有失败,true表示失败了
        for i in range(0, self.size):  # 还有一种情况,就是虽然填满了
            for j in range(0, self.size):  # 但是还有相邻位置可合成
                #这种需要多个条件结合判断,上下左右相邻的两个能不能合成
                #这里面的i-1,j-1等只是为了防止访问超出,不要超出最外层
                if (i-1 >= 0 and self.map[i][j] == self.map[i-1][j])\
                        or (j-1 >= 0 and self.map[i][j] == self.map[i][j-1])\
                        or (i+1 < self.size and self.map[i][j] == self.map[i+1][j])\
                        or (j+1 < self.size and self.map[i][j] == self.map[i][j+1]):
                    return False
        return True  # 如果上面两种情况都没出现,那就返回true,表示游戏失败
#第6次添加程序结束--------------------------------------------------------------------

#第7次添加程序开始,注意缩进,这部分仍然属于上面的Map类------------------------------------
#添加一个检测某个数字存不存在的函数,注意缩进---------------------------------------------
    def check(self, num):  # 参数是你需要检测的数字,num
        for i in self.map:  # 还是通过嵌套访问每一个数字
            for j in i:
                if j == num:  # 如果有相等的,说明这个数字存在
                    return True  # 那么返回true
        return False  # 不存在就返回false
#第7次添加程序结束---------------------------------------------------------------------

#第8次添加程序开始,注意缩进,这部分仍然属于上面的Map类------------------------------------
#添加一个让整个数字方阵往左移动的函数,注意缩进--------------------------------------------
    def move_to_left(self):
        changed = False  # 每次移动完之后,更新是否移动,默认是不移动
        for a in self.map:  # 利用循环,取出每一行,存入a
            b = []  # 新建一个空列表,用于临时存合成后的
            last = 0  # 新建一个变量,用来存上一个元素,这样才好比较
            for v in a:  # 从每一行里面读取每一个元素,开始判断
                if v != 0:  # 如果这个元素不是0,就添加到b中
                    if v != last:  # 如果读取的数和上一个元素不等
                        b.append(v)  # 那么将v添入b列表,这个是不可合成的
                        last = v  # 然后更新last,用于下一次合成判断
                    else:  # 否则的话,那就说明v和last相等,可以合成
                        b.append(b.pop()*2)  # 这时直接让b的最后弹出一个并*2
                        last = 0  # 更新last,已经合成了,所以这个位置应该变成0
            b += [0]*(self.size-len(b))  # 合成完之后,在末尾添加0,凑足4个元素
            for i in range(0, self.size):  # 每次合成完毕之后,检查和原来的是否相同
                if a[i] != b[i]:  # 如果和原来的不同,说明移动成功
                    changed = True  # 只要有一行移动成功,说明左移成功,返回true
            a[:] = b  # 每一行移动完毕后,把移动后的b列表传给原来的a列表,进行替换
        return changed  # 所有行都移动完毕之后,返回移动是否成功的结果
#第8次添加程序结束---------------------------------------------------------------------

#第9次添加程序开始,注意缩进,这部分仍然属于上面的Map类------------------------------------
#添加一个让整个数字方阵顺时针90度的函数,注意缩进------------------------------------------
    def change(self):
        #顺时针旋转90度,就是用reversed倒序访问每一排,然后每j个喵来,看图理解好一点
        self.map = [[self.map[i][j] for i in reversed(
            range(self.size))] for j in range(self.size)]
#第9次添加程序结束---------------------------------------------------------------------

#第10次添加程序开始,注意缩进,这部分仍然属于上面的Map类------------------------------------
#添加四个上下左右移动方块的函数,注意缩进--------------------------------------------------
    def move_left(self):  # 左移不需要旋转,只要能移,就直接移
        if self.move_to_left():  # 判断能不能移
            self.is_move = 1  # 能移就标记为移动了
            self.add()  # 移动完毕之后需要添加一个数

    def move_up(self):  # 上移,就比左移多了旋转操作,先顺时针3次
        self.change()
        self.change()
        self.change()
        if self.move_to_left():  # 然后和左移一样,合并
            self.is_move = 1
            self.add()
        self.change()  # 完事再移一次就恢复了,其他方向都类似了

    def move_right(self):  # 右移,先旋转两次,移动后再旋转两次
        self.change()
        self.change()
        if self.move_to_left():  # 然后和左移一样,合并
            self.is_move = 1
            self.add()
        self.change()
        self.change()

    def move_down(self):
        self.change()
        if self.move_to_left():  # 然后和左移一样,合并
            self.is_move = 1
            self.add()
        self.change()
        self.change()
        self.change()
#第10次添加程序结束-------------------------------------------------------


#第4次添加程序开始-------------------------------------------------------
#定义一个显示函数-------------------------------------------------------
def display(map, screen):  # 参数是我们定义的画面类实例和屏幕信息
    block_font = pygame.font.Font(None, 86)  # 设定方块上的数字字体和大小
    score_font = pygame.font.Font(None, 86)  # 分数字体和大小
    screen.fill(DEFAULT)  # 使用默认颜色填充屏幕
    for i in range(map.size):  # 使用循环嵌套来访问每一个位置的数值
        for j in range(map.size):
            block = pygame.Surface((LENGTH, LENGTH))  # 定义方块表面
            block.fill(get_color(map.map[i][j]))  # 调用函数获取对应颜色
            #接下来定义字喵置、表面、外接矩形等信息
            font_surf = block_font.render(str(map.map[i][j]), True, C_FONT)
            #这里因为要显示文字,是字符串,所以要str转换一下
            font_rect = font_surf.get_rect()
            font_rect.center = (j*LENGTH+LENGTH/2, LENGTH*i+LENGTH/2)
            #方块的中心坐标注意除以2
            screen.blit(block, (j*LENGTH, i*LENGTH))  # 显示对应方块
            if map.map[i][j] != 0:  # 如果对应位置不是0,那么应该显示数字
                screen.blit(font_surf, font_rect)  # 把对应数字放这里
    #上面循环嵌套已经显示完4*4所有方格,下面来显示分数
    score_surf = score_font.render('score:'+str(map.score), True, C_FONT)
    score_rect = score_surf.get_rect()
    score_rect.center = (LENGTH*SIZE/2, LENGTH*SIZE+SCORE_HEIGHT/2)
    screen.blit(score_surf, score_rect)
    pygame.display.update()  # 设定完毕,更新画面
#第4次添加程序结束--------------------------------------------------------------

#定义主逻辑------------------------------


def main():
    pygame.init()  # 初始化界面
    screen = pygame.display.set_mode(
        (LENGTH*SIZE, LENGTH*SIZE+SCORE_HEIGHT))  # 设置屏幕尺寸
    pygame.display.set_caption("2048")  # 设置窗体标题
    clock = pygame.time.Clock()  # 用于控制帧率
    #第5次添加程序开始----------------------------------------------
    #创建map实例,并调用显示
    map = Map(SIZE)  # 初始化实例,用之前定义的尺寸常量
    display(map, screen)  # 调用显示函数,参数就是上面创建的
    #第5次添加程序结束----------------------------------------------

    #第11次添加程序开始---------------------------------------------
    #添加键盘检测相关内容-------------------------------------------
    while not map.failed():  # 如果没有失败,就一直检测键盘
        for event in pygame.event.get():  # 获取事件情况
            if event.type == QUIT:  # 处理退出情况
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN:  # 键盘按下的话,准备判断
                keys = pygame.key.get_pressed()  # 获取键值
                map.is_move = 0  # 记录是否移动
                if keys[K_w]:  # 如果是上键
                    map.move_up()  # 就调用上移函数
                elif keys[K_s]:  # 之后的都类似
                    map.move_down()
                elif keys[K_d]:
                    map.move_right()
                elif keys[K_a]:
                    map.move_left()
            elif event.type == KEYUP:  # 按键松开之后,就更新画面,使用子线程来更新
                t = threading.Thread(target=display, args=(map, screen))
                #创建一个线程,目标函数是显示,参数是map和screen信息,可回看上次课
                t.setDaemon(True)  # 设置线程
                t.start()  # 开始执行
        if map.is_move == 1:  # 每次键盘检测结束后,都判断是否移动了
            if map.check(2048):  # 如果移动了,检测有没有2048出现
                break  # 出现了说明游戏结束,赢了,循环终止
            time.sleep(0.01)  # 每次判定完毕,留一点时间让画面刷新
    #第11次添加程序结束-----------------------------------------------------------

    #第12次添加程序开始-----------------------------------------------------------
    #胜负情况显示处理-------------------------------------------------------------
    result = 'You Failed!'  # 默认情况下,上面循环结束,游戏失败了
    if map.check(2048):  # 如果上面循环结束的情况下,检测到2048
        result = 'You Win!'  # 那这时说明游戏你赢了
    screen.fill(DEFAULT)  # 游戏结算界面直接默认底色
    map_font = pygame.font.Font(None, 86)  # 设置一下字体什么的
    font_surf = map_font.render(result, True, C_FONT)
    font_rect = font_surf.get_rect()
    font_rect.center = (SIZE*LENGTH/2, SIZE*LENGTH/2)
    screen.blit(font_surf, font_rect)
    pygame.display.update()
    #第12次添加结束----------------------------------------------------------------
    while True:
        #检测是否退出
        clock.tick(5)  # 控制帧率为5
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()


#调用主要逻辑-----------------------------
if __name__ == "__main__":
    main()
#----------------------------------------

 

【提示】

部分含有Python第三方库相关内容的作品,在海龟编辑器网页端无法运行哦!如遇到这种情况,可以打开下面的链接,下载海龟编辑器客户端:

https://python.codemao.cn


回复

上一页1 页 / 共 1下一页
射手座的丁丁然射手座的丁丁然

请多多点赞,评论emotion_编程猫_谢谢老板

点赞0


评论