Added new sample AI - "vixen"
[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
66         def mobile(self):
67                 return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+'
68
69         def valuedRank(self):
70                 if ranks.count(self.rank) > 0:
71                         return len(ranks) - 2 - ranks.index(self.rank)
72                 else:
73                         return 0
74         
75
76 def valuedRank(rank):
77         if ranks.count(rank) > 0:
78                 return len(ranks) - 2 - ranks.index(rank)
79         else:
80                 return 0
81
82
83
84 class BasicAI:
85         """
86                 BasicAI class to play a game of stratego
87                 Implements the protocol correctly. Stores the state of the board in self.board
88                 Only makes random moves.
89                 Override method "MakeMove" for more complex moves
90         """
91         def __init__(self):     
92                 """ Constructs the BasicAI agent, and starts it playing the game """
93                 #sys.stderr.write("BasicAI __init__ here...\n");
94                 self.turn = 0
95                 self.board = []
96                 self.units = []
97                 self.enemyUnits = []
98
99                 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}
100                 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}
101                 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}
102                 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}
103                 self.lastMoved = None
104
105                 
106
107         def Setup(self):
108                 """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """
109                 #sys.stderr.write("BasicAI Setup here...\n");
110                 setup = sys.stdin.readline().split(' ')
111                 if len(setup) != 4:
112                         sys.stderr.write("BasicAI setup fails, expected 4 tokens, got " + str(len(setup)) + " "+str(setup) + "\n")
113                 self.colour = setup[0]
114                 self.opponentName = setup[1]
115                 self.width = int(setup[2])
116                 self.height = int(setup[3])
117                 for x in range(0, self.width):
118                         self.board.append([])
119                         for y in range(0, self.height):         
120                                 self.board[x].append(None)
121                 if self.colour == "RED":
122                         print "FB8sB479B8\nBB31555583\n6724898974\n967B669999"
123                 elif self.colour == "BLUE":
124                         print "967B669999\n6724898974\nBB31555583\nFB8sB479B8"
125                 return True
126
127         def MoveCycle(self):
128                 #sys.stderr.write("BasicAI MakeMove here...\n");
129                 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
130                         return False
131                 self.turn += 1
132                 return self.InterpretResult()
133
134         def MakeMove(self):
135                 """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """
136                 #TODO: Over-ride this function in base classes with more complex move behaviour
137
138                 #sys.stderr.write("BasicAI MakeMove here...\n")
139                 #self.debugPrintBoard()
140
141                 if len(self.units) <= 0:
142                         return False
143
144                 index = random.randint(0, len(self.units)-1)
145                 startIndex = index
146
147                 directions = ("UP", "DOWN", "LEFT", "RIGHT")
148                 while True:
149                         piece = self.units[index]
150                         if piece != None and piece.mobile():
151                                 dirIndex = random.randint(0, len(directions)-1)
152                                 startDirIndex = dirIndex
153                                 
154                                 while True:
155                                         #sys.stderr.write("Trying index " + str(dirIndex) + "\n")
156                                         p = move(piece.x, piece.y, directions[dirIndex],1)
157                                         if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height:
158                                                 target = self.board[p[0]][p[1]]
159                                                 if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"):   
160                                                         print str(piece.x) + " " + str(piece.y) + " "+directions[dirIndex]
161                                                         return True
162                                         dirIndex = (dirIndex + 1) % len(directions)
163                                         if startDirIndex == dirIndex:
164                                                 break
165
166                         index = (index + 1) % len(self.units)
167                         if startIndex == index:
168                                 print "NO_MOVE"
169                                 return True
170                                                         
171                         
172         def ReadBoard(self):
173                 """ Reads in the board. 
174                         On the very first turn, sets up the self.board structure
175                         On subsequent turns, the board is simply read, but the self.board structure is not updated here.
176                 """
177                 #sys.stderr.write("BasicAI ReadBoard here...\n");
178                 for y in range(0,self.height):
179                         row = sys.stdin.readline().strip()
180                         if len(row) < self.width:
181                                 sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n")
182                                 return False
183                         for x in range(0,self.width):
184                                 if self.turn == 0:
185                                         if row[x] == '.':
186                                                 pass
187                                         elif row[x] == '#':
188                                                 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
189                                                 self.enemyUnits.append(self.board[x][y])
190                                         elif row[x] == '+':
191                                                 self.board[x][y] = Piece("NONE", '+', x, y)
192                                         else:
193                                                 self.board[x][y] = Piece(self.colour, row[x],x,y)
194                                                 self.units.append(self.board[x][y])
195                                 else:
196                                         pass
197                 return True
198                 
199
200         def InterpretResult(self):
201                 """ Interprets the result of a move, and updates the board. 
202                         The very first move is ignored. 
203                         On subsequent moves, the self.board structure is updated
204                 """
205                 #sys.stderr.write("BasicAI InterpretResult here...\n")
206                 result = sys.stdin.readline().split(' ')
207                 #sys.stderr.write("     Read status line \"" + str(result) + "\"\n")
208                 if self.turn == 0:
209                         return True
210
211                 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
212                         return False
213
214                 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
215                         return True
216
217                 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
218                         return False
219
220                 x = int(result[0].strip())
221                 y = int(result[1].strip())
222
223
224                 #sys.stderr.write("     Board position " + str(x) + " " + str(y) + " is OK!\n")         
225
226                 direction = result[2].strip()
227
228                 multiplier = 1
229                 outcome = result[3].strip()
230                 outIndex = 3
231                 if is_integer(outcome):
232                         multiplier = int(outcome)
233                         outcome = result[4].strip()
234                         outIndex = 4
235                 
236                 p = move(x,y,direction, multiplier)
237
238                 #Determine attacking piece
239                 attacker = self.board[x][y]
240                 self.board[x][y] = None
241
242                 if attacker == None:
243                         return False
244
245                 lastMoved = attacker
246
247                 defender = self.board[p[0]][p[1]]
248
249                 #Update attacker's position (Don't overwrite the board yet though)
250                 attacker.x = p[0]
251                 attacker.y = p[1]
252
253                 
254                 #Determine ranks of pieces if supplied
255                 if len(result) >= outIndex + 3:
256                         if defender == None:
257                                 return False
258                         attacker.rank = result[outIndex+1].strip()
259                         if attacker.beenRevealed == False:
260                                 if attacker.colour == self.colour:
261                                         self.hiddenAllies[attacker.rank] -= 1
262                                 elif attacker.colour == oppositeColour(self.colour):
263                                         self.hiddenEnemies[attacker.rank] -= 1
264                         attacker.beenRevealed = True
265                         defender.rank = result[outIndex+2].strip()
266                         if defender.beenRevealed == False:
267                                 if defender.colour == self.colour:
268                                         self.hiddenAllies[defender.rank] -= 1
269                                 elif defender.colour == oppositeColour(self.colour):
270                                         self.hiddenEnemies[defender.rank] -= 1
271
272                         defender.beenRevealed = True
273
274                         
275                 
276                 if outcome == "OK":
277                         self.board[p[0]][p[1]] = attacker
278                         
279                 elif outcome == "KILLS":
280                         self.board[p[0]][p[1]] = attacker
281
282                         if defender.colour == self.colour:
283                                 self.totalAllies[defender.rank] -= 1
284                                 self.units.remove(defender)
285                         elif defender.colour == oppositeColour(self.colour):
286                                 self.totalEnemies[defender.rank] -= 1
287                                 self.enemyUnits.remove(defender)
288         
289                 elif outcome == "DIES":
290                         if attacker.colour == self.colour:
291                                 self.totalAllies[attacker.rank] -= 1
292                                 self.units.remove(attacker)
293                         elif attacker.colour == oppositeColour(self.colour):
294                                 self.totalEnemies[attacker.rank] -= 1
295                                 self.enemyUnits.remove(attacker)
296
297                 elif outcome == "BOTHDIE":
298                         self.board[p[0]][p[1]] = None
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                         if attacker.colour == self.colour:
308                                 self.totalAllies[attacker.rank] -= 1
309                                 self.units.remove(attacker)
310                         elif attacker.colour == oppositeColour(self.colour):
311                                 self.totalEnemies[attacker.rank] -= 1
312                                 self.enemyUnits.remove(attacker)
313
314                 elif outcome == "FLAG":
315                         #sys.stderr.write("     Game over!\n")
316                         return False
317                 elif outcome == "ILLEGAL":
318                         #sys.stderr.write("     Illegal move!\n")
319                         return False
320                 else:
321                         #sys.stderr.write("     Don't understand outcome \"" + outcome + "\"!\n");
322                         return False
323
324                 #sys.stderr.write("     Completed interpreting move!\n");               
325                 return True
326
327         def debugPrintBoard(self):
328                 """ For debug purposes only. Prints the board to stderr.
329                         Does not indicate difference between allied and enemy pieces
330                         Unknown (enemy) pieces are shown as '?'
331                 """
332                 for y in range(0, self.height):
333                         for x in range(0, self.width):
334                                 if self.board[x][y] == None:
335                                         sys.stderr.write(".");
336                                 else:
337                                         sys.stderr.write(str(self.board[x][y].rank));
338                         sys.stderr.write("\n")
339
340 if __name__ == "__main__":
341         basicAI = BasicAI()
342         if basicAI.Setup():
343                 while basicAI.MoveCycle():
344                         pass
345

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