a python and math learning resource

python

creating a chat log for a game using tkinter in python

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)
Chat Log 1

Putting the GUI section together yields the chat log.

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
Chat Log 2

Sending a message to the chat log.

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.

Chat Log 3

Sending a command to the chat log.

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:

Animated Chat Log

The chat log as it was put together, in action.

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()

2 Comments

  1. 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.”

Leave a Reply to Cancel reply