From: Sam Moore Date: Wed, 7 Dec 2011 04:32:56 +0000 (+0800) Subject: Modified manager output/protocol, added "basic" AI, made Asmodeus better X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=2120cc40abf9e3fd763c84a1e09b61064bb40be6;p=progcomp2012.git Modified manager output/protocol, added "basic" AI, made Asmodeus better Thats all --- diff --git a/manager/controller.cpp b/manager/controller.cpp index b7ab105..e68c1dc 100644 --- a/manager/controller.cpp +++ b/manager/controller.cpp @@ -80,6 +80,16 @@ MovementResult Controller::MakeMove(string & buffer) if (query != MovementResult::OK) return query; + if (buffer == "NO_MOVE") + { + buffer += " OK"; + return MovementResult::OK; + } + if (buffer == "SURRENDER") + { + buffer += " OK"; + return MovementResult::SURRENDER; + } int x; int y; string direction=""; stringstream s(buffer); @@ -149,7 +159,7 @@ MovementResult Controller::MakeMove(string & buffer) } if (Game::theGame->allowIllegalMoves && !Board::LegalResult(moveResult)) - return MovementResult::OK; //HACK - Legal results returned! + return MovementResult::OK; //HACK - Illegal results returned as legal! else return moveResult; diff --git a/manager/controller.h b/manager/controller.h index 1a8ce02..24f0c0d 100644 --- a/manager/controller.h +++ b/manager/controller.h @@ -21,7 +21,7 @@ class Controller - void Message(std::string & buffer) {Message(buffer.c_str());} + void Message(const std::string & buffer) {Message(buffer.c_str());} virtual void Message(const char * string) = 0; virtual MovementResult QuerySetup(const char * opponentName, std::string setup[]) = 0; diff --git a/manager/game.cpp b/manager/game.cpp index 49c2c90..b4fcc62 100644 --- a/manager/game.cpp +++ b/manager/game.cpp @@ -74,7 +74,16 @@ Game::~Game() fclose(input); } -bool Game::Setup(const char * redName, const char * blueName) +/** + * Attempts to setup the board and controllers + * @param redName the name of the red AI + * @param blueName the name of the blue AI + * @returns A colour, indicating if there were any errors + Piece::NONE indicates no errors + Piece::BOTH indicates errors with both AI + Piece::RED / Piece::BLUE indicates an error with only one of the two AI + */ +Piece::Colour Game::Setup(const char * redName, const char * blueName) { if (!red->Valid()) @@ -85,8 +94,16 @@ bool Game::Setup(const char * redName, const char * blueName) { logMessage("Controller for Player BLUE is invalid!\n"); } - if (!red->Valid() || !blue->Valid()) - return false; + if (!red->Valid()) + { + if (!blue->Valid()) + return Piece::BOTH; + return Piece::RED; + } + else if (!blue->Valid()) + { + return Piece::BLUE; + } for (int y = 4; y < 6; ++y) { @@ -104,35 +121,38 @@ bool Game::Setup(const char * redName, const char * blueName) MovementResult redSetup = red->Setup(blueName); MovementResult blueSetup = blue->Setup(redName); + + Piece::Colour result = Piece::NONE; if (redSetup != MovementResult::OK) { if (blueSetup != MovementResult::OK) { logMessage("BOTH players give invalid setup!\n"); - red->Message("ILLEGAL"); - blue->Message("ILLEGAL"); + result = Piece::BOTH; } else { logMessage("Player RED gave an invalid setup!\n"); - red->Message("ILLEGAL"); - blue->Message("DEFAULT"); + result = Piece::RED; } - return false; + } else if (blueSetup != MovementResult::OK) { logMessage("Player BLUE gave an invalid setup!\n"); - red->Message("DEFAULT"); - blue->Message("ILLEGAL"); - return false; + result = Piece::BLUE; } logMessage("%s RED SETUP\n", red->name.c_str()); for (int y=0; y < 4; ++y) { for (int x=0; x < theBoard.Width(); ++x) - logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, y)->type)]); + { + if (theBoard.GetPiece(x, y) != NULL) + logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, y)->type)]); + else + logMessage("."); + } logMessage("\n"); } @@ -145,7 +165,7 @@ bool Game::Setup(const char * redName, const char * blueName) } - return true; + return result; } @@ -241,18 +261,25 @@ void Game::HandleBrokenPipe(int sig) void Game::PrintEndMessage(const MovementResult & result) { - if (turn == Piece::RED) - { - logMessage("Game ends on RED's turn - REASON: "); - } - else if (turn == Piece::BLUE) + if (turnCount == 0) { - logMessage("Game ends on BLUE's turn - REASON: "); + logMessage("Game ends in the SETUP phase - REASON: "); } else { - logMessage("Game ends on ERROR's turn - REASON: "); + if (turn == Piece::RED) + { + logMessage("Game ends on RED's turn - REASON: "); + } + else if (turn == Piece::BLUE) + { + logMessage("Game ends on BLUE's turn - REASON: "); + } + else + { + logMessage("Game ends on ERROR's turn - REASON: "); + } } switch (result.type) { @@ -287,7 +314,7 @@ void Game::PrintEndMessage(const MovementResult & result) logMessage("Selected unit cannot move that way\n"); break; case MovementResult::POSITION_FULL: - logMessage("Attempted move into square occupied by allied piece\n"); + logMessage("Attempted move into square occupied by neutral or allied piece\n"); break; case MovementResult::VICTORY: logMessage("Captured the flag\n"); @@ -304,9 +331,32 @@ void Game::PrintEndMessage(const MovementResult & result) case MovementResult::ERROR: logMessage("Internal controller error - Unspecified ERROR\n"); break; - case MovementResult::DRAW: + case MovementResult::DRAW_DEFAULT: logMessage("Game declared a draw after %d turns\n", turnCount); break; + case MovementResult::DRAW: + logMessage("Game declared a draw because neither player has mobile pieces\n"); + break; + case MovementResult::SURRENDER: + logMessage("This player has surrendered!\n"); + break; + case MovementResult::BAD_SETUP: + switch (turn) + { + case Piece::RED: + logMessage("An illegal setup was made by RED\n"); + break; + case Piece::BLUE: + logMessage("An illegal setup was made by BLUE\n"); + break; + case Piece::BOTH: + logMessage("An illegal setup was made by BOTH players\n"); + break; + case Piece::NONE: + logMessage("Unknown internal error.\n"); + break; + } + break; } @@ -350,7 +400,7 @@ void Game::PrintEndMessage(const MovementResult & result) MovementResult Game::Play() { - + MovementResult result = MovementResult::OK; turnCount = 1; string buffer; @@ -359,7 +409,7 @@ MovementResult Game::Play() red->Message("START"); //logMessage("START\n"); - while (Board::LegalResult(result) && (turnCount < maxTurns || maxTurns < 0)) + while (!Board::HaltResult(result) && (turnCount < maxTurns || maxTurns < 0)) { @@ -369,7 +419,7 @@ MovementResult Game::Play() red->Message(buffer); blue->Message(buffer); logMessage( "%s\n", buffer.c_str()); - if (!Board::LegalResult(result)) + if (Board::HaltResult(result)) break; if (graphicsEnabled) theBoard.Draw(reveal); @@ -389,7 +439,7 @@ MovementResult Game::Play() red->Message(buffer); logMessage( "%s\n", buffer.c_str()); - if (!Board::LegalResult(result)) + if (Board::HaltResult(result)) break; @@ -406,13 +456,15 @@ MovementResult Game::Play() Wait(stallTime); + if (theBoard.MobilePieces(Piece::BOTH) == 0) + result = MovementResult::DRAW; + ++turnCount; } if ((maxTurns >= 0 && turnCount >= maxTurns) && result == MovementResult::OK) { - result = MovementResult::DRAW; - turn = Piece::BOTH; + result = MovementResult::DRAW_DEFAULT; } diff --git a/manager/game.h b/manager/game.h index f6d7b6b..48926ea 100644 --- a/manager/game.h +++ b/manager/game.h @@ -21,7 +21,7 @@ class Game void Wait(double wait); - bool Setup(const char * redName, const char * blueName); + Piece::Colour Setup(const char * redName, const char * blueName); MovementResult Play(); void PrintEndMessage(const MovementResult & result); @@ -30,6 +30,7 @@ class Game const Piece::Colour Turn() const {return turn;} + void ForceTurn(const Piece::Colour & newTurn) {turn = newTurn;} int TurnCount() const {return turnCount;} static Game * theGame; diff --git a/manager/main.cpp b/manager/main.cpp index 01a7fab..44f8fd3 100644 --- a/manager/main.cpp +++ b/manager/main.cpp @@ -10,9 +10,9 @@ using namespace std; -void CreateGame(int argc, char ** argv); +Piece::Colour SetupGame(int argc, char ** argv); void DestroyGame(); -void PrintResults(const MovementResult & result); +void PrintResults(const MovementResult & result, string & buffer); int main(int argc, char ** argv) { @@ -26,22 +26,40 @@ int main(int argc, char ** argv) exit(EXIT_SUCCESS); } - CreateGame(argc, argv); - if (Game::theGame == NULL) + + + Piece::Colour setupError = SetupGame(argc, argv); + MovementResult result = MovementResult::OK; + if (setupError == Piece::NONE) { - fprintf(stderr, "ERROR: Couldn't create a game!\n"); - exit(EXIT_FAILURE); + result = Game::theGame->Play(); } - - MovementResult result = Game::theGame->Play(); + else + { + result = MovementResult::BAD_SETUP; + Game::theGame->ForceTurn(setupError); + } + Game::theGame->PrintEndMessage(result); - PrintResults(result); + + string buffer = ""; + PrintResults(result, buffer); + + //Message the AI's the quit message + Game::theGame->red->Message("QUIT " + buffer); + Game::theGame->blue->Message("QUIT " + buffer); + + //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; } -void CreateGame(int argc, char ** argv) +Piece::Colour SetupGame(int argc, char ** argv) { char * red = NULL; char * blue = NULL; double timeout = 0.00001; bool graphics = false; bool allowIllegal = false; FILE * log = NULL; Piece::Colour reveal = Piece::BOTH; char * inputFile = NULL; int maxTurns = 5000; bool printBoard = false; @@ -54,7 +72,7 @@ void CreateGame(int argc, char ** argv) case 't': if (argc - ii <= 1) { - fprintf(stderr, "Expected timeout value after -t switch!\n"); + fprintf(stderr, "ARGUMENT_ERROR - Expected timeout value after -t switch!\n"); exit(EXIT_FAILURE); } timeout = atof(argv[ii+1]); @@ -73,12 +91,12 @@ void CreateGame(int argc, char ** argv) case 'o': if (argc - ii <= 1) { - fprintf(stderr, "Expected filename or \"stdout\" after -o switch!\n"); + fprintf(stderr, "ARGUMENT_ERROR - Expected filename or \"stdout\" after -o switch!\n"); exit(EXIT_FAILURE); } if (log != NULL) { - fprintf(stderr, "Expected at most ONE -o switch!\n"); + fprintf(stderr, "ARGUMENT_ERROR - Expected at most ONE -o switch!\n"); exit(EXIT_FAILURE); } if (strcmp(argv[ii+1], "stdout") == 0) @@ -105,10 +123,10 @@ void CreateGame(int argc, char ** argv) case 'm': if (argc - ii <= 1) { - fprintf(stderr, "Expected max_turns value after -m switch!\n"); + fprintf(stderr, "ARGUMENT_ERROR - Expected max_turns value after -m switch!\n"); exit(EXIT_FAILURE); } - if (strcmp(argv[ii+1], "inf")) + if (strcmp(argv[ii+1], "inf") == 0) maxTurns = -1; else maxTurns = atoi(argv[ii+1]); @@ -117,12 +135,12 @@ void CreateGame(int argc, char ** argv) case 'f': if (argc - ii <= 1) { - fprintf(stderr, "Expected filename after -f switch!\n"); + fprintf(stderr, "ARGUMENT_ERROR - Expected filename after -f switch!\n"); exit(EXIT_FAILURE); } if (log != NULL) { - fprintf(stderr, "Expected at most ONE -f switch!\n"); + fprintf(stderr, "ARGUMENT_ERROR - Expected at most ONE -f switch!\n"); exit(EXIT_FAILURE); } red = (char*)("file"); @@ -144,7 +162,7 @@ void CreateGame(int argc, char ** argv) } else { - fprintf(stderr, "Unrecognised switch \"%s\"...\n", argv[ii]); + fprintf(stderr, "ARGUMENT_ERROR - Unrecognised switch \"%s\"...\n", argv[ii]); exit(EXIT_FAILURE); } } @@ -158,67 +176,93 @@ void CreateGame(int argc, char ** argv) blue = argv[ii]; else { - fprintf(stderr, "Unexpected argument \"%s\"...\n", argv[ii]); + fprintf(stderr, "ARGUMENT_ERROR - Unexpected argument \"%s\"...\n", argv[ii]); exit(EXIT_FAILURE); } } } + + if (inputFile == NULL) { + if (red == NULL || blue == NULL) //Not enough arguments + { + fprintf(stderr, "ARGUMENT_ERROR - Did not recieve enough players (did you mean to use the -f switch?)\n"); + exit(EXIT_FAILURE); + } Game::theGame = new Game(red,blue, graphics, timeout, allowIllegal,log, reveal,maxTurns, printBoard); } else { Game::theGame = new Game(inputFile, graphics, timeout, allowIllegal,log, reveal,maxTurns, printBoard); } - if (!Game::theGame->Setup(red, blue)) + + if (Game::theGame == NULL) { - fprintf(stdout, "NONE %d\n",Game::theGame->TurnCount()); - exit(EXIT_SUCCESS); + fprintf(stderr,"INTERNAL_ERROR - Error creating Game!\n"); + exit(EXIT_FAILURE); } - atexit(DestroyGame); + + return Game::theGame->Setup(red, blue); + } -void PrintResults(const MovementResult & result) +void PrintResults(const MovementResult & result, string & buffer) { - Piece::Colour winner = Game::theGame->Turn(); - if (Board::LegalResult(result)) - { - if (winner == Piece::BOTH) - winner = Piece::NONE; - else - { - if (winner == Piece::RED) - winner = Piece::BLUE; - else - winner = Piece::RED; - } - } - - - switch (winner) + stringstream s(""); + switch (Game::theGame->Turn()) { case Piece::RED: - fprintf(stdout, "%s RED %d\n", Game::theGame->red->name.c_str(),Game::theGame->TurnCount()); - Game::theGame->logMessage("%s RED %d\n", Game::theGame->red->name.c_str(),Game::theGame->TurnCount()); + s << Game::theGame->red->name << " RED "; break; case Piece::BLUE: - fprintf(stdout, "%s BLUE %d\n", Game::theGame->blue->name.c_str(),Game::theGame->TurnCount()); - Game::theGame->logMessage("%s BLUE %d\n", Game::theGame->blue->name.c_str(),Game::theGame->TurnCount()); + s << Game::theGame->blue->name << " BLUE "; break; case Piece::BOTH: - fprintf(stdout, "DRAW %d\n",Game::theGame->TurnCount()); - Game::theGame->logMessage("DRAW %d\n",Game::theGame->TurnCount()); + s << "neither BOTH "; break; case Piece::NONE: - fprintf(stdout, "NONE %d\n",Game::theGame->TurnCount()); - Game::theGame->logMessage("NONE %d\n",Game::theGame->TurnCount()); + s << "neither NONE "; break; + } + if (!Board::LegalResult(result) && result != MovementResult::BAD_SETUP) + s << "ILLEGAL "; + else if (!Board::HaltResult(result)) + s << "INTERNAL_ERROR "; + else + { + switch (result.type) + { + case MovementResult::VICTORY: + s << "VICTORY "; + break; + case MovementResult::SURRENDER: + s << "SURRENDER "; + break; + case MovementResult::DRAW: + s << "DRAW "; + break; + case MovementResult::DRAW_DEFAULT: + s << "DRAW_DEFAULT "; + break; + case MovementResult::BAD_SETUP: + s << "BOTH_ILLEGAL "; + break; + default: + s << "INTERNAL_ERROR "; + break; + } } + + s << Game::theGame->TurnCount() << " " << Game::theGame->theBoard.TotalPieceValue(Piece::RED) << " " << Game::theGame->theBoard.TotalPieceValue(Piece::BLUE); + + buffer = s.str(); + + } void DestroyGame() diff --git a/manager/manual.txt b/manager/manual.txt index 55a8f05..e7a15d1 100644 --- a/manager/manual.txt +++ b/manager/manual.txt @@ -64,7 +64,7 @@ OPTIONS GAME RULES - Each player sets up 40 pieces on the Board. The pieces consist of the following: + Each player controls up to 40 pieces on the Board. The pieces consist of the following: Piece Name Rank Number Abilities 1 Marshal 1 1 Dies if attacked by Spy @@ -77,16 +77,24 @@ GAME RULES 8 Miner 8 5 Destroys Bombs without being killed 9 Scout 9 8 May move more through multiple empty squares s Spy 10 1 If the Spy attacks the Marshal, the Marshal dies - B Bomb NA 6 Immobile. If an enemy piece (except a Miner) encounters a Bomb, both pieces are destroyed - F Flag NA 1 Immobile. If any enemy piece encounters the Flag, the controlling player wins. + B Bomb NA 6 Immobile. If any piece (except a Miner) encounters an enemy Bomb, both pieces are destroyed + F Flag NA 1 Immobile. If any piece encounters the enemy Flag, the controlling player wins. + + Additional pieces, not controlled by the player: + Piece Name Number Notes + + Obstacle 8 Immobile. Do not belong to either player. Can't be passed through. + # Enemy Piece 0 - 40 Indicates that the position on the board is occupied by an enemy piece. + . Empty NA Indicates that the position on the board is empty. + + Players take turns to move their pieces. RED begins the game. - Pieces may move one square horizontally or vertically unless otherwise stated. - Pieces may not move through squares occupied by allied pieces. - Pieces may move into squares occupied by enemy pieces, in which case the piece with the lower rank (higher number) is destroyed. + Pieces may only move one square horizontally or vertically unless otherwise stated. + Pieces may not move through squares occupied by allied pieces, or Obstacle (+) pieces. + Pieces may move into squares occupied by Enemy Pieces (#), in which case the piece with the lower rank (higher number) is destroyed. Each player's pieces are hidden from the other player. When two pieces encounter each other, the ranks will be revealed. - The objective is to destroy all enemy pieces or capture the Flag. + The objective is to destroy all Enemy Pieces (#) or capture the Enemy Flag (also #). PROTOCOL @@ -100,32 +108,46 @@ PROTOCOL RESPONSE: 4 lines, each of length BOARD_WIDTH, of characters. Each character represents a piece. The characters are shown above. 2. TURN - QUERY: START | CONFIRMATION + QUERY: START | CONFIRMATION + BOARD_STATE + On the first turn, "START" is printed to the Red player. On subsequent turns, the CONFIRMATION of the opponent's last turn is printed (see below). - RESPONSE: X Y DIRECTION [MULTIPLIER=1] + BOARD_STATE consists of a BOARD_HEIGHT lines of length BOARD_WIDTH characters, each of which represents a single piece + as described in the GAME_RULES section. Each line ends with the newline character. + + + RESPONSE: X Y DIRECTION [MULTIPLIER=1] | NO_MOVE X and Y are the coords (starting from 0) of the piece to move DIRECTION is either UP, DOWN, LEFT or RIGHT MULTIPLIER is optional and only valid for units of type Scout. Scouts may move through any number of unblocked squares in one direction. - CONFIRMATION: X Y DIRECTION [MULTIPLIER=1] OUTCOME + The AI program should print "NO_MOVE" if it is unable to determine a move. + This will typically occur when the only pieces belonging to the AI program are Bombs and the Flag. + + CONFIRMATION: X Y DIRECTION [MULTIPLIER=1] OUTCOME | NO_MOVE | QUIT RESULT OUTCOME may be either OK, ILLEGAL, KILLS or DIES OK - Move was successful ILLEGAL - Move was not allowed. If stratego was not started with the -i switch, the game will end. KILLS ATTACKER_RANK DEFENDER_RANK - The piece moved into an occupied square and killed the defender. DIES ATTACKER_RANK DEFENDER_RANK - The piece moved into an occupied square and was killed by the defender. + A confirmation of "NO_MOVE" occurs when the AI program made no move for a legitimate reason. + "NO_MOVE ILLEGAL" is printed if the AI program made no move for an illegitimate reason. + + If both AI programs successively make a "NO_MOVE" response, then the game will end. + The player with the highest piece value will win, or a draw will be declared if the values are equal. + + 3. END GAME - QUERY: VICTORY | DEFEAT | ILLEGAL | DEFAULT - VICTORY - This player won the game - DEFEAT - The other player won the game - ILLEGAL - Game ended because this player made a bad response or timed out - (NOTE: Even if the -i option is provided, the game may end with an ILLEGAL signal if a bad response is made) - DEFAULT - Game ended because the other player made a bad response. - - No response is necessary; the program should exit or it will be sent a SIGKILL signal. + If the CONFIRMATION line is of the form: + QUIT RESULT + Then the game is about to end. + + If present, RESULT will be a direct copy of the message to stdout described in the EXIT/OUTPUT section below. + 4. TIMEOUTS If a program fails to respond to a query within 2 (two) seconds, the game will end and that AI will be sent the ILLEGAL result. @@ -136,15 +158,30 @@ PROTOCOL EXIT/OUTPUT If the game ends due to a player either winning, or making an illegal move, stratego will print one of the following result messages to stdout. - 1. - WINNING_NAME WINNING_COLOUR TURNS - 2. - DRAW TURNS - When the result was a draw + NAME COLOUR OUTCOME TURN_NUMBER OUTCOME RED_PIECE_VALUE BLUE_PIECE_VALUE + + Where: + NAME is the name of the player on whose turn the game ended, + COLOUR is the colour of that player, + OUTCOME is one of the following: + VICTORY - The indicated player won + DEFEAT - The indicated player lost + SURRENDER - The indicated player surrendered + DRAW - The game ended in a draw because neither player moved + DRAW_DEFAULT - The game ended in a draw because the maximum number of moves was exceeded + ILLEGAL - The indicated player loses due to an Illegal move/response + DEFAULT - The indicated player wins by default due to the other player making an Illegal move/response + BOTH_ILLEGAL - Both players made an Illegal move/response. Usually occurs due to simultaneous setup errors, or bad executable paths. + INTERNAL_ERROR - The game ended, even though it shouldn't have. + + TURN_NUMBER is the number of turns that elapsed before the game ended + + RED_PIECE_VALUE and BLUE_PIECE_VALUE are the summed piece values of the pieces of RED and BLUE respectively. + Bombs and Flags are worth zero, and the ranked pieces (Spys -> Marshal) are worth (11 - rank). + So the Spy is worth 1 point, ... the Marshal is worth 10. - 3. - NONE TURNS - When for some reason both the AI programs failed to respond or crashed. + (The initial piece values can be determined by running with -m 0) + stratego will then return exit code 0. diff --git a/manager/movementresult.h b/manager/movementresult.h index 695a1e2..5a2aed6 100644 --- a/manager/movementresult.h +++ b/manager/movementresult.h @@ -13,7 +13,7 @@ class Piece; class MovementResult { public: - typedef enum {OK, DIES, KILLS, BOTH_DIE, NO_BOARD, INVALID_POSITION, NO_SELECTION, NOT_YOUR_UNIT, IMMOBILE_UNIT, INVALID_DIRECTION, POSITION_FULL, VICTORY, BAD_RESPONSE, NO_MOVE, COLOUR_ERROR, ERROR, DRAW} Type; + typedef enum {OK, DIES, KILLS, BOTH_DIE, NO_BOARD, INVALID_POSITION, NO_SELECTION, NOT_YOUR_UNIT, IMMOBILE_UNIT, INVALID_DIRECTION, POSITION_FULL, VICTORY, SURRENDER, BAD_RESPONSE, NO_MOVE, COLOUR_ERROR, ERROR, DRAW_DEFAULT, DRAW, BAD_SETUP} Type; MovementResult(const Type & result = OK, const Piece::Type & newAttackerRank = Piece::NOTHING, const Piece::Type & newDefenderRank = Piece::NOTHING) : type(result), attackerRank(newAttackerRank), defenderRank(newDefenderRank) {} diff --git a/manager/program.cpp b/manager/program.cpp index acf501e..3fca369 100644 --- a/manager/program.cpp +++ b/manager/program.cpp @@ -22,8 +22,18 @@ 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 + { + pid = -1; + return; + } - int readPipe[2]; int writePipe[2]; assert(pipe(readPipe) == 0); assert(pipe(writePipe) == 0); @@ -47,8 +57,8 @@ Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0 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 + //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 { @@ -69,17 +79,28 @@ Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0 */ Program::~Program() { - if (kill(pid, 0) == 0) //Check if the process created is still running... + if (Running()) //Check if the process created is still running... { fputc(EOF, output); //If it was, tell it to stop with EOF - usleep(500000); //Give it 1/2 a second to respond... - if (kill(pid, 0) == 0) //Check if its still running + + TimerThread timer(2); //Wait for 2 seconds + timer.Start(); + while (!timer.Finished()) { - kill(pid, 9); //Slay the infidel mercilessly! + if (!Running()) + { + timer.Stop(); + break; + } } + timer.Stop(); + kill(pid, SIGKILL); + } + if (pid > 0) + { + fclose(input); + fclose(output); } - fclose(input); - fclose(output); } @@ -95,7 +116,7 @@ Program::~Program() */ bool Program::SendMessage(const char * print, ...) { - if (kill(pid, 0) != 0) //Is the process running... + if (!Running()) //Is the process running... return false; va_list ap; @@ -123,7 +144,7 @@ bool Program::SendMessage(const char * print, ...) */ bool Program::GetMessage(string & buffer, double timeout) { - if (kill(pid, 0) != 0) + if (!Running()) return false; assert(&buffer != NULL); @@ -164,7 +185,7 @@ bool Program::GetMessage(string & buffer, double timeout) */ bool Program::Running() const { - return (kill(pid,0) == 0); + return (pid > 0 && kill(pid,0) == 0); } diff --git a/manager/program.h b/manager/program.h index bddde0f..8fef696 100644 --- a/manager/program.h +++ b/manager/program.h @@ -32,6 +32,7 @@ class Program private: pid_t pid; //Process ID of the program wrapped + }; #endif //PROGRAM_H diff --git a/manager/stratego.cpp b/manager/stratego.cpp index d84819f..ed38e35 100644 --- a/manager/stratego.cpp +++ b/manager/stratego.cpp @@ -65,7 +65,7 @@ Piece::Type Piece::GetType(char fromToken) * @param newWidth - the width of the board * @param newHeight - the height of the board */ -Board::Board(int newWidth, int newHeight) : winner(Piece::NONE), width(newWidth), height(newHeight), board(NULL) +Board::Board(int newWidth, int newHeight) : winner(Piece::NONE), width(newWidth), height(newHeight), board(NULL), pieces() { board = new Piece**[width]; for (int x=0; x < width; ++x) @@ -269,6 +269,8 @@ bool Board::AddPiece(int x, int y, const Piece::Type & newType, const Piece::Col Piece * piece = new Piece(newType, newColour); board[x][y] = piece; + + pieces.push_back(piece); return true; } @@ -374,7 +376,7 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m { if (target->type == Piece::MINER) { - + RemovePiece(defender); delete defender; board[x][y] = NULL; board[x2][y2] = target; @@ -382,6 +384,8 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m } else { + RemovePiece(defender); + RemovePiece(target); delete defender; delete target; board[x][y] = NULL; @@ -391,6 +395,7 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m } else if (defender->type == Piece::MARSHAL && target->type == Piece::SPY) { + RemovePiece(defender); delete defender; board[x][y] = NULL; board[x2][y2] = target; @@ -398,6 +403,7 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m } else if (target->operator > (*defender)) { + RemovePiece(defender); delete defender; board[x][y] = NULL; board[x2][y2] = target; @@ -405,6 +411,7 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m } else if (target->operator==(*defender) && rand() % 2 == 0) { + RemovePiece(defender); delete defender; board[x][y] = NULL; board[x2][y2] = target; @@ -412,6 +419,7 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m } else { + RemovePiece(target); delete target; board[x][y] = NULL; return MovementResult(MovementResult::DIES, attackerType, defenderType); @@ -424,5 +432,77 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m return MovementResult(MovementResult::OK); } +/** + * Removes a piece from the board + * @param piece The piece to remove + * @returns true iff the piece actually existed + */ +bool Board::RemovePiece(Piece * piece) +{ + bool result = false; + for (int x = 0; x < width; ++x) + { + for (int y = 0; y < height; ++y) + { + if (board[x][y] == piece) + { + result = true; + board[x][y] = NULL; + } + } + } + + vector::iterator i = pieces.begin(); + while (i != pieces.end()) + { + if ((*i) == piece) + { + i = pieces.erase(i); + result = true; + continue; + } + ++i; + } + return result; +} + +/** + * Returns the total value of pieces belonging to colour + * @param colour the colour + * @returns the total value of pieces belonging to colour. + * (Redundant repetition <3) + */ +int Board::TotalPieceValue(const Piece::Colour & colour) const +{ + int result = 0; + for (vector::const_iterator i = pieces.begin(); i != pieces.end(); ++i) + { + if ((*i)->colour == colour || colour == Piece::BOTH) + { + result += (*i)->PieceValue(); + } + } + return result; +} + +/** + * Returns the total number of mobile pieces belonging to colour + * @param colour the colour + * @returns the total value of mobile pieces belonging to colour. + * (Redundant repetition <3) + */ +int Board::MobilePieces(const Piece::Colour & colour) const +{ + int result = 0; + for (vector::const_iterator i = pieces.begin(); i != pieces.end(); ++i) + { + if ((*i)->colour == colour || colour == Piece::BOTH) + { + if ((*i)->type <= Piece::MARSHAL && (*i)->type >= Piece::SPY) + result++; + } + } + return result; +} diff --git a/manager/stratego.h b/manager/stratego.h index 39e8873..929f870 100644 --- a/manager/stratego.h +++ b/manager/stratego.h @@ -10,6 +10,7 @@ #include "graphics.h" #include "array.h" +#include /** * Contains classes for a game of Stratego @@ -25,9 +26,10 @@ class Piece typedef enum {ERROR=14,BOMB=13,MARSHAL=12, GENERAL=11, COLONEL=10, MAJOR=9, CAPTAIN=8, LIEUTENANT=7, SERGEANT=6, MINER=5, SCOUT=4, SPY=3, FLAG=2,BOULDER=1, NOTHING=0} Type; //Type basically defines how strong the piece is + typedef enum {RED=0, BLUE=1, NONE=2, BOTH=3} Colour; //Used for the allegiance of the pieces - terrain counts as NONE. - Piece(const Type & newType, const Colour & newColour) : type(newType), colour(newColour) {} + Piece(const Type & newType, const Colour & newColour) : type(newType), colour(newColour), beenRevealed(false) {} virtual ~Piece() {} @@ -46,11 +48,14 @@ class Piece static Type GetType(char fromToken); + int PieceValue() const {if (type == BOMB || type == FLAG) {return 0;} return (int)(type) - (int)(SPY) + 1;} //Attributes of the piece const Type type; const Colour colour; + bool beenRevealed; + public: class TextureManager : public Graphics::TextureManager, private Array @@ -118,8 +123,13 @@ class Board static bool LegalResult(const MovementResult & result) { - return (result == MovementResult::OK || result == MovementResult::DIES || result == MovementResult::KILLS || result == MovementResult::BOTH_DIE || result == MovementResult::VICTORY || result == MovementResult::DRAW); - } + return (result == MovementResult::OK || result == MovementResult::DIES || result == MovementResult::KILLS || result == MovementResult::BOTH_DIE || result == MovementResult::VICTORY || result == MovementResult::DRAW || result == MovementResult::DRAW_DEFAULT || result == MovementResult::SURRENDER); + } + + static bool HaltResult(const MovementResult & result) + { + return (result == MovementResult::VICTORY || result == MovementResult::DRAW || result == MovementResult::DRAW_DEFAULT || result == MovementResult::SURRENDER || !LegalResult(result)); + } MovementResult MovePiece(int x, int y, const Direction & direction, int multiplier=1,const Piece::Colour & colour=Piece::NONE); //Move piece from position in direction @@ -128,10 +138,15 @@ class Board int Width() const {return width;} int Height() const {return height;} + + int MobilePieces(const Piece::Colour & colour) const; + int TotalPieceValue(const Piece::Colour & colour) const; + bool RemovePiece(Piece * piece); private: int width; int height; Piece ** * board; + std::vector pieces; }; #endif //STRATEGO_H diff --git a/samples/asmodeus/asmodeus.py b/samples/asmodeus/asmodeus.py old mode 100755 new mode 100644 index 2f83db6..29c50a2 --- a/samples/asmodeus/asmodeus.py +++ b/samples/asmodeus/asmodeus.py @@ -14,201 +14,68 @@ git git.ucc.asn.au/progcomp2012.git ''' -import sys -import random - -ranks = ('B','1','2','3','4','5','6','7','8','9','s','F', '?', '+') - -def move(x, y, direction): - if direction == "UP": - return (x,y-1) - elif direction == "DOWN": - return (x,y+1) - elif direction == "LEFT": - return (x-1, y) - elif direction == "RIGHT": - return (x+1, y) - print "Error in move!" - return (x,y) - -def oppositeColour(colour): - if colour == "RED": - return "BLUE" - elif colour == "BLUE": - return "RED" - else: - return "NONE" - -class Piece: - def __init__(self, colour, rank, x, y): - self.colour = colour - self.rank = rank - self.x = x - self.y = y - - - - -class Asmodeus: - def __init__(self): - #sys.stderr.write("Asmodeus __init__ here...\n"); - self.turn = 0 - self.board = [] - self.units = [] - if self.Setup(): - while self.MoveCycle(): #derp derp derp - pass - +from basic_python import * +from path import * + - def Setup(self): - #sys.stderr.write("Asmodeus Setup here...\n"); - setup = sys.stdin.readline().split(' ') - self.colour = setup[0] - self.opponentName = setup[1] - self.width = int(setup[2]) - self.height = int(setup[3]) - for x in range(0, self.width): - self.board.append([]) - for y in range(0, self.height): - self.board[x].append(None) - if self.colour == "RED": - print "FB8sB479B8\nBB31555583\n6724898974\n967B669999" - elif self.colour == "BLUE": - print "967B669999\n6724898974\nBB31555583\nFB8sB479B8" - return True - - def MoveCycle(self): - #sys.stderr.write("Asmodeus MakeMove here...\n"); - if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False: - return False - self.turn += 1 - return self.InterpretResult() + +class Asmodeus(BasicAI): + " A slightly more advanced python based AI who calculates the optimum score for each move " + def __init__(self): + #sys.stderr.write("Asmodeus initialised...\n") + BasicAI.__init__(self) + self.riskScores = {'1' : 0.01 , '2' : 0.05 , '3' : 0.15 , '4' : 0.2, '5' : 0.2, '6' : 0.25, '7' : 0.25, '8' : 0.01 , '9' : 0.4, 's' : 0.01} + self.bombScores = {'1' : 0.0 , '2' : 0.0 , '3' : 0.05 , '4' : 0.1, '5' : 0.3, '6' : 0.4, '7' : 0.5, '8' : 1 , '9' : 0.6, 's' : 0.1} + self.flagScores = {'1' : 1.0 , '2' : 1.0 , '3' : 1.0 , '4' : 1.0, '5' : 1.0, '6' : 1.0, '7' : 1, '8' : 1.0 , '9' : 1.0, 's' : 1.0} + self.suicideScores = {'1' : 0.0 , '2' : 0.0 , '3' : 0.0, '4' : 0.0, '5' : 0.0, '6' : 0.05, '7' : 0.1, '8' : 0.0 , '9' : 0.0, 's' : 0.0} + self.killScores = {'1' : 1.0 , '2' : 0.9 , '3' : 0.8 , '4' : 0.5, '5' : 0.5, '6' : 0.5, '7' : 0.4, '8' : 0.9 , '9' : 0.6, 's' : 0.9} def MakeMove(self): - #TODO: Over-ride this function in base classes with more complex move behaviour - - #sys.stderr.write("Asmodeus MakeMove here...\n") - #self.debugPrintBoard() - while True: - if len(self.units) <= 0: - return False - piece = random.choice(self.units) - if piece == None: - continue - if piece.rank == '?' or piece.rank == 'B' or piece.rank == 'F': - continue + #sys.stderr.write("Asmodeus MakingMove...\n") + "Over-rides the default BasicAI.MakeMove function" - direction = random.choice(("UP", "DOWN", "LEFT", "RIGHT")) - p = move(piece.x, piece.y, direction) - if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height: - if self.board[p[0]][p[1]] == None or self.board[p[0]][p[1]].colour == oppositeColour(self.colour): - print str(piece.x) + " " + str(piece.y) + " " + direction - break - return True - - - def ReadBoard(self): - #sys.stderr.write("Asmodeus ReadBoard here...\n"); - for y in range(0,self.height): - row = sys.stdin.readline() - for x in range(0,len(row)-1): - if self.turn == 0: - if row[x] == '.': - pass - elif row[x] == '#': - self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y) - elif row[x] == '+': - self.board[x][y] = Piece("NONE", '+', x, y) - else: - self.board[x][y] = Piece(self.colour, row[x],x,y) - self.units.append(self.board[x][y]) - else: - pass - return True - + moveList = [] + + for unit in self.units: + if unit.mobile() == False: + continue - def InterpretResult(self): - #sys.stderr.write("Asmodeus InterpretResult here...\n") - result = sys.stdin.readline().split(' ') - #sys.stderr.write(" Read status line \"" + str(result) + "\"\n") - if self.turn == 0: - return True + for enemy in self.enemyUnits: + if enemy == unit: + continue + path = PathFinder().pathFind((unit.x, unit.y), (enemy.x, enemy.y), self.board) - x = int(result[0].strip()) - y = int(result[1].strip()) + #sys.stderr.write("Computed path: " + str(path) + "\n") + if path == False or len(path) <= 0: + continue + score = self.CalculateScore(unit, enemy) + score = float(score / float(len(path) + 1)) + moveList.append([unit, path, enemy, score]) - #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n") - direction = result[2].strip() - outcome = result[3].strip() - p = move(x,y,direction) - - if outcome == "OK": - self.board[p[0]][p[1]] = self.board[x][y] - self.board[x][y].x = p[0] - self.board[x][y].y = p[1] - - self.board[x][y] = None - elif outcome == "KILLS": - if self.board[p[0]][p[1]] == None: - return False - - if self.board[p[0]][p[1]].colour == self.colour: - self.units.remove(self.board[p[0]][p[1]]) - self.board[x][y].x = p[0] - self.board[x][y].y = p[1] - - - self.board[p[0]][p[1]] = self.board[x][y] - self.board[x][y].rank = result[4] - - self.board[x][y] = None - - elif outcome == "DIES": - if self.board[p[0]][p[1]] == None: - return False - - if self.board[x][y].colour == self.colour: - self.units.remove(self.board[x][y]) - - self.board[p[0]][p[1]].rank = result[5] - self.board[x][y] = None - elif outcome == "BOTHDIE": - if self.board[p[0]][p[1]] == None: - return False - - - if self.board[x][y].colour == self.colour: - self.units.remove(self.board[x][y]) - if self.board[p[0]][p[1]].colour == self.colour: - self.units.remove(self.board[p[0]][p[1]]) - self.board[p[0]][p[1]] = None - self.board[x][y] = None - elif outcome == "FLAG": - #sys.stderr.write(" Game over!\n") - return False - elif outcome == "ILLEGAL": - #sys.stderr.write(" Illegal move!\n") - return False + if len(moveList) <= 0: + #sys.stderr.write("NO Moves!\n") + return BasicAI.MakeMove(self) + + moveList.sort(key = lambda e : e[len(e)-1], reverse=True) + + #sys.stderr.write("Chosen move is: " + str(moveList[0][0].x) + " " + str(moveList[0][0].y) + " " + moveList[0][1][0] + " (targeting enemy with rank " + moveList[0][2].rank + " at position " + str(moveList[0][2].x) + " " + str(moveList[0][2].y) + " (my rank " + moveList[0][0].rank+")\n") + print str(moveList[0][0].x) + " " + str(moveList[0][0].y) + " " + moveList[0][1][0] + return True + + def CalculateScore(self, attacker, defender): + if defender.rank == '?': + return self.riskScores[attacker.rank] + elif defender.rank == 'B': + return self.bombScores[attacker.rank] + elif defender.rank == 'F': + return self.flagScores[attacker.rank] + elif defender.valuedRank() < attacker.valuedRank() or defender.rank == '1' and attacker.rank == 's': + return self.killScores[defender.rank] else: - #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n"); - return False - - #sys.stderr.write(" Completed interpreting move!\n"); - return True - - def debugPrintBoard(self): - for y in range(0, self.height): - for x in range(0, self.width): - if self.board[x][y] == None: - sys.stderr.write("."); - else: - sys.stderr.write(str(self.board[x][y].rank)); - sys.stderr.write("\n") - - - -asmodeus = Asmodeus() + return self.suicideScores[attacker.rank] + + diff --git a/samples/asmodeus/asmodeus.pyc b/samples/asmodeus/asmodeus.pyc new file mode 100644 index 0000000..2555c0f Binary files /dev/null and b/samples/asmodeus/asmodeus.pyc differ diff --git a/samples/asmodeus/basic_python.py b/samples/asmodeus/basic_python.py new file mode 120000 index 0000000..3d6b342 --- /dev/null +++ b/samples/asmodeus/basic_python.py @@ -0,0 +1 @@ +../basic_python/basic_python.py \ No newline at end of file diff --git a/samples/asmodeus/basic_python.pyc b/samples/asmodeus/basic_python.pyc new file mode 100644 index 0000000..bca3a0a Binary files /dev/null and b/samples/asmodeus/basic_python.pyc differ diff --git a/samples/asmodeus/info b/samples/asmodeus/info index da91d83..2ce2124 100644 --- a/samples/asmodeus/info +++ b/samples/asmodeus/info @@ -1 +1 @@ -asmodeus.py +run.py diff --git a/samples/asmodeus/path.py b/samples/asmodeus/path.py new file mode 100644 index 0000000..3f08979 --- /dev/null +++ b/samples/asmodeus/path.py @@ -0,0 +1,57 @@ + +import sys +import random + + +class PathFinder: + def __init__(self): + self.visited = [] + + pass + + def pathFind(self, start, end, board): + if start[0] == end[0] and start[1] == end[1]: + #sys.stderr.write("Got to destination!\n") + return [] + + if self.visited.count(start) > 0: + #sys.stderr.write("Back track!!\n") + return False + if start[0] < 0 or start[0] >= len(board) or start[1] < 0 or start[1] >= len(board[start[0]]): + #sys.stderr.write("Out of bounds!\n") + return False + if len(self.visited) > 0 and board[start[0]][start[1]] != None: + #sys.stderr.write("Full position!\n") + return False + + + + self.visited.append(start) + left = (start[0]-1, start[1]) + right = (start[0]+1, start[1]) + up = (start[0], start[1]-1) + down = (start[0], start[1]+1) + choices = [left, right, up, down] + choices.sort(key = lambda e : random.randint(0,5)) + options = [] + for point in choices: + option = [point, self.pathFind(point,end,board)] + if option[1] != False: + options.append(option) + + options.sort(key = lambda e : len(e[1])) + if len(options) == 0: + #sys.stderr.write("NO options!\n") + return False + else: + if options[0][0] == left: + options[0][1].insert(0,"LEFT") + elif options[0][0] == right: + options[0][1].insert(0,"RIGHT") + elif options[0][0] == up: + options[0][1].insert(0,"UP") + elif options[0][0] == down: + options[0][1].insert(0,"DOWN") + #sys.stderr.write("PathFind got path " + str(options[0]) + "\n") + return options[0][1] + diff --git a/samples/asmodeus/path.pyc b/samples/asmodeus/path.pyc new file mode 100644 index 0000000..3afa749 Binary files /dev/null and b/samples/asmodeus/path.pyc differ diff --git a/samples/asmodeus/run.py b/samples/asmodeus/run.py new file mode 100755 index 0000000..b646d06 --- /dev/null +++ b/samples/asmodeus/run.py @@ -0,0 +1,8 @@ +#!/usr/bin/python -u + +from asmodeus import * + +asmodeus = Asmodeus() +if asmodeus.Setup(): + while asmodeus.MoveCycle(): + pass diff --git a/samples/basic_cpp/Makefile b/samples/basic_cpp/Makefile new file mode 100644 index 0000000..0a249b3 --- /dev/null +++ b/samples/basic_cpp/Makefile @@ -0,0 +1,30 @@ +#Makefile for basic_cpp +# Sample C++ Stratego AI +# UCC Programming Competition 2012 + +CPP = g++ -Wall -pedantic -lSDL -lGL -g +OBJ = basic_cpp.o + +BIN = basic_cpp + + + +$(BIN) : $(OBJ) + $(CPP) -o $(BIN) $(OBJ) + + + + +%.o : %.cpp %.h + $(CPP) -c $< + +clean : + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + +#Cleans up all backup files +clean_full: + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + $(RM) *.*~ + $(RM) *~ + + diff --git a/samples/basic_cpp/basic_cpp b/samples/basic_cpp/basic_cpp new file mode 100755 index 0000000..a56a036 Binary files /dev/null and b/samples/basic_cpp/basic_cpp differ diff --git a/samples/basic_cpp/basic_cpp.cpp b/samples/basic_cpp/basic_cpp.cpp new file mode 100644 index 0000000..ab00826 --- /dev/null +++ b/samples/basic_cpp/basic_cpp.cpp @@ -0,0 +1,560 @@ +/** + * "basic_cpp", a sample Stratego AI for the UCC Programming Competition 2012 + * Implementations of main function, and Helper functions + * + * @author Sam Moore (matches) [SZM] + * @website http://matches.ucc.asn.au/stratego + * @email progcomp@ucc.asn.au or matches@ucc.asn.au + * @git git.ucc.asn.au/progcomp2012.git + */ + +#include "basic_cpp.h" //Needs class Base_Cpp and the includes in this file + +using namespace std; + +/** + * The characters used to represent various pieces + * NOTHING, BOULDER, FLAG, SPY, SCOUT, MINER, SERGEANT, LIETENANT, CAPTAIN, MAJOR, COLONEL, GENERAL, MARSHAL, BOMB, UNKNOWN + */ +char Piece::tokens[] = {'.','*','F','s','9','8','7','6','5','4','3','2','1','B','?'}; + +/** + * Gets a rank from the character used to represent it + * Basic lookup of Piece::tokens + */ +Rank Piece::GetRank(char token) +{ + for (int ii=0; ii <= 14; ++ii) + { + if (tokens[ii] == token) + return (Rank)(ii); + } + return UNKNOWN; +} + +/** + * IMPLEMENTATION of Helper FOLLOWS + */ + +/** + * Convert string to direction + */ +Direction Helper::StrToDir(const string & dir) +{ + if (dir == "UP") + return UP; + else if (dir == "DOWN") + return DOWN; + else if (dir == "LEFT") + return LEFT; + else if (dir == "RIGHT") + return RIGHT; + else + return DIRECTION_ERROR; +} + +/** + * Direction to String + */ +void Helper::DirToStr(const Direction & dir, std::string & buffer) +{ + switch (dir) + { + case UP: + buffer = "UP"; + break; + case DOWN: + buffer = "DOWN"; + break; + case LEFT: + buffer = "LEFT"; + break; + case RIGHT: + buffer = "RIGHT"; + break; + default: + buffer = "DIRECTION_ERROR"; + break; + } +} + +/** + * Move a point in a direction + */ +void Helper::MoveInDirection(int & x, int & y, const Direction & dir, int multiplier) +{ + switch (dir) + { + case UP: + y -= multiplier; + break; + case DOWN: + y += multiplier; + break; + case LEFT: + x -= multiplier; + break; + case RIGHT: + x += multiplier; + break; + default: + break; + } + +} + +/** + * Tokenise a string + */ +int Helper::Tokenise(std::vector & 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(); +} + +/** + * Convert string to integer + */ +int Helper::Integer(std::string & fromStr) +{ + stringstream s(fromStr); + int result = 0; + s >> result; + return result; +} + +/** + * Read in a line from stdin + */ +void Helper::ReadLine(std::string & buffer) +{ + buffer = ""; + for (char c = cin.get(); c != '\n' && cin.good(); c = cin.get()) + { + buffer += c; + } +} + +/** + * IMPLEMENTATION of Board FOLLOWS + */ + +/** + * Constructer for Board + */ +Board::Board(int w, int h) : width(w), height(h), board(NULL) +{ + //Construct 2D array of P*'s + board = new Piece**[width]; + for (int x=0; x < width; ++x) + { + board[x] = new Piece*[height]; + for (int y=0; y < height; ++y) + board[x][y] = NULL; + } +} + +/** + * Destructor for board + */ +Board::~Board() +{ + //Destroy the 2D array of P*'s + for (int x=0; x < width; ++x) + { + for (int y=0; y < height; ++y) + delete board[x][y]; + delete [] board[x]; + } +} + +/** + * Retrieves a piece on the Board + * @param x x coordinate + * @param y y coordinate + * @returns A Piece* for the piece, or NULL if there is no piece at the point given + */ +Piece * Board::Get(int x, int y) const +{ + if (ValidPosition(x, y)) + return board[x][y]; + return NULL; +} +/** + * Sets a piece on the Board + * @param x x coordinate + * @param y y coordinate + * @param newPiece + * @param returns newPiece if successful, NULL if not + */ +Piece * Board::Set(int x, int y, Piece * newPiece) +{ + if (!ValidPosition(x, y)) + return NULL; + board[x][y] = newPiece; + assert(Get(x,y) == newPiece); + return newPiece; +} + +/** + * IMPLEMENTATION of Base_Cpp FOLLOWS + */ + +/** + * Constructor for AI + */ +BasicAI::BasicAI() : turn(0), board(NULL), units(), enemyUnits(), colour(NONE), colourStr("") +{ + srand(time(NULL)); + cin.rdbuf()->pubsetbuf(NULL, 0); + cout.rdbuf()->pubsetbuf(NULL, 0); +} + +/** + * Destructor for AI + */ +BasicAI::~BasicAI() +{ + if (board != NULL) + delete board; +} + + +/** + * Setup the AI + * @returns true if successful, false on error + */ +bool BasicAI::Setup() +{ + + cin >> colourStr; + + + std::string opponentName(""); //opponentName is unused, just read it + cin >> opponentName; + + int width = 0; int height = 0; + cin >> width; cin >> height; + + while(cin.get() != '\n' && cin.good()); //trim newline + + board = new Board(width, height); + + if (colourStr == "RED") + { + colour = RED; + cout << "FB8sB479B8\nBB31555583\n6724898974\n967B669999\n"; + } + else if (colourStr == "BLUE") + { + colour = BLUE; + cout << "967B669999\n6724898974\nBB31555583\nFB8sB479B8\n"; + } + else + return false; + + return (board != NULL); +} + +/** + * Performs a move, including the saving states bits + * @returns true if the game is to continue, false if it is to end + */ +bool BasicAI::MoveCycle() +{ + //cerr << "BasicAI at MoveCycle()\n"; + if (!InterpretResult()) + return false; + if (!ReadBoard()) + return false; + if (!MakeMove()) + return false; + + turn++; + return InterpretResult(); +} + +/** + * Interprets the result of a move. Ignores the first move + * @returns true if successful, false if there was an error + */ +bool BasicAI::InterpretResult() +{ + //cerr << "BasicAI at InterpretResult()\n"; + if (turn == 0) + { + while (cin.get() != '\n' && cin.good()); + return true; + } + + + string resultLine; Helper::ReadLine(resultLine); + vector tokens; Helper::Tokenise(tokens, resultLine, ' '); + + if (tokens.size() <= 0) + { + //cerr << "No tokens!\n"; + return false; + } + + if (tokens[0] == "QUIT") + { + return false; + } + + if (tokens[0] == "NO_MOVE") + { + return true; + + } + + if (tokens.size() < 4) + { + //cerr << "Only got " << tokens.size() << " tokens\n"; + return false; + } + + + int x = Helper::Integer(tokens[0]); + int y = Helper::Integer(tokens[1]); + + + + Direction dir = Helper::StrToDir(tokens[2]); + string & outcome = tokens[3]; + + int x2 = x; int y2 = y; Helper::MoveInDirection(x2,y2,dir); + + Piece * attacker = board->Get(x,y); + if (attacker == NULL) + { + //cerr << "No attacker!\n"; + return false; + } + Piece * defender = board->Get(x2,y2); + if (outcome == "OK") + { + board->Set(x2,y2, attacker); + board->Set(x,y,NULL); + attacker->x = x2; attacker->y = y2; + } + else if (outcome == "KILLS") + { + if (defender == NULL) + { + //cerr << "No defender!\n"; + return false; + } + + board->Set(x2,y2, attacker); + board->Set(x,y,NULL); + attacker->x = x2; attacker->y = y2; + + attacker->rank = Piece::GetRank(tokens[4][0]); + ForgetUnit(defender); + } + else if (outcome == "DIES") + { + if (defender == NULL) + { + //cerr << "No defender!\n"; + return false; + } + + + board->Set(x,y,NULL); + defender->rank = Piece::GetRank(tokens[5][0]); + ForgetUnit(attacker); + + + } + else if (outcome == "BOTHDIE") + { + board->Set(x,y,NULL); + board->Set(x2,y2, NULL); + + ForgetUnit(attacker); + ForgetUnit(defender); + } + else if (outcome == "FLAG") + { + //cerr << "BasicAI - Flag was captured, exit!\n"; + return false; + } + else if (outcome == "ILLEGAL") + { + //cerr << "BasicAI - Illegal move, exit!\n"; + return false; + } + + //cerr << "BasicAI finished InterpretResult()\n"; + return true; +} + +/** + * Performs a random move + * TODO: Overwrite with custom move + * @returns true if a move could be made (including NO_MOVE), false on error + */ +bool BasicAI::MakeMove() +{ + //cerr << "BasicAI at MakeMove()\n"; + if (units.size() <= 0) + { + //cerr << " No units!\n"; + return false; + + } + + int index = rand() % units.size(); + int startIndex = index; + while (true) + { + + + Piece * piece = units[index]; + if (piece != NULL && piece->Mobile()) + { + int dirIndex = rand() % 4; + int startDirIndex = dirIndex; + while (true) + { + int x = piece->x; int y = piece->y; + assert(board->Get(x,y) == piece); + Helper::MoveInDirection(x,y,(Direction)(dirIndex)); + if (board->ValidPosition(x,y)) + { + Piece * target = board->Get(x,y); + if (target == NULL || (target->colour != piece->colour && target->colour != NONE)) + { + string dirStr; + Helper::DirToStr((Direction)(dirIndex), dirStr); + cout << piece->x << " " << piece->y << " " << dirStr << "\n"; + return true; + } + } + + dirIndex = (dirIndex + 1) % 4; + if (dirIndex == startDirIndex) + break; + } + } + + index = (index+1) % (units.size()); + if (index == startIndex) + { + cout << "NO_MOVE\n"; + return true; + } + } + return true; +} + +/** + * Reads in the board + * On first turn, sets up Board + * On subsquent turns, takes no action + * @returns true on success, false on error + */ +bool BasicAI::ReadBoard() +{ + //cerr << "BasicAI at ReadBoard()\n"; + for (int y = 0; y < board->Height(); ++y) + { + string row; + Helper::ReadLine(row); + for (unsigned int x = 0; x < row.size(); ++x) + { + if (turn == 0) + { + switch (row[x]) + { + case '.': + break; + case '#': + board->Set(x,y, new Piece(x,y,Piece::Opposite(colour), UNKNOWN)); + enemyUnits.push_back(board->Get(x,y)); + break; + case '+': + board->Set(x,y, new Piece(x,y,NONE, BOULDER)); + break; + default: + board->Set(x,y,new Piece(x,y,colour, Piece::GetRank(row[x]))); + units.push_back(board->Get(x,y)); + break; + } + } + } + } + return true; +} + +/** + * Removes a piece from memory + * @param piece The piece to delete + * @returns true if the piece was actually found + */ +bool BasicAI::ForgetUnit(Piece * piece) +{ + //cerr << "BasicAI at ForgetUnit()\n"; + bool result = false; + vector::iterator i = units.begin(); + while (i != units.end()) + { + if ((*i) == piece) + { + i = units.erase(i); result = true; + continue; + } + ++i; + } + + i = enemyUnits.begin(); + while (i != enemyUnits.end()) + { + if ((*i) == piece) + { + i = enemyUnits.erase(i); result = true; + continue; + } + ++i; + } + + + delete piece; + return result; +} + + +/** + * The main function + * @param argc + * @param argv + * @returns zero on success, non-zero on failure + */ +int main(int argc, char ** argv) +{ + + srand(time(NULL)); + + BasicAI * basicAI = new BasicAI(); + if (basicAI->Setup()) + { + while (basicAI->MoveCycle()); + } + delete basicAI; + exit(EXIT_SUCCESS); + return 0; +} diff --git a/samples/basic_cpp/basic_cpp.h b/samples/basic_cpp/basic_cpp.h new file mode 100644 index 0000000..733d9a7 --- /dev/null +++ b/samples/basic_cpp/basic_cpp.h @@ -0,0 +1,145 @@ +/** + * "basic_cpp", a sample Stratego AI for the UCC Programming Competition 2012 + * Declarations for classes Piece, Board and Basic_Cpp + * @author Sam Moore (matches) [SZM] + * @website http://matches.ucc.asn.au/stratego + * @email progcomp@ucc.asn.au or matches@ucc.asn.au + * @git git.ucc.asn.au/progcomp2012.git + */ + +#ifndef BASIC_CPP_H +#define BASIC_CPP_H + +#include +#include +#include +#include +#include +#include + + + + + +/** + * enum for possible ranks of pieces + */ +typedef enum {UNKNOWN=14,BOMB=13,MARSHAL=12, GENERAL=11, COLONEL=10, MAJOR=9, CAPTAIN=8, LIEUTENANT=7, SERGEANT=6, MINER=5, SCOUT=4, SPY=3, FLAG=2,BOULDER=1, NOTHING=0} Rank; + +/** + * enum for possible colours of pieces and the AI + */ +typedef enum {RED=0, BLUE=1, NONE, BOTH} Colour; + + +/** + * Class to represent a piece on the board + * TODO: Add features required for Pieces as used by your AI + * For example, can replace the single member "rank" with two, "maxRank" and "minRank" + * Or add an array of probability values for EVERY rank! + */ +class Piece +{ + public: + static char tokens[]; //The tokens used to identify various pieces + + Piece(int newX, int newY,const Colour & newColour, const Rank & newRank = UNKNOWN) + : x(newX), y(newY), colour(newColour), rank(newRank) {} + virtual ~Piece() {} + + bool Mobile() const {return rank != BOMB && rank != FLAG;} + + static Colour Opposite(const Colour & colour) {return colour == RED ? BLUE : RED;} + + int x; int y; + const Colour colour; //The colour of the piece + Rank rank; //The rank of the piece + + static Rank GetRank(char token); //Helper to get rank from character + +}; + +/** + * enum for Directions that a piece can move in + */ +typedef enum {UP=0, DOWN=1, LEFT=2, RIGHT=3, DIRECTION_ERROR=4} Direction; + +/** + * Class to represent a board + */ +class Board +{ + public: + Board(int width, int height); //Construct a board with width and height + virtual ~Board(); //Destroy the board + + Piece * Get(int x, int y) const; //Retrieve single piece + Piece * Set(int x, int y, Piece * newPiece); //Add piece to board + + int Width() const {return width;} //getter for width + int Height() const {return height;} //getter for height + + bool ValidPosition(int x, int y) const {return (x >= 0 && x < width && y >= 0 && y < height);} //Helper - is position within board? + private: + + int width; //The width of the board + int height; //The height of the board + Piece ** * board; //The pieces on the board +}; + +/** + * Basic AI class + * TODO: Make sure that if Piece is changed, BasicAI is updated to be consistent + * TODO: More complex AI's should inherit off this class. + * It is recommended that only MakeMove is altered - hence the other functions are not virtual. + */ +class BasicAI +{ + public: + BasicAI(); //Constructor + virtual ~BasicAI(); //Destructor + + bool Setup(); //Implements setup protocol + bool MoveCycle(); //Implements the MoveCycle protocol + bool ReadBoard(); //Reads the board as part of the MoveCycle protocol + bool InterpretResult(); //Interprets the result of a move + virtual bool MakeMove(); //Makes a move - defaults to randomised moves + bool DebugPrintBoard(); //DEBUG - Prints the board to stderr + protected: + int turn; + Board * board; //The board + std::vector units; //Allied units + std::vector enemyUnits; //Enemy units + + bool ForgetUnit(Piece * forget); //Delete and forget about a unit + + Colour colour; + std::string colourStr; +}; + +/** + * Purely static class of Helper functions + */ +class Helper +{ + public: + static Direction StrToDir(const std::string & str); //Helper - Convert string to a Direction enum + static void DirToStr(const Direction & dir, std::string & buffer); //Helper - Convert Direction enum to a string + static void MoveInDirection(int & x, int & y, const Direction & dir, int multiplier = 1); //Helper - Move a point in a direction + static int Tokenise(std::vector & buffer, std::string & str, char split = ' '); //Helper - Split a string into tokens + static int Integer(std::string & fromStr); //Helper - convert a string to an integer + static void ReadLine(std::string & buffer); //Helper - read a line from stdin + private: + //By making these private we ensure that no one can create an instance of Helper + //Yes we could use namespaces instead, but I prefer this method because you can use private static member variables + //Not that I am. But you can. + Helper() {} + ~Helper() {} +}; + + + +#endif //BASIC_CPP_H + +//EOF + diff --git a/samples/basic_cpp/info b/samples/basic_cpp/info new file mode 100644 index 0000000..ee1b502 --- /dev/null +++ b/samples/basic_cpp/info @@ -0,0 +1 @@ +basic_cpp diff --git a/samples/basic_python/basic_python.py b/samples/basic_python/basic_python.py new file mode 100644 index 0000000..be88d56 --- /dev/null +++ b/samples/basic_python/basic_python.py @@ -0,0 +1,292 @@ +#!/usr/bin/python -u + +#NOTE: The -u option is required for unbuffered stdin/stdout. +# If stdin/stdout are buffered, the manager program will not recieve any messages and assume that the agent has timed out. + +""" + basic_python.py - A sample Stratego AI for the UCC Programming Competition 2012 + + Written in python, the slithery language + Simply makes random moves, as long as possible + + author Sam Moore (matches) [SZM] + website http://matches.ucc.asn.au/stratego + email progcomp@ucc.asn.au or matches@ucc.asn.au + git git.ucc.asn.au/progcomp2012.git +""" + +import sys +import random + +ranks = ['B','1','2','3','4','5','6','7','8','9','s','F', '?', '+'] + +def move(x, y, direction): + """ Moves point (x,y) in direction, returns a pair """ + if direction == "UP": + return (x,y-1) + elif direction == "DOWN": + return (x,y+1) + elif direction == "LEFT": + return (x-1, y) + elif direction == "RIGHT": + return (x+1, y) + return (x,y) + + + +def oppositeColour(colour): + """ Returns the opposite colour to that given """ + if colour == "RED": + return "BLUE" + elif colour == "BLUE": + return "RED" + else: + return "NONE" + +class Piece: + """ Class representing a piece + Pieces have colour, rank and co-ordinates + """ + def __init__(self, colour, rank, x, y): + self.colour = colour + self.rank = rank + self.x = x + self.y = y + self.lastMoved = -1 + + def mobile(self): + return self.rank != 'F' and self.rank != 'B' and self.rank != '?' and self.rank != '+' + + def valuedRank(self): + if ranks.count(self.rank) > 0: + return len(ranks) - 2 - ranks.index(self.rank) + else: + return 0 + + + + +class BasicAI: + """ + BasicAI class to play a game of stratego + Implements the protocol correctly. Stores the state of the board in self.board + Only makes random moves. + Override method "MakeMove" for more complex moves + """ + def __init__(self): + """ Constructs the BasicAI agent, and starts it playing the game """ + #sys.stderr.write("BasicAI __init__ here...\n"); + self.turn = 0 + self.board = [] + self.units = [] + self.enemyUnits = [] + + + + def Setup(self): + """ Implements Setup part of protocol. Always uses the same setup. Override to create custom setups """ + #sys.stderr.write("BasicAI Setup here...\n"); + setup = sys.stdin.readline().split(' ') + self.colour = setup[0] + self.opponentName = setup[1] + self.width = int(setup[2]) + self.height = int(setup[3]) + for x in range(0, self.width): + self.board.append([]) + for y in range(0, self.height): + self.board[x].append(None) + if self.colour == "RED": + print "FB8sB479B8\nBB31555583\n6724898974\n967B669999" + elif self.colour == "BLUE": + print "967B669999\n6724898974\nBB31555583\nFB8sB479B8" + return True + + def MoveCycle(self): + #sys.stderr.write("BasicAI MakeMove here...\n"); + if self.InterpretResult() == False or self.ReadBoard() == False or self.MakeMove() == False: + return False + self.turn += 1 + return self.InterpretResult() + + def MakeMove(self): + """ Randomly moves any moveable piece, or prints "NO_MOVE" if there are none """ + #TODO: Over-ride this function in base classes with more complex move behaviour + + #sys.stderr.write("BasicAI MakeMove here...\n") + #self.debugPrintBoard() + + if len(self.units) <= 0: + return False + + index = random.randint(0, len(self.units)-1) + startIndex = index + + directions = ("UP", "DOWN", "LEFT", "RIGHT") + while True: + piece = self.units[index] + if piece != None and piece.mobile(): + dirIndex = random.randint(0, len(directions)-1) + startDirIndex = dirIndex + + while True: + #sys.stderr.write("Trying index " + str(dirIndex) + "\n") + p = move(piece.x, piece.y, directions[dirIndex]) + if p[0] >= 0 and p[0] < self.width and p[1] >= 0 and p[1] < self.height: + target = self.board[p[0]][p[1]] + if target == None or (target.colour != piece.colour and target.colour != "NONE" and target.colour != "BOTH"): + print str(piece.x) + " " + str(piece.y) + " "+directions[dirIndex] + return True + dirIndex = (dirIndex + 1) % len(directions) + if startDirIndex == dirIndex: + break + + index = (index + 1) % len(self.units) + if startIndex == index: + print "NO_MOVE" + return True + + + def ReadBoard(self): + """ Reads in the board. + On the very first turn, sets up the self.board structure + On subsequent turns, the board is simply read, but the self.board structure is not updated here. + """ + #sys.stderr.write("BasicAI ReadBoard here...\n"); + for y in range(0,self.height): + row = sys.stdin.readline().strip() + if len(row) < self.width: + sys.stderr.write("Row has length " + str(len(row)) + " vs " + str(self.width) + "\n") + return False + for x in range(0,self.width): + if self.turn == 0: + if row[x] == '.': + pass + elif row[x] == '#': + self.board[x][y] = Piece(oppositeColour(self.colour), '?',x,y) + self.enemyUnits.append(self.board[x][y]) + elif row[x] == '+': + self.board[x][y] = Piece("NONE", '+', x, y) + else: + self.board[x][y] = Piece(self.colour, row[x],x,y) + self.units.append(self.board[x][y]) + else: + pass + return True + + + def InterpretResult(self): + """ Interprets the result of a move, and updates the board. + The very first move is ignored. + On subsequent moves, the self.board structure is updated + """ + #sys.stderr.write("BasicAI InterpretResult here...\n") + result = sys.stdin.readline().split(' ') + #sys.stderr.write(" Read status line \"" + str(result) + "\"\n") + if self.turn == 0: + return True + + if result[0].strip() == "QUIT": #Make sure we exit when the manager tells us to! + return False + + if result[0].strip() == "NO_MOVE": #No move was made, don't need to update anything + return True + + if len(result) < 4: #Should be at least 4 tokens (X Y DIRECTION OUTCOME) in any other case + return False + + x = int(result[0].strip()) + y = int(result[1].strip()) + + + #sys.stderr.write(" Board position " + str(x) + " " + str(y) + " is OK!\n") + + direction = result[2].strip() + outcome = result[3].strip() + + p = move(x,y,direction) + + + + if outcome == "OK": + self.board[p[0]][p[1]] = self.board[x][y] + self.board[x][y].x = p[0] + self.board[x][y].y = p[1] + + self.board[x][y] = None + elif outcome == "KILLS": + if self.board[p[0]][p[1]] == None: + return False + + if self.board[p[0]][p[1]].colour == self.colour: + self.units.remove(self.board[p[0]][p[1]]) + elif self.board[p[0]][p[1]].colour == oppositeColour(self.colour): + self.enemyUnits.remove(self.board[p[0]][p[1]]) + + self.board[x][y].x = p[0] + self.board[x][y].y = p[1] + + + self.board[p[0]][p[1]] = self.board[x][y] + self.board[x][y].rank = result[4].strip() + + self.board[x][y] = None + + elif outcome == "DIES": + if self.board[p[0]][p[1]] == None: + return False + + if self.board[x][y].colour == self.colour: + self.units.remove(self.board[x][y]) + elif self.board[x][y].colour == oppositeColour(self.colour): + self.enemyUnits.remove(self.board[x][y]) + + self.board[p[0]][p[1]].rank = result[5].strip() + self.board[x][y] = None + elif outcome == "BOTHDIE": + if self.board[p[0]][p[1]] == None: + return False + + + if self.board[x][y].colour == self.colour: + self.units.remove(self.board[x][y]) + elif self.board[x][y].colour == oppositeColour(self.colour): + self.enemyUnits.remove(self.board[x][y]) + if self.board[p[0]][p[1]].colour == self.colour: + self.units.remove(self.board[p[0]][p[1]]) + elif self.board[p[0]][p[1]].colour == oppositeColour(self.colour): + self.enemyUnits.remove(self.board[p[0]][p[1]]) + + + self.board[p[0]][p[1]] = None + self.board[x][y] = None + elif outcome == "FLAG": + #sys.stderr.write(" Game over!\n") + return False + elif outcome == "ILLEGAL": + #sys.stderr.write(" Illegal move!\n") + return False + else: + #sys.stderr.write(" Don't understand outcome \"" + outcome + "\"!\n"); + return False + + #sys.stderr.write(" Completed interpreting move!\n"); + return True + + def debugPrintBoard(self): + """ For debug purposes only. Prints the board to stderr. + Does not indicate difference between allied and enemy pieces + Unknown (enemy) pieces are shown as '?' + """ + for y in range(0, self.height): + for x in range(0, self.width): + if self.board[x][y] == None: + sys.stderr.write("."); + else: + sys.stderr.write(str(self.board[x][y].rank)); + sys.stderr.write("\n") + +#basicAI = BasicAI() +#if basicAI.Setup(): +# while basicAI.MoveCycle(): +# pass + diff --git a/samples/basic_python/basic_python.pyc b/samples/basic_python/basic_python.pyc new file mode 100644 index 0000000..4c201ef Binary files /dev/null and b/samples/basic_python/basic_python.pyc differ diff --git a/samples/basic_python/info b/samples/basic_python/info new file mode 100644 index 0000000..9260726 --- /dev/null +++ b/samples/basic_python/info @@ -0,0 +1 @@ +basic_python.py diff --git a/samples/basic_python/run.py b/samples/basic_python/run.py new file mode 100755 index 0000000..80b4cfe --- /dev/null +++ b/samples/basic_python/run.py @@ -0,0 +1,8 @@ +#!/usr/bin/python -u + +from basic_python import * + +basicAI = BasicAI() +if basicAI.Setup(): + while basicAI.MoveCycle(): + pass diff --git a/simulator/Makefile b/simulator/Makefile new file mode 100644 index 0000000..1d71ef5 --- /dev/null +++ b/simulator/Makefile @@ -0,0 +1,15 @@ +#Makefile for simulations +# 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 + + + +clean: + rm -r -f $(RESULTSDIR) + rm -r -f $(LOGDIR) diff --git a/simulator/simulate.py b/simulator/simulate.py index 5531143..60320d9 100755 --- a/simulator/simulate.py +++ b/simulator/simulate.py @@ -26,9 +26,9 @@ nGames = 10 #Number of games played by each agent against each opponent. Half wi managerPath = baseDirectory+"manager/stratego" #Path to the manager program -scores = {"WIN":3, "DRAW":2, "LOSS":1, "NONE":0} #Score dictionary -#NONE occurs if both programs crash. If only one program crashes the result is reported as a win-loss +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 +verbose = True #Make necessary directories @@ -55,15 +55,19 @@ agentNames = os.listdir(agentsDirectory) agents = [] for name in agentNames: #sys.stdout.write("\nLooking at Agent: \""+ str(name)+"\"... ") - sys.stdout.write("Scan \""+name+"\"... ") + if verbose: + sys.stdout.write("Scan \""+name+"\"... ") if os.path.isdir(agentsDirectory+name) == False: #Remove non-directories - sys.stdout.write(" Invalid! (Not a directory)\n") + 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 - sys.stdout.write(" Invalid! (No \"info\" file found)\n") + if verbose: + sys.stdout.write(" Invalid! (No \"info\" file found)\n") continue - sys.stdout.write(" Valid!") + 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) @@ -71,19 +75,17 @@ for name in agentNames: #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]) - sys.stdout.write(" (Run program \""+agentExecutable+"\")\n") + 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! ("+str(nGames)+" games per pairing)" -print "Total: " + str(len(agents)) + " valid agents found (From "+str(len(agentNames))+" possibilities)" - -print "" - -print "Commencing ROUND " + str(roundNumber) + " combat! ("+str(nGames)+" games per pairing)" -print "Points values are: "+str(scores) -print "" normalGames = 0 draws = 0 @@ -96,45 +98,47 @@ for red in agents: #for each agent playing as red, 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 - sys.stdout.write("Agents: \""+red[0]+"\" and \""+blue[0]+"\" playing game " + str(i) + "/"+str(nGames/2) + "... ") + 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(' ') - #Look at who won, and adjust scores based on that - if results[0] == "NONE": - red[2] += scores["NONE"] - blue[2] += scores["NONE"] - sys.stdout.write(" No contest. (Check AI for errors).\n") - aiErrors += 1 - elif results[0] == "DRAW": - red[2] += scores["DRAW"] - blue[2] += scores["DRAW"] - sys.stdout.write(" Draw.\n") - draws += 1 - elif results[0] == red[1]: - red[2] += scores["WIN"] - blue[2] += scores["LOSS"] - sys.stdout.write(" \""+red[0]+"\"\n") - normalGames += 1 - elif results[0] == blue[1]: - red[2] += scores["LOSS"] - blue[2] += scores["WIN"] - sys.stdout.write(" \""+blue[0]+"\"\n") - normalGames += 1 + + if len(results) != 6: + if verbose: + sys.stdout.write("Garbage output! " + outline) else: - sys.stdout.write(" Garbage output! \""+outline+"\" (log file \""+logFile+"\")\n") - managerErrors += 1 - + 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] + + if verbose: + sys.stdout.write(" " + outline) + + + + -print "Completed combat. Total of " + str(normalGames + draws + aiErrors + managerErrors) + " games played. " + +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." + print "WARNING: Recieved "+str(managerErrors)+" garbage outputs. Check the manager program." -print "" +if verbose: + print "" #We should now have complete score values. - -sys.stdout.write("Creating results files for ROUND " + str(roundNumber) + "... ") +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 @@ -142,13 +146,14 @@ resultsFile = open(resultsDirectory+"round"+str(roundNumber)+".results", "w") #C for agent in agents: resultsFile.write(agent[0] + " " + str(agent[2]) +"\n") #Write the agent names and scores into the file, in descending order -sys.stdout.write(" Complete!\n") - -sys.stdout.write("Updating total scores... "); +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"): - sys.stdout.write(" Reading from \""+resultsDirectory+"total.scores\" to update 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(' ') @@ -163,11 +168,13 @@ if os.path.exists(resultsDirectory+"total.scores"): agents.sort(key = lambda e : e[2], reverse=True) else: - sys.stdout.write(" First round - creating \""+resultsDirectory+"total.scores\"... ") -sys.stdout.write(" Complete!\n") + 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 "Finished writing results for ROUND " + str(roundNumber) -print "" print "RESULTS FOR ROUND " + str(roundNumber) print "Agent: [name, path, total_score, recent_score]"