''' Python 3.6.5 code using Tkinter graphical user interface. Combination of '24 game' and '24 game/Solve' allowing user or random selection of 4-digit number and user or computer solution. Note that all computer solutions are displayed''' from tkinter import * from tkinter import messagebox from tkinter.scrolledtext import ScrolledText # 'from tkinter import scrolledtext' in later versions? import random import itertools # ************************************************ class Game: def __init__(self, gw): self.window = gw self.digits = '0000' a1 = "(Enter '4 Digits' & click 'My Digits'" a2 = "or click 'Random Digits')" self.msga = a1 + '\n' + a2 b1 = "(Enter 'Solution' & click 'Check Solution'" b2 = "or click 'Show Solutions')" self.msgb = b1 + '\n' + b2 # top frame: self.top_fr = Frame(gw, width=600, height=100, bg='dodger blue') self.top_fr.pack(fill=X) self.hdg = Label(self.top_fr, text=' 24 Game ', font='arial 22 bold', fg='navy', bg='lemon chiffon') self.hdg.place(relx=0.5, rely=0.5, anchor=CENTER) self.close_btn = Button(self.top_fr, text='Quit', bd=5, bg='navy', fg='lemon chiffon', font='arial 12 bold', command=self.close_window) self.close_btn.place(relx=0.07, rely=0.5, anchor=W) self.clear_btn = Button(self.top_fr, text='Clear', bd=5, bg='navy', fg='lemon chiffon', font='arial 12 bold', command=self.clear_screen) self.clear_btn.place(relx=0.92, rely=0.5, anchor=E) # bottom frame: self.btm_fr = Frame(gw, width=600, height=500, bg='lemon chiffon') self.btm_fr.pack(fill=X) self.msg = Label(self.btm_fr, text=self.msga, font='arial 16 bold', fg='navy', bg='lemon chiffon') self.msg.place(relx=0.5, rely=0.1, anchor=CENTER) self.user_dgt_btn = Button(self.btm_fr, text='My Digits', width=12, bd=5, bg='navy', fg='lemon chiffon', font='arial 12 bold', command=self.get_digits) self.user_dgt_btn.place(relx=0.07, rely=0.2, anchor=W) self.rdm_dgt_btn = Button(self.btm_fr, text='Random Digits', width=12, bd=5, bg='navy', fg='lemon chiffon', font='arial 12 bold', command=self.gen_digits) self.rdm_dgt_btn.place(relx=0.92, rely=0.2, anchor=E) self.dgt_fr = LabelFrame(self.btm_fr, text=' 4 Digits ', bg='dodger blue', fg='navy', bd=4, relief=RIDGE, font='arial 12 bold') self.dgt_fr.place(relx=0.5, rely=0.27, anchor=CENTER) self.digit_ent = Entry(self.dgt_fr, justify='center', font='arial 16 bold', fg='navy', disabledforeground='navy', bg='lemon chiffon', disabledbackground='lemon chiffon', bd=4, width=6) self.digit_ent.grid(row=0, column=0, padx=(8,8), pady=(8,8)) self.chk_soln_btn = Button(self.btm_fr, text='Check Solution', state='disabled', width=14, bd=5, bg='navy', fg='lemon chiffon', font='arial 12 bold', command=self.check_soln) self.chk_soln_btn.place(relx=0.07, rely=.42, anchor=W) self.show_soln_btn = Button(self.btm_fr, text='Show Solutions', state='disabled', width=14, bd=5, bg='navy', fg='lemon chiffon', font='arial 12 bold', command=self.show_soln) self.show_soln_btn.place(relx=0.92, rely=.42, anchor=E) self.soln_fr = LabelFrame(self.btm_fr, text=' Solution ', bg='dodger blue', fg='navy', bd=4, relief=RIDGE, font='arial 12 bold') self.soln_fr.place(relx=0.07, rely=0.58, anchor=W) self.soln_ent = Entry(self.soln_fr, justify='center', font='arial 16 bold', fg='navy', disabledforeground='navy', bg='lemon chiffon', disabledbackground='lemon chiffon', state='disabled', bd=4, width=15) self.soln_ent.grid(row=0, column=0, padx=(8,8), pady=(8,8)) self.solns_fr = LabelFrame(self.btm_fr, text=' Solutions ', bg='dodger blue', fg='navy', bd=4, relief=RIDGE, font='arial 12 bold') self.solns_fr.place(relx=0.92, rely=0.5, anchor='ne') self.solns_all = ScrolledText(self.solns_fr, font='courier 14 bold', state='disabled', fg='navy', bg='lemon chiffon', height=8, width=14) self.solns_all.grid(row=0, column=0, padx=(8,8), pady=(8,8)) # validate '4 Digits' entry. # save if valid and switch screen to solution mode. def get_digits(self): txt = self.digit_ent.get() if not(len(txt) == 4 and txt.isdigit()): self.err_msg('Please enter 4 digits (eg 1357)') return self.digits = txt # save self.reset_one() # to solution mode return # generate 4 random digits, display them, # save them, and switch screen to solution mode. def gen_digits(self): self.digit_ent.delete(0, 'end') self.digits = ''.join([random.choice('123456789') for i in range(4)]) self.digit_ent.insert(0, self.digits) # display self.reset_one() # to solution mode return # switch screen from get digits to solution mode: def reset_one(self): self.digit_ent.config(state='disabled') self.user_dgt_btn.config(state='disabled') self.rdm_dgt_btn.config(state='disabled') self.msg.config(text=self.msgb) self.chk_soln_btn.config(state='normal') self.show_soln_btn.config(state='normal') self.soln_ent.config(state='normal') return # edit user's solution: def check_soln(self): txt = self.soln_ent.get() # user's expression d = '' # save digits in expression dgt_op = 'd' # expecting d:digit or o:operation for t in txt: if t not in '123456789+-*/() ': self.err_msg('Invalid character found: ' + t) return if t.isdigit(): if dgt_op == 'd': d += t dgt_op = 'o' else: self.err_msg('Need operator between digits') return if t in '+-*/': if dgt_op == 'o': dgt_op = 'd' else: self.err_msg('Need digit befor operator') return if sorted(d) != sorted(self.digits): self.err_msg("Use each digit in '4 Digits' once") return try: # round covers up Python's # representation of floats if round(eval(txt),5) == 24: messagebox.showinfo( 'Success', 'YOUR SOLUTION IS VADLID!') self.show_soln() # show all solutions return except: self.err_msg('Invalid arithmetic expression') return messagebox.showinfo( 'Failure', 'Your expression does not yield 24') return # show all solutions: def show_soln(self): # get all sets of 3 operands: ('+', '+', '*'), ...) ops = ['+-*/', '+-*/', '+-*/'] combs = [p for p in itertools.product(*ops)] # get unique permutations for requested 4 digits: d = self.digits perms = set([''.join(p) for p in itertools.permutations(d)]) # list of all (hopefully) expressions for # 4 operands and 3 operations: formats = ['Aop1Bop2Cop3D', '(Aop1Bop2C)op3D', '((Aop1B)op2C)op3D', '(Aop1(Bop2C))op3D', 'Aop1Bop2(Cop3D)', 'Aop1(Bop2C)op3D', '(Aop1B)op2Cop3D', '(Aop1B)op2(Cop3D)', 'Aop1(Bop2Cop3D)', 'Aop1((Bop2C)op3D)', 'Aop1(Bop2(Cop3D))'] lox = [] # list of valid expressions for fm in formats: # pick a format for c in combs: # plug in 3 ops f = fm.replace('op1', c[0]) f = f.replace('op2', c[1]) f = f.replace('op3', c[2]) for A, B, C, D in perms: # plug in 4 digits x = f.replace('A', A) x = x.replace('B', B) x = x.replace('C', C) x = x.replace('D', D) try: # evaluate expression # round covers up Python's # representation of floats if round(eval(x),5) == 24: lox.append(' ' + x) except ZeroDivisionError: # can ignore these continue if lox: txt = '\n'.join(x for x in lox) else: txt =' No Solution' self.solns_all.config(state='normal') self.solns_all.insert('end', txt) # show solutions self.solns_all.config(state='disabled') self.chk_soln_btn.config(state='disabled') self.show_soln_btn.config(state='disabled') self.soln_ent.config(state='disabled') return def err_msg(self, msg): messagebox.showerror('Error Message', msg) return # restore screen to it's 'initial' state: def clear_screen(self): self.digits = '' self.digit_ent.config(state='normal') self.user_dgt_btn.config(state='normal') self.rdm_dgt_btn.config(state='normal') self.digit_ent.delete(0, 'end') self.chk_soln_btn.config(state='disabled') self.show_soln_btn.config(state='disabled') self.soln_ent.config(state='normal') self.soln_ent.delete(0, 'end') self.soln_ent.config(state='disabled') self.msg.config(text=self.msga) self.clear_solns_all() return # clear the 'Solutions' frame. # note: state must be 'normal' to change data def clear_solns_all(self): self.solns_all.config(state='normal') self.solns_all.delete(1.0, 'end') self.solns_all.config(state='disabled') return def close_window(self): self.window.destroy() # ************************************************ root = Tk() root.title('24 Game') root.geometry('600x600+100+50') root.resizable(False, False) g = Game(root) root.mainloop()