+ tokens = self.tokenize(msg)
+ if len(tokens) == 0:
+ return None
+ method_candidate = 'cmd_' + tokens[0]
+ if not hasattr(self.game, method_candidate):
+ return None
+ method = getattr(self.game, method_candidate)
+ if len(tokens) == 1:
+ if not hasattr(method, 'argtypes'):
+ return method
+ else:
+ raise ParseError('Command expects argument(s).')
+ args_candidates = tokens[1:]
+ if not hasattr(method, 'argtypes'):
+ raise ParseError('Command expects no argument(s).')
+ args, kwargs = self.argsparse(method.argtypes, args_candidates)
+ return partial(method, *args, **kwargs)
+
+ def parse_yx_tuple(self, yx_string):
+ """Parse yx_string as yx_tuple:nonneg argtype, return result."""
+
+ def get_axis_position_from_argument(axis, token):
+ if len(token) < 3 or token[:2] != axis + ':' or \
+ not token[2:].isdigit():
+ raise ParseError('Non-int arg for ' + axis + ' position.')
+ n = int(token[2:])
+ if n < 1:
+ raise ParseError('Arg for ' + axis + ' position < 1.')
+ return n
+
+ tokens = yx_string.split(',')
+ if len(tokens) != 2:
+ raise ParseError('Wrong number of yx-tuple arguments.')
+ y = get_axis_position_from_argument('Y', tokens[0])
+ x = get_axis_position_from_argument('X', tokens[1])
+ return (y, x)
+
+ def argsparse(self, signature, args_tokens):
+ """Parse into / return args_tokens as args/kwargs defined by signature.
+
+ Expects signature to be a ' '-delimited sequence of any of the strings
+ 'int:nonneg', 'yx_tuple:nonneg', 'string', defining the respective
+ argument types.
+ """
+ tmpl_tokens = signature.split()
+ if len(tmpl_tokens) != len(args_tokens):
+ raise ParseError('Number of arguments (' + str(len(args_tokens)) +
+ ') not expected number (' + str(len(tmpl_tokens))
+ + ').')
+ args = []
+ for i in range(len(tmpl_tokens)):
+ tmpl = tmpl_tokens[i]
+ arg = args_tokens[i]
+ if tmpl == 'int:nonneg':
+ if not arg.isdigit():
+ raise ParseError('Argument must be non-negative integer.')
+ args += [int(arg)]
+ elif tmpl == 'yx_tuple:nonneg':
+ args += [self.parse_yx_tuple(arg)]
+ elif tmpl == 'string':
+ args += [arg]
+ else:
+ raise ParseError('Unknown argument type.')
+ return args, {}
+
+
+class Game:
+ turn = 0
+ log_text = ''
+ map_size = (5, 5)
+ terrain_map = ('?'*5+'\n')*4+'?'*5
+ things = []
+
+ class Thing:
+ def __init__(self, position, symbol):
+ self.position = position
+ self.symbol = symbol
+
+ def log(self, msg):
+ """Prefix msg plus newline to self.log_text."""
+ self.log_text = msg + '\n' + self.log_text
+
+ def cmd_THING(self, type_, yx):
+ """Add to self.things at .position yx with .symbol defined by type_."""
+ symbol = '?'
+ if type_ == 'TYPE:human':
+ symbol = '@'
+ elif type_ == 'TYPE:monster':
+ symbol = 'm'
+ self.things += [self.Thing(yx, symbol)]
+ cmd_THING.argtypes = 'string yx_tuple:nonneg'
+
+ def cmd_MAP_SIZE(self, yx):
+ """Set self.map_size to yx, redraw self.terrain_map as '?' cells."""
+ y, x = yx
+ self.map_size = (y, x)
+ self.terrain_map = ''
+ for y in range(self.map_size[0]):
+ self.terrain_map += '?' * self.map_size[1] + '\n'
+ self.terrain_map = self.terrain_map[:-1]
+ cmd_MAP_SIZE.argtypes = 'yx_tuple:nonneg'
+
+ def cmd_TURN_FINISHED(self, n):
+ """Do nothing. (This may be extended later.)"""
+ pass
+ cmd_TURN_FINISHED.argtypes = 'int:nonneg'
+
+ def cmd_NEW_TURN(self, n):
+ """Set self.turn to n, empty self.things."""
+ self.turn = n
+ self.things = []
+ cmd_NEW_TURN.argtypes = 'int:nonneg'
+
+ def cmd_TERRAIN(self, terrain_map):
+ """Reset self.terrain_map from terrain_map."""
+ lines = terrain_map.split('\n')
+ if len(lines) != self.map_size[0]:
+ raise ArgumentError('wrong map height')
+ for line in lines:
+ if len(line) != self.map_size[1]:
+ raise ArgumentError('wrong map width')
+ self.terrain_map = terrain_map
+ cmd_TERRAIN.argtypes = 'string'
+
+
+class WidgetManager:
+
+ def __init__(self, socket, game):
+ """Set up all urwid widgets we want on the screen."""
+ self.game = game
+ edit_widget = self.EditToSocketWidget(socket, 'SEND: ')
+ self.map_widget = urwid.Text('', wrap='clip')
+ self.turn_widget = urwid.Text('')
+ self.log_widget = urwid.Text('')