No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

buttonsrv.py 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. #!/usr/bin/env python2
  2. from __future__ import print_function
  3. # Seopardy Network Button Server
  4. #
  5. # This button server was written as a replacement for the "usual" button
  6. # hardware used with this game. It opens a port where users can telnet to
  7. # and allows them to use the enter key of their laptop as a button.
  8. #
  9. # Disclaimer: This code was written on the 31c3 while being sleep-deprived
  10. #
  11. # Workflow:
  12. # 1. Configure seopardy to use the Unix player input
  13. # 2. Start seopardy with questions
  14. # 3. Start button server with -u <path to unix socket>
  15. # 4. Tell players to telnet/netcat to your machine (default port 9999)
  16. # 5. Assign player numbers to all connection participants
  17. # 6. Start game
  18. import SocketServer
  19. import threading
  20. import select
  21. import Queue
  22. import sys
  23. import argparse
  24. import socket
  25. class PlayerHandler(SocketServer.BaseRequestHandler):
  26. """ Handle a player connection, used by SocketServer """
  27. banner = " ____ _ \n" + \
  28. "/ ___| ___ ___ _ __ __ _ _ __ __| |_ _ \n" + \
  29. "\___ \ / _ \/ _ \| '_ \ / _` | '__/ _` | | | |\n" + \
  30. " ___) | __/ (_) | |_) | (_| | | | (_| | |_| |\n" + \
  31. "|____/ \___|\___/| .__/ \__,_|_| \__,_|\__, |\n" + \
  32. " Network Button |_| Server v0.1 |___/ \n"
  33. connQueue = None
  34. regOpen = True
  35. usock = None
  36. def handle(self):
  37. self.player = None
  38. self.running = True
  39. try:
  40. self.handle_client()
  41. except:
  42. self.running = False
  43. raise
  44. self.running = False
  45. def handle_client(self):
  46. # self.request is the TCP socket connected to the client
  47. self.request.send(self.banner + "\n")
  48. if not self.regOpen:
  49. self.request.send("Currently no new players are accepted. Goodbye.\n")
  50. return
  51. print("?? Putting new client into queue", ":".join(map(str, self.client_address)))
  52. self.connQueue.put(self)
  53. self.request.send("Requesting a player socket for you\n")
  54. # waiting for a player number...
  55. while not self.player:
  56. if not self.regOpen:
  57. # management has closed the registration. be polite and say goodbye
  58. self.request.send("Currently no new players are accepted. Goodbye.\n")
  59. return
  60. if not self.running:
  61. # management has said "discard"
  62. self.request.send("Request denied. Goodbye.\n")
  63. return
  64. if len(select.select([self.request.fileno()], [], [], 0.5)[0]) > 0:
  65. if self.request.recv(1024) == '':
  66. print("!! Client %s was tired of waiting" % (":".join(map(str, cli.client_address)),))
  67. self.running = False
  68. return
  69. self.request.send("You are player %s!\n" % self.player)
  70. while self.running:
  71. # everytime we receive a UDP packet we interpret it as a button press
  72. data = self.request.recv(1024)
  73. if data == '':
  74. print("!! Client %s closed the connection" % (":".join(map(str, cli.client_address)),))
  75. return
  76. if self.running and self.usock:
  77. # if we are still connected && the board is still there, send a button press
  78. print("Button press from player %s (%s)" % (self.player, self.client_address[0]))
  79. self.request.sendall("Button press recognized... ")
  80. self.usock.send(str(self.player))
  81. class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
  82. pass
  83. class ButtonServer(threading.Thread):
  84. """ Thread that runs the button telnet server """
  85. def __init__(self, port):
  86. threading.Thread.__init__(self)
  87. self._port = port
  88. SocketServer.TCPServer.allow_reuse_address = True
  89. self.server = ThreadedTCPServer(("", self._port), PlayerHandler)
  90. self.daemon = True
  91. self._acceptClients = True
  92. def isOpen(self):
  93. return self._acceptClients
  94. def run(self):
  95. self.server.serve_forever()
  96. if __name__ == "__main__":
  97. parser = argparse.ArgumentParser()
  98. parser.add_argument("-u", "--unix-socket", required=True, help="Path to unix domain socket with seopardy on the other side")
  99. parser.add_argument("-p", "--port", type=int, default=9999, help="Port")
  100. args = parser.parse_args()
  101. # queue for new player connections
  102. connQueue = Queue.Queue()
  103. PlayerHandler.connQueue = connQueue
  104. # connect to unix domain socket
  105. usock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  106. try:
  107. usock.connect(args.unix_socket)
  108. except socket.error as e:
  109. print("Error: Could not connect to seopardy unix domain socket '%s': %s" % (args.unix_socket, str(e)))
  110. print("Exiting")
  111. sys.exit(1)
  112. PlayerHandler.usock = usock
  113. # start telnet button server
  114. ThreadedTCPServer.daemon_threads = True
  115. server = ButtonServer(args.port)
  116. server.start()
  117. buttonsOpen = False
  118. lastPlayer = None
  119. clients = {}
  120. print(PlayerHandler.banner)
  121. print("Disclaimer: Very hacky software!")
  122. print()
  123. print("Seopardy UNIX domain socket:", args.unix_socket)
  124. print("Server running on port:", args.port)
  125. helpText = "You are on the management console.\n\n" + \
  126. "Press c to close registration and k to kill a player's connection\n" + \
  127. "Options are: (c)lose registration, re(o)pen registration, (k)ill player, show (s)tatus\n"
  128. print(helpText)
  129. while True:
  130. (readSocks, _, _) = select.select([sys.stdin, usock], [], [], 0.5)
  131. # clean up players
  132. for playerNo, player in clients.items():
  133. if not player.running:
  134. print("-- Player %s (%s) disconnected" % (playerNo, ":".join(map(str, player.client_address))))
  135. del(clients[playerNo])
  136. if len(readSocks) > 0:
  137. if sys.stdin in readSocks:
  138. # handle data coming from stdin
  139. cmd = sys.stdin.readline().strip()
  140. if cmd == "c":
  141. # closing registration: new connections to the server will be discarded
  142. print(">> Closing registration")
  143. PlayerHandler.regOpen = False
  144. elif cmd == "o":
  145. # reopen registration
  146. if not PlayerHandler.regOpen:
  147. print(">> Reopening registration")
  148. PlayerHandler.regOpen = True
  149. else:
  150. print("!! Registration still open")
  151. elif cmd == "k":
  152. # disconnect a player
  153. inp = raw_input("Which player should I kill? ")
  154. playerNo = None
  155. try:
  156. playerNo = int(inp)
  157. except ValueError:
  158. pass
  159. if playerNo and playerNo in clients.keys():
  160. clients[playerNo].running = False
  161. clients[playerNo].request.close()
  162. del(clients[playerNo])
  163. print("++ Player %s killed" % playerNo)
  164. else:
  165. print("!! Player %s not found" % playerNo)
  166. elif cmd == "s":
  167. print("Unix socket:", args.unix_socket)
  168. print("TCP Port:", args.port)
  169. print("Clients: %d connected" % len(clients))
  170. for playerNo, player in clients.items():
  171. print(" -- Player %s (%s)" % (playerNo, ":".join(map(str, player.client_address))))
  172. print()
  173. elif cmd in ("h", "?", "help"):
  174. print(helpText)
  175. else:
  176. print("!! Unknown command...")
  177. elif usock in readSocks:
  178. # handle data coming from the seopardy board
  179. cmd = usock.recv(1)
  180. if cmd == '':
  181. # FIXME: Can we reconnect?
  182. print("!! Seopardy quitted its socket")
  183. PlayerHandler.usock = None
  184. for playerNo, player in clients.items():
  185. if player.running:
  186. clients[playerNo].request.send("\nThe board has quit and so do we. Goodbye player %s! :)\n" % playerNo)
  187. clients[playerNo].request.close()
  188. del(clients[playerNo])
  189. sys.exit(1)
  190. if cmd == "T":
  191. # T<playerNo> tells us which player's turn it is
  192. player = usock.recv(1)
  193. playerNo = None
  194. try:
  195. playerNo = int(player)
  196. except ValueError:
  197. pass
  198. if playerNo and playerNo in clients.keys():
  199. lastPlayer = playerNo
  200. clients[playerNo].request.send("Your turn! Please ask a question!\n")
  201. else:
  202. print("!! Unknown player: '%s' (not connected?)" % playerNo)
  203. elif cmd == "C":
  204. # buttons are currently closed
  205. print("-- Buttons are closed")
  206. if buttonsOpen:
  207. buttonsOpen = False
  208. for playerNo, client in clients.iteritems():
  209. if playerNo != lastPlayer:
  210. client.request.send("Buttons are closed...\n")
  211. elif cmd == "O":
  212. # buttons are open again!
  213. print("-- Buttons are open")
  214. lastPlayer = None
  215. if not buttonsOpen:
  216. buttonsOpen = True
  217. for client in clients.values():
  218. client.request.send("Buttons are open!\n")
  219. else:
  220. # if nothing else to do, check if we have a new client in the queue
  221. if not connQueue.empty():
  222. # new client awaits for us to assign them a player number
  223. cli = connQueue.get()
  224. if cli.running:
  225. cliHandled = False
  226. while not cliHandled and cli.running:
  227. print("New client, connecting from", ":".join(map(str, cli.client_address)))
  228. print("Options are: (d)iscard, or assigning a player number from 1 to 9")
  229. cmd = sys.stdin.readline().strip()
  230. if cmd == 'd':
  231. print("++ Discarding...")
  232. cli.running = False
  233. cliHandled = True
  234. elif ord(cmd[0]) > ord("0") and ord(cmd[0]) <= ord("9"):
  235. playerNo = ord(cmd[0]) - ord("0")
  236. assign = True
  237. if playerNo in clients.keys():
  238. # if player number is taken, allow user to kill them
  239. inp = raw_input("Player %s is already present. Kill them? (y/n) " % playerNo)
  240. if inp.strip().lower() == "y":
  241. print("Killing old player %s for your convenience." % playerNo)
  242. clients[playerNo].running = False
  243. clients[playerNo].request.close()
  244. del(clients[playerNo])
  245. else:
  246. print("Not assigning plaser number %s" % playerNo)
  247. assign = False
  248. if assign:
  249. cli.player = playerNo
  250. clients[playerNo] = cli
  251. cli.running = True
  252. cliHandled = True
  253. print("Player", playerNo, "connected")