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.
11 Now (sortof) generates .html files to display results in a prettiful manner.
14 author Sam Moore (matches) [SZM]
15 website http://matches.ucc.asn.au/stratego
17 git git.ucc.asn.au/progcomp2012.git
24 #Global variables/arguments
26 baseDirectory = "../.." #Base directory for results, logs, agents
27 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?)
30 if len(sys.argv) >= 2:
31 nRounds = int(sys.argv[1])
32 if len(sys.argv) >= 3:
33 nGames = int(sys.argv[2])
35 print "Warning: nGames should be even. "+str(nGames)+" specified, but only " + str(int(nGames/2) * 2)+" will be played!"
36 if len(sys.argv) >= 4:
37 baseDirectory = sys.argv[3]
38 if len(sys.argv) >= 6:
39 print "Useage: " +sys.argv[0] + " [nRounds=1] [nGames=10] [baseDirectory=\""+baseDirectory+"\"] [managerPath=baseDirectory+\"/judge/manager/stratego\"]"
42 resultsDirectory = baseDirectory+"/results/" #Where results will go (results are in the form of text files of agent names and scores)
43 logDirectory = baseDirectory+"/log/" #Where log files go (direct output of manager program)
44 agentsDirectory = baseDirectory+"/agents/" #Where agents are found (each agent has its own subdirectory within this directory)
45 managerPath = baseDirectory+"/judge/manager/stratego" #Path to the executable that plays the games
46 if len(sys.argv) >= 5:
47 managerPath = sys.argv[5]
50 #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.
51 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")}
54 #Verbose - print lots of useless stuff about what you are doing (kind of like matches talking on irc...)
59 #Check the manager program exists TODO: And is executable!
60 if os.path.exists(managerPath) == False:
61 print "Manager program at \""+managerPath+"\" doesn't exist!"
64 #Make necessary directories
66 if os.path.exists(resultsDirectory) == False:
67 os.mkdir(resultsDirectory) #Make the results directory if it didn't exist
69 Obselete older version doesn't work with new .html files
70 #Identify the round number by reading the results directory
71 totalRounds = len(os.listdir(resultsDirectory)) + 1
79 if os.path.exists(logDirectory) == False:
80 os.mkdir(logDirectory) #Make the log directory if it didn't exist
83 startTime = time() #Record time at which simulation starts
87 print "Simulating " + str(nRounds) + " rounds (" + str(totalRounds) + " to " + str(totalRounds + nRounds-1) + ")"
89 print "Simulating one round."
91 print "Identifying possible agents in \""+agentsDirectory+"\""
93 #Get all agent names from agentsDirectory
94 agentNames = os.listdir(agentsDirectory)
96 for name in agentNames:
98 sys.stdout.write("Scan \""+name+"\"... ")
99 if os.path.isdir(agentsDirectory+name) == False: #Remove non-directories
101 sys.stdout.write(" Invalid! (Not a directory)\n")
104 if os.path.exists(agentsDirectory+name+"/info") == False: #Try and find the special "info" file in each directory; ignore if it doesn't exist
106 sys.stdout.write(" Invalid! (No \"info\" file found)\n")
109 agentExecutable = agentsDirectory+name+"/"+(open(agentsDirectory+name+"/info").readline().strip())
111 if os.path.exists(agentExecutable) == False:
113 sys.stdout.write(" Invalid! (Path: \""+agentExecutable+"\" does not exist!)\n")
118 sys.stdout.write(" Valid! (Path: \""+agentExecutable+"\")\n")
120 #Convert array of valid names into array of dictionaries containing information about each agent
121 #I'm starting to like python...
122 agents.append({"name":name, "path":agentExecutable, "score":[0], "VICTORY":[], "DEFEAT":[], "DRAW":[], "ILLEGAL":[], "DEFAULT":[], "INTERNAL_ERROR":[], "SURRENDER":[], "DRAW_DEFAULT":[], "BOTH_ILLEGAL":[], "BAD_SETUP":[], "ALL":[], "totalScore":0, "Wins":0, "Losses":0, "Draws":0, "Illegal":0, "Errors":0})
125 print "Couldn't find any agents! Check paths (Edit this script) or generate \"info\" files for agents."
128 print "Total: " + str(len(agents)) + " valid agents found (From "+str(len(agentNames))+" possibilities)"
131 #Prepare the pretty .html files if they don't exist
133 print "Preparing .html results files..."
134 htmlDir = resultsDirectory + "pretty/"
135 if os.path.exists(htmlDir) == False:
137 if os.path.exists(htmlDir) == False:
138 print "Couldn't create directory \""+htmlDir+"\"."
142 if os.path.exists(htmlDir+agent["name"] + ".html") == False:
143 agentFile = open(htmlDir+agent["name"] + ".html", "w")
144 agentFile.write("<html>\n<head>\n <title> " + agent["name"] + " results</title>\n</head>\n<body>\n<h1> Results for " + agent["name"]+" </h1>\n</body>\n</html>\n")
147 os.rename(htmlDir+agent["name"] + ".html", "tmpfile")
149 oldFile = open("tmpfile", "r")
150 agentFile = open(htmlDir+agent["name"] + ".html", "w")
151 line = oldFile.readline()
154 # print "Interpreting line \"" + line.strip() + "\""
155 if line.strip() == "</body>":
157 elif line == "<tr> <th> Score </th> <th> Wins </th> <th> Losses </th> <th> Draws </th> <th> Illegal </th> <th> Errors </th></tr>\n":
158 agentFile.write(line)
159 line = oldFile.readline()
161 values = line.split(' ')
162 agent["totalScore"] += int(values[2].strip())
163 agent["Wins"] += int(values[5].strip())
164 agent["Losses"] += int(values[8].strip())
165 agent["Draws"] += int(values[11].strip())
166 agent["Illegal"] += int(values[14].strip())
167 agent["Errors"] += int(values[17].strip())
168 agentFile.write(line)
169 line = oldFile.readline()
172 print "Prepared results file \"" + htmlDir+agent["name"] + ".html\"."
181 totalGames = nGames/2 * len(agents) * (len(agents)-1)
182 for roundNumber in range(totalRounds, totalRounds + nRounds):
184 if os.path.exists(logDirectory + "round"+str(roundNumber)) == False:
185 os.mkdir(logDirectory + "round"+str(roundNumber)) #Check there is a directory for this round's logs
188 agent.update({"name":agent["name"], "path":agent["path"], "score":[0], "VICTORY":[], "DEFEAT":[], "DRAW":[], "ILLEGAL":[], "DEFAULT":[], "INTERNAL_ERROR":[], "SURRENDER":[], "DRAW_DEFAULT":[], "BOTH_ILLEGAL":[], "BAD_SETUP":[], "ALL":[]})
191 print "Commencing ROUND " + str(roundNumber) + " combat!"
192 print "Total: " + str(totalGames) + " games to be played. This could take a while... (Estimate 60s/game)"
197 #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.
199 for red in agents: #for each agent playing as red,
200 for blue in agents: #against each other agent, playing as blue
202 continue #Exclude battles against self
204 gameID = str(roundNumber) + "." + str(gameNumber)
205 for i in range(1, nGames/2 + 1):
206 #Play a game and read the result. Note the game is logged to a file based on the agent's names
208 sys.stdout.write("Agents: \""+red["name"]+"\" and \""+blue["name"]+"\" playing game (ID: " + gameID + ") ... ")
209 logFile = logDirectory + "round"+str(roundNumber) + "/"+red["name"]+".vs."+blue["name"]+"."+str(i)
210 outline = os.popen(managerPath + " -o " + logFile + " " + red["path"] + " " + blue["path"], "r").read()
211 results = outline.split(' ')
213 if len(results) != 6:
215 sys.stdout.write("Garbage output! \"" + outline + "\"\n")
216 red["INTERNAL_ERROR"].append((blue["name"], gameID, scores["INTERNAL_ERROR"][0]))
217 blue["INTERNAL_ERROR"].append((red["name"], gameID, scores["INTERNAL_ERROR"][0]))
218 red["ALL"].append((blue["name"], gameID, scores["INTERNAL_ERROR"][0], "INTERNAL_ERROR"))
219 blue["ALL"].append((red["name"], gameID, scores["INTERNAL_ERROR"][0], "INTERNAL_ERROR"))
223 if results[1] == "RED":
228 elif results[1] == "BLUE":
235 if results[1] == "BOTH":
236 red["INTERNAL_ERROR"].append((blue["name"], gameID, scores["INTERNAL_ERROR"][0]))
237 blue["INTERNAL_ERROR"].append((red["name"], gameID, scores["INTERNAL_ERROR"][0]))
238 red["ALL"].append((blue["name"], gameID, scores["INTERNAL_ERROR"][0], "INTERNAL_ERROR", "RED"))
239 blue["ALL"].append((red["name"], gameID, scores["INTERNAL_ERROR"][0], "INTERNAL_ERROR", "BLUE"))
242 endColour["score"].insert(0,endColour["score"][0] + scores[results[2]][0])
243 endColour[results[2]].append((otherColour["name"], gameID, scores[results[2]][0]))
244 endColour["ALL"].append((otherColour["name"], gameID, scores[results[2]][0], results[2], endStr))
245 otherColour["score"].insert(0, otherColour["score"][0] + scores[results[2]][1])
246 otherColour[scores[results[2]][2]].append((endColour["name"], gameID, scores[results[2]][1]))
247 otherColour["ALL"].append((endColour["name"], gameID, scores[results[2]][1], scores[results[2]][2], otherStr))
251 sys.stdout.write(" Result \"")
252 for ii in range(1, len(results)):
253 sys.stdout.write(results[ii].strip())
254 if ii < (len(results) - 1):
255 sys.stdout.write(" ")
256 sys.stdout.write("\"\n")
259 print "Completed combat. Total of " + str(gameNumber) + " games played. "
260 if managerErrors != 0:
261 print "WARNING: Registered "+str(managerErrors)+" errors. Check the manager program."
265 #We should now have complete score values.
268 Obselete, non prettified results
270 sys.stdout.write("Creating raw results files for ROUND " + str(roundNumber) + "... ")
272 agents.sort(key = lambda e : e["score"], reverse=True) #Sort the agents based on score
274 resultsFile = open(resultsDirectory+"round"+str(roundNumber)+".results", "w") #Create a file to store all the scores for this round
276 resultsFile.write(agent["name"] + " " + str(agent["score"]) +"\n") #Write the agent names and scores into the file, in descending order
279 sys.stdout.write(" Complete!\n")
280 sys.stdout.write("Updating total scores... ");
282 #Now update the total scores
283 if os.path.exists(resultsDirectory+"total.scores"):
285 sys.stdout.write(" Reading from \""+resultsDirectory+"total.scores\" to update scores... ")
286 totalFile = open(resultsDirectory+"total.scores", "r") #Try to open the total.scores file
287 for line in totalFile: #For all entries,
288 data = line.split(' ')
290 if agent["name"] == data[0]:
291 agent["totalScore"] = int(data[1]) + agent["score"][0] #Simply increment the current score by the recorded total score of the matching file entry
293 totalFile.close() #Close the file, so we can delete it
294 os.remove(resultsDirectory+"total.scores") #Delete the file
295 #Sort the agents again
296 agents.sort(key = lambda e : e["totalScore"], reverse=True)
300 sys.stdout.write(" First round - creating \""+resultsDirectory+"total.scores\"... ")
302 sys.stdout.write(" Complete!\n")
303 print "Finished writing results for ROUND " + str(roundNumber)
307 print "RESULTS FOR ROUND " + str(roundNumber)
309 #totalFile = open(resultsDirectory+"total.scores", "w") #Recreate the file
311 #totalFile.write(agent["name"] + " " + str(agent["totalScore"]) +"\n") #Write the total scores in descending order
313 print "Agent: " + str(agent)
317 print "Updating pretty .html files... "
320 agentFile = open(htmlDir + agent["name"]+".html", "a")
321 agentFile.write("<h2> Round " + str(roundNumber) + "</h2>\n")
322 agentFile.write("<h3> Round Overview </h3>\n")
323 agentFile.write("<table border=\"0\" cellpadding=\"10\">\n")
324 agentFile.write("<tr> <th> Score </th> <th> Wins </th> <th> Losses </th> <th> Draws </th> <th> Illegal </th> <th> Errors </th></tr>\n")
325 agentFile.write("<tr> <td> "+str(agent["score"][0])+" </td> <td> "+str(len(agent["VICTORY"]) + len(agent["DEFAULT"]))+" </td> <td> "+str(len(agent["DEFEAT"]) + len(agent["SURRENDER"]))+" </td> <td> "+str(len(agent["DRAW"]) + len(agent["DRAW_DEFAULT"]))+" </td> <td> "+str(len(agent["ILLEGAL"]) + len(agent["BOTH_ILLEGAL"]) + len(agent["BAD_SETUP"]))+" </td> <td> " +str(len(agent["INTERNAL_ERROR"]))+" </td> </tr>\n")
327 agentFile.write("</table>\n")
329 agentFile.write("<h3> Detailed </h3>\n")
330 agentFile.write("<table border=\"0\" cellpadding=\"10\">\n")
331 agentFile.write("<tr> <th> Game ID </th> <th> Opponent </th> <th> Played as </th> <th> Outcome </th> <th> Score </th> <th> Accumulated Score </th> </tr> </th>\n")
333 for index in range(0, len(agent["ALL"])):
334 agentFile.write("<tr> <td> " + str(agent["ALL"][index][1]) + " </td> <td> <a href="+agent["ALL"][index][0]+".html>"+agent["ALL"][index][0] + " </a> </td> <td> " + agent["ALL"][index][4] + " </td> <td> " + agent["ALL"][index][3] + " </td> <td> " + str(agent["ALL"][index][2]) + "</td> <td> " + str(agent["score"][len(agent["score"])-index -2]) + " </td> </tr> </th>\n")
335 agentFile.write("</table>\n")
337 agent["totalScore"] += agent["score"][0]
338 agent["Wins"] += len(agent["VICTORY"]) + len(agent["DEFAULT"])
339 agent["Losses"] += len(agent["DEFEAT"]) + len(agent["SURRENDER"])
340 agent["Draws"] += len(agent["DRAW"]) + len(agent["DRAW_DEFAULT"])
341 agent["Illegal"] += len(agent["ILLEGAL"]) + len(agent["BOTH_ILLEGAL"]) + len(agent["BAD_SETUP"])
342 agent["Errors"] += len(agent["INTERNAL_ERROR"])
344 agentFile.write("<h3> Accumulated Results </h3>\n")
345 agentFile.write("<table border=\"0\" cellpadding=\"10\">\n")
346 agentFile.write("<tr> <th> Score </th> <th> Wins </th> <th> Losses </th> <th> Draws </th> <th> Illegal </th> <th> Errors </th></tr>\n")
347 agentFile.write("<tr> <td> "+str(agent["totalScore"])+" </td> <td> "+str(agent["Wins"])+" </td> <td> "+str(agent["Losses"])+" </td> <td> "+str(agent["Draws"])+" </td> <td> "+str(agent["Illegal"])+" </td> <td> " +str(agent["Errors"])+" </td> </tr>\n")
349 agentFile.write("</table>\n")
353 roundFile = open(htmlDir + "round"+str(roundNumber)+".html", "w")
354 roundFile.write("<html>\n<head>\n <title> Round " +str(roundNumber)+ " Overview </title>\n</head>\n<body>\n")
355 roundFile.write("<h1> Round " +str(roundNumber)+ " Overview </h1>\n")
356 roundFile.write("<table border=\"0\" cellpadding=\"10\">\n")
357 roundFile.write("<tr> <th> Name </th> <th> Score </th> <th> Total Score </th> </tr>\n")
358 agents.sort(key = lambda e : e["score"][0], reverse=True)
360 roundFile.write("<tr> <td> <a href="+agent["name"]+".html>"+agent["name"] + " </a> </td> <td> " + str(agent["score"][0]) + " </td> <td> " + str(agent["totalScore"]) + " </td> </tr>\n")
361 roundFile.write("</table>\n</body>\n<!-- Results file for Round " + str(roundNumber) + " autogenerated by \"" + sys.argv[0] + "\" at time " + str(time()) + " -->\n</html>\n\n")
369 print "Finalising .html files... "
371 agentFile = open(htmlDir + agent["name"]+".html", "a")
373 #Write the "total" statistics
375 agentFile.write("</body>\n<!-- Results file for \"" + agent["name"] + "\" autogenerated by \"" + sys.argv[0] + "\" at time " + str(time()) + " -->\n</html>\n\n")
378 if os.path.exists(htmlDir + "total.html") == True:
379 os.remove(htmlDir + "total.html") #Delete the file
381 totalFile = open(htmlDir + "total.html", "w")
382 totalFile.write("<html>\n<head>\n <title> Total Overview </title>\n</head>\n<body>\n")
383 totalFile.write("<h1> Total Overview </h1>\n")
384 totalFile.write("<table border=\"0\" cellpadding=\"10\">\n")
385 totalFile.write("<tr> <th> Name </th> <th> Total Score </th> </tr>\n")
386 agents.sort(key = lambda e : e["totalScore"], reverse=True)
388 totalFile.write("<tr> <td> <a href="+agent["name"]+".html>"+agent["name"] + " </a> </td> <td> " + str(agent["totalScore"]) + " </td> </tr>\n")
389 totalFile.write("</table>\n</body>\n<!-- Total Results file autogenerated by \"" + sys.argv[0] + "\" at time " + str(time()) + " -->\n</html>\n\n")
397 print "Completed simulating " + str(nRounds) + " rounds in " + str(endTime - startTime) + " seconds."