summaryrefslogtreecommitdiff
path: root/napoleon.py
blob: 15685ebc5630580d42ab8f0318a54443256cf773 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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()