Script to playback and save videos of games
[progcomp2012.git] / agents / hunter / hunter.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" : 5, "enemies" : 5}
52
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                 for desiredMove in moveList:
107                         if desiredMove[1] > 0.0:
108                                 desiredMove[1] = desiredMove[1] / float(len(desiredMove[2]))
109                 
110                 if len(moveList) <= 0:
111                         return None
112                 moveList.sort(key = lambda e : e[1], reverse = True)                    
113                 return moveList[0]
114                                 
115                                                 
116         def DesiredMove(self, ally, enemy):
117                 """ Determine desired move of allied piece, towards or away from enemy, with score value """
118                 scaleFactor = 1.0
119                 if ally.rank == 'F' or ally.rank == 'B':
120                         return ["NO_MOVE", 0, None, ally, enemy]
121                 
122                 actionScores = {"ATTACK" : 0, "RETREAT" : 0}
123                 if enemy.rank == '?':
124                         for i in range(0, len(ranks)):
125                                 prob = self.rankProbability(enemy, ranks[i])
126                                 if prob > 0:
127                                         desiredAction = self.DesiredAction(ally, ranks[i])
128                                         actionScores[desiredAction[0]] += prob* (desiredAction[1] / 2.0)
129                         if len(enemy.positions) <= 1 and ally.rank != '8':
130                                 scaleFactor *= (1.0 - float(valuedRank(ally.rank)) / float(valuedRank('1')))**2.0
131                         elif len(enemy.positions) > 1 and ally.rank == '8':
132                                 scaleFactor *= 0.05
133                         #elif len(enemy.positions) > 1:
134                         #       scaleFactor *= (1.0 - float(valuedRank(ally.rank)) / float(valuedRank('1')))**0.25
135                         #       scaleFactor = max(0.05, scaleFactor)
136                 else:
137                         desiredAction = self.DesiredAction(ally, enemy.rank)
138                         actionScores[desiredAction[0]] += desiredAction[1]
139                 
140
141                 desiredAction = sorted(actionScores.items(), key = lambda e : e[1], reverse = True)[0]
142                 direction = None
143                 #path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)
144                 
145                 #if path != False and len(path) > 0:
146                 #       if desiredAction[0] == "RETREAT":
147                                 #sys.stderr.write("Recommend retreat! "+ally.rank + " from " + enemy.rank+"\n")
148                 #               direction = OppositeDirection(path[0])
149                 #               p = move(ally.x, ally.y, direction, 1)
150                 #               if self.PositionLegal(p[0], p[1], ally) == False:
151                 #                       path = None
152                 #               scaleFactor = 0.05 * scaleFactor
153                 #       else:
154                 #               direction = path[0]
155
156                 #       return [str(ally.x) + " " + str(ally.y) + " " + direction, desiredAction[1] * scaleFactor, path, ally, enemy]
157
158                 directions = {"RIGHT" : enemy.x - ally.x, "LEFT" : ally.x - enemy.x, "DOWN" : enemy.y - ally.y, "UP" : ally.y - enemy.y}
159                 if desiredAction[0] == "RETREAT":
160                         for key in directions.keys():
161                                 directions[key] = -directions[key]
162
163                 while direction == None:
164                         d = sorted(directions.items(), key = lambda e : e[1], reverse = True)
165                         p = move(ally.x, ally.y, d[0][0], 1)
166                         if self.PositionLegal(p[0], p[1]) and (self.board[p[0]][p[1]] == None or self.board[p[0]][p[1]] == enemy):
167                                 direction = d[0][0]
168                                 scaleFactor *= (1.0 - float(max(d[0][1], 0.0)) / 10.0)**2.0
169                         else:
170                                 del directions[d[0][0]]
171                                 if len(directions.keys()) <= 0:
172                                         break
173
174                 #if abs(enemy.x - ally.x) >= abs(enemy.y - ally.y):
175                 #       if enemy.x > ally.x:
176                 #               direction = "RIGHT"
177                 #       elif enemy.x < ally.x:
178                 #
179                 #else:
180                 #       if enemy.y > ally.y:
181                 #               direction = "DOWN"
182                 #       elif enemy.y < ally.y:
183                 #               direction = "UP"
184                 if direction == None:
185                         return ["NO_MOVE", 0, [], ally, enemy]
186                 return [str(ally.x) + " " + str(ally.y) + " " + direction, desiredAction[1], [direction], ally, enemy]                  
187                         
188
189         def DesiredAction(self, ally, enemyRank):
190                 if enemyRank == 'F':
191                         return ["ATTACK", 1.0]
192                 if ally.rank == '8' and enemyRank == 'B':
193                         return ["ATTACK", 0.9]
194                 if ally.rank == '1' and enemyRank == 's':
195                         return ["RETREAT", 0.9]
196                 if ally.rank == 's' and enemyRank == '1':
197                         return ["ATTACK", 0.6]
198                 if enemyRank == 'B':
199                         return ["RETREAT", 0.0]
200                 if ally.rank == enemyRank:
201                         return ["ATTACK", 0.1]
202                 if valuedRank(ally.rank) > valuedRank(enemyRank):
203                         return ["ATTACK", float(self.scoreTable[ranks.index(enemyRank)]) * (0.1 + 1.0/float(self.scoreTable[ranks.index(ally.rank)]))]
204                 else:
205                         return ["RETREAT", float(self.scoreTable[ranks.index(ally.rank)]) / 10.0]
206                 
207
208         def MakeMove(self):
209                 if len(self.units) < 20:
210                         self.maxdepth = 1
211                 bestMove = self.BestMove(self.maxdepth)
212
213
214                 if bestMove == None:
215                         #sys.stderr.write("Khaos makes random move!\n")
216                         return BasicAI.MakeMove(self)
217                 
218                 #sys.stderr.write("Board state before move: \n")
219                 #self.debugPrintBoard()
220                 
221                 sys.stderr.write("Best move is \"" + bestMove[0] + "\" with score " +  str(bestMove[1]) + " as part of path " +str(bestMove[2]) + " ...\n")
222                 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")
223                 sys.stdout.write(bestMove[0] + "\n")
224
225
226                 return True
227
228
229
230         def rankProbability(self, target, targetRank):
231
232                 if targetRank == '+' or targetRank == '?':
233                         return 0.0
234                 if target.rank == targetRank:
235                         return 1.0
236                 elif target.rank != '?':
237                         return 0.0
238
239                 total = 0.0
240                 for rank in ranks:
241                         if rank == '+' or rank == '?':
242                                 continue
243                         elif rank == 'F' or rank == 'B':
244                                 if target.lastMoved < 0:
245                                         total += self.hiddenEnemies[rank]
246                         else:
247                                 total += self.hiddenEnemies[rank]
248
249                 if total == 0.0:
250                         return 0.0
251                 return float(float(self.hiddenEnemies[targetRank]) / float(total))
252
253         def InterpretResult(self, string=None):
254                 if BasicAI.InterpretResult(self, string) == False:
255                         return False
256
257
258                 if self.maxdepth > 1:
259                         if self.lastMoved != None and self.lastMoved.colour == self.colour and self.lastMoved.alive == False:
260                                 self.units.sort(key = lambda e : valuedRank(e.rank), reverse = True)
261                         elif self.lastMoved != None and self.lastMoved.colour == oppositeColour(self.colour) and self.lastMoved.alive == True:
262                                 oldRank = self.lastMoved.rank
263                                 self.lastMoved.rank = '1'
264                                 self.enemyUnits.sort(key = lambda e : valuedRank(e.rank), reverse = True)
265                                 self.lastMoved.rank = oldRank
266                         
267                 
268                 return True                     
269                                 
270                 
271 if __name__ == "__main__":
272         if len(sys.argv) > 1:
273                 hunter = Hunter(sys.argv[1])
274         else:
275                 string = ""
276                 path = sys.argv[0].split('/')
277                 for i in range(0, len(path)-1):
278                         string += path[i] + "/"
279                 string += "default.scores"
280                 
281                 
282                 hunter = Hunter(string)
283         if hunter.Setup():
284                 while hunter.MoveCycle():
285                         pass
286

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