Introduction
While working on a little game of mine written using python I came across the need for a chat log that had some basic functionality, namely it could:
- consume text input submitted by the player
- store text input submitted by the player
- send text to the player in the console
- have different color tags based on how the text was submitted
This chat log has no network capabilities, as the game I’m making is single-player, and there’s no need for it at the moment. I am by no means an expert in Tkinter, but I was able to accomplish the above goals by doing a bit of research. If there are experts out there who see things that could be improved with this little project feel free to comment!
Required tech
The Chat Log
When thinking of a standard chat log it really consists of just a few things, namely a log where the text is displayed, a text box where data is entered, a submit button, and maybe a header. If you want to skip the explanation of the code and just go to the output, click the Results or Project Code section in the table of contents.
GUI
To create the GUI I used Tkinter, which is a well known and popular way to create guis. I’ve pulled the code out of my game structure to illustrate the route I went to get the chatbox to satisfy the requirements I outlined in the Introduction. First, we’ve got our import statements:
import copy import threading import Tkinter
We’ll use copy to make a copy of the player input. In the game this player input is stored so it can be used later, but in our example here it’s only used once for illustrative purposes. The same can be said for threading, we’ll need our gui to spawn a thread that runs a toy chat example. This is necessary because Tkinter will timeout if threading isn’t used to execute the functions.
The following code snippets are taken from the GUI class.
# GLOBALS chat_log = None chat_log_input = None def __init__(self): # root business self.root = Tkinter.Tk() self.root.title("a chat log") # chat_frame self.chat_frame = Tkinter.Frame(self.root) self.chat_frame.grid(row=1, column=0) self.chat_log_title = None self.scroll = None self.chat_log_submit = None # bindings self.root.bind("<Return>", LOGGER.receive)
Our GUI consists of a few main pieces. First I wanted the chat log and its input box to be accessible from elsewhere in my code, so I made them global class variables. There will only be one instance of this class floating around, so I’m ok with that. In our __init__ we generate root, the chat widget, and its components. This consists of a Tkinter Frame filled with the Label, Entry, Text, and Scrollbar widgets.
The binding allows the user to press Enter on their keyboard and send the data through so it can be processed. It uses a callback function inside our Logger class that we’ll look at soon called receive.
def build(self): """put it all together""" self.compileChatFrame() self.root.mainloop()
We use the build method to compile everything together, and finally execute the mainloop once all of the widgets are defined.
def compileChatFrame(self): """build the chat log frame widget""" # chat log title self.chat_log_title = Tkinter.Label(self.chat_frame, text='Chat Log') self.chat_log_title.grid(row=0, column=0, sticky=Tkinter.W) # chat log GUI.chat_log = Tkinter.Text(self.chat_frame) GUI.chat_log.config(state=Tkinter.DISABLED) GUI.chat_log.tag_configure(tagName=COMMAND_LOG_TYPE, foreground='purple') GUI.chat_log.tag_configure(tagName=NARRATOR_LOG_TYPE, foreground='#0000A0') GUI.chat_log.tag_configure(tagName=PLAYER_LOG_TYPE, foreground='black') GUI.chat_log.tag_configure(tagName=SYSTEM_LOG_TYPE, foreground='#FF00FF') GUI.chat_log.grid(row=1, column=0, sticky=Tkinter.W) # scrollbar self.scroll = Tkinter.Scrollbar(self.chat_frame, orient=Tkinter.VERTICAL, command=GUI.chat_log.yview) GUI.chat_log.configure(yscrollcommand=self.scroll.set) self.scroll.config(command=GUI.chat_log.yview) self.scroll.grid(row=1, column=1, sticky='nsew') # chat input GUI.chat_log_input = Tkinter.Entry(self.chat_frame, background='gray') GUI.chat_log_input.grid(row=2, sticky='nsew') GUI.chat_log_input.focus() # chat submit self.chat_log_submit = Tkinter.Button(self.chat_frame, text='Submit', command=LOGGER.receive) self.chat_log_submit.grid(row=2, column=0, columnspan=2, sticky=Tkinter.E)
When we call the method compileChatFrame all of the widgets are linked together. Tkinters grid system was used to place all of the widgets appropriately.
The chat log is given a header that uses the Label widget, and it has a fixed text value in this example. The chat log itself used tags to add color to the various messages as they come through to help distinguish them from each other.
The scrollbar is added to the right of the chat log, and is configured in the usual way a scrollbar is. You can see how configure is used to link the chat log and scroll bar together.
The chat input uses the Tkinter Entry widget, and it’s placed below the chat log. A submit button is placed to the right of the chat input, and calls LOGGER.receive when pressed.
This completes the GUI part of our little project. You can view the entire class, and the final product below.
class GUI(object): """build the game UI""" # GLOBALS chat_log = None chat_log_input = None def __init__(self): # root business self.root = Tkinter.Tk() self.root.title("a chat log") # chat_frame self.chat_frame = Tkinter.Frame(self.root) self.chat_frame.grid(row=1, column=0) self.chat_log_title = None self.scroll = None self.chat_log_submit = None # bindings self.root.bind("<Return>", LOGGER.receive) def build(self): """ put it all together""" self.compileChatFrame() self.root.mainloop() def compileChatFrame(self): """build the chat log frame widget""" # chat log title self.chat_log_title = Tkinter.Label(self.chat_frame, text='Chat Log') self.chat_log_title.grid(row=0, column=0, sticky=Tkinter.W) # chat log GUI.chat_log = Tkinter.Text(self.chat_frame) GUI.chat_log.config(state=Tkinter.DISABLED) GUI.chat_log.tag_configure(tagName=COMMAND_LOG_TYPE, foreground='purple') GUI.chat_log.tag_configure(tagName=NARRATOR_LOG_TYPE, foreground='#0000A0') GUI.chat_log.tag_configure(tagName=PLAYER_LOG_TYPE, foreground='black') GUI.chat_log.tag_configure(tagName=SYSTEM_LOG_TYPE, foreground='#FF00FF') GUI.chat_log.grid(row=1, column=0, sticky=Tkinter.W) # scrollbar self.scroll = Tkinter.Scrollbar(self.chat_frame, orient=Tkinter.VERTICAL, command=GUI.chat_log.yview) GUI.chat_log.configure(yscrollcommand=self.scroll.set) self.scroll.config(command=GUI.chat_log.yview) self.scroll.grid(row=1, column=1, sticky='nsew') # chat input GUI.chat_log_input = Tkinter.Entry(self.chat_frame, background='gray') GUI.chat_log_input.grid(row=2, sticky='nsew') GUI.chat_log_input.focus() # chat submit self.chat_log_submit = Tkinter.Button(self.chat_frame, text='Submit', command=LOGGER.receive) self.chat_log_submit.grid(row=2, column=0, columnspan=2, sticky=Tkinter.E)
Logger
The concept of the Logger class is that it will handle sending and receiving messages in the chat log. One instance of this class would be created that handles the task.
The following code is inside the Logger class. First, within the __init__ we define a few variables that we’ll use to send the messages.
def __init__(self): self.message = None self.message_type = None self.received = False self.stored = None
The message variable is the actual text that makes up the message, while message_type will be used with the tags we defined in the GUI to give color to the messages. The received variable will be used to control a loop that waits for player input, and stored is a record of the last input the player provided, excluding commands.
def bm(self, message, message_type): """update the GR with a new message so it can be sent using receive/transmit""" self.message_type = message_type if self.message_type == PLAYER_LOG_TYPE: self.message = '[You]: ' + message elif self.message_type == SYSTEM_LOG_TYPE: self.message = '[System]: ' + message elif self.message_type == NARRATOR_LOG_TYPE: self.message = '[Narrator]: ' + message elif self.message_type == COMMAND_LOG_TYPE: self.message = '[Command]: ' + message else: self.message = message
The first method in the class is bm, which is short for “build message”. All it does it update self.message and self.message_type with the message that needs to be sent, and it attaches a string to the front of the message that will be used with the tags we defined earlier.
def transmit(self): """transmit the message to the chat log""" if self.message: GAME_UI.chat_log.config(state=Tkinter.NORMAL) GAME_UI.chat_log.insert(Tkinter.END, "\n" + self.message) GAME_UI.chat_log_input.delete(0, Tkinter.END) # attempt to find a message type for coloring if self.message_type is not None: pattern = '[' + self.message_type + ']: GAME_UI.chat_log.color_message_type(pattern=pattern, tag=self.message_type) GAME_UI.chat_log.see("end") GAME_UI.chat_log.config(state=Tkinter.DISABLED) else: print('attempted to transmit an empty message') GAME_UI.chat_log.config(state=Tkinter.NORMAL) GAME_UI.chat_log_input.delete(0, Tkinter.END) GAME_UI.chat_log.see("end") GAME_UI.chat_log.config(state=Tkinter.DISABLED) # reset variables after transmitting self.message = None self.message_type = None
First transmit checks to see if there is a message. If there is a message then we essentially turn the chat_log “on”, insert the message, delete what’s in the input widget. Those are the first three lines of code.
Next we finally get to use the tags we defined in the GUI. I looked around and found a way to add color to the various chat entries so they could be distinguished from each other easier. This was done by adding the following function to the core Tkinter project:
def color_message_type(self, pattern, tag, start="1.0", end="end", regexp=False): """Apply the given tag to all text that matches the given pattern If 'regexp' is set to True, pattern will be treated as a regular expression according to Tcl's regular expression syntax. """ start = self.index(start) end = self.index(end) self.mark_set("matchStart", start) self.mark_set("matchEnd", start) self.mark_set("searchLimit", end) count = IntVar() while True: index = self.search(pattern, "matchEnd", "searchLimit", count=count, regexp=regexp) if index == "": break if count.get() == 0: break # degenerate pattern which matches zero-length strings self.mark_set("matchStart", index) self.mark_set("matchEnd", "%s+%sc" % (index, count.get())) self.tag_add(tag, "matchStart", "matchEnd")
Once the above code is added we use the tag supplied to bm to alter the color of the tag we appended to the beginning of the message. Next we use the Tkinter Text see method and disable the chat_log so the user can’t enter data into it directly again.
Notice the steps we take in the not message condition is very similar to the steps where we have a message, minus a few lines that aren’t needed (like inserting, and coloring).
Finally, we reset the message and message_type to None after the message was transmitted to the chat_log. This completes the explanation of the transmit method.
The next method we’ll look at is the receive method. This is the callback function used in the GUI that was pointed out earlier. I’m going to list the entire method below and then explain what it does.
def receive(self, event=None): """use to capture player input""" # wait for player response self.message = GAME_UI.chat_log_input.get() # when message is found if self.message: self.message_type = PLAYER_LOG_TYPE # execute command if self.message[0] == '/': COMMANDS.command = self.message self.message, continue_command = COMMANDS.runCommand() if continue_command is True: self.message_type = COMMAND_LOG_TYPE LOGGER.bm(self.message, self.message_type) self.transmit() else: # keep last thing typed by player in memory, currently overwrite each time # do not store commands if self.message_type == PLAYER_LOG_TYPE: self.stored = copy.deepcopy(self.message) # send the non-command message if self.message is None: LOGGER.bm(self.stored, self.message_type) else: LOGGER.bm(self.message, self.message_type) self.transmit() self.received = True
First we use the get method of the Entry widget to grab input that has been typed into chat_log_input. If there is nothing in self.message, we do nothing. If there is something in self.message then we parse it.
We’re using this exclusively with player supplied input, so we set self.message_type. If the player has executed a slash command, for example /help, then we don’t want to store it in self.stored, or count it as a response to an NPC instigated query. We’ll look at the Command class soon. If we’ve got a valid response from the player we use bm to create the message, and transmit to send it. The variable self.received is used in the next method, capture.
def capture(self): """whenever a player value needs to be stored works by keeping track of whether the value has been stored or not""" self.received = False self.message_type = PLAYER_LOG_TYPE # wait for keypress while LOGGER.received is False: continue return LOGGER.stored
We use capture when we want to grab player input. For example, we might ask the player what their name is, and then use capture to store it and recite it back to them. It’s a while loop that runs and only if receive gets a valid player response does it end the loop.
See the entire Logger class in the collapsed code block below, and an example message sent by the player.
class Logger(object): """ this class controls how messages are sent to the chat log fn: bm: update gr with the message and message type. this MUST be done prior to sending fn: receive: called by pressing enter or submit while the chat input is active fn: transmit: called be receive and does the actual work of sending the message to the chat log fn: capture: called when player input is needed to be captured and/or stored ex: """ def __init__(self): self.message = None self.message_type = None self.received = False self.stored = None def bm(self, message, message_type): """update the GR with a new message so it can be sent using receive/transmit""" self.message_type = message_type if self.message_type == PLAYER_LOG_TYPE: self.message = '[You]: ' + message elif self.message_type == SYSTEM_LOG_TYPE: self.message = '[System]: ' + message elif self.message_type == NARRATOR_LOG_TYPE: self.message = '[Narrator]: ' + message elif self.message_type == COMMAND_LOG_TYPE: self.message = '[Command]: ' + message else: self.message = message def transmit(self): """transmit the message to the chat log""" if self.message: GAME_UI.chat_log.config(state=Tkinter.NORMAL) GAME_UI.chat_log.insert(Tkinter.END, "\n" + self.message) GAME_UI.chat_log_input.delete(0, Tkinter.END) # attempt to find a message type for coloring if self.message_type is not None: pattern = '[' + self.message_type + ']: GAME_UI.chat_log.color_message_type(pattern=pattern, tag=self.message_type) GAME_UI.chat_log.see("end") GAME_UI.chat_log.config(state=Tkinter.DISABLED) else: print('attempted to transmit an empty message') GAME_UI.chat_log.config(state=Tkinter.NORMAL) GAME_UI.chat_log_input.delete(0, Tkinter.END) GAME_UI.chat_log.see("end") GAME_UI.chat_log.config(state=Tkinter.DISABLED) # reset variables after transmitting self.message = None self.message_type = None def receive(self, event=None): """use to capture player input""" # wait for player response self.message = GAME_UI.chat_log_input.get() # when message is found if self.message: self.message_type = PLAYER_LOG_TYPE # execute command if self.message[0] == '/': COMMANDS.command = self.message self.message, continue_command = COMMANDS.runCommand() if continue_command is True: self.message_type = COMMAND_LOG_TYPE LOGGER.bm(self.message, self.message_type) self.transmit() else: # keep last thing typed by player in memory, currently overwrite each time # do not store commands if self.message_type == PLAYER_LOG_TYPE: self.stored = copy.deepcopy(self.message) # send the non-command message if self.message is None: LOGGER.bm(self.stored, self.message_type) else: LOGGER.bm(self.message, self.message_type) self.transmit() self.received = True def capture(self): """whenever a player value needs to be stored works by keeping track of whether the value has been stored or not""" self.received = False self.message_type = PLAYER_LOG_TYPE # wait for keypress while LOGGER.received is False: continue return LOGGER.stored
Commands
The Commands class manages what commands the player has available to them, and what happens when a command is executed. I created a few examples so we can see how they would work inside the chat log, namely: /help, /status, and /start chat.
class Commands(object): # commands _HELP = '/help' _STATUS = '/status' _START_CHAT = '/start chat' _COMMAND_LS = [_HELP, _STATUS, _START_CHAT] def __init__(self, command): self.command = command def runCommand(self): # check if command is valid if self.command not in self._COMMAND_LS: message = ("'%s' is not a valid slash command. Type '/help' to see a list of commands." % self.command) return message, True if self.command in self._COMMAND_LS: # the help command if self.command == self._HELP: message = self.getCommands(self) return message, True # the status command if self.command == self._STATUS: message = (' Your current status:' '\n Combat: %s' '\n Targets: TBD' % (COMBAT_STATE )) return message, True # the start chat command if self.command == self._START_CHAT: startThread() return LOGGER.message, False @staticmethod def getCommands(self): """ give player a list of valid commands """ message = " Available commands:" for c in self._COMMAND_LS: message += '\n ' + c return message, True
We see that this module is mainly a bunch of if statements that provide output to the chat log using bm. A command will return a message and whether or not that message should be sent using the continue_command variable. The toy example I created is a method called “haveAChat”.
def haveAChat(): # send some data to the chat widget LOGGER.bm("Welcome to the chat log!", NARRATOR_LOG_TYPE) LOGGER.transmit() # ask a question LOGGER.bm("How are you feeling today?", NARRATOR_LOG_TYPE) LOGGER.transmit() user_feels = LOGGER.capture() LOGGER.bm(("I see you feel %s...that's either good, bad, or neither. \n Aren't I smart?" % user_feels), NARRATOR_LOG_TYPE) LOGGER.transmit() LOGGER.bm("Well, it was nice chatting! Have a great day!", NARRATOR_LOG_TYPE) LOGGER.transmit() def startThread(): global submit_thread submit_thread = threading.Thread(target=haveAChat) submit_thread.daemon = True submit_thread.start()
Looking through the haveAChat toy example we see that the player is going to be welcomed to the chat, asked a question, waits for a response, and then transmits a couple more messages. A thread is created to let this function run, and this is needed to prevent the GUI from becoming unresponsive as Tkinters mainloop doesn’t want to compete with python scripts from running after it’s been executed.
Results
Now that the program has been explained, let’s take a look at it in action!
if __name__ == '__main__': # core stuff PLAYER = None COMBAT_STATE = False # logging stuff COMMAND_LOG_TYPE = 'Command' NARRATOR_LOG_TYPE = 'Narrator' PLAYER_LOG_TYPE = 'Player' SYSTEM_LOG_TYPE = 'System' # build the classes COMMANDS = Commands(command=None) LOGGER = Logger() GAME_UI = GUI() GAME_UI.build()
By executing the script with the above added we see the following results:
You can see as we type in data it’s echoed to the chat log. We can send data to the chat log and if we ask the player for input we can capture it and echo it to the player. We also have some color associated with each chat message to make it easier to read. This covers all four of our original points we outlined at the beginning of this article!
Conclusion
This was a fun little project and these three classes will be used to facilitate the creation of a text-based python game. Feel free to grab the code below and configure it for your own project.
Project Code
import copy import threading import Tkinter class GUI(object): """build the game UI""" # GLOBALS chat_log = None chat_log_input = None def __init__(self): # root business self.root = Tkinter.Tk() self.root.title("a chat log") # chat_frame self.chat_frame = Tkinter.Frame(self.root) self.chat_frame.grid(row=1, column=0) self.chat_log_title = None self.scroll = None self.chat_log_submit = None # bindings self.root.bind("<Return>", LOGGER.receive) def build(self): """ put it all together""" self.compileChatFrame() self.root.mainloop() def compileChatFrame(self): """build the chat log frame widget""" # chat log title self.chat_log_title = Tkinter.Label(self.chat_frame, text='Chat Log') self.chat_log_title.grid(row=0, column=0, sticky=Tkinter.W) # chat log GUI.chat_log = Tkinter.Text(self.chat_frame) GUI.chat_log.config(state=Tkinter.DISABLED) GUI.chat_log.tag_configure(tagName=COMMAND_LOG_TYPE, foreground='purple') GUI.chat_log.tag_configure(tagName=NARRATOR_LOG_TYPE, foreground='#0000A0') GUI.chat_log.tag_configure(tagName=PLAYER_LOG_TYPE, foreground='black') GUI.chat_log.tag_configure(tagName=SYSTEM_LOG_TYPE, foreground='#FF00FF') GUI.chat_log.grid(row=1, column=0, sticky=Tkinter.W) # scrollbar self.scroll = Tkinter.Scrollbar(self.chat_frame, orient=Tkinter.VERTICAL, command=GUI.chat_log.yview) GUI.chat_log.configure(yscrollcommand=self.scroll.set) self.scroll.config(command=GUI.chat_log.yview) self.scroll.grid(row=1, column=1, sticky='nsew') # chat input GUI.chat_log_input = Tkinter.Entry(self.chat_frame, background='gray') GUI.chat_log_input.grid(row=2, sticky='nsew') GUI.chat_log_input.focus() # chat submit self.chat_log_submit = Tkinter.Button(self.chat_frame, text='Submit', command=LOGGER.receive) self.chat_log_submit.grid(row=2, column=0, columnspan=2, sticky=Tkinter.E) class Logger(object): """ this class controls how messages are sent to the chat log fn: bm: update gr with the message and message type. this MUST be done prior to sending fn: receive: called by pressing enter or submit while the chat input is active fn: transmit: called be receive and does the actual work of sending the message to the chat log fn: capture: called when player input is needed to be captured and/or stored ex: """ def __init__(self): self.message = None self.message_type = None self.received = False self.stored = None def bm(self, message, message_type): """update the GR with a new message so it can be sent using receive/transmit""" self.message_type = message_type if self.message_type == PLAYER_LOG_TYPE: self.message = '[You]: ' + message elif self.message_type == SYSTEM_LOG_TYPE: self.message = '[System]: ' + message elif self.message_type == NARRATOR_LOG_TYPE: self.message = '[Narrator]: ' + message elif self.message_type == COMMAND_LOG_TYPE: self.message = '[Command]: ' + message else: self.message = message def transmit(self): """transmit the message to the chat log""" if self.message: GAME_UI.chat_log.config(state=Tkinter.NORMAL) GAME_UI.chat_log.insert(Tkinter.END, "\n" + self.message) GAME_UI.chat_log_input.delete(0, Tkinter.END) # attempt to find a message type for coloring if self.message_type is not None: pattern = '[' + self.message_type + ']:' GAME_UI.chat_log.color_message_type(pattern=pattern, tag=self.message_type) GAME_UI.chat_log.see("end") GAME_UI.chat_log.config(state=Tkinter.DISABLED) else: print('attempted to transmit an empty message') GAME_UI.chat_log.config(state=Tkinter.NORMAL) GAME_UI.chat_log_input.delete(0, Tkinter.END) GAME_UI.chat_log.see("end") GAME_UI.chat_log.config(state=Tkinter.DISABLED) # reset variables after transmitting self.message = None self.message_type = None def receive(self, event=None): """use to capture player input""" # wait for player response self.message = GAME_UI.chat_log_input.get() # when message is found if self.message: self.message_type = PLAYER_LOG_TYPE # execute command if self.message[0] == '/': COMMANDS.command = self.message self.message, continue_command = COMMANDS.runCommand() if continue_command is True: self.message_type = COMMAND_LOG_TYPE LOGGER.bm(self.message, self.message_type) self.transmit() else: # keep last thing typed by player in memory, currently overwrite each time # do not store commands if self.message_type == PLAYER_LOG_TYPE: self.stored = copy.deepcopy(self.message) # send the non-command message if self.message is None: LOGGER.bm(self.stored, self.message_type) else: LOGGER.bm(self.message, self.message_type) self.transmit() self.received = True def capture(self): """whenever a player value needs to be stored works by keeping track of whether the value has been stored or not""" self.received = False self.message_type = PLAYER_LOG_TYPE # wait for keypress while LOGGER.received is False: continue return LOGGER.stored class Commands(object): # commands _HELP = '/help' _STATUS = '/status' _START_CHAT = '/start chat' _COMMAND_LS = [_HELP, _STATUS, _START_CHAT] def __init__(self, command): self.command = command def runCommand(self): # check if command is valid if self.command not in self._COMMAND_LS: message = ("'%s' is not a valid slash command. Type '/help' to see a list of commands." % self.command) return message, True if self.command in self._COMMAND_LS: # the help command if self.command == self._HELP: message = self.getCommands(self) return message, True # the status command if self.command == self._STATUS: message = (' Your current status:' '\n Combat: %s' '\n Targets: TBD' % (COMBAT_STATE )) return message, True # the start chat command if self.command == self._START_CHAT: startThread() return LOGGER.message, False @staticmethod def getCommands(self): """ give player a list of valid commands """ message = " Available commands:" for c in self._COMMAND_LS: message += '\n ' + c return message def haveAChat(): # send some data to the chat widget LOGGER.bm("Welcome to the chat log!", NARRATOR_LOG_TYPE) LOGGER.transmit() # ask a question LOGGER.bm("How are you feeling today?", NARRATOR_LOG_TYPE) LOGGER.transmit() user_feels = LOGGER.capture() LOGGER.bm(("I see you feel %s...that's either good, bad, or neither. \n Aren't I smart?" % user_feels), NARRATOR_LOG_TYPE) LOGGER.transmit() LOGGER.bm("Well, it was nice chatting! Have a great day!", NARRATOR_LOG_TYPE) LOGGER.transmit() def startThread(): global submit_thread submit_thread = threading.Thread(target=haveAChat) submit_thread.daemon = True submit_thread.start() if __name__ == '__main__': # core stuff PLAYER = None COMBAT_STATE = False # logging stuff COMMAND_LOG_TYPE = 'Command' NARRATOR_LOG_TYPE = 'Narrator' PLAYER_LOG_TYPE = 'Player' SYSTEM_LOG_TYPE = 'System' # build the classes COMMANDS = Commands(command=None) LOGGER = Logger() GAME_UI = GUI() GAME_UI.build()
delvin malory
What Python version are you running it on?
When I tried to run the final product, I kept getting Tkinter errors; namely, “name Tkinter is not defined”.
atoqed
Python2.7
Assuming Tkinter is installed correctly then running it on Python3.x is most likely the cause of the problem as according to this website, “The capitalization of Tkinter changed between version 2 and 3.”