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.
7 khaos.py - A sample Stratego AI for the UCC Programming Competition 2012
9 The name describes the state of this file :S
11 Written in python, the slithery language
13 author Sam Moore (matches) [SZM]
14 website http://matches.ucc.asn.au/stratego
16 git git.ucc.asn.au/progcomp2012.git
21 from basic_python import *
24 def OppositeDirection(direction):
27 elif direction == "DOWN":
29 elif direction == "LEFT":
31 elif direction == "RIGHT":
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)
44 scoresFile = open(scoresFilename, "r")
46 for i in scoresFile.readline().strip().split(' '):
47 self.scoreTable.append(float(i))
51 self.recursiveConsider = {"allies" : 5, "enemies" : 5}
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]):
60 return self.board[x][y] == None or self.board[x][y].colour == oppositeColour(unit.colour)
64 def BestMove(self, maxdepth = 1):
69 if maxdepth < self.maxdepth:
70 #sys.stderr.write("Recurse!\n")
71 considerAllies = self.recursiveConsider["allies"]
72 considerEnemies = self.recursiveConsider["enemies"]
74 considerAllies = len(self.units)+1
75 considerEnemies = len(self.enemyUnits)+1
77 for enemy in self.enemyUnits[0:considerEnemies]:
78 for ally in self.units[0:considerAllies]:
79 moveList.append(self.DesiredMove(ally, enemy))
81 for desiredMove in moveList:
82 if desiredMove[0] == "NO_MOVE" or desiredMove[2] == None:
89 for desiredMove in moveList:
90 if desiredMove[2] == None or desiredMove[1] < 0.0:
92 p = move(desiredMove[3].x, desiredMove[3].y, desiredMove[2][0], 1)
93 if self.board[p[0]][p[1]] == None:
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]
106 for desiredMove in moveList:
107 if desiredMove[1] > 0.0:
108 desiredMove[1] = desiredMove[1] / float(len(desiredMove[2]))
110 if len(moveList) <= 0:
112 moveList.sort(key = lambda e : e[1], reverse = True)
116 def DesiredMove(self, ally, enemy):
117 """ Determine desired move of allied piece, towards or away from enemy, with score value """
119 if ally.rank == 'F' or ally.rank == 'B':
120 return ["NO_MOVE", 0, None, ally, enemy]
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])
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':
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)
137 desiredAction = self.DesiredAction(ally, enemy.rank)
138 actionScores[desiredAction[0]] += desiredAction[1]
141 desiredAction = sorted(actionScores.items(), key = lambda e : e[1], reverse = True)[0]
143 #path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)
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:
152 # scaleFactor = 0.05 * scaleFactor
154 # direction = path[0]
156 # return [str(ally.x) + " " + str(ally.y) + " " + direction, desiredAction[1] * scaleFactor, path, ally, enemy]
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]
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):
168 scaleFactor *= (1.0 - float(max(d[0][1], 0.0)) / 10.0)**2.0
170 del directions[d[0][0]]
171 if len(directions.keys()) <= 0:
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:
180 # if enemy.y > ally.y:
182 # elif enemy.y < ally.y:
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]
189 def DesiredAction(self, ally, enemyRank):
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]
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)]))]
205 return ["RETREAT", float(self.scoreTable[ranks.index(ally.rank)]) / 10.0]
209 if len(self.units) < 20:
211 bestMove = self.BestMove(self.maxdepth)
215 #sys.stderr.write("Khaos makes random move!\n")
216 return BasicAI.MakeMove(self)
218 #sys.stderr.write("Board state before move: \n")
219 #self.debugPrintBoard()
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")
230 def rankProbability(self, target, targetRank):
232 if targetRank == '+' or targetRank == '?':
234 if target.rank == targetRank:
236 elif target.rank != '?':
241 if rank == '+' or rank == '?':
243 elif rank == 'F' or rank == 'B':
244 if target.lastMoved < 0:
245 total += self.hiddenEnemies[rank]
247 total += self.hiddenEnemies[rank]
251 return float(float(self.hiddenEnemies[targetRank]) / float(total))
253 def InterpretResult(self, string=None):
254 if BasicAI.InterpretResult(self, string) == False:
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
271 if __name__ == "__main__":
272 if len(sys.argv) > 1:
273 hunter = Hunter(sys.argv[1])
276 path = sys.argv[0].split('/')
277 for i in range(0, len(path)-1):
278 string += path[i] + "/"
279 string += "default.scores"
282 hunter = Hunter(string)
284 while hunter.MoveCycle():