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):
66 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
69 if ranks.count(self.rank) > 0:
70 return len(ranks) - 2 - ranks.index(self.rank)
79 BasicAI class to play a game of stratego
80 Implements the protocol correctly. Stores the state of the board in self.board
81 Only makes random moves.
82 Override method "MakeMove" for more complex moves
85 """ Constructs the BasicAI agent, and starts it playing the game """
86 #sys.stderr.write("BasicAI __init__ here...\n");
95 """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
96 #sys.stderr.write("BasicAI Setup here...\n");
97 setup = sys.stdin.readline().split(' ')
99 sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
100 self.colour = setup[0]
101 self.opponentName = setup[1]
102 self.width = int(setup[2])
103 self.height = int(setup[3])
104 for x in range(0, self.width):
105 self.board.append([])
106 for y in range(0, self.height):
107 self.board[x].append(None)
108 if self.colour == "RED":
109 print "FB8sB479B8\nBB31555583\n6724898974\n967B669999"
110 elif self.colour == "BLUE":
111 print "967B669999\n6724898974\nBB31555583\nFB8sB479B8"
115 #sys.stderr.write("BasicAI MakeMove here...\n");
116 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
119 return self.InterpretResult()
122 """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
123 #TODO: Over-ride this function in base classes with more complex move behaviour
125 #sys.stderr.write("BasicAI MakeMove here...\n")
126 #self.debugPrintBoard()
128 if len(self.units) <= 0:
131 index = random.randint(0, len(self.units)-1)
134 directions = ("UP", "DOWN", "LEFT", "RIGHT")
136 piece = self.units[index]
137 if piece != None and piece.mobile():
138 dirIndex = random.randint(0, len(directions)-1)
139 startDirIndex = dirIndex
142 #sys.stderr.write("Trying index " + str(dirIndex) + "\n")
143 p = move(piece.x, piece.y, directions[dirIndex])
144 if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
145 target = self.board[p[0]][p[1]]
146 if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):
147 print str(piece.x) + " " + str(piece.y) + " "+directions[dirIndex]
149 dirIndex = (dirIndex + 1) % len(directions)
150 if startDirIndex == dirIndex:
153 index = (index + 1) % len(self.units)
154 if startIndex == index:
160 """ Reads in the board.
161 On the very first turn, sets up the self.board structure
162 On subsequent turns, the board is simply read, but the self.board structure is not updated here.
164 #sys.stderr.write("BasicAI ReadBoard here...\n");
165 for y in range(0,self.height):
166 row = sys.stdin.readline().strip()
167 if len(row) < self.width:
168 sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
170 for x in range(0,self.width):
175 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
176 self.enemyUnits.append(self.board[x][y])
178 self.board[x][y] = Piece("NONE", '+', x, y)
180 self.board[x][y] = Piece(self.colour, row[x],x,y)
181 self.units.append(self.board[x][y])
187 def InterpretResult(self):
188 """ Interprets the result of a move, and updates the board.
189 The very first move is ignored.
190 On subsequent moves, the self.board structure is updated
192 #sys.stderr.write("BasicAI InterpretResult here...\n")
193 result = sys.stdin.readline().split(' ')
194 #sys.stderr.write(" Read status line \"" + str(result) + "\"\n")
198 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
201 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
204 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
207 x = int(result[0].strip())
208 y = int(result[1].strip())
211 #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n")
213 direction = result[2].strip()
216 outcome = result[3].strip()
218 if is_integer(outcome):
219 multiplier = int(outcome)
220 outcome = result[4].strip()
223 p = move(x,y,direction, multiplier)
228 self.board[p[0]][p[1]] = self.board[x][y]
229 self.board[x][y].x = p[0]
230 self.board[x][y].y = p[1]
232 self.board[x][y] = None
233 elif outcome == "KILLS":
234 if self.board[p[0]][p[1]] == None:
237 if self.board[p[0]][p[1]].colour == self.colour:
238 self.units.remove(self.board[p[0]][p[1]])
239 elif self.board[p[0]][p[1]].colour == oppositeColour(self.colour):
240 self.enemyUnits.remove(self.board[p[0]][p[1]])
242 self.board[x][y].x = p[0]
243 self.board[x][y].y = p[1]
246 self.board[p[0]][p[1]] = self.board[x][y]
247 self.board[x][y].rank = result[outIndex+1].strip()
249 self.board[x][y] = None
251 elif outcome == "DIES":
252 if self.board[p[0]][p[1]] == None:
255 if self.board[x][y].colour == self.colour:
256 self.units.remove(self.board[x][y])
257 elif self.board[x][y].colour == oppositeColour(self.colour):
258 self.enemyUnits.remove(self.board[x][y])
260 self.board[p[0]][p[1]].rank = result[outIndex+2].strip()
261 self.board[x][y] = None
262 elif outcome == "BOTHDIE":
263 if self.board[p[0]][p[1]] == None:
267 if self.board[x][y].colour == self.colour:
268 self.units.remove(self.board[x][y])
269 elif self.board[x][y].colour == oppositeColour(self.colour):
270 self.enemyUnits.remove(self.board[x][y])
271 if self.board[p[0]][p[1]].colour == self.colour:
272 self.units.remove(self.board[p[0]][p[1]])
273 elif self.board[p[0]][p[1]].colour == oppositeColour(self.colour):
274 self.enemyUnits.remove(self.board[p[0]][p[1]])
277 self.board[p[0]][p[1]] = None
278 self.board[x][y] = None
279 elif outcome == "FLAG":
280 #sys.stderr.write(" Game over!\n")
282 elif outcome == "ILLEGAL":
283 #sys.stderr.write(" Illegal move!\n")
286 #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n");
289 #sys.stderr.write(" Completed interpreting move!\n");
292 def debugPrintBoard(self):
293 """ For debug purposes only. Prints the board to stderr.
294 Does not indicate difference between allied and enemy pieces
295 Unknown (enemy) pieces are shown as '?'
297 for y in range(0, self.height):
298 for x in range(0, self.width):
299 if self.board[x][y] == None:
300 sys.stderr.write(".");
302 sys.stderr.write(str(self.board[x][y].rank));
303 sys.stderr.write("\n")
305 if __name__ == "__main__":
308 while basicAI.MoveCycle():