Improved simulate.py
[progcomp2012.git] / progcomp / judge / simulator / simulate.py
1 #!/usr/bin/python -u
2
3 '''
4  simulate.py - simulation script for the 2012 UCC Programming Competition
5         NOTE: This is not the manager program for a stratego game
6         It merely calls the manager program as appropriate, and records results
7         Plays exactly ONE round, but does not overwrite previously played rounds
8         eg: run once to generate round1.results, twice to generate round2.results etc
9         Also generates total.scores based on results from every round.
10         
11
12  author Sam Moore (matches) [SZM]
13  website http://matches.ucc.asn.au/stratego
14  email progcomp@ucc.asn.au or matches@ucc.asn.au
15  git git.ucc.asn.au/progcomp2012.git
16 '''
17
18 import os
19 import sys
20 from time import time
21
22 #Global variables/arguments
23
24 baseDirectory = "../.." #Base directory for results, logs, agents
25 nGames = 2 #Number of games played by each agent against each opponent. Half will be played as RED, half as BLUE. If nGames <= 1, then no games will be played (useful for dry run?)
26 nRounds = 1
27
28 if len(sys.argv) >= 2:
29         nRounds = int(sys.argv[1])
30 if len(sys.argv) >= 3:
31         nGames = int(sys.argv[2])
32         if nGames % 2 != 0:
33                 print "Warning: nGames should be even. "+str(nGames)+" specified, but only " + str(int(nGames/2) * 2)+" will be played!"
34 if len(sys.argv) >= 4:
35         baseDirectory = sys.argv[3]
36 if len(sys.argv) >= 6:
37         print "Useage: " +sys.argv[0] + " [nRounds=1] [nGames=10] [baseDirectory=\""+baseDirectory+"\"] [managerPath=baseDirectory+\"/judge/manager/stratego\"]"
38         sys.exit(1)
39
40 resultsDirectory = baseDirectory+"/results/" #Where results will go (results are in the form of text files of agent names and scores)
41 logDirectory = baseDirectory+"/log/" #Where log files go (direct output of manager program)
42 agentsDirectory = baseDirectory+"/agents/" #Where agents are found (each agent has its own subdirectory within this directory)
43 managerPath = baseDirectory+"/judge/manager/stratego" #Path to the executable that plays the games
44 if len(sys.argv) >= 5:
45         managerPath = sys.argv[5] 
46
47
48 #Score dictionary - Tuple is of the form: (end score, other score, other result) where end is the player on whose turn the result occurs, other is the other player, other result indicates what to record the outcome as for the other player.
49 scores = {"VICTORY":(3,1, "DEFEAT"), "DEFEAT":(1,3, "VICTORY"), "SURRENDER":(1,3, "VICTORY"), "DRAW":(2,2, "DRAW"), "DRAW_DEFAULT":(1,1, "DRAW_DEFAULT"), "ILLEGAL":(-1,2, "DEFAULT"), "DEFAULT":(2,-1, "ILLEGAL"), "BOTH_ILLEGAL":(-1,-1, "BOTH_ILLEGAL"), "INTERNAL_ERROR":(0,0, "INTERNAL_ERROR"), "BAD_SETUP":(0,0,"BAD_SETUP")}
50
51
52 #Verbose - print lots of useless stuff about what you are doing (kind of like matches in irc...)
53 verbose = True
54 makePrettyResults = False
55
56
57 #Check the manager program exists TODO: And is executable!
58 if os.path.exists(managerPath) == False:
59         print "Manager program at \""+managerPath+"\" doesn't exist!"
60         sys.exit(1)
61
62 #Make necessary directories
63
64 if os.path.exists(resultsDirectory) == False:
65         os.mkdir(resultsDirectory) #Make the results directory if it didn't exist
66 #Identify the round number by reading the results directory
67 totalRounds = len(os.listdir(resultsDirectory)) + 1
68 if totalRounds > 1:
69         totalRounds -= 1
70
71 if os.path.exists(logDirectory) == False:
72         os.mkdir(logDirectory) #Make the log directory if it didn't exist
73
74
75 startTime = time() #Record time at which simulation starts
76
77 #Do each round...
78 for roundNumber in range(totalRounds, totalRounds + nRounds):
79
80         if os.path.exists(logDirectory + "round"+str(roundNumber)) == False:
81                 os.mkdir(logDirectory + "round"+str(roundNumber)) #Check there is a directory for this round's logs
82
83         if verbose:
84                 print "Simulating ROUND " +str(roundNumber)
85                 print "Identifying possible agents in \""+agentsDirectory+"\""
86
87         #Get all agent names from agentsDirectory
88         #TODO: Move this part outside the loop? It only has to happen once
89         agentNames = os.listdir(agentsDirectory) 
90         agents = []
91         for name in agentNames:
92                 #sys.stdout.write("\nLooking at Agent: \""+ str(name)+"\"... ")
93                 if verbose:
94                         sys.stdout.write("Scan \""+name+"\"... ")
95                 if os.path.isdir(agentsDirectory+name) == False: #Remove non-directories
96                         if verbose:
97                                 sys.stdout.write(" Invalid! (Not a directory)\n")
98                         continue
99
100                 if os.path.exists(agentsDirectory+name+"/info") == False: #Try and find the special "info" file in each directory; ignore if it doesn't exist   
101                         if verbose:
102                                 sys.stdout.write(" Invalid! (No \"info\" file found)\n")
103                         continue
104
105         
106         
107                 agentExecutable = agentsDirectory+name+"/"+(open(agentsDirectory+name+"/info").readline().strip())
108         
109                 if os.path.exists(agentExecutable) == False:
110                         if verbose:
111                                 sys.stdout.write(" Invalid! (File \""+agentExecutable+"\" does not exist!)\n")
112                         continue
113
114
115                 if verbose:
116                         sys.stdout.write(" Valid! (To run: \""+agentExecutable+"\")\n")
117
118                 #Convert array of valid names into array of dictionaries containing information about each agent
119                 #I'm starting to like python...
120                 agents.append({"name":name, "path":agentExecutable,"score":[0], "totalScore":0, "VICTORY":[], "DEFEAT":[], "DRAW":[], "ILLEGAL":[], "INTERNAL_ERROR":[]})       
121         if len(agents) == 0:
122                 print "Couldn't find any agents! Check paths (Edit this script) or generate \"info\" files for agents."
123                 sys.exit(0)
124         if verbose:
125                 print "Total: " + str(len(agents)) + " valid agents found (From "+str(len(agentNames))+" possibilities)"
126                 print ""
127                 print "Commencing ROUND " + str(roundNumber) + " combat! This could take a while... "
128
129
130         normalGames = 0
131         draws = 0
132         aiErrors = 0
133         managerErrors = 0
134         #This double for loop simulates a round robin, with each agent getting the chance to play as both red and blue against every other agent.
135         gameID = 0
136         for red in agents:  #for each agent playing as red,
137                 for blue in agents: #against each other agent, playing as blue
138                         if red == blue:
139                                 continue #Exclude battles against self
140                         gameID += 1
141                         for i in range(1, nGames/2 + 1):
142                                 #Play a game and read the result. Note the game is logged to a file based on the agent's names
143                                 if verbose:
144                                         sys.stdout.write("Agents: \""+red["name"]+"\" and \""+blue["name"]+"\" playing game " + str(i) + "/"+str(nGames/2) + "... ")
145                                 logFile = logDirectory + "round"+str(roundNumber) + "/"+red["name"]+".vs."+blue["name"]+"."+str(i)
146                                 outline = os.popen(managerPath + " -o " + logFile + " " + red["path"] + " " + blue["path"], "r").read()
147                                 results = outline.split(' ')
148                         
149                                 if len(results) != 6:
150                                         if verbose:
151                                                 sys.stdout.write("Garbage output! \"" + outline + "\"\n")
152                                         red["manager_errors"].append((gameID, blue["name"]))
153                                         managerErrors += 1
154                                 else:
155
156                                         if results[1] == "RED":
157                                                 endColour = red
158                                                 otherColour = blue
159                                         elif results[1] == "BLUE":
160                                                 endColour = blue
161                                                 otherColour = red
162                                         if results[1] == "BOTH":
163                                                 pass
164                                         else:
165                                                 endColour["score"].insert(0,endColour["score"][0] + scores[results[2]][0])
166                                                 endColour[results[2]].append((otherColour["name"], gameID, scores[results[2]][0]))
167                                                 otherColour["score"].insert(0, otherColour["score"][0] + scores[results[2]][1])
168                                                 otherColour[scores[results[2]][2]].append((endColour["name"], gameID, scores[results[2]][1]))
169
170                                         if verbose:
171                                                 sys.stdout.write(" Result \"")
172                                                 for ii in range(1, len(results)):
173                                                         sys.stdout.write(results[ii].strip())
174                                                         if ii < (len(results) - 1):
175                                                                 sys.stdout.write(" ")
176                                                 sys.stdout.write("\"\n")
177                 
178         if verbose:
179                 print "Completed combat. Total of " + str(normalGames + draws + aiErrors + managerErrors) + " games played. "
180         if managerErrors != 0:
181                 print "WARNING: Recieved "+str(managerErrors)+" garbage outputs. Check the manager program."
182
183         if verbose:
184                 print "" 
185         #We should now have complete score values.
186                 
187         if verbose:
188                 sys.stdout.write("Creating results files for ROUND " + str(roundNumber) + "... ")
189
190         agents.sort(key = lambda e : e["score"], reverse=True) #Sort the agents based on score
191         
192         resultsFile = open(resultsDirectory+"round"+str(roundNumber)+".results", "w") #Create a file to store all the scores for this round
193         for agent in agents:
194                 resultsFile.write(agent["name"] + " " + str(agent["score"]) +"\n") #Write the agent names and scores into the file, in descending order
195
196         if verbose:
197                 sys.stdout.write(" Complete!\n")
198                 sys.stdout.write("Updating total scores... ");
199         
200         #Now update the total scores
201         if os.path.exists(resultsDirectory+"total.scores"):
202                 if verbose:
203                         sys.stdout.write(" Reading from \""+resultsDirectory+"total.scores\" to update scores... ")
204                 totalFile = open(resultsDirectory+"total.scores", "r") #Try to open the total.scores file
205                 for line in totalFile: #For all entries, 
206                         data = line.split(' ')
207                         for agent in agents:
208                                 if agent["name"] == data[0]:
209                                         agent["totalScore"] = int(data[1]) + agent["score"][0] #Simply increment the current score by the recorded total score of the matching file entry
210                                         break
211                 totalFile.close() #Close the file, so we can delete it
212                 os.remove(resultsDirectory+"total.scores") #Delete the file
213                 #Sort the agents again
214                 agents.sort(key = lambda e : e["totalScore"], reverse=True)
215
216         else:
217                 if verbose:
218                         sys.stdout.write(" First round - creating \""+resultsDirectory+"total.scores\"... ")
219         if verbose:
220                 sys.stdout.write(" Complete!\n")
221                 print "Finished writing results for ROUND " + str(roundNumber)
222                 print ""
223         
224         
225         print "RESULTS FOR ROUND " + str(roundNumber)
226
227         totalFile = open(resultsDirectory+"total.scores", "w") #Recreate the file
228         for agent in agents:
229                 totalFile.write(agent["name"] + " " + str(agent["totalScore"]) +"\n") #Write the total scores in descending order
230                 print "Agent: " + str(agent)
231
232
233         #I just want to say the even though I still think python is evil, it is much better than bash. Using bash makes me cry.
234
235 endTime = time()
236 print "Completed simulating " + str(nRounds) + " rounds in " + str(endTime - startTime) + " seconds."
237
238 if makePrettyResults:
239         if verbose:
240                 print "Now creating prettiful .html files..."
241         
242 if verbose:
243         print "Done!"
244 sys.exit(0)

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