summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--bot_server.py33
-rw-r--r--capturer.py52
-rw-r--r--example-bots/adaptive.py63
-rw-r--r--example-bots/manhattan.py (renamed from manhattan.py)23
-rw-r--r--example-bots/pathing.py40
-rw-r--r--example-bots/random.py11
-rw-r--r--example-bots/random_valid.py30
-rw-r--r--example-bots/up_bot.py11
-rw-r--r--manifest.scm11
-rw-r--r--maps/arena.txt (renamed from arena.txt)0
-rw-r--r--maps/empty.txt (renamed from empty.txt)0
-rw-r--r--maps/large.txt (renamed from large.txt)0
-rw-r--r--maps/maze.txt (renamed from maze.txt)0
-rw-r--r--maps/small.txt11
-rw-r--r--maps/two.txt (renamed from two.txt)0
-rw-r--r--napoleon.py146
-rw-r--r--network/server.py1
-rw-r--r--peter.py15
-rw-r--r--real-setup.py20
-rw-r--r--requirements.txt8
-rw-r--r--robots/algorithms.pyx162
-rw-r--r--robots/client.py37
-rw-r--r--robots/cursesviewer.py20
-rw-r--r--robots/game.py4
-rw-r--r--robots/server.py2
-rw-r--r--robots/state.py41
-rw-r--r--run_competition.py2
-rw-r--r--setup.cfg2
-rw-r--r--setup.py40
-rw-r--r--simple.py19
-rw-r--r--up_bot.py5
32 files changed, 285 insertions, 525 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ed8ebf5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__ \ No newline at end of file
diff --git a/bot_server.py b/bot_server.py
new file mode 100644
index 0000000..6d8c72c
--- /dev/null
+++ b/bot_server.py
@@ -0,0 +1,33 @@
+import importlib
+import os
+import robots
+import sys
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print(f'Usage: python3 {sys.argv[0]} {{your-name}}')
+ print(f' your-name: your name, used to identify your server on the network')
+ print()
+ print(f'Run this command to host a server advertising all of the bots in the bots/ directory.')
+ sys.exit(1);
+
+ name, = sys.argv[1:]
+ server = robots.Server()
+ server.SERVER_NAME = name
+
+ num_bots = 0
+ for filename in os.listdir('bots'):
+ if filename.endswith('.py'):
+ bot_name = filename.removesuffix('.py')
+ module = importlib.import_module(f'bots.{bot_name}')
+ try:
+ server.add_simple_bot(module.calculate_orders, bot_name)
+ num_bots += 1
+ except AttributeError:
+ print(f'Warning: bots/{filename} was missing `calculate_orders` function.', file=sys.stderr)
+
+ if num_bots == 0:
+ print(f'No bots found: not starting a bot server.', file=sys.stderr)
+ sys.exit(2)
+ else:
+ server.run()
diff --git a/capturer.py b/capturer.py
deleted file mode 100644
index 54b0be6..0000000
--- a/capturer.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import random
-
-import robots
-
-from robots.algorithms import distance_relaxer
-from robots.constants import City
-from robots.utils import add_spawns
-
-class CaptureSpawns(object):
- def __init__(self, variance=0.1):
- self.iterations = 10
- self.variance = variance
-
- def __call__(self, whoami, state):
- my_robots = state.robots_by_player[whoami]
-
- # Create a distance matrix.
- distances = []
- for y, row in enumerate(state.cities):
- output = []
- for x, city in enumerate(row):
- d = float('inf')
- if city == City.FACTORY:
- if state.allegiances.get((x, y)) != whoami:
- d = 0
- elif city == City.GHOST:
- d = None
- output.append(d)
- distances.append(output)
-
- # Find the shortest path to a target from each cell.
- predecessors = distance_relaxer(distances)
-
- # Direct the robots to follow those paths.
- results = []
- for x, y, energy in my_robots:
- if random.random() < self.variance:
- result = random.choice('ULDR')
- elif predecessors[y][x]:
- result = random.choice(predecessors[y][x])
- elif state.allegiances.get((x, y)) != whoami:
- result = 'P'
- else:
- result = random.choice('UDLR')
- results.append(result)
- return results
-
-if __name__ == '__main__':
- server = robots.Server()
- server.SERVER_NAME = 'Capturer Server'
- server.add_bot(CaptureSpawns, 'Capture')
- server.run()
diff --git a/example-bots/adaptive.py b/example-bots/adaptive.py
new file mode 100644
index 0000000..18f8717
--- /dev/null
+++ b/example-bots/adaptive.py
@@ -0,0 +1,63 @@
+import random
+
+def move_toward_closest_factory(whoami, width, height, board, rx, ry, capture_types):
+ if board[rx, ry].allegiance != whoami and board[rx, ry].city in capture_types:
+ return 'P'
+ to_do = [
+ (rx + 1, ry, 'R'),
+ (rx - 1, ry, 'L'),
+ (rx, ry - 1, 'U'),
+ (rx, ry + 1, 'D')
+ ]
+ random.shuffle(to_do)
+ seen = []
+ while to_do:
+ (x, y, move) = to_do.pop(0)
+ x %= width
+ y %= height
+ if (x, y) in seen or board[x, y].city not in ['+', '.']:
+ continue
+ seen.append((x, y))
+ if board[x, y].allegiance != whoami and board[x, y].city in capture_types:
+ return move
+ else:
+ to_add = [(x + 1, y, move),
+ (x - 1, y, move),
+ (x, y + 1, move),
+ (x, y - 1, move)]
+ random.shuffle(to_add)
+ to_do += to_add
+
+def calculate_orders(whoami, state):
+ # In order to start painting, we need to be leading by at least
+ # this percentage (per opposing player).
+ leading_factor_per_opponent = 0.1 # 10%
+ opponents = (len(state.robots_by_player) - 1)
+ factor = 1 - (leading_factor_per_opponent * opponents)
+
+ orders = []
+
+ board = state.board
+ width = state.width
+ height = state.height
+ my_robots = state.robots_by_player[whoami]
+ max_robots = max(
+ len(robots)
+ for player, robots in state.robots_by_player.items()
+ if player != whoami
+ )
+ my_factories = state.factories_by_player[whoami]
+ max_factories = max(
+ len(factories)
+ for player, factories in state.factories_by_player.items()
+ if player != whoami
+ )
+ if factor * len(my_robots) < max_robots or factor * len(my_factories) < max_factories:
+ capture_types = ['+']
+ else:
+ capture_types = ['+', '.']
+ for rx, ry, energy in my_robots:
+ move = move_toward_closest_factory(whoami, width, height, board, rx, ry, capture_types)
+ orders.append(move or random.choice('UDLRP'))
+
+ return orders
diff --git a/manhattan.py b/example-bots/manhattan.py
index f64b1e2..a65d0e9 100644
--- a/manhattan.py
+++ b/example-bots/manhattan.py
@@ -1,25 +1,9 @@
import random
def distance(x1, y1, x2, y2):
- '''
- >>> distance(0, 0, 3, 0)
- 3
- >>> distance(1, 1, 3, 3)
- 4
- '''
return abs(x1 - x2) + abs(y1 - y2)
def direction_to(rx, ry, fx, fy):
- '''
- >>> direction_to(0, 0, 3, 0)
- 'R'
- >>> direction_to(0, 0, 0, 3)
- 'D'
- >>> direction_to(6, 0, 3, 0)
- 'L'
- >>> direction_to(0, 6, 0, 3)
- 'U'
- '''
moves = []
if fx < rx:
moves.append('L')
@@ -36,7 +20,7 @@ def closest_factory(whoami, state, rx, ry):
closest_distance = 9999999
for fx, fy in state.factories:
- if state.allegiances.get((fx, fy)) != whoami:
+ if state[fx, fy].allegiance != whoami:
d = distance(rx, ry, fx, fy)
if d < closest_distance:
closest_factory = (fx, fy)
@@ -44,10 +28,11 @@ def closest_factory(whoami, state, rx, ry):
return closest_distance, closest_factory
-def manhattan(whoami, state):
+def calculate_orders(whoami, state):
orders = []
- for rx, ry, energy in state.robots_by_player[whoami]:
+ my_robots = state.robots_by_player[whoami]
+ for rx, ry, energy in my_robots:
distance, factory = closest_factory(whoami, state, rx, ry)
if factory is None or random.random() < 0.1:
diff --git a/example-bots/pathing.py b/example-bots/pathing.py
new file mode 100644
index 0000000..d537940
--- /dev/null
+++ b/example-bots/pathing.py
@@ -0,0 +1,40 @@
+import random
+
+def move_toward_closest_factory(whoami, state, rx, ry):
+ if state[rx, ry].allegiance != whoami and state[rx, ry].city == '+':
+ return 'P'
+ to_do = [
+ (rx + 1, ry, 'R'),
+ (rx - 1, ry, 'L'),
+ (rx, ry - 1, 'U'),
+ (rx, ry + 1, 'D')
+ ]
+ random.shuffle(to_do)
+ seen = []
+ while to_do:
+ (x, y, move) = to_do.pop(0)
+ if (x, y) in seen or state[x, y].city == 'X':
+ continue
+ seen.append((x, y))
+ if state[x, y].allegiance != whoami and state[x, y].city == '+':
+ return move
+ else:
+ to_add = [(x + 1, y, move),
+ (x - 1, y, move),
+ (x, y + 1, move),
+ (x, y - 1, move)]
+ random.shuffle(to_add)
+ to_do += to_add
+
+def calculate_orders(whoami, state):
+ orders = []
+
+ my_robots = state.robots_by_player[whoami]
+ for rx, ry, energy in my_robots:
+ move = move_toward_closest_factory(whoami, state, rx, ry)
+ if move and random.random() > 0.1:
+ orders.append(move)
+ else:
+ orders.append(random.choice('UDLRP'))
+
+ return orders
diff --git a/example-bots/random.py b/example-bots/random.py
new file mode 100644
index 0000000..e61ff15
--- /dev/null
+++ b/example-bots/random.py
@@ -0,0 +1,11 @@
+import random
+
+def calculate_orders(whoami, state):
+ orders = []
+
+ my_robots = state.robots_by_player[whoami]
+ for robot in my_robots:
+ # Make a random move for each robot
+ orders.append(random.choice('ULDRP'))
+
+ return orders
diff --git a/example-bots/random_valid.py b/example-bots/random_valid.py
new file mode 100644
index 0000000..0a84b96
--- /dev/null
+++ b/example-bots/random_valid.py
@@ -0,0 +1,30 @@
+import random
+
+def calculate_orders(whoami, state):
+ orders = []
+
+ my_robots = state.robots_by_player[whoami]
+ for rx, ry, energy in my_robots:
+ if state[rx, ry].city == '+' and state[rx, ry].allegiance != whoami:
+ orders.append('P')
+ continue
+ potential_orders = []
+ if state[rx, ry].allegiance != whoami:
+ potential_orders.append('P')
+ if state[rx + 1, ry].city != 'X':
+ potential_orders.append('R')
+ if state[rx - 1, ry].city != 'X':
+ potential_orders.append('L')
+ if state[rx, ry + 1].city != 'X':
+ potential_orders.append('D')
+ if state[rx, ry - 1].city != 'X':
+ potential_orders.append('U')
+
+ if potential_orders:
+ # Make a random move for each robot
+ orders.append(random.choice(potential_orders))
+ else:
+ # There are no valid moves, so do nothing for this robot
+ orders.append('-')
+
+ return orders
diff --git a/example-bots/up_bot.py b/example-bots/up_bot.py
new file mode 100644
index 0000000..282d25b
--- /dev/null
+++ b/example-bots/up_bot.py
@@ -0,0 +1,11 @@
+def calculate_orders(whoami, state):
+ orders = []
+
+ # Get the robots you control.
+ my_robots = state.robots_by_player[whoami]
+
+ # Tell them all to go up.
+ for robot in my_robots:
+ orders.append('U')
+
+ return orders
diff --git a/manifest.scm b/manifest.scm
new file mode 100644
index 0000000..4539292
--- /dev/null
+++ b/manifest.scm
@@ -0,0 +1,11 @@
+(use-modules (gnu packages))
+
+(specifications->manifest
+ (list "python"
+ "python-six"
+ "python-urwid"
+ "python-pyzmq"
+ "python-blessings"
+ "python-logbook"
+ "python-pygobject"
+ "python-dbus"))
diff --git a/arena.txt b/maps/arena.txt
index 03ea50b..03ea50b 100644
--- a/arena.txt
+++ b/maps/arena.txt
diff --git a/empty.txt b/maps/empty.txt
index 105bc1b..105bc1b 100644
--- a/empty.txt
+++ b/maps/empty.txt
diff --git a/large.txt b/maps/large.txt
index 1fbaae8..1fbaae8 100644
--- a/large.txt
+++ b/maps/large.txt
diff --git a/maze.txt b/maps/maze.txt
index 7a405b7..7a405b7 100644
--- a/maze.txt
+++ b/maps/maze.txt
diff --git a/maps/small.txt b/maps/small.txt
new file mode 100644
index 0000000..81c5753
--- /dev/null
+++ b/maps/small.txt
@@ -0,0 +1,11 @@
+XXXXXXXXXXX
+X++.....++X
+X+a.XXX.c+X
+X.........X
+X.X.X+X.X.X
+X.X.+++.X.X
+X.X.X+X.X.X
+X.........X
+X+d.XXX.b+X
+X++.....++X
+XXXXXXXXXXX
diff --git a/two.txt b/maps/two.txt
index add738d..add738d 100644
--- a/two.txt
+++ b/maps/two.txt
diff --git a/napoleon.py b/napoleon.py
deleted file mode 100644
index 15685eb..0000000
--- a/napoleon.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# Strategiser
-# Goals
-
-from collections import defaultdict
-from functools import lru_cache
-from random import choice
-
-import robots
-
-from robots.algorithms import distance_relaxer
-from robots.constants import City
-
-def get_close_spawns(whoami, state, threshold):
- distances = []
- for y, row in enumerate(state.cities):
- distances.append([])
- for x, cell in enumerate(row):
- if cell not in City.traversable:
- d = None
- elif (
- cell == City.FACTORY and
- state.allegiances.get((x, y)) != whoami
- ):
- d = 0
- else:
- d = float('inf')
- distances[-1].append(d)
-
- predecessors = distance_relaxer(distances, threshold)
- return predecessors, distances
-
-@lru_cache(maxsize=16)
-def closest_unpainted(whoami, state):
- distances = []
- for y, row in enumerate(state.cities):
- distances.append([])
- for x, cell in enumerate(row):
- if cell == City.GHOST:
- d = None
- elif state.allegiances.get((x, y)) != whoami:
- d = 0
- else:
- d = float('inf')
- distances[-1].append(d)
-
- return distance_relaxer(distances)
-
-def good_moves(state, x, y, moves):
- return [
- move
- for move in moves
- if not state.robots[state.expected_position(x, y, move)]
- ]
-
-class Napoleon:
- def __init__(self, capturers_frac=1, dampening=0.01):
- # (x, y) -> (goal, goal_state)
- self.bot_goals = {}
- self.capturers_frac = capturers_frac
- self.dampening = dampening
-
- def goal_capture(self, whoami, state, x, y, goal_state):
- moves = goal_state
- good = good_moves(state, x, y, moves)
- if good:
- return choice(good)
- return choice(moves or 'P')
-
- def goal_paint(self, whoami, state, x, y, goal_state):
- predecessors = closest_unpainted(whoami, state)
- moves = predecessors[y][x]
- good = good_moves(state, x, y, moves)
- if good:
- return choice(good)
- return choice(moves or 'P')
-
- def strategise(self, whoami, state):
- bot_goals = {}
-
- robots = state.robots_by_player[whoami]
-
- if len(state.factories_by_player[whoami]) <= 3:
- self.capturers_frac = 1.0
- if len(robots) <= 10:
- self.capturers_frac = 1.0
-
-# threshold = 10 if early_stages else None
- threshold = None
- spawns, spawns_dist = get_close_spawns(whoami, state, threshold)
-
- max_capturers = max(3, int(len(robots) * self.capturers_frac))
- self.capturers_frac = max(0.01, self.capturers_frac - self.dampening)
- capturers = {
- (x, y)
- for x, y, _ in sorted(
- robots,
- key=lambda r: spawns_dist[r[1]][r[0]],
- )[:max_capturers]
- if threshold is None or spawns_dist[y][x] <= threshold
- }
- assert len(capturers) <= max_capturers
-
- for x, y, energy in robots:
- if (x, y) in capturers:
- goal = 'capture'
- goal_state = spawns[y][x]
- else:
- goal = 'paint'
- goal_state = None
-
- bot_goals[x, y] = (goal, goal_state)
-
-# print(whoami, ''.join(v[0].upper() for v, _ in bot_goals.values()))
- return bot_goals
-
- def __call__(self, whoami, state):
- self.bot_goals = self.strategise(whoami, state)
-
- results = {}
-
- todo = self.bot_goals.keys()
-
- for _ in range(2):
- by_dest = defaultdict(list)
-
- for x, y in todo:
- goal, goal_state = self.bot_goals[x, y]
- goal_fn = getattr(self, 'goal_' + goal)
- action = goal_fn(whoami, state, x, y, goal_state)
- by_dest[state.expected_position(x, y, action)].append((x, y))
- results[x, y] = action
-
- todo = []
- for robots in by_dest.values():
- if len(robots) > 1:
- todo.extend(robots)
-
- return ''.join(
- results[x, y]
- for x, y, _ in state.robots_by_player[whoami]
- )
-
-if __name__ == '__main__':
- server = robots.Server()
- server.add_bot(Napoleon, 'Napoleon')
- server.run()
diff --git a/network/server.py b/network/server.py
index 9c63ea5..7aea705 100644
--- a/network/server.py
+++ b/network/server.py
@@ -134,7 +134,6 @@ class Server(object):
host = ''
domain = ''
- help(self.avahi_group)
self.avahi_group.AddService(
avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,
dbus.UInt32(0),
diff --git a/peter.py b/peter.py
deleted file mode 100644
index a79e545..0000000
--- a/peter.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import robots
-
-from capturer import CaptureSpawns
-from simple import random_walk
-from napoleon import Napoleon
-from manhattan import manhattan
-from up_bot import up_bot
-
-server = robots.Server()
-server.add_simple_bot(up_bot, 'Simba')
-server.add_simple_bot(random_walk, 'Zazu')
-server.add_simple_bot(manhattan, 'Nala')
-server.add_bot(CaptureSpawns, 'Scar')
-server.add_bot(Napoleon, 'Mufasa')
-server.run()
diff --git a/real-setup.py b/real-setup.py
deleted file mode 100644
index 5a15cf2..0000000
--- a/real-setup.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from setuptools import find_packages, setup
-from Cython.Build import cythonize
-
-setup(
- name='robots',
- packages=find_packages(),
- ext_modules=cythonize('robots/*.pyx'),
- install_requires=[
- 'blessings',
- 'cython',
- 'logbook',
- 'pyzmq',
- 'urwid'
- ],
- entry_points={
- 'console_scripts': [
- 'robots-client = robots.client:main',
- ]
- },
-)
diff --git a/requirements.txt b/requirements.txt
index d9b8239..f9d1f00 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,6 @@
--e ../simple-network
--e .
+blessings==1.7
+Logbook==1.5.3
+PyGObject==3.40.1
+pyzmq==22.3.0
+six==1.16.0
+urwid==2.1.2
diff --git a/robots/algorithms.pyx b/robots/algorithms.pyx
deleted file mode 100644
index 3bf6916..0000000
--- a/robots/algorithms.pyx
+++ /dev/null
@@ -1,162 +0,0 @@
-# cython: language_level=3
-
-cimport cython
-
-import numpy as np
-cimport numpy as np
-
-ITYPE = np.int32
-ctypedef np.int32_t ITYPE_t
-
-ctypedef cython.bint bool
-
-INFINITY = float('inf')
-FAKE_INFINITY = 1<<31 - 1
-
-PREDECESSORS = {}
-for i in range(16):
- p = []
- if i & 1:
- p.append('U')
- if i & 2:
- p.append('D')
- if i & 4:
- p.append('L')
- if i & 8:
- p.append('R')
- PREDECESSORS[i] = p
-
-def distance_relaxer(distances, iterations=None):
- width = len(distances[0])
- height = len(distances)
-
- predecessors = [
- [
- []
- for _ in range(width)
- ]
- for _ in range(height)
- ]
-
- convert = {
- INFINITY: FAKE_INFINITY,
- None: -1,
- }
- unconvert = {
- FAKE_INFINITY: INFINITY,
- -1: None,
- }
- _distances = np.array(
- [
- [
- convert.get(v, v)
- for v in row
- ]
- for row in distances
- ],
- dtype=ITYPE,
- order='c',
- )
-
- if iterations is None:
- iterations = -1
-
- _distance_relaxer(
- _distances,
- predecessors,
- iterations,
- width, height,
- )
-
- for y in range(height):
- for x in range(width):
- v = _distances[y, x]
- distances[y][x] = unconvert.get(v, v)
-
- return predecessors
-
-@cython.boundscheck(False)
-cdef void _distance_relaxer(
- np.ndarray[ITYPE_t, ndim=2, mode='c'] distances,
- predecessors,
- int iterations,
- Py_ssize_t width,
- Py_ssize_t height,
-):
- cdef Py_ssize_t x, y
- cdef Py_ssize_t nx, ny
- cdef ITYPE_t distance, this_distance
-
- cdef int i = 0
- cdef bool updated
- cdef int p
-
- while i != iterations:
- updated = False
-
- for y from 0 <= y < height:
- for x from 0 <= x < width:
- distance = distances[y, x]
- if distance == -1:
- continue
-
- p = 0
-
- nx = x
- ny = (y - 1) % height
- this_distance = distances[ny, nx]
- if this_distance != -1:
- if this_distance != FAKE_INFINITY:
- this_distance += 1
- if this_distance < distances[y, x]:
- p = 0
- distances[y, x] = this_distance
- updated = True
- if this_distance == distances[y, x]:
- p |= 1
-
- nx = x
- ny = (y + 1) % height
- this_distance = distances[ny, nx]
- if this_distance != -1:
- if this_distance != FAKE_INFINITY:
- this_distance += 1
- if this_distance < distances[y, x]:
- p = 0
- distances[y, x] = this_distance
- updated = True
- if this_distance == distances[y, x]:
- p |= 2
-
- nx = (x - 1) % width
- ny = y
- this_distance = distances[ny, nx]
- if this_distance != -1:
- if this_distance != FAKE_INFINITY:
- this_distance += 1
- if this_distance < distances[y, x]:
- p = 0
- distances[y, x] = this_distance
- updated = True
- if this_distance == distances[y, x]:
- p |= 4
-
- nx = (x + 1) % width
- ny = y
- this_distance = distances[ny, nx]
- if this_distance != -1:
- if this_distance != FAKE_INFINITY:
- this_distance += 1
- if this_distance < distances[y, x]:
- p = 0
- distances[y, x] = this_distance
- updated = True
- if this_distance == distances[y, x]:
- p |= 8
-
- if p:
- predecessors[y][x] = PREDECESSORS[p]
-
- if not updated:
- break
- i += 1
diff --git a/robots/client.py b/robots/client.py
index 45b593e..021a11d 100644
--- a/robots/client.py
+++ b/robots/client.py
@@ -3,7 +3,6 @@ import itertools
import os
import random
import signal
-#from string import ascii_lowercase as lowercase
import time
import zmq
@@ -159,7 +158,7 @@ def make_ui(avail_walker, added_walker):
lines = content.split('\n')
stdout.set_text('\n'.join(lines[-5:]))
- frame = urwid.Frame(main, footer=urwid.LineBox(stdout))
+ frame = urwid.Frame(main) #, footer=urwid.LineBox(stdout))
return frame, update_stdout
def choose_bots():
@@ -256,23 +255,31 @@ def main():
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),
- name,
- )
+
+ bots_with_names = [
+ [RemoteBot(*key), f"{server_info['name']}.{name}"]
+ for key, server_info, name in bots
+ ]
+
+ for i, (bot, name) in enumerate(bots_with_names):
+ same_name_before = 0
+ same_name_total = 0
+ for j, (_, other_name) in enumerate(bots_with_names):
+ if name == other_name:
+ if j < i:
+ same_name_before += 1
+ same_name_total += 1
+ if same_name_total > 1:
+ game.add_bot(bot, f"{name} ({same_name_before + 1})")
+ else:
+ game.add_bot(bot, name)
viewer = CursesViewer(game)
viewer.run()
- time.sleep(1)
- random.shuffle(bots)
+ if args.loop:
+ time.sleep(3)
+ random.shuffle(bots)
if __name__ == '__main__':
main()
diff --git a/robots/cursesviewer.py b/robots/cursesviewer.py
index 96042a2..6fbbaca 100644
--- a/robots/cursesviewer.py
+++ b/robots/cursesviewer.py
@@ -36,27 +36,29 @@ class CursesViewer:
players.append(fn(name))
stdout.append(' '.join(players) + '\n')
- width = len(state.board[0]) * 3
+ width = state.width * 3
stdout.append(Box.TL + Box.H * width + Box.TR + '\n')
- for y, row in enumerate(state.board):
+ board = state.board
+ for y in range(state.height):
output = []
- for x, cell in enumerate(row):
+ for x in range(state.width):
+ cell = board[(x, y)]
value = ' '
- if cell['city'] == City.GHOST:
+ if cell.city == City.GHOST:
background = 15
else:
- background = self.player_colours.get(cell['allegiance'])
+ background = self.player_colours.get(cell.allegiance)
- if cell['robots']:
- (robot_owner, energy), = cell['robots']
+ if cell.robots:
+ (robot_owner, energy), = cell.robots
foreground = self.player_colours[robot_owner]
if foreground == background:
foreground = 15
- if cell['city'] == City.FACTORY:
+ if cell.city == City.FACTORY:
value = '[%s]' % energy
else:
value = '<%s>' % energy
@@ -64,7 +66,7 @@ class CursesViewer:
func = self.terminal.color(foreground)
value = func(value)
- elif cell['city'] == City.FACTORY:
+ elif cell.city == City.FACTORY:
value = '[ ]'
if background is not None:
diff --git a/robots/game.py b/robots/game.py
index 0581020..afc84b6 100644
--- a/robots/game.py
+++ b/robots/game.py
@@ -222,11 +222,13 @@ class Game:
while not self.finished:
yield self.state
self.next()
- if self.time > 400:
+ if self.time > 10000:
break
yield self.state
def next(self):
+ # 0. Clear the board cache so we can see the baord after the last moves.
+ self.state.board_cache = None
# 1. You give commands you your robots.
actions = self.call_bots()
# 2. They perform the requested commands.
diff --git a/robots/server.py b/robots/server.py
index 65c1136..41b6f33 100644
--- a/robots/server.py
+++ b/robots/server.py
@@ -8,6 +8,7 @@ import logbook
import network
from robots.state import GameState
+from traceback import print_exc
log = logbook.Logger(__name__)
@@ -114,6 +115,7 @@ class Server(object):
try:
result = instance(whoami, state)
except Exception:
+ print_exc()
message = 'Exception running instance %s.' % instance_id
log.exception(message)
return server_error(message)
diff --git a/robots/state.py b/robots/state.py
index 37d2554..59ee026 100644
--- a/robots/state.py
+++ b/robots/state.py
@@ -3,6 +3,12 @@ from collections import defaultdict, Counter
from robots.constants import City, DIRECTIONS
from robots.utils import ceil_div#, immutable
+class CityState:
+ def __init__(self, city, allegiance, robots):
+ self.city = city
+ self.allegiance = allegiance
+ self.robots = robots
+
class GameState:
"""The state of a game at a point in time.
@@ -31,6 +37,8 @@ class GameState:
Each robot is represented by (x, y, energy).
"""
+ self.board_cache = None
+
def __hash__(self):
return id(self)
# return hash(immutable(self._authorative_state))
@@ -94,35 +102,34 @@ class GameState:
@property
def board(self):
"""
- A 2D list with all available information about each city.
-
- Each city is represented with a dictionary:
+ A dict with all available information about each city.
- >>> {
- ... 'city': robots.constants.City,
- ... 'allegiance': player or None,
- ... 'robots': [robot] or [],
- ... }
+ Keys are (x, y) tuples.
+ Each city is represented with a CityState object:
"""
+ if self.board_cache:
+ return self.board_cache
+
# TODO: remove this once I've figured out caching.
self_robots = self.robots
- result = []
+ result = {}
for y, row in enumerate(self.cities):
- result_row = []
for x, city in enumerate(row):
allegiance = self.allegiances.get((x, y))
robots = self_robots[x, y]
- result_row.append({
- 'city': city,
- 'allegiance': allegiance,
- 'robots': robots,
- })
- result.append(result_row)
+ result[(x, y)] = CityState(city, allegiance, robots)
+ self.board_cache = result;
return result
+ def __getitem__(self, coordinates):
+ x, y = coordinates
+ x %= self.width
+ y %= self.height
+ return self.board[x, y]
+
@property
def width(self):
"""Width of the map."""
@@ -186,7 +193,7 @@ class GameState:
@property
def n_cities_to_win(self):
"""How many cities you need to pledge allegiance to you to win."""
- return ceil_div(self.n_allegiable_cities, self.n_alive_players)
+ return ceil_div(self.n_allegiable_cities, self.n_alive_players) + 1
def expected_position(self, x, y, action):
"""
diff --git a/run_competition.py b/run_competition.py
new file mode 100644
index 0000000..ccbb328
--- /dev/null
+++ b/run_competition.py
@@ -0,0 +1,2 @@
+import robots.client
+robots.client.main()
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 01f6a07..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[metadata]
-setup-requires = Cython
diff --git a/setup.py b/setup.py
deleted file mode 100644
index cd079e6..0000000
--- a/setup.py
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env python
-# Install dependencies from a "[metadata] setup-requires = ..." section in
-# setup.cfg, then run real-setup.py.
-# From https://bitbucket.org/dholth/setup-requires
-
-import sys, os, subprocess, codecs, pkg_resources
-
-sys.path[0:0] = ['setup-requires']
-pkg_resources.working_set.add_entry('setup-requires')
-
-try:
- import configparser
-except:
- import ConfigParser as configparser
-
-def get_requirements():
- if not os.path.exists('setup.cfg'): return
- config = configparser.ConfigParser()
- config.readfp(codecs.open('setup.cfg', encoding='utf-8'))
- setup_requires = config.get('metadata', 'setup-requires')
- specifiers = [line.strip() for line in setup_requires.splitlines()]
- for specifier in specifiers:
- try:
- pkg_resources.require(specifier)
- except pkg_resources.DistributionNotFound:
- yield specifier
-
-try:
- to_install = list(get_requirements())
- if to_install:
- subprocess.call([sys.executable, "-m", "pip", "install",
- "-t", "setup-requires"] + to_install)
-except (configparser.NoSectionError, configparser.NoOptionError):
- pass
-
-# Run real-setup.py
-exec(compile(open("real-setup.py").read().replace('\\r\\n', '\\n'),
- __file__,
- 'exec'))
-
diff --git a/simple.py b/simple.py
deleted file mode 100644
index 1ff37f2..0000000
--- a/simple.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import random
-import sys
-
-import robots
-
-def random_walk(whoami, state):
- my_robots = state.robots_by_player[whoami]
- return ''.join(
- random.choice('ULDRP')
- for _ in range(len(my_robots))
- )
-
-if __name__ == '__main__':
- name, = sys.argv[1:]
- server = robots.Server()
- server.SERVER_NAME = name
- server.add_simple_bot(random_walk, 'Bot1')
- server.add_simple_bot(random_walk, 'Bot2')
- server.run()
diff --git a/up_bot.py b/up_bot.py
deleted file mode 100644
index 4b2b403..0000000
--- a/up_bot.py
+++ /dev/null
@@ -1,5 +0,0 @@
-def up_bot(whoami, state):
- # Get the number of robots you control.
- n_robots = len(state.robots_by_player[whoami])
- # Tell them all to go up.
- return 'U' * n_robots