+ def quoted(self, string):
+ """Quote and 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 quoted_map(self, map_string, map_width):
+ """Put \n into map_string at map_width intervals, return quoted whole."""
+ map_lines = []
+ map_size = len(map_string)
+ start_cut = 0
+ while start_cut < map_size:
+ limit = start_cut + map_width
+ map_lines += [map_string[start_cut:limit]]
+ start_cut = limit
+ return self.quoted("\n".join(map_lines))
+
+ def send_all_gamestate(self):
+ """Send out game state data relevant to clients."""
+ 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.quoted_map(self.world.map_,
+ self.world.map_size[1]))
+ for thing in self.world.things:
+ self.send_all('THING TYPE:' + thing.type_ + ' '
+ + self.stringify_yx(thing.position))
+
+ def proceed_to_next_player_turn(self, connection_id):
+ """Run game world turns until player can decide their next step.
+
+ Sends a 'TURN_FINISHED' message, then iterates through all non-player
+ things, on each step furthering them in their tasks (and letting them
+ decide new ones if they finish). The iteration order is: first all
+ things that come after the player in the world things list, then (after
+ incrementing the world turn) all that come before the player; then the
+ player's .proceed() is run, and if it does not finish his task, the
+ loop starts at the beginning. Once the player's task is finished, the
+ loop breaks, and client-relevant game data is sent.
+ """
+ self.send_all('TURN_FINISHED ' + str(self.world.turn))
+ while True:
+ for thing in self.world.things[self.world.player_i+1:]:
+ thing.proceed()
+ self.world.turn += 1
+ for thing in self.world.things[:self.world.player_i]:
+ thing.proceed()
+ self.world.player.proceed(is_AI=False)
+ if self.world.player.task is None:
+ break
+ self.send_all_gamestate()
+
+ def cmd_MOVE(self, direction, connection_id):
+ """Set player task to 'move' with direction arg, finish player turn."""
+ if direction not in {'UP', 'DOWN', 'RIGHT', 'LEFT'}:
+ raise ArgError('Move argument must be one of: '
+ 'UP, DOWN, RIGHT, LEFT')
+ self.world.player.set_task('move', direction=direction)
+ self.proceed_to_next_player_turn(connection_id)
+ cmd_MOVE.argtypes = 'string'
+
+ def cmd_WAIT(self, connection_id):
+ """Set player task to 'wait', finish player turn."""
+ self.world.player.set_task('wait')
+ self.proceed_to_next_player_turn(connection_id)
+
+ def cmd_GET_TURN(self, connection_id):
+ """Send world.turn to caller."""
+ self.send_to(connection_id, str(self.world.turn))
+
+ def cmd_ECHO(self, msg, connection_id):
+ """Send msg to caller."""
+ self.send_to(connection_id, msg)
+ cmd_ECHO.argtypes = 'string'
+
+ def cmd_ALL(self, msg, connection_id):
+ """Send msg to all clients."""
+ self.send_all(msg)
+ cmd_ALL.argtypes = 'string'
+
+ def cmd_FIB(self, numbers, connection_id):