完整代码已上传至github
首先我们要先载入一张图片,并把图片分割开来。
python的PIL是一个图像处理库,使用
import PIL
Pic_path = "img5.jpg" # 图片路径
im = Image.open(Pic_path) # 载入图片
载入图片后就是分割图片,不过windows操作系统下会使用系统自带的图片处理器打开,我需要把分割后的图片展示在一个新的tkinter窗体上:
这里我们把载入的im转化为tk窗口能够接收的格式,PIL库中提供了一种PhotoImage的格式。但是PhotoImage显示时,当把该变量放在自定义函数里时,不能正常显示,移出函数又可正常显示,所以想到可能是变量不是全局性的缘故,改为全局变量后果然可正常显示:
win = tk.Tk()
Pic_path = "img5.jpg"
im = Image.open(Pic_path) # 读取图片
tkImage_list = [] # 定义为全局变量,以便photoimage能够正常显示
depth = 3 # 分割的层数
width, height = im.size # 获取图片的大小
def cut_image(Pic): # 分割图片
global tkImage_list
w = int(width/depth)
h = int(height/depth)
for i in range(depth):
for j in range(depth):
box = (w*j, h*i, w*(j+1), h*(i+1)) # 需要切割的矩形区域
image = Pic.crop(box)
tkImage_list.append(ImageTk.PhotoImage(image))
label = tk.Label(win, image=tkImage_list[i*depth + j])
label.grid(row=i, column=j) # 行列布局
python tkinter库的三种布局方式
创建用一个新的列表[0, 1, 2, 3, …],打乱这个列表并按照打乱后的列表展示被分割的图片;
生成一张纯色图并添加进tkImage_list中;
重写展示image_list的方法,通过index_list展示image_list中的图片:
def cut_image(Pic): # 分割图片并存进tkImage_list中
global tkImage_list
global index_list
for i in range(depth):
for j in range(depth):
box = (w*j, h*i, w*(j+1), h*(i+1)) # 需要切割的矩形区域
image = Pic.crop(box)
tkImage_list.append(ImageTk.PhotoImage(image))
index_list.append(i*depth+j)
random.shuffle(index_list) # 打乱下标的顺序
print(index_list)
def form_white_box(): # 生成一个白色的区域并添加至tkImage_list中
global tkImage_list
(x, y) = int(depth / 2), int(depth / 2) # 坐标
list_index = x * depth + y # 列表下标
white_box = Image.new('RGB', (w, h), box_color) # 生成一张颜色为box_color的图片
tk_white_box = ImageTk.PhotoImage(white_box)
tkImage_list.pop(list_index)
tkImage_list.insert(list_index, tk_white_box) # 通过索引删除和添加元素
def show_image_list(): # 通过index_list展示image_list中的图片
i = 0
for index in index_list:
label = tk.Label(win, image=tkImage_list[index])
label.grid(row=int(i/depth), column=int(i % depth)) # 第i张图片的位置
i += 1
效果:
为了游戏的进行需要为游戏添加一个循环,打乱index_list并刷新win,测试是否能实现预期的效果:
def game_loop():
win.update()
show_image_list()
"""测试代码:发现可以根据index_list快速地更新图片,可以通过修改index_list实现窗口图片的更新"""
global index_list
random.shuffle(index_list)
win.after(FPS, game_loop)
def main():
cut_image(im)
form_white_box()
game_loop()
win.mainloop()
if __name__ == '__main__':
main()
效果:
为空白图添加按钮响应,按下箭头 上下左右 键或 wsad 能够做出相应的反应:
def exchange_img(event):
"""
响应键盘事件
交换白色图片与相邻图片的位置
:return:
"""
global index_index
global index_list
x = index_index
if event.keysym == "Left" or event.keysym == "a":
# 需要实现index_list的交换
if x % depth != 0: # 不是在最左边
index_list[x - 1], index_list[x] = index_list[x], index_list[x - 1]
index_index -= 1
elif event.keysym == "Right" or event.keysym == "d":
if (x+1) % depth != 0: # 不是在最右边
index_list[x + 1], index_list[x] = index_list[x], index_list[x + 1]
index_index += 1
elif event.keysym == "Up" or event.keysym == "w":
if x >= depth: # 不是在最上面
index_list[x - depth], index_list[x] = index_list[x], index_list[x - depth]
index_index -= depth
elif event.keysym == "Down" or event.keysym == "s":
if x + depth < depth*depth: # 不是在最下面
index_list[x + depth], index_list[x] = index_list[x], index_list[x + depth]
index_index += depth
else:
return
窗体绑定键盘按钮:
win.focus_set()
win.bind("" , exchange_img)
win.bind("" , exchange_img)
win.bind("" , exchange_img)
win.bind("" , exchange_img)
win.bind("" , exchange_img)
win.bind("" , exchange_img)
win.bind("" , exchange_img)
win.bind("" , exchange_img)
添加游戏结束的条件:
def check_list():
for i in range(depth * depth):
if index_list[i] != i:
return False
return True
def game_loop():
win.update()
show_image_list()
if not check_list():
win.after(FPS, game_loop)
到这里就已经大致完成了,我发现这个游戏居然还挺难的额…
效果:
添加game_over()方法,并在check_list()成功时调用,使得游戏结束时的表现更直观:
def game_over():
"""
游戏结束时在窗口添加“you win”文字
将被替代的图片还原
:return:
"""
label = tk.Label(win, text="YOU WIN!!!")
label.grid(row=depth, column=1)
tkImage_list.pop(depth * depth - 1)
tkImage_list.append(replaced_img)
show_image_list()
win.update()
return
我发现用random自带的shuffle函数打乱列表会出现无解的情况。网上找到这种说法:
九宫格拼图的可解性:
原数组和随机出来的数组的逆序数的奇偶性一致,那么就是可解的。如果原数组是[0,1,2,3,4,5,6,7,8], 算出来的 逆序数 为 0, 那么只要保证随机出来的数组的 逆序数 为偶数 ,那么拼图就是可解的。
于是我修改了打乱列表的方法:
def shuffle_list():
"""
打乱index_list并使逆序数一定为偶数
:return:
"""
global index_list
reverseCount = 0
random.shuffle(index_list)
for i in range(len(index_list)):
for j in range(i):
if index_list[i] < index_list[j]:
reverseCount += 1 # 计算逆序数
if reverseCount % 2 != 0:
shuffle_list()