server.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #!/usr/bin/env python3
  2. import configparser
  3. import threading
  4. import socket
  5. import argparse
  6. import os
  7. import json
  8. import random
  9. import time
  10. TITLES = [
  11. "Python",
  12. "3",
  13. "JavaScript",
  14. "Hamburger",
  15. "Wieloryb",
  16. "Szkoła",
  17. "Uniwersytet",
  18. "Remiza",
  19. "Pomidor",
  20. "Radość",
  21. "Młot",
  22. "Baran",
  23. "Hiacynt",
  24. "Jedzenie w samolocie",
  25. "Rozpacz",
  26. "Ciasto",
  27. "Słońce",
  28. "Pantofel",
  29. "Widmo",
  30. "Dama",
  31. "Krawędź",
  32. "Japonki",
  33. "Zamek",
  34. "Szmalcownik",
  35. "Bazy danych",
  36. "Szarada",
  37. "Głaz",
  38. "Piwnica",
  39. "Pióro",
  40. "Rachunek prawdopodobieństwa",
  41. "Jabłko",
  42. "Granat",
  43. "Przystań",
  44. "Sumo",
  45. "Baba",
  46. "Tygrys",
  47. "Staw",
  48. "Oliwa",
  49. "Grzebień",
  50. "Kuba",
  51. "Piła",
  52. "Polka",
  53. "Warta",
  54. "Róża",
  55. "Kosa",
  56. "Narcyz",
  57. "Zebra",
  58. "Kapelusz",
  59. "Kule",
  60. "Mars",
  61. "Kiwi",
  62. "Mysz"
  63. ]
  64. class Server(threading.Thread):
  65. def __init__(self, host, port):
  66. super().__init__()
  67. self.config = configparser.ConfigParser()
  68. self.config.read("config.ini", "UTF8")
  69. self.connections = []
  70. self.host = host
  71. self.port = port
  72. self.data = {}
  73. self.data["title"] = "undefined"
  74. self.data["state"] = "wait"
  75. self.data["users"] = {}
  76. self.data["messages"] = {}
  77. self.data["scores"] = {}
  78. self.data["total_scores"] = {}
  79. self.votes = {}
  80. def reset(self):
  81. time.sleep(10)
  82. print("New round")
  83. self.votes = {}
  84. self.data["scores"] = {}
  85. self.data["messages"] = {}
  86. self.data["title"] = random.choice(TITLES)
  87. self.data["state"] = "wait"
  88. if self.player_count() >= 3:
  89. self.data["state"] = "game"
  90. else:
  91. self.data["state"] = "wait"
  92. dump = json.dumps(self.data, ensure_ascii=False)
  93. self.broadcast_all(dump)
  94. def run(self):
  95. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  96. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  97. sock.bind((self.host, self.port))
  98. sock.listen(1)
  99. print('Listening at', sock.getsockname())
  100. idx = 0
  101. try:
  102. while True:
  103. sc, sockname = sock.accept()
  104. print('{} has joined'.format(idx))
  105. server_socket = ServerSocket(sc, sockname, self, idx)
  106. idx += 1
  107. server_socket.start()
  108. self.connections.append(server_socket)
  109. except KeyboardInterrupt:
  110. self.quit()
  111. def quit(self):
  112. with open("config.ini", 'w') as config_file:
  113. self.config.write(config_file)
  114. os._exit(0)
  115. def broadcast(self, message, source):
  116. for connection in self.connections:
  117. # Send to all connected clients except the source client
  118. if connection.sockname != source:
  119. connection.send(message)
  120. def broadcast_all(self, message):
  121. for connection in self.connections:
  122. connection.send(message)
  123. def remove_connection(self, connection):
  124. self.connections.remove(connection)
  125. def player_count(self):
  126. return len(self.connections)
  127. class ServerSocket(threading.Thread):
  128. def __init__(self, sc, sockname, server, id):
  129. super().__init__()
  130. self.sc = sc
  131. self.id = id
  132. self.sockname = sockname
  133. self.server = server
  134. self.username = ""
  135. def call_quit(self):
  136. print('{} has left'.format(self.id))
  137. self.server.data["users"].pop(self.id, None)
  138. self.server.data["messages"].pop(self.id, None)
  139. self.server.data["scores"].pop(self.id, None)
  140. self.server.data["total_scores"].pop(self.id, None)
  141. self.server.votes.pop(self.id, None)
  142. self.sc.close()
  143. server.remove_connection(self)
  144. if self.server.player_count() == 0:
  145. self.server.data["state"] = "wait"
  146. def run(self):
  147. """
  148. Receives data from the connected client and broadcasts the message to all other clients.
  149. If the client has left the connection, closes the connected socket and removes itself
  150. from the list of ServerSocket threads in the parent Server thread.
  151. """
  152. try:
  153. while True:
  154. message = self.sc.recv(1024).decode('utf8')
  155. if not message:
  156. self.call_quit()
  157. return
  158. elif message:
  159. if self.username == "":
  160. self.username = message
  161. self.server.data["users"][self.id] = self.username
  162. if len(self.server.data["users"]) >= 3 and self.server.data["state"] == "wait":
  163. self.server.data["title"] = random.choice(TITLES)
  164. self.server.data["state"] = "game"
  165. dump = json.dumps(self.server.data, ensure_ascii=False)
  166. self.server.broadcast(dump, self.sockname)
  167. data_copy = self.server.data.copy()
  168. data_copy["playerid"] = self.id
  169. dump = json.dumps(data_copy, ensure_ascii=False)
  170. self.send(dump)
  171. elif self.server.data["state"] == "game":
  172. self.server.data["messages"][self.id] = message
  173. if len(self.server.data["messages"]) == self.server.player_count():
  174. self.server.data["state"] = "vote"
  175. dump = json.dumps(self.server.data, ensure_ascii=False)
  176. self.server.broadcast_all(dump)
  177. elif self.server.data["state"] == "vote":
  178. self.server.votes[self.id] = int(message)
  179. if len(self.server.votes) == self.server.player_count():
  180. for key in self.server.votes:
  181. player = self.server.votes[key]
  182. if player not in self.server.data["scores"]:
  183. self.server.data["scores"][player] = 0
  184. self.server.data["scores"][player] += 1
  185. for player in self.server.data["scores"]:
  186. name = self.server.data["users"][player]
  187. if player not in self.server.data["total_scores"]:
  188. if name in self.server.config["DEFAULT"]:
  189. self.server.data["total_scores"][player] = int(
  190. self.server.config["DEFAULT"][name])
  191. else:
  192. self.server.data["total_scores"][player] = 0
  193. self.server.config["DEFAULT"][name] = "0"
  194. self.server.data["total_scores"][player] += self.server.data["scores"][player]
  195. self.server.config["DEFAULT"][name] = str(int(
  196. int(self.server.config["DEFAULT"][name]) + int(self.server.data["scores"][player])))
  197. self.server.data["state"] = "display"
  198. dump = json.dumps(self.server.data, ensure_ascii=False)
  199. self.server.broadcast_all(dump)
  200. with open("config.ini", 'w') as config_file:
  201. self.server.config.write(config_file)
  202. self.server.reset()
  203. # print(self.server.data)
  204. except KeyboardInterrupt or EOFError:
  205. print("Caught keyboard interrupt, exiting")
  206. self.call_quit()
  207. os._exit(0)
  208. except ConnectionResetError:
  209. self.call_quit()
  210. def send(self, message):
  211. self.sc.sendall(message.encode('utf8'))
  212. def quit(server):
  213. for connection in server.connections:
  214. connection.sc.close()
  215. os._exit(0)
  216. def exit(server):
  217. try:
  218. while True:
  219. input('')
  220. except KeyboardInterrupt:
  221. quit(server)
  222. except EOFError:
  223. quit(server)
  224. if __name__ == '__main__':
  225. parser = argparse.ArgumentParser(description='Aphorism Server')
  226. parser.add_argument('host', help='Interface the server listens at')
  227. args = parser.parse_args()
  228. server = Server(args.host, 7312)
  229. server.start()
  230. exit = threading.Thread(target=exit, args=(server,))
  231. exit.start()