From: Sam Moore Date: Sat, 28 Apr 2012 06:27:01 +0000 (+0800) Subject: [PATCH] Remove arguments, patch vixen, add hunter AI X-Git-Url: https://git.ucc.asn.au/?p=progcomp2012.git;a=commitdiff_plain;h=ac335e7c423d067effae82cc80db518f896271b9 [PATCH] Remove arguments, patch vixen, add hunter AI The arguments stuff has been causing errors for everyone. No one uses arguments for their AI, so I removed it. sulix now beats vixen, so I have patched it to not make illegal moves. hunter uses the same sort of idea as vixen, but seems to do slightly better. Enjoy [SZM] --- diff --git a/agents/asmodeus/path.py b/agents/asmodeus/path.py index 3f08979..3a4527e 100644 --- a/agents/asmodeus/path.py +++ b/agents/asmodeus/path.py @@ -32,7 +32,7 @@ class PathFinder: up = (start[0], start[1]-1) down = (start[0], start[1]+1) choices = [left, right, up, down] - choices.sort(key = lambda e : random.randint(0,5)) + choices.sort(key = lambda e : (e[0] - end[0])**2.0 + (e[1] - end[1])**2.0 ) options = [] for point in choices: option = [point, self.pathFind(point,end,board)] diff --git a/agents/hunter/basic_python.py b/agents/hunter/basic_python.py new file mode 100755 index 0000000..b94b857 --- /dev/null +++ b/agents/hunter/basic_python.py @@ -0,0 +1,363 @@ +#!/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. + +""" + basic_python.py - A sample Stratego AI for the UCC Programming Competition 2012 + + Written in python, the slithery language + Simply makes random moves, as long as possible + + author Sam Moore (matches) [SZM] + website http://matches.ucc.asn.au/stratego + email progcomp@ucc.asn.au or matches@ucc.asn.au + git git.ucc.asn.au/progcomp2012.git +""" + +import sys +import random + +ranks = ['B','1','2','3','4','5','6','7','8','9','s','F', '?', '+'] + +def is_integer(s): + """ Using exceptions for this feels... wrong...""" + try: + int(s) + return True + except ValueError: + return False + +def move(x, y, direction, multiplier): + """ Moves point (x,y) in direction, returns a pair """ + if direction == "UP": + return (x,y-multiplier) + elif direction == "DOWN": + return (x,y+multiplier) + elif direction == "LEFT": + return (x-multiplier, y) + elif direction == "RIGHT": + return (x+multiplier, y) + return (x,y) + + + +def oppositeColour(colour): + """ Returns the opposite colour to that given """ + if colour == "RED": + return "BLUE" + elif colour == "BLUE": + return "RED" + else: + return "NONE" + +class Piece: + """ Class representing a piece + Pieces have colour, rank and co-ordinates + """ + def __init__(self, colour, rank, x, y): + self.colour = colour + self.rank = rank + self.x = x + self.y = y + self.lastMoved = -1 + self.beenRevealed = False + self.positions = [(x, y)] + + def copy(self): + p = Piece(self.colour, self.rank, self.x, self.y) + p.lastMoved = self.lastMoved + p.beenRevealed = self.beenRevealed + p.positions = [] + for pos in self.positions: + p.positions.append((pos[0], pos[1])) + return p + + def mobile(self): + return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+' + + def valuedRank(self): + if ranks.count(self.rank) > 0: + return len(ranks) - 2 - ranks.index(self.rank) + else: + return 0 + + +def valuedRank(rank): + if ranks.count(rank) > 0 and rank != '?': + return len(ranks) - 2 - ranks.index(rank) + else: + return 0 + + + +class BasicAI: + """ + BasicAI class to play a game of stratego + Implements the protocol correctly. Stores the state of the board in self.board + Only makes random moves. + Override method "MakeMove" for more complex moves + """ + def __init__(self): + """ Constructs the BasicAI agent, and starts it playing the game """ + #sys.stderr.write("BasicAI __init__ here...\n"); + self.turn = 0 + self.board = [] + self.units = [] + self.enemyUnits = [] + + self.totalAllies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1} + self.totalEnemies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1} + self.hiddenEnemies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1} + self.hiddenAllies = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1} + self.lastMoved = None + + def LegalPosition(self, x, y): + return x >= 0 and y >= 0 and x < len(self.board) and y < len(self.board[x]) + + + def Setup(self): + """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """ + #sys.stderr.write("BasicAI Setup here...\n"); + setup = sys.stdin.readline().split(' ') + if len(setup) != 4: + sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n") + self.colour = setup[0] + self.opponentName = setup[1] + self.width = int(setup[2]) + self.height = int(setup[3]) + for x in range(0, self.width): + self.board.append([]) + for y in range(0, self.height): + self.board[x].append(None) + if self.colour == "RED": + print "FB8sB479B8\nBB31555583\n6724898974\n967B669999" + elif self.colour == "BLUE": + print "967B669999\n6724898974\nBB31555583\nFB8sB479B8" + return True + + def MoveCycle(self): + #sys.stderr.write("BasicAI MakeMove here...\n"); + if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False: + return False + self.turn += 1 + return self.InterpretResult() + + def MakeMove(self): + """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """ + #TODO: Over-ride this function in base classes with more complex move behaviour + + #sys.stderr.write("BasicAI MakeMove here...\n") + #self.debugPrintBoard() + + if len(self.units) <= 0: + return False + + index = random.randint(0, len(self.units)-1) + startIndex = index + + directions = ("UP", "DOWN", "LEFT", "RIGHT") + while True: + piece = self.units[index] + if piece != None and piece.mobile(): + dirIndex = random.randint(0, len(directions)-1) + startDirIndex = dirIndex + + while True: + #sys.stderr.write("Trying index " + str(dirIndex) + "\n") + p = move(piece.x, piece.y, directions[dirIndex],1) + if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height: + target = self.board[p[0]][p[1]] + if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"): + print str(piece.x) + " " + str(piece.y) + " "+directions[dirIndex] + return True + dirIndex = (dirIndex + 1) % len(directions) + if startDirIndex == dirIndex: + break + + index = (index + 1) % len(self.units) + if startIndex == index: + print "NO_MOVE" + return True + + + def ReadBoard(self): + """ Reads in the board. + On the very first turn, sets up the self.board structure + On subsequent turns, the board is simply read, but the self.board structure is not updated here. + """ + #sys.stderr.write("BasicAI ReadBoard here...\n"); + for y in range(0,self.height): + row = sys.stdin.readline().strip() + if len(row) < self.width: + sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n") + return False + for x in range(0,self.width): + if self.turn == 0: + if row[x] == '.': + pass + elif row[x] == '#': + self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y) + self.enemyUnits.append(self.board[x][y]) + elif row[x] == '+': + self.board[x][y] = Piece("NONE", '+', x, y) + else: + self.board[x][y] = Piece(self.colour, row[x],x,y) + self.units.append(self.board[x][y]) + else: + pass + return True + + + def InterpretResult(self, string = None): + """ Interprets the result of a move, and updates the board. + The very first move is ignored. + On subsequent moves, the self.board structure is updated + """ + #sys.stderr.write("BasicAI InterpretResult here...\n") + if string == None: + string = sys.stdin.readline() + + result = string.split(' ') + + #sys.stderr.write(" Read status line \"" + str(result) + "\"\n") + if self.turn == 0: + return True + + if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to! + return False + + if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything + return True + + if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case + return False + + x = int(result[0].strip()) + y = int(result[1].strip()) + + + #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n") + + direction = result[2].strip() + + multiplier = 1 + outcome = result[3].strip() + outIndex = 3 + if is_integer(outcome): + multiplier = int(outcome) + outcome = result[4].strip() + outIndex = 4 + + p = move(x,y,direction, multiplier) + + #Determine attacking piece + attacker = self.board[x][y] + self.board[x][y] = None + + if attacker == None: + return False + + lastMoved = attacker + + defender = self.board[p[0]][p[1]] + + #Update attacker's position (Don't overwrite the board yet though) + + attacker.x = p[0] + attacker.y = p[1] + attacker.positions.insert(0, (attacker.x, attacker.y)) + + + #Determine ranks of pieces if supplied + if len(result) >= outIndex + 3: + if defender == None: + return False + attacker.rank = result[outIndex+1].strip() + if attacker.beenRevealed == False: + if attacker.colour == self.colour: + self.hiddenAllies[attacker.rank] -= 1 + elif attacker.colour == oppositeColour(self.colour): + self.hiddenEnemies[attacker.rank] -= 1 + attacker.beenRevealed = True + defender.rank = result[outIndex+2].strip() + if defender.beenRevealed == False: + if defender.colour == self.colour: + self.hiddenAllies[defender.rank] -= 1 + elif defender.colour == oppositeColour(self.colour): + self.hiddenEnemies[defender.rank] -= 1 + + defender.beenRevealed = True + + + + if outcome == "OK": + self.board[p[0]][p[1]] = attacker + + elif outcome == "KILLS": + self.board[p[0]][p[1]] = attacker + + if defender.colour == self.colour: + self.totalAllies[defender.rank] -= 1 + self.units.remove(defender) + elif defender.colour == oppositeColour(self.colour): + self.totalEnemies[defender.rank] -= 1 + self.enemyUnits.remove(defender) + + elif outcome == "DIES": + if attacker.colour == self.colour: + self.totalAllies[attacker.rank] -= 1 + self.units.remove(attacker) + elif attacker.colour == oppositeColour(self.colour): + self.totalEnemies[attacker.rank] -= 1 + self.enemyUnits.remove(attacker) + + elif outcome == "BOTHDIE": + self.board[p[0]][p[1]] = None + + if defender.colour == self.colour: + self.totalAllies[defender.rank] -= 1 + self.units.remove(defender) + elif defender.colour == oppositeColour(self.colour): + self.totalEnemies[defender.rank] -= 1 + self.enemyUnits.remove(defender) + + if attacker.colour == self.colour: + self.totalAllies[attacker.rank] -= 1 + self.units.remove(attacker) + elif attacker.colour == oppositeColour(self.colour): + self.totalEnemies[attacker.rank] -= 1 + self.enemyUnits.remove(attacker) + + elif outcome == "FLAG": + #sys.stderr.write(" Game over!\n") + return False + elif outcome == "ILLEGAL": + #sys.stderr.write(" Illegal move!\n") + return False + else: + #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n"); + return False + + #sys.stderr.write(" Completed interpreting move!\n"); + return True + + def debugPrintBoard(self): + """ For debug purposes only. Prints the board to stderr. + Does not indicate difference between allied and enemy pieces + Unknown (enemy) pieces are shown as '?' + """ + for y in range(0, self.height): + for x in range(0, self.width): + if self.board[x][y] == None: + sys.stderr.write("."); + else: + sys.stderr.write(str(self.board[x][y].rank)); + sys.stderr.write("\n") + +if __name__ == "__main__": + basicAI = BasicAI() + if basicAI.Setup(): + while basicAI.MoveCycle(): + pass + diff --git a/agents/hunter/default.scores b/agents/hunter/default.scores new file mode 100644 index 0000000..a3cc91e --- /dev/null +++ b/agents/hunter/default.scores @@ -0,0 +1 @@ +9 10 9 8 7 6 5 4 9 2 5 9 diff --git a/agents/hunter/hunter.path.py b/agents/hunter/hunter.path.py new file mode 100755 index 0000000..0a488f1 --- /dev/null +++ b/agents/hunter/hunter.path.py @@ -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 + email progcomp@ucc.asn.au or matches@ucc.asn.au + 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 + diff --git a/agents/hunter/hunter.py b/agents/hunter/hunter.py new file mode 100755 index 0000000..2f591bf --- /dev/null +++ b/agents/hunter/hunter.py @@ -0,0 +1,286 @@ +#!/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 + email progcomp@ucc.asn.au or matches@ucc.asn.au + 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" : 5, "enemies" : 5} + + + + 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 + + for desiredMove in moveList: + if desiredMove[1] > 0.0: + desiredMove[1] = desiredMove[1] / float(len(desiredMove[2])) + + if len(moveList) <= 0: + return None + moveList.sort(key = lambda e : e[1], reverse = True) + return moveList[0] + + + 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 = PathFinder().pathFind((ally.x, ally.y), (enemy.x, enemy.y), self.board) + + #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] + + # 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: + # + #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], [direction], 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") + + + 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 + diff --git a/agents/hunter/path.py b/agents/hunter/path.py new file mode 120000 index 0000000..1d82284 --- /dev/null +++ b/agents/hunter/path.py @@ -0,0 +1 @@ +../asmodeus/path.py \ No newline at end of file diff --git a/agents/hunter/path.pyc b/agents/hunter/path.pyc new file mode 100644 index 0000000..9a0db59 Binary files /dev/null and b/agents/hunter/path.pyc differ diff --git a/agents/vixen/vixen.py b/agents/vixen/vixen.py index e4dee3f..669df83 100755 --- a/agents/vixen/vixen.py +++ b/agents/vixen/vixen.py @@ -44,20 +44,25 @@ class Vixen(BasicAI): if unit.mobile() == False: continue - scores = {"LEFT":0, "RIGHT":0, "UP":0, "DOWN":0} + scores = {"LEFT":None, "RIGHT":None, "UP":None, "DOWN":None} - for target in self.enemyUnits: if target == unit: continue path = PathFinder().pathFind((unit.x, unit.y), (target.x, target.y), self.board) if path == False or len(path) == 0: continue - #moveList.append({"unit":unit, "direction":path[0], "score":self.CalculateScore(unit, target, path)}) + if scores[path[0]] == None: + scores[path[0]] = 0 + scores[path[0]] += self.CalculateScore(unit, target, path) - bestScore = sorted(scores.items(), key = lambda e : e[1], reverse=True)[0] - if bestScore[1] > -100.0: + for d in scores.keys(): + if scores[d] == None: + del scores[d] + + if len(scores.items()) > 0: + bestScore = sorted(scores.items(), key = lambda e : e[1], reverse=True)[0] moveList.append({"unit":unit, "direction":bestScore[0], "score":bestScore[1]}) diff --git a/judge/manager/program.cpp b/judge/manager/program.cpp index 5ea1591..c936de3 100644 --- a/judge/manager/program.cpp +++ b/judge/manager/program.cpp @@ -32,7 +32,7 @@ Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0 { - + /* vector args; if (executablePath[0] != '"') args.push_back((char*)executablePath); @@ -70,6 +70,7 @@ Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0 for (unsigned int i=0; i < args.size(); ++i) arguments[i] = args[i]; } + */ //See if file exists and is executable... if (access(executablePath, X_OK) != 0) { @@ -100,7 +101,8 @@ Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0 if (access(executablePath, X_OK) == 0) //Check we STILL have permissions to start the file { - execv(executablePath,arguments); ///Replace process with desired executable + execl(executablePath, executablePath, (char*)(NULL)); ///Replace process with desired executable + //execv(executablePath,arguments); ///Replace process with desired executable } perror("execv error:\n"); fprintf(stderr, "Program::Program - Could not run program \"%s\"!\n", executablePath);