From baf69b8ddea3e2749f114a1e82bc1253ef062bc0 Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Sun, 11 Dec 2011 22:24:40 +0800 Subject: [PATCH] Fixed bugs, minor changes Fixed segfault in manager caused by attempt to print invalid setups to log Fixed SIGPIPE in manager caused by attempt to message non-existant programs. Although I previously fixed a similar SIGPIPE, It is also possible for a file to exist but not have executable permissions set. Controllers set to use such files as executables were returning true for Valid(), but were in fact, not valid at all. Use the access function (thanks stack overflow!) to check for executable permissions and existence in Program::Program. If they aren't set, or file doesn't exist, set pid to -1 which is an "invalid" controller. Discovered python trick which allows me to get rid of stupid "run.py" files for the python AIs. Modified the simulate script to take the number of rounds as an argument. Also made its output slightly prettier. Currently testing simulation of 10 rounds on my laptop at home. "./simulate 10; shutdown -h -P now" I hope it doesn't set the desk on fire while I'm asleep... :S Oh, and the VM is finally setup, hooray! --- progcomp/agents/asmodeus/asmodeus.py | 6 +- progcomp/agents/asmodeus/info | 2 +- progcomp/agents/asmodeus/run.py | 8 - progcomp/agents/basic_python/basic_python.py | 9 +- progcomp/agents/basic_python/run.py | 8 - progcomp/judge/manager/game.cpp | 56 +++- progcomp/judge/manager/main.cpp | 2 +- progcomp/judge/manager/program.cpp | 18 +- progcomp/judge/manager/program.h | 1 + progcomp/judge/simulator/Makefile | 10 +- progcomp/judge/simulator/simulate.py | 289 +++++++++++-------- 11 files changed, 226 insertions(+), 183 deletions(-) mode change 100644 => 100755 progcomp/agents/asmodeus/asmodeus.py delete mode 100755 progcomp/agents/asmodeus/run.py mode change 100644 => 100755 progcomp/agents/basic_python/basic_python.py delete mode 100755 progcomp/agents/basic_python/run.py diff --git a/progcomp/agents/asmodeus/asmodeus.py b/progcomp/agents/asmodeus/asmodeus.py old mode 100644 new mode 100755 index 29c50a2..0371dcd --- a/progcomp/agents/asmodeus/asmodeus.py +++ b/progcomp/agents/asmodeus/asmodeus.py @@ -77,5 +77,9 @@ class Asmodeus(BasicAI): else: return self.suicideScores[attacker.rank] - +if __name__ == "__main__": + asmodeus = Asmodeus() + if asmodeus.Setup(): + while asmodeus.MoveCycle(): + pass diff --git a/progcomp/agents/asmodeus/info b/progcomp/agents/asmodeus/info index 2ce2124..da91d83 100644 --- a/progcomp/agents/asmodeus/info +++ b/progcomp/agents/asmodeus/info @@ -1 +1 @@ -run.py +asmodeus.py diff --git a/progcomp/agents/asmodeus/run.py b/progcomp/agents/asmodeus/run.py deleted file mode 100755 index b646d06..0000000 --- a/progcomp/agents/asmodeus/run.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/python -u - -from asmodeus import * - -asmodeus = Asmodeus() -if asmodeus.Setup(): - while asmodeus.MoveCycle(): - pass diff --git a/progcomp/agents/basic_python/basic_python.py b/progcomp/agents/basic_python/basic_python.py old mode 100644 new mode 100755 index cf024e8..024ae55 --- a/progcomp/agents/basic_python/basic_python.py +++ b/progcomp/agents/basic_python/basic_python.py @@ -287,8 +287,9 @@ class BasicAI: sys.stderr.write(str(self.board[x][y].rank)); sys.stderr.write("\n") -#basicAI = BasicAI() -#if basicAI.Setup(): -# while basicAI.MoveCycle(): -# pass +if __name__ == "__main__": + basicAI = BasicAI() + if basicAI.Setup(): + while basicAI.MoveCycle(): + pass diff --git a/progcomp/agents/basic_python/run.py b/progcomp/agents/basic_python/run.py deleted file mode 100755 index 80b4cfe..0000000 --- a/progcomp/agents/basic_python/run.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/python -u - -from basic_python import * - -basicAI = BasicAI() -if basicAI.Setup(): - while basicAI.MoveCycle(): - pass diff --git a/progcomp/judge/manager/game.cpp b/progcomp/judge/manager/game.cpp index 26369d3..d88f759 100644 --- a/progcomp/judge/manager/game.cpp +++ b/progcomp/judge/manager/game.cpp @@ -89,10 +89,14 @@ Piece::Colour Game::Setup(const char * redName, const char * blueName) if (!red->Valid()) { logMessage("Controller for Player RED is invalid!\n"); + if (!red->HumanController()) + logMessage("Check that program \"%s\" exists and has executable permissions set.\n", redName); } if (!blue->Valid()) { logMessage("Controller for Player BLUE is invalid!\n"); + if (!blue->HumanController()) + logMessage("Check that program \"%s\" exists and has executable permissions set.\n", blueName); } if (!red->Valid()) { @@ -132,37 +136,57 @@ Piece::Colour Game::Setup(const char * redName, const char * blueName) } else { - logMessage("Player RED gave an invalid setup!\n"); + //logMessage("Player RED gave an invalid setup!\n"); result = Piece::RED; } } else if (blueSetup != MovementResult::OK) { - logMessage("Player BLUE gave an invalid setup!\n"); + //logMessage("Player BLUE gave an invalid setup!\n"); result = Piece::BLUE; } + logMessage("%s RED SETUP\n", red->name.c_str()); - for (int y=0; y < 4; ++y) + if (redSetup == MovementResult::OK) { - for (int x=0; x < theBoard.Width(); ++x) + for (int y=0; y < 4; ++y) { - if (theBoard.GetPiece(x, y) != NULL) - logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, y)->type)]); - else - logMessage("."); - } - logMessage("\n"); - } + for (int x=0; x < theBoard.Width(); ++x) + { + if (theBoard.GetPiece(x, y) != NULL) + logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, y)->type)]); + else + logMessage("."); + } + logMessage("\n"); + } + } + else + { + logMessage("INVALID!\n"); + } logMessage("%s BLUE SETUP\n", blue->name.c_str()); - for (int y=0; y < 4; ++y) + if (blueSetup == MovementResult::OK) + { + for (int y=0; y < 4; ++y) + { + for (int x=0; x < theBoard.Width(); ++x) + { + if (theBoard.GetPiece(x, theBoard.Height()-4+y) != NULL) + logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, theBoard.Height()-4+y)->type)]); + else + logMessage("."); + } + logMessage("\n"); + } + } + else { - for (int x=0; x < theBoard.Width(); ++x) - logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, theBoard.Height()-4 + y)->type)]); - logMessage("\n"); - } + logMessage("INVALID!\n"); + } return result; diff --git a/progcomp/judge/manager/main.cpp b/progcomp/judge/manager/main.cpp index 1db8d42..0fbd6ae 100644 --- a/progcomp/judge/manager/main.cpp +++ b/progcomp/judge/manager/main.cpp @@ -253,7 +253,7 @@ void PrintResults(const MovementResult & result, string & buffer) s << "DRAW_DEFAULT "; break; case MovementResult::BAD_SETUP: - s << "BOTH_ILLEGAL "; + s << "BAD_SETUP "; break; default: s << "INTERNAL_ERROR "; diff --git a/progcomp/judge/manager/program.cpp b/progcomp/judge/manager/program.cpp index 660362a..588f714 100644 --- a/progcomp/judge/manager/program.cpp +++ b/progcomp/judge/manager/program.cpp @@ -22,13 +22,8 @@ using namespace std; */ Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0) { - //See if file exists... - FILE * file = fopen(executablePath, "r"); - if (file != NULL) - { - fclose(file); - } - else + //See if file exists and is executable... + if (access(executablePath, X_OK) != 0) { pid = -1; return; @@ -55,10 +50,11 @@ Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0 //If your wrapped program is not written in C/C++, you will probably have a problem - - execl(executablePath, executablePath, (char*)(NULL)); ///Replace process with desired executable - //fprintf(stderr, "Program::Program - Could not run program \"%s\"!\n", executablePath); - //exit(EXIT_FAILURE); //We will probably have to terminate the whole program if this happens + if (access(executablePath, X_OK) == 0) //Check we STILL have permissions to start the file + execl(executablePath, executablePath, (char*)(NULL)); ///Replace process with desired executable + + fprintf(stderr, "Program::Program - Could not run program \"%s\"!\n", executablePath); + exit(EXIT_FAILURE); //We will probably have to terminate the whole program if this happens } else { diff --git a/progcomp/judge/manager/program.h b/progcomp/judge/manager/program.h index 8fef696..01e00bc 100644 --- a/progcomp/judge/manager/program.h +++ b/progcomp/judge/manager/program.h @@ -4,6 +4,7 @@ #include "thread_util.h" #include +#include //Needed to check permissions /** * A wrapping class for an external program, which can exchange messages with the current process through stdin/stdout diff --git a/progcomp/judge/simulator/Makefile b/progcomp/judge/simulator/Makefile index 1d71ef5..4237516 100644 --- a/progcomp/judge/simulator/Makefile +++ b/progcomp/judge/simulator/Makefile @@ -2,11 +2,11 @@ # Not used for building simulate.py # Used for building/removing results -BASEDIR = /home/sam/Documents/progcomp2012/ -RESULTSDIR = /home/sam/Documents/progcomp2012/results -LOGDIR = /home/sam/Documents/progcomp2012/log -AGENTSDIR = /home/sam/Documents/progcomp2012/samples -MANAGER = /home/sam/Documents/progcomp2012/manager/stratego +BASEDIR = /home/sam/Documents/progcomp2012/progcomp +RESULTSDIR = /home/sam/Documents/progcomp2012/progcomp/results +LOGDIR = /home/sam/Documents/progcomp2012/progcomp/log +AGENTSDIR = /home/sam/Documents/progcomp2012/progcomp/agents +MANAGER = /home/sam/Documents/progcomp2012/progcomp/judge/manager/stratego diff --git a/progcomp/judge/simulator/simulate.py b/progcomp/judge/simulator/simulate.py index 32f2b42..8937a2b 100755 --- a/progcomp/judge/simulator/simulate.py +++ b/progcomp/judge/simulator/simulate.py @@ -17,16 +17,27 @@ import os import sys +from time import time -baseDirectory = "/home/sam/Documents/progcomp2012/" +baseDirectory = "/home/sam/Documents/progcomp2012/progcomp/" resultsDirectory = baseDirectory+"results/" #Where results will go (results are in the form of text files of agent names and scores) -agentsDirectory = baseDirectory+"samples/" #Where agents are found (each agent has its own directory) +agentsDirectory = baseDirectory+"agents/" #Where agents are found (each agent has its own directory) logDirectory = baseDirectory+"log/" #Where log files go nGames = 10 #Number of games played by each agent against each opponent. Half will be played as RED, half as BLUE -managerPath = baseDirectory+"manager/stratego" #Path to the manager program +managerPath = baseDirectory+"judge/manager/stratego" #Path to the manager program +nRounds = 1 -scores = {"VICTORY":(3,1), "DEFEAT":(1,3), "SURRENDER":(0,3), "DRAW":(2,2), "DRAW_DEFAULT":(1,1), "ILLEGAL":(-1,2), "DEFAULT":(2,-1), "BOTH_ILLEGAL":(-1,-1), "INTERNAL_ERROR":(0,0)} #Score dictionary +time() + +if len(sys.argv) == 2: + nRounds = int(sys.argv[1]) +elif len(sys.argv) != 1: + print "Useage: simulate.py [nRounds]" + sys.exit(1) + + +scores = {"VICTORY":(3,1), "DEFEAT":(1,3), "SURRENDER":(0,3), "DRAW":(2,2), "DRAW_DEFAULT":(1,1), "ILLEGAL":(-1,2), "DEFAULT":(2,-1), "BOTH_ILLEGAL":(-1,-1), "INTERNAL_ERROR":(0,0), "BAD_SETUP":(0,0)} #Score dictionary verbose = True @@ -35,155 +46,177 @@ verbose = True if os.path.exists(resultsDirectory) == False: os.mkdir(resultsDirectory) #Make the results directory if it didn't exist #Identify the round number by reading the results directory -roundNumber = len(os.listdir(resultsDirectory)) + 1 -if roundNumber > 1: - roundNumber -= 1 +totalRounds = len(os.listdir(resultsDirectory)) + 1 +if totalRounds > 1: + totalRounds -= 1 if os.path.exists(logDirectory) == False: os.mkdir(logDirectory) #Make the log directory if it didn't exist +startTime = time() +for roundNumber in range(totalRounds, totalRounds + nRounds): -if os.path.exists(logDirectory + "round"+str(roundNumber)) == False: - os.mkdir(logDirectory + "round"+str(roundNumber)) #Check there is a directory for this round's logs + if os.path.exists(logDirectory + "round"+str(roundNumber)) == False: + os.mkdir(logDirectory + "round"+str(roundNumber)) #Check there is a directory for this round's logs -print "Simulating ROUND " +str(roundNumber) -print "Identifying possible agents in \""+agentsDirectory+"\"" - -#Get all agent names from agentsDirectory -agentNames = os.listdir(agentsDirectory) -agents = [] -for name in agentNames: - #sys.stdout.write("\nLooking at Agent: \""+ str(name)+"\"... ") if verbose: - sys.stdout.write("Scan \""+name+"\"... ") - if os.path.isdir(agentsDirectory+name) == False: #Remove non-directories + print "Simulating ROUND " +str(roundNumber) + print "Identifying possible agents in \""+agentsDirectory+"\"" + + #Get all agent names from agentsDirectory + agentNames = os.listdir(agentsDirectory) + agents = [] + for name in agentNames: + #sys.stdout.write("\nLooking at Agent: \""+ str(name)+"\"... ") if verbose: - sys.stdout.write(" Invalid! (Not a directory)\n") - continue + sys.stdout.write("Scan \""+name+"\"... ") + if os.path.isdir(agentsDirectory+name) == False: #Remove non-directories + if verbose: + sys.stdout.write(" Invalid! (Not a directory)\n") + continue - if os.path.exists(agentsDirectory+name+"/info") == False: #Try and find the special "info" file in each directory; ignore if it doesn't exist - if verbose: - sys.stdout.write(" Invalid! (No \"info\" file found)\n") - continue - if verbose: - sys.stdout.write(" Valid!") - #sys.stdout.write("OK") - #Convert the array of names to an array of triples - #agents[0] - The name of the agent (its directory) - #agents[1] - The path to the program for the agent (typically agentsDirectory/agent/agent). Read from agentsDirectory/agent/info file - #agents[2] - The score the agent achieved in _this_ round. Begins at zero - agentExecutable = agentsDirectory+name+"/"+(open(agentsDirectory+name+"/info").readline().strip()) - agents.append([name, agentExecutable, 0]) - if verbose: - sys.stdout.write(" (Run program \""+agentExecutable+"\")\n") - -if len(agents) == 0: - print "Couldn't find any agents! Check paths (Edit this script) or generate \"info\" files for agents." - sys.exit(0) -if verbose: - print "Total: " + str(len(agents)) + " valid agents found (From "+str(len(agentNames))+" possibilities)" - print "" - print "Commencing ROUND " + str(roundNumber) + " combat! This could take a while... ("+str(nGames)+" games per pairing * " + str(len(agents) * len(agents)-1) + " pairings = " + str((len(agents) * len(agents)-1) * nGames) + " games)" - - -normalGames = 0 -draws = 0 -aiErrors = 0 -managerErrors = 0 -#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. -for red in agents: #for each agent playing as red, - for blue in agents: #against each other agent, playing as blue - if red == blue: - continue #Exclude battles against self - for i in range(1, nGames/2 + 1): - #Play a game and read the result. Note the game is logged to a file based on the agent's names + if os.path.exists(agentsDirectory+name+"/info") == False: #Try and find the special "info" file in each directory; ignore if it doesn't exist if verbose: - sys.stdout.write("Agents: \""+red[0]+"\" and \""+blue[0]+"\" playing game " + str(i) + "/"+str(nGames/2) + "... ") - logFile = logDirectory + "round"+str(roundNumber) + "/"+red[0]+"_vs_"+blue[0]+"_"+str(i) - outline = os.popen(managerPath + " -o " + logFile + " " + red[1] + " " + blue[1], "r").read() - results = outline.split(' ') - - if len(results) != 6: - if verbose: - sys.stdout.write("Garbage output! " + outline) - else: - if results[1] == "RED": - red[2] += scores[results[2]][0] - blue[2] += scores[results[2]][1] - elif results[1] == "BLUE": - red[2] += scores[results[2]][1] - blue[2] += scores[results[2]][0] - elif results[1] == "BOTH": - red[2] += scores[results[2]][0] - blue[2] += scores[results[2]][1] - red[2] += scores[results[2]][1] - blue[2] += scores[results[2]][0] + sys.stdout.write(" Invalid! (No \"info\" file found)\n") + continue + + + #Convert the array of names to an array of triples + #agents[0] - The name of the agent (its directory) + #agents[1] - The path to the program for the agent (typically agentsDirectory/agent/agent). Read from agentsDirectory/agent/info file + #agents[2] - The score the agent achieved in _this_ round. Begins at zero + agentExecutable = agentsDirectory+name+"/"+(open(agentsDirectory+name+"/info").readline().strip()) + + if os.path.exists(agentExecutable) == False: if verbose: - sys.stdout.write(" " + outline) + sys.stdout.write(" Invalid! (File \""+agentExecutable+"\" does not exist!)\n") + continue + + + if verbose: + sys.stdout.write(" Valid! (To run: \""+agentExecutable+"\")\n") + agents.append([name, agentExecutable, 0]) + + if len(agents) == 0: + print "Couldn't find any agents! Check paths (Edit this script) or generate \"info\" files for agents." + sys.exit(0) + if verbose: + print "Total: " + str(len(agents)) + " valid agents found (From "+str(len(agentNames))+" possibilities)" + print "" + print "Commencing ROUND " + str(roundNumber) + " combat! This could take a while... " + + + normalGames = 0 + draws = 0 + aiErrors = 0 + managerErrors = 0 + #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. + for red in agents: #for each agent playing as red, + for blue in agents: #against each other agent, playing as blue + if red == blue: + continue #Exclude battles against self + for i in range(1, nGames/2 + 1): + #Play a game and read the result. Note the game is logged to a file based on the agent's names + if verbose: + sys.stdout.write("Agents: \""+red[0]+"\" and \""+blue[0]+"\" playing game " + str(i) + "/"+str(nGames/2) + "... ") + logFile = logDirectory + "round"+str(roundNumber) + "/"+red[0]+"_vs_"+blue[0]+"_"+str(i) + outline = os.popen(managerPath + " -o " + logFile + " " + red[1] + " " + blue[1], "r").read() + results = outline.split(' ') + + if len(results) != 6: + if verbose: + sys.stdout.write("Garbage output! \"" + outline + "\"\n") + managerErrors += 1 + else: + if results[1] == "RED": + red[2] += scores[results[2]][0] + blue[2] += scores[results[2]][1] + normalGames += 1 + elif results[1] == "BLUE": + red[2] += scores[results[2]][1] + blue[2] += scores[results[2]][0] + normalGames += 1 + elif results[1] == "BOTH": + red[2] += scores[results[2]][0] + blue[2] += scores[results[2]][1] + red[2] += scores[results[2]][1] + blue[2] += scores[results[2]][0] + draws += 1 + if verbose: + sys.stdout.write(" Result \"") + for ii in range(1, len(results)): + sys.stdout.write(results[ii].strip()) + if ii < (len(results) - 1): + sys.stdout.write(" ") + sys.stdout.write("\"\n") + -if verbose: - print "Completed combat. Total of " + str(normalGames + draws + aiErrors + managerErrors) + " games played. " -if managerErrors != 0: - print "WARNING: Recieved "+str(managerErrors)+" garbage outputs. Check the manager program." - -if verbose: - print "" -#We should now have complete score values. - -if verbose: - sys.stdout.write("Creating results files for ROUND " + str(roundNumber) + "... ") - -agents.sort(key = lambda e : e[2], reverse=True) #Sort the agents based on score - -resultsFile = open(resultsDirectory+"round"+str(roundNumber)+".results", "w") #Create a file to store all the scores for this round -for agent in agents: - resultsFile.write(agent[0] + " " + str(agent[2]) +"\n") #Write the agent names and scores into the file, in descending order - -if verbose: - sys.stdout.write(" Complete!\n") - sys.stdout.write("Updating total scores... "); + if verbose: + print "Completed combat. Total of " + str(normalGames + draws + aiErrors + managerErrors) + " games played. " + if managerErrors != 0: + print "WARNING: Recieved "+str(managerErrors)+" garbage outputs. Check the manager program." -#Now update the total scores -if os.path.exists(resultsDirectory+"total.scores"): if verbose: - sys.stdout.write(" Reading from \""+resultsDirectory+"total.scores\" to update scores... ") - totalFile = open(resultsDirectory+"total.scores", "r") #Try to open the total.scores file - for line in totalFile: #For all entries, - data = line.split(' ') - for agent in agents: - if agent[0] == data[0]: - agent.append(agent[2]) #Store the score achieved this round at the end of the list - agent[2] += int(data[1]) #Simply increment the current score by the recorded total score of the matching file entry - break - totalFile.close() #Close the file, so we can delete it - os.remove(resultsDirectory+"total.scores") #Delete the file - #Sort the agents again - agents.sort(key = lambda e : e[2], reverse=True) - -else: + print "" + #We should now have complete score values. + if verbose: - sys.stdout.write(" First round - creating \""+resultsDirectory+"total.scores\"... ") -if verbose: - sys.stdout.write(" Complete!\n") - print "Finished writing results for ROUND " + str(roundNumber) - print "" + sys.stdout.write("Creating results files for ROUND " + str(roundNumber) + "... ") + agents.sort(key = lambda e : e[2], reverse=True) #Sort the agents based on score + + resultsFile = open(resultsDirectory+"round"+str(roundNumber)+".results", "w") #Create a file to store all the scores for this round + for agent in agents: + resultsFile.write(agent[0] + " " + str(agent[2]) +"\n") #Write the agent names and scores into the file, in descending order -print "RESULTS FOR ROUND " + str(roundNumber) -print "Agent: [name, path, total_score, recent_score]" + if verbose: + sys.stdout.write(" Complete!\n") + sys.stdout.write("Updating total scores... "); + + #Now update the total scores + if os.path.exists(resultsDirectory+"total.scores"): + if verbose: + sys.stdout.write(" Reading from \""+resultsDirectory+"total.scores\" to update scores... ") + totalFile = open(resultsDirectory+"total.scores", "r") #Try to open the total.scores file + for line in totalFile: #For all entries, + data = line.split(' ') + for agent in agents: + if agent[0] == data[0]: + agent.append(agent[2]) #Store the score achieved this round at the end of the list + agent[2] += int(data[1]) #Simply increment the current score by the recorded total score of the matching file entry + break + totalFile.close() #Close the file, so we can delete it + os.remove(resultsDirectory+"total.scores") #Delete the file + #Sort the agents again + agents.sort(key = lambda e : e[2], reverse=True) + + else: + if verbose: + sys.stdout.write(" First round - creating \""+resultsDirectory+"total.scores\"... ") + if verbose: + sys.stdout.write(" Complete!\n") + print "Finished writing results for ROUND " + str(roundNumber) + print "" + + + print "RESULTS FOR ROUND " + str(roundNumber) + print "Agent: [name, path, total_score, recent_score]" -totalFile = open(resultsDirectory+"total.scores", "w") #Recreate the file -for agent in agents: - totalFile.write(agent[0] + " " + str(agent[2]) +"\n") #Write the total scores in descending order - print "Agent: " + str(agent) + totalFile = open(resultsDirectory+"total.scores", "w") #Recreate the file + for agent in agents: + totalFile.write(agent[0] + " " + str(agent[2]) +"\n") #Write the total scores in descending order + print "Agent: " + str(agent) -#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. + #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. +endTime = time() +print "Completed simulating " + str(nRounds) + " rounds in " + str(endTime - startTime) + " seconds." +sys.exit(0) -- 2.20.1