• python 图形界面“诈金花”游戏,更新了!附完整代码


    旧版本的代码请见上一篇博文: 

    python 从一道作业题到制作一个图形界面的“诈金花”游戏_Hann Yang的博客-CSDN博客Player1: ('♥Q', '♣2', '♣8') - 单张Player2: ('♦10', '♥7', '♠6') - 单张Player3: ('♣4', '♠4', '♦2') - 对子Player4: ('♠5', '♠9', '♥6') - 单张Player5: ('♠7', '♠3', '♣5') - 单张【Player3 win!】--> ('♣4', '♠4', '♦2') 对子(16.94%)https://blog.csdn.net/boysoft2002/article/details/128146513本文尝试在旧版本的基础上,“升级”以下几个部分:

    一、图形的旋转,模拟四个玩家两两面对围坐在牌桌上

    旋转方法 rotate(angle) 本文只用到转动角度这一个参数,角度正值表示逆时针转动;负值表示顺时针转动。

    method rotate in module PIL.Image:

    rotate(angle, resample=0, expand=0, center=None, translate=None, fillcolor=None) method of PIL.Image.Image instance
        Returns a rotated copy of this image.  This method returns a copy of this image, rotated the given number of degrees counter clockwise around its centre.
        :param angle: In degrees counter clockwise.
        :param resample: An optional resampling filter.  This can be one of :py:data: `PIL.Image.NEAREST`  (use nearest neighbour), :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 environment), or :py:data:`PIL.Image.BICUBIC`
           (cubic spline interpolation in a 4x4 environment).
           If omitted, or if the image has mode "1" or "P", it is set to :py:data: `PIL.Image.NEAREST`. See :ref:`concept-filters`.
        :param expand: Optional expansion flag.  If true, expands the output image to make it large enough to hold the entire rotated image.
           If false or omitted, make the output image the same size as the input image.  Note that the expand flag assumes rotation around the center and no translation.
        :param center: Optional center of rotation (a 2-tuple).  Origin is the upper left corner.  Default is the center of the image.
        :param translate: An optional post-rotate translation (a 2-tuple).
        :param fillcolor: An optional color for area outside the rotated image.
        :returns: An :py:class:`~PIL.Image.Image` object.

    如不是正方形图片,转动角度不是180度的话,就会被截掉一部分。效果如下: 

    演示代码:

    1. import tkinter as tk
    2. from PIL import Image,ImageTk
    3. def load(i=0):
    4. img = Image.open("pokers.png").resize((375,150))
    5. box = img.rotate(90*i)
    6. res = ImageTk.PhotoImage(image=box)
    7. img.close()
    8. return res
    9. if __name__ == '__main__':
    10. root = tk.Tk()
    11. root.geometry('800x480')
    12. root.title('图片旋转')
    13. cv = tk.Canvas(root, width=1600, height=800, bg='darkgreen')
    14. cv.pack()
    15. png = [None]*4
    16. coord = ((i,j) for j in (120,345) for i in (200,600))
    17. for i,xy in enumerate(coord):
    18. png[i] = load(i)
    19. cv.create_image(xy, image=png[i])
    20. cv.create_text(xy[0],xy[1]+95, text=f'逆时针转动{i*90}度',fill='white')
    21. root.mainloop()

    为保存全图在转动之前,设置一个正方形框 box = img.crop((0,0,375,375)).rotate(-90*i),顺时针转动的效果如下:

    演示代码:

    1. import tkinter as tk
    2. from PIL import Image,ImageTk
    3. def load(i=0):
    4. img = Image.open("pokers.png").resize((375,150))
    5. box = img.crop((0,0,375,375)).rotate(-90*i)
    6. res = ImageTk.PhotoImage(image=box)
    7. img.close()
    8. return res
    9. if __name__ == '__main__':
    10. root = tk.Tk()
    11. root.geometry('800x800')
    12. root.title('图片旋转')
    13. cv = tk.Canvas(root, width=1600, height=800, bg='darkgreen')
    14. cv.pack()
    15. png = []
    16. coord = ((i,j) for j in (200,600) for i in (200,600))
    17. for i,xy in enumerate(coord):
    18. png.append(load(i))
    19. cv.create_image(xy, image=png[i])
    20. root.mainloop()

    然后再用crop()方法来截取出黑色背景除外的部分,就是所需的转动四个方向上的图像;最后把这些图片再次分割成一张张小纸牌,存入一个三维列表备用。 

    二、增加筹码变量,使得比大小游戏有累积输赢过程

    在玩家文本框后各添加一个筹码文本框,动态显示每一局的输赢情况;各玩家的筹码值存放于全局变量Money列表中,主要代码如下:

    1. ALL, ONE = 1000, 200 #筹码初始值、单次输赢值
    2. Money = [ALL]*4 #设置各方筹码初始值
    3. ...
    4. ...
    5. cv.create_text(tx,ty, text=f'Player{x+1}', fill='white') #玩家1-4显示文本框
    6. txt.append(cv.create_text(tx+60,ty, fill='gold',text=Money[x])) #筹码显示框
    7. ...
    8. ...
    9. Money[idx] += ONE*4 #每次赢ONE*3,多加自己的一份
    10. for i in range(4):
    11. Money[i] -= ONE #多加的在此扣减
    12. cv.itemconfig(txt[i], text=str(Money[i])) #修改各方的筹码值
    13. cv.update()

    三、界面增加下拉式菜单,菜单项调用绑定函数

    显示效果见题图左上角,主要代码如下:

    1. btnCmd = '发牌',dealCards,'开牌',playCards,'洗牌',Shuffle
    2. Menu = tk.Menu(root)
    3. menu = tk.Menu(Menu, tearoff = False)
    4. for t,cmd in zip(btnCmd[::2],btnCmd[1::2]):
    5. menu.add_radiobutton(label = t, command = cmd)
    6. menu.add_separator() #菜单分割线
    7. menu.add_command(label = "退出", command = ExitApp)
    8. Menu.add_cascade(label="菜单",menu = menu)
    9. root.config(menu = Menu)

    四、导入信息框库,增加提示信息框的使用

    使用了2种信息框类型:提示showinfo()和确认选择askokcancel()

    tkinter.messagebox库共有8种信息框类型,其使用方法基本相同,只是显示的图标有区别:

    Help on module tkinter.messagebox in tkinter:

    NAME
        tkinter.messagebox

    FUNCTIONS
        askokcancel(title=None, message=None, **options)
            Ask if operation should proceed; return true if the answer is ok
        
        askquestion(title=None, message=None, **options)
            Ask a question
        
        askretrycancel(title=None, message=None, **options)
            Ask if operation should be retried; return true if the answer is yes
        
        askyesno(title=None, message=None, **options)
            Ask a question; return true if the answer is yes
        
        askyesnocancel(title=None, message=None, **options)
            Ask a question; return true if the answer is yes, None if cancelled.
        
        showerror(title=None, message=None, **options)
            Show an error message
        
        showinfo(title=None, message=None, **options)
            Show an info message
        
        showwarning(title=None, message=None, **options)
            Show a warning message

    DATA
        ABORT = 'abort'
        ABORTRETRYIGNORE = 'abortretryignore'
        CANCEL = 'cancel'
        ERROR = 'error'
        IGNORE = 'ignore'
        INFO = 'info'
        NO = 'no'
        OK = 'ok'
        OKCANCEL = 'okcancel'
        QUESTION = 'question'
        RETRY = 'retry'
        RETRYCANCEL = 'retrycancel'
        WARNING = 'warning'
        YES = 'yes'
        YESNO = 'yesno'
        YESNOCANCEL = 'yesnocancel'

    :发牌、开牌、洗牌按钮可否点击,由两个全局变量控制,当不能使用时弹出提示信息框。但更好方式通常是设置按钮的state状态,在 tk.DISABLED 和 tk.NORMAL 之间切换,用以下代码:

    1. if btn[0]['state'] == tk.DISABLED:
    2. btn[0]['state'] = tk.NORMAL
    3. else:
    4. btn[0]['state'] = tk.DISABLED #使得按钮灰化,无法被按下
    5. #或者在初始按钮时使用:
    6. tk.Button(root,text="点不了",command=test,width=10,state=tk.DISABLED)

    “诈金花”完整源代码

    1. import tkinter as tk
    2. import tkinter.messagebox as msg
    3. from PIL import Image,ImageTk
    4. from time import sleep
    5. from random import shuffle as DealCards
    6. #代码中所有print()语句行测试用都可删除
    7. #Written by Hann@CSDN,2022.12.06
    8. #https://hannyang.blog.csdn.net/
    9. def loadCards():
    10. PngList = []
    11. infile = Image.open("pokers.png")
    12. for k in range(4):
    13. box = 0,0,1500,1500 #设置正方形区域存放图片旋转后的状态
    14. box = infile.crop(box).rotate(90*k) #图片逆时间旋转90度的整数倍
    15. if k==0: rng = (0,0,1500,600)
    16. elif k==1: rng = (0,0,600,1500)
    17. elif k==2: rng = (0,900,1500,1500)
    18. elif k==3: rng = (900,0,1500,1500)
    19. box = box.crop(rng) #截取掉旋转产生的黑色背景
    20. images = []
    21. for j in range(4): #分割所有纸牌的图像存入三维列表
    22. image = []
    23. for i in range(15):
    24. if k%2:
    25. rng = (j*150,i*100,j*150+150,i*100+100)
    26. else:
    27. rng = (i*100,j*150,i*100+100,j*150+150)
    28. img = ImageTk.PhotoImage(image=box.crop(rng))
    29. image.append(img)
    30. images.append(image)
    31. PngList.append(images)
    32. '''调整并统一各方向上的纸牌图像顺序'''
    33. PngList[0],PngList[2] = PngList[2],PngList[0]
    34. for i in range(4):
    35. for j in range(2):
    36. PngList[j][i]=PngList[j][i][::-1]
    37. for i in range(0,4,3):
    38. PngList[i]=PngList[i][::-1]
    39. infile.close()
    40. return PngList
    41. def initCards():
    42. global P
    43. P = [f+v for f in F for v in V]
    44. DealCards(P)
    45. print('洗牌结果:\n',P)
    46. def clearCards():
    47. global cv
    48. cv.itemconfig(txt1, text="")
    49. cv.itemconfig(txt2, text="")
    50. if len(Pokers):
    51. for j in range(3):
    52. for i in range(4):
    53. cv.itemconfig(cards[i][j], image=Cards[i][0][0])
    54. cv.update()
    55. sleep(0.3)
    56. def dealCards():
    57. global cv,isReady1,isReady2
    58. if not isReady1:
    59. showInfo('不要重复发牌,发牌结束后请开牌!')
    60. return
    61. clearCards()
    62. isReady1 = False
    63. for j in range(3):
    64. for i in range(4):
    65. cv.itemconfig(cards[i][j], image=Cards[i][1][0])
    66. cv.update()
    67. sleep(0.2)
    68. if len(P)<12: initCards()
    69. isReady2 = False
    70. def playCards():
    71. global cv,isReady1,isReady2,P,Pokers
    72. if isReady1:
    73. showInfo('还没发牌,请发牌或者洗牌!')
    74. elif isReady2:
    75. showInfo('发牌还未结束,请勿开牌!')
    76. else:
    77. P = Result(P)
    78. isReady1,isReady2 = True,True
    79. for i,pok in enumerate(Pokers):
    80. for j,p in enumerate(pok):
    81. x,y = F.index(p[0]), V.index(p[1])
    82. cv.itemconfig(cards[i][j], image=Cards[i][x][y+2])
    83. cv.update()
    84. for i in range(4):
    85. cv.itemconfig(txt[i], text=str(Money[i])) #修改各方的筹码值
    86. cv.update()
    87. if min(Money)<=0: #统计输赢成绩
    88. zero = [f'Player{i}' for i,j in enumerate(Money,1) if j<=0]
    89. winner = [f'Player{i}({j})' for i,j in enumerate(Money,1) if j>ALL]
    90. info = '、'.join(zero)+'已输光筹码,点击确定重新开始!\n\n'
    91. info += f'本轮胜者(筹码多于初始值):'+'、'.join(winner)
    92. sleep(1)
    93. msg.showinfo(title = '提示',message=info)
    94. for i in range(4):
    95. Money[i] = ALL #重置各方筹码初始值
    96. cv.itemconfig(txt[i], text=str(Money[i]))
    97. cv.update()
    98. clearCards()
    99. initCards()
    100. isReady1,isReady2 = True,False
    101. def Scores(pokers):
    102. f,p = [],[]
    103. for poker in pokers:
    104. f.append(F.index(poker[0])+1)
    105. p.append(V.index(poker[1])+2)
    106. t = sorted(p)
    107. intSame = len(set(t))
    108. isFlush = int(len(set(f))==1)
    109. isStraight = t[0]+1==t[1]==t[2]-1
    110. if intSame==1:
    111. return 500_0000+t[0] #豹子
    112. elif intSame==2: #对子一样大比较剩下的单张
    113. return (100+t[1])*10000+(t[2] if t[0]==t[1] else t[0])
    114. else: #顺金(同花顺)顺子 else #单张或金花
    115. Straight = 200_0000*(1+isFlush)+t[2]
    116. Flush = ((300*isFlush+t[2])*100+t[1])*100+t[0]
    117. return Straight if isStraight else Flush
    118. def Result(P):
    119. global cv,Pokers
    120. Pokers,Winner = [],[]
    121. for i in range(0,3*4,3):
    122. Pokers.append(P[i:i+3])
    123. for i,p in enumerate(Pokers,1):
    124. win = Scores(p)
    125. idx = win//100_0000
    126. print(f"Player{i}: {*p,} - {W[idx]}".replace('T','10'))
    127. Winner.append(win)
    128. win = max(Winner) #没有判断“一样大”,如是则谁在前谁为大
    129. idx = Winner.index(win)
    130. big = win//10000
    131. win = big//100
    132. per = X[win] if win else Y[big-5]
    133. pok = W[win] if win else '单'+V[big-2]
    134. text1 = f"【Player{idx+1} win!】"
    135. text2 = f"{pok}{*Pokers[idx],} {per}%\n".replace('T','10')
    136. print(text1,'--> ',text2)
    137. cv.itemconfig(txt1, text=text1)
    138. cv.itemconfig(txt2, text=text2)
    139. Money[idx] += ONE*4 #每次赢ONE*3,多加自己的一份
    140. for i in range(4): Money[i] -= ONE #多加的在此扣减
    141. return P[3*4:] #去掉一局已发的牌
    142. def Shuffle():
    143. global isReady1
    144. if isReady1:
    145. clearCards()
    146. initCards()
    147. showInfo('已重新洗牌,请发牌!')
    148. else:
    149. showInfo('还没开牌呢,请勿洗牌!')
    150. def showInfo(info):
    151. msg.showinfo(title='提示',message=info)
    152. def ExitApp():
    153. if msg.askokcancel(title='确认',message='确定要退出吗?'):
    154. root.destroy() #关闭窗口退出程序
    155. if __name__ == '__main__':
    156. W = "单张","对子","顺子","金花","顺金","豹子" #牌型
    157. X = 74.38, 16.94, 3.26, 4.96, 0.22, 0.24 #各牌型出现概率
    158. Y = 0.54, 1.36, 2.44, 3.8, 5.43, 7.33, 9.5, 11.95, 14.66, 17.38 #单张概率
    159. V = *(str(i) for i in range(2,10)),*'TJQKA' #T代表10
    160. F = '♠', '♥', '♣', '♦'
    161. ALL, ONE = 1000, 200 #筹码初始值、单次输赢值
    162. Money = [ALL]*4 #设置各方筹码初始值
    163. '''Written by HannYang@CSDN, 2022.12.03'''
    164. root = tk.Tk()
    165. root.title('诈金花')
    166. root.geometry('1024x768')
    167. root.resizable(0,0) #禁止窗体改变大小
    168. cv = tk.Canvas(root, width=1024, height=680, bg='darkgreen')
    169. cv.pack()
    170. Pokers = []
    171. initCards()
    172. Cards = loadCards()
    173. isReady1,isReady2 = True,True #发牌、开牌的准备标记
    174. cards = [[None]*3 for _ in range(4)]
    175. '''设置发牌区域'''
    176. x1, x2, x3, dx = 400, 180, 830, 105
    177. y1, y2, y3 = 100, 550, 220 #调整坐标,使左右两家的牌横向显示
    178. imgxy = [[(x1,y1),(x1+dx,y1),(x1+2*dx,y1)],
    179. [(x3,y3),(x3,y3+dx),(x3,y3+2*dx)],
    180. [(x1,y2),(x1+dx,y2),(x1+2*dx,y2)],
    181. [(x2,y3),(x2,y3+dx),(x2,y3+2*dx)]]
    182. btn,txt = [],[]
    183. for x,lst in enumerate(imgxy):
    184. for y,coord in enumerate(lst):
    185. cards[x][y] = cv.create_image(coord, image=Cards[x][0][0])
    186. x1,y1,x2,y2 = coord[0]-50,coord[1]-50,coord[0]+50,coord[1]+50
    187. if x%2:
    188. cv.create_rectangle(x1-25,y1,x2+25,y2) #纸牌外框
    189. else:
    190. cv.create_rectangle(x1,y1-25,x2,y2+25)
    191. if x%2:
    192. tx,ty = coord[0]-25,coord[1]+65 #玩家、筹码的显示坐标
    193. else:
    194. tx,ty = coord[0]-130,coord[1]+90
    195. cv.create_text(tx,ty, text=f'Player{x+1}', fill='white') #玩家1-4显示文本框
    196. txt.append(cv.create_text(tx+60,ty, fill='gold',text=Money[x])) #筹码显示框
    197. '''设置显示每次开牌的胜者、牌型和出现概率的文本框'''
    198. txt1 = cv.create_text(510,300, fill='tomato', font=("宋体", 24))
    199. txt2 = cv.create_text(510,360, fill='yellow', font=("宋体", 12))
    200. '''设置命令按钮和菜单'''
    201. btnCmd = '发牌',dealCards,'开牌',playCards,'洗牌',Shuffle
    202. for i in range(3):
    203. btn.append(tk.Button(root,text=btnCmd[i*2],command=btnCmd[i*2+1],width=10))
    204. btn[i].place(y=710, x=350+i*110)
    205. Menu = tk.Menu(root)
    206. menu = tk.Menu(Menu, tearoff = False)
    207. for t,cmd in zip(btnCmd[::2],btnCmd[1::2]):
    208. menu.add_radiobutton(label = t, command = cmd)
    209. menu.add_separator() #菜单分割线
    210. menu.add_command(label = "退出", command = ExitApp)
    211. Menu.add_cascade(label="菜单",menu = menu)
    212. root.config(menu = Menu)
    213. ''''''
    214. root.mainloop()

    运行结果:

  • 相关阅读:
    想要数字化转型成功落地,企业还要做些什么?
    超越NumPy和Pandas的Python库
    新零售系统主要功能有哪些?新零售系统开发公司推荐
    同花顺_知识_庄家技法_1打压股价
    【毕业设计】基于stm32的车牌识别系统 - 物联网 单片机
    基于Qt QSlider滑动条小项目
    深度学习基础知识 最近邻插值法、双线性插值法、双三次插值算法
    LeetCode常见题型——链表
    DNA修饰碳纳米管|DNA修饰单层二硫化钼|DNA修饰二硫化钨(注意事项)
    项目上线计划表
  • 原文地址:https://blog.csdn.net/boysoft2002/article/details/128179253