Commit 53a66690 authored by Sam Moore's avatar Sam Moore

Fixed "forfax" sample AI

Forfax now plays a fairly mediocre game of Stratego
He usually beats the dummy AI. Usually. After a while.

Sometimes he gets stuck in a loop where he repeats the same moves over and over again.
Sometimes he attempts to move bombs or the flag (???)
He also never actually captures the flag; he just destroys all the enemy pieces before going into a loop.
This is probably due to the movement value being very low for moving over pieces that are likely to be bombs
And when all mobile pieces are destroyed, the remainder are seen to be likely to be bombs.

The segfault was caused by much stupidity involving a for loop in Board::ForgetPiece and the continue statement
Changed to a while loop to fix.

Since Forfax works (Although it plays pretty badly) I will probably move onto other things for a while.
parent 041c37d1
......@@ -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;
}
......
......@@ -8,12 +8,8 @@ dummy : dummy.o
clean :
$(RM) $(BIN) $(OBJ) $(LINKOBJ)
clean_full: #cleans up all backup files
$(RM) $(BIN) $(OBJ) $(LINKOBJ)
$(RM) *.*~
$(RM) *~
rm -f dummy.o
rm -f dummy
......@@ -18,7 +18,8 @@ int main(int argc, char ** argv)
{
setbuf(stdout, NULL);
setbuf(stdin, NULL);
srand(time(NULL));
//Read in the colour, and choose a layout
......
......@@ -17,6 +17,8 @@
#include <list>
#include <cmath>
//#define DEBUG
using namespace std;
......@@ -48,7 +50,7 @@ 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
* @param newColour - colour
*/
Piece::Piece(int newX, int newY,const Colour & newColour)
: x(newX), y(newY), colour(newColour), minRank(Piece::FLAG), maxRank(Piece::BOMB), lastMove(0)
: x(newX), y(newY), colour(newColour), minRank(Piece::FLAG), maxRank(Piece::BOMB), lastMove(0), lastx(newX), lasty(newY)
{
}
......@@ -61,7 +63,7 @@ Piece::Piece(int newX, int newY,const Colour & newColour)
* @param fixedRank - rank of the new piece
*/
Piece::Piece(int newX, int newY,const Colour & newColour, const Type & fixedRank)
: x(newX), y(newY), colour(newColour), minRank(fixedRank), maxRank(fixedRank), lastMove(0)
: x(newX), y(newY), colour(newColour), minRank(fixedRank), maxRank(fixedRank), lastMove(0), lastx(newX), lasty(newY)
{
......@@ -265,6 +267,19 @@ Board::Direction Board::DirectionBetween(int x1, int y1, int x2, int y2)
}
/**
* HELPER - Returns the number of moves between two points
* @param x1 x coordinate of the first point
* @param y1 y coordinate of the first point
* @param x2 x coordinate of the second point
* @param y2 y coordinate of the second point
* @returns The number of moves taken to progress from (x1, y1) to (x2, y2), assuming no obstacles
*/
int Board::NumberOfMoves(int x1, int y1, int x2, int y2)
{
return (abs(x2 - x1) + abs(y2 - y1)); //Pieces can't move diagonally, so this is pretty straight forward
}
/**
......@@ -279,7 +294,8 @@ bool Board::ForgetPiece(Piece * forget)
return false;
vector<Piece*> & in = GetPieces(forget->colour); bool result = false;
for (vector<Piece*>::iterator i=in.begin(); i != in.end(); ++i)
vector<Piece*>::iterator i=in.begin();
while (i != in.end())
{
if ((*i) == forget)
......@@ -289,12 +305,61 @@ bool Board::ForgetPiece(Piece * forget)
continue;
}
++i;
}
return result;
}
/**
* Gets the closest Piece of a specified colour to a point
* @param x The x coordinate of the point
* @param y The y coordinate of the point
* @param colour The colour that the piece must match (may be Piece::BOTH to match either colour)
* @returns Piece* pointing to the closest piece of a matching colour, NULL if no piece found
*/
Piece * Board::GetClosest(int x, int y, const Piece::Colour & colour) const
{
if (x < 0 || y < 0 || x >= width || y >= height)
return NULL;
for (int dist = 0; dist < max<int>(width, height); ++dist)
{
for (int yy = y-dist; yy <= y+dist; ++yy)
{
Piece * get = Get(x+dist, y);
if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
return get;
}
for (int yy = y-dist; yy <= y+dist; ++yy)
{
Piece * get = Get(x-dist, y);
if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
return get;
}
for (int xx = x-dist; xx <= x+dist; ++xx)
{
Piece * get = Get(xx, y+dist);
if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
return get;
}
for (int xx = x-dist; xx <= y+dist; ++xx)
{
Piece * get = Get(xx, y-dist);
if ((get != NULL) && (get->colour == colour || colour == Piece::BOTH))
return get;
}
}
return NULL;
}
/**
......@@ -303,6 +368,7 @@ bool Board::ForgetPiece(Piece * forget)
Forfax::Forfax() : board(NULL), colour(Piece::NONE), strColour("NONE"), turnNumber(0)
{
//By default, Forfax knows nothing; the main function in main.cpp calls Forfax's initialisation functions
srand(time(NULL));
}
/**
......@@ -366,32 +432,51 @@ double Forfax::MovementScore(Piece * piece, const Board::Direction & dir) const
if (!board->ValidPosition(x2, y2) || !piece->Mobile())
{
basevalue = 0;
basevalue = 0; //No point attempting to move immobile units, or moving off the edges of the board
}
else if (board->Get(x2, y2) == NULL)
{
basevalue = 0.5*IntrinsicWorth(x2, y2);
//If the position is empty...
Piece * closestEnemy = board->GetClosest(x2, y2, Piece::Opposite(piece->colour));
if (closestEnemy == NULL)
{
basevalue = 0.05*IntrinsicWorth(x2, y2); //Should never get this unless there are no enemies left
}
else
{
//Allocate score based on score of Combat with nearest enemy to the target square, decrease with distance between target square and the enemy
basevalue = 0.95*CombatScore(closestEnemy->x, closestEnemy->y, piece) - 0.2*(double)((double)(Board::NumberOfMoves(closestEnemy->x, closestEnemy->y, x2, y2))/(double)((max<int>(board->Width(), board->Height()))));
}
}
else if (board->Get(x2, y2)->colour != Piece::Opposite(piece->colour))
{
basevalue = 0;
basevalue = 0; //The position is occupied by an ally, and so its pointless to try and move there
}
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);
basevalue = CombatScore(x2, y2, piece); //The position is occupied by an enemy; compute combat score
}
if (basevalue > 0)
{
//Hack which decreases score for units that moved recently
//Does not decrease below a threshold (so that at the start of the game units will actually move!)
double oldValue = basevalue;
basevalue -= (double)(1.0/((double)(1.0 + (turnNumber - piece->lastMove))));
if (basevalue < oldValue/8.0)
basevalue = oldValue/8.0;
if (basevalue < oldValue/1000.0)
basevalue = oldValue/1000.0;
}
if (x2 == piece->lastx && y2 == piece->lasty) //Hack which decreases score for backtracking moves
basevalue = basevalue/1.5;
if (rand() % 10 == 0) //Hack which introduces some randomness by boosting one in every 10 scores
basevalue *= 4;
if (basevalue > 1)
basevalue = 1;
return basevalue;
}
......@@ -475,7 +560,7 @@ Forfax::Status Forfax::MakeMove()
if (interpret != OK) {return interpret;}
//Forfax ignores the board state; he only uses the move result lines
#ifndef DEBUG
for (int y=0; y < board->Height(); ++y)
{
for (int x = 0; x < board->Width(); ++x)
......@@ -483,6 +568,7 @@ Forfax::Status Forfax::MakeMove()
if (cin.get() != '\n')
return NO_NEWLINE;
}
#endif //DEBUG
}
......@@ -589,13 +675,13 @@ Forfax::Status Forfax::InterpretMove()
//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);
//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);
//assert(defender->minRank <= defenderRank && defender->maxRank >= defenderRank);
defender->minRank = defenderRank;
defender->maxRank = defenderRank;
......@@ -635,6 +721,8 @@ Forfax::Status Forfax::InterpretMove()
//Update board and piece
board->Set(x2, y2, attacker);
board->Set(x, y, NULL);
attacker->lastx = attacker->x;
attacker->lasty = attacker->y;
attacker->x = x2;
attacker->y = y2;
}
......@@ -648,6 +736,8 @@ Forfax::Status Forfax::InterpretMove()
board->Set(x2, y2, attacker);
board->Set(x, y, NULL);
attacker->lastx = attacker->x;
attacker->lasty = attacker->y;
attacker->x = x2;
attacker->y = y2;
......@@ -810,6 +900,8 @@ double Forfax::IntrinsicWorth(int x, int y) const
*/
double Forfax::VictoryScore(Piece * attacker, Piece * defender) const
{
//If defender's rank is known, flags or bombs are worth more than usual
if (defender->minRank == defender->maxRank)
{
if (defender->minRank == Piece::FLAG)
......@@ -817,6 +909,7 @@ double Forfax::VictoryScore(Piece * attacker, Piece * defender) const
else if (defender->minRank == Piece::BOMB)
return 0.9;
}
//Return average of normalised defender ranks
return max<double>(((defender->maxRank / Piece::BOMB) + (defender->minRank / Piece::BOMB))/2, 0.6);
}
......@@ -828,30 +921,78 @@ double Forfax::VictoryScore(Piece * attacker, Piece * defender) const
*/
double Forfax::DefeatScore(Piece * attacker, Piece * defender) const
{
if (attacker->minRank == Piece::SPY)
return 0.05;
if (defender->minRank == defender->maxRank)
double result = 0;
if (defender->minRank == defender->maxRank) //If the defender's rank is known for certain...
{
if (defender->minRank == Piece::BOMB)
return 1 - (double)((double)(attacker->minRank) / (double)(Piece::BOMB));
if (defender->minRank == Piece::BOMB) //Committing suicide to destroy bombs has a value that decreases with the attacker's rank
result = 1 - 0.5*(double)((double)(attacker->minRank) / (double)(Piece::BOMB));
else if (defender->minRank == Piece::FLAG)
result = 1; //Its impossible to lose to the flag anyway...
else
return 0.5;
}
{
//This is committing suicide on a higher ranked non-bomb enemy piece.
//Basically pointless, but the greater the attacker's rank the more pointless!
double temp = (double)((double)(attacker->minRank) / (double)(Piece::BOMB));
result = 0.01*(1 - temp)*(1 - temp);
double possibleRanks = 0; double totalRanks = 0;
for (Piece::Type rank = Piece::NOTHING; rank <= Piece::BOMB; rank = Piece::Type((int)(rank) + 1))
}
}
else //The defender's rank is not known
{
totalRanks += remainingUnits[(int)(defender->colour)][(int)(rank)];
if (rank >= defender->minRank && rank <= defender->maxRank)
possibleRanks += remainingUnits[(int)(defender->colour)][(int)(rank)];
//Score is allocated based on how much knowledge is gained by attacking defender
//The more possible ranks for the defender, the greater the score
//The score decreases as the rank of the attacker is increased.
double possibleRanks = 0; double totalRanks = 0; double bonus = 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 (rank == Piece::BOMB)
bonus += remainingUnits[(int)(defender->colour)][(int)(rank)];
if (rank == Piece::FLAG)
bonus += 2*remainingUnits[(int)(defender->colour)][(int)(rank)];
}
}
if (totalRanks > 0)
result = (possibleRanks/totalRanks) - 0.8*(double)((double)(attacker->minRank) / (double)(Piece::BOMB));
result += bonus / totalRanks;
if (result > 1)
result = 1;
}
if (totalRanks > 0)
return (possibleRanks/totalRanks) - (double)((double)(attacker->minRank) / (double)(Piece::BOMB));
return 0;
}
if (attacker->minRank == Piece::SPY) //Spies are slightly more valuable than usual since they kill the Marshal
result = result / 1.5;
return result;
}
/**
* Calculates a score indicating the worth of invoking combat in a square
* @param x The x coordinate
* @param y The y coordinate
* @param attacker The piece invoking the combat
* @returns A value between 0 in 1, with 0 indicating worthless (or no combat) and 1 indicating highest value
*/
double Forfax::CombatScore(int x, int y, Piece * attacker) const
{
Piece * defender = board->Get(x, y);
if (defender == NULL)
return 0;
double combatSuccess = CombatSuccessChance(attacker, defender);
return IntrinsicWorth(x, y)*combatSuccess*VictoryScore(attacker, defender) + (1.0 - combatSuccess)*DefeatScore(attacker, defender);
}
/**
* DEBUG - Print the board seen by Forfax to a stream
......
......@@ -54,6 +54,7 @@ class Piece
Type minRank; //The minimum possible rank of the piece
Type maxRank; //The maximum possible rank of the piece
int lastMove;
int lastx; int lasty;
......@@ -72,6 +73,7 @@ class Board
std::vector<Piece*> & GetPieces(const Piece::Colour & colour) {return colour == Piece::RED ? red : blue;} //retrieve array of pieces
Piece * Get(int x, int y) const; //Retrieve single piece
Piece * GetClosest(int x, int y, const Piece::Colour & search = Piece::BOTH) const; //Retrieve closest piece of specified colour to the point
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);}
......@@ -85,7 +87,7 @@ class Board
static void MoveInDirection(int & x, int & y, const Direction & dir, int multiplier = 1);
static Direction DirectionBetween(int x1, int y1, int x2, int y2);
static int NumberOfMoves(int x1, int y1, int x2, int y2);
static int redUnits[];
......@@ -125,6 +127,7 @@ class Forfax
//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 CombatScore(int x, int y, Piece * attacker) const; //Calculate total worth of combat at a point
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
......@@ -176,6 +179,7 @@ class MovementChoice
};
#endif //FORFAX_H
//EOF
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment