在上一篇文章的最后,我们留了一个小作业:花果山美男子:tkinter模拟扑克牌和狼人杀发牌zhuanlan.zhihu.com
小作业2:模拟斗地主发牌,上方是牌库,实现从牌库到三个方向的动态发牌,最后揭示三张底牌(提示:可以用空白图片覆盖,或者删除指定组件)
今天我们就来完成一下斗地主发牌的模拟。
发牌的动态效果,原理和上一篇文章末尾狼人杀发牌是一样的。唯一的难点是牌库减少和揭示底牌的动画,方法就是每次更新图片时增加一个参数——tags标签,然后删除对应的图片。
另外还增加了“洗牌”、“发牌”、“亮牌”、“码牌”和“叫牌”的按钮,功能顾名思义,实现的逻辑也并不难。
具体方法请参考完整代码:
from tkinter import *
import random
from PIL import Image, ImageTk
import time
import tkinter.messagebox
n=54
player1,player2,player3,player4=[],[],[],[]
p1,p2,p3,p4=[],[],[],[]
pocker=[i for i in range(n)]
imgs=[]
root=Tk()
root.title('斗地主发牌模拟')
cv=Canvas(root,bg="White",width=800,heigh=650)
count=53 #牌堆剩余牌数(索引值,从0开始,用于tags标签的命名)
img0=ImageTk.PhotoImage(file="imgS/55.gif") #牌背图片
for i in range(1,55):
imgs.insert(i,ImageTk.PhotoImage(file="imgS/"+str(i)+".gif"))
lock=0 #是否叫地主的标记
#洗牌(用随机交换打乱牌序)
def gen_pocker(n):
x=100
while(x>0):
x=x-1
p1=random.randint(0,n-1)
p2=random.randint(0,n-1)
t=pocker[p1]
pocker[p1]=pocker[p2]
pocker[p2]=t
return pocker
#将三个玩家和底牌分配到对应的列表
def mm():
global p1,p2,p3,p4
p1,p2,p3,p4=[],[],[],[]
pocker=gen_pocker(n) #打乱后的牌组编号
for m in range(0,51,3):
try:
p2.append(pocker[m])
p3.append(pocker[m+1])
p4.append(pocker[m+2])
except:
break
for m in range(51,54):
p1.append(pocker[m]) #p1存储的是最后三张底牌
#绘制初始的牌库
def init():
for i in range(0,54):
#tags命名一定要有字母,纯数字的话,重新洗牌后再调用delete会失效
cv.create_image((270+5*i,80), image=img0,tags='a'+str(i))
#发牌效果的实现
def ks(x):
global count
if count != 53 and x==0: #洗牌前不能发牌
return
#底牌减少的效果,每次减少三张
cv.delete('a'+str(count))
count-=1
cv.delete('a'+str(count))
count-=1
cv.delete('a'+str(count))
count-=1
if x==0:
mm()
#发牌效果,此时牌序是随机的
img1=imgs[p2[x]]
img2=imgs[p3[x]]
img3=imgs[p4[x]]
player2.append(cv.create_image((100,100+25*x), image=img1))
player3.append(cv.create_image((220+18*x,500), image=img2))
player4.append(cv.create_image((700,100+25*x), image=img3))
x+=1
if x==17:
return
root.after(100,ks,x)
#亮底牌
def ks2():
if count !=2: #发牌结束前不能亮底牌
return
cv.delete('a2')
cv.create_image((230+50*2,80), image=imgs[p1[0]],tags='a')
cv.delete('a1')
cv.create_image((230+50*3,80), image=imgs[p1[1]],tags='b')
cv.delete('a0')
cv.create_image((230+50*4,80), image=imgs[p1[2]],tags='c')
#整理手牌(码牌)
def ks3():
global p2,p3,p4
if count != 2: #发牌结束前不能码牌
return
p2.sort()
p3.sort()
p4.sort()
for x in range(0,17):
img=imgs[p2[x]]
player2.append(cv.create_image((100,100+25*x), image=img))
img = imgs[p3[x]]
player3.append(cv.create_image((220+18*x,500), image=img))
img = imgs[p4[x]]
player4.append(cv.create_image((700,100+25*x), image=img))
#如果先叫牌后码牌,地主手里应该整理20张牌
if lock==2:
for x in range(17,20):
img=imgs[p2[x]]
player2.append(cv.create_image((100,100+25*x), image=img))
elif lock==3:
for x in range(17,20):
img=imgs[p3[x]]
player3.append(cv.create_image((220+18*x,500), image=img))
elif lock==4:
for x in range(17,20):
img=imgs[p4[x]]
player4.append(cv.create_image((700,100+25*x), image=img))
#洗牌(清空屏幕,重新绘制牌库)
def ks1():
global count,p1,p2,p3,p4,lock
p1,p2,p3,p4=[],[],[],[]
count=53
lock=0
cv.delete('all')
init()
#3号叫牌
def jp3():
try: #如果发牌前就叫牌,会进入except,lock重新清零
global p2,p3,p4,lock
if lock==0:
lock=3 #用数字来标记叫地主的玩家,此后除了该玩家不能再点击叫牌
for i in p1:
p3.append(i)
p3.sort()
for x in range(0,20):
img = imgs[p3[x]]
player3.append(cv.create_image((220+18*x,500), image=img))
#如果没有亮牌就叫牌,没有亮出的底牌也要清空
cv.delete('a')
cv.delete('b')
cv.delete('c')
cv.delete('a2')
cv.delete('a1')
cv.delete('a0')
elif lock!=3:
tkinter.messagebox.showerror("错误", "已经有地主了!")
return
else:
cv.delete('a')
cv.delete('b')
cv.delete('c')
cv.delete('a2')
cv.delete('a1')
cv.delete('a0')
except:
lock=0
pass
#2号叫牌
def jp2():
try:
global p2,p3,p4,lock
if lock==0:
lock=2
for i in p1:
p2.append(i)
p2.sort()
for x in range(0,20):
img = imgs[p2[x]]
player2.append(cv.create_image((100,100+25*x), image=img))
cv.delete('a')
cv.delete('b')
cv.delete('c')
cv.delete('a2')
cv.delete('a1')
cv.delete('a0')
elif lock!=2:
tkinter.messagebox.showerror("错误", "已经有地主了!")
return
else:
cv.delete('a')
cv.delete('b')
cv.delete('c')
cv.delete('a2')
cv.delete('a1')
cv.delete('a0')
except:
lock=0
pass
#4号叫牌
def jp4():
try:
global p2,p3,p4,lock
if lock==0:
lock=4
for i in p1:
p4.append(i)
p4.sort()
for x in range(0,20):
img = imgs[p4[x]]
player4.append(cv.create_image((700,100+25*x), image=img))
cv.delete('a')
cv.delete('b')
cv.delete('c')
cv.delete('a2')
cv.delete('a1')
cv.delete('a0')
elif lock!=4:
tkinter.messagebox.showerror("错误", "已经有地主了!")
return
else:
cv.delete('a')
cv.delete('b')
cv.delete('c')
cv.delete('a2')
cv.delete('a1')
cv.delete('a0')
except:
lock=0
pass
init()
button = Button(root, text ="洗牌", font=('黑体', 10),fg='blue',width=10,height=1,command = ks1)
button.place(x=280, y=260)
button = Button(root, text ="发牌", font=('黑体', 10),fg='blue',width=10,height=1,command = lambda:ks(0))
button.place(x=400, y=260)
button = Button(root, text ="亮牌", font=('黑体', 10),fg='blue',width=10,height=1,command = ks2)
button.place(x=280, y=300)
button = Button(root, text ="码牌", font=('黑体', 10),fg='blue',width=10,height=1,command = ks3)
button.place(x=400, y=300)
button = Button(root, text ="叫牌", font=('黑体', 10),fg='blue',width=10,height=1,command = jp3)
button.place(x=350, y=390)
button = Button(root, text ="叫\n\n牌", font=('黑体', 10),fg='blue',width=1,height=4,command = jp2)
button.place(x=160, y=200)
button = Button(root, text ="叫\n\n牌", font=('黑体', 10),fg='blue',width=1,height=4,command = jp4)
button.place(x=620, y=200)
cv.pack()
root.mainloop()
注意事项:
用delete和tags标记删除指定组件时,如果直接用数字,只有第一次能够成功删除,在重新绘制同名的组件后,再调用delete就无法删除了。如果tags中包含字母则没有这个bug。例如:cv.create_image((100,100), image=img,tags='a1')
cv.delete('a1')
而不能是:cv.create_image((100,100), image=img,tags=str(1))
cv.delete(str(1))
最后的效果请看视频:https://www.zhihu.com/video/1239598801498542080
ps.视频里我留了一手牌堆区边框和地主标识的绘制,这个大家就自己研究吧
如果实在手气不好的话:https://www.zhihu.com/video/1239604032827957248