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 = 3idea = 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 :
Disable the button , To prevent players from accidentally clicking many times , Thus, the prompt times are wasted
One less “ Small bulb ” 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']=DISABLEDdef 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 :
clear list 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 :
There are two ways to consider the case of parentheses :
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 :
find 3 Operators ( Add, subtract, multiply and divide ) The permutation of , Because operators can be reused , So it is 4^3=64 Maybe .( There are some combinations that are impossible to calculate 24 Dot , For example, three consecutive minus signs or division signs , But if you add parentheses to change the calculation order , The results will be different . In order to save trouble , All permutations and combinations are considered here )
Add parentheses in different positions of the expression . According to the above , All in all 8 Maybe . 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 permutationsdef 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 .
IntVar type
permutations function 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 tmimport randomimport mathfrom itertools import permutationsdef 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=Falsedef 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 Truedef 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 = 3operations=[]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 myanswerlb.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()
author : Please ask me to ask brother
Game programming , A game development favorite ~
If the picture is not displayed for a long time , Please use Chrome Kernel browser .