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 p = Piece(self.colour, self.rank, self.x, self.y)
69 p.lastMoved = self.lastMoved
70 p.beenRevealed = self.beenRevealed
72 for pos in self.positions:
73 p.positions.append((pos[0], pos[1]))
77 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
80 if ranks.count(self.rank) > 0:
81 return len(ranks) - 2 - ranks.index(self.rank)
87 if ranks.count(rank) > 0 and rank != '?':
88 return len(ranks) - 2 - ranks.index(rank)
96 BasicAI class to play a game of stratego
97 Implements the protocol correctly. Stores the state of the board in self.board
98 Only makes random moves.
99 Override method "MakeMove" for more complex moves
102 """ Constructs the BasicAI agent, and starts it playing the game """
103 #sys.stderr.write("BasicAI __init__ here...\n");
109 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}
110 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}
111 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}
112 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}
113 self.lastMoved = None
115 def LegalPosition(self, x, y):
116 return x >= 0 and y >= 0 and x < len(self.board) and y < len(self.board[x])
120 """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
121 #sys.stderr.write("BasicAI Setup here...\n");
122 setup = sys.stdin.readline().split(' ')
124 sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
125 self.colour = setup[0]
126 self.opponentName = setup[1]
127 self.width = int(setup[2])
128 self.height = int(setup[3])
129 for x in range(0, self.width):
130 self.board.append([])
131 for y in range(0, self.height):
132 self.board[x].append(None)
133 if self.colour == "RED":
134 print "FB8sB479B8\nBB31555583\n6724898974\n967B669999"
135 elif self.colour == "BLUE":
136 print "967B669999\n6724898974\nBB31555583\nFB8sB479B8"
140 #sys.stderr.write("BasicAI MakeMove here...\n");
141 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
144 return self.InterpretResult()
147 """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
148 #TODO: Over-ride this function in base classes with more complex move behaviour
150 #sys.stderr.write("BasicAI MakeMove here...\n")
151 #self.debugPrintBoard()
153 if len(self.units) <= 0:
156 index = random.randint(0, len(self.units)-1)
159 directions = ("UP", "DOWN", "LEFT", "RIGHT")
161 piece = self.units[index]
162 if piece != None and piece.mobile():
163 dirIndex = random.randint(0, len(directions)-1)
164 startDirIndex = dirIndex
167 #sys.stderr.write("Trying index " + str(dirIndex) + "\n")
168 p = move(piece.x, piece.y, directions[dirIndex],1)
169 if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
170 target = self.board[p[0]][p[1]]
171 if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):
172 print str(piece.x) + " " + str(piece.y) + " "+directions[dirIndex]
174 dirIndex = (dirIndex + 1) % len(directions)
175 if startDirIndex == dirIndex:
178 index = (index + 1) % len(self.units)
179 if startIndex == index:
185 """ Reads in the board.
186 On the very first turn, sets up the self.board structure
187 On subsequent turns, the board is simply read, but the self.board structure is not updated here.
189 #sys.stderr.write("BasicAI ReadBoard here...\n");
190 for y in range(0,self.height):
191 row = sys.stdin.readline().strip()
192 if len(row) < self.width:
193 sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
195 for x in range(0,self.width):
200 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
201 self.enemyUnits.append(self.board[x][y])
203 self.board[x][y] = Piece("NONE", '+', x, y)
205 self.board[x][y] = Piece(self.colour, row[x],x,y)
206 self.units.append(self.board[x][y])
212 def InterpretResult(self, string = None):
213 """ Interprets the result of a move, and updates the board.
214 The very first move is ignored.
215 On subsequent moves, the self.board structure is updated
217 #sys.stderr.write("BasicAI InterpretResult here...\n")
219 string = sys.stdin.readline()
221 result = string.split(' ')
223 #sys.stderr.write(" Read status line \"" + str(result) + "\"\n")
227 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
230 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
233 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
236 x = int(result[0].strip())
237 y = int(result[1].strip())
240 #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n")
242 direction = result[2].strip()
245 outcome = result[3].strip()
247 if is_integer(outcome):
248 multiplier = int(outcome)
249 outcome = result[4].strip()
252 p = move(x,y,direction, multiplier)
254 #Determine attacking piece
255 attacker = self.board[x][y]
256 self.board[x][y] = None
263 defender = self.board[p[0]][p[1]]
265 #Update attacker's position (Don't overwrite the board yet though)
269 attacker.positions.insert(0, (attacker.x, attacker.y))
272 #Determine ranks of pieces if supplied
273 if len(result) >= outIndex + 3:
276 attacker.rank = result[outIndex+1].strip()
277 if attacker.beenRevealed == False:
278 if attacker.colour == self.colour:
279 self.hiddenAllies[attacker.rank] -= 1
280 elif attacker.colour == oppositeColour(self.colour):
281 self.hiddenEnemies[attacker.rank] -= 1
282 attacker.beenRevealed = True
283 defender.rank = result[outIndex+2].strip()
284 if defender.beenRevealed == False:
285 if defender.colour == self.colour:
286 self.hiddenAllies[defender.rank] -= 1
287 elif defender.colour == oppositeColour(self.colour):
288 self.hiddenEnemies[defender.rank] -= 1
290 defender.beenRevealed = True
295 self.board[p[0]][p[1]] = attacker
297 elif outcome == "KILLS":
298 self.board[p[0]][p[1]] = attacker
300 if defender.colour == self.colour:
301 self.totalAllies[defender.rank] -= 1
302 self.units.remove(defender)
303 elif defender.colour == oppositeColour(self.colour):
304 self.totalEnemies[defender.rank] -= 1
305 self.enemyUnits.remove(defender)
307 elif outcome == "DIES":
308 if attacker.colour == self.colour:
309 self.totalAllies[attacker.rank] -= 1
310 self.units.remove(attacker)
311 elif attacker.colour == oppositeColour(self.colour):
312 self.totalEnemies[attacker.rank] -= 1
313 self.enemyUnits.remove(attacker)
315 elif outcome == "BOTHDIE":
316 self.board[p[0]][p[1]] = None
318 if defender.colour == self.colour:
319 self.totalAllies[defender.rank] -= 1
320 self.units.remove(defender)
321 elif defender.colour == oppositeColour(self.colour):
322 self.totalEnemies[defender.rank] -= 1
323 self.enemyUnits.remove(defender)
325 if attacker.colour == self.colour:
326 self.totalAllies[attacker.rank] -= 1
327 self.units.remove(attacker)
328 elif attacker.colour == oppositeColour(self.colour):
329 self.totalEnemies[attacker.rank] -= 1
330 self.enemyUnits.remove(attacker)
332 elif outcome == "FLAG":
333 #sys.stderr.write(" Game over!\n")
335 elif outcome == "ILLEGAL":
336 #sys.stderr.write(" Illegal move!\n")
339 #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n");
342 #sys.stderr.write(" Completed interpreting move!\n");
345 def debugPrintBoard(self):
346 """ For debug purposes only. Prints the board to stderr.
347 Does not indicate difference between allied and enemy pieces
348 Unknown (enemy) pieces are shown as '?'
350 for y in range(0, self.height):
351 for x in range(0, self.width):
352 if self.board[x][y] == None:
353 sys.stderr.write(".");
355 sys.stderr.write(str(self.board[x][y].rank));
356 sys.stderr.write("\n")
358 if __name__ == "__main__":
361 while basicAI.MoveCycle():