Demo entry 6324310

Client file form A-Game-Of-Codes Project

   

Submitted by anonymous on Nov 15, 2016 at 23:48
Language: Python 3. Code size: 56.8 kB.

"""The client communicates with the server and acts as a node in the peer to 
peer network."""

from tkinter import *           #Import GUI Libraries
from tkinter.ttk import *       #Import more GUI Libraries
from tkinter.scrolledtext import ScrolledText   #A specific GUI element
from ServerComms import *       #ServerComms for server communication
from Encrypt import *           #Encrypt to allow encryption
from XOR import *               #XOR to allow the XORProtocol
from Protocols import *         #Protocols for NoneProtocol and ReverseProtocol
from DH import *                #DH for Diffie-Hellman Protocol
import socket                   #Socket to create server comms
import random                   #Random to allow random number generation

PORT = 12345    #Arbitrary port for server comms

#This file has the capability to run a client and operate as a node of the 
#   peer to peer network and communicate with the other players.

class GameClient(ServerListener):
    """A game client operates the peer to peer node and communications
    
    The GameClient will listen to the server and create a server node in the 
    peer to peer network. The client interacts with the GUI component to manage
    communications and allow the player to interact with other nodes in the 
    network
    
    Attributes:
        client: Connection to the server.
        connections: Socket connections to the other players and server saved as
            either UserThread or ClientThread's.
        client_interface: GUI Component of the interface as a ClientInterface.
        server: The client's server node to allow other players to connect to 
            and communicate with this node.
    """
    
    def __init__(self, client, client_interface):
        """Creates a GameClient and initializes fields.

        Args:
            client: ClientThread object that has been connected to a server.
            client_interface: ClientInterface GUI to communicate to the user.
        """
        #Initialize fields
        self.client = client
        self.connections = []
        self.client_interface = client_interface
        self.server = None
        self.conversations = []

    def message_received(self, message, sender):
        """This fuction will parse a message received from connections
        
        Depending on who sent the message, the message is read differently 
        depending on whether the server or another client sent the message.
        
        Args:
            message: Message sent by the user
            sender: ClientThread or UserThread that received the message.
        """
        #Boolean to determine whether the message should be displayed
        sorted = False
        #If the message was sent by the server
        if sender == self.client:
            #If the message contains the codename... assign codename
            if "Your codename is" in message:
                self.name = message[message.find("\'") + 1: message.rfind("\'")]
                self.client_interface.codename.config(text = "Codename: " + self.name)
            #If the assigned port in the message, create server on given port
            elif "Your port is" in message:
                #Parse port
                port = int(message[message.find("\'") + 1: message.rfind("\'")])
                #Create and start the server
                self.server = AcceptThread(port, self.client_interface.you.get())
                self.server.add_listener(self)
                self.server.start_accepting()
            #If instructed to forget token, forget token 
            elif "Forget token" in message:
                self.client_interface.set_token_text("No token yet")
            #If instructed to connect to another user, attempt to connect with a
            #   ClientThread and establish two way communications
            elif "Connect to:" in message:
                #Create socket
                conn = socket.socket()
                #Parse address
                addr = message[message.find("\'") + 1: message.rfind("\'")].split(":")
                #Connect to the client
                conn.connect((addr[0], int(addr[1])))
                #Create ClientThread and save it in the list
                thread = ClientThread(conn, (addr[0], int(addr[1])), [self])
                self.connections.append(thread)
                print("Connected to neighbor " + addr[0] + ":" + addr[1])
                self.client_interface.neighbors.append(addr[0] + ":" + addr[1])
                self.client_interface.display_neighbors()
            #If a player joined the network, register him or her as a player
            elif "Player joined" in message:
                self.client_interface.all_players.append(message[message.find("\'") + 1: message.rfind("\'")])
                self.client_interface.display_players()
            #If a player left the network, remove him or her from the list
            elif "Player left" in message:
                #Parse name
                name = message[message.find("\'") + 1: message.rfind("\'")]
                #Remove it from the list if it is there.
                if name in self.client_interface.all_players:
                    self.client_interface.all_players.remove(name)
                    self.client_interface.display_players()
            #If the server sends a list of connected players, parse it and add 
            #   the list to the current list of players.
            elif "Currently connected" in message:
                for name in message[message.find("[") + 1: message.rfind("]")].split(","):
                    self.client_interface.all_players.append(name[name.find("\'") + 1: name.rfind("\'")])
                self.client_interface.display_players()
            #If the server is sending a token, register the token as this user's
            #   token and display it to the user.
            elif "Token" in message:
                #Parse token
                token = message[message.find("(") + 1: message.rfind(")")].split(",")
                self.target = token[0]
                self.value = token[1]
                #Display token
                self.client_interface.set_token_text("Send " + self.value + " to " + self.target)
            #If the message did not meet any of these tags, assume it is part of
            #   a conversation and attempt to find the tag.
            else:
                #Parse tag
                tag = message[message.find("<") + 1: message.rfind(">")]
                #Find the corresponding conversation and forward the message
                if tag in PlayerConversation.tags:
                    for conversation in self.conversations:
                        if conversation.tag == tag:
                            #If a conversation is found, set sorted to true so the
                            #   message will not display in the general feed
                            sorted = True
                            conversation.accept_input("Server> " + message[message.rfind(">") + 1:])
        #If the sender is one of the neighbors
        elif sender in self.connections:
            #If requesting a name, send the user's name as a resopnse
            if "What is your name" in message:
                sender.send_message("My codename is '" + self.name + "'")
            #If giving a name, register the name as being from that neighbor.
            if "My codename" in message:
                self.client_interface.neighbor_names[sender.addr[0] + ":" + 
                        str(sender.addr[1])] = message[message.find("\'") + 1: message.rfind("\'")]
                self.client_interface.display_neighbors()
            #If the message is part of a conversation, identify the conversation
            #   and forawrad the message.
            if message[:message.find(">")] == "Conversation":
                #Format of any conversation message:
                #   Conversation>(sender)>(recipent)>(conversation tag)> message
                parts = message[:message.rfind(">")].split(">")
                if len(parts) > 3:
                    #Identify parts
                    sorted = True   #A message from another client will always 
                                    #   be sorted if it has a tag.
                    start = parts[1]
                    recipent = parts[2]
                    tag = parts[3]
                    message = message[len("Conversation>") + len(start) + len(recipent) + len(tag) + 3:]
                    #If the tag is not currenlty part of a conversation, make a 
                    #   conversation to allow communication.
                    if not tag in PlayerConversation.tags:
                        target = recipent
                        #Make a new coversation tab and add it to the notebook
                        talktracker = PlayerConversation(self, 
                                self.client_interface.window, tag, not target == self.name)
                        self.conversations.append(talktracker)
                        #If the user is the recipent, register conversation as a
                        #   normal conversation
                        if target == self.name:
                            #Append conversation to notebook
                            self.client_interface.conversations.add(talktracker, text = start)
                            #Set recipent of conversation as originator
                            talktracker.set_recipent(start)
                            #Tell conversation to send messages back to the sender
                            #   in order to communicate with the originator
                            talktracker.accept_input("Server> Send message to '" + 
                                    self.get_name(sender) + "' in order to reach [" + target + "]")
                            #Log conversation start
                            talktracker.add_line("Conversation opened between you and " + start)
                        #If not intended recipent, register conversation as a spy
                        else:
                            #Append conversation to notebook
                            self.client_interface.conversations.add(talktracker,
                                    text = start + ">" + recipent)
                            #Set sender as start to pose as the conversation originator
                            talktracker.set_sender(start)
                            #Attempt to find the shortest path to the recipent by
                            #   requesting it from the server
                            self.client.send_message("<" + talktracker.tag + "> Get route '" + target + "'")
                            #Log the conversation for user to see
                            talktracker.add_line("Spying on conversation between " + start + " and " + target)
                            #Register a back method to talk to the user and be a
                            #   bridge for the two different clients to communicate
                            talktracker.sender_connection = sender
                    #Find conversation who corresponds to the tag (Including a 
                    #   conversation that was just made) and forward the message
                    for conversation in self.conversations:
                        if conversation.tag == tag:
                            conversation.accept_input(start + ">" + recipent + "> " + message)
        #If the message was not sorted, log it to the user
        if not sorted:
            #If the sender was the server, tell the user
            if sender == self.client:
                self.client_interface.server.add_line("Server> " + message)
            #Otherwise, log the user it came from and display the message
            elif sender in self.connections:
                self.client_interface.server.add_line(self.get_name(sender) + "> " + message)

    def get_name(self, user):
        """Gets the name of a neighbor if applicable
        
        Checks all the users who are connected and if the user has a name, this
        method will return the name of the neighbor based on his or her either
        connection to this GameClient
        
        Args:
            user: ClientThread or UserThread that the neighbor communicates from.
        
        Returns:
            The String name of the the neighbor or the neighbors address in the 
            format of "ip:port" if the user does not have a name yet.
        """
        if user.addr[0] + ":" + str(user.addr[1]) in list(self.client_interface.neighbor_names.keys()):
            return self.client_interface.neighbor_names[user.addr[0] + ":" + str(user.addr[1])]
        return user.addr[0] + ":" + str(user.addr[1])

    def get_connection(self, name):
        """Gets the connection thread for a user based on the neighbor's name
        
        Iterates through the different connections and if the connection's name 
        is the same as the given name, returns the connection.
        
        Args:
            name: Name of the neighbor to search for.
            
        Returns:
            The UserThread or ClientThread of the neighbor or None if there is 
            no neighbor with the given name.
        """
        for conn in self.connections:
            if self.client_interface.neighbor_names[conn.addr[0] + ":" + str(conn.addr[1])] == name:
                return conn
        return None
    
    def user_connected(self, user):
        """This method will be called every time a user connects to the server
        
        This parses the user and logs then event. Then it polls the other user
        for his or her name and saves the user as a neighbor.
        
        Args:
            user: UserThread created when a player connected
        """
        #Add connection
        self.connections.append(user)
        #Register self as a listener
        user.add_listener(self)
        #Save the neighbor as the name "ip:port"
        self.client_interface.neighbors.append(user.addr[0] + ":" + str(user.addr[1]))
        #Log event
        print("Neighbor connected to you " + user.addr[0] + ":" + str(user.addr[1]))
        self.client_interface.display_neighbors()   #update display
        #Request the name of the other user and give my name
        user.send_message("What is your name?, " + "My codename is '" + self.name + "'")
    
    def user_disconnect(self, user):
        """This method will be called every time a use disconnects
        
        This clears the records of the user and then adjusts the connections 
        accordingly.
        
        Args:
            user: UserThread disconnecting from the server.
        """
        #If the user was connected clear data
        if user.addr[0] + ":" + str(user.addr[1]) in self.client_interface.neighbors:
            #Remove from list of neighbors
            self.client_interface.neighbors.remove(user.addr[0] + ":" + str(user.addr[1]))
            self.client_interface.display_neighbors()   #update display
            self.connections.remove(user)
            if user.addr[0] + ":" + str(user.addr[1]) in self.client_interface.neighbor_names.keys():
                del self.client_interface.neighbor_names[user.addr[0] + ":" + str(user.addr[1])]
    
    def is_disconnected(self, sender):
        """This method is called when the user is disconnected from the server
        
        Upon disconnecting, check if it is from the server or from another node 
        in the network then act accordingly to close connections or remove the 
        disconnected user.
        
        Args:
            sender: UserThread or ClientThread who is disconnecting.
        """
        #If the server disconnected.
        if sender == self.client:
            self.client_interface.on_disconnect()
        #If disconnected from another node
        elif sender.addr[0] + ":" + str(sender.addr[1]) in self.client_interface.neighbors:
            self.client_interface.neighbors.remove(sender.addr[0] + ":" + str(sender.addr[1]))
            self.client_interface.display_neighbors()
            if sender in self.connections:
                self.connections.remove(sender)
            if sender.addr[0] + ":" + str(sender.addr[1]) in self.client_interface.neighbor_names.keys():
                del self.client_interface.neighbor_names[sender.addr[0] + ":" + str(sender.addr[1])]
    
    def close(self):
        """Closes client and disconnects form all connected nodes"""
        #If connected to server, disconnect
        if not self.server == None:
            self.server.close_server()
        #while connected to other users, disconnect from other users
        while len(self.connections) > 0:
            self.connections.pop(0).disconnect()

class ConversationWindow(Frame):
    """A conversation window represents a conversation between two players
    
    The conversation window is a GUI display of a message log between two nodes
    and allows communication between the nodes.
    
    Attributes:
        textares: scrolledtext panel of the conversation log.
        send: send text entry to send a message.
        button: button to send text.
        conn: connection to other user as a UserThread or ClientThread
    """
    
    def __init__(self, parent=None, enabled=True):
        """Creates a ConversationWindow
        
        Initializes all the parts of the window and allows sending messages via 
        the GUI if enabled.
        
        Args:
            parent: Parent panel or component.
            enabled: Can the user send messages via pressing the 'send' button
        """
        #Call super constructor
        Frame.__init__(self, parent)
        #Create scrolled text area and place it in the view
        self.textarea = ScrolledText(self)
        self.textarea.grid(column = 0, row = 0, columnspan = 4, sticky = N+E+S+W)
        #Create text entry area and place it in the view
        self.send = Entry(self)
        self.send.grid(column = 1, row = 1, pady = 1, sticky = N+E+S+W)
        #Create button and place it into the view
        self.button = Button(self, text = "send")
        self.button.grid(column = 2, row = 1, pady = 1)
        #Setup text area to be view only
        self.textarea.configure(state = "disabled")
        #configure grid and columns to expand
        Grid.columnconfigure(self, 1, weight = 1)
        Grid.rowconfigure(self, 0, weight = 1)
        #Initialize connection field
        self.conn = None
        #Bind button to call self.send_action
        self.button.bind("<Button-1>", self.send_action)
        #disable button if not enabled
        if not enabled:
            self.disable_button()
    
    def enable_button(self):
        """Enables the button for send action"""
        self.button.config(state = "normal")
    
    def disable_button(self):
        """Disabled the button for send action"""
        self.button.config(state = "disabled")
    
    def add_line(self, line):
        """Adds a line to the textarea
        
        Adds a line to the user view and adds a new line character to the end of
        the given line to add.
        
        Args:
            line: Line of text to add.
        """
        self.textarea.configure(state = "normal")   #Allow editing of text area
        self.textarea.insert(END, line + "\n")      #Tack on the line at the end
        self.textarea.see("end")                    #Change line display area    
        self.textarea.configure(state = "disabled") #Stop editing of text area
    
    def add_connection(self, conn):
        """Sets the recipent of this conversation
        
        Args:
            conn: ClientThread or UserThread that receives messages from the 
                    send_action method.
        """
        self.conn = conn
    
    def remove_connection(self):
        """Resets the recipent of this conversation to None"""
        self.conn = None
    
    def send_message(self, message):
        """Send a message to the recipent
        
        If the connection (self.conn) is None, 'You are nto connected to anything'
        is displayed on the screen and the action is aborted. If the conversation
        is connected, then the message is sent using conn.send_message(message)
        
        Args:
            message: String message to send.
        """
        #If not connected
        if self.conn == None:
            self.add_line("You are not connected to anything")
        #If connected
        else:
            #Check to make sure it is not an empty message
            if len(message.strip()) == 0:
                pass
            #Send message if it is a non-empty string
            else:
                self.conn.send_message(message)
                self.send.delete(0, "end")
                self.add_line("You> " + message)
    
    def send_action(self, event):
        """Send action called by the button to forward message in send entry"""
        self.send_message(message = self.send.get())

class PlayerConversation(ConversationWindow):
    """A player conversation is Conversation between two players
    
    This is a conversation window designed to oversee a conversation between two
    players or for a spy to watch two other players' conversation
    
    Attributes:
        protocol: Communicatin protocol.
        tag: Identifier for this conversation.
        target: Intended recipent of the conversation.
        recipent: Same as target if not spy, but if spy, the target is the 
                original sender.
        sender: Self if not spy, but if spy, the origial starter of the conversation.
        game_client: GameClient operating the ClientInterface.
        queue: Queue of messages to forward if disconnected.
        
        is_spy: Is this a spy window.
        sender_connection: Back communication to send messages to both the target
            and the sender for spy windows.
        block: Should block communications between both parties?
    """
    tags = []   #Tags currently reserved by active conversations        
    
    def __init__(self, game_client, parent=None, tag=None, is_spy=False, protocol=None):
        """Creates a PlayerConversation with given attributes to be a spy or not
        
        Will create and setup a player conversation. If there is no tag, it is
        assumed that this is a new conversation and therefore a new tag is 
        created that has not already been reserved by another conversation. If 
        this window has no protocol, it will assume that the conversation has 
        been started by another player and wait for that player to communicate 
        which protocol he or she wishes to use.
        
        Args:
            game_client: GameClient operating the ClientInterface.
            tag: Tag to use for conversation or None if this is a new conversation.
            is_spy: Is this a spy window.
            protocol: Protocol to use in order to communicate with the other user
                    or None to wait for the other user to select a protocol.
        """
        #Call super constructor
        super().__init__(parent, True)
        self.is_spy = is_spy        #Register fields
        self.protocol = protocol
        if tag == None:             #Generate tag if needed
            self.tag = hex(random.getrandbits(64))[2:]
            while self.tag in PlayerConversation.tags:
                self.tag = hex(random.getrandbits(64))[2:]
        else:
            self.tag = tag
        #Add tag to list
        PlayerConversation.tags.append(self.tag)
        self.target = None          #Initialize fields
        self.recipent = None
        self.sender_connection = None
        self.sender = game_client.name
        self.game_client = game_client
        self.queue = []
        self.block = IntVar(0)
        if not protocol == None:    #If this has a protocol, do setup for protocol
            protocol.set_conversation(self)
    
    def set_sender(self, sender):
        """Sets the sender of the conversation
        
        Used to create spy windows
        
        Args:
            sender: Name of intended sender.
        """
        self.sender = sender
    
    def set_recipent(self, recipent):
        """Sets the recipent of the conversation
        
        Used to create spy windows
        
        Args:
            recipent: Name of intended recipent.
        """
        self.recipent = recipent
    
    def accept_input(self, message):
        """Accepts and processes input from a socket
        
        Parses the input in an expected format of:
            "sender>recipent> message" or
            "Server> message" for messages from the server.
        Then determines whether to display or forward the message or do something
        else if the protocol requests it
        
        Args:
            message: Message in expected format
        """
        sender = message[:message.find(">")]    #Identify sender
        #If sent by server
        if sender == "Server" and self.target == None:
            #Find fastest path
            #If there is no path, abort
            if "is not connected to the game" in message:   #Not connected == no path
                self.add_line("Cannot connect to " + message[message.find("\'") 
                        + 1: message.rfind("\'")] + ". Send 'Close' to exit this tab")
            elif "Cannot reach" in message:                 #Can't reach == no path
                self.add_line("Cannot reach " + message[message.find("\'") + 1: 
                        message.rfind("\'")] + ". Send 'Close' to exit this tab")
            #If the person him/herself is the intended recipent, abort
            elif "Cannot start conversation with yourself" in message:
                self.add_line("Narcissist, send 'Close' to exit this tab")
            #If target is found, register the recipent/connection
            elif "in order to reach" in message:
                #Find target to send messages to
                self.target = message[message.find("\'") + 1: message.rfind("\'")]
                self.add_connection(self.game_client.get_connection(self.target))
                #Identify who messages are comming from
                recipent = message[message.find("[") + 1: message.find("]")]
                if self.recipent == None:
                    self.recipent = recipent
                #If this is not a spy
                if not self.is_spy:
                    #If there is a spy protocol, start it
                    if not self.protocol == None:
                        self.conn.send_message("Conversation>" + self.sender + ">" + self.recipent +
                                ">" + self.tag + "> " + self.protocol.name)
                        self.protocol.set_conversation(self)
                        self.protocol.on_start()
                #If this is a spy, setup spy fields and GUI
                else:
                    #Second set of buttons and entries for spying and posing as
                    #   either user to steal information.
                    self.send2 = Entry(self)
                    self.send2.grid(column = 1, row = 2, pady = 1, sticky = N+E+S+W)
                    self.button2 = Button(self, text = "send to " + self.sender)
                    self.button.configure(text = "send to " + self.recipent)
                    self.button2.grid(column = 2, row = 2, pady = 1)
                    Label(self, text = "As " + self.sender).grid(column = 0, row = 1, sticky = N+E+S+W)
                    Label(self, text = "As " + self.recipent).grid(column = 0, row = 2, sticky = N+E+S+W)
                    Grid.columnconfigure(self, 1, weight = 1)
                    Grid.rowconfigure(self, 0, weight = 1)
                    
                    #Setup new button action
                    self.button.bind("<Button-1>", lambda event: self.send_action(
                            event,source=self.sender))
                    self.button2.bind("<Button-1>", self.send_action)
                    self.button2.bind("<Button-1>", lambda event: self.send_action(
                            event,source=self.recipent))
                    
                    #Create button to block communications
                    self.blockbutton = Checkbutton(self, text = "Block comms", var = self.block)
                    self.blockbutton.grid(column = 3, row = 1, sticky = N+E+S+W)
                #After making a connection, run through the queue sending all 
                #   queued messages.
                while len(self.queue) > 0:
                    self.conn.send_message(self.queue.pop(0))
        #If the message was not sent by the server
        else:
            #Identify message parts
            #   "Sender>Recipent> message"
            message = message[message.find(">") + 1:]
            recipent = message[:message.find(">")]
            message = message[message.find(">") + 2:]
            #If there is a protocol and this is not a spy window
            #   Setup a protocol based on the message received
            if self.protocol == None and not self.is_spy:
                protocol = message.strip()
                #Choose protocol based on message
                if protocol == "None":
                    self.protocol = NoneProtocol()
                elif protocol == "Reverse":
                    self.protocol = ReverseProtocol()
                elif protocol == "XOR":
                    self.protocol = XORProtocol()
                elif protocol == "DH":
                    self.protocol = DHProtocol()
                #If a protocol could be found, setup the protocol
                if not self.protocol == None:
                    self.protocol.set_conversation(self)
                    self.protocol.on_start()
                #Return and do not display this message
                return  
            #If there now is a protocol, have the protocol process the message
            if not self.protocol == None:
                message = self.protocol.before_process(sender, recipent, message)
            #If the message is "CANCEL_MESSAGE" (Set by protocol), do not display it
            if message == "CANCEL_MESSAGE":
                return
            #If the message passed all the previous tests, process it and then
            #   display it on the screen.
            self.process_message(sender, recipent, message)
    
    def process_message(self, sender, recipent, message):
        """This will process a message before displaying it on the screen
        
        Depending on whether this is a spy window or not, this instance will 
        interpret the fields differently and possilby forward or modify the 
        message before displaying it if this instance is a spy. If this instance
        is not a spy, it will simply display the message.
        
        Args:
            sender: Person sending the message.
            recipent: Person receiving the message.
            message: Message being sent.
        """
        #If this instance is the intended recipent, simply display the message.
        if recipent == self.game_client.name: 
            self.add_line(sender + "> " + message)
        #If this instance is not the intended recipent, it gets complicated
        else:
            #If not blocing message, log on screen.
            if self.block.get() == 0:
                self.add_line(sender + "> " + message)
            #If blocking messages, log that the message was blocked and exit.
            else:
                self.add_line(sender + " [BLOCKED]> " + message)
                return
            
            #If not connected, note that this is not connected and add the message
            #   to the queue.
            if self.conn == None:
                self.add_line("Not connected to anything yet, added message to queue")
                self.queue.append("Conversation>" + sender + ">" + self.recipent 
                        + ">" + self.tag + "> " + message)
            #If the recipent is the target (the recipent of the message is the 
            #   end target of the conversation) forward the message to the target
            elif recipent == self.target:
                self.conn.send_message("Conversation>" + sender + ">" + recipent 
                        + ">" + self.tag + ">" + message)
            #If the recipent is the sender (the reciepnt of the message is the 
            #   oritinal starter of the conversation) forward message to sender
            elif recipent == self.sender:
                self.sender_connection.send_message("Conversation>" + sender + 
                        ">" + recipent + ">" + self.tag + ">" + message)

    def send_action(self, event, source=None):
        """Send action called by the button to forward message in send entry and
            passes a source"""
        self.send_message(self.send.get(), source)
    
    def send_message(self, message, source=None):
        """Sends a message with a source and message 
        
        Depending on the source and whether or not this is a spy, the recipent 
        of the message will be determined. If this instance is not a spy, then 
        the recipent will by the intended recipent and the source is the client
        him or herself. If this instance is a spy, then the source is either the
        target or the starter and the recipent will be determined to be whichever
        the source is not (target to starter or starter to target).
        
        Args:
            message: Message to send.
            source: Person sending the message.
        """
        #If there is no otherwise specified source, source is the person him/herself
        if source == None:
            source = self.game_client.name
        #If the source is the recipent, change the message for spy windows
        elif source == self.recipent:
            message = self.send2.get()
        
        #Save an unmodified message
        normal = message
        should_show = True      #Determine wether the message should be shown 
                                #   based on the protocol
        if not self.protocol == None:
            (message, should_show) = self.protocol.before_send(source, self.recipent, message)
        
        #If the message is 'CANCEL_MESSAGE', abort operation (usually done by the
        #   protocol to avoid displaying excess information)
        if message == "CANCEL_MESSAGE":
            return
            
        #If the message is 'close', exit this window
        if (self.send.get().lower() == "close" and (source == self.game_client.name or source == self.sender)) or (self.is_spy and (self.send2.get().lower() == "close" and source == self.recipent)):
            #forget this conversation (but if this was a spy window, it will continue
            #   to forward or block messages based on the last setting)
            self.game_client.client_interface.conversations.forget(self)
        #If not closing the window, attempt to send the message
        else:
            #If not connected, say so then abort
            if self.conn == None:
                self.add_line("You are not connected to anything yet")
            #If connected, attempt to send
            else:
                #If empty message, abort
                if len(message.strip()) == 0:
                    pass
                #If non-empty message, attempt to send
                else:
                    #If the source is the gameclient, send message as normal
                    if source == self.game_client.name:
                        #Send message in format:
                        #   'Converstaion>source>recipent>tag> message'
                        self.conn.send_message("Conversation>" + source + ">" + 
                                self.recipent + ">" + self.tag + ">" + message)
                        #Clear text entry box
                        self.send.delete(0, "end")
                        #If the message whould be displayed, display it
                        if should_show:
                            self.add_line("You> " + normal)
                    #If the source is the original starter of the conversation,
                    #   set the sender to be the modified source and the 
                    #   recipent is the intended recipent of the conversation
                    elif source == self.sender:
                        #Send message in format:
                        #   'Converstaion>source>recipent>tag> message'
                        self.conn.send_message("Conversation>" + source + ">" + 
                                self.sender + ">" + self.tag + ">" + message)
                        #Clear text entry box
                        self.send.delete(0, "end")
                        #If the message whould be displayed, display it
                        if should_show:
                            self.add_line("You as " + source + "> " + normal)
                    #If the source is the intended recipent of the conversation,
                    #   set the sender to be the modified source and the 
                    #   recipent is the original starter of the conversation
                    elif source == self.recipent:
                        #Send message in format:
                        #   'Converstaion>source>recipent>tag> message'
                        self.sender_connection.send_message("Conversation>" + source + ">" + 
                                self.recipent + ">" + self.tag + ">" + message)
                        #Clear text entry box
                        self.send2.delete(0, "end")
                        #If the message whould be displayed, display it
                        if should_show:
                            self.add_line("You as " + source + "> " + normal)

class ClientInterface:
    """A client interface is an interface that allows clients to play the game 
        in a GUI setting.
    
    The Client interface has space for starting new conversations, viewing current
    conversations, viewing all users and neighbors, connecting to the server or 
    disconnecting, displaying the codename of the client. The Client interface 
    works with the GameClient to operate the game and keep it functioning for the
    user using the setup.
    
    Attributes:
        window: Window of the application.
        neighbor_names: Dictionary of the neighbor names saved in format...
            "ip:port" by "name"
        client: Client connection to the server.
        is_updating: Is this currently updating the display.
        queued: Is there a display update queued.
        client_thread: ClientThread to communicate with the server.
        connect_thread: Thread for creating connection to the server.
        monitor: IP address entry for monitor.
        you: Entry area for the user's host IP identification.
        connect_button: Button to connect to server.
        disconnect_button: Button to disconnect from the server.
        status: Display of connection status.
        codename: Display of the user's codename.
        token_text: Display of the user's token and target.
        neighbor_text: Display of the user's neighbors and their IP addresses.
        all_players: List of players connected to the game.
        selected_player: Player selected to be recipent of conversation.
        recipent: Option menu to select recipent of conversation.
        methods: Possible methods of communication.
        selected_method: Selected method of communication.
        method: Option menu to select method of communication.
        converstaion_button: Button to start a converation.
        conversations: notebook of all conversations.
        server: ConversationWindow for communication with server and general feed
            for all messages to the user.
        token: Text entry to submit token.
        submit_token: Button to submit token.
    """
    def __init__(self):
        self.is_updating = False    #Setup fields
        self.queued = False
        window = Tk()
        self.window = window
        window.title("Game Client") #Setup window
        window.protocol( "WM_DELETE_WINDOW", self.close )
        
        self.neighbor_names = dict()#Initialize more fields
        self.client = None
        self.connect_thread = None
        self.client_thread = None
        self.neighbors = []
        self.all_players = []
        
        #Create diplay area for connecting to server
        #Monitor entry
        Label(window, text = "Monitor IP", width = 10).grid(row = 0, column = 0, pady = 5)
        self.monitor = Entry(window, width = 20)
        self.monitor.grid(row = 0, column = 1, pady = 5)
        #User's ip entry
        Label(window, text = "Your IP", width = 10).grid(row = 1, column = 0, pady = 5, stick = "n")
        self.you = Entry(window, width = 20)
        self.you.insert(END, socket.gethostname())
        self.you.grid(row = 1, column = 1, pady = 5, stick = "n")

        #Create a panel to hold Connect and disconnect and status disply elements
        panel = Frame(window)
        #Create connect button and setup
        self.connect_button = Button(panel, text = "Connect")
        self.connect_button.pack(side = "left", padx = 10)      #Position
        self.connect_button.bind("<Button-1>", self.connect_action)
                                        #Bind action to self.connect_action
        #Create disconnect button and setup
        self.disconnect_button = Button(panel, text = "Disconnect")
        self.disconnect_button.pack(side = "left", padx = 10)   #Position
        self.disconnect_button.bind("<Button-1>", self.disconnect_action)
                                        #Bind action to self.disconnect_action
        #Create status for server connection area
        self.status = Label(panel, text = "Connection Status: Not Connected")
        self.status.pack(side = "left", padx = 10)      #Position
        #Create a codename display text field
        self.codename = Label(panel, text = "Codename: (Waiting for assignment)")
        self.codename.pack(side = "left", padx = 10)    #Position    
        #Position the panel in the grid
        panel.grid(row = 0, column = 2, stick = "w", pady = 10)

        #Create area for diplaying token
        #Label
        Label(window, text = "Token:").grid(row = 3, column = 0, pady = 5, 
                columnspan = 2, sticky="w")
        #Create selectable token and target display field
        self.token_text = Text(window, width = 45, height = 1, state = "disabled")
        self.set_token_text("No token yet")     #Set field text
        self.token_text.grid(row = 4, column = 0, columnspan = 2)   #Position

        #Create area to show current neighbors
        #Label
        Label(window, text = "Neighbors:").grid(row = 6, column = 0, pady = 5,
                columnspan = 2, sticky = "w")
        #Create text area
        self.neighbor_text = ScrolledText(window, state = "disabled", width = 40, height = 5)
        #Position the text area
        self.neighbor_text.grid(row = 7, column = 0, columnspan = 2, pady = 10)
        #Update display
        self.display_neighbors()
        
        #Create a frame for starting a new conversation
        start_tab = Frame(window)
        #Label the frame
        Label(start_tab, text = "Start Conversation", width = 40).grid(row = 0, column = 0)

        #Create area for recipent of the converation
        Label(start_tab, text = "Recipent:").grid(row = 1, column = 0, padx = 10, sticky = "w")
        #Setup fields to track player selection
        self.selected_player = StringVar(window)
        #Allow player to select from a drop down menu
        self.recipent = OptionMenu(start_tab, self.selected_player, *self.all_players)
        self.recipent.grid(row = 2, column = 0, padx = 20, sticky = "w")
        #Update the display
        self.display_players()

        #Create an area for selecting encryption method
        Label(start_tab, text = "Encryption Method:").grid(row = 3, column = 0, padx = 10, stick = "w")
        #List all possible methods
        self.methods = ["None", "None", "Reverse", "XOR", "DH"]
        #Field for selected method
        self.selected_method = StringVar(window)
        self.selected_method.set("None")    #Set default to none
        #Create a drop down menu for selection
        self.method = OptionMenu(start_tab, self.selected_method, *self.methods)
        self.method.grid(row = 4, column = 0, padx = 20, stick = "w")
        
        #Create a button to start a new converation
        self.converstaion_button = Button(start_tab, text = "Start Conversation")
        #Position button
        self.converstaion_button.grid(row = 5, column = 0, padx = 10, pady = 20, stick = "w")
        #Bind button action to self.start_conversation
        self.converstaion_button.bind("<Button-1>", self.start_conversation)
        
        #Position the entire start converation tab
        start_tab.grid(row = 5, column = 0, columnspan = 2, pady = 30, stick = "w")

        #Create a notebook for all converations
        self.conversations = Notebook(window)
        #Position notebook
        self.conversations.grid(row = 2, column = 2, rowspan = 8, stick = N+E+S+W, padx = 5, pady = 5)
        #Create a window for server comms and general information
        self.server = ConversationWindow()
        #Log start 
        self.server.add_line("Not connected to server yet")
        self.conversations.add(self.server, text = "Server")

        #Setup grid and column expansion
        Grid.columnconfigure(window, 2, weight = 1)
        Grid.rowconfigure(window, 2, weight = 1)
        
        #Create an area for submitting a tokoen
        Label(window, text = "Submit Token").grid(row = 8, column = 0, columnspan = 2, pady = 5, stick = "n")
        #Entry area for the token
        self.token = Entry(window, width = 30)
        self.token.grid(row = 9, column = 0, pady = 5, stick = "n")
        #Button to submit token
        self.submit_token = Button(window, text = "Submit")
        self.submit_token.grid(row = 9, column = 1, pady = 5, stick = "n")
        self.submit_token.bind("<Button-1>", self.submit_action)
        
        #Start the window
        window.mainloop()
    
    def start_conversation(self, event):
        """Starts a converation when the button is pressed"""
        #If connected to the server
        if not self.client_thread == None:
            #Find target
            target = self.selected_player.get()
            #Find protocol (if possible)
            protocol = None
            if self.selected_method.get() == "None":
                protocol = NoneProtocol()
            elif self.selected_method.get() == "Reverse":
                protocol = ReverseProtocol()
            elif self.selected_method.get() == "XOR":
                protocol = XORProtocol()
            elif self.selected_method.get() == "DH":
                protocol = DHProtocol()
            #Create a PlayerConversation based on specified target and protocol
            talktracker = PlayerConversation(self.game_client, self.window, protocol = protocol)
            #Log converation start
            talktracker.add_line("Conversation opened between you and " + target)
            #Add PlayerConversation to notebook
            self.game_client.conversations.append(talktracker)
            self.conversations.add(talktracker, text = target)
            self.conversations.select(talktracker)  #Show new tab
            
            #Find fastest route to target via the server.
            self.server.send_message("<" + talktracker.tag + "> Get route '" + target + "'")
        #If not connected to the server, abort and log
        else:
            self.server.add_line("You are not connected to the server")
    
    def set_token_text(self, text):
        """Sets the text of the token_text fields
        
        Args:
            text: String of what the token_text field should be set to.
        """
        #Enable editing
        self.token_text.configure(state="normal")
        self.token_text.delete(1.0,END)     #Delete old text
        self.token_text.insert(1.0, text)   #Set new text
        #Disable editing and adjust width
        self.token_text.configure(state="disabled", width = len(text))
    
    def submit_action(self, event):
        """Submit a token to the server based on button press"""
        if len(self.token.get().strip()) > 0:
            self.server.send_message("Token submission: '" + self.token.get().strip() + "'")
    
    def display_players(self):
        """Update display of players in the recipent menu to start converations"""
        m = self.recipent['menu']
        m.delete(0,END)     #Clear out old option
        #Add new options if there are plays
        if len(self.all_players) > 0:
            for val in self.all_players:
                m.add_command(label=val,command=lambda v=self.selected_player,l=val:v.set(l))
            #If the previously selected player is no longer in the list, select
            #   the first player in the list
            if not self.selected_player.get() in self.all_players:
                self.selected_player.set(self.all_players[0])
        #If there are no options, just set it to an empty, descriptive value
        else:
            self.selected_player.set("Select Recipent")
    
    def display_neighbors(self):
        """Display the neighbors in the neighbors field"""
        #If currently updating neighbors, queue aciton and abort
        if self.is_updating:
            self.queued = True
            return
        #note that this is in action
        self.is_updating = True
        #Allow the text to be edited
        self.neighbor_text.configure(state = "normal")
        #Delete the text
        self.neighbor_text.delete("1.0", "end")
        #If there are no neighbors, note that
        if len(self.neighbors) == 0:
            self.neighbor_text.insert("end", "You have no neighbors")
        #If there are neighbors, add them to the list with their name, if applicable
        else:
            for index in range(0, len(self.neighbors)):
                #If the neighbor does not have a name, show the neighbor ip
                if not self.neighbors[index] in self.neighbor_names.keys():
                    self.neighbor_text.insert("end", self.neighbors[index] + "\n")
                #If the neighbor has a name, show the name and ip
                else: 
                    self.neighbor_text.insert("end", self.neighbor_names[
                            self.neighbors[index]] + " - " + self.neighbors[index] + "\n")
        #Disable editing of the text area
        self.neighbor_text.configure(state = "disabled")
        #Note that this is no longer editing
        self.is_updating = False
        #If there is an action queued, empty queue and repeat action
        if self.queued:
            self.queued = False
            self.display_neighbors()
        
    def close(self):
        """Close the game"""
        print("Bye bye")
        #If connected, disconnect and close the client 
        if not self.client_thread == None:
            self.client_thread.disconnect()
            self.game_client.close()
        #Run normal disconnect actions
        self.on_disconnect()
        #Exit the program
        sys.exit()
    
    def connect_server(self):
        """Attempt to connect to the server"""
        #If not currently connected, attempt to connect
        if self.client_thread == None:
            #Get specified address
            address = self.monitor.get()
            #Note start of action
            self.server.add_line("Attempting to connect using address: " + address)
            try:
                self.client = socket.socket()           #Get socket
                self.client.connect((address, PORT))    #attempt to connect
                #Attempt to make client thread
                self.client_thread = ClientThread(self.client, (address, PORT))
                #Make game client based on client thread
                self.game_client = GameClient(self.client_thread, self)
                #Add listener to actions and note success
                self.client_thread.add_listener(self.game_client)
                self.server.add_line("Succesfully connected to monitor " + address)
                self.status.config(text = "Connection Status: Connected")
                self.server.add_connection(self.client_thread)
                return                                  #if successful, exit
            #If there is an error, log the error
            except socket.herror as error:
                self.server.add_line("error with address " + address + " error of " + str(error))
            except socket.error as error:
                self.server.add_line("error occured with setting up socket: " + str(error))
            #If unsuccessful, reset client to None
            self.client = None
        #If connected, note that this is already connected to server
        else:
            self.server.add_line("Already connected to server")

    def disconnect_action(self, event):
        """Disconnect action from disconnect button"""
        #If connected, cose client and disconnect
        if not self.client_thread == None:
            self.game_client.close()
            self.client_thread.disconnect()
            self.server.add_line("Disconnected from game")
        #If not connected, note that this is not connected
        else:
            self.server.add_line("You are not connected to the server")
            
    def connect_action(self, event):
        """Connect ation from connect button"""
        #If not currently connection, try to connect
        if self.connect_thread == None or not self.connect_thread.is_alive():
            #export operation to thread to avoid delaying the display
            self.connect_thread = threading.Thread(target=self.connect_server)
            self.connect_thread.start()
        #If already connected, do not attempt to connect and note action
        else:
            self.server.add_line("Already attempting to connect to server")
            
    def on_disconnect(self):
        """This method is called whenever the client disconnects

        The mehtod resets the fields to allow a reconnect or close of the client 
        without any extra threads running"""
        #If connected, clear converations and close connection
        if not self.client_thread == None:
            for conv in self.game_client.conversations:
                if not conv == self.server and conv in self.conversations.tabs():
                    self.conversations.forget(conv)
            self.client_thread = None
            self.game_client.close()
        #Reset fields for displaying connection, codename and token
        self.status.configure(text = "Connection Status: Not Connected")
        self.codename.configure(text = "Codename: (Waiting for assignment)")
        self.set_token_text("No token yet")
        #Clear out or reset fields
        self.neighbors.clear()
        self.all_players = []
        self.neighbor_names = dict()
        #Update displays
        self.display_neighbors()
        self.display_players()

#Start a client interface GUI that will be capable of operating all functions 
#   and allows a player to create a game client to faciliate the game
ClientInterface()

This snippet took 0.05 seconds to highlight.

Back to the Entry List or Home.

Delete this entry (admin only).