home · contact · privacy
Add player move (only vertically), basic input error handling on server.
[plomrogue2-experiments] / server.py
index ed60b6a34b455b95a1511db4512cb0268e636ba2..121e22906377985100bb099759eb52f36574fdbf 100755 (executable)
--- a/server.py
+++ b/server.py
@@ -81,6 +81,13 @@ class IO_Handler(socketserver.BaseRequestHandler):
 
 class World:
     turn = 0
+    map_size = (5, 5)
+    map_ = 'xxxxx\n'+\
+           'x...x\n'+\
+           'x.X.x\n'+\
+           'x...x\n'+\
+           'xxxxx'
+    player_pos = [3, 3]
 
 
 def fib(n):
@@ -91,6 +98,10 @@ def fib(n):
         return fib(n-1) + fib(n-2)
 
 
+class ArgumentError(Exception):
+    pass
+
+
 class CommandHandler:
 
     def __init__(self, queues_out):
@@ -98,6 +109,7 @@ class CommandHandler:
         self.queues_out = queues_out
         self.pool = Pool()
         self.world = World()
+        self.pool_result = None
 
     def send_to(self, connection_id, msg):
         """Send msg to client of connection_id."""
@@ -108,38 +120,62 @@ class CommandHandler:
         for connection_id in self.queues_out:
             self.send_to(connection_id, msg)
 
+    def stringify_yx(self, tuple_):
+        """Transform tuple (y,x) into string 'Y:'+str(y)+',X:'+str(x)."""
+        return 'Y:' + str(tuple_[0]) + ',X:' + str(tuple_[1])
+
     def cmd_fib(self, tokens, connection_id):
         """Reply with n-th Fibonacci numbers, n taken from tokens[1:].
 
         Numbers are calculated in parallel as far as possible, using fib().
         A 'CALCULATING …' message is sent to caller before the result.
         """
-        fib_fail = 'MALFORMED FIB REQUEST'
         if len(tokens) < 2:
-            self.send_to(connection_id, fib_fail)
-            return
+            raise ArgumentError('FIB NEEDS AT LEAST ONE ARGUMENT')
         numbers = []
         for token in tokens[1:]:
-            if token != '0' and token.isdigit():
-                numbers += [int(token)]
-            else:
-                self.send_to(connection_id, fib_fail)
-                return
+            if token == '0' or not token.isdigit():
+                raise ArgumentError('FIB ARGUMENTS MUST BE INTEGERS > 0')
+            numbers += [int(token)]
         self.send_to(connection_id, 'CALCULATING …')
         results = self.pool.map(fib, numbers)
         reply = ' '.join([str(r) for r in results])
         self.send_to(connection_id, reply)
 
     def cmd_inc(self, connection_id):
-        """Increment world.turn, send TURN_FINISHED, NEW_TURN to everyone."""
+        """Increment world.turn, send game turn data to everyone.
+
+        To simulate game processing waiting times, a one second delay between
+        TURN_FINISHED and NEW_TURN occurs; after NEW_TURN, some expensive
+        calculations are started as pool processes that need to be finished
+        until a further INC finishes the turn.
+        """
+        from time import sleep
+        if self.pool_result is not None:
+            self.pool_result.wait()
         self.send_all('TURN_FINISHED ' + str(self.world.turn))
+        sleep(1)
         self.world.turn += 1
         self.send_all('NEW_TURN ' + str(self.world.turn))
+        self.send_all('MAP_SIZE ' + self.stringify_yx(self.world.map_size))
+        self.send_all('TERRAIN\n' + self.world.map_)
+        self.send_all('POSITION ' + self.stringify_yx(self.world.player_pos))
+        self.pool_result = self.pool.map_async(fib, (35, 35))
 
     def cmd_get_turn(self, connection_id):
         """Send world.turn to caller."""
         self.send_to(connection_id, str(self.world.turn))
 
+    def cmd_move(self, direction):
+        """Move player 'UP' or 'DOWN' depending on direction string."""
+        if not direction in {'UP', 'DOWN'}:
+            raise ArgumentError('MOVE ARGUMENT MUST BE "UP" or "DOWN"')
+        if direction == 'UP':
+            self.world.player_pos[0] -= 1
+        else:
+            self.world.player_pos[0] += 1
+        self.send_all('POSITION ' + self.stringify_yx(self.world.player_pos))
+
     def cmd_echo(self, tokens, input_, connection_id):
         """Send message in input_ beyond tokens[0] to caller."""
         msg = input_[len(tokens[0]) + 1:]
@@ -153,21 +189,26 @@ class CommandHandler:
     def handle_input(self, input_, connection_id):
         """Process input_ to command grammar, call command handler if found."""
         tokens = [token for token in input_.split(' ') if len(token) > 0]
-        if len(tokens) == 0:
-            self.send_to(connection_id, 'EMPTY COMMAND')
-        elif len(tokens) == 1 and tokens[0] == 'INC':
-            self.cmd_inc(connection_id)
-        elif len(tokens) == 1 and tokens[0] == 'GET_TURN':
-            self.cmd_get_turn(connection_id)
-        elif len(tokens) >= 1 and tokens[0] == 'ECHO':
-            self.cmd_echo(tokens, input_, connection_id)
-        elif len(tokens) >= 1 and tokens[0] == 'ALL':
-            self.cmd_all(tokens, input_)
-        elif len(tokens) >= 1 and tokens[0] == 'FIB':
-            # TODO: Should this really block the whole loop?
-            self.cmd_fib(tokens, connection_id)
-        else:
-            self.send_to(connection_id, 'UNKNOWN COMMAND')
+        try:
+            if len(tokens) == 0:
+                self.send_to(connection_id, 'EMPTY COMMAND')
+            elif len(tokens) == 1 and tokens[0] == 'INC':
+                self.cmd_inc(connection_id)
+            elif len(tokens) == 1 and tokens[0] == 'GET_TURN':
+                self.cmd_get_turn(connection_id)
+            elif len(tokens) == 2 and tokens[0] == 'MOVE':
+                self.cmd_move(tokens[1])
+            elif len(tokens) >= 1 and tokens[0] == 'ECHO':
+                self.cmd_echo(tokens, input_, connection_id)
+            elif len(tokens) >= 1 and tokens[0] == 'ALL':
+                self.cmd_all(tokens, input_)
+            elif len(tokens) >= 1 and tokens[0] == 'FIB':
+                # TODO: Should this really block the whole loop?
+                self.cmd_fib(tokens, connection_id)
+            else:
+                self.send_to(connection_id, 'UNKNOWN COMMAND')
+        except ArgumentError as e:
+            self.send_to(connection_id, 'ARGUMENT ERROR: ' + str(e))
 
 
 def io_loop(q):