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
67 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
70 if ranks.count(self.rank) > 0:
71 return len(ranks) - 2 - ranks.index(self.rank)
80 BasicAI class to play a game of stratego
81 Implements the protocol correctly. Stores the state of the board in self.board
82 Only makes random moves.
83 Override method "MakeMove" for more complex moves
86 """ Constructs the BasicAI agent, and starts it playing the game """
87 #sys.stderr.write("BasicAI __init__ here...\n");
92 self.alliedNumber = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1}
93 self.enemyNumber = {'B':6,'1':1,'2':1,'3':2,'4':3,'5':4,'6':4,'7':4,'8':5,'9':8,'s':1,'F':1}
97 """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
98 #sys.stderr.write("BasicAI Setup here...\n");
99 setup = sys.stdin.readline().split(' ')
101 sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
102 self.colour = setup[0]
103 self.opponentName = setup[1]
104 self.width = int(setup[2])
105 self.height = int(setup[3])
106 for x in range(0, self.width):
107 self.board.append([])
108 for y in range(0, self.height):
109 self.board[x].append(None)
110 if self.colour == "RED":
111 print "FB8sB479B8\nBB31555583\n6724898974\n967B669999"
112 elif self.colour == "BLUE":
113 print "967B669999\n6724898974\nBB31555583\nFB8sB479B8"
117 #sys.stderr.write("BasicAI MakeMove here...\n");
118 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
121 return self.InterpretResult()
124 """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
125 #TODO: Over-ride this function in base classes with more complex move behaviour
127 #sys.stderr.write("BasicAI MakeMove here...\n")
128 #self.debugPrintBoard()
130 if len(self.units) <= 0:
133 index = random.randint(0, len(self.units)-1)
136 directions = ("UP", "DOWN", "LEFT", "RIGHT")
138 piece = self.units[index]
139 if piece != None and piece.mobile():
140 dirIndex = random.randint(0, len(directions)-1)
141 startDirIndex = dirIndex
144 #sys.stderr.write("Trying index " + str(dirIndex) + "\n")
145 p = move(piece.x, piece.y, directions[dirIndex],1)
146 if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
147 target = self.board[p[0]][p[1]]
148 if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):
149 print str(piece.x) + " " + str(piece.y) + " "+directions[dirIndex]
151 dirIndex = (dirIndex + 1) % len(directions)
152 if startDirIndex == dirIndex:
155 index = (index + 1) % len(self.units)
156 if startIndex == index:
162 """ Reads in the board.
163 On the very first turn, sets up the self.board structure
164 On subsequent turns, the board is simply read, but the self.board structure is not updated here.
166 #sys.stderr.write("BasicAI ReadBoard here...\n");
167 for y in range(0,self.height):
168 row = sys.stdin.readline().strip()
169 if len(row) < self.width:
170 sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
172 for x in range(0,self.width):
177 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
178 self.enemyUnits.append(self.board[x][y])
180 self.board[x][y] = Piece("NONE", '+', x, y)
182 self.board[x][y] = Piece(self.colour, row[x],x,y)
183 self.units.append(self.board[x][y])
189 def InterpretResult(self):
190 """ Interprets the result of a move, and updates the board.
191 The very first move is ignored.
192 On subsequent moves, the self.board structure is updated
194 #sys.stderr.write("BasicAI InterpretResult here...\n")
195 result = sys.stdin.readline().split(' ')
196 #sys.stderr.write(" Read status line \"" + str(result) + "\"\n")
200 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
203 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
206 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
209 x = int(result[0].strip())
210 y = int(result[1].strip())
213 #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n")
215 direction = result[2].strip()
218 outcome = result[3].strip()
220 if is_integer(outcome):
221 multiplier = int(outcome)
222 outcome = result[4].strip()
225 p = move(x,y,direction, multiplier)
227 #Determine attacking piece
228 attacker = self.board[x][y]
229 self.board[x][y] = None
233 defender = self.board[p[0]][p[1]]
235 #Update attacker's position (Don't overwrite the board yet though)
240 #Determine ranks of pieces if supplied
241 if len(result) >= outIndex + 3:
244 attacker.rank = result[outIndex+1].strip()
245 attacker.beenRevealed = True
246 defender.rank = result[outIndex+2].strip()
247 defender.beenRevealed = True
252 self.board[p[0]][p[1]] = attacker
254 elif outcome == "KILLS":
255 self.board[p[0]][p[1]] = attacker
257 if defender.colour == self.colour:
258 self.alliedNumber[defender.rank] -= 1
259 self.units.remove(defender)
260 elif defender.colour == oppositeColour(self.colour):
261 self.enemyNumber[defender.rank] -= 1
262 self.enemyUnits.remove(defender)
264 elif outcome == "DIES":
265 if attacker.colour == self.colour:
266 self.alliedNumber[attacker.rank] -= 1
267 self.units.remove(attacker)
268 elif attacker.colour == oppositeColour(self.colour):
269 self.enemyNumber[attacker.rank] -= 1
270 self.enemyUnits.remove(attacker)
272 elif outcome == "BOTHDIE":
273 self.board[p[0]][p[1]] = None
275 if defender.colour == self.colour:
276 self.alliedNumber[defender.rank] -= 1
277 self.units.remove(defender)
278 elif defender.colour == oppositeColour(self.colour):
279 self.enemyNumber[defender.rank] -= 1
280 self.enemyUnits.remove(defender)
282 if attacker.colour == self.colour:
283 self.alliedNumber[attacker.rank] -= 1
284 self.units.remove(attacker)
285 elif attacker.colour == oppositeColour(self.colour):
286 self.enemyNumber[attacker.rank] -= 1
287 self.enemyUnits.remove(attacker)
289 elif outcome == "FLAG":
290 #sys.stderr.write(" Game over!\n")
292 elif outcome == "ILLEGAL":
293 #sys.stderr.write(" Illegal move!\n")
296 #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n");
299 #sys.stderr.write(" Completed interpreting move!\n");
302 def debugPrintBoard(self):
303 """ For debug purposes only. Prints the board to stderr.
304 Does not indicate difference between allied and enemy pieces
305 Unknown (enemy) pieces are shown as '?'
307 for y in range(0, self.height):
308 for x in range(0, self.width):
309 if self.board[x][y] == None:
310 sys.stderr.write(".");
312 sys.stderr.write(str(self.board[x][y].rank));
313 sys.stderr.write("\n")
315 if __name__ == "__main__":
318 while basicAI.MoveCycle():