Fixing simulate.py
[progcomp2012.git] / progcomp / agents / basic_python / 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 mobile(self):
68                 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
69
70         def valuedRank(self):
71                 if ranks.count(self.rank) > 0:
72                         return len(ranks) - 2 - ranks.index(self.rank)
73                 else:
74                         return 0
75         
76
77 def valuedRank(rank):
78         if ranks.count(rank) > 0:
79                 return len(ranks) - 2 - ranks.index(rank)
80         else:
81                 return 0
82
83
84
85 class BasicAI:
86         """
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
91         """
92         def __init__(self):     
93                 """ Constructs the BasicAI agent, and starts it playing the game """
94                 #sys.stderr.write("BasicAI __init__ here...\n");
95                 self.turn = 0
96                 self.board = []
97                 self.units = []
98                 self.enemyUnits = []
99
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
105
106                 
107
108         def Setup(self):
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(' ')
112                 if len(setup) != 4:
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"
126                 return True
127
128         def MoveCycle(self):
129                 #sys.stderr.write("BasicAI MakeMove here...\n");
130                 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
131                         return False
132                 self.turn += 1
133                 return self.InterpretResult()
134
135         def MakeMove(self):
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
138
139                 #sys.stderr.write("BasicAI MakeMove here...\n")
140                 #self.debugPrintBoard()
141
142                 if len(self.units) <= 0:
143                         return False
144
145                 index = random.randint(0, len(self.units)-1)
146                 startIndex = index
147
148                 directions = ("UP", "DOWN", "LEFT", "RIGHT")
149                 while True:
150                         piece = self.units[index]
151                         if piece != None and piece.mobile():
152                                 dirIndex = random.randint(0, len(directions)-1)
153                                 startDirIndex = dirIndex
154                                 
155                                 while True:
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]
162                                                         return True
163                                         dirIndex = (dirIndex + 1) % len(directions)
164                                         if startDirIndex == dirIndex:
165                                                 break
166
167                         index = (index + 1) % len(self.units)
168                         if startIndex == index:
169                                 print "NO_MOVE"
170                                 return True
171                                                         
172                         
173         def ReadBoard(self):
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.
177                 """
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")
183                                 return False
184                         for x in range(0,self.width):
185                                 if self.turn == 0:
186                                         if row[x] == '.':
187                                                 pass
188                                         elif row[x] == '#':
189                                                 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
190                                                 self.enemyUnits.append(self.board[x][y])
191                                         elif row[x] == '+':
192                                                 self.board[x][y] = Piece("NONE", '+', x, y)
193                                         else:
194                                                 self.board[x][y] = Piece(self.colour, row[x],x,y)
195                                                 self.units.append(self.board[x][y])
196                                 else:
197                                         pass
198                 return True
199                 
200
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
205                 """
206                 #sys.stderr.write("BasicAI InterpretResult here...\n")
207                 result = sys.stdin.readline().split(' ')
208                 #sys.stderr.write("     Read status line \"" + str(result) + "\"\n")
209                 if self.turn == 0:
210                         return True
211
212                 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
213                         return False
214
215                 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
216                         return True
217
218                 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
219                         return False
220
221                 x = int(result[0].strip())
222                 y = int(result[1].strip())
223
224
225                 #sys.stderr.write("     Board position " + str(x) + " " + str(y) + " is OK!\n")         
226
227                 direction = result[2].strip()
228
229                 multiplier = 1
230                 outcome = result[3].strip()
231                 outIndex = 3
232                 if is_integer(outcome):
233                         multiplier = int(outcome)
234                         outcome = result[4].strip()
235                         outIndex = 4
236                 
237                 p = move(x,y,direction, multiplier)
238
239                 #Determine attacking piece
240                 attacker = self.board[x][y]
241                 self.board[x][y] = None
242
243                 if attacker == None:
244                         return False
245
246                 lastMoved = attacker
247
248                 defender = self.board[p[0]][p[1]]
249
250                 #Update attacker's position (Don't overwrite the board yet though)
251
252                 attacker.x = p[0]
253                 attacker.y = p[1]
254                 attacker.positions.insert(0, (attacker.x, attacker.y))
255
256                 
257                 #Determine ranks of pieces if supplied
258                 if len(result) >= outIndex + 3:
259                         if defender == None:
260                                 return False
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
274
275                         defender.beenRevealed = True
276
277                         
278                 
279                 if outcome == "OK":
280                         self.board[p[0]][p[1]] = attacker
281                         
282                 elif outcome == "KILLS":
283                         self.board[p[0]][p[1]] = attacker
284
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)
291         
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)
299
300                 elif outcome == "BOTHDIE":
301                         self.board[p[0]][p[1]] = None
302
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)
309
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)
316
317                 elif outcome == "FLAG":
318                         #sys.stderr.write("     Game over!\n")
319                         return False
320                 elif outcome == "ILLEGAL":
321                         #sys.stderr.write("     Illegal move!\n")
322                         return False
323                 else:
324                         #sys.stderr.write("     Don't understand outcome \"" + outcome + "\"!\n");
325                         return False
326
327                 #sys.stderr.write("     Completed interpreting move!\n");               
328                 return True
329
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 '?'
334                 """
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(".");
339                                 else:
340                                         sys.stderr.write(str(self.board[x][y].rank));
341                         sys.stderr.write("\n")
342
343 if __name__ == "__main__":
344         basicAI = BasicAI()
345         if basicAI.Setup():
346                 while basicAI.MoveCycle():
347                         pass
348

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