seopardy/extra/buttonsrv.py

294 lines
9.3 KiB
Python
Executable File

#!/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 <path to unix socket>
# 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<playerNo> 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")