Python写个小游戏:速算24点(上)
文章目录
- 前言
- 速算24点
- 1. 玩法简介
- 2. 游戏流程
- 3. 搭建游戏界面
- 1). 基本界面
- 2). 洗牌、发牌
- 洗牌
- 发牌
- 3). 计时器
- 4). 玩家输入公式(答案)
- StringVar类
- 按键事件绑定
- 5). 判断玩家输入是否正确
- 4. 知识点回顾
前言
大家好,又见面了。六一后渐渐复工,工作开始忙碌起来,所以更新的频次不得不慢了下来。而且问哥最近开发的小游戏都是原创,麻雀虽小,五脏俱全,美工创意、窗口布局、代码实现、功能调试,一项都少不了。所以花了不少时间。
24点是我一直想做的小游戏,之前在文本界面就想做这么一期,但是做出来发现文本界面下实在没什么意思,所以就搁置了。现在来到图形界面,终于可以实现小时候的玩法,用扑克牌做道具。因而就会用到洗牌、发牌、计算点数等等小功能。而且还要让程序判断当前的四张牌能不能计算出24点,背后就要实现自动计算24点的方法。(因为问哥采用的是穷举法,肯定有很多重复计算,这里就不敢妄称“算法”了。)
因为内容比较多,还是和之前一样,分为上下篇:
上篇 —— 游戏界面搭建
下篇 —— 功能代码实现
速算24点
1. 玩法简介
相信不少人小时候都玩过,规则也比较简单:找一副扑克牌,去掉大小王,52张牌,每次随机抽取四张牌,运用加减乘除四种计算方法,看看谁能最快计算出24点。小时候由于没有电脑,所以无法判断四张牌到底有没有解,所以只要所有人都同一放弃,就可以跳到下一组。
游戏截图:
2. 游戏流程
问哥感觉这个小程序比之前的都要复杂一些,代码量也达到了两百行。究其原因,就是问哥想要实现的功能太多。洗牌、发牌、判断能否算出24点、计时、提示等等,问哥本来还想做一个记分牌,在游戏结束后,弹出窗口显示正确率。但最后由于精力不济,还是简单地在面板上显示“已测试”、“已通过”作罢。但其实这部分功能比较简单,有兴趣的朋友可以自由添加进来。
速算24点的简易流程图如下:
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()
这里面有几个细节:
- 当牌堆最后四张牌被抓完,牌背面的图片应该是被删掉的。然后再下次发牌的时候,调用洗牌的shuffle_card()函数
- Canvas的move方法其实很简单,因为我们在“抓好牌”后,四张牌的图片已经命名好(在列表cv_card里),所以move函数只要传三个参数 move(cv_card[i],1,0),第一个参数表示要移动那个图片,第二个参数1 表示横坐标移动1像素,第三个参数0表示纵坐标不变。
- 每次向右移动1像素,循环150遍(第二、三、四张牌循环次数更多),而在每次循环的时候,必须调用Canvas的update()方法,将每个像素的图片显示出来。
- 之前的文章里,因为是静态界面,问哥建议把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. 知识点回顾
- Canvas的move方法
- 组件的事件绑定
- StringVar类型
Python写个小游戏:速算24点(上)相关推荐
- Python写个小游戏:蛇棋(上)
文章目录 前言 蛇棋 1. 玩法简介 2. 游戏流程 3. 搭建游戏界面 1). 绘制棋盘 2). 放置信息框 3). 掷骰子的动画 4. 子窗口 1). 游戏开始函数 2). 棋子初始化函数 3). ...
- python写游戏脚本-使用Python写一个小游戏
引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下 ...
- python编程小游戏-使用Python写一个小游戏
引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下 ...
- 使用python制作聊天框解谜游戏_使用Python写一个小游戏alien invasion!
最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下其中的 ...
- 手机版python3h如何自制游戏_教你如何用 Python 写一个小游戏
教你如何用 Python 写一个小游戏 引言 最近 python 语言大火, 除了在科学计算领域 python 有用武之地之外, 在游戏后台等方面, python 也大放异彩, 本篇博文将按照正规的项 ...
- 关于python小游戏的毕业论文_使用Python写一个小游戏
引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下 ...
- 用python做一个简单的游戏,用python写一个小游戏
大家好,本文将围绕如何用python做一个简单的小游戏展开说明,python编写的入门简单小游戏是一个很多人都想弄明白的事情,想搞清楚用python做一个简单的游戏需要先了解以下几个事情. 1.Pyt ...
- Python写个小游戏:速算24点(下)
文章目录 前言 速算24点 1. 玩法简介 2. 游戏流程 3. 剩下的部分 1). 关卡 / 分数信息 IntVar类 2). 提示按钮 图片 按钮 3). 重新发牌 4. 让电脑计算24点 1). ...
- python能制作游戏吗_如何用python写一个小游戏
广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 引言最近python语言大火,除了在科学计算领域python有用武之地之外,在游 ...
最新文章
- 一个很简单小数正负数行转列问题
- linux下各种Raid介绍
- rails3高级查询
- Quartz Java resuming a job excecutes it many times--转
- 计算机考试用英语怎么说,“全国大学生计算机等级考试一级”英语怎么说?
- java spring mvc_java spring mvc 全注解
- 如何防止在listbox中添加很多数据出现不停的刷新
- 解决ORA-01578错误一例
- Java直连Access
- JAVA 汉字转化中文拼音
- 【步态识别】GaitMPL
- word页眉前后不一致怎么设置_2007word中,如何在某几页设置与前后不同的页眉?
- Verilog HDL 编程语言接口
- 获取Json对象中Json数组中的一个Json对象
- 你必须收藏的快速学习Autodesk最新编程接口的免费录像
- 条码固定资产管理系统的作用,固定资产条码化管理
- 【深度学习】【Python】【Widerface数据集】 转VOC格式,VOC 转YOLOv5格式,YOLOv5训练WiderFace数据集,检查yolo labels对不对
- 亲测好用,AI论文写作工具推荐
- 【好书推荐】《华为数据之道》
- .Net网站架构设计(二)Web服务器集群架构