From 5f9adddd695f2664a0d690b59a779e40b51ade3d Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Sun, 4 Mar 2012 19:10:39 +0800 Subject: [PATCH] Added image output to manager, added plots to results pages Can generate images from manager program with the -I option Can generate a video from these images with the -v option (Note: just runs ffmpeg) These two options only work if the program was built with graphics enabled. Note: You can generate a video from a saved log file by "./stratego -v video -o logfile" So that's cool. Results pages look nicer. Fixed bug with score updating between rounds. Added pretty plots. Most plots end up being straight lines. But its pretty anyway. --- judge/manager/game.cpp | 32 +++++++-- judge/manager/game.h | 6 +- judge/manager/graphics.cpp | 76 +++++++++++++++++++++ judge/manager/graphics.h | 5 +- judge/manager/main.cpp | 47 ++++++++++++- judge/simulator/simulate.py | 127 +++++++++++++++++++++++++++++++----- web/doc/manager_manual.txt | 20 +++++- 7 files changed, 286 insertions(+), 27 deletions(-) diff --git a/judge/manager/game.cpp b/judge/manager/game.cpp index 5282ec0..0e768c5 100644 --- a/judge/manager/game.cpp +++ b/judge/manager/game.cpp @@ -7,7 +7,7 @@ using namespace std; Game* Game::theGame = NULL; bool Game::gameCreated = false; -Game::Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard, double newTimeoutTime) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard), timeoutTime(newTimeoutTime) +Game::Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard, double newTimeoutTime, const char * newImageOutput) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard), timeoutTime(newTimeoutTime), imageOutput(newImageOutput) { gameCreated = false; if (gameCreated) @@ -46,7 +46,7 @@ Game::Game(const char * redPath, const char * bluePath, const bool enableGraphic // logMessage("Game initialised.\n"); } -Game::Game(const char * fromFile, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard, double newTimeoutTime) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard), timeoutTime(newTimeoutTime) +Game::Game(const char * fromFile, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard, double newTimeoutTime,const char * newImageOutput) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard), timeoutTime(newTimeoutTime), imageOutput(newImageOutput) { gameCreated = false; if (gameCreated) @@ -482,7 +482,7 @@ MovementResult Game::Play() // logMessage("Messaging red with \"START\"\n"); red->Message("START"); - + int moveCount = 0; while (!Board::HaltResult(result) && (turnCount < maxTurns || maxTurns < 0)) { @@ -501,7 +501,15 @@ MovementResult Game::Play() #ifdef BUILD_GRAPHICS if (graphicsEnabled) + { theBoard.Draw(toReveal); + if (imageOutput != "") + { + string imageFile = "" + imageOutput + "/"+ itostr(moveCount) + ".bmp"; + Graphics::ScreenShot(imageFile.c_str()); + } + + } #endif //BUILD_GRAPHICS turn = Piece::RED; @@ -541,10 +549,19 @@ MovementResult Game::Play() theBoard.PrintPretty(stdout, toReveal); fprintf(stdout, "\n\n"); } + + ++moveCount; #ifdef BUILD_GRAPHICS if (graphicsEnabled) + { theBoard.Draw(toReveal); + if (imageOutput != "") + { + string imageFile = "" + imageOutput + "/" + itostr(moveCount) + ".bmp"; + Graphics::ScreenShot(imageFile.c_str()); + } + } #endif //BUILD_GRAPHICS @@ -589,7 +606,7 @@ MovementResult Game::Play() else ReadUserCommand(); - + ++moveCount; ++turnCount; } @@ -863,3 +880,10 @@ void Game::MakeControllers(const char * redPath, const char * bluePath) } +string itostr(int i) +{ + stringstream s; + s << i; + return s.str(); +} + diff --git a/judge/manager/game.h b/judge/manager/game.h index 3f46abb..5a5f8a6 100644 --- a/judge/manager/game.h +++ b/judge/manager/game.h @@ -15,8 +15,8 @@ class Game { public: - Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false, double newTimeoutTime = 2.0); - Game(const char * fromFile, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false, double newTimeoutTime = 2.0); + Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false, double newTimeoutTime = 2.0, const char * newImageOutput = ""); + Game(const char * fromFile, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false, double newTimeoutTime = 2.0, const char * newImageOutput = ""); virtual ~Game(); @@ -73,6 +73,7 @@ class Game private: double timeoutTime; + std::string imageOutput; }; @@ -93,6 +94,7 @@ class FileController : public Controller }; +std::string itostr(int i); #endif //MAIN_H diff --git a/judge/manager/graphics.cpp b/judge/manager/graphics.cpp index 7cc22d0..5710898 100644 --- a/judge/manager/graphics.cpp +++ b/judge/manager/graphics.cpp @@ -449,4 +449,80 @@ void Graphics::Wait(int n) SDL_Delay(n); } +/* Writes an upside down image??? +void Graphics::ScreenShot(const char * fileName) +{ + + std::vector< GLubyte > pixeldata; + + pixeldata.resize( swidth * sheight * 3 ); + + SDL_Surface* image = SDL_CreateRGBSurface(SDL_SWSURFACE, screenWidth, screenHeight, 24,255U << (16),255 << (8),255 << (0),0); + + SDL_LockSurface( image ); + + glReadPixels(0, 0, swidth, sheight, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)image->pixels); + + + + SDL_UnlockSurface( image ); + + SDL_SaveBMP(image, fileName); + SDL_FreeSurface( image ); + + +} +*/ + + +// Hacky code from http://www.gamedev.net/topic/389159-bitmap-file-saving-problem/ +// Should probably make it nicer + +void Graphics::ScreenShot(const char * fileName) +{ + + unsigned char *pixels = (unsigned char*)(malloc (screenWidth * screenHeight * 3)); + + SDL_Surface* reversed_image = SDL_CreateRGBSurface(SDL_SWSURFACE, screenWidth, screenHeight, 24, + 255U << (0), // Blue channel + 255 << (8), // Green channel + 255 << (16), // Red channel + 0 /* no alpha! */); + + SDL_LockSurface( reversed_image ); + + // Read in the pixel data + glReadPixels(0, 0, screenWidth, screenHeight, GL_RGB, GL_UNSIGNED_BYTE, pixels); + + SDL_UnlockSurface( reversed_image ); + + /* At this point the image has been reversed, so we need to re-reverse it so that + it is the correct way around. We do this by copying the "image" pixels to another + surface in reverse order */ + SDL_Surface* image = SDL_CreateRGBSurface(SDL_SWSURFACE, screenWidth, screenHeight, 24, + 255U << (0), // Blue channel + 255 << (8), // Green channel + 255 << (16), // Red channel + 0 /* no alpha! */); + + uint8_t *imagepixels = reinterpret_cast(image->pixels); + // Copy the "reversed_image" memory to the "image" memory + for (int y = (screenHeight - 1); y >= 0; --y) { + uint8_t *row_begin = pixels + y * screenWidth * 3; + uint8_t *row_end = row_begin + screenWidth * 3; + + std::copy(row_begin, row_end, imagepixels); + + // Advance a row in the output surface. + imagepixels += image->pitch; + } + + // Save file + SDL_SaveBMP(image, fileName); + + // Clear memory + SDL_FreeSurface( reversed_image ); + SDL_FreeSurface( image ); +} + #endif //BUILD_GRAPHICS diff --git a/judge/manager/graphics.h b/judge/manager/graphics.h index caa57c6..89a44df 100644 --- a/judge/manager/graphics.h +++ b/judge/manager/graphics.h @@ -18,6 +18,7 @@ typedef unsigned char Uint8; +#include @@ -93,7 +94,7 @@ class Graphics }; static bool Initialised() {return initialised;} - + static void ScreenShot(const char * fileName); protected: static void DrawColourData(SDL_Surface * dest, int destX, int destY, std::vector * R, std::vector * G, std::vector * B, std::vector * A = NULL); static void DrawColourData(SDL_Surface * dest, int destX, int destY, std::vector > * R, std::vector > * G, std::vector > * B, std::vector > * A = NULL); @@ -151,6 +152,8 @@ class Font : private Texture }; + + #endif //GRAPHICS_H #endif //BUILD_GRAPHICS diff --git a/judge/manager/main.cpp b/judge/manager/main.cpp index e65ef0d..b0e2b4c 100644 --- a/judge/manager/main.cpp +++ b/judge/manager/main.cpp @@ -15,6 +15,8 @@ Piece::Colour SetupGame(int argc, char ** argv); void DestroyGame(); void PrintResults(const MovementResult & result, string & buffer); +char * video = NULL; + int main(int argc, char ** argv) { @@ -50,12 +52,21 @@ int main(int argc, char ** argv) Game::theGame->red->Message("QUIT " + buffer); Game::theGame->blue->Message("QUIT " + buffer); + if (video != NULL) + { + string command = "mv "; command += video; command += " tmp; cd tmp; ffmpeg -r 10 -b 1024k -i %d.bmp "; command += video; + command += ";mv "; command += video; command += " ../; cd ../; rm -rf tmp;"; + system(command.c_str()); + } + //Log the message if (Game::theGame->GetLogFile() != stdout) Game::theGame->logMessage("%s\n", buffer.c_str()); fprintf(stdout, "%s\n", buffer.c_str()); + + exit(EXIT_SUCCESS); return 0; } @@ -64,6 +75,8 @@ Piece::Colour SetupGame(int argc, char ** argv) { char * red = NULL; char * blue = NULL; double stallTime = 0.0; bool graphics = false; bool allowIllegal = false; FILE * log = NULL; Piece::Colour reveal = Piece::BOTH; char * inputFile = NULL; int maxTurns = 5000; bool printBoard = false; double timeoutTime = 2.0; + char * imageOutput = (char*)""; + for (int ii=1; ii < argc; ++ii) { @@ -99,7 +112,7 @@ Piece::Colour SetupGame(int argc, char ** argv) case 'g': #ifdef BUILD_GRAPHICS - graphics = !graphics; + graphics = true; #else fprintf(stderr, "ERROR: -g switch supplied, but the program was not built with graphics.\n Please do not use the -g switch."); exit(EXIT_FAILURE); @@ -173,6 +186,34 @@ Piece::Colour SetupGame(int argc, char ** argv) inputFile = argv[ii+1]; ++ii; break; + case 'v': + #ifdef BUILD_GRAPHICS + video = argv[ii+1]; + #endif //BUILD_GRAPHICS + case 'I': + { + #ifdef BUILD_GRAPHICS + graphics = true; + if (argc - ii <= 1) + { + fprintf(stderr, "ARGUMENT_ERROR - Expected filename after -I switch!\n"); + exit(EXIT_FAILURE); + } + imageOutput = argv[ii+1]; + string m("mkdir -p "); m += imageOutput; + system(m.c_str()); + ++ii; + #else + fprintf(stderr, "ERROR: -%c switch supplied, but the program was not built with graphics."); + exit(EXIT_FAILURE); + #endif //BUILD_GRAPHICS + + break; + + } + + + case 'h': system("clear"); system("less manual.txt"); @@ -218,11 +259,11 @@ Piece::Colour SetupGame(int argc, char ** argv) exit(EXIT_FAILURE); } - Game::theGame = new Game(red,blue, graphics, stallTime, allowIllegal,log, reveal,maxTurns, printBoard, timeoutTime); + Game::theGame = new Game(red,blue, graphics, stallTime, allowIllegal,log, reveal,maxTurns, printBoard, timeoutTime, imageOutput); } else { - Game::theGame = new Game(inputFile, graphics, stallTime, allowIllegal,log, reveal,maxTurns, printBoard, timeoutTime); + Game::theGame = new Game(inputFile, graphics, stallTime, allowIllegal,log, reveal,maxTurns, printBoard, timeoutTime, imageOutput); } if (Game::theGame == NULL) diff --git a/judge/simulator/simulate.py b/judge/simulator/simulate.py index 634e424..217a80e 100755 --- a/judge/simulator/simulate.py +++ b/judge/simulator/simulate.py @@ -10,6 +10,7 @@ Now (sortof) generates .html files to display results in a prettiful manner. + THIS FILE IS TERRIBLE author Sam Moore (matches) [SZM] website http://matches.ucc.asn.au/stratego @@ -152,6 +153,26 @@ if verbose: print "Preparing .html results files..." +if os.path.exists(resultsDirectory + "index.html") == True: + os.remove(resultsDirectory + "index.html") #Delete the file +totalFile = open(resultsDirectory + "index.html", "w") +totalFile.write("\n\n Round in progress... \n\n\n") +if nRounds > 1: + totalFile.write("

Rounds " + str(totalRounds) + " to " + str(totalRounds + nRounds-1) + " in progress...

\n") +else: + totalFile.write("

Round " + str(totalRounds) + " in progress...

\n") +totalFile.write("

Please wait for the rounds to finish. You can view the current progress by watching the Log Files

") +if totalRounds > 1: + totalFile.write("

Round Summaries

\n") + totalFile.write("\n") + for i in range(1, totalRounds): + totalFile.write("\n") + totalFile.write("
Round " + str(i) + "
\n") + +totalFile.write("\n\n\n\n") +totalFile.close() + + for agent in agents: if os.path.exists(resultsDirectory+agent["name"] + ".html") == False: agentFile = open(resultsDirectory+agent["name"] + ".html", "w") @@ -173,19 +194,24 @@ for agent in agents: while line != "": #if verbose: # print "Interpreting line \"" + line.strip() + "\"" - if line.strip() == "": + if line.strip() == "" or line.strip() == "": break - elif line == " Score Wins Losses Draws Illegal Errors \n": + elif line == "

Round Overview

\n": agentFile.write(line) line = oldFile.readline() - - values = line.split(' ') - agent["totalScore"] += int(values[2].strip()) - agent["Wins"] += int(values[5].strip()) - agent["Losses"] += int(values[8].strip()) - agent["Draws"] += int(values[11].strip()) - agent["Illegal"] += int(values[14].strip()) - agent["Errors"] += int(values[17].strip()) + agentFile.write(line) + line = oldFile.readline() + if line == " Score Wins Losses Draws Illegal Errors \n": + #sys.stdout.write("Adding scores... " + line + "\n") + agentFile.write(line) + line = oldFile.readline() + values = line.split(' ') + agent["totalScore"] += int(values[2].strip()) + agent["Wins"] += int(values[5].strip()) + agent["Losses"] += int(values[8].strip()) + agent["Draws"] += int(values[11].strip()) + agent["Illegal"] += int(values[14].strip()) + agent["Errors"] += int(values[17].strip()) agentFile.write(line) line = oldFile.readline() @@ -233,6 +259,7 @@ for roundNumber in range(totalRounds, totalRounds + nRounds): errorLog = [logDirectory + "error/" + red["name"] + "."+str(gameID), logDirectory + "error/" + blue["name"] + "."+str(gameID)] #Run the game, outputting to logFile; stderr of (both) AI programs is directed to logFile.stderr outline = os.popen(managerPath + " -o " + logFile + " -T " + str(timeoutValue) + " \"" + red["path"] + "\" \"" + blue["path"] + "\" 2>> " + logFile+".stderr", "r").read() + #os.system("mv tmp.mp4 " + logFile + ".mp4") #If there were no errors, get rid of the stderr file if os.stat(logFile+".stderr").st_size <= 0: @@ -274,6 +301,13 @@ for roundNumber in range(totalRounds, totalRounds + nRounds): otherColour["score"].insert(0, otherColour["score"][0] + scores[results[2]][1]) otherColour[scores[results[2]][2]].append((endColour["name"], gameID, scores[results[2]][1])) otherColour["ALL"].append((endColour["name"], gameID, scores[results[2]][1], scores[results[2]][2], otherStr)) + #Write scores to raw text files + for agent in [endColour, otherColour]: + scoreFile = open(resultsDirectory + agent["name"] + ".scores", "a") + scoreFile.write(str(agent["totalScore"] + agent["score"][0]) + "\n") + scoreFile.close() + + if verbose: @@ -345,6 +379,9 @@ for roundNumber in range(totalRounds, totalRounds + nRounds): agentFile.write("\n") + + + agentFile.close() #Update round file @@ -357,7 +394,31 @@ for roundNumber in range(totalRounds, totalRounds + nRounds): for agent in agents: roundFile.write(" "+agent["name"] + " " + str(agent["score"][0]) + " " + str(agent["totalScore"]) + " \n") roundFile.write("\n") - roundFile.write("

Current Scoreboard

\n") + + command = "cp scores.plt " + resultsDirectory + "scores.plt;" + os.system(command) + + scorePlot = open(resultsDirectory + "scores.plt", "a") + scorePlot.write("plot ") + for i in range(0, len(agents)): + if i > 0: + scorePlot.write(", ") + scorePlot.write("\""+agents[i]["name"]+".scores\" using ($0+1):1 with linespoints title \""+agents[i]["name"]+"\"") + + scorePlot.write("\nexit\n") + scorePlot.close() + + command = "d=$(pwd); cd " + resultsDirectory + ";" + command += "gnuplot scores.plt;" + command += "rm -f scores.plt;" + command += "mv scores.png round"+str(roundNumber)+".png;" + command += "cd $d;" + os.system(command) + + roundFile.write("

Accumulated Scores - up to Round " + str(roundNumber)+"

\n") + roundFile.write("\"round"+str(roundNumber)+".png\"\n") + + roundFile.write("

Current Scoreboard

\n") roundFile.write("\n\n\n\n") roundFile.close() @@ -369,16 +430,32 @@ if verbose: print "Finalising .html files... " for agent in agents: agentFile = open(resultsDirectory + agent["name"]+".html", "a") - - #Write the "total" statistics + agentFile.write("\n") + #Write a graph + #Comment out if you don't have gnuplot + + command = "rm -f " + agent["name"] + ".png;" + command += "cp template.plt " + resultsDirectory + agent["name"] + ".plt;" + command += "d=$(pwd); cd " + resultsDirectory + ";" + command += "sed -i \"s:\[NAME\]:"+agent["name"]+":g\" " +resultsDirectory + agent["name"]+".plt;" + command += "gnuplot " + resultsDirectory + agent["name"]+".plt;" + command += "rm -f " + resultsDirectory + agent["name"] + ".plt;" + command += "cd $d;" + os.system(command) + agentFile.write("\n") + agentFile.write("

Score Graph

\n") + agentFile.write("\""+agent["name"]+".png\"\n") + + #Link to main file + agentFile.write("

Total Statistics

\n") agentFile.write("\n\n\n\n") agentFile.close() - if os.path.exists(resultsDirectory + "total.html") == True: - os.remove(resultsDirectory + "total.html") #Delete the file +if os.path.exists(resultsDirectory + "index.html") == True: + os.remove(resultsDirectory + "index.html") #Delete the file -totalFile = open(resultsDirectory + "total.html", "w") +totalFile = open(resultsDirectory + "index.html", "w") totalFile.write("\n\n Total Overview \n\n\n") totalFile.write("

Total Overview

\n") totalFile.write("\n") @@ -388,6 +465,19 @@ for agent in agents: totalFile.write("\n") totalFile.write("
"+agent["name"] + " " + str(agent["totalScore"]) + "
\n") +totalFile.write("

Score Graph

\n") + + +command = "d=$(pwd);" +command += "cd " + resultsDirectory + ";" +command += "rm -f scores.png;" +command += "cp round"+str(roundNumber)+".png scores.png;" +command += "cd $d;" +os.system(command) + +totalFile.write("\"scores.png\"\n") + + totalFile.write("

Round Summaries

\n") totalFile.write("\n") for i in range(1, totalRounds+1): @@ -397,6 +487,11 @@ totalFile.write("
\n") totalFile.write("\n\n\n\n") totalFile.close() +#Write results to a raw text file as well +textResults = open(resultsDirectory + "total.txt", "w") +for agent in agents: + textResults.write(agent["name"] + " " + str(agent["totalScore"]) + "\n") +textResults.close() if verbose: print "Done!" diff --git a/web/doc/manager_manual.txt b/web/doc/manager_manual.txt index a19cd5d..fc338ac 100644 --- a/web/doc/manager_manual.txt +++ b/web/doc/manager_manual.txt @@ -4,7 +4,7 @@ NAME SYNOPSIS - stratego {[-gpirb] [-o output_file ] [-t stall_time] [-T timeout_time] [-m max_turns] {red_player blue_player | -f input_file} | {-h | --help} } + stratego {[-gpirb] [-o output_file ] [-t stall_time] [-T timeout_time] [-m max_turns] [-I image_directory] {red_player blue_player | -f input_file} | {-h | --help} } QUICK EXAMPLE - Play against a sample AI, using graphics, hiding the AI's pieces stratego -g -b @human ../../agents/vixen/vixen.py @@ -101,6 +101,24 @@ OPTIONS All switches function as normal with -f. NOTE: It is recommended that -g is used with -f. + -I + stratego can output image files in the BMP format, when built with graphics. + If this option is supplied, a directory indicated will be used, and a .bmp image will be saved to the directory after each move. + The images will be numbered from 0.bmp (before the game starts) with increasing integers for each move. + Note that the image number corresponds to the move number, not the turn number. + The move number is odd after RED has moved, and even after BLUE has moved. + + NOTE: The -I switch will automatically enable graphics, even if the -g switch is not supplied. + + -v + If you have ffmpeg on your system, you can use this to quickly create a video. + When this option is supplied, stratego first outputs image files to a temporary directory in BMP format. + After the game is finished, these files are used to create an mp4 movie with the specified name: + + $ ffmpeg -r 10 -b 1024k -i tmp/%d.bmp filename + + The temporary images will then be deleted. + -h, --help If the -h switch is used, this page will be printed and stratego will exit. -- 2.20.1