python 24点 tkinter_编程实现一个有GUI的24点小程序

24点是指从去除大小王后的52张扑克牌中任取 4 张,通过「加、减、乘、除」四则运算得到 24。是一个历史悠久的趣味小游戏。

《数据化管理》书中在测试数据敏感度章节提到一个细节“每天上下班的路上,盯着公交车外看到的汽车尾部牌照玩24点”,去练运算能力。根据排列组合知识可以算出:在1~10的数字中任选4个,有C(13,4)=715种情况(因为数字可以重复,如[5,5,5,5],故不是直接从10个数中取4个的组合),从1~13中任选4个是C(16,4)=1820种情况,经过大佬门的枚举和推导,只考虑加减乘除,715种情况中,有566种有解,也就是79.16%的概率,而从1~13中选的1820种情况中是1362种情况下能算出24点,概率为74.83%。

最近自己也在练24点的计算,需要随机生成4个数的组合,并且在需要有答案,看这题有哪些做法能算出24点,于是就打算用Python来实现生成4个随机数以及求给定序列的24点计算方法。可以选择在4个数之间的3个空格中枚举各种符号的情况,并且考虑括号,还有一种思路是“降数法”:4个数经过一步运算“降维”成3个数,再变成2个数,最后得到1个数,如果得到24说明这种组合成立。后一种需要的判断更少些,于是选择实现这一思路。

代码的大致流程如下:1),对给定的4个数进行排列,得到A(4,4)=4!=24种排列,对这24种情况执行:

2),前2个数实现第一步计算,合并成1个数,生成一个3个数的新序列;

3),对这3个数做排列,同样前2个做四则运算,3个数合并成2个;

4),最后两个数的排列为[a,b]和[b,a],分别做加减乘除运算,变成一个数;

5),如果最后生成的数是24,则记录这种计算方式;否则继续对下一个排列重复上面2~4。

得到一个序列的全排列的递归方法在之前的一个蛰虫始航:Ann全排列的枚举_递归实现(基于Python)​zhuanlan.zhihu.com

有具体讲解,这里不赘述。

最后求24点计算方法的代码如下:

#枚举列表lst的全排列

def perm(lst): #input:list,[1,2,3,4]

n=len(lst)

if n<=1: #终止条件1

return lst

elif n==2:

return [[lst[0],lst[1]],[lst[1],lst[0]]] #终止条件2

kk=[]

for i in range(n):

nlst=lst[0:i]+lst[i+1:] #除lst[i]外的元素

c=perm(nlst) #对子序列进行递归

ss=[]

for j in c:

sw=[lst[i]]

sw.extend(j)

ss.append(sw)

kk.extend(ss) #注意是extend不是append

return kk

def cal24(a): #24点计算

lst=[[i,''] for i in a]

d1=perm(lst) #len==24

ev=['+','-','*','/']

res=[]

for d in d1: #len(d)==4

for e1 in ev: #24*4

if e1=='/' and d[1][0]==0: #被除数为0

continue

r='({0}{1}{2})'.format(d[0][0],e1,d[1][0])

k1=[[eval(r),r],d[2],d[3]] #k1=[eval(),d[2],d[3]] k1.extend(d[2:])

d2=perm(k1) #len(k1)==3 len(d2)==A(3,2)=6

for d3 in d2: #len(d3)==3

for e2 in ev:

if e2=='/' and d3[1][0]==0: #被除数为0

continue

r1='{0}{1}{2}'.format(d3[0][0],e2,d3[1][0])

y0=d3[0][0] if d3[0][1]=='' else d3[0][1]

y1=d3[1][0] if d3[1][1]=='' else d3[1][1]

r2='({0}{1}{2})'.format(y0,e2,y1)

k2=[[eval(r1),r2],d3[2]] # k2.extend(d3[2:])

d4=[[k2[0],k2[1]],[k2[1],k2[0]]]

for d5 in d4:

for e3 in ev:

if e3=='/' and d5[1][0]==0:

continue

k3=eval('{0}{1}{2}'.format(d5[0][0],e3,d5[1][0]))

if abs(k3-24)<1e-6:

y0=d5[0][0] if d5[0][1]=='' else d5[0][1]

y1=d5[1][0] if d5[1][1]=='' else d5[1][1]

rss='({0}{1}{2})'.format(y0,e3,y1)

k4=eval(rss)

if abs(k4-24)<1e-6:

res.append(rss)

return list(set(res)) #初步去重

我们拿几个实例来进行测试,输入结果如下:

这种实现还是有些粗暴,没有很好地进行各种情况的去重,例如2×7+6+4和2×7+4+6是一种情况,对交换律和括号的去重实现可以参考如何不重复地枚举 24 点算式?(上) - 王赟 Maigo。

基于上面写的代码我们可以求任意4个数算24的所有情况,加上随机数生成平时就不缺24点的练习了,为了更好用,我们再加上GUI。为了兼容性,这里选择用内置的tkinter去实现GUI。

整体流程如下:导入tk库,创建主窗体->添加控件->处理交互->进入主事件循环

交互的逻辑还是“降数法”的思路。

整体的界面如下图:

代码比较长,主要分为了生成各种按钮并设置坐标放在合适的位置,编写按钮按下的回调函数两个部分。部分代码如下:

root=tk.Tk()

root.geometry('280x320+400+100') #大小和位置 widthxheight+x+y

root.title('cal 24')

ctv=tk.StringVar(root,'')

btnUs=tk.IntVar(root,0)

cur=[]

result=[]

if result==[]:

for _ in range(4):

cur.append(random.randint(0,10))

cur.append('') #对应各个按钮当前值

scur=cur.copy() #重来 用

stk=[['',''],'',['',''],''] #操作符点击

itv=tk.StringVar(root,'---')

infov=tk.Label(root,textvariable=itv) #显示信息用

infov.place(x=170,y=5,width=120,height=20)

stk[3]=tk.Button(root,text='').cget("background") #默认按钮背景色 linux: #d9d9d9 win:SystemButtonFace

#回调函数

def btnClick(btn,bt=''): #btn:按下的按钮 bt:所按下按钮的标识,主要是数值键用

global cur,stk,scur,result

ith=itv.get()

btnus=btnUs.get()

uop=[i for i in range(15)] #[0,14]

opw=['+','-','*','/']

if btn=='--':return

if btn in uop: #按的是数值类型的键

btnn=cur[bt-1]

itv.set('{0}'.format(btnn))

if stk[0][0]=='': #第一次按到数值键

stk[0]=[btnn,bt] #or stk[0][0]=btnn;stk[0][1]=bt

elif stk[1]=='':#没有按过符号键

if stk[0][0] !='':#如两次点到数值键

stk[0]=[btnn,bt]

elif stk[1]!='': #关键 完成了 a+b的输入

stk[2]=[btnn,bt]

btnus+=1 #在这个if条件下会合并两个按钮为一个,用掉一个按钮

vss='{0}{1}{2}'.format(stk[0][0],stk[1],stk[2][0]) #a+b

cur[4]='({0})'.format(vss)

#暂时不好区分是cur[4],stk[1],stk[2][0] 还是 stk[0][0],stk[1],cur[4]

v=eval(vss)

itv.set(vss)

ccv=float("%.3f" %v)

if abs(v-ccv)<1e-6: setVBtnval(v,bt)

else: setVBtnval(ccv,bt)

setVBtnCol('#808080',stk[0][1]) #“失效”一个按钮

setVBtnval('--',stk[0][1])

stk[0]=[v,bt]

stk[1]='' #置空后两步操作,第一步更新为v的值,以方便实现a*b+c (a+b)*c

stk[2]=['','']

if abs(v-24)<1e-6:

if btnus==3: #用掉三个,结果正确,到达endgame

messagebox.showinfo(str(scur[:4]),'恭喜你计算正确!')

elif btn in opw: #操作符,更新stk[1]

if stk[0][0]=='':

itv.set('操作符前没有数值')

return #无效 操作符前没有数值

elif stk[1] in opw: #覆盖上一步点的操作符

stk[1]=btn

elif stk[1]=='': #当前循环还没有输入过运算符

stk[1]=btn

elif btn=='C': #清空操作重来

itv.set('--')

cur=scur.copy()

updateVBtn(cur) #更新数值按钮上的值

resetVBtnColor(stk[3]) #重设按钮的背景色

stk=resetStk(stk) #重设stk的值

btnus=0 #按钮使用数重设为0

elif btn=='Next': #下一题

ch=[]

for i in range(150):

ch=[]

for _ in range(4):

ch.append(random.randint(0,10))

result=cal24(ch)

if result!=[]:

if len(result)>9: #只取前10个答案

result=result[:9]

break

if ch==[]:

for i in range(4):

cur[i]=random.randint(0,10)

else:

for i in range(4):

cur[i]=ch[i]

cur[4]=''

updateVBtn(cur)

resetVBtnColor(stk[3])

stk=resetStk(stk)

scur=cur.copy()

itv.set('--')

btnus=0

btnUs.set(btnus)

def showAnswer(): #用消息框展示当前题目的答案

global result,cur

rss='\n'.join([str(i) for i in result])

messagebox.showinfo(str(cur),rss)

btn1=tk.Button(root,text=str(cur[0]),command=lambda x=cur[0]:btnClick(x,1))

btn1.place(x=0,y=10,width=90,height=90)

btn2=tk.Button(root,text=str(cur[1]),command=lambda x=cur[1]:btnClick(x,2))

btn2.place(x=90,y=10,width=90,height=90)

btn3=tk.Button(root,text=str(cur[2]),command=lambda x=cur[2]:btnClick(x,3))

btn3.place(x=0,y=100,width=90,height=90)

btn4=tk.Button(root,text=str(cur[3]),command=lambda x=cur[3]:btnClick(x,4))

btn4.place(x=90,y=100,width=90,height=90)

btn5=tk.Button(root,text='+',command=lambda :btnClick('+'))

btn5.place(x=0,y=200,width=40,height=20)

#……

btnClear=tk.Button(root,text='重来',command=lambda :btnClick('C'))

btnClear.place(x=0,y=250,width=60,height=20)

# ……

root.mainloop()

运行效果如下:

代码改一下可以变成命令行下的交互版本:

def cmdcal24():

import random

print('欢迎使用命令行版24点训练器!\n## 说明')

q=''

cur,res=[],[]

while q!='q':

if res==[]:

res,cur=getOne()

q=input('当前题目:{0}\n输入您的答案:'.format(str(cur)))

elif q=='a':

print(res)

res,cur=getOne()

q=input('当前题目:{0}\n输入您的答案:'.format(str(cur)))

else:

try:

c=re.compile(r'\d+').findall(q)

if len(c)!=4:

q=input('式子有问题,请检查后重新输入\n')

else:

cr=[str(i) for i in cur]

if cmptlst(c,cr):

c=eval(q)

if abs(c-24)<1e-6:

print('计算正确!')

res,cur=getOne()

q=input('当前题目:{0}\n输入您的答案:'.format(str(cur)))

except Exception as e:

print(e)

q=input('输入您的答案:'.format(str(cur)))

最后GUI版的脚本可以导出为exe文件,其他人也可以方便的使用,通过pyindatller可以快速打包py脚本为exe文件。

Python打包为exe普遍文件会比较大(还是C#好),我这边导出的结果是8.3MB,可以接受,用内置库的好处。写小型程序用tkinter是够用的。

代码持续更新。QLWeilcf/cal24withGUI​github.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值