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.
7 basic_python.py - A sample Stratego AI for the UCC Programming Competition 2012
9 Written in python, the slithery language
10 Simply makes random moves, as long as possible
12 author Sam Moore (matches) [SZM]
13 website http://matches.ucc.asn.au/stratego
15 git git.ucc.asn.au/progcomp2012.git
21 ranks = ['B','1','2','3','4','5','6','7','8','9','s','F', '?', '+']
24 """ Using exceptions for this feels... wrong..."""
31 def move(x, y, direction, multiplier):
32 """ Moves point (x,y) in direction, returns a pair """
34 return (x,y-multiplier)
35 elif direction == "DOWN":
36 return (x,y+multiplier)
37 elif direction == "LEFT":
38 return (x-multiplier, y)
39 elif direction == "RIGHT":
40 return (x+multiplier, y)
45 def oppositeColour(colour):
46 """ Returns the opposite colour to that given """
49 elif colour == "BLUE":
55 """ Class representing a piece
56 Pieces have colour, rank and co-ordinates
58 def __init__(self, colour, rank, x, y):
64 self.beenRevealed = False
65 self.positions = [(x, y)]
68 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
71 if ranks.count(self.rank) > 0:
72 return len(ranks) - 2 - ranks.index(self.rank)
78 if ranks.count(rank) > 0:
79 return len(ranks) - 2 - ranks.index(rank)
87 BasicAI class to play a game of stratego
88 Implements the protocol correctly. Stores the state of the board in self.board
89 Only makes random moves.
90 Override method "MakeMove" for more complex moves
93 """ Constructs the BasicAI agent, and starts it playing the game """
94 #sys.stderr.write("BasicAI __init__ here...\n");
100 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}
101 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}
102 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}
103 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}
104 self.lastMoved = None
109 """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
110 #sys.stderr.write("BasicAI Setup here...\n");
111 setup = sys.stdin.readline().split(' ')
113 sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
114 self.colour = setup[0]
115 self.opponentName = setup[1]
116 self.width = int(setup[2])
117 self.height = int(setup[3])
118 for x in range(0, self.width):
119 self.board.append([])
120 for y in range(0, self.height):
121 self.board[x].append(None)
122 if self.colour == "RED":
123 print "FB8sB479B8\nBB31555583\n6724898974\n967B669999"
124 elif self.colour == "BLUE":
125 print "967B669999\n6724898974\nBB31555583\nFB8sB479B8"
129 #sys.stderr.write("BasicAI MakeMove here...\n");
130 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
133 return self.InterpretResult()
136 """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
137 #TODO: Over-ride this function in base classes with more complex move behaviour
139 #sys.stderr.write("BasicAI MakeMove here...\n")
140 #self.debugPrintBoard()
142 if len(self.units) <= 0:
145 index = random.randint(0, len(self.units)-1)
148 directions = ("UP", "DOWN", "LEFT", "RIGHT")
150 piece = self.units[index]
151 if piece != None and piece.mobile():
152 dirIndex = random.randint(0, len(directions)-1)
153 startDirIndex = dirIndex
156 #sys.stderr.write("Trying index " + str(dirIndex) + "\n")
157 p = move(piece.x, piece.y, directions[dirIndex],1)
158 if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
159 target = self.board[p[0]][p[1]]
160 if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):
161 print str(piece.x) + " " + str(piece.y) + " "+directions[dirIndex]
163 dirIndex = (dirIndex + 1) % len(directions)
164 if startDirIndex == dirIndex:
167 index = (index + 1) % len(self.units)
168 if startIndex == index:
174 """ Reads in the board.
175 On the very first turn, sets up the self.board structure
176 On subsequent turns, the board is simply read, but the self.board structure is not updated here.
178 #sys.stderr.write("BasicAI ReadBoard here...\n");
179 for y in range(0,self.height):
180 row = sys.stdin.readline().strip()
181 if len(row) < self.width:
182 sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
184 for x in range(0,self.width):
189 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
190 self.enemyUnits.append(self.board[x][y])
192 self.board[x][y] = Piece("NONE", '+', x, y)
194 self.board[x][y] = Piece(self.colour, row[x],x,y)
195 self.units.append(self.board[x][y])
201 def InterpretResult(self):
202 """ Interprets the result of a move, and updates the board.
203 The very first move is ignored.
204 On subsequent moves, the self.board structure is updated
206 #sys.stderr.write("BasicAI InterpretResult here...\n")
207 result = sys.stdin.readline().split(' ')
208 #sys.stderr.write(" Read status line \"" + str(result) + "\"\n")
212 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
215 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
218 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
221 x = int(result[0].strip())
222 y = int(result[1].strip())
225 #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n")
227 direction = result[2].strip()
230 outcome = result[3].strip()
232 if is_integer(outcome):
233 multiplier = int(outcome)
234 outcome = result[4].strip()
237 p = move(x,y,direction, multiplier)
239 #Determine attacking piece
240 attacker = self.board[x][y]
241 self.board[x][y] = None
248 defender = self.board[p[0]][p[1]]
250 #Update attacker's position (Don't overwrite the board yet though)
254 attacker.positions.insert(0, (attacker.x, attacker.y))
257 #Determine ranks of pieces if supplied
258 if len(result) >= outIndex + 3:
261 attacker.rank = result[outIndex+1].strip()
262 if attacker.beenRevealed == False:
263 if attacker.colour == self.colour:
264 self.hiddenAllies[attacker.rank] -= 1
265 elif attacker.colour == oppositeColour(self.colour):
266 self.hiddenEnemies[attacker.rank] -= 1
267 attacker.beenRevealed = True
268 defender.rank = result[outIndex+2].strip()
269 if defender.beenRevealed == False:
270 if defender.colour == self.colour:
271 self.hiddenAllies[defender.rank] -= 1
272 elif defender.colour == oppositeColour(self.colour):
273 self.hiddenEnemies[defender.rank] -= 1
275 defender.beenRevealed = True
280 self.board[p[0]][p[1]] = attacker
282 elif outcome == "KILLS":
283 self.board[p[0]][p[1]] = attacker
285 if defender.colour == self.colour:
286 self.totalAllies[defender.rank] -= 1
287 self.units.remove(defender)
288 elif defender.colour == oppositeColour(self.colour):
289 self.totalEnemies[defender.rank] -= 1
290 self.enemyUnits.remove(defender)
292 elif outcome == "DIES":
293 if attacker.colour == self.colour:
294 self.totalAllies[attacker.rank] -= 1
295 self.units.remove(attacker)
296 elif attacker.colour == oppositeColour(self.colour):
297 self.totalEnemies[attacker.rank] -= 1
298 self.enemyUnits.remove(attacker)
300 elif outcome == "BOTHDIE":
301 self.board[p[0]][p[1]] = None
303 if defender.colour == self.colour:
304 self.totalAllies[defender.rank] -= 1
305 self.units.remove(defender)
306 elif defender.colour == oppositeColour(self.colour):
307 self.totalEnemies[defender.rank] -= 1
308 self.enemyUnits.remove(defender)
310 if attacker.colour == self.colour:
311 self.totalAllies[attacker.rank] -= 1
312 self.units.remove(attacker)
313 elif attacker.colour == oppositeColour(self.colour):
314 self.totalEnemies[attacker.rank] -= 1
315 self.enemyUnits.remove(attacker)
317 elif outcome == "FLAG":
318 #sys.stderr.write(" Game over!\n")
320 elif outcome == "ILLEGAL":
321 #sys.stderr.write(" Illegal move!\n")
324 #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n");
327 #sys.stderr.write(" Completed interpreting move!\n");
330 def debugPrintBoard(self):
331 """ For debug purposes only. Prints the board to stderr.
332 Does not indicate difference between allied and enemy pieces
333 Unknown (enemy) pieces are shown as '?'
335 for y in range(0, self.height):
336 for x in range(0, self.width):
337 if self.board[x][y] == None:
338 sys.stderr.write(".");
340 sys.stderr.write(str(self.board[x][y].rank));
341 sys.stderr.write("\n")
343 if __name__ == "__main__":
346 while basicAI.MoveCycle():