home · contact · privacy
Refactor.
authorChristian Heller <c.heller@plomlompom.de>
Tue, 15 Jan 2019 02:04:51 +0000 (03:04 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 15 Jan 2019 02:04:51 +0000 (03:04 +0100)
server.py
server_/game.py
server_/io.py

index 9ba6d6fe24fdbf87cec3df148f281e24bc51ff5b..ac26ca7f1c39dd0e1853c038ff80df2a393b2844 100755 (executable)
--- a/server.py
+++ b/server.py
@@ -9,7 +9,7 @@ if len(sys.argv) != 2:
     print('wrong number of arguments, expected one (game file)')
     exit(1)
 game_file_name = sys.argv[1]
     print('wrong number of arguments, expected one (game file)')
     exit(1)
 game_file_name = sys.argv[1]
-command_handler = server_.game.CommandHandler(game_file_name)
+game = server_.game.Game(game_file_name)
 if os.path.exists(game_file_name):
     if not os.path.isfile(game_file_name):
         print('game file name does not refer to a valid game file')
 if os.path.exists(game_file_name):
     if not os.path.isfile(game_file_name):
         print('game file name does not refer to a valid game file')
@@ -19,18 +19,18 @@ if os.path.exists(game_file_name):
         for i in range(len(lines)):
             line = lines[i]
             print("FILE INPUT LINE %s: %s" % (i, line), end='')
         for i in range(len(lines)):
             line = lines[i]
             print("FILE INPUT LINE %s: %s" % (i, line), end='')
-            command_handler.handle_input(line, store=False)
+            game.io.handle_input(line, store=False)
 else:
 else:
-    command_handler.handle_input('MAP_SIZE Y:5,X:5')
-    command_handler.handle_input('TERRAIN_LINE 0 "xxxxx"')
-    command_handler.handle_input('TERRAIN_LINE 1 "x...x"')
-    command_handler.handle_input('TERRAIN_LINE 2 "x.X.x"')
-    command_handler.handle_input('TERRAIN_LINE 3 "x...x"')
-    command_handler.handle_input('TERRAIN_LINE 4 "xxxxx"')
-    command_handler.handle_input('THING_TYPE 0 human')
-    command_handler.handle_input('THING_POS 0 Y:3,X:3')
-    command_handler.handle_input('THING_TYPE 1 monster')
-    command_handler.handle_input('THING_POS 1 Y:1,X:1')
+    game.io.handle_input('MAP_SIZE Y:5,X:5')
+    game.io.handle_input('TERRAIN_LINE 0 "xxxxx"')
+    game.io.handle_input('TERRAIN_LINE 1 "x...x"')
+    game.io.handle_input('TERRAIN_LINE 2 "x.X.x"')
+    game.io.handle_input('TERRAIN_LINE 3 "x...x"')
+    game.io.handle_input('TERRAIN_LINE 4 "xxxxx"')
+    game.io.handle_input('THING_TYPE 0 human')
+    game.io.handle_input('THING_POS 0 Y:3,X:3')
+    game.io.handle_input('THING_TYPE 1 monster')
+    game.io.handle_input('THING_POS 1 Y:1,X:1')
 
 
 
 
-server_.io.run_server_with_io_loop(command_handler)
+server_.io.run_server_with_io_loop(game)
index b52f16ef8eb3f20e7f2b4699c08bc34093c4d3de..c881f8e2ad98c2aa62b62cd742e5e45226aae18f 100644 (file)
@@ -1,7 +1,6 @@
 import sys
 sys.path.append('../')
 import game_common
 import sys
 sys.path.append('../')
 import game_common
-import parser
 
 
 class GameError(Exception):
 
 
 class GameError(Exception):
@@ -194,71 +193,18 @@ def fib(n):
         return fib(n-1) + fib(n-2)
 
 
         return fib(n-1) + fib(n-2)
 
 
-class CommandHandler(game_common.Commander):
+class Game(game_common.Commander):
 
     def __init__(self, game_file_name):
 
     def __init__(self, game_file_name):
-        self.queues_out = {}
+        import server_.io
         self.world = World()
         self.world = World()
-        self.parser = parser.Parser(self)
-        self.game_file_name = game_file_name
+        self.io = server_.io.GameIO(game_file_name, self)
         # self.pool and self.pool_result are currently only needed by the FIB
         # command and the demo of a parallelized game loop in cmd_inc_p.
         from multiprocessing import Pool
         self.pool = Pool()
         self.pool_result = None
 
         # self.pool and self.pool_result are currently only needed by the FIB
         # command and the demo of a parallelized game loop in cmd_inc_p.
         from multiprocessing import Pool
         self.pool = Pool()
         self.pool_result = None
 
-    def send(self, msg, connection_id=None):
-        """Send message msg to server's client(s) via self.queues_out.
-
-        If a specific client is identified by connection_id, only
-        sends msg to that one. Else, sends it to all clients
-        identified in self.queues_out.
-
-        """
-        if connection_id:
-            self.queues_out[connection_id].put(msg)
-        else:
-            for connection_id in self.queues_out:
-                self.queues_out[connection_id].put(msg)
-
-    def handle_input(self, input_, connection_id=None, store=True):
-        """Process input_ to command grammar, call command handler if found."""
-        from inspect import signature
-
-        def answer(connection_id, msg):
-            if connection_id:
-                self.send(msg, connection_id)
-            else:
-                print(msg)
-
-        try:
-            command = self.parser.parse(input_)
-            if command is None:
-                answer(connection_id, 'UNHANDLED_INPUT')
-            else:
-                if 'connection_id' in list(signature(command).parameters):
-                    command(connection_id=connection_id)
-                else:
-                    command()
-                    if store:
-                        with open(self.game_file_name, 'a') as f:
-                            f.write(input_ + '\n')
-        except parser.ArgError as e:
-            answer(connection_id, 'ARGUMENT_ERROR ' + self.quote(str(e)))
-        except game.GameError as e:
-            answer(connection_id, 'GAME_ERROR ' + self.quote(str(e)))
-
-    def quote(self, string):
-        """Quote & escape string so client interprets it as single token."""
-        quoted = []
-        quoted += ['"']
-        for c in string:
-            if c in {'"', '\\'}:
-                quoted += ['\\']
-            quoted += [c]
-        quoted += ['"']
-        return ''.join(quoted)
-
     def send_gamestate(self, connection_id=None):
         """Send out game state data relevant to clients."""
 
     def send_gamestate(self, connection_id=None):
         """Send out game state data relevant to clients."""
 
@@ -266,17 +212,17 @@ class CommandHandler(game_common.Commander):
             """Transform tuple (y,x) into string 'Y:'+str(y)+',X:'+str(x)."""
             return 'Y:' + str(tuple_[0]) + ',X:' + str(tuple_[1])
 
             """Transform tuple (y,x) into string 'Y:'+str(y)+',X:'+str(x)."""
             return 'Y:' + str(tuple_[0]) + ',X:' + str(tuple_[1])
 
-        self.send('NEW_TURN ' + str(self.world.turn))
-        self.send('MAP_SIZE ' + stringify_yx(self.world.map_.size))
+        self.io.send('NEW_TURN ' + str(self.world.turn))
+        self.io.send('MAP_SIZE ' + stringify_yx(self.world.map_.size))
         visible_map = self.world.get_player().get_visible_map()
         for y in range(self.world.map_.size[0]):
         visible_map = self.world.get_player().get_visible_map()
         for y in range(self.world.map_.size[0]):
-            self.send('VISIBLE_MAP_LINE %5s %s' %
-                      (y, self.quote(visible_map.get_line(y))))
+            self.io.send('VISIBLE_MAP_LINE %5s %s' %
+                         (y, self.io.quote(visible_map.get_line(y))))
         visible_things = self.world.get_player().get_visible_things()
         for thing in visible_things:
         visible_things = self.world.get_player().get_visible_things()
         for thing in visible_things:
-            self.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
-            self.send('THING_POS %s %s' % (thing.id_,
-                                           stringify_yx(thing.position)))
+            self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
+            self.io.send('THING_POS %s %s' % (thing.id_,
+                                              stringify_yx(thing.position)))
 
     def proceed(self):
         """Send turn finish signal, run game world, send new world data.
 
     def proceed(self):
         """Send turn finish signal, run game world, send new world data.
@@ -284,10 +230,10 @@ class CommandHandler(game_common.Commander):
         First sends 'TURN_FINISHED' message, then runs game world
         until new player input is needed, then sends game state.
         """
         First sends 'TURN_FINISHED' message, then runs game world
         until new player input is needed, then sends game state.
         """
-        self.send('TURN_FINISHED ' + str(self.world.turn))
+        self.io.send('TURN_FINISHED ' + str(self.world.turn))
         self.world.proceed_to_next_player_turn()
         msg = str(self.world.get_player().last_task_result)
         self.world.proceed_to_next_player_turn()
         msg = str(self.world.get_player().last_task_result)
-        self.send('LAST_PLAYER_TASK_RESULT ' + self.quote(msg))
+        self.io.send('LAST_PLAYER_TASK_RESULT ' + self.io.quote(msg))
         self.send_gamestate()
 
     def cmd_FIB(self, numbers, connection_id):
         self.send_gamestate()
 
     def cmd_FIB(self, numbers, connection_id):
@@ -296,10 +242,10 @@ class CommandHandler(game_common.Commander):
         Numbers are calculated in parallel as far as possible, using fib().
         A 'CALCULATING …' message is sent to caller before the result.
         """
         Numbers are calculated in parallel as far as possible, using fib().
         A 'CALCULATING …' message is sent to caller before the result.
         """
-        self.send('CALCULATING …', connection_id)
+        self.io.send('CALCULATING …', connection_id)
         results = self.pool.map(fib, numbers)
         reply = ' '.join([str(r) for r in results])
         results = self.pool.map(fib, numbers)
         reply = ' '.join([str(r) for r in results])
-        self.send(reply, connection_id)
+        self.io.send(reply, connection_id)
     cmd_FIB.argtypes = 'seq:int:nonneg'
 
     def cmd_INC_P(self, connection_id):
     cmd_FIB.argtypes = 'seq:int:nonneg'
 
     def cmd_INC_P(self, connection_id):
@@ -319,7 +265,7 @@ class CommandHandler(game_common.Commander):
         from time import sleep
         if self.pool_result is not None:
             self.pool_result.wait()
         from time import sleep
         if self.pool_result is not None:
             self.pool_result.wait()
-        self.send('TURN_FINISHED ' + str(self.world.turn))
+        self.io.send('TURN_FINISHED ' + str(self.world.turn))
         sleep(1)
         self.world.turn += 1
         self.send_gamestate()
         sleep(1)
         self.world.turn += 1
         self.send_gamestate()
@@ -327,6 +273,7 @@ class CommandHandler(game_common.Commander):
 
     def cmd_MOVE(self, direction):
         """Set player task to 'move' with direction arg, finish player turn."""
 
     def cmd_MOVE(self, direction):
         """Set player task to 'move' with direction arg, finish player turn."""
+        import parser
         if direction not in {'UP', 'DOWN', 'RIGHT', 'LEFT'}:
             raise parser.ArgError('Move argument must be one of: '
                                   'UP, DOWN, RIGHT, LEFT')
         if direction not in {'UP', 'DOWN', 'RIGHT', 'LEFT'}:
             raise parser.ArgError('Move argument must be one of: '
                                   'UP, DOWN, RIGHT, LEFT')
@@ -345,12 +292,12 @@ class CommandHandler(game_common.Commander):
 
     def cmd_ECHO(self, msg, connection_id):
         """Send msg to caller."""
 
     def cmd_ECHO(self, msg, connection_id):
         """Send msg to caller."""
-        self.send(msg, connection_id)
+        self.io.send(msg, connection_id)
     cmd_ECHO.argtypes = 'string'
 
     def cmd_ALL(self, msg, connection_id):
         """Send msg to all clients."""
     cmd_ECHO.argtypes = 'string'
 
     def cmd_ALL(self, msg, connection_id):
         """Send msg to all clients."""
-        self.send(msg)
+        self.io.send(msg)
     cmd_ALL.argtypes = 'string'
 
     def cmd_TERRAIN_LINE(self, y, terrain_line):
     cmd_ALL.argtypes = 'string'
 
     def cmd_TERRAIN_LINE(self, y, terrain_line):
index 95e7d7782f7eecf4ab318d330853cb2285881652..7a4c3b0fc5717bca8ece750f5c62d111b3302c4f 100644 (file)
@@ -1,6 +1,9 @@
 import socketserver
 import threading
 import queue
 import socketserver
 import threading
 import queue
+import sys
+sys.path.append('../')
+import parser
 
 
 # Avoid "Address already in use" errors.
 
 
 # Avoid "Address already in use" errors.
@@ -86,53 +89,113 @@ class IO_Handler(socketserver.BaseRequestHandler):
         self.request.close()
 
 
         self.request.close()
 
 
-def io_loop(q, game_command_handler):
-    """Handle commands coming through queue q, send results back.
+class GameIO():
 
 
-    Commands from q are expected to be tuples, with the first element
-    either 'ADD_QUEUE', 'COMMAND', or 'KILL_QUEUE', the second element
-    a UUID, and an optional third element of arbitrary type. The UUID
-    identifies a receiver for replies.
+    def __init__(self, game_file_name, game):
+        self.game_file_name = game_file_name
+        self.queues_out = {}
+        self.parser = parser.Parser(game)
 
 
-    An 'ADD_QUEUE' command should contain as third element a queue
-    through which to send messages back to the sender of the
-    command. A 'KILL_QUEUE' command removes the queue for that
-    receiver from the list of queues through which to send replies.
+    def loop(self, q):
+        """Handle commands coming through queue q, send results back.
 
 
-    A 'COMMAND' command is specified in greater detail by a string
-    that is the tuple's third element. The game_command_handler takes
-    care of processing this and sending out replies.
+        Commands from q are expected to be tuples, with the first element
+        either 'ADD_QUEUE', 'COMMAND', or 'KILL_QUEUE', the second element
+        a UUID, and an optional third element of arbitrary type. The UUID
+        identifies a receiver for replies.
 
 
-    """
-    while True:
-        x = q.get()
-        command_type = x[0]
-        connection_id = x[1]
-        content = None if len(x) == 2 else x[2]
-        if command_type == 'ADD_QUEUE':
-            game_command_handler.queues_out[connection_id] = content
-        elif command_type == 'KILL_QUEUE':
-            del game_command_handler.queues_out[connection_id]
-        elif command_type == 'COMMAND':
-            game_command_handler.handle_input(content, connection_id)
-
-
-def run_server_with_io_loop(command_handler):
+        An 'ADD_QUEUE' command should contain as third element a queue
+        through which to send messages back to the sender of the
+        command. A 'KILL_QUEUE' command removes the queue for that
+        receiver from the list of queues through which to send replies.
+
+        A 'COMMAND' command is specified in greater detail by a string
+        that is the tuple's third element. The game_command_handler takes
+        care of processing this and sending out replies.
+
+        """
+        while True:
+            x = q.get()
+            command_type = x[0]
+            connection_id = x[1]
+            content = None if len(x) == 2 else x[2]
+            if command_type == 'ADD_QUEUE':
+                self.queues_out[connection_id] = content
+            elif command_type == 'KILL_QUEUE':
+                del self.queues_out[connection_id]
+            elif command_type == 'COMMAND':
+                self.handle_input(content, connection_id)
+
+    def handle_input(self, input_, connection_id=None, store=True):
+        """Process input_ to command grammar, call command handler if found."""
+        from inspect import signature
+        import server_.game
+
+        def answer(connection_id, msg):
+            if connection_id:
+                self.send(msg, connection_id)
+            else:
+                print(msg)
+
+        try:
+            command = self.parser.parse(input_)
+            if command is None:
+                answer(connection_id, 'UNHANDLED_INPUT')
+            else:
+                if 'connection_id' in list(signature(command).parameters):
+                    command(connection_id=connection_id)
+                else:
+                    command()
+                    if store:
+                        with open(self.game_file_name, 'a') as f:
+                            f.write(input_ + '\n')
+        except parser.ArgError as e:
+            answer(connection_id, 'ARGUMENT_ERROR ' + self.quote(str(e)))
+        except server_.game.GameError as e:
+            answer(connection_id, 'GAME_ERROR ' + self.quote(str(e)))
+
+    def send(self, msg, connection_id=None):
+        """Send message msg to server's client(s) via self.queues_out.
+
+        If a specific client is identified by connection_id, only
+        sends msg to that one. Else, sends it to all clients
+        identified in self.queues_out.
+
+        """
+        if connection_id:
+            self.queues_out[connection_id].put(msg)
+        else:
+            for connection_id in self.queues_out:
+                self.queues_out[connection_id].put(msg)
+
+    def quote(self, string):
+        """Quote & escape string so client interprets it as single token."""
+        # FIXME: Don't do this as a method, makes no sense.
+        quoted = []
+        quoted += ['"']
+        for c in string:
+            if c in {'"', '\\'}:
+                quoted += ['\\']
+            quoted += [c]
+        quoted += ['"']
+        return ''.join(quoted)
+
+
+def run_server_with_io_loop(game):
     """Run connection of server talking to clients and game IO loop.
 
     We have the TCP server (an instance of Server) and we have the
     """Run connection of server talking to clients and game IO loop.
 
     We have the TCP server (an instance of Server) and we have the
-    game IO loop, a thread running io_loop. Both communicate with each
-    other via a queue.Queue. While the TCP server may spawn parallel
-    threads to many clients, the IO loop works sequentially through
-    game commands received from the TCP server's threads (= client
-    connections to the TCP server), calling command_handler to process
-    them. A processed command may trigger messages to the commanding
-    client or to all clients, delivered from the IO loop to the TCP
-    server via the queue.
+    game IO loop, a thread running Game.io.loop. Both communicate with
+    each other via a queue.Queue. While the TCP server may spawn
+    parallel threads to many clients, the IO loop works sequentially
+    through game commands received from the TCP server's threads (=
+    client connections to the TCP server). A processed command may
+    trigger messages to the commanding client or to all clients,
+    delivered from the IO loop to the TCP server via the queue.
 
     """
     q = queue.Queue()
 
     """
     q = queue.Queue()
-    c = threading.Thread(target=io_loop, daemon=True, args=(q, command_handler))
+    c = threading.Thread(target=game.io.loop, daemon=True, args=(q,))
     c.start()
     server = Server(q)
     try:
     c.start()
     server = Server(q)
     try: