'''
Tkinter Number Guessing Game
Guess the random number between 1 and 100
'''
import tkinter as tk
from random import randint
class Model:
'''
Model class handles comparison and generate a random number
'''
def __init__(self, number=None):
self.number = number
def __lt__(self, other):
return self.number < other.number
def __gt__(self, other):
return self.number > other.number
def __eq__(self, other):
return self.number == other.number
def rnd_num(self):
return randint(1, 100)
class View:
'''
View class handles the gui view
'''
def __init__(self, parent):
# Set class variables and configure root grid
parent.columnconfigure(0, weight=1)
parent.rowconfigure(0, weight=1)
# Container will hold all widgets
container = tk.Frame(parent, relief='ridge', border=3)
container.grid(column=0, row=0, sticky='news')
container.grid_columnconfigure(0, weight=3)
# Create our header widget
header = tk.Label(container)
header['text'] = 'Tkinter Number Guessing Game'
header['font'] = (None, 16, 'bold')
header['fg'] = 'blue'
header.grid(column=0, row=0, sticky='new', padx=5, pady=3)
# Create the direction header
header2 = tk.Label(container, anchor='w')
header2['text']= 'Choose a number between 1 and 100'
header2['bg'] = 'lightyellow'
header2.grid(column=0, row=1, sticky='new', padx=5, pady=3)
# try_frame will hold the permanent static text as well as the variable text
try_frame = tk.Frame(container)
try_frame.grid(column=0, row=2, sticky='new', padx=5, pady=3)
try_frame.grid_columnconfigure(0, weight=0)
try_frame.grid_columnconfigure(1, weight=3)
# Static text label
try_text = tk.Label(try_frame, anchor='w')
try_text['text'] = 'Tries:'
try_text['font'] = (None, 10, 'bold')
try_text.grid(column=0, row=0, sticky='new')
# Variable text label
self.try_guesses = tk.Label(try_frame, anchor='w', text='0')
self.try_guesses['font'] = (None, 10, 'normal')
self.try_guesses.grid(column=1, row=0, sticky='nw')
# Entry field for entering a number
self.entry = tk.Entry(container)
self.entry.focus()
self.entry.grid(column=0, row=3, sticky='new', padx=5, pady=3)
# A Button for submitting a number
self.button = tk.Button(container, text='Guess')
self.button.grid(column=0, row=4, padx=5, pady=3, sticky='ne')
# msg_frame will hold various message that will be displayed
msg_frame = tk.Frame(container, relief='ridge')
msg_frame['borderwidth'] = 1
msg_frame.grid(column=0, row= 5, sticky='new', padx=8)
msg_frame.grid_columnconfigure(0, weight=0)
msg_frame.grid_columnconfigure(1, weight=3)
# msg_header will hold static text
msg_header = tk.Label(msg_frame, text='MSG:', anchor='w', padx=5)
msg_header['font'] = (None, 10, 'bold')
msg_header.grid(column=0, row=0, sticky='new', padx=5, pady=3)
# self.msg is a label for displaying dynamic/changing text messages
self.msg = tk.Label(msg_frame, anchor='w')
self.msg['text'] = ''
self.msg['font'] = (None, 10, 'normal')
self.msg.grid(column=1, row=0, sticky='new', padx=5, pady=3)
class Controller:
'''
Controller class will handle communications between Model and View classes
'''
def __init__(self, view):
'''
Set some instant variables and get our first random number
'''
self.view = view
self.rand_number = Model().rnd_num()
self.counter = 0
# Set out button command and also bind the Return/Enter keys as well as
# number pad enter key to the entry field. This allows us to press them
# as well as use the submit button
self.view.button['command'] = lambda x=self.view.entry: self.callback(self.view.entry)
self.view.entry.bind('<Return>', lambda x: self.callback(self.view.entry))
self.view.entry.bind('<KP_Enter>', lambda x: self.callback(self.view.entry))
def callback(self, arg):
'''
Callback function does some error checking and does the comparison
of entered number and the random number. The try block ensures that
only whole numbers are entered. If no errors continue to the else block
'''
try:
number = int(arg.get())
except ValueError:
self.view.msg['text'] = 'Error! Only whole numbers are allowed.'
self.view.msg['fg'] = 'red'
else:
# Set a text color for messages, increase counter, update counter text
self.view.msg['fg'] = 'blue'
self.counter += 1
self.view.try_guesses['text'] = f'{self.counter}'
# Do the comparison and return the correct message
if Model(number=number) > Model(number=self.rand_number):
self.view.msg['text'] = f'{number} is too high.'
if Model(number) < Model(self.rand_number):
self.view.msg['text'] = f'{number} is too low.'
if Model(number) == Model(self.rand_number):
self.view.msg['text'] = f'Congradulations! You guessed the number {self.rand_number}'
self.view.msg['fg'] = 'green'
# The correct number has been guessed, call this function to disable some of
# the functions and change the button text and command to call the reset function
self.disable()
# Clear the entry field
self.view.entry.delete(0, tk.END)
def disable(self):
'''
This function disables some of the widgets and
changes the button text and command
'''
self.view.entry.unbind('<Return>')
self.view.entry.unbind('<KP_Enter>')
self.view.entry['state'] = 'disabled'
self.view.button['text'] = 'Reset'
self.view.button['command'] = self.reset
def reset(self):
'''
Reset function restores all counts functions and widgets
'''
self.counter = 0
self.view.button['text'] = 'Guess'
self.view.button['command'] = lambda x=self.view.entry: self.callback(self.view.entry)
self.view.entry.bind('<Return>', lambda x: self.callback(self.view.entry))
self.view.entry.bind('<KP_Enter>', lambda x: self.callback(self.view.entry))
self.view.entry['state'] = 'normal'
self.view.try_guesses['text'] = '0'
self.view.msg['text'] = ''
self.view.entry.delete(0, tk.END)
self.rand_number = Model().rnd_num()
if __name__ == '__main__':
root = tk.Tk()
root.title('Tkinter Number Guessing Game')
root.update()
root.minsize(500, root.winfo_height())
controller = Controller(View(root))
root.mainloop()