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

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,18 +28,20 @@ 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]);
atexit(cleanup);
......@@ -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
......@@ -244,6 +248,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;
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;