From 7957b69ab78adfa35f79253be792713e968452d7 Mon Sep 17 00:00:00 2001 From: Peter Ward Date: Fri, 19 Sep 2014 12:49:34 +1000 Subject: make the client work (terribly) --- robots/client.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- robots/empty.txt | 5 ++++ robots/game.py | 17 +++++++---- robots/server.py | 26 ++++++++++++++++- robots/state.py | 20 +++++++++++++ robots/utils.py | 8 ++++++ 6 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 robots/empty.txt diff --git a/robots/client.py b/robots/client.py index c25d641..8832506 100644 --- a/robots/client.py +++ b/robots/client.py @@ -1,4 +1,7 @@ +import argparse import os +import random +from string import ascii_lowercase as lowercase import zmq import logbook @@ -6,6 +9,10 @@ import urwid from network.client import Discoverer +from robots.cursesviewer import CursesViewer +from robots.game import Game +from robots.utils import read_map + log = logbook.Logger(__name__) def button_width(btn): @@ -170,11 +177,86 @@ def choose_bots(): browser = Browser(avail_walker, added_walker) mainloop.run() - return [key for key, _, _ in browser.added] + return browser.added + +class RemoteBot(object): + def __init__(self, target, name, ctx=None): + if ctx is None: + ctx = zmq.Context.instance() + + self.sock = ctx.socket(zmq.REQ) + self.sock.connect(target) + + self.debug_name = target + '/' + name + + self.sock.send_json({ + 'action': 'create', + 'name': name, + 'options': {}, + }) + resp = self.sock.recv_json() + if resp['status'] != 200: + raise RuntimeError( + 'Failed to create remote bot %s/%s: %s' % ( + target, name, resp.get('message'), + ) + ) + self.instance_id = resp['instance_id'] + + def __call__(self, whoami, state): + self.sock.send_json({ + 'action': 'process', + 'instance_id': self.instance_id, + 'whoami': whoami, + 'state': state.to_json(), + }) + result = self.sock.recv_json() + if result['status'] != 200: + raise RuntimeError( + 'Failed to run %s: %s' % ( + self.debug_name, result.get('message'), + ) + ) + return result['result'] + + def close(self): + self.sock.send_json({ + 'action': 'destroy', + 'instance_id': self.instance_id, + }) + self.sock.recv_json() + + def __del__(self): + self.close() def main(): + parser = argparse.ArgumentParser() + parser.add_argument('map', type=argparse.FileType('r')) + args = parser.parse_args() + + with args.map: + map_ = read_map(args.map) + bots = choose_bots() - print(bots) + + game = Game( + map_, + victory_by_combat=len(bots) != 1, + ) + for key, server_info, name in bots: + suffix = ''.join(random.sample(lowercase, 3)) + fullname = '%s.%s.%s' % ( + server_info['name'], + name, + suffix, + ) + game.add_bot( + RemoteBot(*key), + fullname, + ) + + viewer = CursesViewer(game) + viewer.run() if __name__ == '__main__': main() diff --git a/robots/empty.txt b/robots/empty.txt new file mode 100644 index 0000000..1e0efe7 --- /dev/null +++ b/robots/empty.txt @@ -0,0 +1,5 @@ +...........+........ +......+............. +...1............2... +.............+...... +........+........... diff --git a/robots/game.py b/robots/game.py index 3a62bf5..893f147 100644 --- a/robots/game.py +++ b/robots/game.py @@ -34,9 +34,10 @@ def extract_spawn_points(map_): return [point for n, point in points] class Game: - def __init__(self, map_): + def __init__(self, map_, victory_by_combat=True): self.bots = {} self.time = 0 + self.victory_by_combat = victory_by_combat map_ = deepcopy(map_) self.spawn_points = deque(extract_spawn_points(map_)) @@ -56,7 +57,10 @@ class Game: self.bots[name] = bot # Place the initial robot for this player. - x, y = self.spawn_points.popleft() + try: + x, y = self.spawn_points.popleft() + except IndexError: + raise IndexError('Not enough spawn points in the map.') self.state.robots_by_player[name] = [ (x, y, Energy.INITIAL), ] @@ -78,10 +82,11 @@ class Game: if robots: roboteers.append(name) - won = bool(winners) - if len(roboteers) <= 1: - won = True - winners.extend(roboteers) + if self.victory_by_combat: + won = bool(winners) + if len(roboteers) <= 1: + won = True + winners.extend(roboteers) return winners or won diff --git a/robots/server.py b/robots/server.py index 421afd8..1b87392 100644 --- a/robots/server.py +++ b/robots/server.py @@ -8,6 +8,8 @@ import logbook import network +from robots.state import GameState + log = logbook.Logger(__name__) def n_required_args(argspec): @@ -113,6 +115,27 @@ class Server(object): log.debug('Created new bot instance %s' % instance_id) return success(instance_id=instance_id) + def next_move(self, instance_id, whoami, state): + try: + instance = self.instances[instance_id] + except KeyError: + return not_found('No instance %s on server.' % instance_id) + + state = GameState.from_json(state) + + try: + result = instance(whoami, state) + except Exception: + message = 'Exception running instance %s.' % instance_id + log.exception(message) + return server_error(message) + + return success(result=result) + + def destroy(self, instance_id): + self.instances.pop(instance_id, None) + return success() + def handle(self, request): try: action = request['action'] @@ -127,8 +150,9 @@ class Server(object): elif action == 'process': instance_id = request['instance_id'] + whoami = request['whoami'] state = request['state'] - return self.next_move(instance_id, state) + return self.next_move(instance_id, whoami, state) elif action == 'destroy': instance_id = request['instance_id'] diff --git a/robots/state.py b/robots/state.py index 453cb7b..10a899e 100644 --- a/robots/state.py +++ b/robots/state.py @@ -25,6 +25,26 @@ class GameState: if isinstance(other, GameState): return self._authorative_state == other._authorative_state + def to_json(self): + return ( + self.cities, + { + '%d,%d' % position: player + for position, player in self.allegiances.items() + }, + self.robots_by_player + ) + + @classmethod + def from_json(cls, obj): + cities, allegiances, robots_by_player = obj + state = cls(cities) + for position, player in allegiances.items(): + position = tuple(map(int, position.split(','))) + state.allegiances[position] = player + state.robots_by_player = robots_by_player + return state + @property def _authorative_state(self): return (self.cities, self.allegiances, self.robots_by_player) diff --git a/robots/utils.py b/robots/utils.py index 5793a00..9abb08c 100644 --- a/robots/utils.py +++ b/robots/utils.py @@ -23,6 +23,14 @@ def immutable(value): pass return value +def read_map(f): + lines = [line.rstrip('\n') for line in f] + width = max(len(line) for line in lines) + return [ + list(line.ljust(width, City.NORMAL)) + for line in lines + ] + def add_spawns(map_, n_spawns, city=None): available = [] for x, y, cell in iter_board(map_): -- cgit v1.2.3