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
manager/dummy
deleted
120000 → 0
manager/forfax
deleted
120000 → 0
/** | ||
* "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 [email protected] or [email protected] | ||
* @git git.ucc.asn.au/progcomp2012.git | ||
*/ | ||
#include "forfax.h" | ||
#include <cstdlib> | ||
#include <sstream> | ||
#include <iostream> | ||
#include <string> | ||
#include <vector> | ||
#include <list> | ||
#include <cmath> | ||
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; | ||
double basevalue; | ||
if (!board->ValidPosition(x2, y2) || !piece->Mobile()) | ||
{ | ||
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); | ||
basevalue = 0; | ||
} | ||
else if (board->Get(x2, y2) == NULL) | ||
{ | ||
basevalue = 0.5*IntrinsicWorth(x2, y2); | ||
list<MovementChoice> opponentMoves; | ||
vector<Piece*> & enemies = board->GetPieces(Piece::Opposite(accordingTo)); | ||
for (vector<Piece*>::iterator i = enemies.begin(); i != enemies.end(); ++i) | ||
} | ||
else if (board->Get(x2, y2)->colour != Piece::Opposite(piece->colour)) | ||
{ | ||
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 | ||
{ | ||
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; | ||