From: Sam Moore Date: Thu, 1 Dec 2011 10:54:44 +0000 (+0800) Subject: Mostly messing with "forfax" AI X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=041c37d1dfc4a94024fe1d329d289e4c59667885;p=progcomp2012.git Mostly messing with "forfax" AI It would be nice to have an AI that doesn't segfault. Currently segfault caused by Board::ForgetPiece. valgrind outputs a lot of wierd crap about std::vector and uninitialised values Uninitialised values created by std::vector::push_back() All I am pushing is a simple pointer (Piece*), so I don't know WHY uninitialised values happen... The std::list used in MakeMove is somehow using the same memory as the std::vectors of the board, which is causing invalid reads Stupid, stupid stdlib. I think that once that is fixed, forfax is pretty much done. I'd like to see how well it plays, but... segfaults. I also fixed dummy to take into account the modified turn protocol which prints piece ranks. dummy just reads them and ignores them. I plan to make the manager program more useful - Enable human players - Add command line arguments for things like timeouts, graphics on/off etc - Read a game from a file (so that games can be viewed after they are run) I need to go through the manager program carefully and make sure that the way AI programs quit actually works Ideally the AI program has a short period to exit gracefully before it is killed I think for some reason the AI program always just gets killed. At some point I need to setup a VM for this. I should probably do that. I also might change minor things like the tokens (from random characters to digits + a few characters) and the internal ordering of the enum Piece::Type --- diff --git a/manager/controller.cpp b/manager/controller.cpp index 736663e..de46f02 100644 --- a/manager/controller.cpp +++ b/manager/controller.cpp @@ -146,7 +146,7 @@ MovementResult Controller::MakeMove(string & buffer) //I stored the ranks in the wrong order; rank 1 is the marshal, 2 is the general etc... //So I am reversing them in the output... great work - s << (Piece::BOMB - moveResult.attackerRank) << " " << (Piece::BOMB - moveResult.defenderRank) << "\n"; + s << (Piece::BOMB - moveResult.attackerRank) << " " << (Piece::BOMB - moveResult.defenderRank); switch (moveResult.type) { case MovementResult::OK: @@ -174,9 +174,9 @@ MovementResult Controller::MakeMove(string & buffer) } - if (!Board::LegalResult(moveResult)) - return MovementResult::OK; //HACK - Legal results returned! - else + //if (!Board::LegalResult(moveResult)) + // return MovementResult::OK; //HACK - Legal results returned! + //else return moveResult; } diff --git a/manager/dummy b/manager/dummy deleted file mode 120000 index 5347eb2..0000000 --- a/manager/dummy +++ /dev/null @@ -1 +0,0 @@ -../samples/dummy \ No newline at end of file diff --git a/manager/forfax b/manager/forfax deleted file mode 120000 index a715b0f..0000000 --- a/manager/forfax +++ /dev/null @@ -1 +0,0 @@ -../samples/forfax/forfax \ No newline at end of file diff --git a/manager/main.cpp b/manager/main.cpp index 4f203d9..e3e435e 100644 --- a/manager/main.cpp +++ b/manager/main.cpp @@ -28,17 +28,19 @@ int main(int argc, char ** argv) { assert(argc == 3); - for (int y = 5; y < 9; ++y) + + for (int y = 4; y < 6; ++y) { - for (int x = 3; x < 5; ++x) + for (int x = 2; x < 4; ++x) { theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); } - for (int x = 9; x < 11; ++x) + for (int x = 6; x < 8; ++x) { theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); } } + red = new Controller(Piece::RED, argv[1]); blue = new Controller(Piece::BLUE, argv[2]); @@ -90,7 +92,7 @@ int main(int argc, char ** argv) break; #ifdef GRAPHICS Board::theBoard.Draw(); - if (CheckForQuitWhilstWaiting(0.2)) + if (CheckForQuitWhilstWaiting(0.5)) { red->SendMessage("QUIT"); blue->SendMessage("QUIT"); @@ -112,7 +114,7 @@ int main(int argc, char ** argv) #ifdef GRAPHICS Board::theBoard.Draw(); - if (CheckForQuitWhilstWaiting(0.2)) + if (CheckForQuitWhilstWaiting(0.5)) { red->SendMessage("QUIT"); blue->SendMessage("QUIT"); @@ -130,12 +132,14 @@ int main(int argc, char ** argv) printf("Final board state\n"); #ifdef GRAPHICS Board::theBoard.Draw(); + if (CheckForQuitWhilstWaiting(4)) { red->SendMessage("QUIT"); blue->SendMessage("QUIT"); exit(EXIT_SUCCESS); } + #else Board::theBoard.Print(stderr); #endif //GRAPHICS @@ -245,6 +249,16 @@ void BrokenPipe(int sig) fprintf(stderr,"Game ends on ERROR's turn - REASON: Broken pipe\n"); } + Board::theBoard.Draw(); + while (true) + { + if (CheckForQuitWhilstWaiting(4000)) + { + red->SendMessage("QUIT"); + blue->SendMessage("QUIT"); + exit(EXIT_SUCCESS); + } + } exit(EXIT_SUCCESS); } diff --git a/manager/stratego.cpp b/manager/stratego.cpp index 871d382..ba59c5f 100644 --- a/manager/stratego.cpp +++ b/manager/stratego.cpp @@ -7,7 +7,7 @@ using namespace std; /** * Static variables */ -Board Board::theBoard(14,14); +Board Board::theBoard(10,10); //nothing, boulder, flag, spy, scout, miner, sergeant, lietenant, captain, major, colonel, general, marshal, bomb, error char Piece::tokens[] = {'.','+','F','y','s','n','S','L','c','m','C','G','M','B','?'}; int Piece::maxUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; diff --git a/samples/Makefile b/samples/Makefile deleted file mode 100644 index 5a05c00..0000000 --- a/samples/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -#Makefile for the sample AI programs for UCC progcomp 2012 - -CPP = g++ -Wall -pedantic -lSDL -lGL -g - -dummy : dummy.o - $(CPP) -o dummy dummy.o - -clean : - $(RM) $(BIN) $(OBJ) $(LINKOBJ) - -clean_full: #cleans up all backup files - $(RM) $(BIN) $(OBJ) $(LINKOBJ) - $(RM) *.*~ - $(RM) *~ - - - diff --git a/samples/dummy.cpp b/samples/dummy.cpp deleted file mode 100644 index 4f9c33f..0000000 --- a/samples/dummy.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include -using namespace std; - -/** - * A suitably terrible program which combines C style IO with C++ style IO - * Enjoy! - * Mwuhahaha - */ - -int main(int argc, char ** argv) -{ - setbuf(stdout, NULL); - setbuf(stdin, NULL); - - - //Read in the colour, and choose a layout - - int width = 14; int height = 14; - - string colour; string opponent; - cin >> colour; cin >> opponent; cin >> width; cin >> height; - fgetc(stdin); - - //fprintf(stderr, "Colour is \"%s\", width and height are (%d, %d), opponent is \"%s\"\n", colour.c_str(), width, height, opponent.c_str()); - - assert(width == 14 && height == 14); //Can't deal with other sized boards - if (colour == "RED") - { - fprintf(stdout, "FB..........B.\n"); - fprintf(stdout, "BBCM....cccc.C\n"); - fprintf(stdout, "LSGmnsBmSsnsSm\n"); - fprintf(stdout, "sLSBLnLssssnyn\n"); - } - else if (colour == "BLUE") - { - fprintf(stdout, "sLSBLnLssssnyn\n"); - fprintf(stdout, "LSGmnsBmSsnsSm\n"); - fprintf(stdout, "BBCM....cccc.C\n"); - fprintf(stdout, "FB..........B.\n"); - } - else - { - return 1; - } - - - - char board[width][height]; - - vector > choices; - - int myPid = (int)(getpid()); - - while (true) - { - //fprintf(stderr, "%s [%d] looping\n", argv[0], myPid); - choices.clear(); - - // fprintf(stderr, "%s Waiting for status line...\n", colour.c_str()); - while (fgetc(stdin) != '\n') - { - //fprintf(stderr,"."); - } - //fprintf(stderr, "%s Got status, waiting for board line...\n", colour.c_str()); - - //Read in board - for (int y=0; y < height; ++y) - { - for (int x=0; x < width; ++x) - { - board[x][y] = fgetc(stdin); - if (board[x][y] == EOF) - exit(EXIT_SUCCESS); - - if (board[x][y] != '.' && board[x][y] != '*' && board[x][y] != '#' && board[x][y] != '+') - { - choices.push_back(pair(x, y)); - } - } - assert(fgetc(stdin) == '\n'); - } - - - - int dir = 0; int startDir = 0; int choice = rand() % choices.size(); int startChoice = choice; - int x1 = 0; int y1 = 0; - do - { - - - pair pear = choices[choice]; - x1 = pear.first; - y1 = pear.second; - //fprintf(stderr,"Trying unit at %d %d...\n", x1, y1); - - if (board[x1][y1] == 'B' || board[x1][y1] == 'F') - { - choice = (choice+1) % choices.size(); - continue; - } - - int x2 = x1; - int y2 = y1; - dir = rand() % 4; startDir = dir; int lastDir = dir; - - bool okay = false; - while (!okay) - { - //fprintf(stderr," Trying direction %d...\n", dir); - x2 = x1; y2 = y1; - switch (dir) - { - case 0: - --y2; - break; - case 1: - ++y2; - break; - case 2: - --x2; - break; - case 3: - ++x2; - break; - } - - okay = !(x2 < 0 || y2 < 0 || x2 >= width || y2 >= height || (board[x2][y2] != '.' && board[x2][y2] != '*' && board[x2][y2] != '#')); - if (!okay) - { - dir = (dir+1) % 4; - if (dir == startDir) - break; - } - - } - - - - choice = (choice+1) % choices.size(); - if (dir != startDir) - break; - } - while (choice != startChoice); - - - string direction=""; - switch (dir) - { - case 0: - direction = "UP"; - break; - case 1: - direction = "DOWN"; - break; - case 2: - direction = "LEFT"; - break; - case 3: - direction = "RIGHT"; - break; - } - printf("%d %d %s\n", x1, y1, direction.c_str()); - //fprintf(stderr,"%s Made move, waiting for confirmation line\n", colour.c_str()); - while (fgetc(stdin) != '\n'); //Read in result line - //fprintf(stderr, "%s Done turn\n", colour.c_str()); - //fprintf(stderr,"%s - %d %d %s\n",colour.c_str(), x1, y1, direction.c_str() ); - //fprintf(stderr, "%s [%d] computed move\n", argv[0], myPid); - } -} diff --git a/samples/dummy/Makefile b/samples/dummy/Makefile new file mode 100644 index 0000000..96cb969 --- /dev/null +++ b/samples/dummy/Makefile @@ -0,0 +1,19 @@ +#Makefile for the sample AI programs for UCC progcomp 2012 + +CPP = g++ -Wall -pedantic -lSDL -lGL -g + +dummy : dummy.o + $(CPP) -o dummy dummy.o + + + +clean : + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + +clean_full: #cleans up all backup files + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + $(RM) *.*~ + $(RM) *~ + + + diff --git a/samples/dummy/dummy.cpp b/samples/dummy/dummy.cpp new file mode 100644 index 0000000..a4a68ce --- /dev/null +++ b/samples/dummy/dummy.cpp @@ -0,0 +1,178 @@ +#include +#include + +#include +#include +#include +#include +#include +using namespace std; + +/** + * A suitably terrible program which combines C style IO with C++ style IO + * Enjoy! + * Mwuhahaha + */ + +int main(int argc, char ** argv) +{ + setbuf(stdout, NULL); + setbuf(stdin, NULL); + + + //Read in the colour, and choose a layout + + int width = 14; int height = 14; + + string colour; string opponent; + cin >> colour; cin >> opponent; cin >> width; cin >> height; + fgetc(stdin); + + //fprintf(stderr, "Colour is \"%s\", width and height are (%d, %d), opponent is \"%s\"\n", colour.c_str(), width, height, opponent.c_str()); + + assert(width == 10 && height == 10); //Can't deal with other sized boards + if (colour == "RED") + { + fprintf(stdout, "FBnyBmSsBn\n"); + fprintf(stdout, "BBCMccccnC\n"); + fprintf(stdout, "LSGmnsnsSm\n"); + fprintf(stdout, "sLSBLLssss\n"); + } + else if (colour == "BLUE") + { + fprintf(stdout, "sLSBLLssss\n"); + fprintf(stdout, "LSGmnsnsSm\n"); + fprintf(stdout, "BBCMccccnC\n"); + fprintf(stdout, "FBnyBmSsBn\n"); + } + else + { + return 1; + } + + + + char board[width][height]; + + vector > choices; + + int myPid = (int)(getpid()); + + while (true) + { + //fprintf(stderr, "%s [%d] looping\n", argv[0], myPid); + choices.clear(); + + //fprintf(stderr, "%s Waiting for status line...\n", colour.c_str()); + char c = fgetc(stdin); + while (c != '\n') + { + //fprintf(stderr,"%c",c); + c = fgetc(stdin); + } + //fprintf(stderr, "%s Got status, waiting for board line...\n", colour.c_str()); + + //Read in board + for (int y=0; y < height; ++y) + { + for (int x=0; x < width; ++x) + { + board[x][y] = fgetc(stdin); + if (board[x][y] == EOF) + exit(EXIT_SUCCESS); + + if (board[x][y] != '.' && board[x][y] != '*' && board[x][y] != '#' && board[x][y] != '+') + { + choices.push_back(pair(x, y)); + } + } + assert(fgetc(stdin) == '\n'); + } + + + + int dir = 0; int startDir = 0; int choice = rand() % choices.size(); int startChoice = choice; + int x1 = 0; int y1 = 0; + do + { + + + pair pear = choices[choice]; + x1 = pear.first; + y1 = pear.second; + //fprintf(stderr,"Trying unit at %d %d...\n", x1, y1); + + if (board[x1][y1] == 'B' || board[x1][y1] == 'F') + { + choice = (choice+1) % choices.size(); + continue; + } + + int x2 = x1; + int y2 = y1; + dir = rand() % 4; startDir = dir; int lastDir = dir; + + bool okay = false; + while (!okay) + { + //fprintf(stderr," Trying direction %d...\n", dir); + x2 = x1; y2 = y1; + switch (dir) + { + case 0: + --y2; + break; + case 1: + ++y2; + break; + case 2: + --x2; + break; + case 3: + ++x2; + break; + } + + okay = !(x2 < 0 || y2 < 0 || x2 >= width || y2 >= height || (board[x2][y2] != '.' && board[x2][y2] != '*' && board[x2][y2] != '#')); + if (!okay) + { + dir = (dir+1) % 4; + if (dir == startDir) + break; + } + + } + + + + choice = (choice+1) % choices.size(); + if (dir != startDir) + break; + } + while (choice != startChoice); + + + string direction=""; + switch (dir) + { + case 0: + direction = "UP"; + break; + case 1: + direction = "DOWN"; + break; + case 2: + direction = "LEFT"; + break; + case 3: + direction = "RIGHT"; + break; + } + printf("%d %d %s\n", x1, y1, direction.c_str()); + //fprintf(stderr,"%s Made move, waiting for confirmation line\n", colour.c_str()); + while (fgetc(stdin) != '\n'); //Read in result line + //fprintf(stderr, "%s Done turn\n", colour.c_str()); + //fprintf(stderr,"%s - %d %d %s\n",colour.c_str(), x1, y1, direction.c_str() ); + //fprintf(stderr, "%s [%d] computed move\n", argv[0], myPid); + } +} diff --git a/samples/forfax/forfax.cpp b/samples/forfax/forfax.cpp index 6fa0ba3..729e2f9 100644 --- a/samples/forfax/forfax.cpp +++ b/samples/forfax/forfax.cpp @@ -1,73 +1,69 @@ +/** + * "forfax", a sample Stratego AI for the UCC Programming Competition 2012 + * Implementations of classes Piece, Board and Forfax + * @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 "forfax.h" #include - #include #include #include #include #include - #include using namespace std; + + + /** - * Static variables + * The characters used to represent various pieces + * NOTHING, BOULDER, FLAG, SPY, SCOUT, MINER, SERGEANT, LIETENANT, CAPTAIN, MAJOR, COLONEL, GENERAL, MARSHAL, BOMB, ERROR */ -//nothing, boulder, flag, spy, scout, miner, sergeant, lietenant, captain, major, colonel, general, marshal, bomb, error char Piece::tokens[] = {'.','+','F','y','s','n','S','L','c','m','C','G','M','B','?'}; -int Piece::maxUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; -int Board::redUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; -int Board::blueUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; +/** + * The number of units remaining for each colour + * Accessed by [COLOUR][TYPE] + * COLOUR: RED, BLUE + * TYPE: NOTHING, BOULDER, FLAG, SPY, SCOUT, MINER, SERGEANT, LIETENANT, CAPTAIN, MAJOR, COLONEL, GENERAL, MARSHAL, BOMB, ERROR + */ +int Forfax::remainingUnits[][15] = {{0,0,1,1,8,5,4,4,4,3,2,1,1,6,0},{0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}}; + + /** - * Constructor for a piece + * Constructor for a piece of unknown rank * @param newX - x coord * @param newY - y coord * @param newColour - colour */ Piece::Piece(int newX, int newY,const Colour & newColour) - : x(newX), y(newY), colour(newColour), lastMove(0) + : x(newX), y(newY), colour(newColour), minRank(Piece::FLAG), maxRank(Piece::BOMB), lastMove(0) { - minRank[RED] = Piece::FLAG; - minRank[BLUE] = Piece::FLAG; - maxRank[RED] = Piece::BOMB; - maxRank[BLUE] = Piece::BOMB; + } /** - * Constructor for a piece + * Constructor for a piece of known rank * @param newX - x coord * @param newY - y coord * @param newColour - colour - * @param rankKnownBy - Colour that knows the piece's rank - * @param fixedRank - Rank the piece has + * @param fixedRank - rank of the new piece */ -Piece::Piece(int newX, int newY,const Colour & newColour, const Colour & rankKnownBy, const Type & fixedRank) - : x(newX), y(newY), colour(newColour), lastMove(0) +Piece::Piece(int newX, int newY,const Colour & newColour, const Type & fixedRank) + : x(newX), y(newY), colour(newColour), minRank(fixedRank), maxRank(fixedRank), lastMove(0) { - if (rankKnownBy == BOTH) - { - minRank[RED] = fixedRank; - minRank[BLUE] = fixedRank; - maxRank[RED] = fixedRank; - maxRank[BLUE] = fixedRank; - } - else - { - minRank[rankKnownBy] = fixedRank; - maxRank[rankKnownBy] = fixedRank; - - Colour opposite = Opposite(rankKnownBy); - minRank[opposite] = Piece::FLAG; - maxRank[opposite] = Piece::BOMB; - - } + } @@ -77,7 +73,7 @@ Piece::Piece(int newX, int newY,const Colour & newColour, const Colour & rankKno /** - * Returns the Piece::Type matching a given character + * HELPER - Returns the Piece::Type matching a given character * @param token - The character to match * @returns A Piece::Type corresponding to the character, or Piece::ERROR if none was found */ @@ -99,6 +95,7 @@ Piece::Type Piece::GetType(char token) */ Board::Board(int newWidth, int newHeight) : width(newWidth), height(newHeight), board(NULL), red(), blue() { + //Construct 2D array of Piece*'s board = new Piece**[width]; for (int x=0; x < width; ++x) { @@ -113,6 +110,7 @@ Board::Board(int newWidth, int newHeight) : width(newWidth), height(newHeight), */ Board::~Board() { + //Destroy the 2D array of Piece*'s for (int x=0; x < width; ++x) { for (int y=0; y < height; ++y) @@ -122,14 +120,14 @@ Board::~Board() } /** - * Retrieve a piece from the board + * Retrieve a piece from the board at specified coordinates * @param x - x coord of the piece * @param y - y coord of the piece * @returns Piece* to the piece found at (x,y), or NULL if there was no piece, or the coords were invalid */ Piece * Board::Get(int x, int y) const { - if (board == NULL || x < 0 || y < 0 || x > width || y > height) + if (board == NULL || x < 0 || y < 0 || x >= width || y >= height) return NULL; return board[x][y]; } @@ -140,12 +138,12 @@ Piece * Board::Get(int x, int y) const * @param x - x coord of the piece * @param y - y coord of the piece * @param newPiece - pointer to the piece to add - * @returns newPiece if the piece was successfully added, NULL if it was not + * @returns newPiece if the piece was successfully added, NULL if it was not (ie invalid coordinates specified) * */ Piece * Board::Set(int x, int y, Piece * newPiece) { - if (board == NULL || x < 0 || y < 0 || x > width || y > height) + if (board == NULL || x < 0 || y < 0 || x >= width || y >= height) return NULL; board[x][y] = newPiece; @@ -159,9 +157,9 @@ Piece * Board::Set(int x, int y, Piece * newPiece) /** - * Convert a string to a direction + * HELPER - Convert a string to a direction * @param str - The string to convert to a direction - * @returns The equivelent Direction + * @returns The equivalent Direction */ Board::Direction Board::StrToDir(const string & str) { @@ -178,9 +176,9 @@ Board::Direction Board::StrToDir(const string & str) } /** - * Convert a Direction to a string + * HELPER - Convert a Direction to a string * @param dir - the Direction to convert - * @param str - A buffer string + * @param str - A buffer string, which will contain the string representation of the Direction once this function returns. */ void Board::DirToStr(const Direction & dir, string & str) { @@ -206,7 +204,7 @@ void Board::DirToStr(const Direction & dir, string & str) } /** - * Moves the co-ords in the specified direction + * HELPER - Translates the given coordinates in a specified direction * @param x - x coord * @param y - y coord * @param dir - Direction to move in @@ -235,7 +233,7 @@ void Board::MoveInDirection(int & x, int & y, const Direction & dir, int multipl } /** - * Returns the best direction to move in to get from one point to another + * HELPER - Returns the best direction to move in to get from one point to another * @param x1 - x coord of point 1 * @param y1 - y coord of point 1 * @param x2 - x coord of point 2 @@ -300,19 +298,11 @@ bool Board::ForgetPiece(Piece * forget) } /** - * Construct Forfax + * Construct the Forfax AI */ Forfax::Forfax() : board(NULL), colour(Piece::NONE), strColour("NONE"), turnNumber(0) { - for (int ii=0; ii <= Piece::BOMB; ++ii) - { - remainingUnits[ii][Piece::RED][Piece::RED] = Piece::maxUnits[ii]; - remainingUnits[ii][Piece::RED][Piece::BLUE] = Piece::maxUnits[ii]; - remainingUnits[ii][Piece::BLUE][Piece::RED] = Piece::maxUnits[ii]; - remainingUnits[ii][Piece::BLUE][Piece::BLUE] = Piece::maxUnits[ii]; - - - } + //By default, Forfax knows nothing; the main function in main.cpp calls Forfax's initialisation functions } /** @@ -327,21 +317,24 @@ Forfax::~Forfax() } /** - * Calculate the probability that attacker beats defender in combat, from the point of view of a certain player + * Calculate the probability that attacker beats defender in combat + * @param attacker The attacking piece + * @param defender The defending piece + * @returns A double between 0 and 1 indicating the probability of success */ -double Forfax::CombatSuccessChance(Piece * attacker, Piece * defender, const Piece::Colour & accordingTo) const +double Forfax::CombatSuccessChance(Piece * attacker, Piece * defender) const { double probability=1; - for (Piece::Type aRank = attacker->minRank[accordingTo]; aRank <= attacker->maxRank[accordingTo]; aRank = (Piece::Type)((int)(aRank) + 1)) + for (Piece::Type aRank = attacker->minRank; aRank <= attacker->maxRank; aRank = (Piece::Type)((int)(aRank) + 1)) { - double lesserRanks; double greaterRanks; - for (Piece::Type dRank = defender->minRank[accordingTo]; dRank <= defender->maxRank[accordingTo]; dRank = (Piece::Type)((int)(dRank) + 1)) + double lesserRanks=0; double greaterRanks=0; + for (Piece::Type dRank = defender->minRank; dRank <= defender->maxRank; dRank = (Piece::Type)((int)(dRank) + 1)) { if (dRank < aRank) - lesserRanks++; + lesserRanks += remainingUnits[defender->colour][(int)(dRank)]; else if (dRank > aRank) - greaterRanks++; + greaterRanks += remainingUnits[defender->colour][(int)(dRank)]; else { lesserRanks++; greaterRanks++; @@ -353,257 +346,302 @@ double Forfax::CombatSuccessChance(Piece * attacker, Piece * defender, const Pie } /** - * Calculate the base score of a move + * Calculate the score of a move + * TODO: Alter this to make it better * @param piece - The Piece to move * @param dir - The direction in which to move - * @param accordingTo - the colour to use for assumptions + * @returns a number between 0 and 1, indicating how worthwhile the move is */ -double Forfax::MovementBaseScore(Piece * piece, const Board::Direction & dir, const Piece::Colour & accordingTo) const +double Forfax::MovementScore(Piece * piece, const Board::Direction & dir) const { + assert(piece != NULL); + + int x2 = piece->x; int y2 = piece->y; Board::MoveInDirection(x2, y2, dir); - if (board->Get(x2, y2) == NULL) - return 1; - else if (board->Get(x2, y2)->colour == piece->colour) - return 0; - else - return CombatSuccessChance(piece, board->Get(x2, y2), accordingTo); -} - -/** - * Calculate the total score of a move according to certain colour - * @param piece - the piece to move - * @param dir - the direction to move in - * @param accordingTo - the colour to use - */ -double Forfax::MovementTotalScore(Piece * piece, const Board::Direction & dir, const Piece::Colour & accordingTo) const -{ - double base = MovementBaseScore(piece, dir, accordingTo); - - if (base == 0) - return base; - - - int x = piece->x; int y = piece->y; - Board::MoveInDirection(x, y, dir); - Piece * old = board->Get(x, y); - board->Set(x, y, piece); - board->Set(piece->x, piece->y, NULL); + - list opponentMoves; - vector & enemies = board->GetPieces(Piece::Opposite(accordingTo)); - for (vector::iterator i = enemies.begin(); i != enemies.end(); ++i) + double basevalue; + if (!board->ValidPosition(x2, y2) || !piece->Mobile()) { - opponentMoves.push_back(MovementChoice((*i), Board::UP, *this,Piece::Opposite(accordingTo))); - opponentMoves.push_back(MovementChoice((*i), Board::DOWN, *this,Piece::Opposite(accordingTo))); - opponentMoves.push_back(MovementChoice((*i), Board::LEFT, *this,Piece::Opposite(accordingTo))); - opponentMoves.push_back(MovementChoice((*i), Board::RIGHT, *this,Piece::Opposite(accordingTo))); + + basevalue = 0; + } + else if (board->Get(x2, y2) == NULL) + { + basevalue = 0.5*IntrinsicWorth(x2, y2); + + } + else if (board->Get(x2, y2)->colour != Piece::Opposite(piece->colour)) + { + basevalue = 0; + } + else + { + Piece * defender = board->Get(x2, y2); + double combatSuccess = CombatSuccessChance(piece, defender); + basevalue = IntrinsicWorth(x2, y2)*combatSuccess*VictoryScore(piece, defender) + (1.0 - combatSuccess)*DefeatScore(piece, defender); } - opponentMoves.sort(); - - - - MovementChoice & best = opponentMoves.back(); - - board->Set(x, y, old); - board->Set(piece->x, piece->y, piece); - - return base / best.score; + if (basevalue > 0) + { + double oldValue = basevalue; + basevalue -= (double)(1.0/((double)(1.0 + (turnNumber - piece->lastMove)))); + if (basevalue < oldValue/8.0) + basevalue = oldValue/8.0; + } - - + return basevalue; } - /** - * Forfax sets himself up - * Really should just make input and output stdin and stdout respectively, but whatever + * Initialisation for Forfax + * Reads information from stdin about the board, and Forfax's colour. Initialises board, and prints appropriate setup to stdout. + * @returns true if Forfax was successfully initialised, false otherwise. */ -bool Forfax::Setup() +Forfax::Status Forfax::Setup() { //The first line is used to identify Forfax's colour, and the size of the board - //Currently the name of the opponent is ignored - + //Currently the name of the opponent is ignored. + + //Forfax then responds with a setup. + //Forfax only uses one of two setups, depending on what colour he was assigned. + + + //Variables to store information read from stdin strColour.clear(); string strOpponent; int boardWidth; int boardHeight; cin >> strColour; cin >> strOpponent; cin >> boardWidth; cin >> boardHeight; if (cin.get() != '\n') - return false; + return NO_NEWLINE; + //Determine Forfax's colour and respond with an appropriate setup if (strColour == "RED") { colour = Piece::RED; - cout << "FB..........B.\n"; - cout << "BBCM....cccc.C\n"; - cout << "LSGmnsBmSsnsSm\n"; - cout << "sLSBLnLssssnyn\n"; + cout << "FBmSsnsnBn\n"; + cout << "BBCMccccyC\n"; + cout << "LSGmnsBsSm\n"; + cout << "sLSBLnLsss\n"; } else if (strColour == "BLUE") { colour = Piece::BLUE; - cout << "sLSBLnLssssnyn\n"; - cout << "LSGmnsBmSsnsSm\n"; - cout << "BBCM....cccc.C\n"; - cout << "FB..........B.\n"; - - - - + cout << "sLSBLnLsss\n"; + cout << "LSGmnsBsSm\n"; + cout << "BBCMccccyC\n"; + cout << "FBmSsnsnBn\n"; } else - return false; - + return INVALID_QUERY; + //Create the board + //NOTE: At this stage, the board is still empty. The board is filled on Forfax's first turn + // The reason for this is because the opponent AI has not placed pieces yet, so there is no point adding only half the pieces to the board + board = new Board(boardWidth, boardHeight); - return (board != NULL); + if (board == NULL) + return BOARD_ERROR; + return OK; } /** - * Forfax makes a move - * + * Make a single move + * 1. Read result of previous move from stdin (or "START" if Forfax is RED and it is the very first move) + * 2. Read in board state from stdin (NOTE: Unused - all information needed to maintain board state is in 1. and 4.) + * TODO: Considering removing this step from the protocol? (It makes debugging annoying because I have to type a lot more!) + * 3. Print desired move to stdout + * 4. Read in result of chosen move from stdin + * @returns true if everything worked, false if there was an error or unexpected query */ -bool Forfax::MakeMove() +Forfax::Status Forfax::MakeMove() { ++turnNumber; - cerr << "Forfax " << strColour << " making move number " << turnNumber << "...\n"; + if (turnNumber == 1) { - if (!MakeFirstMove()) - { - return false; - } - + Status firstMove = MakeFirstMove(); + if (firstMove != OK) + return firstMove; } else { - if (!InterpretMove()) - { - - return false; - } - + //Read and interpret the result of the previous move + Status interpret = InterpretMove(); + if (interpret != OK) {return interpret;} //Forfax ignores the board state; he only uses the move result lines + for (int y=0; y < board->Height(); ++y) { for (int x = 0; x < board->Width(); ++x) cin.get(); if (cin.get() != '\n') - return false; + return NO_NEWLINE; } + } - //Make move here - - list choices; + //Now compute the best move + // 1. Construct list of all possible moves + // As each move is added to the list, a score is calculated for that move. + // WARNING: This is the "tricky" part! + // 2. Sort the moves based on their score + // 3. Simply use the highest scoring move! + + list choices; vector & allies = board->GetPieces(colour); for (vector::iterator i = allies.begin(); i != allies.end(); ++i) { - choices.push_back(MovementTotalChoice((*i), Board::UP, *this, colour)); - choices.push_back(MovementTotalChoice((*i), Board::DOWN, *this, colour)); - choices.push_back(MovementTotalChoice((*i), Board::LEFT, *this, colour)); - choices.push_back(MovementTotalChoice((*i), Board::RIGHT, *this, colour)); + choices.push_back(MovementChoice((*i), Board::UP, *this)); + choices.push_back(MovementChoice((*i), Board::DOWN, *this)); + choices.push_back(MovementChoice((*i), Board::LEFT, *this)); + choices.push_back(MovementChoice((*i), Board::RIGHT, *this)); } - - MovementTotalChoice & choice = choices.back(); - string direction; Board::DirToStr(choice.dir, direction); - cerr << "Forfax %s computed optimum move of " << choice.piece->x << " " << choice.piece->y << " " << direction << " [score=" << choice.score << "]\n"; + choices.sort(); //Actually sort the choices!!! + MovementChoice & choice = choices.back(); //The best one is at the back, because sort() sorts the list in ascending order + + + + //Convert information about the move into a printable form + string direction; Board::DirToStr(choice.dir, direction); + + //Print chosen move to stdout cout << choice.piece->x << " " << choice.piece->y << " " << direction << "\n"; + + + + + //Interpret the result of the chosen move return InterpretMove(); + } -bool Forfax::InterpretMove() +/** + * Reads and interprets the result of a move + * Reads information from stdin + * @returns true if the result was successfully interpreted, false if there was a contradiction or error + */ +Forfax::Status Forfax::InterpretMove() { - int x; int y; string direction; string result; int multiplier = 1; int attackerVal = (int)(Piece::BOMB); int defenderVal = (int)(Piece::BOMB); + //Variables to store move information + int x; int y; string direction; string result = ""; int multiplier = 1; int attackerVal = (int)(Piece::BOMB); int defenderVal = (int)(Piece::BOMB); + - cerr << "Forfax " << strColour << " waiting for movement information...\n"; + //Read in information from stdin cin >> x; cin >> y; cin >> direction; cin >> result; + + //If necessary, read in the ranks of involved pieces (this happens if the outcome was DIES or KILLS or BOTHDIE) if (cin.peek() != '\n') { - cerr << "Forfax " << strColour << " reading multiplier\n"; - stringstream s(result); - s >> multiplier; - result.clear(); - cin >> result; + string buf = ""; + stringstream s(buf); + cin >> buf; + s.clear(); s.str(buf); + s >> attackerVal; - if (cin.peek() != '\n') - { - cerr << "Forfax " << strColour << " reading ranks of pieces\n"; - s.clear(); s.str(result); - s >> attackerVal; - result.clear(); - cin >> result; - s.clear(); s.str(result); - s >> defenderVal; - result.clear(); - - cin >> result; - } + + buf.clear(); + cin >> buf; + s.clear(); s.str(buf); + s >> defenderVal; + + } + + //TODO: Deal with move multipliers somehow (when a scout moves more than one space) + + //Check that the line ends where expected... if (cin.get() != '\n') { - cerr << "Forfax " << strColour << " didn't recieve new line. Very angry.\n"; - cerr << "Read result so far: " << x << " " << y << " " << direction << " " << result << " ...\n"; - return false; + return NO_NEWLINE; } + + //Convert printed ranks into internal Piece::Type ranks Piece::Type attackerRank = Piece::Type(Piece::BOMB - attackerVal); Piece::Type defenderRank = Piece::Type(Piece::BOMB - defenderVal); - cerr << "Forfax " << strColour << " interpreting movement result of " << x << " " << y << " " << direction << " " << result << " ...\n"; + //Work out the square moved into int x2 = x; int y2 = y; Board::Direction dir = Board::StrToDir(direction); Board::MoveInDirection(x2, y2, dir, multiplier); + + //Determine the attacker and defender (if it exists) Piece * attacker = board->Get(x, y); Piece * defender = board->Get(x2, y2); + + //If ranks were supplied, update the known ranks of the involved pieces + if (attackerRank != Piece::NOTHING && attacker != NULL) + { + assert(attacker->minRank <= attackerRank && attacker->maxRank >= attackerRank); + attacker->minRank = attackerRank; + attacker->maxRank = attackerRank; + } + if (defenderRank != Piece::NOTHING && defender != NULL) + { + assert(defender->minRank <= defenderRank && defender->maxRank >= defenderRank); + defender->minRank = defenderRank; + defender->maxRank = defenderRank; + + } + + //There should always be an attacking piece (but not necessarily a defender) if (attacker == NULL) - return false; + return EXPECTED_ATTACKER; - Piece::Colour oppositeColour = Piece::Opposite(attacker->colour); - if (attacker->minRank[oppositeColour] == Piece::FLAG) - attacker->minRank[oppositeColour] = Piece::SPY; - if (attacker->maxRank[oppositeColour] == Piece::BOMB) - attacker->maxRank[oppositeColour] = Piece::MARSHAL; + attacker->lastMove = turnNumber; //Update stats of attacking piece (last move) + + //Eliminate certain ranks from the possibilities for the piece based on its movement + //(This is useful if the piece was an enemy piece) + if (attacker->minRank == Piece::FLAG) + attacker->minRank = Piece::SPY; + if (attacker->maxRank == Piece::BOMB) + attacker->maxRank = Piece::MARSHAL; if (multiplier > 1) { - attacker->maxRank[oppositeColour] = Piece::SCOUT; - attacker->minRank[oppositeColour] = Piece::SCOUT; + attacker->maxRank = Piece::SCOUT; + attacker->minRank = Piece::SCOUT; } + //Now look at the result of the move (I wish you could switch strings in C++) + + //The move was uneventful (attacker moved into empty square) if (result == "OK") { if (defender != NULL) - return false; + return UNEXPECTED_DEFENDER; + + //Update board and piece board->Set(x2, y2, attacker); board->Set(x, y, NULL); attacker->x = x2; attacker->y = y2; } - else if (result == "KILLS") + else if (result == "KILLS") //The attacking piece killed the defending piece { if (defender == NULL || defender->colour == attacker->colour) - return false; + return COLOUR_MISMATCH; @@ -613,61 +651,69 @@ bool Forfax::InterpretMove() attacker->x = x2; attacker->y = y2; - if (attacker->minRank[oppositeColour] < defender->maxRank[oppositeColour]) - attacker->minRank[oppositeColour] = defender->maxRank[oppositeColour]; + remainingUnits[(int)(defender->colour)][(int)(defenderRank)]--; if (!board->ForgetPiece(defender)) - return false; + return NO_DEFENDER; delete defender; } - else if (result == "DIES") + else if (result == "DIES") //The attacking piece was killed by the defending piece { if (defender == NULL || defender->colour == attacker->colour) - return false; -cerr << "Forfax - Unit " << attacker << " dies \n"; + return COLOUR_MISMATCH; + + remainingUnits[(int)(attacker->colour)][(int)(attackerRank)]--; + if (!board->ForgetPiece(attacker)) - return false; + return NO_ATTACKER; delete attacker; board->Set(x, y, NULL); } - else if (result == "BOTHDIE") + else if (result == "BOTHDIE") //Both attacking and defending pieces died { if (defender == NULL || defender->colour == attacker->colour) - return false; + return COLOUR_MISMATCH; + + remainingUnits[(int)(defender->colour)][(int)(defenderRank)]--; + remainingUnits[(int)(attacker->colour)][(int)(attackerRank)]--; + if (board->ForgetPiece(attacker) == false) - return false; + return NO_ATTACKER; if (board->ForgetPiece(defender) == false) - return false; + return NO_DEFENDER; delete attacker; delete defender; board->Set(x2, y2, NULL); board->Set(x, y, NULL); } - else if (result == "VICTORY") + else if (result == "VICTORY") //The attacking piece captured a flag { - return false; + return VICTORY; + } - return true; + return OK; } /** - * First move only + * Forfax's first move + * Sets the state of the board + * @returns true if the board was successfully read, false if an error occurred. * */ -bool Forfax::MakeFirstMove() +Forfax::Status Forfax::MakeFirstMove() { if (colour == Piece::RED) { string derp; cin >> derp; if (derp != "START") - return false; + return INVALID_QUERY; if (cin.get() != '\n') - return false; + return NO_NEWLINE; } else { @@ -682,12 +728,12 @@ bool Forfax::MakeFirstMove() char c = cin.get(); switch (c) { - case '.': + case '.': //Empty square break; - case '+': - board->Set(x, y, new Piece(x, y, Piece::NONE, Piece::BOTH, Piece::BOULDER)); + case '+': //Boulder/Obstacle + board->Set(x, y, new Piece(x, y, Piece::NONE, Piece::BOULDER)); break; - case '#': + case '#': //Enemy piece occupies square case '*': { Piece * toAdd = new Piece(x, y, Piece::Opposite(colour)); @@ -695,10 +741,10 @@ bool Forfax::MakeFirstMove() board->GetPieces(toAdd->colour).push_back(toAdd); break; } - default: + default: //Allied piece occupies square { Piece::Type type = Piece::GetType(c); - Piece * toAdd = new Piece(x, y, colour, colour, type); + Piece * toAdd = new Piece(x, y, colour, type); board->Set(x, y, toAdd); board->GetPieces(toAdd->colour).push_back(toAdd); break; @@ -706,10 +752,138 @@ bool Forfax::MakeFirstMove() } } if (cin.get() != '\n') - return false; + return NO_NEWLINE; } - return true; + return OK; +} + +/** + * Calculates the intrinsic strategic worth of a point on the board + * @param x the x coordinate of the point + * @param y the y coordinate of the point + * @returns a value between 0 and 1, with 0 indicating worthless and 1 indicating highly desirable + * (NOTE: No points will actually be worth 0) + */ +double Forfax::IntrinsicWorth(int x, int y) const +{ + static double intrinsicWorth[][10][10] = + { + //Red + { + {0.1,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1}, + {0.5,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1}, + {0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2}, + {0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3}, + {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6}, + {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6}, + {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6}, + {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6}, + {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6}, + {0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7} + + + }, + //Blue + { + {0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7}, + {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6}, + {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6}, + {0.6,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.6}, + {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6}, + {0.6,0.6,0.1,0.1,0.65,0.65,0.1,0.1,0.6,0.6}, + {0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3}, + {0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2}, + {0.5,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1}, + {0.1,0.5,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1} + } + }; + + return intrinsicWorth[(int)(colour)][x][y]; +} + +/** + * Calculates a score assuming that attacker will beat defender, indicating how much killing that piece is worth + * @param attacker the Attacking piece + * @param defender the Defending piece + * @returns a value between 0 and 1, with 0 indicating worthless and 1 indicating highly desirable + */ +double Forfax::VictoryScore(Piece * attacker, Piece * defender) const +{ + if (defender->minRank == defender->maxRank) + { + if (defender->minRank == Piece::FLAG) + return 1; + else if (defender->minRank == Piece::BOMB) + return 0.9; + } + return max(((defender->maxRank / Piece::BOMB) + (defender->minRank / Piece::BOMB))/2, 0.6); +} + +/** + * Calculates a score assuming that attacker will lose to defender, indicating how much learning the rank of that piece is worth + * @param attacker the Attacking piece + * @param defender the Defending piece + * @returns a value between 0 and 1, with 0 indicating worthless and 1 indicating highly desirable + */ +double Forfax::DefeatScore(Piece * attacker, Piece * defender) const +{ + if (attacker->minRank == Piece::SPY) + return 0.05; + + if (defender->minRank == defender->maxRank) + { + if (defender->minRank == Piece::BOMB) + return 1 - (double)((double)(attacker->minRank) / (double)(Piece::BOMB)); + else + return 0.5; + } + + double possibleRanks = 0; double totalRanks = 0; + for (Piece::Type rank = Piece::NOTHING; rank <= Piece::BOMB; rank = Piece::Type((int)(rank) + 1)) + { + totalRanks += remainingUnits[(int)(defender->colour)][(int)(rank)]; + if (rank >= defender->minRank && rank <= defender->maxRank) + possibleRanks += remainingUnits[(int)(defender->colour)][(int)(rank)]; + + } + + if (totalRanks > 0) + return (possibleRanks/totalRanks) - (double)((double)(attacker->minRank) / (double)(Piece::BOMB)); + return 0; +} + +/** + * DEBUG - Print the board seen by Forfax to a stream + * @param out The stream to print to + */ +void Forfax::PrintBoard(ostream & out) +{ + for (int y = 0; y < board->Height(); ++y) + { + for (int x = 0; x < board->Width(); ++x) + { + Piece * at = board->Get(x, y); + if (at == NULL) + out << "."; + else + { + if (at->colour == colour) + { + out << Piece::tokens[(int)(at->minRank)]; + } + else if (at->colour == Piece::Opposite(colour)) + { + out << "#"; + } + else + { + out << "+"; + } + } + } + out << "\n"; + } } //EOF diff --git a/samples/forfax/forfax.h b/samples/forfax/forfax.h index fe370a9..4bd7478 100644 --- a/samples/forfax/forfax.h +++ b/samples/forfax/forfax.h @@ -1,13 +1,24 @@ +/** + * "forfax", a sample Stratego AI for the UCC Programming Competition 2012 + * Declarations for classes Piece, Board and Forfax, Declaration/Implementation of helper class MovementChoice + * @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 FORFAX_H #define FORFAX_H #include //Uses C++ std::vectors to store pieces #include //Uses C++ std::string -/** - * Header for the sample Stratego AI "forfax" - * @author Sam Moore 2011 - */ -class Board; +#include //For debug +#include //For debug + + + +class Board; //Forward declaration used by class Piece + /** * Class to represent a piece on the board */ @@ -18,7 +29,7 @@ class Piece typedef enum {RED=0, BLUE=1, NONE, BOTH} Colour; //Used for the allegiance of the pieces - terrain counts as NONE. Piece(int newX, int newY,const Colour & newColour); - Piece(int newX, int newY,const Colour & newColour, const Colour & rankKnownBy, const Type & fixedRank); + Piece(int newX, int newY,const Colour & newColour, const Type & fixedRank); virtual ~Piece() {} void SetCoords(int newX, int newY) {x = newX; y = newY;} @@ -30,12 +41,18 @@ class Piece static Type GetType(char fromToken); //Retrieves the type of a piece given its character token static Colour Opposite(const Colour & colour) {return colour == RED ? BLUE : RED;} - + bool Mobile() const + { + if (minRank == maxRank) + return (minRank != Piece::FLAG && minRank != Piece::BOMB); + else + return true; + } int x; int y; const Colour colour; //The colour of the piece - Type minRank[2]; //The minimum possible rank of the piece, according to each colour - Type maxRank[2]; //The maximum possible rank of the piece, according to each colour + Type minRank; //The minimum possible rank of the piece + Type maxRank; //The maximum possible rank of the piece int lastMove; @@ -57,6 +74,8 @@ class Board Piece * Get(int x, int y) const; //Retrieve single piece Piece * Set(int x, int y, Piece * newPiece); //Add piece to board + bool ValidPosition(int x, int y) const {return (x > 0 && x < width && y > 0 && y < height);} + int Width() const {return width;} int Height() const {return height;} @@ -89,39 +108,52 @@ class Board }; /** - * Small class to manage the Forfax AI + * Class to manage the Forfax AI */ class Forfax { public: Forfax(); virtual ~Forfax(); - bool Setup(); //Waits for input to determine colour and board size, and then responds with setup - bool MakeMove(); //Should be called each turn - determines Forfax's move - double CombatSuccessChance(Piece * attacker, Piece * defender, const Piece::Colour & accordingTo) const; - double MovementBaseScore(Piece * move, const Board::Direction & dir, const Piece::Colour & accordingTo) const; - double MovementTotalScore(Piece * move, const Board::Direction & dir, const Piece::Colour & accordingTo) const; + typedef enum {OK, NO_NEWLINE, EXPECTED_ATTACKER, UNEXPECTED_DEFENDER, NO_ATTACKER, NO_DEFENDER, COLOUR_MISMATCH, INVALID_QUERY, BOARD_ERROR, VICTORY} Status; + + Status Setup(); //Waits for input to determine colour and board size, and then responds with setup + Status MakeMove(); //Should be called each turn - determines Forfax's move + + + //Move score functions + double MovementScore(Piece * move, const Board::Direction & dir) const; //Calculate total score + double CombatSuccessChance(Piece * attacker, Piece * defender) const; //Calculate chance of success in combat + double IntrinsicWorth(int x, int y) const; //How much a given point on the board is worth + double VictoryScore(Piece * attacker, Piece * defender) const; //How much killing the defender is worth + double DefeatScore(Piece * attacker, Piece * defender) const; //How much losing is worth + + + void PrintBoard(std::ostream & out); protected: - bool MakeFirstMove(); //Should only be called on the first turn - bool InterpretMove(); + Status MakeFirstMove(); //Should only be called on the first turn + Status InterpretMove(); private: Board * board; //Forfax stores the state on a board Piece::Colour colour; //Forfax needs to know his colour std::string strColour; //String of colour int turnNumber; //Forfax needs to know what turn number it is - int remainingUnits[14][2][2]; //Known remaining units, accessed by (type, colour, accordingTo) + static int remainingUnits[2][15]; //Known remaining units, accessed by [colour][type] }; +/** + * Helper class used to store various moves in the board, and their associated scores + */ class MovementChoice { public: - MovementChoice(Piece * newPiece, const Board::Direction & newDir, const Forfax & forfax, const Piece::Colour & accordingTo) : piece(newPiece), dir(newDir) + MovementChoice(Piece * newPiece, const Board::Direction & newDir, const Forfax & forfax) : piece(newPiece), dir(newDir) { - score = forfax.MovementBaseScore(piece, dir, accordingTo); + score = forfax.MovementScore(piece, dir); } MovementChoice(const MovementChoice & cpy) : piece(cpy.piece), dir(cpy.dir), score(cpy.score) @@ -143,14 +175,6 @@ class MovementChoice }; -class MovementTotalChoice : public MovementChoice -{ - public: - MovementTotalChoice(Piece * newPiece, const Board::Direction & newDir, const Forfax & forfax, const Piece::Colour & accordingTo) : MovementChoice(newPiece, newDir, forfax, accordingTo) - { - score = score/(forfax.MovementTotalScore(piece, dir, Piece::Opposite(accordingTo))); - } -}; #endif //FORFAX_H diff --git a/samples/forfax/main.cpp b/samples/forfax/main.cpp index 2b31fee..f3678e8 100644 --- a/samples/forfax/main.cpp +++ b/samples/forfax/main.cpp @@ -1,3 +1,12 @@ +/** + * "forfax", a sample Stratego AI for the UCC Programming Competition 2012 + * The main function for the "forfax" AI program + * @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 #include @@ -6,21 +15,73 @@ using namespace std; #include -void quit(); + +/** + * The AI + */ Forfax forfax; + +/** + * The main function + * @param argc the number of arguments + * @param argv the arguments + * @returns exit code 0 for success, something else for error + * Do I really need to tell you this? + */ int main(int argc, char ** argv) { setbuf(stdin, NULL); setbuf(stdout, NULL); - atexit(&quit); - if (!forfax.Setup()) - exit(EXIT_SUCCESS); + Forfax::Status move = forfax.Setup(); + + + while (move == Forfax::OK) + { + move = forfax.MakeMove(); + } + + switch (move) + { + case Forfax::OK: + cerr << argv[0] << " Error - Should never see this!\n"; + break; + case Forfax::NO_NEWLINE: + cerr << argv[0] << " Error - Expected a new line!\n"; + break; + case Forfax::EXPECTED_ATTACKER: + cerr << argv[0] << " Error - Attacking piece does not exist on board!\n"; + break; + case Forfax::UNEXPECTED_DEFENDER: + cerr << argv[0] << " Error - Unexpected defending piece on board!\n"; + break; + case Forfax::NO_ATTACKER: + cerr << argv[0] << " Error - Couldn't find attacker in list of pieces!\n"; + break; + case Forfax::NO_DEFENDER: + cerr << argv[0] << " Error - Couldn't find defender in list of pieces!\n"; + break; - while (forfax.MakeMove()); + case Forfax::COLOUR_MISMATCH: + cerr << argv[0] << " Error - Colour of attacker and defender are the same!\n"; + break; + case Forfax::INVALID_QUERY: + cerr << argv[0] << " Error - Query did not make sense\n"; + break; + case Forfax::VICTORY: + cerr << argv[0] << " Game end - VICTORY!\n"; + break; + case Forfax::BOARD_ERROR: + cerr << argv[0] << " Error - An error occurred with the board!\n"; + break; + } - cerr << "Forfax threw a hissy fit, and exited!\n"; + cerr << "Final board state:\n"; + forfax.PrintBoard(cerr); + + cerr << "Forfax is now exiting!\n"; + exit(EXIT_SUCCESS); return 0; @@ -28,7 +89,4 @@ int main(int argc, char ** argv) } -void quit() -{ - cerr << " Forfax quit\n"; -} +