-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhlt.py
149 lines (110 loc) · 5.46 KB
/
hlt.py
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
147
148
149
import sys
from collections import namedtuple
from itertools import chain, zip_longest
def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
NORTH, EAST, SOUTH, WEST, STILL = range(5)
def opposite_cardinal(direction):
"Returns the opposing cardinal direction."
return (direction + 2) % 4 if direction != STILL else STILL
Square = namedtuple('Square', 'x y owner strength production')
Move = namedtuple('Move', 'square direction')
class GameMap:
def __init__(self, size_string, production_string, map_string=None):
self.width, self.height = tuple(map(int, size_string.split()))
self.production = tuple(tuple(map(int, substring)) for substring in grouper(production_string.split(), self.width))
self.contents = None
self.get_frame(map_string)
self.starting_player_count = len(set(square.owner for square in self)) - 1
def get_frame(self, map_string=None):
"Updates the map information from the latest frame provided by the Halite game environment."
if map_string is None:
map_string = get_string()
split_string = map_string.split()
owners = list()
while len(owners) < self.width * self.height:
counter = int(split_string.pop(0))
owner = int(split_string.pop(0))
owners.extend([owner] * counter)
assert len(owners) == self.width * self.height
assert len(split_string) == self.width * self.height
self.contents = [[Square(x, y, owner, strength, production)
for x, (owner, strength, production)
in enumerate(zip(owner_row, strength_row, production_row))]
for y, (owner_row, strength_row, production_row)
in enumerate(zip(grouper(owners, self.width),
grouper(map(int, split_string), self.width),
self.production))]
def __iter__(self):
"Allows direct iteration over all squares in the GameMap instance."
return chain.from_iterable(self.contents)
def neighbors(self, square, n=1, include_self=False):
"Iterable over the n-distance neighbors of a given square. For single-step neighbors, the enumeration index provides the direction associated with the neighbor."
assert isinstance(include_self, bool)
assert isinstance(n, int) and n > 0
if n == 1:
combos = ((0, -1), (1, 0), (0, 1), (-1, 0), (0, 0)) # NORTH, EAST, SOUTH, WEST, STILL ... matches indices provided by enumerate(game_map.neighbors(square))
else:
combos = ((dx, dy) for dy in range(-n, n+1) for dx in range(-n, n+1) if abs(dx) + abs(dy) <= n)
return (self.contents[(square.y + dy) % self.height][(square.x + dx) % self.width] for dx, dy in combos if include_self or dx or dy)
def get_target(self, square, direction):
"Returns a single, one-step neighbor in a given direction."
dx, dy = ((0, -1), (1, 0), (0, 1), (-1, 0), (0, 0))[direction]
return self.contents[(square.y + dy) % self.height][(square.x + dx) % self.width]
def get_distance(self, sq1, sq2):
"Returns Manhattan distance between two squares."
dx = min(abs(sq1.x - sq2.x), sq1.x + self.width - sq2.x, sq2.x + self.width - sq1.x)
dy = min(abs(sq1.y - sq2.y), sq1.y + self.height - sq2.y, sq2.y + self.height - sq1.y)
return dx + dy
def get_distance_x(self, sq1, sq2):
dx = sq2.x - sq1.x
if dx > self.width / 2:
dx -= self.width
elif dx < -self.width / 2:
dx += self.width
return dx
def get_distance_y(self, sq1, sq2):
dy = sq2.y - sq1.y
if dy > self.height / 2:
dy -= self.height
elif dy < -self.height / 2:
dy += self.height
return dy
# dr = dx if dx >= 0 else self.width + dx
# dl = abs(dx) if dx < 0 else self.width - dx
#
# if abs(dr) < abs(dl):
# return abs(dr)
# else:
# return abs(dl)
#
# return min(abs(sq1.x - sq2.x), sq1.x + self.width - sq2.x, sq2.x + self.width - sq1.x)
def get_direction(self, sq1, sq2):
"Returns distance from sq1 to sq2"
assert self.get_distance(sq1, sq2) == 1
for direction, neighbor in enumerate(self.neighbors(sq1)):
if sq2.x == neighbor.x and sq2.y == neighbor.y:
return direction
#################################################################
# Functions for communicating with the Halite game environment #
#################################################################
def send_string(s):
sys.stdout.write(s)
sys.stdout.write('\n')
sys.stdout.flush()
def get_string():
return sys.stdin.readline().rstrip('\n')
def get_init():
playerID = int(get_string())
m = GameMap(get_string(), get_string())
return playerID, m
def send_init(name):
send_string(name)
def translate_cardinal(direction):
"Translate direction constants used by this Python-based bot framework to that used by the official Halite game environment."
return (direction + 1) % 5
def send_frame(moves):
send_string(' '.join(str(move.square.x) + ' ' + str(move.square.y) + ' ' + str(translate_cardinal(move.direction)) for move in moves))