文章目录

  • 前言
  • 速算24点
    • 1. 玩法简介
    • 2. 游戏流程
    • 3. 搭建游戏界面
      • 1). 基本界面
      • 2). 洗牌、发牌
        • 洗牌
        • 发牌
      • 3). 计时器
      • 4). 玩家输入公式(答案)
        • StringVar类
        • 按键事件绑定
      • 5). 判断玩家输入是否正确
    • 4. 知识点回顾

前言

大家好,又见面了。六一后渐渐复工,工作开始忙碌起来,所以更新的频次不得不慢了下来。而且问哥最近开发的小游戏都是原创,麻雀虽小,五脏俱全,美工创意、窗口布局、代码实现、功能调试,一项都少不了。所以花了不少时间。

24点是我一直想做的小游戏,之前在文本界面就想做这么一期,但是做出来发现文本界面下实在没什么意思,所以就搁置了。现在来到图形界面,终于可以实现小时候的玩法,用扑克牌做道具。因而就会用到洗牌、发牌、计算点数等等小功能。而且还要让程序判断当前的四张牌能不能计算出24点,背后就要实现自动计算24点的方法。(因为问哥采用的是穷举法,肯定有很多重复计算,这里就不敢妄称“算法”了。)

因为内容比较多,还是和之前一样,分为上下篇:

上篇 —— 游戏界面搭建
下篇 —— 功能代码实现


速算24点

1. 玩法简介

相信不少人小时候都玩过,规则也比较简单:找一副扑克牌,去掉大小王,52张牌,每次随机抽取四张牌,运用加减乘除四种计算方法,看看谁能最快计算出24点。小时候由于没有电脑,所以无法判断四张牌到底有没有解,所以只要所有人都同一放弃,就可以跳到下一组。

游戏截图:

2. 游戏流程

问哥感觉这个小程序比之前的都要复杂一些,代码量也达到了两百行。究其原因,就是问哥想要实现的功能太多。洗牌、发牌、判断能否算出24点、计时、提示等等,问哥本来还想做一个记分牌,在游戏结束后,弹出窗口显示正确率。但最后由于精力不济,还是简单地在面板上显示“已测试”、“已通过”作罢。但其实这部分功能比较简单,有兴趣的朋友可以自由添加进来。

速算24点的简易流程图如下:

#mermaid-svg-PgQoeiTAp6WTQCWD {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-PgQoeiTAp6WTQCWD .error-icon{fill:#552222;}#mermaid-svg-PgQoeiTAp6WTQCWD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PgQoeiTAp6WTQCWD .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-PgQoeiTAp6WTQCWD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PgQoeiTAp6WTQCWD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PgQoeiTAp6WTQCWD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PgQoeiTAp6WTQCWD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PgQoeiTAp6WTQCWD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PgQoeiTAp6WTQCWD .marker.cross{stroke:#333333;}#mermaid-svg-PgQoeiTAp6WTQCWD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PgQoeiTAp6WTQCWD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PgQoeiTAp6WTQCWD .cluster-label text{fill:#333;}#mermaid-svg-PgQoeiTAp6WTQCWD .cluster-label span{color:#333;}#mermaid-svg-PgQoeiTAp6WTQCWD .label text,#mermaid-svg-PgQoeiTAp6WTQCWD span{fill:#333;color:#333;}#mermaid-svg-PgQoeiTAp6WTQCWD .node rect,#mermaid-svg-PgQoeiTAp6WTQCWD .node circle,#mermaid-svg-PgQoeiTAp6WTQCWD .node ellipse,#mermaid-svg-PgQoeiTAp6WTQCWD .node polygon,#mermaid-svg-PgQoeiTAp6WTQCWD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PgQoeiTAp6WTQCWD .node .label{text-align:center;}#mermaid-svg-PgQoeiTAp6WTQCWD .node.clickable{cursor:pointer;}#mermaid-svg-PgQoeiTAp6WTQCWD .arrowheadPath{fill:#333333;}#mermaid-svg-PgQoeiTAp6WTQCWD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PgQoeiTAp6WTQCWD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PgQoeiTAp6WTQCWD .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-PgQoeiTAp6WTQCWD .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-PgQoeiTAp6WTQCWD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PgQoeiTAp6WTQCWD .cluster text{fill:#333;}#mermaid-svg-PgQoeiTAp6WTQCWD .cluster span{color:#333;}#mermaid-svg-PgQoeiTAp6WTQCWD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PgQoeiTAp6WTQCWD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

No
Yes
Yes
No
Yes
No 更换下一组
No
Yes
游戏开始
牌堆是否为0
抽取4张牌
计算该组合是否有解
倒计时开始
玩家输入答案
判断答案是否正确
停止倒计时
是否进入下一局
游戏结束
洗牌
有三次机会使用提示
时间用完,自动进入下一局

3. 搭建游戏界面

本篇游戏的界面还是问哥原创,肯定无法符合所有人的审美。大家在了解了实现的逻辑之后,可以自己随意配色、更换图片、调整布局,从而达到令自己满意的效果。

1). 基本界面

游戏背景、标题这种功能的实现,在上篇文章已经介绍过,这里就不啰嗦了。直接上代码:

from tkinter import *
# 初始化主窗口
root = Tk()
root.geometry('800x500+400+200')
root.resizable(0,0)
root.title('速算24点')
# 画布大小和主窗口大小一致
cv = Canvas(root,width=800,height=500)
# 背景图片
bg = PhotoImage(file=r"image\poker\bg.png")
cv_bg = cv.create_image(400,250,image = bg)
# 标题图片
title = PhotoImage(file=r"image\poker\title.png")
cv_title = cv.create_image(400,60,image = title)
# 画布裱在窗口里
cv.pack()
root.mainloop()

如此,我们就得到了一个空空荡荡的窗口:

2). 洗牌、发牌

既然是用扑克计算24点,那洗牌、发牌的操作必不可少。问哥之所以创建这么大的一个窗口,也是为了能够给发牌留下足够的空间。

洗牌

当游戏开始时,或者牌堆用完了,就要开始洗牌。所以为了方便调用,我们创建一个自定义函数。

import random
def shuffle_card():global cardnum, back, cv_backcardnum = list(range(52))random.shuffle(cardnum)# 洗完牌的牌堆,放在窗口左侧back = PhotoImage(file=r"image\poker\back1.png")cv_back = cv.create_image(100,200,image = back)

random模块是我们的老朋友了,为了实现随机的效果,我们在洗牌的时候调用random.shuffle方法,将52个数字(0到51)的顺序随机打散,cardnum这个列表将变成一个不规则排列的列表。正好shuffle的意思就是洗牌的意思,所以,在这里使用这个方法,真是在合适不过了。cardnum方法需要被主程序及其他函数调用,所以使用global关键字将其声明为全局变量。

洗完牌以后,我希望在窗口的左侧显示一张扑克牌背面的图片,代表牌堆有牌。由于精力有限,牌堆的厚度就没有实现了(当然想实现也是可以的)。而要想扑克牌背面的图片能够持续显示,也需要将其声明成全局变量。

代码运行的效果如下:

发牌

我们可以模拟现实中发牌的动作,将发牌分为两个步骤:1)将牌在牌堆顶显示,2)将牌移动到指定位置。

前面洗牌的时候,我们已经创建了一个0到51的数字乱序列表cardnum,现在我们只需要将这52个数字和图片对应上,就可以在抓牌的时候,自动显示扑克牌的图片了。

首先,创建一个扑克牌图片的列表。

card = [PhotoImage(file=f'image/poker/{i:0>2}.png') for i in range(1,53)]

问哥准备的扑克牌图片是从01开始命名的,所以需要使用格式化方法,将01到52的数字命名的图片读入到列表card里。

接着,我们自定义一个抓牌、发牌的函数:

import tkinter.messagebox as tm
def draw_card():draw=[]if len(cardnum)==0:tm.showinfo(message='牌堆已用完,为您重新洗牌')shuffle_card()for i in range(4):# 模拟抓牌:牌堆cardnum尾部弹出一张牌,放进要展示的牌列表draw里draw.append(cardnum.pop())# 将牌在牌堆顶显示cv_card.append(cv.create_image(100,200,image=card[draw[i]]))# 如果抓完最后四张牌,删除牌背面的图片(细节控)if len(cardnum)==0:cv.delete(cv_back)# 调用canvas的move方法,将牌移动到指定位置,实现发牌效果for _ in range(150*(i+1)):cv.move(cv_card[i],1,0)cv.update()

这里面有几个细节:

  1. 当牌堆最后四张牌被抓完,牌背面的图片应该是被删掉的。然后再下次发牌的时候,调用洗牌的shuffle_card()函数
  2. Canvas的move方法其实很简单,因为我们在“抓好牌”后,四张牌的图片已经命名好(在列表cv_card里),所以move函数只要传三个参数 move(cv_card[i],1,0),第一个参数表示要移动那个图片,第二个参数1 表示横坐标移动1像素,第三个参数0表示纵坐标不变。
  3. 每次向右移动1像素,循环150遍(第二、三、四张牌循环次数更多),而在每次循环的时候,必须调用Canvas的update()方法,将每个像素的图片显示出来。
  4. 之前的文章里,因为是静态界面,问哥建议把canvas的pack方法放在最后。但因为这次我们要动态的实现一些动画效果,所以需要先把canvas“裱”上去,才能在上面播放动画。

最后发牌的效果如下:

3). 计时器

当抽出4张牌并展示以后,我们首先要让程序计算出,这四张牌通过各种排列组合,能否得出24点。这部分计算的方法我们下篇内容再介绍。如果确定有解,能够得出24点,我们就要开始计时了。

问哥在之前的小文章里介绍了计时器的制作,这里就可以直接拿过来用了。当然,还是默认90秒倒计时。

Python动画制作:90秒倒计时圆形进度条效果

具体代码在这篇小文章里已经有解释,所以这里问哥就不多啰嗦了。不过这里的代码也解答了那篇小文章里最后的思考题:如果实现平滑的进度条。

代码如下:

def initialize():global angle,count,cv_arc,cv_inner,cv_textcount=90angle=360cv_arc=cv.create_oval(100,330,200,430,fill='red',outline='yellow')cv_inner=cv.create_oval(120,350,180,410,fill='yellow',outline='yellow')cv_text=cv.create_text(150,380,text=count,font =('微软雅黑',20,'bold'),fill='red')draw_card()def countdown():global angle,count,cv_arc,cv_inner,cv_text,cdif angle == 360:angle -= 1else:cv.delete(cv_arc)cv.delete(cv_inner)cv.delete(cv_text)cv_arc=cv.create_arc(100,330,200,430,start=90,extent=angle,fill="red",outline='yellow')angle -= 1if angle%4 == 0: count-=1cv_inner=cv.create_oval(120,350,180,410,fill='yellow',outline='yellow')cv_text=cv.create_text(150,380,text=count,font =('微软雅黑',20,'bold'),fill='red')if count==0:tm.showinfo(message='倒计时结束!自动进入下一局')cv.delete(cv_arc)cv.delete(cv_inner)cv.delete(cv_text)initialize()else:cd = root.after(250,countdown)

另外,问哥这里又定义了一个名叫initialize()的函数,翻译过来就是初始化,目的是为了将一些重复性的工作放进去,比如遮挡进度条的圆形等等。于是,我们可以将抓牌的函数draw_card()在放在里面,而在主程序里只需调用initialize()即可。

实现效果如下:

4). 玩家输入公式(答案)

想要实现玩家从键盘输入答案的方式有许多办法,问哥这里借这个机会介绍一种“事件绑定"的方法。

首先,创建一个Label标签组件,同Button按钮组件、Canvas画布组件一样,标签组件也是tkinter下的组件,可以简单理解为何Canvas同样级别。要在Canvas上显示同样级别的组件,需要使用我们上篇文章里介绍过的create_window方法。

answer=StringVar()
cv.create_text(400,350,text='请输入您的答案',font =('方正楷体简体',18,'bold'))
lb = Label(root,text='',font=('微软雅黑',15),textvariable=answer,bg='lightyellow')
cv_lb = cv.create_window(400,400,window=lb)
# 绑定从键盘获取输入<Key>,并传给自定义函数myanswer
lb.bind('<Key>',myanswer)
# 使标签组件获得焦点,不然无法从键盘输入
lb.focus_set()

StringVar类

这里首先定义了一个tkinter下的StringVar类的实例,其实它就表示一个字符串,但是比普通字符串变量更“智能”。这样,当我们在Label组件里使用textvariable参数,指定这个实例后,就可以动态的绑定在一起了。也就是说StringVar变成什么内容,Label组件会自动显示出来,比赋值操作要简单方便许多。

按键事件绑定

创建好Label标签组件以后,就可以使用bind()方法为其绑定一个Key事件,代表键盘的输入内容,然后将输入的内容隐式传参给myanswer这个自定义回调函数,通过这个函数来将键盘输入的内容赋值给StringVar,然后再在Label上显示出来。

函数定义如下:

trans={'plus':'+','minus':'-','asterisk':'*','slash':'/','parenleft':'(','parenright':')'}
def myanswer(event):s=event.keysymtxt=answer.get()if s=='BackSpace':txt=txt[:-1]elif s=='Return':if is_right(txt):root.after_cancel(cd)c = tm.askyesno(message='继续下一局吗?')if c:cv.delete(cv_arc)cv.delete(cv_inner)cv.delete(cv_text)initialize()else:root.destroy()else:txt=''elif s.isnumeric():txt+=selif s in trans:txt+=trans[s]answer.set(txt)

event是事件绑定函数隐式传参进来的变量,而event.keysym就代表了玩家当前(触发)这个函数得到的字符,也就是从键盘按了哪个键。注意,这里只要按下一个键,就会触发myanswer回调函数,所以event.keysym的值每次只表示一个字符。

但是这个字符的显示形式和我们平时认为的不太一样。除了数字和字母这种单字符的键之外,方向键、符号等等都是使用一个单词表示,而event.keysym得到的也是这个单词,而不是方向键、符号等特殊字符。所以,我们这里创建一个字典trans,来把event.keysym得到的符号,比如加减乘除、小括号等,转化成正确的字符 + - * / ( ) 显示出来。

而我们还需要对玩家输入的字符做出限制,除了数字和算术运算符号 + 0 * / ( ),还有回车与退格(删除),玩家按下其他任何键都不应该有反应。

每次玩家输入一个字符后,都使用answer.get()方法取得当前Label上的字符,再进行相应的操作(删除、增加字符),最后通过answer.set()方法将新的字符串显示在Label上。

除此之外,在这个函数中还包含了一个函数,is_right(),当玩家按下回车键(Return)的时候,该函数用来判断当前Label上的字符是否能够计算出24,从而判断胜负。如果能,则取消计时器(cd),然后开始下一局,如果不能,则“擦”掉当前的答案,请求玩家重新输入。

5). 判断玩家输入是否正确

is_right()函数属于代码实现的部分,但问哥觉得这个函数和上面的回调函数有关联,还是决定放在上篇里介绍一下。

该函数除了判断是否能够计算出24之外,还要确保玩家只使用,且用完了给定的4个数字。于是将得到的等式中的算术符号去除掉,再分割成数字,只要和当前的牌组数字nums对比,如果不同,则表示玩家没有使用正确的数字。

为了防止玩家输入了分母为0等错误的表达式,这里使用try…except方法进行判断。eval()函数用来将算术表达式的字符串转化成真正的表达式进行计算,而如果一旦遇到了分母为0,多或少了小括号,等等表达式错误,则直接抛出一个错误提示。

import math
def is_right(txt):try:result = eval(txt)except:tm.showinfo(message='算式不正确,请重新输入!')return Falsefor i in '+-*/()':txt=txt.replace(i,' ')txt=[int(i) for i in txt.split()]if sorted(txt)!=sorted(nums):tm.showinfo(message='请使用给定的数字!')return Falseif math.isclose(result,24):tm.showinfo(message='恭喜您!回答正确!')return True

最后要注意的是,因为我们在计算24点的过程中,很有可能会使用除法,而一旦使用了除法,结果就必然变成一个浮点数。由于计算机使用二进制表示浮点数,所以一旦出现某些小数不能用二进制完全表达的情况,就会出现无限循环小数,产生误差。所以这里如果直接使用 if result==24来进行判断,在某些情况下将会得不到正确结果,比如23.999999 和24就并不相等,但23.999999可能是计算过程中的除法产生的。

于是我们使用math模块的isclose方法。math.isclose(result, 24)表示两个数的差值在一个极小的范围内,则认为两数相同。

最终效果如下图:

4. 知识点回顾

  1. Canvas的move方法
  2. 组件的事件绑定
  3. StringVar类型

Python写个小游戏:速算24点(上)相关推荐

  1. Python写个小游戏:蛇棋(上)

    文章目录 前言 蛇棋 1. 玩法简介 2. 游戏流程 3. 搭建游戏界面 1). 绘制棋盘 2). 放置信息框 3). 掷骰子的动画 4. 子窗口 1). 游戏开始函数 2). 棋子初始化函数 3). ...

  2. python写游戏脚本-使用Python写一个小游戏

    引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下 ...

  3. python编程小游戏-使用Python写一个小游戏

    引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下 ...

  4. 使用python制作聊天框解谜游戏_使用Python写一个小游戏alien invasion!

    最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下其中的 ...

  5. 手机版python3h如何自制游戏_教你如何用 Python 写一个小游戏

    教你如何用 Python 写一个小游戏 引言 最近 python 语言大火, 除了在科学计算领域 python 有用武之地之外, 在游戏后台等方面, python 也大放异彩, 本篇博文将按照正规的项 ...

  6. 关于python小游戏的毕业论文_使用Python写一个小游戏

    引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下 ...

  7. 用python做一个简单的游戏,用python写一个小游戏

    大家好,本文将围绕如何用python做一个简单的小游戏展开说明,python编写的入门简单小游戏是一个很多人都想弄明白的事情,想搞清楚用python做一个简单的游戏需要先了解以下几个事情. 1.Pyt ...

  8. Python写个小游戏:速算24点(下)

    文章目录 前言 速算24点 1. 玩法简介 2. 游戏流程 3. 剩下的部分 1). 关卡 / 分数信息 IntVar类 2). 提示按钮 图片 按钮 3). 重新发牌 4. 让电脑计算24点 1). ...

  9. python能制作游戏吗_如何用python写一个小游戏

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 引言最近python语言大火,除了在科学计算领域python有用武之地之外,在游 ...

最新文章

  1. 一个很简单小数正负数行转列问题
  2. linux下各种Raid介绍
  3. rails3高级查询
  4. Quartz Java resuming a job excecutes it many times--转
  5. 计算机考试用英语怎么说,“全国大学生计算机等级考试一级”英语怎么说?
  6. java spring mvc_java spring mvc 全注解
  7. 如何防止在listbox中添加很多数据出现不停的刷新
  8. 解决ORA-01578错误一例
  9. Java直连Access
  10. JAVA 汉字转化中文拼音
  11. 【步态识别】GaitMPL
  12. word页眉前后不一致怎么设置_2007word中,如何在某几页设置与前后不同的页眉?
  13. Verilog HDL 编程语言接口
  14. 获取Json对象中Json数组中的一个Json对象
  15. 你必须收藏的快速学习Autodesk最新编程接口的免费录像
  16. 条码固定资产管理系统的作用,固定资产条码化管理
  17. 【深度学习】【Python】【Widerface数据集】 转VOC格式,VOC 转YOLOv5格式,YOLOv5训练WiderFace数据集,检查yolo labels对不对
  18. 亲测好用,AI论文写作工具推荐
  19. 【好书推荐】《华为数据之道》
  20. .Net网站架构设计(二)Web服务器集群架构

热门文章

  1. TCHAR字符串的操作
  2. Linux 安装Mysql8.0.15教程,以及修改密码
  3. 《线性代数应该这样学》读书笔记
  4. vi与gedit的区别
  5. Java程序员的就业方向
  6. absolute导致的高度塌陷问题——解决方法
  7. 有关嵌入式硬件测试的资料
  8. 持续交付中有哪些宝贵数据?
  9. 数字孪生教程大全之 我们如何构建数字孪生?数字孪生的五个级别都是什么
  10. 编程随想(编程学什么语言好)