X-Git-Url: https://plomlompom.com/repos/foo.html?a=blobdiff_plain;f=client.py;h=80cac96aab380849a29c4e54edb6cb5c5bc86579;hb=3576b6cfa39467478565b704de0160fc48c9a181;hp=f356442390d22e2bb53d98521481b7da48d28a4b;hpb=7e119240b273588b4dce81ad211aa1548a0ca7f6;p=plomrogue2-experiments diff --git a/client.py b/client.py index f356442..80cac96 100755 --- a/client.py +++ b/client.py @@ -3,162 +3,25 @@ import urwid import plom_socket_io import socket import threading -from functools import partial -import unittest +from parser import ArgError, Parser +from game_common import World, Commander -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, {} - - -class Game: - turn = 0 +class Game(Commander): + world = World() 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_.""" + def symbol_for_type(self, type_): symbol = '?' - if type_ == 'TYPE:human': + if type_ == 'human': symbol = '@' - elif type_ == 'TYPE:monster': + elif 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' + return symbol def cmd_TURN_FINISHED(self, n): """Do nothing. (This may be extended later.)""" @@ -167,20 +30,13 @@ class Game: def cmd_NEW_TURN(self, n): """Set self.turn to n, empty self.things.""" - self.turn = n - self.things = [] + self.world.turn = n + self.world.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_VISIBLE_MAP_LINE(self, y, terrain_line): + self.world.map_.set_line(y, terrain_line) + cmd_VISIBLE_MAP_LINE.argtypes = 'int:nonneg string' class WidgetManager: @@ -198,18 +54,23 @@ class WidgetManager: self.top = urwid.Filler(widget_pile, valign='top') 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] - 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) + """Draw map view from .game.map_.terrain, .game.things.""" + map_lines = [] + map_size = len(self.game.world.map_.terrain) + start_cut = 0 + while start_cut < map_size: + limit = start_cut + self.game.world.map_.size[1] + map_lines += [self.game.world.map_.terrain[start_cut:limit]] + start_cut = limit + for t in self.game.world.things: + line_as_list = list(map_lines[t.position[0]]) + line_as_list[t.position[1]] = self.game.symbol_for_type(t.type_) + map_lines[t.position[0]] = ''.join(line_as_list) + return "\n".join(map_lines) def update(self): """Redraw all non-edit widgets.""" - self.turn_widget.set_text('TURN: ' + str(self.game.turn)) + self.turn_widget.set_text('TURN: ' + str(self.game.world.turn)) self.log_widget.set_text(self.game.log_text) self.map_widget.set_text(self.draw_map()) @@ -285,7 +146,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 +173,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))