294 lines
9.3 KiB
Python
Executable File
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")
|