93e4100fc3e85a0c76dc3e149caa0c7eb08b07b3
[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
77
78 class BasicAI:
79         """
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
84         """
85         def __init__(self):     
86                 """ Constructs the BasicAI agent, and starts it playing the game """
87                 #sys.stderr.write("BasicAI __init__ here...\n");
88                 self.turn = 0
89                 self.board = []
90                 self.units = []
91                 self.enemyUnits = []
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}
94                 
95
96         def Setup(self):
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(' ')
100                 if len(setup) != 4:
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"
114                 return True
115
116         def MoveCycle(self):
117                 #sys.stderr.write("BasicAI MakeMove here...\n");
118                 if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False:
119                         return False
120                 self.turn += 1
121                 return self.InterpretResult()
122
123         def MakeMove(self):
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
126
127                 #sys.stderr.write("BasicAI MakeMove here...\n")
128                 #self.debugPrintBoard()
129
130                 if len(self.units) <= 0:
131                         return False
132
133                 index = random.randint(0, len(self.units)-1)
134                 startIndex = index
135
136                 directions = ("UP", "DOWN", "LEFT", "RIGHT")
137                 while True:
138                         piece = self.units[index]
139                         if piece != None and piece.mobile():
140                                 dirIndex = random.randint(0, len(directions)-1)
141                                 startDirIndex = dirIndex
142                                 
143                                 while True:
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]
150                                                         return True
151                                         dirIndex = (dirIndex + 1) % len(directions)
152                                         if startDirIndex == dirIndex:
153                                                 break
154
155                         index = (index + 1) % len(self.units)
156                         if startIndex == index:
157                                 print "NO_MOVE"
158                                 return True
159                                                         
160                         
161         def ReadBoard(self):
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.
165                 """
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")
171                                 return False
172                         for x in range(0,self.width):
173                                 if self.turn == 0:
174                                         if row[x] == '.':
175                                                 pass
176                                         elif row[x] == '#':
177                                                 self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y)
178                                                 self.enemyUnits.append(self.board[x][y])
179                                         elif row[x] == '+':
180                                                 self.board[x][y] = Piece("NONE", '+', x, y)
181                                         else:
182                                                 self.board[x][y] = Piece(self.colour, row[x],x,y)
183                                                 self.units.append(self.board[x][y])
184                                 else:
185                                         pass
186                 return True
187                 
188
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
193                 """
194                 #sys.stderr.write("BasicAI InterpretResult here...\n")
195                 result = sys.stdin.readline().split(' ')
196                 #sys.stderr.write("     Read status line \"" + str(result) + "\"\n")
197                 if self.turn == 0:
198                         return True
199
200                 if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to!
201                         return False
202
203                 if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything
204                         return True
205
206                 if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case
207                         return False
208
209                 x = int(result[0].strip())
210                 y = int(result[1].strip())
211
212
213                 #sys.stderr.write("     Board position " + str(x) + " " + str(y) + " is OK!\n")         
214
215                 direction = result[2].strip()
216
217                 multiplier = 1
218                 outcome = result[3].strip()
219                 outIndex = 3
220                 if is_integer(outcome):
221                         multiplier = int(outcome)
222                         outcome = result[4].strip()
223                         outIndex = 4
224                 
225                 p = move(x,y,direction, multiplier)
226
227                 #Determine attacking piece
228                 attacker = self.board[x][y]
229                 self.board[x][y] = None
230
231                 if attacker == None:
232                         return False
233                 defender = self.board[p[0]][p[1]]
234
235                 #Update attacker's position (Don't overwrite the board yet though)
236                 attacker.x = p[0]
237                 attacker.y = p[1]
238
239                 
240                 #Determine ranks of pieces if supplied
241                 if len(result) >= outIndex + 3:
242                         if defender == None:
243                                 return False
244                         attacker.rank = result[outIndex+1].strip()
245                         attacker.beenRevealed = True
246                         defender.rank = result[outIndex+2].strip()
247                         defender.beenRevealed = True
248
249                         
250                 
251                 if outcome == "OK":
252                         self.board[p[0]][p[1]] = attacker
253                         
254                 elif outcome == "KILLS":
255                         self.board[p[0]][p[1]] = attacker
256
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)
263         
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)
271
272                 elif outcome == "BOTHDIE":
273                         self.board[p[0]][p[1]] = None
274
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)
281
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)
288
289                 elif outcome == "FLAG":
290                         #sys.stderr.write("     Game over!\n")
291                         return False
292                 elif outcome == "ILLEGAL":
293                         #sys.stderr.write("     Illegal move!\n")
294                         return False
295                 else:
296                         #sys.stderr.write("     Don't understand outcome \"" + outcome + "\"!\n");
297                         return False
298
299                 #sys.stderr.write("     Completed interpreting move!\n");               
300                 return True
301
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 '?'
306                 """
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(".");
311                                 else:
312                                         sys.stderr.write(str(self.board[x][y].rank));
313                         sys.stderr.write("\n")
314
315 if __name__ == "__main__":
316         basicAI = BasicAI()
317         if basicAI.Setup():
318                 while basicAI.MoveCycle():
319                         pass
320

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