Browse Source

Buttonserver (Telnet Buttons!)

Sebastian Lohff 4 years ago
parent
commit
936c98da86
1 changed files with 293 additions and 0 deletions
  1. 293
    0
      extra/buttonsrv.py

+ 293
- 0
extra/buttonsrv.py View File

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

Loading…
Cancel
Save