Commit 041c37d1 authored by Sam Moore's avatar Sam Moore

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
parent 2ab27eb6
......@@ -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;
}
......
../samples/dummy
\ No newline at end of file
../samples/forfax/forfax
\ No newline at end of file
......@@ -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);
}
......
......@@ -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};
......
......@@ -5,6 +5,8 @@ CPP = g++ -Wall -pedantic -lSDL -lGL -g
dummy : dummy.o
$(CPP) -o dummy dummy.o
clean :
$(RM) $(BIN) $(OBJ) $(LINKOBJ)
......
......@@ -30,20 +30,20 @@ int main(int argc, char ** argv)
//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
assert(width == 10 && height == 10); //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");
fprintf(stdout, "FBnyBmSsBn\n");
fprintf(stdout, "BBCMccccnC\n");
fprintf(stdout, "LSGmnsnsSm\n");
fprintf(stdout, "sLSBLLssss\n");
}
else if (colour == "BLUE")
{
fprintf(stdout, "sLSBLnLssssnyn\n");
fprintf(stdout, "LSGmnsBmSsnsSm\n");
fprintf(stdout, "BBCM....cccc.C\n");
fprintf(stdout, "FB..........B.\n");
fprintf(stdout, "sLSBLLssss\n");
fprintf(stdout, "LSGmnsnsSm\n");
fprintf(stdout, "BBCMccccnC\n");
fprintf(stdout, "FBnyBmSsBn\n");
}
else
{
......@@ -63,10 +63,12 @@ int main(int argc, char ** argv)
//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, "%s Waiting for status line...\n", colour.c_str());
char c = fgetc(stdin);
while (c != '\n')
{
//fprintf(stderr,".");
//fprintf(stderr,"%c",c);
c = fgetc(stdin);
}
//fprintf(stderr, "%s Got status, waiting for board line...\n", colour.c_str());
......
/**
* "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;
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<MovementChoice> opponentMoves;
vector<Piece*> & enemies = board->GetPieces(Piece::Opposite(accordingTo));
for (vector<Piece*>::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())
{