Changed colour of blue pieces
[progcomp2012.git] / agents / hunter / hunter.path.py
1 #!/usr/bin/python -u
2
3 #NOTE: The -u option is required for unbuffered stdin/stdout.
4 #       If stdin/stdout are buffered, the manager program will not recieve any messages and assume that the agent has timed out.
5
6 '''
7  khaos.py - A sample Stratego AI for the UCC Programming Competition 2012
8
9  The name describes the state of this file :S
10
11  Written in python, the slithery language 
12
13  author Sam Moore (matches) [SZM]
14  website http://matches.ucc.asn.au/stratego
15  email progcomp@ucc.asn.au or matches@ucc.asn.au
16  git git.ucc.asn.au/progcomp2012.git
17 '''
18
19 import os
20
21 from basic_python import *
22 from path import *
23
24 def OppositeDirection(direction):
25         if direction == "UP":
26                 return "DOWN"
27         elif direction == "DOWN":
28                 return "UP"
29         elif direction == "LEFT":
30                 return "RIGHT"
31         elif direction == "RIGHT":
32                 return "LEFT"
33         else:
34                 assert(False)
35         return "ERROR"
36
37 class Hunter(BasicAI):
38         " Python based AI of DEATH "
39         def __init__(self, scoresFilename=None):
40                 if scoresFilename == None:
41                         scoresFilename = "default.scores"
42                 BasicAI.__init__(self)
43                 
44                 scoresFile = open(scoresFilename, "r")
45                 self.scoreTable = []
46                 for i in scoresFile.readline().strip().split(' '):
47                         self.scoreTable.append(float(i))
48                 scoresFile.close()
49
50                 self.maxdepth = 1
51                 self.recursiveConsider = {"allies" : 2, "enemies" : 2}
52                 self.paths = {}
53                 
54
55         def PositionLegal(self, x, y, unit = None):
56                 if x >= 0 and x < len(self.board) and y >= 0 and y < len(self.board[x]):
57                         if unit == None:
58                                 return True
59                         else:
60                                 return self.board[x][y] == None or self.board[x][y].colour == oppositeColour(unit.colour)
61                 else:
62                         return False
63
64         def BestMove(self, maxdepth = 1):
65
66                 moveList = []
67
68                 
69                 if maxdepth < self.maxdepth:
70                         #sys.stderr.write("Recurse!\n")
71                         considerAllies = self.recursiveConsider["allies"]
72                         considerEnemies = self.recursiveConsider["enemies"]
73                 else:
74                         considerAllies = len(self.units)+1
75                         considerEnemies = len(self.enemyUnits)+1
76
77                 for enemy in self.enemyUnits[0:considerEnemies]:
78                         for ally in self.units[0:considerAllies]:
79                                 moveList.append(self.DesiredMove(ally, enemy))
80
81                 for desiredMove in moveList:
82                         if desiredMove[0] == "NO_MOVE" or desiredMove[2] == None:
83                                 desiredMove[1] = -2.0
84
85                 
86                         
87
88                 if maxdepth > 1:
89                         for desiredMove in moveList:
90                                 if desiredMove[2] == None or desiredMove[1] < 0.0:
91                                         continue
92                                 p = move(desiredMove[3].x, desiredMove[3].y, desiredMove[2][0], 1)
93                                 if self.board[p[0]][p[1]] == None:
94                                         x = desiredMove[3].x
95                                         y = desiredMove[3].y
96                                         result = desiredMove[0] + " OK"
97                                         self.InterpretResult(result)
98                                         bestRecurse = self.BestMove(maxdepth-1)
99                                         if bestRecurse != None:
100                                                 desiredMove[1] += bestRecurse[1]# / float(max(1.0, maxdepth))
101                                         self.board[desiredMove[3].x][desiredMove[3].y] = None
102                                         self.board[x][y] = desiredMove[3]
103                                         desiredMove[3].x = x
104                                         desiredMove[3].y = y
105
106                 
107
108                 
109                 if len(moveList) <= 0:
110                         return None
111                 moveList.sort(key = lambda e : e[1], reverse = True)                    
112                 return moveList[0]
113                                 
114
115         def GetPath(self, ally, enemy):
116                 #Attempts to do the minimum required work to reconstruct a path
117                 return PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)
118                 if (ally in self.paths.keys()) == False: 
119                         self.paths.update({ally : {}})  
120                         #sys.stderr.write("Update keys are " + str(self.paths.keys()) + "\n")   
121                 #sys.stderr.write("Keys are " + str(self.paths.keys()) + "\n")  
122
123                 if (enemy in self.paths[ally].keys()) == False: #No path exists; compute a new one
124                         path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)                          
125                         if path != False:
126                                 self.paths[ally].update({enemy : [path, (ally.x, ally.y), (enemy.x, enemy.y)]})
127                         return path
128
129                 oldPath = self.paths[ally][enemy]
130                 if oldPath[1][0] != ally.x or oldPath[1][1] != ally.y or oldPath[2][0] != enemy.x or oldPath[2][1] != enemy.y:
131                         #The pieces involved have moved. Recompute the path 
132                         path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)
133                         if path != False:
134                                 self.paths[ally][enemy] = [path, (ally.x, ally.y), (enemy.x, enemy.y)]
135                         return path
136
137                 if len(oldPath[0]) > 1:
138                         #The pieces involved haven't moved, check to see if the path is blocked
139                         p = move(ally.x, ally.y, oldPath[0][0], 1) #Look forward one move
140                         if self.PositionLegal(p[0], p[1]) and self.board[p[0]][p[1]] != None: #If the position is blocked...
141                                 path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)  #Compute new path
142                                 if path != False:
143                                         self.paths[ally][enemy] = [path, (ally.x, ally.y), (enemy.x, enemy.y)]
144                                 return path
145                 return False
146                 
147         def DesiredMove(self, ally, enemy):
148                 """ Determine desired move of allied piece, towards or away from enemy, with score value """
149                 scaleFactor = 1.0
150                 if ally.rank == 'F' or ally.rank == 'B':
151                         return ["NO_MOVE", 0, None, ally, enemy]
152                 
153                 actionScores = {"ATTACK" : 0, "RETREAT" : 0}
154                 if enemy.rank == '?':
155                         for i in range(0, len(ranks)):
156                                 prob = self.rankProbability(enemy, ranks[i])
157                                 if prob > 0:
158                                         desiredAction = self.DesiredAction(ally, ranks[i])
159                                         actionScores[desiredAction[0]] += prob* (desiredAction[1] / 2.0)
160                         if len(enemy.positions) <= 1 and ally.rank != '8':
161                                 scaleFactor *= (1.0 - float(valuedRank(ally.rank)) / float(valuedRank('1')))**2.0
162                         elif len(enemy.positions) > 1 and ally.rank == '8':
163                                 scaleFactor *= 0.05
164                         #elif len(enemy.positions) > 1:
165                         #       scaleFactor *= (1.0 - float(valuedRank(ally.rank)) / float(valuedRank('1')))**0.25
166                         #       scaleFactor = max(0.05, scaleFactor)
167                 else:
168                         desiredAction = self.DesiredAction(ally, enemy.rank)
169                         actionScores[desiredAction[0]] += desiredAction[1]
170                 
171
172                 desiredAction = sorted(actionScores.items(), key = lambda e : e[1], reverse = True)[0]
173                 direction = None
174                 path = self.GetPath(ally, enemy)
175
176                 
177                 if path != False and len(path) > 0:
178                         if desiredAction[0] == "RETREAT":
179                                 #sys.stderr.write("Recommend retreat! "+ally.rank + " from " + enemy.rank+"\n")
180                                 direction = OppositeDirection(path[0])
181                                 p = move(ally.x, ally.y, direction, 1)
182                                 if self.PositionLegal(p[0], p[1], ally) == False:
183                                         path = None
184                                 scaleFactor = 0.05 * scaleFactor
185                         else:
186                                 direction = path[0]
187                         if desiredAction[1] > 0.0 and path != None:
188                                 scaleFactor = scaleFactor / float(len(path))
189                         return [str(ally.x) + " " + str(ally.y) + " " + direction, desiredAction[1] * scaleFactor, path, ally, enemy]
190
191                 #directions = {"RIGHT" : enemy.x - ally.x, "LEFT" : ally.x - enemy.x, "DOWN" : enemy.y - ally.y, "UP" : ally.y - enemy.y}
192                 #if desiredAction[0] == "RETREAT":
193                 #       for key in directions.keys():
194                 #               directions[key] = -directions[key]
195
196                 #while direction == None:
197                 #       d = sorted(directions.items(), key = lambda e : e[1], reverse = True)
198                 #       p = move(ally.x, ally.y, d[0][0], 1)
199                 #       if self.PositionLegal(p[0], p[1]) and (self.board[p[0]][p[1]] == None or self.board[p[0]][p[1]] == enemy):
200                 #               direction = d[0][0]
201                 #               scaleFactor *= (1.0 - float(max(d[0][1], 0.0)) / 10.0)**2.0
202                 #       else:
203                 #               del directions[d[0][0]]
204                 #               if len(directions.keys()) <= 0:
205                 #                       break
206
207                 if abs(enemy.x - ally.x) >= abs(enemy.y - ally.y):
208                         if enemy.x > ally.x:
209                                 direction = "RIGHT"
210                         elif enemy.x < ally.x:
211                                 direction = "LEFT"              
212                 else:
213                         if enemy.y > ally.y:
214                                 direction = "DOWN"
215                         elif enemy.y < ally.y:
216                                 direction = "UP"
217                 if direction == None:
218                         return ["NO_MOVE", 0, [], ally, enemy]
219                 return [str(ally.x) + " " + str(ally.y) + " " + direction, desiredAction[1], None, ally, enemy]                 
220                         
221
222         def DesiredAction(self, ally, enemyRank):
223                 if enemyRank == 'F':
224                         return ["ATTACK", 1.0]
225                 if ally.rank == '8' and enemyRank == 'B':
226                         return ["ATTACK", 0.9]
227                 if ally.rank == '1' and enemyRank == 's':
228                         return ["RETREAT", 0.9]
229                 if ally.rank == 's' and enemyRank == '1':
230                         return ["ATTACK", 0.6]
231                 if enemyRank == 'B':
232                         return ["RETREAT", 0.0]
233                 if ally.rank == enemyRank:
234                         return ["ATTACK", 0.1]
235                 if valuedRank(ally.rank) > valuedRank(enemyRank):
236                         return ["ATTACK", float(self.scoreTable[ranks.index(enemyRank)]) * (0.1 + 1.0/float(self.scoreTable[ranks.index(ally.rank)]))]
237                 else:
238                         return ["RETREAT", float(self.scoreTable[ranks.index(ally.rank)]) / 10.0]
239                 
240
241         def MakeMove(self):
242                 if len(self.units) < 20:
243                         self.maxdepth = 1
244                 bestMove = self.BestMove(self.maxdepth)
245
246
247                 if bestMove == None:
248                         #sys.stderr.write("Khaos makes random move!\n")
249                         return BasicAI.MakeMove(self)
250                 
251                 #sys.stderr.write("Board state before move: \n")
252                 #self.debugPrintBoard()
253                 
254                 #sys.stderr.write("Best move is \"" + bestMove[0] + "\" with score " +  str(bestMove[1]) + " as part of path " +str(bestMove[2]) + " ...\n")
255                 #sys.stderr.write("      Ally with rank " + bestMove[3].rank + " is targeting unit at " + str((bestMove[4].x, bestMove[4].y)) + " rank " + bestMove[4].rank + "\n")
256                 
257                 sys.stdout.write(bestMove[0] + "\n")
258                 #self.paths[bestMove[3]][bestMove[4]].pop(0)
259
260                 return True
261
262
263
264         def rankProbability(self, target, targetRank):
265
266                 if targetRank == '+' or targetRank == '?':
267                         return 0.0
268                 if target.rank == targetRank:
269                         return 1.0
270                 elif target.rank != '?':
271                         return 0.0
272
273                 total = 0.0
274                 for rank in ranks:
275                         if rank == '+' or rank == '?':
276                                 continue
277                         elif rank == 'F' or rank == 'B':
278                                 if target.lastMoved < 0:
279                                         total += self.hiddenEnemies[rank]
280                         else:
281                                 total += self.hiddenEnemies[rank]
282
283                 if total == 0.0:
284                         return 0.0
285                 return float(float(self.hiddenEnemies[targetRank]) / float(total))
286
287         def InterpretResult(self, string=None):
288                 if BasicAI.InterpretResult(self, string) == False:
289                         return False
290
291
292                 if self.maxdepth > 1:
293                         if self.lastMoved != None and self.lastMoved.colour == self.colour and self.lastMoved.alive == False:
294                                 self.units.sort(key = lambda e : valuedRank(e.rank), reverse = True)
295                         elif self.lastMoved != None and self.lastMoved.colour == oppositeColour(self.colour) and self.lastMoved.alive == True:
296                                 oldRank = self.lastMoved.rank
297                                 self.lastMoved.rank = '1'
298                                 self.enemyUnits.sort(key = lambda e : valuedRank(e.rank), reverse = True)
299                                 self.lastMoved.rank = oldRank
300                         
301                 
302                 return True                     
303                                 
304                 
305 if __name__ == "__main__":
306         if len(sys.argv) > 1:
307                 hunter = Hunter(sys.argv[1])
308         else:
309                 string = ""
310                 path = sys.argv[0].split('/')
311                 for i in range(0, len(path)-1):
312                         string += path[i] + "/"
313                 string += "default.scores"
314                 
315                 
316                 hunter = Hunter(string)
317         if hunter.Setup():
318                 while hunter.MoveCycle():
319                         pass
320

UCC git Repository :: git.ucc.asn.au