Buttonserver (Telnet Buttons!)
This commit is contained in:
parent
756010094d
commit
936c98da86
|
@ -0,0 +1,293 @@
|
|||
#!/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")
|
Loading…
Reference in New Issue