X-Git-Url: https://plomlompom.com/repos/berlin_corona.txt?a=blobdiff_plain;f=server.py;h=35bd9bc76ea890c7c72ff9b4a5735c2c5156df2a;hb=676c2ec05e61159b05ee327a6412a17f0c354cc7;hp=0ef44d8e21f62edb8555227694175caf5ddb65f5;hpb=956b2469d5537f1d0863e5a2811d37fa5220b0f9;p=plomrogue2-experiments diff --git a/server.py b/server.py index 0ef44d8..35bd9bc 100755 --- a/server.py +++ b/server.py @@ -10,6 +10,10 @@ from parser import ArgError, Parser socketserver.TCPServer.allow_reuse_address = True +class GameError(Exception): + pass + + class Server(socketserver.ThreadingTCPServer): """Bind together threaded IO handling server and message queue.""" @@ -92,23 +96,27 @@ class Task: class Thing: - def __init__(self, type_, position): - self.type = type_ + def __init__(self, world, type_, position): + self.world = world + self.type_ = type_ self.position = position self.task = Task('wait') - def task_wait(self): - pass - - def task_move(self, direction): + def _move_pos(self, direction, pos_yx): if direction == 'UP': - self.position[0] -= 1 + pos_yx[0] -= 1 elif direction == 'DOWN': - self.position[0] += 1 + pos_yx[0] += 1 elif direction == 'RIGHT': - self.position[1] += 1 + pos_yx[1] += 1 elif direction == 'LEFT': - self.position[1] -= 1 + pos_yx[1] -= 1 + + def task_wait(self): + pass + + def task_move(self, direction): + self._move_pos(direction, self.position) def decide_task(self): if self.position[1] > 1: @@ -118,7 +126,25 @@ class Thing: else: self.set_task('wait') + def check_task(self, task, *args, **kwargs): + if task == 'move': + if len(args) > 0: + direction = args[0] + else: + direction = kwargs['direction'] + test_pos = self.position[:] + self._move_pos(direction, test_pos) + if test_pos[0] < 0 or test_pos[1] < 0 or \ + test_pos[0] >= self.world.map_size[0] or \ + test_pos[1] >= self.world.map_size[1]: + raise GameError('would move outside map bounds') + pos_i = test_pos[0] * self.world.map_size[1] + test_pos[1] + map_tile = self.world.map_[pos_i] + if map_tile != '.': + raise GameError('would move into illegal terrain') + def set_task(self, task, *args, **kwargs): + self.check_task(task, *args, **kwargs) self.task = Task(task, args, kwargs) def proceed(self, is_AI=True): @@ -142,12 +168,15 @@ class World: def __init__(self): self.turn = 0 self.map_size = (5, 5) - self.map_ = 'xxxxx\n' +\ - 'x...x\n' +\ - 'x.X.x\n' +\ - 'x...x\n' +\ + self.map_ = 'xxxxx' +\ + 'x...x' +\ + 'x.X.x' +\ + 'x...x' +\ 'xxxxx' - self.things = [Thing('human', [3, 3]), Thing('monster', [1, 1])] + self.things = [ + Thing(self, 'human', [3, 3]), + Thing(self, 'monster', [1, 1]) + ] self.player_i = 0 self.player = self.things[self.player_i] @@ -172,6 +201,19 @@ class CommandHandler: self.pool = Pool() self.pool_result = None + def handle_input(self, input_, connection_id): + """Process input_ to command grammar, call command handler if found.""" + try: + command = self.parser.parse(input_) + if command is None: + self.send_to(connection_id, 'UNHANDLED INPUT') + else: + command(connection_id=connection_id) + except ArgError as e: + self.send_to(connection_id, 'ARGUMENT ERROR: ' + str(e)) + except GameError as e: + self.send_to(connection_id, 'GAME ERROR: ' + str(e)) + def send_to(self, connection_id, msg): """Send msg to client of connection_id.""" self.queues_out[connection_id].put(msg) @@ -196,6 +238,27 @@ class CommandHandler: 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. @@ -218,12 +281,35 @@ class CommandHandler: self.world.player.proceed(is_AI=False) if self.world.player.task is None: break - 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(self.world.map_)) - for thing in self.world.things: - self.send_all('THING TYPE:' + thing.type + ' ' - + self.stringify_yx(thing.position)) + 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): """Reply with n-th Fibonacci numbers, n taken from tokens[1:]. @@ -257,53 +343,9 @@ class CommandHandler: 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.quoted(self.world.map_)) - for thing in self.world.things: - self.send_all('THING TYPE:' + thing.type + ' ' - + self.stringify_yx(thing.position)) + self.send_all_gamestate() 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, 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_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 handle_input(self, input_, connection_id): - """Process input_ to command grammar, call command handler if found.""" - try: - command = self.parser.parse(input_) - if command is None: - self.send_to(connection_id, 'UNHANDLED INPUT') - else: - command(connection_id=connection_id) - except ArgError as e: - self.send_to(connection_id, 'ARGUMENT ERROR: ' + str(e)) - def io_loop(q): """Handle commands coming through queue q, send results back.