summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--robots/client.py86
-rw-r--r--robots/empty.txt5
-rw-r--r--robots/game.py17
-rw-r--r--robots/server.py26
-rw-r--r--robots/state.py20
-rw-r--r--robots/utils.py8
6 files changed, 153 insertions, 9 deletions
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_):