Python制作2048小游戏
文章目录
- Python制作2048小游戏
- 前言
- 一、关于2048小游戏
- 二、设计
- 1.引入库
- 2.调出窗口代码
- 3.矩阵的展示
- 4.各方向数据计算准备
- 5.计算
- 6.数据的视角返回
- 7.随机生成一个数字
- 8.函数按钮的添加
- 三、完整代码
- 总结
前言
今天我们设计python实现经典的2048小游戏,实现的方式比较浅显易懂,同样也是个人思路的实现,走通了游戏的流程,前期的操作还是比较快,当运行到后期由于数字比较大,导致运行时间比较长,会出现延迟感,有一种卡顿的感觉,主要是代码写得比较基础,所以运行就比较耗时间。
一、关于2048小游戏
2048百度百科:https://baike.baidu.com/item/2048/13383511#1
网上找的一个在线版:https://cn.newdoku.com/2048.php。
大家可以去耍一下,可能会上瘾,哈哈!
二、设计
1.引入库
代码如下:
import tkinter as tk #窗口库
import numpy as np
import random #随机函数,用于每次操作后的新数字添加
numpy库只是为了调试初期显示每次计算后的矩阵的展示,可以更加方便的观察。
2.调出窗口代码
初始代码准备:
首先将窗口调出,调好颜色,设计好大小。
num和num_color分别存储了数字和对应颜色,暂时只设计了0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,当数字超出2048时程序会出错,修改一下这两个列表里面的数据即可。
win = tk.Tk() # 创建窗口
win.title("2048小游戏") # 窗口名称
win.geometry("410x600") # 窗口的大小
win.resizable(width=True, height=True) # 窗口大小可变,True可变
# 创建画布
canvas = tk.Canvas(win, width=410, height=410, bg="tan") #画布
num_color = ['burlywood', 'oldlace', 'moccasin', 'orange', 'coral', 'tomato', 'orangered', 'khaki', 'gold',
'goldenrod', 'lightgreen', 'limegreen'] #每一个数字的方格设置对应一种颜色
num = [0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] #判断数字
a = [[0, 2, 0, 0], [2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]#初始矩阵,可以随机产生一个,只需要调用随机生成的几行代码
num_color是每一个数字对应的位置的方块的颜色,增强直观的视觉效果,增强游戏体验,二维列表a是我所设置的初始矩阵,当然我们也可以设计随机产生数字和位置。方法也比较简单,只需要将后文的产生随机数字的代码调用过来即可。
3.矩阵的展示
将矩阵展示在画布上:
for i in range(0, 4):
for j in range(0, 4):
num_index=num.index(a[i][j])
if a[i][j] == 0:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill='burlywood',
outline='wheat')
else:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill=num_color[num_index],
outline='wheat') # outline='blue'
canvas.create_text(100 * j + 55, 100 * i + 55, text=a[i][j], font=('Time New Roman', 35),
fill='maroon')
canvas.pack()
通过index
找到元素在num列表中的位置,对应num_color的地址就可以将每个数字对应的颜色调出来,进行展示。加入循环来找也可以,在较小的数据范围里,for
查找和index
查找时间的消耗相差不大,当数据比较大的时候index
会更快一些。
canvas.create_rectangle
通过画布画矩形,填充颜色,实现每个数字一个颜色方框,实现数字的直观视觉效果。
canvas.create_text
添加文字到指定位置,定义大小,颜色等。
代码添加在每个函数的后面,将计算好的矩阵展示出来。
4.各方向数据计算准备
我是将所有的数据变化到同一个计算方向上,水平方向,从左向右排列,再进行计算。
将数据都转换到上图的计算方向之后再进行计算,通过实现矩阵数据的位置转化,再进行统一的计算会降低设计难度。我们只需设计三个方向的矩阵转化。
数据向上计算前的转化:
数据向上计算那么位置数据考虑先后为:1,5,9,13以此类推,按照列方向计算
矩阵变化为行方向:
def go_up():
global a #设为全局变量,实现不同按钮之间可以循环使用a
b=[] #调用列表b进行数据的变换
for i in range(0,4):
b.append([])
for j in range(0,4):
b[i].append(a[j][i])
数据向下:
数据向下计算那么位置数据考虑先后从左向右展示为:13,9,5,1以此类推,按照列方向计算
def go_down():
global a
b=[[],[],[],[]]
for i in range(0,4):
for j in range(3,-1,-1):
b[i].append(a[j][i])
数据向右:
数据向右计算那么位置数据考虑先后为:4,3,2,1以此类推,按照行方向计算
def go_right():
global a
b=[]
for i in range(0,4):
b.append([])
for j in range(3,-1,-1):
b[i].append(a[i][j])
我们可以通过调用语句print(np.array(b))
将二维列表以矩阵的方式打印出来,可以更加直观,便于我们分析程序。
5.计算
我们计算的方向是从左向右计算,当然其实就是数据向左挤压,当数据一样时就进行叠加。
- 当计算时数据之间有空位,我们首先处理数据,使得数据紧凑,保证数据之间没有空位,方便进行判断叠加。
- 当有多个数据都一样时,我们从左到右依次叠加,叠加以后的新数据暂不与未叠加的数据叠加。
比如:“2,2,4,2”这四个数字的叠加将展示为:4,4,2 ,_
操作时只有2,2可以叠加,
若再次进行此方向的叠加结果为:8,2,_ ,_
数据集中:我们需要将空位后移,在矩阵中我们使用0代表空位,方便我们的计算。集中的方法是:从前向后遍历,遇到0则删除,同时在尾部添加0,保证不产生遗漏,四个位置都进行遍历。
#首先将数据进行集中,非零数据靠左,方便进行计算。
#采用的方法是,从左向右遍历,若有零则删去第一个零,末尾添加一个零,代表零的后移。
for i in range(0,4):
for j in range(0,4):
if b[i][j]==0:
b[i].remove(0)
b[i].append(0)
数据计算:每一行四个数据,我将每种情况考虑一遍出现如下情况:
例:a,b,c,d为其中一行,那么可能出现的计算情况(数据已经集中完毕),一共会出现如下五种结果:
那么以此类推四次,即可实现这个矩阵的计算。
for i in range(0, 4):
if b[i][0] == b[i][1]:
b[i][0] = 2 * b[i][0]
if b[i][2] == b[i][3]:
b[i][1] = 2 * b[i][2]
b[i][2] = 0
b[i][3] = 0
else:
b[i][1] = b[i][2]
b[i][2] = b[i][3]
b[i][3] = 0
elif b[i][1] == b[i][2]:
b[i][1] = 2 * b[i][1]
b[i][2] = 0
# b[i][3] = 0
elif b[i][1] != b[i][2]:
if b[i][2] == b[i][3]:
b[i][2] = 2 * b[i][2]
b[i][3] = 0
6.数据的视角返回
a矩阵是全局变量,每次操作都需要使用a矩阵,所以在矩阵计算完以后,我们需要将数据恢复到计算前的视图,我们还是采用之前的转化思路:首先将a矩阵清空,将b矩阵转化,元素添加至a中。
通过如下代码,我们可以实现矩阵的变化和再变化。为我们的计算提供便利条件。在中间添加计算的代码即可实现2048游戏的核心计算代码。
import numpy as np
a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]
print("初始矩阵\n",np.array(a))
def go_up():
global a #设为全局变量,实现不同按钮之间可以循环使用a
b=[]
for i in range(0,4):
b.append([])
for j in range(0,4):
b[i].append(a[j][i])
a = []
for i in range(0, 4):
a.append([])
for j in range(0, 4):
a[i].append(b[j][i])
print("向上累加前转化为\n",np.array(b))
print("输出矩阵\n",np.array(a))
def go_down():
global a
b=[]
for i in range(0,4):
b.append([])
for j in range(3,-1,-1):
b[i].append(a[j][i])
a = []
i = 0
for j in range(3, -1, -1):
a.append([])
for k in range(0, 4):
a[i].append(b[k][j])
i = i + 1
print("向下累加前转化为\n",np.array(b))
print("输出矩阵\n",np.array(a))
def go_right():
global a
b=[]
for i in range(0,4):
b.append([])
for j in range(3,-1,-1):
b[i].append(a[i][j])
a = []
for i in range(0, 4):
a.append([])
for j in range(3, -1, -1):
a[i].append(b[i][j])
print("向右累加前转化为\n",np.array(b))
print("输出矩阵\n",np.array(a))
go_up()
go_down()
go_right()
7.随机生成一个数字
每次操作以后需要随机选择一个空位替换为一个随机数字2或4。
设计思路:我们需要随机选择一个空位,再产生一个随机数字2或4替换该空位,随机位置的直接产生暂时没想到。我们转化思路,将位置分别添加进x,y列表内,在小于列表长度的正整数范围内产生一个数字,这个位置对应两个列表里面的数字,产生一个空位,等同于产生一个随机空位。再随机产生一个数字2或4进行替换。
r_i = []
r_j = []
for i in range(0, 4):
for j in range(0, 4):
if a[i][j] == 0:
r_i.append(i)
r_j.append(j)
index_random = random.randint(0, len(r_i) - 1) # 产生一个元素为零的随机地址
index_num = random.choice([2, 4]) # 随机产生一个数字2或4
a[r_i[index_random]][r_j[index_random]] = index_num # 替换对应位置的数字
当然这一步操作在计算之后画图之前即可。
8.函数按钮的添加
原计划是设计按钮的监听,绑定四个方位按键,使操作更方便,后来在操作中发现,按钮的监听占据了进程,导致后续的操作无法进行,没找的解决的办法,后来就妥协了,设计了按钮使用鼠标点击操作。比较原始的操作,如果有解决的办法或者相似的经历,并且已经解决了,可一起讨论,还请不吝赐教,定虚心请教。
按钮的代码比较简单,代码如下:
button1 = tk.Button(win, text="上", font=('楷体', 20), command=lambda:up())
button1.pack()
button2 = tk.Button(win, text="下", font=('楷体', 20), command=lambda:down())
button2.pack()
button1.bind("up", lambda:up())
button3 = tk.Button(win, text="左", font=('楷体', 20), command=lambda:left())
button3.pack()
button4 = tk.Button(win, text="右", font=('楷体', 20), command=lambda:right())
button4.pack()
如果没有lambda
会出现未点击按钮就运行一次函数。
三、完整代码
import tkinter as tk
import numpy as np #打印矩阵便于分析
import random #随机函数
win = tk.Tk() # 创建窗口
win.title("2048小游戏") # 窗口名称
win.geometry("410x600") # 窗口的大小
win.resizable(width=True, height=True) # 窗口大小可变,True可变
# 创建画布
canvas = tk.Canvas(win, width=410, height=410, bg="tan") #画布
num_color = ['burlywood', 'oldlace', 'moccasin', 'orange', 'coral', 'tomato', 'orangered', 'khaki', 'gold',
'goldenrod', 'lightgreen', 'limegreen'] #每一个数字的方格设置对应一种颜色
num = [0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] #判断数字
a = [[0, 2, 0, 0], [2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]#初始矩阵,可以随机产生一个,只需要调用随机生成的几行代码
#循环打印出画布,将数字矩阵显示,对应的颜色数字
for i in range(0, 4):
for j in range(0, 4):
num_index=num.index(a[i][j])
if a[i][j] == 0:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill='burlywood',
outline='wheat')
else:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill=num_color[num_index],
outline='wheat') # outline='blue'
canvas.create_text(100 * j + 55, 100 * i + 55, text=a[i][j], font=('Time New Roman', 35),
fill='maroon')
canvas.pack()
print(np.array(a))
def up():
global a #设为全局变量,实现不同按钮之间可以循环使用a
b=[]
# 我们将所有的需要计算的矩阵,计算方向全都转化为在行方向,从左到右的计算方式进行,简化计算
#不同的方向转化的方式也不相同,有难有易
for i in range(0,4):
b.append([])
for j in range(0,4):
b[i].append(a[j][i])
# print('切换',np.array(b))
#在每一个方向的计算之前都进行转化,之后就可以使用下面的计算方法
#首先将数据进行集中,非零数据靠左,方便进行计算。
#采用的方法是,从左向右遍历,若有零则删去第一个零,末尾添加一个零,代表零的后移。
for i in range(0,4):
for j in range(0,4):
if b[i][j]==0:
b[i].remove(0)
b[i].append(0)
#从左向右计算将每一种情况都检查一遍,进行计算。
for i in range(0, 4):
if b[i][0] == b[i][1]:
b[i][0] = 2 * b[i][0]
if b[i][2] == b[i][3]:
b[i][1] = 2 * b[i][2]
b[i][2] = 0
b[i][3] = 0
else:
b[i][1] = b[i][2]
b[i][2] = b[i][3]
b[i][3] = 0
elif b[i][1] == b[i][2]:
b[i][1] = 2 * b[i][1]
b[i][2] = 0
elif b[i][1] != b[i][2]:
if b[i][2] == b[i][3]:
b[i][2] = 2 * b[i][2]
b[i][3] = 0
# print('计算', np.array(a))
#计算完以后,我们需要还原之前转化的矩阵,还原到他应该展示的视角。并且还给a,保证后续的操作顺利
a=[]
for i in range(0,4):
a.append([])
for j in range(0,4):
a[i].append(b[j][i])
# 随机选择一个元素为零的地址,随机选择2或4,进行替换
r_i = []
r_j = []
for i in range(0, 4):
for j in range(0, 4):
if a[i][j] == 0:
r_i.append(i)
r_j.append(j)
index_random = random.randint(0, len(r_i) - 1) # 产生一个元素为零的随机地址
index_num = random.choice([2, 4]) # 随机产生一个数字2或4
a[r_i[index_random]][r_j[index_random]] = index_num # 替换对应位置的数字
print(np.array(a))
for i in range(0, 4):
for j in range(0, 4):
num_index = num.index(a[i][j])
if a[i][j] == 0:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill='burlywood',
outline='wheat')
else:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100,
fill=num_color[num_index],
outline='wheat') # outline='blue'
canvas.create_text(100 * j + 55, 100 * i + 55, text=a[i][j], font=('Time New Roman', 35),
fill='maroon')
def down():
global a
b=[[],[],[],[]]
for i in range(0,4):
for j in range(3,-1,-1):
b[i].append(a[j][i])
# print('切换',np.array(b))
for i in range(0,4):
for j in range(0,4):
if b[i][j]==0:
b[i].remove(0)
b[i].append(0)
# print('集中', np.array(b))
for i in range(0, 4):
if b[i][0] == b[i][1]:
b[i][0] = 2 * b[i][0]
if b[i][2] == b[i][3]:
b[i][1] = 2 * b[i][2]
b[i][2] = 0
b[i][3] = 0
else:
b[i][1] = b[i][2]
b[i][2] = b[i][3]
b[i][3] = 0
elif b[i][1] == b[i][2]:
b[i][1] = 2 * b[i][1]
b[i][2] = 0
elif b[i][1] != b[i][2]:
if b[i][2] == b[i][3]:
b[i][2] = 2 * b[i][2]
b[i][3] = 0
# print('计算', np.array(b))
a=[]
i=0
for j in range(3,-1,-1):
a.append([])
for k in range(0,4):
a[i].append(b[k][j])
i=i+1
r_i = []
r_j = []
for i in range(0, 4):
for j in range(0, 4):
if a[i][j] == 0:
r_i.append(i)
r_j.append(j)
index_random = random.randint(0, len(r_i) - 1) # 产生一个元素为零的随机地址
index_num = random.choice([2, 4]) # 随机产生一个数字2或4
a[r_i[index_random]][r_j[index_random]] = index_num # 替换对应位置的数字
print(np.array(a))
for i in range(0, 4):
for j in range(0, 4):
num_index = num.index(a[i][j])
if a[i][j] == 0:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill='burlywood',
outline='wheat')
else:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100,
fill=num_color[num_index],
outline='wheat') # outline='blue'
canvas.create_text(100 * j + 55, 100 * i + 55, text=a[i][j], font=('Time New Roman', 35),
fill='maroon')
def left():
global a
b=a
# print('切换',np.array(b))
for i in range(0,4):
for j in range(0,4):
if b[i][j]==0:
b[i].remove(0)
b[i].append(0)
for i in range(0, 4):
if b[i][0] == b[i][1]:
b[i][0] = 2 * b[i][0]
if b[i][2] == b[i][3]:
b[i][1] = 2 * b[i][2]
b[i][2] = 0
b[i][3] = 0
else:
b[i][1] = b[i][2]
b[i][2] = b[i][3]
b[i][3] = 0
elif b[i][1] == b[i][2]:
b[i][1] = 2 * b[i][1]
b[i][2] = 0
elif b[i][1] != b[i][2]:
if b[i][2] == b[i][3]:
b[i][2] = 2 * b[i][2]
b[i][3] = 0
# print('计算', np.array(a))
a=b
r_i = []
r_j = []
for i in range(0, 4):
for j in range(0, 4):
if a[i][j] == 0:
r_i.append(i)
r_j.append(j)
index_random = random.randint(0, len(r_i) - 1) # 产生一个元素为零的随机地址
index_num = random.choice([2, 4]) # 随机产生一个数字2或4
a[r_i[index_random]][r_j[index_random]] = index_num # 替换对应位置的数字
print(np.array(a))
for i in range(0, 4):
for j in range(0, 4):
num_index = num.index(a[i][j])
if a[i][j] == 0:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill='burlywood',
outline='wheat')
else:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100,
fill=num_color[num_index],
outline='wheat') # outline='blue'
canvas.create_text(100 * j + 55, 100 * i + 55, text=a[i][j], font=('Time New Roman', 35),
fill='maroon')
def right():
global a
b=[]
for i in range(0,4):
b.append([])
for j in range(3,-1,-1):
b[i].append(a[i][j])
# print('切换',np.array(b))
for i in range(0,4):
for j in range(0,4):
if b[i][j]==0:
b[i].remove(0)
b[i].append(0)
for i in range(0, 4):
if b[i][0] == b[i][1]:
b[i][0] = 2 * b[i][0]
if b[i][2] == b[i][3]:
b[i][1] = 2 * b[i][2]
b[i][2] = 0
b[i][3] = 0
else:
b[i][1] = b[i][2]
b[i][2] = b[i][3]
b[i][3] = 0
elif b[i][1] == b[i][2]:
b[i][1] = 2 * b[i][1]
b[i][2] = 0
elif b[i][1] != b[i][2]:
if b[i][2] == b[i][3]:
b[i][2] = 2 * b[i][2]
b[i][3] = 0
# print('计算', np.array(a))
a=[]
for i in range(0,4):
a.append([])
for j in range(3,-1,-1):
a[i].append(b[i][j])
r_i = []
r_j = []
for i in range(0, 4):
for j in range(0, 4):
if a[i][j] == 0:
r_i.append(i)
r_j.append(j)
index_random = random.randint(0, len(r_i) - 1) # 产生一个元素为零的随机地址
index_num = random.choice([2, 4]) # 随机产生一个数字2或4
a[r_i[index_random]][r_j[index_random]] = index_num # 替换对应位置的数字
print(np.array(a))
for i in range(0, 4):
for j in range(0, 4):
num_index = num.index(a[i][j])
if a[i][j] == 0:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100, fill='burlywood',
outline='wheat')
else:
canvas.create_rectangle(100 * j + 10, 100 * i + 10, 100 * j + 100, 100 * i + 100,
fill=num_color[num_index],
outline='wheat') # outline='blue'
canvas.create_text(100 * j + 55, 100 * i + 55, text=a[i][j], font=('Time New Roman', 35),
fill='maroon')
canvas.pack()
#上下左右按钮,实现操作的调用
button1 = tk.Button(win, text="上", font=('楷体', 20), command=lambda:up())
button1.pack()
button2 = tk.Button(win, text="下", font=('楷体', 20), command=lambda:down())
button2.pack()
button1.bind("up", lambda:up())
button3 = tk.Button(win, text="左", font=('楷体', 20), command=lambda:left())
button3.pack()
button4 = tk.Button(win, text="右", font=('楷体', 20), command=lambda:right())
button4.pack()
win.mainloop()
总结
代码的设计是比较简单的,采用最基础的代码,可能想法比较绕,导致代码比较长,写到最后发现少设计了几个细节:
一个是,当某一方向上的操作没有数据的叠加且数字都没有移动时,应该不产生新的数字,这个我们可以判断没有进行操作前的矩阵和叠加以后且转化为相同视角的矩阵是否相等,若相等则说明没有数字叠加且没有数字移动。
还有就是没有设计达到2048的提示和矩阵填满了不为零的数字且任意相邻的数字不同是的游戏结束提示。这个也好解决:成功提示只需判断2048是否在矩阵内即可。游戏失败的结束提示只需要判断空位的列表是否为零且任意相邻数字不相同即可,满足就弹窗提示结束游戏。