X-Git-Url: https://plomlompom.com/repos/condition_descriptions?a=blobdiff_plain;f=client.py;h=df5d7e360147a074e2f5754ed9e0ece91e514502;hb=523a3f079075159a16fa172ec99084c17e120044;hp=f356442390d22e2bb53d98521481b7da48d28a4b;hpb=7e119240b273588b4dce81ad211aa1548a0ca7f6;p=plomrogue2-experiments diff --git a/client.py b/client.py index f356442..df5d7e3 100755 --- a/client.py +++ b/client.py @@ -3,132 +3,14 @@ import urwid import plom_socket_io import socket import threading -from functools import partial -import unittest - - -class ArgumentError(Exception): - pass - - -class ParseError(Exception): - pass - - -class Parser: - - def __init__(self, game): - self.game = game - - def tokenize(self, msg): - """Parse msg string into tokens. - - Separates by ' ' and '\n', but allows whitespace in tokens quoted by - '"', and allows escaping within quoted tokens by a prefixed backslash. - """ - tokens = [] - token = '' - quoted = False - escaped = False - for c in msg: - if quoted: - if escaped: - token += c - escaped = False - elif c == '\\': - escaped = True - elif c == '"': - quoted = False - else: - token += c - elif c == '"': - quoted = True - elif c in {' ', '\n'}: - if len(token) > 0: - tokens += [token] - token = '' - else: - token += c - if len(token) > 0: - tokens += [token] - return tokens - - def parse(self, msg): - """Parse msg as call to self.game method, return method with arguments. - - Respects method signatures defined in methods' .argtypes attributes. - """ - 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, {} +from parser import ArgError, Parser class Game: turn = 0 log_text = '' map_size = (5, 5) - terrain_map = ('?'*5+'\n')*4+'?'*5 + terrain_map = ('?'*5)*5 things = [] class Thing: @@ -156,7 +38,7 @@ class Game: 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.map_size[1]# + '\n' self.terrain_map = self.terrain_map[:-1] cmd_MAP_SIZE.argtypes = 'yx_tuple:nonneg' @@ -171,16 +53,16 @@ class Game: 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' + def cmd_TERRAIN_LINE(self, y, terrain_line): + width_map = self.map_size[1] + if y >= self.map_size[0]: + raise ArgError('too large row number %s' % y) + width_line = len(terrain_line) + if width_line > width_map: + raise ArgError('too large map line width %s' % width_line) + self.terrain_map = self.terrain_map[:y * width_map] + \ + terrain_line + self.terrain_map[(y + 1) * width_map:] + cmd_TERRAIN_LINE.argtypes = 'int:nonneg string' class WidgetManager: @@ -199,13 +81,18 @@ class WidgetManager: def draw_map(self): """Draw map view from .game.terrain_map, .game.things.""" - whole_map = [] - for c in self.game.terrain_map: - whole_map += [c] + map_lines = [] + map_size = len(self.game.terrain_map) + start_cut = 0 + while start_cut < map_size: + limit = start_cut + self.game.map_size[1] + map_lines += [self.game.terrain_map[start_cut:limit]] + start_cut = limit for t in self.game.things: - pos_i = t.position[0] * (self.game.map_size[1] + 1) + t.position[1] - whole_map[pos_i] = t.symbol - return ''.join(whole_map) + line_as_list = list(map_lines[t.position[0]]) + line_as_list[t.position[1]] = t.symbol + map_lines[t.position[0]] = ''.join(line_as_list) + return "\n".join(map_lines) def update(self): """Redraw all non-edit widgets.""" @@ -285,7 +172,7 @@ class PlomRogueClient: self.game.log('UNHANDLED INPUT: ' + msg) else: command() - except (ArgumentError, ParseError) as e: + except ArgError as e: self.game.log('ARGUMENT ERROR: ' + msg + '\n' + str(e)) self.widget_manager.update() del self.server_output[0] @@ -312,52 +199,6 @@ class PlomRogueClient: self.recv_loop_thread.join() -class TestParser(unittest.TestCase): - - def test_tokenizer(self): - p = Parser(Game()) - self.assertEqual(p.tokenize(''), []) - self.assertEqual(p.tokenize(' '), []) - self.assertEqual(p.tokenize('abc'), ['abc']) - self.assertEqual(p.tokenize('a b\nc "d"'), ['a', 'b', 'c', 'd']) - self.assertEqual(p.tokenize('a "b\nc d"'), ['a', 'b\nc d']) - self.assertEqual(p.tokenize('a"b"c'), ['abc']) - self.assertEqual(p.tokenize('a\\b'), ['a\\b']) - self.assertEqual(p.tokenize('"a\\b"'), ['ab']) - self.assertEqual(p.tokenize('a"b'), ['ab']) - self.assertEqual(p.tokenize('a"\\"b'), ['a"b']) - - def test_unhandled(self): - p = Parser(Game()) - self.assertEqual(p.parse(''), None) - self.assertEqual(p.parse(' '), None) - self.assertEqual(p.parse('x'), None) - - def test_argsparse(self): - p = Parser(Game()) - assertErr = partial(self.assertRaises, ParseError, p.argsparse) - assertErr('', ['foo']) - assertErr('string', []) - assertErr('string string', ['foo']) - self.assertEqual(p.argsparse('string', ('foo',)), - (['foo'], {})) - self.assertEqual(p.argsparse('string string', ('foo', 'bar')), - (['foo', 'bar'], {})) - assertErr('int:nonneg', ['']) - assertErr('int:nonneg', ['x']) - assertErr('int:nonneg', ['-1']) - assertErr('int:nonneg', ['0.1']) - self.assertEqual(p.argsparse('int:nonneg', ('0',)), - ([0], {})) - assertErr('yx_tuple:nonneg', ['x']) - assertErr('yx_tuple:nonneg', ['Y:0,X:1']) - assertErr('yx_tuple:nonneg', ['Y:1,X:0']) - assertErr('yx_tuple:nonneg', ['Y:1.1,X:1']) - assertErr('yx_tuple:nonneg', ['Y:1,X:1.1']) - self.assertEqual(p.argsparse('yx_tuple:nonneg', ('Y:1,X:2',)), - ([(1, 2)], {})) - - if __name__ == '__main__': game = Game() s = socket.create_connection(('127.0.0.1', 5000))