tkinter日常系列文章目录:
最近不是写了个exe编译器吗,思考着该写个什么游戏,于是(编不下去了)......
直接上代码+注释(本人纯属业余,大神请勿吐槽):
代码
#encoding=utf-8
import random,sys
import math
import tkinter as tk
score=0
#外挂
def msinputkey(text=" OK ",title="Input",show="*"):
window = tk.Tk()
window.title(title)
e = tk.Entry(window,show=show)
e.pack()
x=[]
def ok():
x.append(tk.Entry.get(e))
window.quit()
b.destroy()
b = tk.Button(window, text=text, command=ok)
b.pack()
window.mainloop()
#window.quit()
if len(x)==0:
return ""
return x[0]
k=False
VERNUM=2
_map_data = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
VERSION="1.4"
COM_TYPE="for WIN10"
def reset():
'''重新设置游戏数据,将地图恢复为初始状态,并加入2作用初始状态'''
global score
score=0
global VERNUM
VERNUM=2
k=False
_map_data[:] = [] # _map_data.clear()
_map_data.append([0, 0, 0, 0])
_map_data.append([0, 0, 0, 0])
_map_data.append([0, 0, 0, 0])
_map_data.append([0, 0, 0, 0])
# 在空白地图上填充两个2
fill2()
fill2()
def get_space_count():
"""获取没有数字的方格的数量,如果数量为0则说有无法填充新数据,游戏即将结束
"""
count = 0
for r in _map_data:
count += r.count(0)
return count
def get_score():
global score
score = 0
for r in _map_data:
for c in r:
score += 0 if c < 4 else c * int((math.log(c, 2) - 1.0))
return score # 导入数学模块
def fill2():
'''填充2到空位置,如果填度成功返回True,如果已满,则返回False'''
blank_count = get_space_count() # 得到地图上空白位置的个数
if 0 == blank_count:
return False
# 生成随机位置, 如,当只有四个空时,则生成0~3的数,代表自左至右,自上而下的空位置
pos = random.randrange(0, blank_count)
offset = 0
for row in _map_data: # row为行row
for col in range(4): # col 为列,column
if 0 == row[col]:
if offset == pos:
# 把2填充到第row行,第col列的位置,返回True
row[col] = VERNUM
return True
offset += 1
def is_gameover():
"""判断游戏是否结束,如果结束返回True,否是返回False
"""
for r in _map_data:
if r.count(2048):
return 5
for r in _map_data:
# 如果水平方向还有0,则游戏没有结束
if r.count(0):
return False
# 水平方向如果有两个相邻的元素相同,应当是可以合并的,则游戏没有结束
for i in range(3):
if r[i] == r[i + 1]:
return False
for c in range(4):
# 竖直方向如果有两个相邻的元素相同,应当可以合并的,则游戏没有结束
for r in range(3):
if _map_data[r][c] == _map_data[r + 1][c]:
return False
# 以上都没有,则游戏结束
return True
# 以下是2048游戏的基本算法,此算法是在达内的美女老师"贾琳倩"提供算法上改进而来
def _left_move_number(line):
'''左移一行数字,如果有数据移动则返回True,否则返回False:
此行数据需要左移三次:
最终结果: line = [4, 8, 0, 0]
'''
moveflag = False # 是否移动的标识,先假设没有移动
for _ in range(3): # 重复执行下面算法三次
for i in range(3): # i为索引
if 0 == line[i]: # 此处有空位,右侧相邻数字向左侧移动,右侧填空白
moveflag = True
line[i] = line[i + 1]
line[i + 1] = 0
return moveflag
def _left_marge_number(line):
'''向左侧进行相同单元格合并,合并结果放在左侧,右侧补零
最终结果: line = [4, 8, 8, 0]
'''
for i in range(3):
if line[i] == line[i + 1]:
moveflag = True
line[i] *= 2 # 左侧翻倍
line[i + 1] = 0 # 右侧归零
def _left_move_aline(line):
'''左移一行数据,如果有数据移动则返回True,否则返回False:
左移算法分为三步:
+---+---+---+---+
最终结果: line = [4, 8, 0, 0]
'''
moveflag = False
if _left_move_number(line):
moveflag = True
if _left_marge_number(line):
moveflag = True
if _left_move_number(line):
moveflag = True
return moveflag
def left():
"""游戏左键按下时或向左滑动屏幕时的算法"""
moveflag = False # moveflag 是否成功移动数字标志位,如果有移动则为真值,原地图不变则为假值
# 将第一行都向左移动.如果有移动就返回True
for line in _map_data:
if _left_move_aline(line):
moveflag = True
return moveflag
def right():
"""游戏右键按下时或向右滑动屏幕时的算法
选将屏幕进行左右对调,对调后,原来的向右滑动即为现在的向左滑动
滑动完毕后,再次左右对调回来
"""
# 左右对调
for r in _map_data:
r.reverse()
moveflag = left() # 向左滑动
# 再次左右对调
for r in _map_data:
r.reverse()
return moveflag
def up():
"""游戏上键按下时或向上滑动屏幕时的算法
先把每一列都自上而下放入一个列表中line中,然后执行向滑动,
滑动完成后再将新位置摆回到原来的一列中
"""
moveflag = False
line = [0, 0, 0, 0] # 先初始化一行,准备放入数据
for col in range(4): # 先取出每一列
# 把一列中的每一行数入放入到line中
for row in range(4):
line[row] = _map_data[row][col]
# 将当前列进行上移,即line 左移
if (_left_move_aline(line)):
moveflag = True
# 把左移后的 line中的数据填充回原来的一列
for row in range(4):
_map_data[row][col] = line[row]
return moveflag
def down():
"""游戏下键按下时或向下滑动屏幕时的算法
选将屏幕进行上下对调,对调后,原来的向下滑动即为现在的向上滑动
滑动完毕后,再次上下对调回来
"""
_map_data.reverse()
moveflag = up() # 上滑
_map_data.reverse()
return moveflag
if (sys.version_info > (3, 0)):
from tkinter import *
from tkinter import messagebox
else:
from Tkinter import *
def main():
k=False
reset() # 先重新设置游戏数据
root = Tk() # 创建tkinter窗口
root.title('2048游戏 V '+VERSION) # 设置标题文字
root.resizable(width=False, height=False) # 固定宽和高
# 以下是键盘映射
keymap = {
'a': left,
'd': right,
'w': up,
's': down,
'Left': left,
'Right': right,
'Up': up,
'Down': down
}
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"),
# ----其它颜色都与8192相同---------
2**14: ("#b06ca8", "#f9f6f2"),
2**15: ("#b06ca8", "#f9f6f2"),
2**16: ("#b06ca8", "#f9f6f2"),
2**17: ("#b06ca8", "#f9f6f2"),
2**18: ("#b06ca8", "#f9f6f2"),
2**19: ("#b06ca8", "#f9f6f2"),
2**20: ("#b06ca8", "#f9f6f2"),
}
def on_key_down(event):
global k
global VERNUM
'键盘按下处理函数'
if event.keycode==16:
messagebox.showwarning(title="NOTICE提示", message="请先输入密码!")
key=msinputkey(show="⚫")
if key=="20100922":
messagebox.showwarning(title="Hmmm...", message="已开启外挂模式!!")
VERNUM=1024
else:
messagebox.showerror(title="Hmmm...", message="密码错误!!")
keysym = event.keysym
#"快捷"键
if keysym=='v':
messagebox.showinfo(title="VERSION关于", message="VERSION版本号: V "+VERSION+"\nSYSTEM适配操作系统: "+COM_TYPE)
elif keysym=='k':
messagebox.showinfo(title="KEY SETTINGS键位设置", message="P: 暂停\nA,←: 左移\nW,↑: 上移\nD,→: 右移\nS,↓: 下移\nV: 显示版本信息\nK: 显示键位设置\nQ: 退出游戏q")
elif keysym=='p':
messagebox.showinfo(title="提示", message="游戏已暂停, 按下确定键继续")
elif keysym=='q':
tt=messagebox.askyesno("再想想!",message="确定退出游戏?")
if tt:
log_edit._addlog(score)
root.quit()
if keysym in keymap:
if keymap[keysym](): # 如果有数字移动
fill2() # 填充一个新的2
update_ui()
xx=is_gameover()
if xx:
if xx!=5:
mb = messagebox.askyesno(
title="Game Over", message="游戏结束!\n是否退出游戏!")
if mb:
root.quit()
else:
reset()
k=False
elif not k:
k=True
mb = messagebox.askyesno(
title="恭喜通关!",message="恭喜您已通关!\n是否退出游戏!")
if mb:
root.quit()
update_ui()
def update_ui():
'''刷新界面函数
根据计算出的f地图数据,更新各个Label的设置
'''
for r in range(4):
for c in range(len(_map_data[0])):
number = _map_data[r][c] # 设置数字
label = map_labels[r][c] # 选中Lable控件
label['text'] = str(number) if number else ''
label['bg'] = mapcolor[number][0]
label['foreground'] = mapcolor[number][1]
label_score['text'] = str(get_score()) # 重设置分数
# 创建一个frame窗口,此创建将容纳全部的widget 部件
frame = Frame(root, bg=game_bg_color)
frame.grid(sticky=N+E+W+S)
# 设置焦点能接收按键事件
frame.focus_set()
frame.bind("<Key>", on_key_down)
# 初始化图形界面
# 创建4x4的数字块
map_labels = [] # 游戏各方块的lable Widget
for r in range(4):
row = []
for c in range(len(_map_data[0])):
value = _map_data[r][c]
text = str(value) if value else ''
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)
# 设置显示分数的Lable
label = Label(frame, text='分数', font=("黑体", 30, "bold"),
bg="#bbada0", fg="#eee4da")
label.grid(row=4, column=0, padx=5, pady=5)
label_score = Label(frame, text='0', font=("黑体", 30, "bold"),
bg="#bbada0", fg="#ffffff")
label_score.grid(row=4, columnspan=2, column=1, padx=5, pady=5)
# 以下设置重新开始按钮
def reset_game():
global k
k=False
reset()
update_ui()
restart_button = Button(frame, text='重新开始', font=("黑体", 16, "bold"),
bg="#8f7a66", fg="#f9f6f2", command=reset_game)
restart_button.grid(row=4, column=3, padx=5, pady=5)
update_ui() # 更新界面
root.mainloop() # 进入tkinter主事件循环
main() # 启动游戏
效果演示
编译+开挂!(编译器传送门python+tkinter exe编译器):
temperate video 1
求救
最后还有个问题:程序开头有个小小的bug,msinputkey()一开始我选择了输入完就退出窗口,但并没有退出,所以我改成了button.destroy(),有大神能帮我看一下是怎么回事吗