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

【作品介绍】
完整版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