From 384c43a55bff6bd936bcbdba9bfca37ebdf72e43 Mon Sep 17 00:00:00 2001 From: Peter Ward Date: Wed, 17 Sep 2014 10:07:57 +1000 Subject: implement a terrible bot chooser --- robots/client.py | 172 ++++++++++++++++++++++++++++++++++++++++++++++--------- robots/server.py | 32 ++++++++--- simple.py | 6 +- 3 files changed, 173 insertions(+), 37 deletions(-) diff --git a/robots/client.py b/robots/client.py index 7d60ba2..c25d641 100644 --- a/robots/client.py +++ b/robots/client.py @@ -1,29 +1,79 @@ import os -from threading import Thread -import time -import uuid import zmq import logbook +import urwid -import network +from network.client import Discoverer -SERVER_KIND = 'robots' +log = logbook.Logger(__name__) + +def button_width(btn): + return len(btn.label) + 4 + +def focusable(widget): + return urwid.AttrMap(widget, None, focus_map='reversed') + +class Browser(Discoverer): + SERVER_KIND = 'robots' + + def __init__(self, avail_walker, added_walker, ctx=None): + super(Browser, self).__init__(self.SERVER_KIND) -class Browser(object): - def __init__(self, client, ctx=None): if ctx is None: ctx = zmq.Context.instance() - self.client = client - self.running = False - self.targets = {} self.ctx = ctx - def update(self, services): + self.targets = {} + self.added = [] + + self.avail_walker = avail_walker + self.added_walker = added_walker + + def make_server_widget(self, target, info): + return urwid.AttrMap( + urwid.Text(info['name']), + 'bright', + ) + + def make_bot_widget(self, key, server_info, name): + button = urwid.Button('Add') + + def on_button_pressed(_): + self.added.append((key, server_info, name)) + self.update_added() + + urwid.connect_signal(button, 'click', on_button_pressed) + + return focusable(urwid.Columns([ + urwid.Text(' ' * 4 + name), + (button_width(button), button), + ])) + + def make_added_widget(self, key, server_info, name): + button = urwid.Button('Remove') + + widget = focusable(urwid.Columns([ + urwid.Text(server_info['name'] + ' / ' + name), + (button_width(button), button), + ])) + + def on_button_pressed(_): + self.added.remove((key, server_info, name)) + self.update_added() + + urwid.connect_signal(button, 'click', on_button_pressed) + + return widget + + def on_services_changed(self): current_targets = set() - for service in services: + for service in self.services.values(): + if service is None: + continue + target = service['target'] current_targets.add(target) @@ -45,18 +95,86 @@ class Browser(object): if target not in current_targets: del self.targets[target] - network.browser.clear_terminal() - for info in self.targets.values(): - print(info['name'] + ':') - for name in info['bots']: - print(' * ' + name) - print() - - def run(self, client_run): - client_run() - -client = network.Client(SERVER_KIND) -client.find_server( - browser_cls=Browser, - connect=False -) + self.update_avail() + + def update_avail(self): + things = [] + for target, info in sorted( + self.targets.items(), + key=lambda item: item[1]['name'], + ): + things.append(self.make_server_widget(target, info)) + for name in sorted(info['bots']): + key = (target, name) + things.append(self.make_bot_widget(key, info, name)) + things.append(urwid.Text('')) + + self.avail_walker[:] = things + + def update_added(self): + things = [] + for key, server_info, name in self.added: + things.append(self.make_added_widget(key, server_info, name)) + self.added_walker[:] = things + +def make_ui(avail_walker, added_walker): + start_game = urwid.Button('Start Game') + + def on_start_game(_): + raise urwid.ExitMainLoop() + urwid.connect_signal(start_game, 'click', on_start_game) + + main = urwid.Columns([ + urwid.Frame( + urwid.LineBox(urwid.ListBox(avail_walker)), + header=urwid.Text('Available bots:'), + ), + urwid.Frame( + urwid.Pile([ + urwid.LineBox(urwid.ListBox(added_walker)), + ('pack', urwid.Padding( + focusable(start_game), + align='right', + width=button_width(start_game), + )), + ]), + header=urwid.Text('Bots in game:'), + ), + ]) + + stdout = urwid.Text('') + + def update_stdout(data): + content = stdout.text + data.decode('utf-8') + lines = content.split('\n') + stdout.set_text('\n'.join(lines[-5:])) + + frame = urwid.Frame(main, footer=urwid.LineBox(stdout)) + return frame, update_stdout + +def choose_bots(): + avail_walker = urwid.SimpleFocusListWalker([]) + added_walker = urwid.SimpleFocusListWalker([]) + + frame, update_stdout = make_ui(avail_walker, added_walker) + mainloop = urwid.MainLoop( + frame, + palette=[('reversed', 'standout', '')], + event_loop=urwid.GLibEventLoop(), + ) + + pipe = mainloop.watch_pipe(update_stdout) + handler = logbook.StreamHandler(os.fdopen(pipe, 'w')) + + with handler.applicationbound(): + browser = Browser(avail_walker, added_walker) + mainloop.run() + + return [key for key, _, _ in browser.added] + +def main(): + bots = choose_bots() + print(bots) + +if __name__ == '__main__': + main() diff --git a/robots/server.py b/robots/server.py index 759766c..421afd8 100644 --- a/robots/server.py +++ b/robots/server.py @@ -1,4 +1,5 @@ from functools import wraps +import inspect import json import os import uuid @@ -9,6 +10,9 @@ import network log = logbook.Logger(__name__) +def n_required_args(argspec): + return len(argspec.args) - len(argspec.defaults or ()) + def bad_request(msg): return {'status': 400, 'message': msg} @@ -42,14 +46,6 @@ def json_handler(handle): return handler -def get_options(fn): - arguments = fn.__code__.co_varnames - defaults = fn.__defaults__ - - required = arguments[:-len(defaults)] - optional = list(zip(defaults, arguments[-len(defaults):])) - return (required, optional) - class Server(object): SERVER_NAME = os.getenv('NAME') SERVER_KIND = 'robots' @@ -63,8 +59,26 @@ class Server(object): self.instances = {} def add_bot(self, fn, name=None): + """ + >>> s = Server() + >>> s.add_bot(lambda: 5, name='Alice') + >>> list(s.bots) + ['Alice'] + >>> s.add_bot(lambda zebra, orange: 5, name='Bob') + Traceback (most recent call last): + ... + ValueError: Bot function should have no required arguments (found: zebra, orange) + """ if name is None: name = fn.__name__ + + argspec = inspect.getargspec(fn) + if n_required_args(argspec) != 0: + raise ValueError( + 'Bot function should have no required arguments (found: %s)' % + ', '.join(argspec.args) + ) + self.bots[name] = fn def add_simple_bot(self, fn, name=None): @@ -74,7 +88,7 @@ class Server(object): def list_bots(self): bots = { - name: None and get_options(fn) + name: {} for name, fn in self.bots.items() } return success(bots=bots) diff --git a/simple.py b/simple.py index f8a7edd..1ff37f2 100644 --- a/simple.py +++ b/simple.py @@ -1,4 +1,5 @@ import random +import sys import robots @@ -10,6 +11,9 @@ def random_walk(whoami, state): ) if __name__ == '__main__': + name, = sys.argv[1:] server = robots.Server() - server.add_simple_bot(random_walk, 'Alice') + server.SERVER_NAME = name + server.add_simple_bot(random_walk, 'Bot1') + server.add_simple_bot(random_walk, 'Bot2') server.run() -- cgit v1.2.3