1
+ #!/usr/bin/env python
2
+ import sys
3
+ from random import randint , choice , seed
4
+ from collections import deque
5
+ from itertools import product
6
+ from sys import maxint
7
+
8
+ MY_ANT = 0
9
+ ANTS = 0
10
+ DEAD = - 1
11
+ LAND = - 2
12
+ FOOD = - 3
13
+ WATER = - 4
14
+ UNSEEN = - 5
15
+ MAP_RENDER = 'abcdefghijklmnopqrstuvwxyz?%*.!'
16
+
17
+ AIM = {'n' : (- 1 , 0 ),
18
+ 'e' : (0 , 1 ),
19
+ 's' : (1 , 0 ),
20
+ 'w' : (0 , - 1 )}
21
+
22
+ class Map (object ):
23
+ def __init__ (self , options = {}):
24
+ super (Map , self ).__init__ ()
25
+ self .name = "blank"
26
+ self .map = [[]]
27
+ self .random_seed = options .get ('seed' , None )
28
+ if self .random_seed == None :
29
+ self .random_seed = randint (- maxint - 1 , maxint )
30
+ seed (self .random_seed )
31
+
32
+ def generate (self ):
33
+ raise Exception ("Not Implemented" )
34
+
35
+ def get_random_option (self , option ):
36
+ if type (option ) == tuple :
37
+ if len (option ) == 2 :
38
+ return randint (* option )
39
+ elif len (option ) == 1 :
40
+ return option [0 ]
41
+ elif len (option ) == 0 :
42
+ raise Exception ("Invalid option: 0 length tuple" )
43
+ else :
44
+ return choice (option )
45
+ elif type (option ) in (list , set ):
46
+ if len (option ) > 0 :
47
+ return choice (option )
48
+ else :
49
+ raise Exception ("Invalid option: 0 length list" )
50
+ elif type (option ) in (int , float , str ):
51
+ return option
52
+ else :
53
+ raise Exception ("Invalid option: type {0} not supported" .format (type (option )))
54
+
55
+ def toPNG (self , fd = sys .stdout ):
56
+ raise Exception ("Not Implemented" )
57
+
58
+ def toText (self , fd = sys .stdout ):
59
+ players = set ()
60
+ for row in self .map :
61
+ for c in row :
62
+ if c >= ANTS :
63
+ players .add (c )
64
+ fd .write ("# map_type {0}\n # random_seed {1}\n players {2}\n rows {3}\n cols {4}\n "
65
+ .format (self .name ,
66
+ self .random_seed ,
67
+ len (players ),
68
+ len (self .map ),
69
+ len (self .map [0 ])))
70
+ for row in self .map :
71
+ fd .write ("m {0}\n " .format ('' .join ([MAP_RENDER [c ] for c in row ])))
72
+
73
+ def manhatten_distance (self , loc1 , loc2 , size ):
74
+ rows , cols = size
75
+ row1 , col1 = loc1
76
+ row2 , col2 = loc2
77
+ row1 = row1 % rows
78
+ row2 = row2 % rows
79
+ col1 = col1 % cols
80
+ col2 = col2 % cols
81
+ d_col = min (abs (col1 - col2 ), cols - abs (col1 - col2 ))
82
+ d_row = min (abs (row1 - row2 ), rows - abs (row1 - row2 ))
83
+ return d_row + d_col
84
+
85
+ def euclidean_distance2 (self , loc1 , loc2 , size ):
86
+ rows , cols = size
87
+ row1 , col1 = loc1
88
+ row2 , col2 = loc2
89
+ row1 = row1 % rows
90
+ row2 = row2 % rows
91
+ col1 = col1 % cols
92
+ col2 = col2 % cols
93
+ d_col = min (abs (col1 - col2 ), cols - abs (col1 - col2 ))
94
+ d_row = min (abs (row1 - row2 ), rows - abs (row1 - row2 ))
95
+ return d_row ** 2 + d_col ** 2
96
+
97
+ def destination (self , loc , direction , size ):
98
+ rows , cols = size
99
+ row , col = loc
100
+ d_row , d_col = AIM [direction ]
101
+ return ((row + d_row ) % rows , (col + d_col ) % cols )
102
+
103
+ def section (self , block_size = 1 ):
104
+ rows = len (self .map )
105
+ cols = len (self .map [0 ])
106
+ visited = [[False ] * cols for _ in range (rows )]
107
+
108
+ def is_block_free (loc ):
109
+ row , col = loc
110
+ for d_row in range (- block_size , block_size + 1 ):
111
+ for d_col in range (- block_size , block_size + 1 ):
112
+ h_row = (row + d_row ) % rows
113
+ h_col = (col + d_col ) % cols
114
+ if self .map [h_row ][h_col ] == WATER :
115
+ return False
116
+ return True
117
+
118
+ def mark_block (loc , m , ilk ):
119
+ row , col = loc
120
+ for d_row in range (- block_size , block_size + 1 ):
121
+ for d_col in range (- block_size , block_size + 1 ):
122
+ h_row = (row + d_row ) % rows
123
+ h_col = (col + d_col ) % cols
124
+ m [h_row ][h_col ] = ilk
125
+
126
+ def find_open_spot ():
127
+ for row , col in product (range (rows ), range (cols )):
128
+ if is_block_free ((row , col )) and not visited [row ][col ]:
129
+ return (row , col )
130
+ else :
131
+ return None
132
+
133
+ # list of contiguous areas
134
+ areas = []
135
+
136
+ # flood fill map for each separate area
137
+ while find_open_spot ():
138
+ # maintain lists of visited and seen squares
139
+ # visited will not overlap, but seen may
140
+ area_visited = [[False ] * cols for _ in range (rows )]
141
+ area_seen = [[False ] * cols for _ in range (rows )]
142
+
143
+ squares = deque ()
144
+ row , col = find_open_spot ()
145
+
146
+ #seen_area = open_block((row, col))
147
+ squares .appendleft ((row , col ))
148
+
149
+ while len (squares ) > 0 :
150
+ row , col = squares .pop ()
151
+ visited [row ][col ] = True
152
+ area_visited [row ][col ] = True
153
+ area_seen [row ][col ] = True
154
+ for d_row , d_col in ((1 ,0 ), (0 ,1 ), (- 1 ,0 ), (0 ,- 1 )):
155
+ s_row = (row + d_row ) % rows
156
+ s_col = (col + d_col ) % cols
157
+ if not visited [s_row ][s_col ] and is_block_free ((s_row , s_col )):
158
+ visited [s_row ][s_col ] = True
159
+ mark_block ((s_row , s_col ), area_seen , True )
160
+ squares .appendleft ((s_row , s_col ))
161
+
162
+ # check percentage filled
163
+ #areas.append(1.0 * seen_area / land_area)
164
+ visited_list = []
165
+ seen_list = []
166
+ for row in range (rows ):
167
+ for col in range (cols ):
168
+ if area_visited [row ][col ]:
169
+ visited_list .append ((row , col ))
170
+ elif area_seen [row ][col ]:
171
+ seen_list .append ((row , col ))
172
+ areas .append ([visited_list , seen_list ])
173
+
174
+ # sort by largest area first
175
+ areas .sort (key = lambda area : len (area [0 ]), reverse = True )
176
+ return areas
177
+
178
+ def fill_small_areas (self ):
179
+ # keep largest contiguous area as land, fill the rest with water
180
+ count = 0
181
+ areas = self .section (0 )
182
+ for area in areas [1 :]:
183
+ for row , col in area [0 ]:
184
+ self .map [row ][col ] = WATER
185
+ count += 1
186
+ #print("fill {0}".format(count))
187
+
188
+ def make_wider (self ):
189
+ # make sure the map has more columns than rows
190
+ rows = len (self .map )
191
+ cols = len (self .map [0 ])
192
+ if rows > cols :
193
+ map = [[LAND ] * rows for _ in range (cols )]
194
+ for row in range (rows ):
195
+ for col in range (cols ):
196
+ map [col ][row ] = self .map [row ][col ]
197
+ self .map = map
198
+
199
+ def allowable (self ):
200
+ # all squares must be accessable from all other squares
201
+ # fill small areas can fix this
202
+ areas = self .section (0 )
203
+ if len (areas ) > 1 :
204
+ return "Map not 100% accessable"
205
+ land_area = len (areas [0 ][0 ])
206
+
207
+ # 66% of the map must not be blockable
208
+ # or must be accessable by a 3x3 block
209
+ areas = self .section (1 )
210
+ area_visited , area_seen = areas [0 ]
211
+ if 1.0 * (len (area_seen ) + len (area_visited )) / land_area < 0.66 :
212
+ return "Map is too blockable"
213
+
214
+ # all starting ants must be in the largest area
215
+ ants = {}
216
+ rows = len (self .map )
217
+ cols = len (self .map [0 ])
218
+ for row in range (rows ):
219
+ for col in range (cols ):
220
+ if self .map [row ][col ] >= ANTS :
221
+ if (row , col ) not in area_seen and (row , col ) not in area_visited :
222
+ return "Starting ants not in unblockable area"
223
+ ants [(row , col )] = self .map [row ][col ]
224
+
225
+ return None
0 commit comments