#!/usr/bin/env python2 from __future__ import print_function # Seopardy Network Button Server # # This button server was written as a replacement for the "usual" button # hardware used with this game. It opens a port where users can telnet to # and allows them to use the enter key of their laptop as a button. # # Disclaimer: This code was written on the 31c3 while being sleep-deprived # # Workflow: # 1. Configure seopardy to use the Unix player input # 2. Start seopardy with questions # 3. Start button server with -u # 4. Tell players to telnet/netcat to your machine (default port 9999) # 5. Assign player numbers to all connection participants # 6. Start game import SocketServer import threading import select import Queue import sys import argparse import socket class PlayerHandler(SocketServer.BaseRequestHandler): """ Handle a player connection, used by SocketServer """ banner = " ____ _ \n" + \ "/ ___| ___ ___ _ __ __ _ _ __ __| |_ _ \n" + \ "\___ \ / _ \/ _ \| '_ \ / _` | '__/ _` | | | |\n" + \ " ___) | __/ (_) | |_) | (_| | | | (_| | |_| |\n" + \ "|____/ \___|\___/| .__/ \__,_|_| \__,_|\__, |\n" + \ " Network Button |_| Server v0.1 |___/ \n" connQueue = None regOpen = True usock = None def handle(self): self.player = None self.running = True try: self.handle_client() except: self.running = False raise self.running = False def handle_client(self): # self.request is the TCP socket connected to the client self.request.send(self.banner + "\n") if not self.regOpen: self.request.send("Currently no new players are accepted. Goodbye.\n") return print("?? Putting new client into queue", ":".join(map(str, self.client_address))) self.connQueue.put(self) self.request.send("Requesting a player socket for you\n") # waiting for a player number... while not self.player: if not self.regOpen: # management has closed the registration. be polite and say goodbye self.request.send("Currently no new players are accepted. Goodbye.\n") return if not self.running: # management has said "discard" self.request.send("Request denied. Goodbye.\n") return if len(select.select([self.request.fileno()], [], [], 0.5)[0]) > 0: if self.request.recv(1024) == '': print("!! Client %s was tired of waiting" % (":".join(map(str, cli.client_address)),)) self.running = False return self.request.send("You are player %s!\n" % self.player) while self.running: # everytime we receive a UDP packet we interpret it as a button press data = self.request.recv(1024) if data == '': print("!! Client %s closed the connection" % (":".join(map(str, cli.client_address)),)) return if self.running and self.usock: # if we are still connected && the board is still there, send a button press print("Button press from player %s (%s)" % (self.player, self.client_address[0])) self.request.sendall("Button press recognized... ") self.usock.send(str(self.player)) class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass class ButtonServer(threading.Thread): """ Thread that runs the button telnet server """ def __init__(self, port): threading.Thread.__init__(self) self._port = port SocketServer.TCPServer.allow_reuse_address = True self.server = ThreadedTCPServer(("", self._port), PlayerHandler) self.daemon = True self._acceptClients = True def isOpen(self): return self._acceptClients def run(self): self.server.serve_forever() if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-u", "--unix-socket", required=True, help="Path to unix domain socket with seopardy on the other side") parser.add_argument("-p", "--port", type=int, default=9999, help="Port") args = parser.parse_args() # queue for new player connections connQueue = Queue.Queue() PlayerHandler.connQueue = connQueue # connect to unix domain socket usock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: usock.connect(args.unix_socket) except socket.error as e: print("Error: Could not connect to seopardy unix domain socket '%s': %s" % (args.unix_socket, str(e))) print("Exiting") sys.exit(1) PlayerHandler.usock = usock # start telnet button server ThreadedTCPServer.daemon_threads = True server = ButtonServer(args.port) server.start() buttonsOpen = False lastPlayer = None clients = {} print(PlayerHandler.banner) print("Disclaimer: Very hacky software!") print() print("Seopardy UNIX domain socket:", args.unix_socket) print("Server running on port:", args.port) helpText = "You are on the management console.\n\n" + \ "Press c to close registration and k to kill a player's connection\n" + \ "Options are: (c)lose registration, re(o)pen registration, (k)ill player, show (s)tatus\n" print(helpText) while True: (readSocks, _, _) = select.select([sys.stdin, usock], [], [], 0.5) # clean up players for playerNo, player in clients.items(): if not player.running: print("-- Player %s (%s) disconnected" % (playerNo, ":".join(map(str, player.client_address)))) del(clients[playerNo]) if len(readSocks) > 0: if sys.stdin in readSocks: # handle data coming from stdin cmd = sys.stdin.readline().strip() if cmd == "c": # closing registration: new connections to the server will be discarded print(">> Closing registration") PlayerHandler.regOpen = False elif cmd == "o": # reopen registration if not PlayerHandler.regOpen: print(">> Reopening registration") PlayerHandler.regOpen = True else: print("!! Registration still open") elif cmd == "k": # disconnect a player inp = raw_input("Which player should I kill? ") playerNo = None try: playerNo = int(inp) except ValueError: pass if playerNo and playerNo in clients.keys(): clients[playerNo].running = False clients[playerNo].request.close() del(clients[playerNo]) print("++ Player %s killed" % playerNo) else: print("!! Player %s not found" % playerNo) elif cmd == "s": print("Unix socket:", args.unix_socket) print("TCP Port:", args.port) print("Clients: %d connected" % len(clients)) for playerNo, player in clients.items(): print(" -- Player %s (%s)" % (playerNo, ":".join(map(str, player.client_address)))) print() elif cmd in ("h", "?", "help"): print(helpText) else: print("!! Unknown command...") elif usock in readSocks: # handle data coming from the seopardy board cmd = usock.recv(1) if cmd == '': # FIXME: Can we reconnect? print("!! Seopardy quitted its socket") PlayerHandler.usock = None for playerNo, player in clients.items(): if player.running: clients[playerNo].request.send("\nThe board has quit and so do we. Goodbye player %s! :)\n" % playerNo) clients[playerNo].request.close() del(clients[playerNo]) sys.exit(1) if cmd == "T": # T tells us which player's turn it is player = usock.recv(1) playerNo = None try: playerNo = int(player) except ValueError: pass if playerNo and playerNo in clients.keys(): lastPlayer = playerNo clients[playerNo].request.send("Your turn! Please ask a question!\n") else: print("!! Unknown player: '%s' (not connected?)" % playerNo) elif cmd == "C": # buttons are currently closed print("-- Buttons are closed") if buttonsOpen: buttonsOpen = False for playerNo, client in clients.iteritems(): if playerNo != lastPlayer: client.request.send("Buttons are closed...\n") elif cmd == "O": # buttons are open again! print("-- Buttons are open") lastPlayer = None if not buttonsOpen: buttonsOpen = True for client in clients.values(): client.request.send("Buttons are open!\n") else: # if nothing else to do, check if we have a new client in the queue if not connQueue.empty(): # new client awaits for us to assign them a player number cli = connQueue.get() if cli.running: cliHandled = False while not cliHandled and cli.running: print("New client, connecting from", ":".join(map(str, cli.client_address))) print("Options are: (d)iscard, or assigning a player number from 1 to 9") cmd = sys.stdin.readline().strip() if cmd == 'd': print("++ Discarding...") cli.running = False cliHandled = True elif ord(cmd[0]) > ord("0") and ord(cmd[0]) <= ord("9"): playerNo = ord(cmd[0]) - ord("0") assign = True if playerNo in clients.keys(): # if player number is taken, allow user to kill them inp = raw_input("Player %s is already present. Kill them? (y/n) " % playerNo) if inp.strip().lower() == "y": print("Killing old player %s for your convenience." % playerNo) clients[playerNo].running = False clients[playerNo].request.close() del(clients[playerNo]) else: print("Not assigning plaser number %s" % playerNo) assign = False if assign: cli.player = playerNo clients[playerNo] = cli cli.running = True cliHandled = True print("Player", playerNo, "connected")