aler-aler 3 years ago
parent
commit
4f713ead40
4 changed files with 408 additions and 179 deletions
  1. 87 126
      szablon_gui.py
  2. 5 0
      client_config.ini
  3. 5 0
      config.ini
  4. 311 53
      server.py

+ 87 - 126
szablon_gui.py

@@ -1,21 +1,16 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 
 
 import configparser
 import configparser
-import sys
 import tkinter as tk
 import tkinter as tk
 import tkinter.messagebox
 import tkinter.messagebox
 from tkinter.constants import NSEW
 from tkinter.constants import NSEW
 import threading
 import threading
-import os
 import socket
 import socket
 import selectors
 import selectors
 import json
 import json
 from functools import partial
 from functools import partial
 
 
-config_fname = "config.txt"
-
 sel = selectors.DefaultSelector()
 sel = selectors.DefaultSelector()
-messages = [b"Message 1 from client.", b"Message 2 from client."]
 
 
 class Client(threading.Thread):
 class Client(threading.Thread):
     def __init__(self, host, port, username, gui):
     def __init__(self, host, port, username, gui):
@@ -70,110 +65,86 @@ class Client(threading.Thread):
 class GUI(tk.Frame):
 class GUI(tk.Frame):
     def __init__(self, master=None):
     def __init__(self, master=None):
         self.config= configparser.ConfigParser()
         self.config= configparser.ConfigParser()
-        self.config.read(config_fname, "UTF8")
+        self.config.read("client_config.ini", "UTF8")
         tk.Frame.__init__(self, master)
         tk.Frame.__init__(self, master)
-        self.parent=master
-        self.parent.protocol("WM_DELETE_WINDOW", self.quit)
+        self.master.protocol("WM_DELETE_WINDOW", self.quit)
         domyslne=self.config["DEFAULT"]
         domyslne=self.config["DEFAULT"]
         self.geometria_baza=domyslne.get('bazowa_geometria',"1000x800+50+50")
         self.geometria_baza=domyslne.get('bazowa_geometria',"1000x800+50+50")
-        self.parent.geometry(self.geometria_baza)
-        self.parent.minsize(1024, 768)
-        self.utworz_bazowe_menu()
-        # self.utworz_pasek_narzedzi()
-        self.utworz_status()
-
-        self.dodaj_menu_custom()
-        self.dodaj_menu_help()
-        # self.utworz_dodatki()
-        self.parent.columnconfigure(0, weight=999)
-        self.parent.columnconfigure(1, weight=1)
-        self.parent.rowconfigure(0, weight=1)
-        self.parent.rowconfigure(1, weight=9999)
-        self.parent.rowconfigure(2, weight=1)
-
+        self.master.geometry(self.geometria_baza)
+        self.master.minsize(1024, 480)
+        self.add_file_menu()
+        self.add_help_menu()
+        self.master.columnconfigure(0, weight=999)
+        self.master.columnconfigure(1, weight=1)
+        self.master.rowconfigure(0, weight=1)
+        self.master.rowconfigure(1, weight=9999)
+        self.master.rowconfigure(2, weight=1)
         self.client = None
         self.client = None
-
         self.bg_color = '#36393f'
         self.bg_color = '#36393f'
         self.font_color = '#cccccc'
         self.font_color = '#cccccc'
 
 
+        self.server_data = {}
         self.frames = {}
         self.frames = {}
-        self.frames["wait"] = self.draw_wait_screen()
-        self.frames["game"] = self.draw_game_screen()
+
         self.frames["welcome"] = self.draw_welcome_screen()
         self.frames["welcome"] = self.draw_welcome_screen()
         self.switch_to("welcome")
         self.switch_to("welcome")
 
 
         self.messages = []
         self.messages = []
         self.player_id = -1
         self.player_id = -1
-        self.server_data = {}
 
 
-        self.master.title("AFORYZMY")
+        if "username" in domyslne:
+            self.username_entry.insert(0, domyslne["username"])
 
 
-    def utworz_pasek_narzedzi(self):
-        self.toolbar_images = []   #muszą być pamiętane stale
-        self.toolbar = tk.Frame(self.parent)
-        for image, command in (
-                ("res/filenew.gif", self.file_new),
-                ("res/fileopen.gif", self.file_open),
-                ("res/filesave.gif", self.file_save)):
-            image = os.path.join(os.path.dirname(__file__), image)
-            try:
-                image = tkinter.PhotoImage(file=image)
-                self.toolbar_images.append(image)
-                button = tkinter.Button(self.toolbar, image=image,
-                                        command=command)
-                button.grid(row=0, column=len(self.toolbar_images) -1) #KOLEJNE ELEMENTY
-            except tkinter.TclError as err:
-                print(err)  # gdy kłopoty z odczytaniem pliku
-        self.toolbar.grid(row=0, column=0, columnspan=2, sticky=tkinter.NSEW)
-        
-    
-    def utworz_dodatki(self):
-        pass
-    
-    def utworz_status(self):
-        self.statusbar = tk.Label(self.parent, text="Status...",
-                                       anchor=tkinter.W)
-        self.statusbar.after(5000, self.clearStatusBar)
-        self.statusbar.grid(row=2, column=0, columnspan=2,
-                            sticky=tkinter.EW)
-        pass
-    
-    def ustawStatusBar(self, txt):
-        self.statusbar["text"] = txt
-        
-    def clearStatusBar(self):
-        self.statusbar["text"] = ""
+        if "hostname" in domyslne:
+            self.address_entry.insert(0, domyslne["hostname"])
+        else:
+            self.address_entry.insert(0, "localhost")
+
+        self.master.title("AFORYZMY")
     
     
-    def utworz_bazowe_menu(self):
-        self.menubar = tk.Menu(self.parent)
-        self.parent["menu"] = self.menubar
+    def add_file_menu(self):
+        self.menubar = tk.Menu(self.master)
+        self.master["menu"] = self.menubar
         fileMenu = tk.Menu(self.menubar)
         fileMenu = tk.Menu(self.menubar)
         for label, command, shortcut_text, shortcut in (
         for label, command, shortcut_text, shortcut in (
                 ("Rozłącz", self.disconnect, None, None),
                 ("Rozłącz", self.disconnect, None, None),
-                ("Quit", self.quit, "Ctrl+Q", "<Control-q>")):
+                ("Wyjdź", self.quit, "Ctrl+Q", "<Control-q>")):
             if label is None:
             if label is None:
                 fileMenu.add_separator()
                 fileMenu.add_separator()
             else:
             else:
                 fileMenu.add_command(label=label, underline=0,
                 fileMenu.add_command(label=label, underline=0,
                         command=command, accelerator=shortcut_text)
                         command=command, accelerator=shortcut_text)
-                self.parent.bind(shortcut, command)
-        self.menubar.add_cascade(label="File", menu=fileMenu, underline=0) 
+                self.master.bind(shortcut, command)
+        self.menubar.add_cascade(label="Plik", menu=fileMenu, underline=0)
         pass
         pass
+
+    def popup(self, a=""):
+        window = tk.Toplevel()
+        window.minsize(240, 80)
+        window.configure(bg=self.bg_color)
+
+        label = tk.Label(window, text="Języki skryptowe 2021", bg=self.bg_color, fg=self.font_color)
+        label.pack(fill='x', padx=50, pady=5)
+
+        button_close = tk.Button(window, text="Zamknij", command=window.destroy, width=6)#, bg=self.bg_color, fg=self.font_color)
+        button_close.pack()
     
     
-    def dodaj_menu_help(self):
+    def add_help_menu(self):
         fileMenu = tk.Menu(self.menubar)
         fileMenu = tk.Menu(self.menubar)
-        fileMenu.add_command(label="About...", underline=0,
-                command=self.file_new, accelerator="Ctrl+B")
-        self.parent.bind("<Control-b>", self.file_new)
+        fileMenu.add_command(label="O Aforyzmach...", underline=0,
+                command=self.popup, accelerator="Ctrl+B")
+        self.master.bind("<Control-b>", self.popup)
         self.menubar.add_cascade(label="Help", menu=fileMenu, underline=0) 
         self.menubar.add_cascade(label="Help", menu=fileMenu, underline=0) 
         pass    
         pass    
 
 
     def close(self):
     def close(self):
-        geometria = self.parent.winfo_geometry()
+        geometria = self.master.winfo_geometry()
         self.config["DEFAULT"]["bazowa_geometria"] = geometria
         self.config["DEFAULT"]["bazowa_geometria"] = geometria
-        with open(config_fname, 'w') as config_plik:
-            self.config.write(config_plik)
-        self.parent.destroy()
+        self.config["DEFAULT"]["username"] = self.username_entry.get()
+        self.config["DEFAULT"]["hostname"] = self.address_entry.get()
+        with open("client_config.ini", 'w') as config_file:
+            self.config.write(config_file)
+        self.master.destroy()
 
 
     def disconnect(self, event= None):
     def disconnect(self, event= None):
         if self.client is not None:
         if self.client is not None:
@@ -182,31 +153,24 @@ class GUI(tk.Frame):
             self.switch_to("welcome")
             self.switch_to("welcome")
 
 
     def quit(self, event= None):
     def quit(self, event= None):
+        reply = True
         reply = tkinter.messagebox.askyesno(
         reply = tkinter.messagebox.askyesno(
                         "Wyjście",
                         "Wyjście",
-                        "Naprawdę wyjść?", parent=self.parent)
+                        "Naprawdę wyjść?", master=self.master)
         event=event
         event=event
         if reply:
         if reply:
             if (self.client is not None):
             if (self.client is not None):
                 self.client.close()
                 self.client.close()
             self.close()
             self.close()
-    
-    def dodaj_menu_custom(self):
-        pass
-    def file_new(self, event=None):
-        event=event
-    def file_open(self,event=None):
-        event=event
-        pass
-    def file_save(self,event=None):
-        event=event
-        pass
+
     def connect(self):
     def connect(self):
-        self.client = Client(self.address_entry.get(), 7312, self.username_entry.get(), self)
+        if len(self.username_entry.get()) > 0:
+            self.client = Client(self.address_entry.get(), 7312, self.username_entry.get(), self)
 
 
     def upload(self):
     def upload(self):
-        self.switch_to("wait")
-        self.client.send(self.aphorism_entry.get())
+        if len(self.aphorism_entry.get()) > 0:
+            self.switch_to("wait")
+            self.client.send(self.aphorism_entry.get())
 
 
     def vote(self, player):
     def vote(self, player):
         self.switch_to("wait")
         self.switch_to("wait")
@@ -215,24 +179,22 @@ class GUI(tk.Frame):
     def switch_to(self, state):
     def switch_to(self, state):
         for frame in self.frames:
         for frame in self.frames:
             self.frames[frame].grid_forget()
             self.frames[frame].grid_forget()
-        if state == "vote" or state == "display":
+        if state != "welcome":
             self.frames[state] = self.draw_screen(state)
             self.frames[state] = self.draw_screen(state)
         self.frames[state].grid(row=1, column=0, columnspan=1, rowspan=1, sticky=NSEW)
         self.frames[state].grid(row=1, column=0, columnspan=1, rowspan=1, sticky=NSEW)
 
 
-
-    def draw_wait_screen(self):
-        frame = tk.Frame(self.parent, background=self.bg_color)
-        label = tk.Label(frame, text="Waiting for other players", pady=100, bg=self.bg_color, fg=self.font_color)
-        label.pack()
-        return frame
-
     def draw_screen(self, type):
     def draw_screen(self, type):
-        if(type == "vote"):
-            frame = tk.Frame(self.parent, background=self.bg_color)
+        if type == "wait":
+            frame = tk.Frame(self.master, background=self.bg_color)
+            label = tk.Label(frame, text="Oczekiwanie na innych graczy", pady=100, bg=self.bg_color, fg=self.font_color)
+            label.pack()
+            return frame
+        if type == "vote":
+            frame = tk.Frame(self.master, background=self.bg_color)
             title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
             title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
             title_label.pack()
             title_label.pack()
 
 
-            tk.Label(frame, text="Wybierz najlepszą definicję ASS", fg=self.font_color, bg=self.bg_color, font="Helvetica 12", height="2", anchor="n").pack()
+            tk.Label(frame, text="Wybierz najlepszą definicję {}".format(self.server_data["title"].upper()), fg=self.font_color, bg=self.bg_color, font="Helvetica 12", height="2", anchor="n").pack()
 
 
             for message in self.messages:
             for message in self.messages:
                 player, text = message
                 player, text = message
@@ -240,8 +202,8 @@ class GUI(tk.Frame):
                     tk.Button(frame, text=text, fg=self.font_color, bg=self.bg_color, font="Helvetica 12", width="80", height="3", pady="2", relief="flat", command=partial(self.vote, player)).pack()
                     tk.Button(frame, text=text, fg=self.font_color, bg=self.bg_color, font="Helvetica 12", width="80", height="3", pady="2", relief="flat", command=partial(self.vote, player)).pack()
 
 
             return frame
             return frame
-        if (type == "display"):
-            frame = tk.Frame(self.parent, background=self.bg_color)
+        if type == "display":
+            frame = tk.Frame(self.master, background=self.bg_color)
             title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
             title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
             title_label.pack()
             title_label.pack()
 
 
@@ -256,31 +218,31 @@ class GUI(tk.Frame):
                     total_score = self.server_data["total_scores"][player]
                     total_score = self.server_data["total_scores"][player]
                 if player in self.server_data["scores"]:
                 if player in self.server_data["scores"]:
                     score = self.server_data["scores"][player]
                     score = self.server_data["scores"][player]
-                tk.Label(frame, text="{} ({}):\n{} ({})".format(self.server_data["users"][player], total_score, text, score), fg=self.font_color, bg=self.bg_color, font="Helvetica 12", width="100", anchor="nw",
-                          height="3", pady="2").pack()
+                tk.Label(frame, text="{} ({}): {} ({})".format(self.server_data["users"][player], total_score, text, score), fg=self.font_color, bg=self.bg_color, font="Helvetica 12", width="100", anchor="nw",
+                          height="3", pady="2", padx="2").pack(fill="x")
 
 
             tk.Label(frame, text="Kolejna runda rozpocznie się za chwilę", fg=self.font_color, bg=self.bg_color,
             tk.Label(frame, text="Kolejna runda rozpocznie się za chwilę", fg=self.font_color, bg=self.bg_color,
-                     font="Helvetica 12", height="3", anchor="s").pack()
+                     font="Helvetica 12", height="3", anchor="n").pack(side="bottom")
 
 
             return frame
             return frame
+        if type == "game":
+            frame = tk.Frame(self.master, background=self.bg_color)
+            title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
+            title_label.pack()
 
 
-    def draw_game_screen(self):
-        frame = tk.Frame(self.parent, background=self.bg_color)
-        title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
-        title_label.pack()
-
-        tk.Label(frame, text="Describe the word: ASS", fg=self.font_color, bg=self.bg_color, font="Helvetica 12").pack()
-        sv = tk.StringVar()
-        self.aphorism_entry = tk.Entry(frame, width=80, textvariable=sv)
-        sv.trace_add("write", lambda x, y, z: self.key_fix(sv, 100))
-        self.aphorism_entry.pack()
+            tk.Label(frame, text="Napisz złotą myśl na temat wyrazu {}".format(self.server_data["title"].upper()),
+                     fg=self.font_color, bg=self.bg_color, font="Helvetica 12").pack()
+            sv = tk.StringVar()
+            self.aphorism_entry = tk.Entry(frame, width=80, textvariable=sv)
+            sv.trace_add("write", lambda x, y, z: self.key_fix(sv, 100))
+            self.aphorism_entry.pack()
 
 
-        send_button = tk.Button(frame, text="Send", command=self.upload)
-        send_button.pack()
-        return frame
+            send_button = tk.Button(frame, text="Wyślij", command=self.upload)
+            send_button.pack()
+            return frame
     
     
     def draw_welcome_screen(self):
     def draw_welcome_screen(self):
-        frame = tk.Frame(self.parent, background=self.bg_color)
+        frame = tk.Frame(self.master, background=self.bg_color)
         title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
         title_label = tk.Label(frame, text="AFORYZMY", bg=self.bg_color, fg=self.font_color, font="Helvetica 22")
         title_label.pack()
         title_label.pack()
 
 
@@ -290,12 +252,11 @@ class GUI(tk.Frame):
         sv.trace_add("write", lambda x,y,z: self.key_fix(sv, 20))
         sv.trace_add("write", lambda x,y,z: self.key_fix(sv, 20))
         self.username_entry.pack()
         self.username_entry.pack()
 
 
-        tk.Label(frame, text="Address", fg=self.font_color, bg=self.bg_color, font="Helvetica 12").pack()
+        tk.Label(frame, text="Host", fg=self.font_color, bg=self.bg_color, font="Helvetica 12").pack()
         self.address_entry = tk.Entry(frame)
         self.address_entry = tk.Entry(frame)
-        self.address_entry.insert(0,"localhost")
         self.address_entry.pack()
         self.address_entry.pack()
 
 
-        title_button = tk.Button(frame, text="Connect", command=self.connect)
+        title_button = tk.Button(frame, text="Połącz", command=self.connect, anchor="s")
         title_button.pack()
         title_button.pack()
         return frame
         return frame
 
 
@@ -310,7 +271,7 @@ class GUI(tk.Frame):
             ('ń', 'ñ'), ('Ń', 'Ñ'),
             ('ń', 'ñ'), ('Ń', 'Ñ'),
             ('ą', '¹'), ('Ą', '¥'),
             ('ą', '¹'), ('Ą', '¥'),
             ('ż', '¿'), ('Ż', '¯'),
             ('ż', '¿'), ('Ż', '¯'),
-            ('ź', 'Ÿ'), #('Ź', '')
+            ('ź', 'Ÿ'), ('', "=")#('Ź', '')
         ]
         ]
         for (pl, k) in list:
         for (pl, k) in list:
             str = str.replace(k, pl)
             str = str.replace(k, pl)

+ 5 - 0
client_config.ini

@@ -0,0 +1,5 @@
+[DEFAULT]
+bazowa_geometria = 1024x480+201+356
+username = 
+hostname = 157.230.26.118
+

+ 5 - 0
config.ini

@@ -0,0 +1,5 @@
+[DEFAULT]
+test = 3
+kak eta  budet = 8
+test3 = 4
+

+ 311 - 53
server.py

@@ -1,61 +1,319 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 
 
-import sys
+import configparser
+import threading
 import socket
 import socket
-import selectors
-import types
+import argparse
+import os
+import json
+import random
+import time
 
 
-sel = selectors.DefaultSelector()
+titles = [
+    "Python",
+    "3",
+    "JavaScript",
+    "Hamburger",
+    "Wieloryb",
+    "Szkoła",
+    "Uniwersytet",
+    "Remiza",
+    "Pomidor",
+    "Radość",
+    "Młot",
+    "Baran",
+    "Hiacynt",
+    "Jedzenie w samolocie",
+    "Rozpacz",
+    "Ciasto",
+    "Słońce",
+    "Pantofel",
+    "Widmo",
+    "Dama",
+    "Krawędź",
+    "Japonki",
+    "Zamek",
+    "Szmalcownik",
+    "Bazy danych",
+    "Szarada",
+    "Głaz",
+    "Piwnica",
+    "Pióro",
+    "Rachunek prawdopodobieństwa",
+    "Jabłko",
+    "Granat",
+    "Przystań",
+    "Sumo",
+    "Baba",
+    "Tygrys",
+    "Staw",
+    "Oliwa",
+    "Grzebień",
+    "Kuba",
+    "Piła",
+    "Polka",
+    "Warta",
+    "Róża",
+    "Kosa",
+    "Narcyz",
+    "Zebra",
+    "Kapelusz",
+    "Kule",
+    "Mars",
+    "Kiwi",
+    "Mysz"
+]
 
 
+class Server(threading.Thread):
+    """
+    Supports management of server connections.
 
 
-def accept_wrapper(sock):
-    conn, addr = sock.accept()  # Should be ready to read
-    print("accepted connection from", addr)
-    conn.setblocking(False)
-    data = types.SimpleNamespace(addr=addr, inb=b"", outb=b"")
-    events = selectors.EVENT_READ | selectors.EVENT_WRITE
-    sel.register(conn, events, data=data)
+    Attributes:
+        connections (list): A list of ServerSocket objects representing the active connections.
+        host (str): The IP address of the listening socket.
+        port (int): The port number of the listening socket.
+    """
+    def __init__(self, host, port):
+        super().__init__()
+        self.config = configparser.ConfigParser()
+        self.config.read("config.ini", "UTF8")
+        self.connections = []
+        self.host = host
+        self.port = port
+        # self.players = []
+        self.data = {}
+        self.data["title"] = "undefined"
+        self.data["state"] = "wait"
+        self.data["users"] = {}
+        self.data["messages"] = {}
+        self.data["scores"] = {}
+        self.data["total_scores"] = {}
+        self.votes = {}
 
 
-
-def service_connection(key, mask):
-    sock = key.fileobj
-    data = key.data
-    if mask & selectors.EVENT_READ:
-        recv_data = sock.recv(1024)  # Should be ready to read
-        if recv_data:
-            data.outb += recv_data
+    def reset(self):
+        time.sleep(10)
+        self.votes = {}
+        self.data["scores"] = {}
+        self.data["messages"] = {}
+        self.data["title"] = random.choice(titles)
+        self.data["state"] = "wait"
+        if self.player_count() >= 3:
+            self.data["state"] = "game"
         else:
         else:
-            print("Closing connection to", data.addr)
-            sel.unregister(sock)
-            sock.close()
-    if mask & selectors.EVENT_WRITE:
-        if data.outb:
-            print("Echoing", repr(data.outb), "to", data.addr)
-            sent = sock.send(data.outb)  # Should be ready to write
-            data.outb = data.outb[sent:]
-
-
-if len(sys.argv) < 3:
-    print("Usage:", sys.argv[0], "<host> <port>")
-    sys.exit(1)
-
-host, port = sys.argv[1], int(sys.argv[2])
-lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-lsock.bind((host, port))
-lsock.listen()
-print("Listening on", (host, port))
-lsock.setblocking(False)
-sel.register(lsock, selectors.EVENT_READ, data=None)
-
-try:
-    while True:
-        events = sel.select(timeout=None)
-        for key, mask in events:
-            if key.data is None:
-                accept_wrapper(key.fileobj)
-            else:
-                service_connection(key, mask)
-except KeyboardInterrupt:
-    print("caught keyboard interrupt, exiting")
-finally:
-    sel.close()
+            self.data["state"] = "wait"
+        dump = json.dumps(self.data, ensure_ascii=False)
+        self.broadcast_all(dump)
+    
+    def run(self):
+        """
+        Creates the listening socket. The listening socket will use the SO_REUSEADDR option to
+        allow binding to a previously-used socket address. This is a small-scale application which
+        only supports one waiting connection at a time. 
+        For each new connection, a ServerSocket thread is started to facilitate communications with
+        that particular client. All ServerSocket objects are stored in the connections attribute.
+        """
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        sock.bind((self.host, self.port))
+
+        sock.listen(1)
+        print('Listening at', sock.getsockname())
+
+        idx = 0
+        try:
+            while True:
+                # Accept new connection
+                sc, sockname = sock.accept()
+                print('{} has joined'.format(idx))
+
+                # Create new thread
+                server_socket = ServerSocket(sc, sockname, self, idx)
+                idx += 1
+
+                # Start new thread
+                server_socket.start()
+
+                # Add thread to active connections
+                self.connections.append(server_socket)
+        except KeyboardInterrupt:
+            self.quit()
+
+    def quit(self):
+        with open("config.ini", 'w') as config_file:
+            self.config.write(config_file)
+        os._exit(0)
+
+    def broadcast(self, message, source):
+        """
+        Sends a message to all connected clients, except the source of the message.
+
+        Args:
+            message (str): The message to broadcast.
+            source (tuple): The socket address of the source client.
+        """
+        for connection in self.connections:
+            # Send to all connected clients except the source client
+            if connection.sockname != source:
+                connection.send(message)
+
+    def broadcast_all(self, message):
+        """
+        Sends a message to all connected clients
+
+        Args:
+            message (str): The message to broadcast.
+        """
+        for connection in self.connections:
+            connection.send(message)
+    
+    def remove_connection(self, connection):
+        """
+        Removes a ServerSocket thread from the connections attribute.
+
+        Args:
+            connection (ServerSocket): The ServerSocket thread to remove.
+        """
+        self.connections.remove(connection)
+
+    def player_count(self):
+        return len(self.connections)
+
+
+class ServerSocket(threading.Thread):
+    """
+    Supports communications with a connected client.
+
+    Attributes:
+        sc (socket.socket): The connected socket.
+        sockname (tuple): The client socket address.
+        server (Server): The parent thread.
+    """
+    def __init__(self, sc, sockname, server, id):
+        super().__init__()
+        self.sc = sc
+        self.id = id
+        self.sockname = sockname
+        self.server = server
+        self.username = ""
+
+    def call_quit(self):
+        print('{} has left'.format(self.id))
+        self.server.data["users"].pop(self.id, None)
+        self.server.data["messages"].pop(self.id, None)
+        self.server.data["scores"].pop(self.id, None)
+        self.server.data["total_scores"].pop(self.id, None)
+        self.server.votes.pop(self.id, None)
+        self.sc.close()
+        server.remove_connection(self)
+        if self.server.player_count() == 0:
+            self.server.data["state"] = "wait"
+
+    def run(self):
+        """
+        Receives data from the connected client and broadcasts the message to all other clients.
+        If the client has left the connection, closes the connected socket and removes itself
+        from the list of ServerSocket threads in the parent Server thread.
+        """
+        try:
+            while True:
+                message = self.sc.recv(1024).decode('utf8')
+                if not message:
+                    self.call_quit()
+                    return
+                elif message:
+                    if self.username == "":
+                        self.username = message
+                        self.server.data["users"][self.id] = self.username
+                        if len(self.server.data["users"]) >= 3 and self.server.data["state"] == "wait":
+                            self.server.data["title"] = random.choice(titles)
+                            self.server.data["state"] = "game"
+                        dump = json.dumps(self.server.data, ensure_ascii=False)
+                        self.server.broadcast(dump, self.sockname)
+                        data_copy = self.server.data.copy()
+                        data_copy["playerid"] = self.id
+                        dump = json.dumps(data_copy, ensure_ascii=False)
+                        self.send(dump)
+                    elif self.server.data["state"] == "game":
+                        self.server.data["messages"][self.id] = message
+                        if len(self.server.data["messages"]) == self.server.player_count():
+                            self.server.data["state"] = "vote"
+                            dump = json.dumps(self.server.data, ensure_ascii=False)
+                            self.server.broadcast_all(dump)
+                    elif self.server.data["state"] == "vote":
+                        self.server.votes[self.id] = int(message)
+                        print(self.server.votes)
+                        if len(self.server.votes) == self.server.player_count():
+                            for key in self.server.votes:
+                                player = self.server.votes[key]
+                                if player not in self.server.data["scores"]:
+                                    self.server.data["scores"][player] = 0
+                                self.server.data["scores"][player] += 1
+                            for player in self.server.data["scores"]:
+                                name = self.server.data["users"][player]
+                                print(name)
+                                if player not in self.server.data["total_scores"]:
+                                    if name in self.server.config["DEFAULT"]:
+                                        self.server.data["total_scores"][player] = int(self.server.config["DEFAULT"][name])
+                                    else:
+                                        self.server.data["total_scores"][player] = 0
+                                        self.server.config["DEFAULT"][name] = "0"
+                                self.server.data["total_scores"][player] += self.server.data["scores"][player]
+                                self.server.config["DEFAULT"][name] = str(int(int(self.server.config["DEFAULT"][name]) + int(self.server.data["scores"][player])))
+                            self.server.data["state"] = "display"
+                            dump = json.dumps(self.server.data, ensure_ascii=False)
+                            self.server.broadcast_all(dump)
+                            with open("config.ini", 'w') as config_file:
+                                self.server.config.write(config_file)
+                            self.server.reset()
+                    #print(self.server.data)
+
+        except KeyboardInterrupt or EOFError:
+            print("Caught keyboard interrupt, exiting")
+            call_quit()
+            os._exit(0)
+        except ConnectionResetError:
+            self.call_quit()
+    
+    def send(self, message):
+        """
+        Sends a message to the connected server.
+
+        Args:
+            message (str): The message to be sent.
+        """
+        self.sc.sendall(message.encode('utf8'))
+
+def quit(server):
+    print('Closing all connections...')
+    for connection in server.connections:
+        connection.sc.close()
+    print('Shutting down the server...')
+    os._exit(0)
+
+def exit(server):
+    """
+    Allows the server administrator to shut down the server.
+    Typing 'q' in the command line will close all active connections and exit the application.
+    """
+    try:
+        while True:
+            input('')
+    except KeyboardInterrupt:
+        quit(server)
+    except EOFError:
+        quit(server)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Chatroom Server')
+    parser.add_argument('host', help='Interface the server listens at')
+    parser.add_argument('-p', metavar='PORT', type=int, default=1060,
+                        help='TCP port (default 1060)')
+    args = parser.parse_args()
+
+    # Create and start server thread
+    server = Server(args.host, args.p)
+    server.start()
+
+    exit = threading.Thread(target = exit, args = (server,))
+    exit.start()