前言:
最近在学习Python的过程中,在github上看见别人实现的一个小游戏2048,感觉很有趣,这里记录下实现过程。
界面效果图:
下面我们具体来实现,首先导入我们需要的库,这里呢导入两个库,一个用来产生随机数,另一个用来做数学运算
import random
import math
然后这里声明下使用新式类,至于什么是新式类这里就不提了,但是现在python3下的类都是新式类,也就是说都继承至object,所以下面这行代码可要可不要
__mataclass__ = type # 使用新式类
接下来定义一个类用来封装我们的游戏地图
class map2048():
在这个类里,我们需要定义许多方法来使用,首先是重新设置游戏数据对应的方法
# 重新设置游戏数据
def reset(self):
self.__row = 4 # 行数
self.__col = 4 # 列数
self.data = [
[0 for x in range(self.__col)]
for y in range(self.__row)
]
# self.data = [[x + 4 * y for x in range(self.__col)]
# for y in range(self.__row)]
# self.data = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
self.fill2()
self.fill2()
然后在类初始化时调用上面的方法来保证游戏打开时,数据被重新设置
def __init__(self):
self.reset()
然后是获取没有数字的位置的个数,这样当我们按下按键时,我们可以生成对应的随机数来填充对应的空值
# 获取没有数字的位置的个数
def get_space_count(self):
"""
获取没有数字的方格的数量
"""
count = 0
for r in self.data:
count += r.count(0)
return count
然后获取游戏的分数,这里需要使用到math库里的log函数来生成分数
# 获取游戏的分数。
def get_score(self):
s = 0
for r in self.data:
for c in r:
s += 0 if c < 4 else c * int((math.log(c, 2) - 1.0))
return s
接下来便是填充2到空位置,如果填度成功返回True,如果已满,则返回False。这里我们首先获取有多少空位置,然后产生随机数表示该空值所在位置,再填充2到对应的位置上
# 填充2到空位置,如果填度成功返回True,如果已满,则返回False
def fill2(self):
blank_count = self.get_space_count()
if 0 == blank_count:
return False
# 生成随机位置
pos = random.randrange(0, blank_count)
offset = 0
for r in self.data:
for ci in range(self.__col):
if 0 == r[ci]:
if offset == pos:
r[ci] = 2
return True
offset += 1
然后判断游戏是否结束,这里有3种情况游戏没有结束,水平方向还有0,水平方向有两个相邻元素相同,竖直方向有两个相邻元素相同。
# 判断游戏是否结束
def is_gameover(self):
for r in self.data:
# 如果水平方向还有0,则游戏没有结束
if r.count(0):
return False
# 水平方向如果有两个相邻的元素相同,则没有游戏结束
for i in range(self.__col - 1):
if r[i] == r[i + 1]:
return False
for c in range(self.__col):
# 竖直方向如果有两个相邻的元素相同,则没有游戏结束
for r in range(self.__row - 1):
if self.data[r][c] == self.data[r + 1][c]:
return False
# 以上都没有,则游戏结束
return True
然后是游戏的左移动,主要分为3步,首先所有数字左移填补空格,然后检测是否发生碰撞,也就是相同数字合并,这时由于可能的碰撞会产生新的空格,所以需要再将所有数字左移来填补空格。
def left(self):
# moveflag 是否成功移动数字标志位,如果有移动则为真值,原地图不变则为假值
moveflag = False
# 将所有数字向左移动来填补左侧空格
for times in range(self.__col - 1):
for r in self.data:
for c in range(self.__col - 1):
if 0 == r[c]:
moveflag = True
r[c] = r[c + 1]
r[c + 1] = 0
# 判断是否发生碰幢,如果有碰撞则合并,合并结果靠左,右侧填充空格
for r in self.data:
for c in range(self.__col - 1):
if r[c] == r[c + 1]:
moveflag = True
r[c] *= 2
r[c + 1] = 0
# 再将所有数字向左移动来填补左侧空格
for times in range(self.__col - 1):
for r in self.data:
for c in range(self.__col - 1):
if 0 == r[c]:
moveflag = True
r[c] = r[c + 1]
r[c + 1] = 0
return moveflag
游戏右移操作,与左移刚好相反,所以可以直接使用数组对应的reverse()
方法,注意这里是水平取反,所以取数组data的每一行来执行reverse()
方法
# 游戏右移操作
def right(self):
for r in self.data:
r.reverse()
moveflag = self.left()
for r in self.data:
r.reverse()
return moveflag
游戏上移操作,与左移相对应,只是这里变化的是横坐标。
# 游戏上移操作
def up(self):
# moveflag 是否成功移动数字标志位,如果有移动则为真值,原地图不变则为假值
moveflag = False
# 将所有数字向上移动来填补上面空格
for times in range(self.__row - 1):
for c in range(self.__col):
for r in range(self.__row - 1):
if 0 == self.data[r][c]:
moveflag = True
self.data[r][c] = self.data[r + 1][c]
self.data[r + 1][c] = 0
# 判断是否发生碰幢,如果有碰撞则合并,合并结果靠上,下面填充空格
for c in range(self.__col):
for r in range(self.__row - 1):
if self.data[r][c] == self.data[r + 1][c]:
moveflag = True
self.data[r][c] *= 2
self.data[r + 1][c] = 0
# 再将所有数字向上移动来填补上面空格
for times in range(self.__row - 1):
for c in range(self.__col):
for r in range(self.__row - 1):
if 0 == self.data[r][c]:
moveflag = True
self.data[r][c] = self.data[r + 1][c]
self.data[r + 1][c] = 0
return moveflag
游戏下移操作,同样是取反,只是这里是数组的列取反。
# 游戏下移操作
def down(self):
self.data.reverse()
moveflag = self.up()
self.data.reverse()
return moveflag
以上便是我们封装的地图类,接下来便是生成窗体,绑定事件,设置字体大小和颜色了
首先导入窗体需要的tkinter库,这里先判断下python是否为3.0以上(包括3.0)
import sys
if (sys.version_info > (3, 0)):
from tkinter import *
from tkinter import messagebox
else:
from Tkinter import *
然后生成刚刚实现的地图类对象,并设置键盘按键与对象里的方法一一对应的键值对
game = map2048()
keymap = {
'a': game.left,
'd': game.right,
'w': game.up,
's': game.down,
'Left': game.left,
'Right': game.right,
'Up': game.up,
'Down': game.down,
'q': exit,
}
设置游戏背景色和数字的背景和前景色,这里数字还可以继续扩大2倍,想实现可以自己添加
game_bg_color = "#bbada0"
mapcolor = {
0: ("#cdc1b4", "#776e65"),
2: ("#eee4da", "#776e65"),
4: ("#ede0c8", "#f9f6f2"),
8: ("#f2b179", "#f9f6f2"),
16: ("#f59563", "#f9f6f2"),
32: ("#f67c5f", "#f9f6f2"),
64: ("#f65e3b", "#f9f6f2"),
128: ("#edcf72", "#f9f6f2"),
256: ("#edcc61", "#f9f6f2"),
512: ("#e4c02a", "#f9f6f2"),
1024: ("#e2ba13", "#f9f6f2"),
2048: ("#ecc400", "#f9f6f2"),
4096: ("#ae84a8", "#f9f6f2"),
8192: ("#b06ca8", "#f9f6f2"),
}
游戏各方块的lable数据
# 游戏各方块的lable数据
map_labels = []
鼠标按下处理函数和键盘按下处理函数,这里由于是2048游戏,所以不支持鼠标点击,在对应的方法里便不需要实现
# 鼠标按下处理函数
def on_mouse_down(event):
print("clicked at", event.x, event.y)
# 键盘按下处理函数
def on_key_down(event):
keysym = event.keysym
if keysym in keymap:
if keymap[keysym]():
game.fill2()
update_ui()
if game.is_gameover():
mb = messagebox.askyesno(title="gameover", message="游戏结束!\n是否退出游戏!")
if mb:
exit()
else:
game.reset()
update_ui()
# 刷新界面函数
def update_ui():
# 更改各个Label的设置
for r in range(len(game.data)):
for c in range(len(game.data[0])):
number = game.data[r][c]
label = map_labels[r][c]
label['text'] = str(number) if number else ''
label['bg'] = mapcolor[number][0]
label['foreground'] = mapcolor[number][1]
label_score['text'] = str(game.get_score())
以下为2048的界面
root = Tk()
root.title('2048')
生成对应的窗体,并指定宽高和背景颜色,grid方法表示对齐方式,这里就是填满整个窗体
frame = Frame(root, width=300, height=300, bg=game_bg_color)
frame.grid(sticky=N+E+W+S)
设置焦点能接收按键事件,绑定键盘点击事件
frame.focus_set()
frame.bind("<Key>", on_key_down)
# 以下绑定鼠标抬起事件
frame.bind("<ButtonRelease-1>", on_mouse_down)
初始化图形界面
# 初始化图形界面
for r in range(len(game.data)):
row = []
for c in range(len(game.data[0])):
value = game.data[r][c]
text = '' if 0 == value else str(value)
label = Label(frame, text=text, width=4, height=2,
font=("黑体", 30, "bold"))
label.grid(row=r, column=c, padx=5, pady=5, sticky=N+E+W+S)
row.append(label)
map_labels.append(row)
bottom_row = len(game.data)
print("button", str(bottom_row))
label = Label(frame, text='分数', font=("黑体", 30, "bold"),
bg="#bbada0", fg="#eee4da")
label.grid(row=bottom_row, column=0, padx=5, pady=5)
label_score = Label(frame, text='0', font=("黑体", 30, "bold"),
bg="#bbada0", fg="#ffffff")
label_score.grid(row=bottom_row, columnspan=2, column=1, padx=5, pady=5)
游戏重新开始,设置对应的重新开始按键和布局
#游戏重新开始
def reset_game():
game.reset()
update_ui()
restart_button = Button(frame, text='重新开始', font=("黑体", 16, "bold"),
# width=4, height=2,
bg="#8f7a66", fg="#f9f6f2", command=reset_game)
restart_button.grid(row=bottom_row, column=3, padx=5, pady=5)
启动窗体
update_ui()
root.mainloop()
总结:在本次学习中,收获很多,特别是对python的语法知识更加熟悉,这其中也遇到了不少问题,例如self关键字不能在类外部使用,写完整个程序,还是特别有意思的。