From 2120cc40abf9e3fd763c84a1e09b61064bb40be6 Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Wed, 7 Dec 2011 12:32:56 +0800 Subject: [PATCH] Modified manager output/protocol, added "basic" AI, made Asmodeus better Thats all --- manager/controller.cpp | 12 +- manager/controller.h | 2 +- manager/game.cpp | 108 +++-- manager/game.h | 3 +- manager/main.cpp | 140 ++++--- manager/manual.txt | 89 ++-- manager/movementresult.h | 2 +- manager/program.cpp | 45 ++- manager/program.h | 1 + manager/stratego.cpp | 84 +++- manager/stratego.h | 21 +- samples/asmodeus/asmodeus.py | 239 +++-------- samples/asmodeus/asmodeus.pyc | Bin 0 -> 3232 bytes samples/asmodeus/basic_python.py | 1 + samples/asmodeus/basic_python.pyc | Bin 0 -> 9110 bytes samples/asmodeus/info | 2 +- samples/asmodeus/path.py | 57 +++ samples/asmodeus/path.pyc | Bin 0 -> 2085 bytes samples/asmodeus/run.py | 8 + samples/basic_cpp/Makefile | 30 ++ samples/basic_cpp/basic_cpp | Bin 0 -> 144981 bytes samples/basic_cpp/basic_cpp.cpp | 560 ++++++++++++++++++++++++++ samples/basic_cpp/basic_cpp.h | 145 +++++++ samples/basic_cpp/info | 1 + samples/basic_python/basic_python.py | 292 ++++++++++++++ samples/basic_python/basic_python.pyc | Bin 0 -> 9170 bytes samples/basic_python/info | 1 + samples/basic_python/run.py | 8 + simulator/Makefile | 15 + simulator/simulate.py | 111 ++--- 30 files changed, 1615 insertions(+), 362 deletions(-) mode change 100755 => 100644 samples/asmodeus/asmodeus.py create mode 100644 samples/asmodeus/asmodeus.pyc create mode 120000 samples/asmodeus/basic_python.py create mode 100644 samples/asmodeus/basic_python.pyc create mode 100644 samples/asmodeus/path.py create mode 100644 samples/asmodeus/path.pyc create mode 100755 samples/asmodeus/run.py create mode 100644 samples/basic_cpp/Makefile create mode 100755 samples/basic_cpp/basic_cpp create mode 100644 samples/basic_cpp/basic_cpp.cpp create mode 100644 samples/basic_cpp/basic_cpp.h create mode 100644 samples/basic_cpp/info create mode 100644 samples/basic_python/basic_python.py create mode 100644 samples/basic_python/basic_python.pyc create mode 100644 samples/basic_python/info create mode 100755 samples/basic_python/run.py create mode 100644 simulator/Makefile 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 0000000000000000000000000000000000000000..2555c0f6e33c16ef94c60fb495b7ecc98f699e1b GIT binary patch literal 3232 zcmb_eOK%)S5U!bBukB6zNSwqmmvj^fZ4mM1g-Ifa<2cGg!cMfCD2h?EYR~lUPCSpz zOs^AzeZlz?ApQUsu80!{P9Ts#oVXwmCj>}{6F2y(yKS!`j$XT~y1J{ruI{d?p6~x2 ztKR+Z-Slif2xeuG&dPqhaBfzGN@Ns^9^4Dy z*0ntyfi;}vz-U!;BF$|OZO587GmE)u^e=ujoD*p#ujvuc1$Sb*_rX)w(TdX`3ip8{ z2le?@aPTW7z8mh~^h>5QM2%*x%qEXO_|)U5)79~YNcbRRxGvK`h3*W+sw^PjCZ2UO z5y&{`$GDur9BtOqQPNLD5wIm+m6AahIO|FT>>0Nb+9F!x%WHca7&WusuwI$+sBFzjy(Y@EUgD^1cI@LE1G+#%lX95aRkD#IPNa8ndA z94r~`2!#xH)WRLJaL{ycB?C8Y;lLOUjN!o8J1~X=V>sY293=J*jJ*SMaOMB;4o)&0 z7{h@v99(8NIKprkWH>N}19NaggWest-pyDz?2F;R7!Hi#uxEyYCd0YSLUM2wj+tid z72MKt=lvs}-umUe7xO0S&UHTekbj?8@qFik`F&}{3vTu+D_%4vZ*%~I-*l(&_^o4bExHqv!u{W<^9d9{)*OK5hD(Y}+^wpPu-X4U_kDl2T;e^RJC& z37TKfR*5$8zo&D>=;$(=MneXsV(2infm)&nBB&LiGeX-X+A7m#*)7$ZB2SO9j66rm zKJo187MY!_G9W;y+RMMr-y8Z zt?n*1j`hbV8h53p=`q3tmq27P${{L^p2YrqPak4S_&CE2J7bUvO}{ig%;VEMv7_x< zi|Jdpfz&i{SDeMA++A1snH(`=YhV!f|#k`PC z+6Y+S#&Q#_I1GYQL-hn@2N4}JO%s2{(ncby$12@>Gt_>@q)DW=VwtRl^6FLg3eBck zuCV7qnRFI4c2@IuLLWI-Xb#pSTa99625-t3nw*C!RmsK!qr>7osk=9$G*r1+spmuk z`rObB1F)LOJZv0=g&qf4+I^S&}? zj~Cs1o;0R*4EQN~Cn~HS@5d})JU6E7r5P%?pzP;yH(rGgC)rJdTCVt2s(4f0u;F9g zuy>&(Ko9%EJ6WFc$Niboq8D%saP!d?+4?gOE(GQ~jjO-}d7OZFg^?0OGsq1)Z3}># zMsmJ1T{1PqO(O>fr%kEg5z~1>)Du|6!j_HQ3CP9*nK$5ttuEFJIEA|6U=iW=Q*qf` zy?rpQoBquPZ?16w#8!L0%v`2x4UUJ#1d~Y+6J+Z$?yIoDvmBR=Lm)LipuIartjJ)^ zg&oor5u@^by~z5s z4JJ4m47=y2>FvYQ!-eM{(pp+by`HARoMH`9A}w!9xH5 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bca3a0a3baebd8bdaddb05519ab823bbbd0dce26 GIT binary patch literal 9110 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 R+3)PnoXZpr6beHl{{|1aFtz{y literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3afa749ec31f6cbd9142a8b8d5e47a6e4a028ad8 GIT binary patch literal 2085 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 l6(z=iX3k@M{_`;knz~Lie%edsCo$U & 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 0000000000000000000000000000000000000000..4c201efce0acbd9d849a70cb74cd7174dbeeb55f GIT binary patch literal 9170 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