Hello everyone , I've been a little busy lately , The second part is delayed in writing . The last article also ended in a hurry , I am really sorry. . The code has already been written , But brother Wen still wants to try his best to disassemble it 、 Explain clearly , So instead of sharing code directly . Of course , If you want to skip asking brother's long winded nonsense , Direct reference code , You can also skip to the end of the article .
I don't say much nonsense , Get to the rest of us right away :
Part 1 —— Game interface construction
The next part —— Function code implementation
The rules of the game are simple : Find a deck of playing cards , Remove the big and small king ,52 card , Draw four cards at random each time , Use the four calculation methods of addition, subtraction, multiplication and division , See who can work out the fastest 24 spot . Although it is a stand-alone game , There's no pressure to play , However, the timing function is added : If 90 Failed to find the answer within seconds , The current test is deemed to have failed , Automatically enter the next level . meanwhile , In order to reduce the difficulty , Provides prompt function : In the process of passing through the customs , There are three opportunities to get prompt reference answers .
screenshots :
Quick calculation 24 The simple flow chart of point is as follows :
The previous content explained the construction of most interfaces , There are two small places , One is level information , One is the prompt button .
In the first part, we introduced StringVar class , Instance the variable , Used to bind with tags , The contents of variables can be directly displayed on the label , This saves a lot of trouble . and StringVar Is for string , For pure numbers ,tkinter Similar functions are also provided IntVar class . similarly , Bind it to tags or other components , You can directly put IntVar The value of the class instance is displayed . And StringVar The difference is ,IntVar Is an integer (int), Can directly participate in the calculation , therefore , Used to record the number of levels the player has tested , And pass the level ( fraction ), It's also very convenient .
therefore , Let's first define two variables , Instantiation IntVar class :
level=IntVar()
score=IntVar()
To create a 4 A label , Two are used to display fixed characters “ Tested ”、“ Passed ”, The other two are for and variables level、score binding , To dynamically display the current level and score .
cv.create_text(600,350,text=' Tested :',font =(' Square regular script, simplified Chinese ',16,'bold'))
cv.create_text(600,400,text=' Passed :',font =(' Square regular script, simplified Chinese ',16,'bold'))
level_lable = Label(root,text='',font=(' Microsoft YaHei ',15),textvariable=level,bg='lightyellow')
cv_level = cv.create_window(670,350,window=level_lable)
score_lable = Label(root,text='',font=(' Microsoft YaHei ',15),textvariable=score,bg='lightyellow')
cv_score = cv.create_window(670,400,window=score_lable)
The implementation effect is as follows :
The prompt button area is also divided into two parts , Part of it is the button , The other part is a picture of a small light bulb , Used to indicate how many chances the player has left .
Ask brother about the picture of the small light bulb he found on the Internet , Change it to png Format , Then name it idea.
First define a constant HINT, Used to indicate how many prompt opportunities a player can have . The default is 3 Time , Of course, you can also change it to 5 Time , More or less . And then through for loop , Draw at the specified position 3 Picture of a light bulb , And put these Drawn Components in a list ideas in .
HINT = 3
idea = PhotoImage(file=r"image\poker\idea.png")
ideas = []
for i in range(HINT):
ideas.append(cv.create_image(450+i*25,450,image = idea))
Why put it on the list ? Because it is convenient for us to press the prompt button , Automatically reduce the picture and refresh on the canvas .
Button creation is simple , We've also introduced . To speed up the completion of this small project , No special effects , So the default is tkinter Of Button Components .
btn = Button(root,text=' Tips ',width=5,command=hint)
cv_btn = cv.create_window(400,450,window=btn)
The key is that we need to bind a callback function to this button , It's called hint, In this function , We need to complete the following three functions :
The implementation code is as follows :
def hint():
answer.set(right_answer)
btn['state']=DISABLED
idea = ideas.pop()
cv.delete(idea)
Same thing , To prevent players from misoperating ( You can never fully foresee how players or users will open their minds ), In order to reduce the possibility of the program , We stipulate that this button can only be enabled when it is clicked , let me put it another way , Except for the countdown , When the player begins to answer the question , And the number of prompts is not used up (ideas>0), The status of the button is NORMAL, The button should be in... At other times DISABLED state .
therefore , We have made the following updates in other locations :
def initialize():
# Omit code
btn['state']=DISABLED
def draw_card():
# Omit code
if len(ideas)>0: btn['state']=NORMAL
The test results are basically OK:
The code is written here , Except that the computer hasn't found the right answer for us yet , Basic input has been implemented 、 Judgment and other functions . Try to run it :
Find that the answer is correct 、 After choosing to continue the next round , The deal on the table is confused , I don't know where the new four cards have gone , What's going on ?
original , We used an array in the previous article cv_card To store the four cards drawn , And use for Cycle through the four cards on the desktop , But it doesn't take into account that when a game ends , Need to draw four cards again . under these circumstances , We need to put the... On the table 4 A card is erased from the canvas , There are two main steps in using code :
To avoid repetition , We then define a subroutine to complete the cleanup clear The operation of :
def clear(cv_card):
for i in cv_card:
cv.delete(i)
cv_card.clear()
then , Let's put this function at the beginning of the card grabbing function , such , Every time before starting to draw , We all clean up first ( If any ) Four cards from the previous round , To ensure that the code is still applicable to the new four cards .
def draw_card():
clear(cv_card)
For the same reason , After we restart the next game , The contents of the answer tab also need to be cleared , So we need to be able to initialize Function and myanswer Add the following code to the function :
def initialize():
answer.set('')
# Omit code
def myanswer(event):
# Omit code
if s=='BackSpace':
txt=txt[:-1]
elif s=='Return':
if is_right(txt):
root.after_cancel(cd)
c = tm.askyesno(message=' Go on to the next game ?')
if c:
# Omit code
return # add to return, Indicates that the label will not be displayed after entering the next round
After testing , Without computer help , Has been able to successfully “ Support oneself ” To break through the customs . The problem is , It happens all the time : Draw four cards , But it's hard to write the answer , Because we can't be sure that we didn't come up with an answer , Or these four cards have no answer at all . therefore , We need to design a method , Let the computer calculate the answer for us first , Save in variables right_answer in , If there is no answer , Automatically change the next group . With right_answer, The prompt button can also work .
Count up , This part is actually relatively independent code , Because we can take the problem to be solved out of the game , Convert to “ Given 4 Number , Calculate whether it can be arranged and combined , So that the result of the operation of the formula composed of these four numbers is equal to 24.”
To solve this problem , Let's look at the characteristics of an arithmetic expression composed of four numbers :
Although there is a lot of double counting , But without considering the time complexity , And when the amount of computation is not large , It can make the computer perform exhaustive computation , Check all the possibilities . So we can divide the code implementation process into three steps :
therefore , Regardless of the existence of duplication , Up to a total of 24*64*8 = 12288 Maybe . This amount of computation may be prohibitive for humans , But it's hardly worth mentioning for a computer . What's more? , We don't have to find all the right answers , But just find one .
Now let's start writing code :
The first thing we have to do , Is to extract 4 Cards are converted into numbers . Because the number of playing cards is from 0 To 51, But the figures represented for calculation are from 1 To 13, In fact, this can be achieved by simple complement operation .
therefore , Let's define a function :
def calnum(n):
global nums
nums=[i%13+1 for i in n]
formula=form(nums)
This function takes a variable n, On behalf of contain 4 A list of cards , And then generate the formula from the list ( Or derivation ) Convert it into a list of numbers actually used for calculation nums. Then call another custom function form() Convert this list into a list containing the most 12288 A list of expressions , Save as formula.
It should be noted that , We have to put nums Declared as a global variable . The only purpose of this is , It is to judge the input of players , Whether only the given 4 A digital . therefore , By the way, we will judge the function entered by the player is_right() Also updated as follows :
def is_right(txt):
# Omit code
if sorted(txt)!=sorted(nums):
tm.showinfo(message=' Please use the given number !')
return False
# Omit code
Let's go on to write form() function .
In order not to make wheels repeatedly , We can use it directly Python Provides built-in modules to calculate permutations .
from itertools import permutations
def form(nums):
numlist=set(permutations(nums))
from itertools Import in module permutations After the function , You can use it to calculate the permutation of the list . This function takes two arguments , The first is an iteratable object such as a list , The second is numbers , It means to take several elements from the previous list and arrange them , It can be omitted , If omitted , It means that all elements are arranged and combined by default . So here , We can omit the second parameter , Directly will contain 4 A number of nums Give the list to permutations function , Returns an iteratable object . meanwhile , In order to get heavy , For example, there are repeated numbers in four cards ,3,3,4,4 such , We can convert this result into a set set, Finally save the results in numlist in .
Test results on the console :
>>> from itertools import permutations
>>> nums = [1,2,3,4]
>>> numlist = permutations(nums)
>>> type(numlist)
<class 'itertools.permutations'>
>>> for i in numlist: print(i,end=' ')
(1, 2, 3, 4) (1, 2, 4, 3) (1, 3, 2, 4) (1, 3, 4, 2) (1, 4, 2, 3) (1, 4, 3, 2) (2, 1, 3, 4) (2, 1, 4, 3) (2, 3, 1, 4) (2, 3, 4, 1) (2, 4, 1, 3) (2, 4, 3, 1) (3, 1, 2, 4) (3, 1, 4, 2) (3, 2, 1, 4) (3, 2, 4, 1) (3, 4, 1, 2) (3, 4, 2, 1) (4, 1, 2, 3) (4, 1, 3, 2) (4, 2, 1, 3) (4, 2, 3, 1) (4, 3, 1, 2) (4, 3, 2, 1)
>>>
You can see , As we expected ,4 A non repeating number can make up the most 24 Two non repeating combinations .
Next , We need to insert three operators between these four numbers , Of course , We first have to find the operators that make up 64 Combinations of , It can be realized by using three-layer loop , And save the results in the list operations in .
operations=[]
for i in '+-*/':
for j in '+-*/':
for k in '+-*/':
operation = i+j+k
operations.append(operation)
Because this list will be called frequently ( Every four cards dealt , Just insert the operator ), So we can put it in the main program , In this way, only one calculation is required at the beginning of the game , You can always call... In a function ( And don't modify ).
next , In function form in , We use the same for Loop to 3 Operators inserted into 4 Between numbers :
def form(nums):
numlist=set(permutations(nums))
combo=[]
for num in numlist:
for i in operations:
temp=[]
for j in range(3):
temp+=[str(num[j]),i[j]]
temp.append(str(num[j+1]))
combo.append(temp)
Because eventually we need to convert the expression to a string , So here we can use str Function to convert a number to a string , Save in the list combo in . According to the previous calculation ,combo There should be at most 24*64 = 1536 Elements , On behalf of 1536 Expression . But now they are still single characters , Because we also need to insert parentheses .
Parentheses are used to raise the level of operation 、 Change the order of operations . According to the previous analysis , Because the default calculation order is multiply and divide first 、 Add or subtract after 、 From left to right , So the case without parentheses (a+b+c+d) Must be equivalent to some kind of use of parentheses . So in the end we just need to consider 8 A case of parentheses : Three cases of a pair of parentheses , And five cases of two pairs of parentheses . But how to insert it ?
Through the previous calculation , What we got combo The expression list in the two-dimensional list should look like this :
[‘1’, ‘+’, ‘2’, ‘+’, ‘3’, ‘+’, ‘4’]
so , Each expression contains 7 String elements (4 A digital 、3 Operators ) A list of . therefore , We just need to find the position where we need to add parentheses in advance ( Indexes ), You can add through a loop .
By comparison , We will consider two cases ( A pair and two pairs of parentheses ) The position index of the left and right parentheses of , And create the following list :
one=[(3,0),(5,2),(7,4)]
two=[(7,3,5,0),(5,3,0,0),(5,5,2,0),(7,5,2,2),(7,7,4,2)]
It should be noted that , We have to insert the right parentheses first , Then insert the left parenthesis . Because after inserting the element , The length of the list changes , For the sake of calculation , Insert the right parentheses first to maintain the relative position to the greatest extent . The same is true of two pairs of parentheses ( Insert the right bracket first ), Just pay attention to one of these situations :(a+b)+(c+d). under these circumstances , Insert two closing parentheses , The position of the left bracket sandwiched between the two right brackets has changed , Just record the new location .
therefore , Through the index list , We can form Function completion :
def form(nums):
numlist=set(permutations(nums))
combo=[]
for num in numlist:
for i in operations:
temp=[]
for j in range(3):
temp+=[str(num[j]),i[j]]
temp.append(str(num[j+1]))
combo.append(temp)
one=[(3,0),(5,2),(7,4)]
two=[(7,3,5,0),(5,3,0,0),(5,5,2,0),(7,5,2,2),(7,7,4,2)]
formula=[]
for i in combo:
for j in one:
temp=i[:]
temp.insert(j[0],')')
temp.insert(j[1],'(')
formula.append(''.join(temp)) # Convert list to string
for j in two:
temp=i[:]
temp.insert(j[0],')')
temp.insert(j[1],')')
temp.insert(j[2],'(')
temp.insert(j[3],'(')
formula.append(''.join(temp)) # Convert list to string
return formula
Last , We get the most 12288 A list of expressions formula, And return to the function calnum Use in eval Function to calculate .
def calnum(n):
# Omit code
formula=form(nums)
for i in formula:
try:
result = eval(i)
except:
continue
if math.isclose(result,24):
return i
return 'None'
adopt for Loop traversal formula list , Calculate whether the result is equal to 24. If correct , Put the correct answer ( expression ) return , If you traverse all 12288 None of these possibilities will come to fruition , Then return the string None.
According to our previous logic , In every grab 4 After cards , We all need computers to help us calculate , Look at the current 4 Can a card calculate 24 spot , If not , Will automatically enter the next round ( Recapture ), If possible , Then save the answer in the variable right_answer in , Convenient tips (hint) Button call . therefore , We can update the code of the corresponding part :
def draw_card():
global cv_card, right_answer
invalid=True
while invalid:
clear(cv_card)
draw=[]
if len(cardnum)==0:
tm.showinfo(message=' The deck has been used up , Reshuffle your cards ')
shuffle_card()
for i in range(4):
draw.append(cardnum.pop())
cv_card.append(cv.create_image(100,200,image=card[draw[i]]))
if len(cardnum)==0:cv.delete(cv_back)
for _ in range(150*(i+1)):
cv.move(cv_card[i],1,0)
cv.update()
right_answer = calnum(draw) # Call function calculation 12288 Maybe
if right_answer=='None':
tm.showinfo(message=' This group of figures has no solution , Automatically change the next group for you ')
else:
countdown()
if len(ideas)>0: btn['state']=NORMAL
invalid=False
If 4 Cards cannot be counted 24 spot , You need to draw again all the time , Until it can be calculated . So here we use a while loop , And give a token invalid Assume that the current combination cannot be calculated 24 spot . Only when you get the answer (right_answer The value is not None),invalid Turn into False, end while loop , Start timing and other follow-up procedures .
Come here , We are “ Quick calculation 24 spot ” The little game of . You can continue to add other functions you want , Change the layout 、 Color, etc . The final operation effect is as follows :
from tkinter import *
import tkinter.messagebox as tm
import random
import math
from itertools import permutations
def shuffle_card():
global cardnum, back, cv_back
cardnum = list(range(52))
random.shuffle(cardnum)
back = PhotoImage(file=r"image\poker\back1.png")
cv_back = cv.create_image(100,200,image = back)
def clear(cv_card):
for i in cv_card:
cv.delete(i)
cv_card.clear()
def draw_card():
global cv_card, right_answer
invalid=True
while invalid:
clear(cv_card)
draw=[]
if len(cardnum)==0:
tm.showinfo(message=' The deck has been used up , Reshuffle your cards ')
shuffle_card()
for i in range(4):
draw.append(cardnum.pop())
cv_card.append(cv.create_image(100,200,image=card[draw[i]]))
if len(cardnum)==0:cv.delete(cv_back)
for _ in range(150*(i+1)):
cv.move(cv_card[i],1,0)
cv.update()
right_answer = calnum(draw)
if right_answer=='None':
tm.showinfo(message=' This group of figures has no solution , Automatically change the next group for you ')
else:
countdown()
if len(ideas)>0: btn['state']=NORMAL
invalid=False
def initialize():
global angle,count,cv_arc,cv_inner,cv_text
count=90
angle=360
btn['state']=DISABLED
answer.set('')
cv_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 =(' Microsoft YaHei ',20,'bold'),fill='red')
draw_card()
def countdown():
global angle,count,cv_arc,cv_inner,cv_text,cd
if angle == 360:
angle -= 1
else:
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 -= 1
if angle%4 == 0: count-=1
cv_inner=cv.create_oval(120,350,180,410,fill='yellow',outline='yellow')
cv_text=cv.create_text(150,380,text=count,font =(' Microsoft YaHei ',20,'bold'),fill='red')
if count==0:
tm.showinfo(message=' The countdown is over ! Automatically enter the next round ')
level.set(int(level.get())+1)
cv.delete(cv_arc)
cv.delete(cv_inner)
cv.delete(cv_text)
initialize()
else:
cd = root.after(250,countdown)
def myanswer(event):
s=event.keysym
txt=answer.get()
if s=='BackSpace':
txt=txt[:-1]
elif s=='Return':
if is_right(txt):
level.set(int(level.get())+1)
score.set(int(score.get())+1)
root.after_cancel(cd)
c = tm.askyesno(message=' Go on to the next game ?')
if c:
cv.delete(cv_arc)
cv.delete(cv_inner)
cv.delete(cv_text)
initialize()
return
else:root.destroy()
else:
txt=''
elif s.isnumeric():
txt+=s
elif s in trans:
txt+=trans[s]
answer.set(txt)
def is_right(txt):
try:
result = eval(txt)
except:
tm.showinfo(message=' The formula is incorrect , Please re-enter !')
return False
for i in '+-*/()':
txt=txt.replace(i,' ')
txt=[int(i) for i in txt.split()]
if sorted(txt)!=sorted(nums):
tm.showinfo(message=' Please use the given number !')
return False
if math.isclose(result,24):
tm.showinfo(message=' Congratulations ! Correct answer !')
return True
def hint():
answer.set(right_answer)
btn['state']=DISABLED
idea = ideas.pop()
cv.delete(idea)
def calnum(n):
global nums
nums=[i%13+1 for i in n]
formula=form(nums)
for i in formula:
try:
result = eval(i)
except:
continue
if math.isclose(result,24):
return i
return 'None'
def form(nums):
numlist=set(permutations(nums))
combo=[]
for num in numlist:
for i in operations:
temp=[]
for j in range(3):
temp+=[str(num[j]),i[j]]
temp.append(str(num[j+1]))
combo.append(temp)
one=[(3,0),(5,2),(7,4)]
two=[(7,3,5,0),(5,3,0,0),(5,5,2,0),(7,5,2,2),(7,7,4,2)]
formula=[]
for i in combo:
for j in one:
temp=i[:]
temp.insert(j[0],')')
temp.insert(j[1],'(')
formula.append(''.join(temp))
for j in two:
temp=i[:]
temp.insert(j[0],')')
temp.insert(j[1],')')
temp.insert(j[2],'(')
temp.insert(j[3],'(')
formula.append(''.join(temp))
return formula
# The game starts here
HINT = 3
operations=[]
for i in '+-*/':
for j in '+-*/':
for k in '+-*/':
operation = i+j+k
operations.append(operation)
trans={
'plus':'+','minus':'-','asterisk':'*','slash':'/','parenleft':'(','parenright':')'}
root = Tk()
root.geometry('800x500+400+200')
root.resizable(0,0)
root.title(' Quick calculation 24 spot ')
# The canvas is the same size as the main window
cv = Canvas(root,width=800,height=500)
# Background image
bg = PhotoImage(file=r"image\poker\bg.png")
cv_bg = cv.create_image(400,250,image = bg)
# Title picture
title = PhotoImage(file=r"image\poker\title.png")
cv_title = cv.create_image(400,60,image = title)
# Display the answers, level scores and other information
answer=StringVar()
level=IntVar()
score=IntVar()
cv.create_text(400,350,text=' Please enter your answer ',font =(' Square regular script, simplified Chinese ',18,'bold'))
lb = Label(root,text='',font=(' Microsoft YaHei ',15),textvariable=answer,bg='lightyellow')
cv_lb = cv.create_window(400,400,window=lb)
cv.create_text(600,350,text=' Tested :',font =(' Square regular script, simplified Chinese ',16,'bold'))
cv.create_text(600,400,text=' Passed :',font =(' Square regular script, simplified Chinese ',16,'bold'))
level_lable = Label(root,text='',font=(' Microsoft YaHei ',15),textvariable=level,bg='lightyellow')
cv_level = cv.create_window(670,350,window=level_lable)
score_lable = Label(root,text='',font=(' Microsoft YaHei ',15),textvariable=score,bg='lightyellow')
cv_score = cv.create_window(670,400,window=score_lable)
# Prompt pictures and buttons
idea = PhotoImage(file=r"image\poker\idea.png")
ideas = []
for i in range(HINT):
ideas.append(cv.create_image(450+i*25,450,image = idea))
btn = Button(root,text=' Tips ',width=5,command=hint)
cv_btn = cv.create_window(400,450,window=btn)
# Binding gets input from the keyboard <Key>, And pass it to the user-defined function myanswer
lb.bind('<Key>',myanswer)
# Get the tag component into focus , Otherwise, you cannot input from the keyboard
lb.focus_set()
card = [PhotoImage(file=f'image/poker/{
i:0>2}.png') for i in range(1,53)]
cv_card=[]
cv.pack()
shuffle_card()
initialize()
root.mainloop()