Oh god, I was assuming scores were integers
[progcomp2012.git] / agents / hunter / basic_python.py
1 #!/usr/bin/python -u
2
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.
5
6 """
7  basic_python.py - A sample Stratego AI for the UCC Programming Competition 2012
8
9  Written in python, the slithery language 
10  Simply makes random moves, as long as possible
11
12  author Sam Moore (matches) [SZM]
13  website http://matches.ucc.asn.au/stratego
14  email [email protected] or [email protected]
15  git git.ucc.asn.au/progcomp2012.git
16 """
17
18 import sys
19 import random
20
21 ranks = ['B','1','2','3','4','5','6','7','8','9','s','F', '?', '+']
22
23 def is_integer(s):
24         """ Using exceptions for this feels... wrong..."""
25         try:
26                 int(s)
27                 return True
28         except ValueError:
29                 return False
30
31 def move(x, y, direction, multiplier):
32         """ Moves point (x,y) in direction, returns a pair """
33         if direction == "UP":
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)
41         return (x,y)
42
43
44
45 def oppositeColour(colour):
46         """ Returns the opposite colour to that given """
47         if colour == "RED":
48                 return "BLUE"
49         elif colour == "BLUE":
50                 return "RED"
51         else:
52                 return "NONE"
53
54 class Piece:
55         """ Class representing a piece 
56                 Pieces have colour, rank and co-ordinates       
57         """
58         def __init__(self, colour, rank, x, y):
59                 self.colour = colour
60                 self.rank = rank
61                 self.x = x
62                 self.y = y
63                 self.lastMoved = -1
64                 self.beenRevealed = False
65                 self.positions = [(x, y)]
66
67         def copy(self):
68                 p = Piece(self.colour, self.rank, self.x, self.y)
69                 p.lastMoved = self.lastMoved
70                 p.beenRevealed = self.beenRevealed
71                 p.positions = []
72                 for pos in self.positions:
73                         p.positions.append((pos[0], pos[1]))
74                 return p
75
76         def mobile(self):
77                 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
78
79         def valuedRank(self):
80                 if ranks.count(self.rank) > 0:
81                         return len(ranks) - 2 - ranks.index(self.rank)
82                 else:
83                         return 0
84         
85
86 def valuedRank(rank):
87         if ranks.count(rank) > 0 and rank != '?':
88                 return len(ranks) - 2 - ranks.index(rank)
89         else:
90                 return 0
91
92
93
94 class BasicAI:
95         """
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
100         """
101         def __init__(self):     
102                 """ Constructs the BasicAI agent, and starts it playing the game """
103                 #sys.stderr.write("BasicAI __init__ here...\n");
104                 self.turn = 0
105                 self.board = []
106                 self.units = []
107                 self.enemyUnits = []
108
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
114
115         def LegalPosition(self, x, y):
116                 return x >= 0 and y >= 0 and x < len(self.board) and y < len(self.board[x])
117                 
118
119         def Setup(self):
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(' ')
123                 if len(setup) != 4:
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"
137                 return True
138
139         def MoveCycle(self):
140                 #sys.stderr.write("BasicAI MakeMove here...\n");
141                 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
142                         return False
143                 self.turn += 1
144                 return self.InterpretResult()
145
146         def MakeMove(self):
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
149
150                 #sys.stderr.write("BasicAI MakeMove here...\n")
151                 #self.debugPrintBoard()
152
153                 if len(self.units) <= 0:
154                         return False
155
156                 index = random.randint(0, len(self.units)-1)
157                 startIndex = index
158
159                 directions = ("UP", "DOWN", "LEFT", "RIGHT")
160                 while True:
161                         piece = self.units[index]
162                         if piece != None and piece.mobile():
163                                 dirIndex = random.randint(0, len(directions)-1)
164                                 startDirIndex = dirIndex
165                                 
166                                 while True:
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]
173                                                         return True
174                                         dirIndex = (dirIndex + 1) % len(directions)
175                                         if startDirIndex == dirIndex:
176                                                 break
177
178                         index = (index + 1) % len(self.units)
179                         if startIndex == index:
180                                 print "NO_MOVE"
181                                 return True
182                                                         
183                         
184         def ReadBoard(self):
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.
188                 """
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")
194                                 return False
195                         for x in range(0,self.width):
196                                 if self.turn == 0:
197                                         if row[x] == '.':
198                                                 pass
199                                         elif row[x] == '#':
200                                                 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
201                                                 self.enemyUnits.append(self.board[x][y])
202                                         elif row[x] == '+':
203                                                 self.board[x][y] = Piece("NONE", '+', x, y)
204                                         else:
205                                                 self.board[x][y] = Piece(self.colour, row[x],x,y)
206                                                 self.units.append(self.board[x][y])
207                                 else:
208                                         pass
209                 return True
210                 
211
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
216                 """
217                 #sys.stderr.write("BasicAI InterpretResult here...\n")
218                 if string == None:
219                         string = sys.stdin.readline()
220
221                 result = string.split(' ')
222
223                 #sys.stderr.write("     Read status line \"" + str(result) + "\"\n")
224                 if self.turn == 0:
225                         return True
226
227                 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
228                         return False
229
230                 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
231                         return True
232
233                 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
234                         return False
235
236                 x = int(result[0].strip())
237                 y = int(result[1].strip())
238
239
240                 #sys.stderr.write("     Board position " + str(x) + " " + str(y) + " is OK!\n")         
241
242                 direction = result[2].strip()
243
244                 multiplier = 1
245                 outcome = result[3].strip()
246                 outIndex = 3
247                 if is_integer(outcome):
248                         multiplier = int(outcome)
249                         outcome = result[4].strip()
250                         outIndex = 4
251                 
252                 p = move(x,y,direction, multiplier)
253
254                 #Determine attacking piece
255                 attacker = self.board[x][y]
256                 self.board[x][y] = None
257
258                 if attacker == None:
259                         return False
260
261                 lastMoved = attacker
262
263                 defender = self.board[p[0]][p[1]]
264
265                 #Update attacker's position (Don't overwrite the board yet though)
266
267                 attacker.x = p[0]
268                 attacker.y = p[1]
269                 attacker.positions.insert(0, (attacker.x, attacker.y))
270
271                 
272                 #Determine ranks of pieces if supplied
273                 if len(result) >= outIndex + 3:
274                         if defender == None:
275                                 return False
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
289
290                         defender.beenRevealed = True
291
292                         
293                 
294                 if outcome == "OK":
295                         self.board[p[0]][p[1]] = attacker
296                         
297                 elif outcome == "KILLS":
298                         self.board[p[0]][p[1]] = attacker
299
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)
306         
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)
314
315                 elif outcome == "BOTHDIE":
316                         self.board[p[0]][p[1]] = None
317
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)
324
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)
331
332                 elif outcome == "FLAG":
333                         #sys.stderr.write("     Game over!\n")
334                         return False
335                 elif outcome == "ILLEGAL":
336                         #sys.stderr.write("     Illegal move!\n")
337                         return False
338                 else:
339                         #sys.stderr.write("     Don't understand outcome \"" + outcome + "\"!\n");
340                         return False
341
342                 #sys.stderr.write("     Completed interpreting move!\n");               
343                 return True
344
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 '?'
349                 """
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(".");
354                                 else:
355                                         sys.stderr.write(str(self.board[x][y].rank));
356                         sys.stderr.write("\n")
357
358 if __name__ == "__main__":
359         basicAI = BasicAI()
360         if basicAI.Setup():
361                 while basicAI.MoveCycle():
362                         pass
363

UCC git Repository :: git.ucc.asn.au