[PATCH] Remove arguments, patch vixen, add hunter AI
[progcomp2012.git] / agents / hunter / hunter.path.py
diff --git a/agents/hunter/hunter.path.py b/agents/hunter/hunter.path.py
new file mode 100755 (executable)
index 0000000..0a488f1
--- /dev/null
@@ -0,0 +1,320 @@
+#!/usr/bin/python -u
+
+#NOTE: The -u option is required for unbuffered stdin/stdout.
+#      If stdin/stdout are buffered, the manager program will not recieve any messages and assume that the agent has timed out.
+
+'''
+ khaos.py - A sample Stratego AI for the UCC Programming Competition 2012
+
+ The name describes the state of this file :S
+
+ Written in python, the slithery language 
+
+ author Sam Moore (matches) [SZM]
+ website http://matches.ucc.asn.au/stratego
+ git git.ucc.asn.au/progcomp2012.git
+'''
+
+import os
+
+from basic_python import *
+from path import *
+
+def OppositeDirection(direction):
+       if direction == "UP":
+               return "DOWN"
+       elif direction == "DOWN":
+               return "UP"
+       elif direction == "LEFT":
+               return "RIGHT"
+       elif direction == "RIGHT":
+               return "LEFT"
+       else:
+               assert(False)
+       return "ERROR"
+
+class Hunter(BasicAI):
+       " Python based AI of DEATH "
+       def __init__(self, scoresFilename=None):
+               if scoresFilename == None:
+                       scoresFilename = "default.scores"
+               BasicAI.__init__(self)
+               
+               scoresFile = open(scoresFilename, "r")
+               self.scoreTable = []
+               for i in scoresFile.readline().strip().split(' '):
+                       self.scoreTable.append(float(i))
+               scoresFile.close()
+
+               self.maxdepth = 1
+               self.recursiveConsider = {"allies" : 2, "enemies" : 2}
+               self.paths = {}
+               
+
+       def PositionLegal(self, x, y, unit = None):
+               if x >= 0 and x < len(self.board) and y >= 0 and y < len(self.board[x]):
+                       if unit == None:
+                               return True
+                       else:
+                               return self.board[x][y] == None or self.board[x][y].colour == oppositeColour(unit.colour)
+               else:
+                       return False
+
+       def BestMove(self, maxdepth = 1):
+
+               moveList = []
+
+               
+               if maxdepth < self.maxdepth:
+                       #sys.stderr.write("Recurse!\n")
+                       considerAllies = self.recursiveConsider["allies"]
+                       considerEnemies = self.recursiveConsider["enemies"]
+               else:
+                       considerAllies = len(self.units)+1
+                       considerEnemies = len(self.enemyUnits)+1
+
+               for enemy in self.enemyUnits[0:considerEnemies]:
+                       for ally in self.units[0:considerAllies]:
+                               moveList.append(self.DesiredMove(ally, enemy))
+
+               for desiredMove in moveList:
+                       if desiredMove[0] == "NO_MOVE" or desiredMove[2] == None:
+                               desiredMove[1] = -2.0
+
+               
+                       
+
+               if maxdepth > 1:
+                       for desiredMove in moveList:
+                               if desiredMove[2] == None or desiredMove[1] < 0.0:
+                                       continue
+                               p = move(desiredMove[3].x, desiredMove[3].y, desiredMove[2][0], 1)
+                               if self.board[p[0]][p[1]] == None:
+                                       x = desiredMove[3].x
+                                       y = desiredMove[3].y
+                                       result = desiredMove[0] + " OK"
+                                       self.InterpretResult(result)
+                                       bestRecurse = self.BestMove(maxdepth-1)
+                                       if bestRecurse != None:
+                                               desiredMove[1] += bestRecurse[1]# / float(max(1.0, maxdepth))
+                                       self.board[desiredMove[3].x][desiredMove[3].y] = None
+                                       self.board[x][y] = desiredMove[3]
+                                       desiredMove[3].x = x
+                                       desiredMove[3].y = y
+
+               
+
+               
+               if len(moveList) <= 0:
+                       return None
+               moveList.sort(key = lambda e : e[1], reverse = True)                    
+               return moveList[0]
+                               
+
+       def GetPath(self, ally, enemy):
+               #Attempts to do the minimum required work to reconstruct a path
+               return PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)
+               if (ally in self.paths.keys()) == False: 
+                       self.paths.update({ally : {}})  
+                       #sys.stderr.write("Update keys are " + str(self.paths.keys()) + "\n")   
+               #sys.stderr.write("Keys are " + str(self.paths.keys()) + "\n")  
+
+               if (enemy in self.paths[ally].keys()) == False: #No path exists; compute a new one
+                       path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)                          
+                       if path != False:
+                               self.paths[ally].update({enemy : [path, (ally.x, ally.y), (enemy.x, enemy.y)]})
+                       return path
+
+               oldPath = self.paths[ally][enemy]
+               if oldPath[1][0] != ally.x or oldPath[1][1] != ally.y or oldPath[2][0] != enemy.x or oldPath[2][1] != enemy.y:
+                       #The pieces involved have moved. Recompute the path 
+                       path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)
+                       if path != False:
+                               self.paths[ally][enemy] = [path, (ally.x, ally.y), (enemy.x, enemy.y)]
+                       return path
+
+               if len(oldPath[0]) > 1:
+                       #The pieces involved haven't moved, check to see if the path is blocked
+                       p = move(ally.x, ally.y, oldPath[0][0], 1) #Look forward one move
+                       if self.PositionLegal(p[0], p[1]) and self.board[p[0]][p[1]] != None: #If the position is blocked...
+                               path = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board)  #Compute new path
+                               if path != False:
+                                       self.paths[ally][enemy] = [path, (ally.x, ally.y), (enemy.x, enemy.y)]
+                               return path
+               return False
+               
+       def DesiredMove(self, ally, enemy):
+               """ Determine desired move of allied piece, towards or away from enemy, with score value """
+               scaleFactor = 1.0
+               if ally.rank == 'F' or ally.rank == 'B':
+                       return ["NO_MOVE", 0, None, ally, enemy]
+               
+               actionScores = {"ATTACK" : 0, "RETREAT" : 0}
+               if enemy.rank == '?':
+                       for i in range(0, len(ranks)):
+                               prob = self.rankProbability(enemy, ranks[i])
+                               if prob > 0:
+                                       desiredAction = self.DesiredAction(ally, ranks[i])
+                                       actionScores[desiredAction[0]] += prob* (desiredAction[1] / 2.0)
+                       if len(enemy.positions) <= 1 and ally.rank != '8':
+                               scaleFactor *= (1.0 - float(valuedRank(ally.rank)) / float(valuedRank('1')))**2.0
+                       elif len(enemy.positions) > 1 and ally.rank == '8':
+                               scaleFactor *= 0.05
+                       #elif len(enemy.positions) > 1:
+                       #       scaleFactor *= (1.0 - float(valuedRank(ally.rank)) / float(valuedRank('1')))**0.25
+                       #       scaleFactor = max(0.05, scaleFactor)
+               else:
+                       desiredAction = self.DesiredAction(ally, enemy.rank)
+                       actionScores[desiredAction[0]] += desiredAction[1]
+               
+
+               desiredAction = sorted(actionScores.items(), key = lambda e : e[1], reverse = True)[0]
+               direction = None
+               path = self.GetPath(ally, enemy)
+
+               
+               if path != False and len(path) > 0:
+                       if desiredAction[0] == "RETREAT":
+                               #sys.stderr.write("Recommend retreat! "+ally.rank + " from " + enemy.rank+"\n")
+                               direction = OppositeDirection(path[0])
+                               p = move(ally.x, ally.y, direction, 1)
+                               if self.PositionLegal(p[0], p[1], ally) == False:
+                                       path = None
+                               scaleFactor = 0.05 * scaleFactor
+                       else:
+                               direction = path[0]
+                       if desiredAction[1] > 0.0 and path != None:
+                               scaleFactor = scaleFactor / float(len(path))
+                       return [str(ally.x) + " " + str(ally.y) + " " + direction, desiredAction[1] * scaleFactor, path, ally, enemy]
+
+               #directions = {"RIGHT" : enemy.x - ally.x, "LEFT" : ally.x - enemy.x, "DOWN" : enemy.y - ally.y, "UP" : ally.y - enemy.y}
+               #if desiredAction[0] == "RETREAT":
+               #       for key in directions.keys():
+               #               directions[key] = -directions[key]
+
+               #while direction == None:
+               #       d = sorted(directions.items(), key = lambda e : e[1], reverse = True)
+               #       p = move(ally.x, ally.y, d[0][0], 1)
+               #       if self.PositionLegal(p[0], p[1]) and (self.board[p[0]][p[1]] == None or self.board[p[0]][p[1]] == enemy):
+               #               direction = d[0][0]
+               #               scaleFactor *= (1.0 - float(max(d[0][1], 0.0)) / 10.0)**2.0
+               #       else:
+               #               del directions[d[0][0]]
+               #               if len(directions.keys()) <= 0:
+               #                       break
+
+               if abs(enemy.x - ally.x) >= abs(enemy.y - ally.y):
+                       if enemy.x > ally.x:
+                               direction = "RIGHT"
+                       elif enemy.x < ally.x:
+                               direction = "LEFT"              
+               else:
+                       if enemy.y > ally.y:
+                               direction = "DOWN"
+                       elif enemy.y < ally.y:
+                               direction = "UP"
+               if direction == None:
+                       return ["NO_MOVE", 0, [], ally, enemy]
+               return [str(ally.x) + " " + str(ally.y) + " " + direction, desiredAction[1], None, ally, enemy]                 
+                       
+
+       def DesiredAction(self, ally, enemyRank):
+               if enemyRank == 'F':
+                       return ["ATTACK", 1.0]
+               if ally.rank == '8' and enemyRank == 'B':
+                       return ["ATTACK", 0.9]
+               if ally.rank == '1' and enemyRank == 's':
+                       return ["RETREAT", 0.9]
+               if ally.rank == 's' and enemyRank == '1':
+                       return ["ATTACK", 0.6]
+               if enemyRank == 'B':
+                       return ["RETREAT", 0.0]
+               if ally.rank == enemyRank:
+                       return ["ATTACK", 0.1]
+               if valuedRank(ally.rank) > valuedRank(enemyRank):
+                       return ["ATTACK", float(self.scoreTable[ranks.index(enemyRank)]) * (0.1 + 1.0/float(self.scoreTable[ranks.index(ally.rank)]))]
+               else:
+                       return ["RETREAT", float(self.scoreTable[ranks.index(ally.rank)]) / 10.0]
+               
+
+       def MakeMove(self):
+               if len(self.units) < 20:
+                       self.maxdepth = 1
+               bestMove = self.BestMove(self.maxdepth)
+
+
+               if bestMove == None:
+                       #sys.stderr.write("Khaos makes random move!\n")
+                       return BasicAI.MakeMove(self)
+               
+               #sys.stderr.write("Board state before move: \n")
+               #self.debugPrintBoard()
+               
+               #sys.stderr.write("Best move is \"" + bestMove[0] + "\" with score " +  str(bestMove[1]) + " as part of path " +str(bestMove[2]) + " ...\n")
+               #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")
+               
+               sys.stdout.write(bestMove[0] + "\n")
+               #self.paths[bestMove[3]][bestMove[4]].pop(0)
+
+               return True
+
+
+
+       def rankProbability(self, target, targetRank):
+
+               if targetRank == '+' or targetRank == '?':
+                       return 0.0
+               if target.rank == targetRank:
+                       return 1.0
+               elif target.rank != '?':
+                       return 0.0
+
+               total = 0.0
+               for rank in ranks:
+                       if rank == '+' or rank == '?':
+                               continue
+                       elif rank == 'F' or rank == 'B':
+                               if target.lastMoved < 0:
+                                       total += self.hiddenEnemies[rank]
+                       else:
+                               total += self.hiddenEnemies[rank]
+
+               if total == 0.0:
+                       return 0.0
+               return float(float(self.hiddenEnemies[targetRank]) / float(total))
+
+       def InterpretResult(self, string=None):
+               if BasicAI.InterpretResult(self, string) == False:
+                       return False
+
+
+               if self.maxdepth > 1:
+                       if self.lastMoved != None and self.lastMoved.colour == self.colour and self.lastMoved.alive == False:
+                               self.units.sort(key = lambda e : valuedRank(e.rank), reverse = True)
+                       elif self.lastMoved != None and self.lastMoved.colour == oppositeColour(self.colour) and self.lastMoved.alive == True:
+                               oldRank = self.lastMoved.rank
+                               self.lastMoved.rank = '1'
+                               self.enemyUnits.sort(key = lambda e : valuedRank(e.rank), reverse = True)
+                               self.lastMoved.rank = oldRank
+                       
+               
+               return True                     
+                               
+               
+if __name__ == "__main__":
+       if len(sys.argv) > 1:
+               hunter = Hunter(sys.argv[1])
+       else:
+               string = ""
+               path = sys.argv[0].split('/')
+               for i in range(0, len(path)-1):
+                       string += path[i] + "/"
+               string += "default.scores"
+               
+               
+               hunter = Hunter(string)
+       if hunter.Setup():
+               while hunter.MoveCycle():
+                       pass
+

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