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.
10 ranks = ['B','1','2','3','4','5','6','7','8','9','s','F', '?', '!', '+']
13 The scaretable lists how `scary' pieces are to each other; pieces will move
14 in the least scary direction.
17 # B 1 2 3 4 5 6 7 8 9 s F ? ! +
18 scaretable = [[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #B
19 [ 0, 0,-8,-8,-7,-6,-5,-4,-3,-2, 5,-9, 0,-7, 0], #1
20 [ 0, 4, 0,-7,-6,-5,-4,-3,-2,-1,-2,-9,-3,-6, 0], #2
21 [ 0, 4, 2, 0,-6,-5,-4,-3,-2,-1,-2,-9,-2,-5, 0], #3
22 [ 0, 3, 2, 2, 0,-5,-4,-3,-2,-1,-2,-9,-1,-3, 0], #4
23 [ 0, 3, 2, 2, 2, 0,-4,-3,-2,-1,-2,-9, 0,-2, 0], #5
24 [ 0, 3, 2, 2, 2, 2, 0,-3,-2,-1,-2,-9, 1,-1, 0], #6
25 [ 0, 3, 2, 2, 2, 2, 2, 0,-2,-1,-2,-9,-1, 0, 0], #7
26 [-40, 3, 2, 2, 2, 2, 2, 2, 0,-2,-2,-9,-1, 1, 0], #8
27 [ 0, 3, 2, 2, 2, 2, 2, 2, 2, 0,-2,-9,-2, 2, 0], #9
28 [ 0, -5, 3, 3, 3, 3, 3, 3, 3, 3,-1,-9, 5, 3, 0], #s
29 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #F
30 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #?
31 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #!
32 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] #+
35 The override table allows moves to be forced or prevented, thus ensuring
36 that sacrifices are not made.
38 # B 1 2 3 4 5 6 7 8 9 s F ? ! +
39 overrides = [[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #B
40 [ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,-1,-1, 0, 0, 1], #1
41 [ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #2
42 [ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #3
43 [ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #4
44 [ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1], #5
45 [ 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,-1, 0, 0, 1], #6
46 [ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,-1, 0, 0, 1], #7
47 [-1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,-1, 0, 0, 1], #8
48 [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,-1, 0, 0, 1], #9
49 [ 1,-1, 1, 1, 1, 1, 1, 1, 1, 1,-1,-1, 0, 0, 1], #s
50 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #F
51 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #?
52 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #!
53 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] #+
57 """ Using exceptions for this feels... wrong..."""
64 def move(x, y, direction, multiplier):
65 """ Moves point (x,y) in direction, returns a pair """
67 return (x,y-multiplier)
68 elif direction == "DOWN":
69 return (x,y+multiplier)
70 elif direction == "LEFT":
71 return (x-multiplier, y)
72 elif direction == "RIGHT":
73 return (x+multiplier, y)
78 def oppositeColour(colour):
79 """ Returns the opposite colour to that given """
82 elif colour == "BLUE":
88 """ Class representing a piece
89 Pieces have colour, rank and co-ordinates
91 def __init__(self, colour, rank, x, y):
97 self.beenRevealed = False
98 self.positions = [(x, y)]
106 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
108 def valuedRank(self):
109 if ranks.count(self.rank) > 0:
110 return len(ranks) - 2 - ranks.index(self.rank)
114 def scariness(self, other):
115 scare = scaretable[ranks.index(self.rank)][ranks.index(other.rank)]
120 def getOverride(self, other):
121 return overrides[ranks.index(self.rank)][ranks.index(other.rank)]
123 def getHeatmap(self, x,y,w,h):
124 if (x < 0) or (x >= w) or (y < 0) or (y >= h):
127 return self.heatmap[x][y]
129 def validSquare(self, x, y, width, height, board):
138 if board[x][y] != None and board[x][y].colour == self.colour:
140 if board[x][y] != None and board[x][y].rank == '#':
144 def generateHeatmap(self, width, height, board):
147 for x in range(0,width):
148 self.heatmap.append([])
150 for y in range(0,height):
151 self.heatmap[x].append(0)
153 if board[x][y] == None:
154 self.heatmap[x][y] = 0
156 if board[x][y].colour == self.colour:
157 if board[x][y].rank == 'F':
158 self.heatmap[x][y] = -5 # + self.valuedRank() # Defend our flag
160 self.heatmap[x][y] = self.scariness(board[x][y])
162 # Make pieces prefer to stay where they are
163 #self.heatmap[self.x][self.y] = -0.5
165 for i in range(0,min(30,len(self.positions))):
166 p = self.positions[len(self.positions)-1-i]
167 if board[p[0]][p[1]] != None:
168 self.heatmap[p[0]][p[1]] += 0.2 * ((50 - i)/50)
173 for x in range(0,width):
174 for y in range(0,height):
175 if self.heatmap[x][y] != 0:
176 newmap[x][y] = self.heatmap[x][y]
178 newmap[x][y] = 0 #self.heatmap[x][y] * 0.2
179 if self.validSquare(x-1,y,width,height,board):
180 newmap[x][y] += self.heatmap[x-1][y] * 0.2
182 newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
183 if self.validSquare(x+1,y,width,height,board):
184 newmap[x][y] += self.heatmap[x+1][y] * 0.2
186 newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
187 if self.validSquare(x,y-1,width,height,board):
188 newmap[x][y] += self.heatmap[x][y-1] * 0.2
190 newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
191 if self.validSquare(x,y+1,width,height,board):
192 newmap[x][y] += self.heatmap[x][y+1] * 0.2
194 newmap[x][y] += 0 #self.heatmap[x][y] * 0.1
195 self.heatmap = newmap
197 def debugPrintHeat(self,w,h):
198 """ For debug purposes only. Prints the board to stderr.
199 Does not indicate difference between allied and enemy pieces
200 Unknown (enemy) pieces are shown as '?'
202 sys.stderr.write("Pos: " + str(self.x) + ", " + str(self.y) + " -- rank: " + str(self.rank) + "\n")
203 for y in range(0, h):
204 for x in range(0, w):
205 if (self.heatmap[x][y] - self.heatmap[self.x][self.y] > 0.0):
206 sys.stderr.write("O")
207 elif (self.heatmap[x][y] - self.heatmap[self.x][self.y] == 0.0):
208 sys.stderr.write("X")
209 elif (self.heatmap[x][y] - self.heatmap[self.x][self.y] < 0.0):
210 sys.stderr.write(".")
212 sys.stderr.write(" ")
213 sys.stderr.write("\n")
214 sys.stderr.write("\n")
219 def valuedRank(rank):
220 if ranks.count(rank) > 0:
221 return len(ranks) - 2 - ranks.index(rank)
229 BasicAI class to play a game of stratego
230 Implements the protocol correctly. Stores the state of the board in self.board
231 Only makes random moves.
232 Override method "MakeMove" for more complex moves
235 """ Constructs the BasicAI agent, and starts it playing the game """
236 #sys.stderr.write("BasicAI __init__ here...\n");
244 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}
245 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}
246 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}
247 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}
248 self.lastMoved = None
253 """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
254 #sys.stderr.write("BasicAI Setup here...\n");
255 setup = sys.stdin.readline().split(' ')
257 sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
258 self.colour = setup[0]
259 self.opponentName = setup[1]
260 self.width = int(setup[2])
261 self.height = int(setup[3])
262 for x in range(0, self.width):
263 self.board.append([])
264 for y in range(0, self.height):
265 self.board[x].append(None)
266 if self.colour == "RED":
267 print "FB8sB979B8\nBB99555583\n6724898974\nB314676699"
268 elif self.colour == "BLUE":
269 print "B314676699\n6724898974\nBB99555583\nFB8sB979B8"
273 #sys.stderr.write("BasicAI MakeMove here...\n");
274 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
277 return self.InterpretResult()
280 """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
281 #TODO: Over-ride this function in base classes with more complex move behaviour
283 #sys.stderr.write("Sulix's AI makes a move...\n")
284 #self.debugPrintBoard()
286 if len(self.units) <= 0:
289 index = random.randint(0, len(self.units)-1)
292 directions = ("UP", "DOWN", "LEFT", "RIGHT")
297 piece = self.units[index]
299 if piece != None and piece.mobile():
300 dirIndex = random.randint(0, len(directions)-1)
301 startDirIndex = dirIndex
302 piece.generateHeatmap(self.width, self.height, self.board)
303 currentScary = piece.getHeatmap(piece.x, piece.y, self.width, self.height) * 0 + piece.turnCount*0 #Perhaps just look for the best move
304 piece.turnCount = piece.turnCount + 1
306 #sys.stderr.write("Trying index " + str(dirIndex) + "\n")
307 p = move(piece.x, piece.y, directions[dirIndex],1)
308 if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
309 target = self.board[p[0]][p[1]]
310 if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):
311 scare = piece.getHeatmap(p[0], p[1],self.width, self.height) - currentScary
314 override = piece.getOverride(target)
316 if (self.total_turns % 250 < 15) and (self.total_turns > 250):
317 scare += random.randint(0, 5)
324 print str(piece.x) + " " + str(piece.y) + " " + directions[dirIndex]
330 if scare < bestScare:
335 dirIndex = (dirIndex + 1) % len(directions)
336 if startDirIndex == dirIndex:
340 index = (index + 1) % len(self.units)
341 if startIndex == index:
343 bestpiece.turnCount = 0
344 print str(bestpiece.x) + " " + str(bestpiece.y) + " "+directions[bestdir]
345 # bestpiece.debugPrintHeat(self.width, self.height)
353 """ Reads in the board.
354 On the very first turn, sets up the self.board structure
355 On subsequent turns, the board is simply read, but the self.board structure is not updated here.
357 #sys.stderr.write("BasicAI ReadBoard here...\n");
358 for y in range(0,self.height):
359 row = sys.stdin.readline().strip()
360 if len(row) < self.width:
361 sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
363 for x in range(0,self.width):
368 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
369 self.enemyUnits.append(self.board[x][y])
371 self.board[x][y] = Piece("NONE", '+', x, y)
373 self.board[x][y] = Piece(self.colour, row[x],x,y)
374 self.units.append(self.board[x][y])
380 def InterpretResult(self):
381 """ Interprets the result of a move, and updates the board.
382 The very first move is ignored.
383 On subsequent moves, the self.board structure is updated
386 self.total_turns = self.total_turns + 1
388 #sys.stderr.write("BasicAI InterpretResult here...\n")
389 result = sys.stdin.readline().split(' ')
390 #sys.stderr.write(" Read status line \"" + str(result) + "\"\n")
394 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
397 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
400 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
403 x = int(result[0].strip())
404 y = int(result[1].strip())
407 # The piece moved! It's not a bomb
408 if self.board[x][y].rank == '?':
409 self.board[x][y].rank = '!'
410 #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n")
412 direction = result[2].strip()
415 outcome = result[3].strip()
417 if is_integer(outcome):
418 multiplier = int(outcome)
419 outcome = result[4].strip()
422 p = move(x,y,direction, multiplier)
424 # It's a scout! I saw it move.
426 self.board[x][y].rank = '9'
428 #Determine attacking piece
429 attacker = self.board[x][y]
430 self.board[x][y] = None
437 defender = self.board[p[0]][p[1]]
439 #Update attacker's position (Don't overwrite the board yet though)
443 attacker.positions.insert(0, (attacker.x, attacker.y))
446 #Determine ranks of pieces if supplied
447 if len(result) >= outIndex + 3:
450 attacker.rank = result[outIndex+1].strip()
451 if attacker.beenRevealed == False:
452 if attacker.colour == self.colour:
453 self.hiddenAllies[attacker.rank] -= 1
454 elif attacker.colour == oppositeColour(self.colour):
455 self.hiddenEnemies[attacker.rank] -= 1
456 attacker.beenRevealed = True
457 defender.rank = result[outIndex+2].strip()
458 if defender.beenRevealed == False:
459 if defender.colour == self.colour:
460 self.hiddenAllies[defender.rank] -= 1
461 elif defender.colour == oppositeColour(self.colour):
462 self.hiddenEnemies[defender.rank] -= 1
464 defender.beenRevealed = True
469 self.board[p[0]][p[1]] = attacker
471 elif outcome == "KILLS":
472 self.board[p[0]][p[1]] = attacker
474 if defender.colour == self.colour:
475 self.totalAllies[defender.rank] -= 1
476 self.units.remove(defender)
477 elif defender.colour == oppositeColour(self.colour):
478 self.totalEnemies[defender.rank] -= 1
479 self.enemyUnits.remove(defender)
481 elif outcome == "DIES":
482 if attacker.colour == self.colour:
483 self.totalAllies[attacker.rank] -= 1
484 self.units.remove(attacker)
485 elif attacker.colour == oppositeColour(self.colour):
486 self.totalEnemies[attacker.rank] -= 1
487 self.enemyUnits.remove(attacker)
489 elif outcome == "BOTHDIE":
490 self.board[p[0]][p[1]] = None
492 if defender.colour == self.colour:
493 self.totalAllies[defender.rank] -= 1
494 self.units.remove(defender)
495 elif defender.colour == oppositeColour(self.colour):
496 self.totalEnemies[defender.rank] -= 1
497 self.enemyUnits.remove(defender)
499 if attacker.colour == self.colour:
500 self.totalAllies[attacker.rank] -= 1
501 self.units.remove(attacker)
502 elif attacker.colour == oppositeColour(self.colour):
503 self.totalEnemies[attacker.rank] -= 1
504 self.enemyUnits.remove(attacker)
506 elif outcome == "FLAG":
507 #sys.stderr.write(" Game over!\n")
509 elif outcome == "ILLEGAL":
510 #sys.stderr.write(" Illegal move!\n")
513 #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n");
516 #sys.stderr.write(" Completed interpreting move!\n");
521 def debugPrintBoard(self):
522 """ For debug purposes only. Prints the board to stderr.
523 Does not indicate difference between allied and enemy pieces
524 Unknown (enemy) pieces are shown as '?'
526 for y in range(0, self.height):
527 for x in range(0, self.width):
528 if self.board[x][y] == None:
529 sys.stderr.write(".");
531 sys.stderr.write(str(self.board[x][y].rank));
532 sys.stderr.write("\n")
534 if __name__ == "__main__":
537 while sulixAI.MoveCycle():