From: Sam Moore Date: Sat, 5 May 2012 13:15:06 +0000 (+0800) Subject: Add version 1.2 of Celsius X-Git-Url: https://git.ucc.asn.au/?p=progcomp2012.git;a=commitdiff_plain;h=97f72faa7d6fa846d7acaff49abd1a3b34bda6d6;hp=52489d9b9b2d7633f9b7caaed0ed1ea40972032b;ds=sidebyside Add version 1.2 of Celsius Celsius 1.2 by David Gow (in directory 'celsius') (The old one is under celsius1.1) I don't know what was happening before, but it didn't get added. --- diff --git a/agents/celsius/celsius.py b/agents/celsius/celsius.py index cc71b90..c3e6017 100755 --- a/agents/celsius/celsius.py +++ b/agents/celsius/celsius.py @@ -245,6 +245,8 @@ class SulixAI: 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.numEnemies = 6+1+1+2+3+4+4+4+5+8+1+1 + self.numStillEnemies = 6+1 self.lastMoved = None @@ -318,7 +320,7 @@ class SulixAI: if override == 1: - scare = 999 + scare = 998 elif override == -1: piece.turnCount = 0 print str(piece.x) + " " + str(piece.y) + " " + directions[dirIndex] @@ -485,6 +487,11 @@ class SulixAI: elif attacker.colour == oppositeColour(self.colour): self.totalEnemies[attacker.rank] -= 1 self.enemyUnits.remove(attacker) + if attacker.rank == 'B': + self.numStillEnemies -= 1 + #if self.numStillEnemies == 0: # There are no bombs left + for i in range(0,ranks['s']): + scaretable[i][ranks['?']] -= 2 elif outcome == "BOTHDIE": self.board[p[0]][p[1]] = None @@ -502,7 +509,6 @@ class SulixAI: 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 @@ -513,6 +519,9 @@ class SulixAI: #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n"); return False + + + #sys.stderr.write(" Completed interpreting move!\n"); return True diff --git a/agents/celsius/celsius.py~ b/agents/celsius/celsius.py~ new file mode 100755 index 0000000..4af3522 --- /dev/null +++ b/agents/celsius/celsius.py~ @@ -0,0 +1,548 @@ +#!/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. + + +import sys +import random + +ranks = ['B','1','2','3','4','5','6','7','8','9','s','F', '?', '!', '+'] + +""" +The scaretable lists how `scary' pieces are to each other; pieces will move +in the least scary direction. +""" + +# B 1 2 3 4 5 6 7 8 9 s F ? ! + +scaretable = [[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #B + [ 0, 0,-8,-8,-7,-6,-5,-4,-3,-2, 5,-9, 0,-7, 0], #1 + [ 0, 4, 0,-7,-6,-5,-4,-3,-2,-1,-2,-9,-3,-6, 0], #2 + [ 0, 4, 2, 0,-6,-5,-4,-3,-2,-1,-2,-9,-2,-5, 0], #3 + [ 0, 3, 2, 2, 0,-5,-4,-3,-2,-1,-2,-9,-1,-3, 0], #4 + [ 0, 3, 2, 2, 2, 0,-4,-3,-2,-1,-2,-9, 0,-2, 0], #5 + [ 0, 3, 2, 2, 2, 2, 0,-3,-2,-1,-2,-9, 1,-1, 0], #6 + [ 0, 3, 2, 2, 2, 2, 2, 0,-2,-1,-2,-9,-1, 0, 0], #7 + [-40, 3, 2, 2, 2, 2, 2, 2, 0,-2,-2,-9,-1, 1, 0], #8 + [ 0, 3, 2, 2, 2, 2, 2, 2, 2, 0,-2,-9,-2, 2, 0], #9 + [ 0, -5, 3, 3, 3, 3, 3, 3, 3, 3,-1,-9, 5, 3, 0], #s + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #F + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #? + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #! + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] #+ + +""" +The override table allows moves to be forced or prevented, thus ensuring +that sacrifices are not made. +""" +# B 1 2 3 4 5 6 7 8 9 s F ? ! + +overrides = [[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #B + [ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,-1,-1, 0, 0, 1], #1 + [ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #2 + [ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #3 + [ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #4 + [ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #5 + [ 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,-1, 0, 0, 1], #6 + [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,-1, 0, 0, 1], #7 + [-1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,-1, 0, 0, 1], #8 + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,-1, 0, 0, 1], #9 + [ 1,-1, 1, 1, 1, 1, 1, 1, 1, 1,-1,-1, 0, 0, 1], #s + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #F + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #? + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #! + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] #+ + + +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)] + + + + self.heatmap = [] + self.turnCount = 0 + + 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 scariness(self, other): + scare = scaretable[ranks.index(self.rank)][ranks.index(other.rank)] + if scare > 0: + scare = scare * 1 + return scare + + def getOverride(self, other): + return overrides[ranks.index(self.rank)][ranks.index(other.rank)] + + def getHeatmap(self, x,y,w,h): + if (x < 0) or (x >= w) or (y < 0) or (y >= h): + return 10 + else: + return self.heatmap[x][y] + + def validSquare(self, x, y, width, height, board): + if x < 0: + return False + if y < 0: + return False + if x >= width: + return False + if y >= height: + return False + if board[x][y] != None and board[x][y].colour == self.colour: + return False + if board[x][y] != None and board[x][y].rank == '#': + return False + return True + + def generateHeatmap(self, width, height, board): + self.heatmap = [] + newmap = [] + for x in range(0,width): + self.heatmap.append([]) + newmap.append([]) + for y in range(0,height): + self.heatmap[x].append(0) + newmap[x].append(0) + if board[x][y] == None: + self.heatmap[x][y] = 0 + continue + if board[x][y].colour == self.colour: + if board[x][y].rank == 'F': + self.heatmap[x][y] = -5 # + self.valuedRank() # Defend our flag + else: + self.heatmap[x][y] = self.scariness(board[x][y]) + + # Make pieces prefer to stay where they are + #self.heatmap[self.x][self.y] = -0.5 + + for i in range(0,min(30,len(self.positions))): + p = self.positions[len(self.positions)-1-i] + if board[p[0]][p[1]] != None: + self.heatmap[p[0]][p[1]] += 0.2 * ((50 - i)/50) + + + + for n in range(0,8): + for x in range(0,width): + for y in range(0,height): + if self.heatmap[x][y] != 0: + newmap[x][y] = self.heatmap[x][y] + continue + newmap[x][y] = 0 #self.heatmap[x][y] * 0.2 + if self.validSquare(x-1,y,width,height,board): + newmap[x][y] += self.heatmap[x-1][y] * 0.2 + else: + newmap[x][y] += 0 #self.heatmap[x][y] * 0.1 + if self.validSquare(x+1,y,width,height,board): + newmap[x][y] += self.heatmap[x+1][y] * 0.2 + else: + newmap[x][y] += 0 #self.heatmap[x][y] * 0.1 + if self.validSquare(x,y-1,width,height,board): + newmap[x][y] += self.heatmap[x][y-1] * 0.2 + else: + newmap[x][y] += 0 #self.heatmap[x][y] * 0.1 + if self.validSquare(x,y+1,width,height,board): + newmap[x][y] += self.heatmap[x][y+1] * 0.2 + else: + newmap[x][y] += 0 #self.heatmap[x][y] * 0.1 + self.heatmap = newmap + + def debugPrintHeat(self,w,h): + """ For debug purposes only. Prints the board to stderr. + Does not indicate difference between allied and enemy pieces + Unknown (enemy) pieces are shown as '?' + """ + sys.stderr.write("Pos: " + str(self.x) + ", " + str(self.y) + " -- rank: " + str(self.rank) + "\n") + for y in range(0, h): + for x in range(0, w): + if (self.heatmap[x][y] - self.heatmap[self.x][self.y] > 0.0): + sys.stderr.write("O") + elif (self.heatmap[x][y] - self.heatmap[self.x][self.y] == 0.0): + sys.stderr.write("X") + elif (self.heatmap[x][y] - self.heatmap[self.x][self.y] < 0.0): + sys.stderr.write(".") + else: + sys.stderr.write(" ") + sys.stderr.write("\n") + sys.stderr.write("\n") + + + + +def valuedRank(rank): + if ranks.count(rank) > 0: + return len(ranks) - 2 - ranks.index(rank) + else: + return 0 + + + +class SulixAI: + """ + 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.total_turns = 0 + + 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.numEnemies = 6+1+1+2+3+4+4+4+5+8+1+1 + self.numStillEnemies = 6+1 + self.lastMoved = None + + + + 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 "FB8sB979B8\nBB99555583\n6724898974\nB314676699" + elif self.colour == "BLUE": + print "B314676699\n6724898974\nBB99555583\nFB8sB979B8" + 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("Sulix's AI makes a move...\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") + bestdir = 0 + bestScare = 999 + bestpiece = None + while True: + piece = self.units[index] + + if piece != None and piece.mobile(): + dirIndex = random.randint(0, len(directions)-1) + startDirIndex = dirIndex + piece.generateHeatmap(self.width, self.height, self.board) + currentScary = piece.getHeatmap(piece.x, piece.y, self.width, self.height) * 0 + piece.turnCount*0 #Perhaps just look for the best move + piece.turnCount = piece.turnCount + 1 + 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"): + scare = piece.getHeatmap(p[0], p[1],self.width, self.height) - currentScary + override = 0 + if target != None: + override = piece.getOverride(target) + + if (self.total_turns % 250 < 15) and (self.total_turns > 250): + scare += random.randint(0, 5) + + + if override == 1: + scare = 998 + elif override == -1: + piece.turnCount = 0 + print str(piece.x) + " " + str(piece.y) + " " + directions[dirIndex] + return True + + + + + if scare < bestScare: + bestdir = dirIndex + bestScare = scare + bestpiece = piece + + dirIndex = (dirIndex + 1) % len(directions) + if startDirIndex == dirIndex: + break + + + index = (index + 1) % len(self.units) + if startIndex == index: + if bestScare != 999: + bestpiece.turnCount = 0 + print str(bestpiece.x) + " " + str(bestpiece.y) + " "+directions[bestdir] +# bestpiece.debugPrintHeat(self.width, self.height) + return True + else: + print "SURRENDER" + 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): + """ 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 + """ + + self.total_turns = self.total_turns + 1 + + #sys.stderr.write("BasicAI InterpretResult here...\n") + result = sys.stdin.readline().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()) + + + # The piece moved! It's not a bomb + if self.board[x][y].rank == '?': + self.board[x][y].rank = '!' + #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) + + # It's a scout! I saw it move. + if multiplier > 1: + self.board[x][y].rank = '9' + + #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) + if attacker.rank == 'B': + self.numStillEnemies -= 1 + #if self.numStillEnemies == 0: # There are no bombs left + for i in range(0,ranks['s']): + scaretable[i][ranks['?']] -= 5 + + 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__": + sulixAI = SulixAI() + if sulixAI.Setup(): + while sulixAI.MoveCycle(): + pass +