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.numEnemies = 6+1+1+2+3+4+4+4+5+8+1+1
249 self.numStillEnemies = 6+1
250 self.lastMoved = None
255 """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
256 #sys.stderr.write("BasicAI Setup here...\n");
257 setup = sys.stdin.readline().split(' ')
259 sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
260 self.colour = setup[0]
261 self.opponentName = setup[1]
262 self.width = int(setup[2])
263 self.height = int(setup[3])
264 for x in range(0, self.width):
265 self.board.append([])
266 for y in range(0, self.height):
267 self.board[x].append(None)
268 if self.colour == "RED":
269 print "FB8sB979B8\nBB99555583\n6724898974\nB314676699"
270 elif self.colour == "BLUE":
271 print "B314676699\n6724898974\nBB99555583\nFB8sB979B8"
275 #sys.stderr.write("BasicAI MakeMove here...\n");
276 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
279 return self.InterpretResult()
282 """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
283 #TODO: Over-ride this function in base classes with more complex move behaviour
285 #sys.stderr.write("Sulix's AI makes a move...\n")
286 #self.debugPrintBoard()
288 if len(self.units) <= 0:
291 index = random.randint(0, len(self.units)-1)
294 directions = ("UP", "DOWN", "LEFT", "RIGHT")
299 piece = self.units[index]
301 if piece != None and piece.mobile():
302 dirIndex = random.randint(0, len(directions)-1)
303 startDirIndex = dirIndex
304 piece.generateHeatmap(self.width, self.height, self.board)
305 currentScary = piece.getHeatmap(piece.x, piece.y, self.width, self.height) * 0 + piece.turnCount*0 #Perhaps just look for the best move
306 piece.turnCount = piece.turnCount + 1
308 #sys.stderr.write("Trying index " + str(dirIndex) + "\n")
309 p = move(piece.x, piece.y, directions[dirIndex],1)
310 if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
311 target = self.board[p[0]][p[1]]
312 if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):
313 scare = piece.getHeatmap(p[0], p[1],self.width, self.height) - currentScary
316 override = piece.getOverride(target)
318 if (self.total_turns % 250 < 15) and (self.total_turns > 250):
319 scare += random.randint(0, 5)
326 print str(piece.x) + " " + str(piece.y) + " " + directions[dirIndex]
332 if scare < bestScare:
337 dirIndex = (dirIndex + 1) % len(directions)
338 if startDirIndex == dirIndex:
342 index = (index + 1) % len(self.units)
343 if startIndex == index:
345 bestpiece.turnCount = 0
346 print str(bestpiece.x) + " " + str(bestpiece.y) + " "+directions[bestdir]
347 # bestpiece.debugPrintHeat(self.width, self.height)
355 """ Reads in the board.
356 On the very first turn, sets up the self.board structure
357 On subsequent turns, the board is simply read, but the self.board structure is not updated here.
359 #sys.stderr.write("BasicAI ReadBoard here...\n");
360 for y in range(0,self.height):
361 row = sys.stdin.readline().strip()
362 if len(row) < self.width:
363 sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
365 for x in range(0,self.width):
370 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
371 self.enemyUnits.append(self.board[x][y])
373 self.board[x][y] = Piece("NONE", '+', x, y)
375 self.board[x][y] = Piece(self.colour, row[x],x,y)
376 self.units.append(self.board[x][y])
382 def InterpretResult(self):
383 """ Interprets the result of a move, and updates the board.
384 The very first move is ignored.
385 On subsequent moves, the self.board structure is updated
388 self.total_turns = self.total_turns + 1
390 #sys.stderr.write("BasicAI InterpretResult here...\n")
391 result = sys.stdin.readline().split(' ')
392 #sys.stderr.write(" Read status line \"" + str(result) + "\"\n")
396 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
399 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
402 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
405 x = int(result[0].strip())
406 y = int(result[1].strip())
409 # The piece moved! It's not a bomb
410 if self.board[x][y].rank == '?':
411 self.board[x][y].rank = '!'
412 #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n")
414 direction = result[2].strip()
417 outcome = result[3].strip()
419 if is_integer(outcome):
420 multiplier = int(outcome)
421 outcome = result[4].strip()
424 p = move(x,y,direction, multiplier)
426 # It's a scout! I saw it move.
428 self.board[x][y].rank = '9'
430 #Determine attacking piece
431 attacker = self.board[x][y]
432 self.board[x][y] = None
439 defender = self.board[p[0]][p[1]]
441 #Update attacker's position (Don't overwrite the board yet though)
445 attacker.positions.insert(0, (attacker.x, attacker.y))
448 #Determine ranks of pieces if supplied
449 if len(result) >= outIndex + 3:
452 attacker.rank = result[outIndex+1].strip()
453 if attacker.beenRevealed == False:
454 if attacker.colour == self.colour:
455 self.hiddenAllies[attacker.rank] -= 1
456 elif attacker.colour == oppositeColour(self.colour):
457 self.hiddenEnemies[attacker.rank] -= 1
458 attacker.beenRevealed = True
459 defender.rank = result[outIndex+2].strip()
460 if defender.beenRevealed == False:
461 if defender.colour == self.colour:
462 self.hiddenAllies[defender.rank] -= 1
463 elif defender.colour == oppositeColour(self.colour):
464 self.hiddenEnemies[defender.rank] -= 1
466 defender.beenRevealed = True
471 self.board[p[0]][p[1]] = attacker
473 elif outcome == "KILLS":
474 self.board[p[0]][p[1]] = attacker
476 if defender.colour == self.colour:
477 self.totalAllies[defender.rank] -= 1
478 self.units.remove(defender)
479 elif defender.colour == oppositeColour(self.colour):
480 self.totalEnemies[defender.rank] -= 1
481 self.enemyUnits.remove(defender)
483 elif outcome == "DIES":
484 if attacker.colour == self.colour:
485 self.totalAllies[attacker.rank] -= 1
486 self.units.remove(attacker)
487 elif attacker.colour == oppositeColour(self.colour):
488 self.totalEnemies[attacker.rank] -= 1
489 self.enemyUnits.remove(attacker)
490 if attacker.rank == 'B':
491 self.numStillEnemies -= 1
492 #if self.numStillEnemies == 0: # There are no bombs left
493 for i in range(0,ranks['s']):
494 scaretable[i][ranks['?']] -= 2
496 elif outcome == "BOTHDIE":
497 self.board[p[0]][p[1]] = None
499 if defender.colour == self.colour:
500 self.totalAllies[defender.rank] -= 1
501 self.units.remove(defender)
502 elif defender.colour == oppositeColour(self.colour):
503 self.totalEnemies[defender.rank] -= 1
504 self.enemyUnits.remove(defender)
506 if attacker.colour == self.colour:
507 self.totalAllies[attacker.rank] -= 1
508 self.units.remove(attacker)
509 elif attacker.colour == oppositeColour(self.colour):
510 self.totalEnemies[attacker.rank] -= 1
511 self.enemyUnits.remove(attacker)
512 elif outcome == "FLAG":
513 #sys.stderr.write(" Game over!\n")
515 elif outcome == "ILLEGAL":
516 #sys.stderr.write(" Illegal move!\n")
519 #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n");
525 #sys.stderr.write(" Completed interpreting move!\n");
530 def debugPrintBoard(self):
531 """ For debug purposes only. Prints the board to stderr.
532 Does not indicate difference between allied and enemy pieces
533 Unknown (enemy) pieces are shown as '?'
535 for y in range(0, self.height):
536 for x in range(0, self.width):
537 if self.board[x][y] == None:
538 sys.stderr.write(".");
540 sys.stderr.write(str(self.board[x][y].rank));
541 sys.stderr.write("\n")
543 if __name__ == "__main__":
546 while sulixAI.MoveCycle():