Added image output to manager, added plots to results pages
[progcomp2012.git] / judge / manager / game.cpp
index df7a061..0e768c5 100644 (file)
@@ -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) : 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)
+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)
@@ -25,21 +25,28 @@ Game::Game(const char * redPath, const char * bluePath, const bool enableGraphic
                        Graphics::Initialise("Stratego", theBoard.Width()*32, theBoard.Height()*32);
        #endif //BUILD_GRAPHICS
 
-       if (strcmp(redPath, "human") == 0)
-               red = new Human_Controller(Piece::RED, graphicsEnabled);
-       else
-               red = new AI_Controller(Piece::RED, redPath);
-       
-       
-       if (strcmp(bluePath, "human") == 0)
-               blue = new Human_Controller(Piece::BLUE, graphicsEnabled);
-       else
-               blue = new AI_Controller(Piece::BLUE, bluePath);
 
 
+       MakeControllers(redPath, bluePath);
+
+       if (red == NULL || blue == NULL)
+       {
+               fprintf(stderr, "Game::Game - Error creating controller: ");
+               if (red == NULL)
+               {
+                       if (blue == NULL)
+                               fprintf(stderr, " BOTH! (red: \"%s\", blue: \"%s\"\n", redPath, bluePath);
+                       else
+                               fprintf(stderr, " RED! (red: \"%s\")\n", redPath);
+               }
+               else
+                       fprintf(stderr, "BLUE! (blue: \"%s\")\n", bluePath);
+               exit(EXIT_FAILURE);
+       }
+//     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) : 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)
+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)
@@ -93,13 +100,13 @@ Piece::Colour Game::Setup(const char * redName, const char * blueName)
        {
                logMessage("Controller for Player RED is invalid!\n");
                if (!red->HumanController())
-                       logMessage("Check that program \"%s\" exists and has executable permissions set.\n", redName);
+                       logMessage("Check that executable \"%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);
+                       logMessage("Check that executable \"%s\" exists and has executable permissions set.\n", blueName);
        }
        if (!red->Valid())
        {
@@ -243,13 +250,15 @@ void Game::HandleBrokenPipe(int sig)
        if (theGame->turn == Piece::RED)
        {
                theGame->logMessage("Game ends on RED's turn - REASON: ");
-               theGame->blue->Message("DEFAULT");      
+               if (theGame->blue->Valid()) //Should probably check this
+                       theGame->blue->Message("DEFAULT");      
        }
        else if (theGame->turn == Piece::BLUE)
        {
        
                theGame->logMessage("Game ends on BLUE's turn - REASON: ");
-               theGame->red->Message("DEFAULT");
+               if (theGame->red->Valid()) //Should probably check this
+                       theGame->red->Message("DEFAULT");
        }
        else
        {
@@ -257,7 +266,7 @@ void Game::HandleBrokenPipe(int sig)
                        
        }
        
-       theGame->logMessage("SIGPIPE - Broken pipe (AI program may have segfaulted)\n");
+       theGame->logMessage("SIGPIPE - Broken pipe (AI program no longer running)\n");
 
        if (Game::theGame->printBoard)
                Game::theGame->theBoard.PrintPretty(stdout, Piece::BOTH);
@@ -285,7 +294,7 @@ void Game::HandleBrokenPipe(int sig)
        else
        #endif //BUILD_GRAPHICS
        {
-               if (theGame->log == stdout)
+               if (theGame->log == stdout || theGame->log == stderr)
                {
                        theGame->logMessage( "PRESS ENTER TO EXIT\n");
                        theGame->theBoard.Print(theGame->log);
@@ -354,14 +363,17 @@ void Game::PrintEndMessage(const MovementResult & result)
                case MovementResult::POSITION_FULL:
                        logMessage("Attempted move into square occupied by neutral or allied piece\n");
                        break;
-               case MovementResult::VICTORY:
+               case MovementResult::VICTORY_FLAG:
                        logMessage("Captured the flag\n");
                        break;
+               case MovementResult::VICTORY_ATTRITION:
+                       logMessage("Destroyed all mobile enemy pieces\n");
+                       break;
                case MovementResult::BAD_RESPONSE:
                        logMessage("Unintelligable response\n");
                        break;
                case MovementResult::NO_MOVE:
-                       logMessage("Did not make a move (may have exited)\n");
+                       logMessage("Response timeout after %2f seconds.\n", timeoutTime);
                        break;
                case MovementResult::COLOUR_ERROR:
                        logMessage("Internal controller error - COLOUR_ERROR\n");
@@ -432,13 +444,29 @@ void Game::PrintEndMessage(const MovementResult & result)
                {
                        logMessage("PRESS ENTER TO EXIT\n");
                        while (fgetc(stdin) != '\n');
+                       exit(EXIT_SUCCESS); //Might want to actually exit, you foolish fool
                }
        }
 
 }
+/** Checks for victory by attrition (destroying all mobile pieces)
+ *
+ *  @returns OK for no victory, 
+ *     DRAW if both players have no pieces, or 
+ *     VICTORY_ATTRITION  if the current player has won by attrition
+ */
+MovementResult Game::CheckVictoryAttrition()
+{
+        if (theBoard.MobilePieces(Piece::OppositeColour(turn)) == 0)
+       {
+               if (theBoard.MobilePieces(turn) == 0)
+                       return MovementResult::DRAW;
+               else
+                       return MovementResult::VICTORY_ATTRITION;
+       }
+       return MovementResult::OK;
 
-
-
+}
 MovementResult Game::Play()
 {
 
@@ -451,10 +479,10 @@ MovementResult Game::Play()
        
        
 
-
+//     logMessage("Messaging red with \"START\"\n");
        red->Message("START");
        
-
+       int moveCount = 0;
 
        while (!Board::HaltResult(result) && (turnCount < maxTurns || maxTurns < 0))
        {
@@ -473,19 +501,41 @@ 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;
+               blue->Pause();
+               red->Continue();
+               if (!Board::HaltResult(result))
+               {
+                       result = CheckVictoryAttrition();
+               }
+               if (Board::HaltResult(result))
+                       break;
+
                logMessage( "%d RED: ", turnCount);
                result = red->MakeMove(buffer);
                red->Message(buffer);
                blue->Message(buffer);
                logMessage( "%s\n", buffer.c_str());
+
+               if (!Board::HaltResult(result))
+               {
+                       result = CheckVictoryAttrition();
+               }
                if (Board::HaltResult(result))
                        break;
 
-               if (stallTime > 0)
+               if (stallTime >= 0)
                        Wait(stallTime);
                else
                        ReadUserCommand();
@@ -499,35 +549,64 @@ 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
 
                
                
                turn = Piece::BLUE;
+               red->Pause();
+               blue->Continue();
+               if (!Board::HaltResult(result))
+               {
+                       result = CheckVictoryAttrition();
+               }
+               if (Board::HaltResult(result))
+                       break;
+
                logMessage( "%d BLU: ", turnCount);
                result = blue->MakeMove(buffer);
                blue->Message(buffer);
                red->Message(buffer);
                logMessage( "%s\n", buffer.c_str());
 
+               if (!Board::HaltResult(result))
+               {
+                       result = CheckVictoryAttrition();
+               }
                if (Board::HaltResult(result))
                        break;
 
-               
+               if (theBoard.MobilePieces(Piece::RED) == 0)
+                       result = MovementResult::DRAW;
 
-               
+               if (theBoard.MobilePieces(Piece::RED) == 0)
+               {
+                       if (theBoard.MobilePieces(Piece::BLUE) == 0)
+                               result = MovementResult::DRAW;
+                       else
+                               result = MovementResult::VICTORY_ATTRITION;
+                       break;                  
+               }
 
-               if (stallTime > 0)
+               if (stallTime >= 0)
                        Wait(stallTime);
                else
                        ReadUserCommand();
        
-               if (theBoard.MobilePieces(Piece::BOTH) == 0)
-                       result = MovementResult::DRAW;
+               ++moveCount;
 
                ++turnCount;
        }
@@ -569,12 +648,18 @@ int Game::logMessage(const char * format, ...)
  */
 void Game::ReadUserCommand()
 {
-       fprintf(stdout, "Waiting for user to press enter...\n");
+       fprintf(stdout, "Waiting for user to press enter... (type QUIT to exit)\n");
        string command("");
        for (char c = fgetc(stdin); c != '\n' && (int)(c) != EOF; c = fgetc(stdin))
        {
                command += c;
        }
+
+       if (command == "QUIT")
+       {
+               fprintf(stdout, "Ordered to quit... exiting...\n");
+               exit(EXIT_SUCCESS);
+       }
 }
 
 MovementResult FileController::QuerySetup(const char * opponentName, std::string setup[])
@@ -610,17 +695,195 @@ MovementResult FileController::QuerySetup(const char * opponentName, std::string
 
 MovementResult FileController::QueryMove(std::string & buffer)
 {
+       //This bit is kind of hacky and terrible, and yes I am mixing C with C++
+       //Yes I should have used fstream for the whole thing and it would be much easier.
+       //Oh well.
+
        char buf[BUFSIZ];
 
        fgets(buf, sizeof(buf), file);
        char * s = (char*)(buf);
        while (*s != ':' && *s != '\0')
                ++s;
-
-       s += 2;
        
+       //Move forward to the start of the move information
+       for (int i=0; i < 2; ++i)
+       {
+               if (*s != '\0' && *s != '\n')
+                       ++s;
+       }
+       
+       //Unfortunately we can't just copy the whole line
        buffer = string(s);
+       //We have to remove the movement result tokens
+       
+
+       vector<string> tokens;
+       Game::Tokenise(tokens, buffer, ' ');
+       buffer.clear();
+
+       if (tokens.size() < 1)
+               return MovementResult::BAD_RESPONSE;
+       buffer += tokens[0];
+
+       
+       if (tokens[0] == "NO_MOVE") //tokens[0] is either the x coordinate, or "NO_MOVE"
+               return MovementResult::OK;
+       if (tokens.size() < 2)
+               return MovementResult::BAD_RESPONSE;
+       buffer += " ";
+       buffer += tokens[1]; //The y coordinate
+       buffer += " ";
+       buffer += tokens[2]; //The direction
+       
+       //Check for a possible multiplier. If tokens[3] is an integer it will be the multiplier, otherwise it won't be.
+       if (tokens.size() > 3 && atoi(tokens[3].c_str()) != 0)
+       {
+               buffer += " ";
+               buffer += tokens[3];
+       }
+       else
+       {
+               //(tokens[3] should include a new line)
+               //buffer += "\n";
+       }
+
+       
+
+       
+       
+       
        return MovementResult::OK;
 }
 
+/**
+ * Tokenise a string
+ */
+int Game::Tokenise(std::vector<string> & buffer, std::string & str, char split)
+{
+       string token = "";
+       for (unsigned int x = 0; x < str.size(); ++x)
+       {
+               if (str[x] == split && token.size() > 0)
+               {
+                       buffer.push_back(token);
+                       token = "";
+               }
+               if (str[x] != split)
+                       token += str[x];
+       }
+       if (token.size() > 0)
+               buffer.push_back(token);
+       return buffer.size();
+}
+
+/**
+ * Creates Controller baseds off strings. Takes into account controllers other than AI_Controller.
+ * @param redPath - Either the path to an AI_Controller compatable executable, or one of %human or %network or %network:[IP_ADDRESS]
+ * @param bluePath - Ditto
+ * Sets this->red to a controller using redPath, and this->blue to a controller using bluePath
+ * TODO: Make nicer (this function should be ~half the length)
+ */
+void Game::MakeControllers(const char * redPath, const char * bluePath)
+{
+       Network * redNetwork = NULL;
+       Network * blueNetwork = NULL;
+       //To allow for running two network controllers (I don't know why you would, but beside the point...) use two ports
+       static const int port1 = 4560;
+       static const int port2 = 4561;
+
+       if (redPath[0] == '@')
+       {
+               if (strcmp(redPath, "@human") == 0)
+                       red = new Human_Controller(Piece::RED, graphicsEnabled);
+               else
+               {
+                       const char * network = strstr(redPath, "@network");
+                       if (network == NULL)
+                       {
+                               red = NULL;
+                               return;
+                       }
+                       network = strstr(network, ":");
+               
+                       if (network == NULL)
+                       {
+                               logMessage("Creating server for red AI... ");
+                               redNetwork = new Server(port1);
+                               logMessage("Successful!\n");
+
+                       }
+                       else
+                       {
+                               network = (const char*)(network+1);
+                               logMessage("Creating client for red AI... ");
+                               redNetwork = new Client(network, port2);
+                               logMessage("Connected to address %s\n", network);
+                       }
+
+                       logMessage("    (Red's responses will be received over the connection)\n");
+                       red = new NetworkReceiver(Piece::RED, redNetwork);
+               }               
+       }
+       else
+               red = new AI_Controller(Piece::RED, redPath, timeoutTime);
+
+       if (bluePath[0] == '@')
+       {
+               if (strcmp(bluePath, "@human") == 0)
+                       blue = new Human_Controller(Piece::BLUE, graphicsEnabled);
+               else
+               {
+                       const char * network = strstr(bluePath, "@network");
+                       if (network == NULL)
+                       {
+                               blue = NULL;
+                               return;
+                       }
+                       network = strstr(network, ":");
+               
+                       if (network == NULL)
+                       {
+                               logMessage("Creating server for blue AI... ");
+                               blueNetwork = new Server(port2);
+                               logMessage("Successful!\n");
+
+                       }
+                       else
+                       {
+                               network = (const char*)(network+1);
+                               logMessage("Creating client for blue AI... ");
+                               blueNetwork = new Client(network, port1);
+                               logMessage("Connected to address %s\n", network);
+                       }
+                       logMessage("    (Blue's responses will be received over the connection)\n");
+                       blue = new NetworkReceiver(Piece::BLUE, blueNetwork);
+               }               
+       }
+       else
+               blue = new AI_Controller(Piece::BLUE, bluePath, timeoutTime);
+
+       if (redNetwork != NULL)
+       {
+               
+               blue = new NetworkSender(Piece::BLUE,blue, redNetwork);
+               logMessage("    (Blue's responses will be copied over the connection)\n");
+       }
+       if (blueNetwork != NULL)
+       {
+               
+               red = new NetworkSender(Piece::RED, red, blueNetwork);
+               logMessage("    (Red's responses will be copied over the connection)\n");
+       }
+
+       red->FixName(); blue->FixName();
+       
+}
+
+string itostr(int i)
+{
+       stringstream s;
+       s << i;
+       return s.str();
+}
 

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