Commit e1153eeb authored by Sam Moore's avatar Sam Moore

[RULE CHANGE] *Victory by "attrition"* + Bug fixes

Minor bugs in the manager program fixed.
Changed some messages for clarity, can't remember what, look at diff.

Added VICTORY_ATTRITION; victory by destroying all of the opponents _mobile_ pieces
 (ie: Everything except Bombs/Flag)

This means we don't get results of DRAW or DRAW_DEFAULT when one AI destroys all the other's mobile pieces.
Since those games would last up to 5000 turns, this saves a lot of wasted time.

AI should still respond with "NO_MOVE" when they have no mobile pieces.

Made timeout value adjustable by an argument switch, '-T'
Altered simulate.py to use a timeout variable and supply the switch to the manager program.

So now I don't need to recompile and commit the manager program every time I want to test a different timeout value on mufasa!

Mufasa is now on game 3 of the test round, out of 12. After FIVE HOURS.
This particular game has lasted 1132 turns, with BLUE making "NO_MOVE" for the last 600 or so.
The new victory condition will stop this sort of thing :)

Merry Christmas!
parent de728517
#Makefile for Stratego
#Use this to build with graphics
#CPP = g++ -Wall -pedantic -lSDL -lGL -lpthread -g
CPP = g++ -Wall -pedantic -lSDL -lGL -lpthread -g
#Use this to build without graphics
CPP = g++ -Wall -pedantic -lpthread -g
#CPP = g++ -Wall -pedantic -lpthread -g
OBJ = main.o controller.o ai_controller.o human_controller.o program.o thread_util.o stratego.o graphics.o game.o
BIN = stratego
......
......@@ -138,8 +138,11 @@ MovementResult Controller::MakeMove(string & buffer)
case MovementResult::OK:
buffer += " OK";
break;
case MovementResult::VICTORY:
buffer += " FLAG";
case MovementResult::VICTORY_FLAG:
buffer += " VICTORY_FLAG";
break;
case MovementResult::VICTORY_ATTRITION:
buffer += " VICTORY_ATTRITION";
break;
case MovementResult::KILLS:
buffer += " KILLS ";
......
......@@ -7,7 +7,7 @@ using namespace std;
Game* Game::theGame = NULL;
bool Game::gameCreated = false;
Game::Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard)
Game::Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard, double newTimeoutTime) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard), timeoutTime(newTimeoutTime)
{
gameCreated = false;
if (gameCreated)
......@@ -28,18 +28,18 @@ Game::Game(const char * redPath, const char * bluePath, const bool enableGraphic
if (strcmp(redPath, "human") == 0)
red = new Human_Controller(Piece::RED, graphicsEnabled);
else
red = new AI_Controller(Piece::RED, redPath);
red = new AI_Controller(Piece::RED, redPath, timeoutTime);
if (strcmp(bluePath, "human") == 0)
blue = new Human_Controller(Piece::BLUE, graphicsEnabled);
else
blue = new AI_Controller(Piece::BLUE, bluePath);
blue = new AI_Controller(Piece::BLUE, bluePath, timeoutTime);
}
Game::Game(const char * fromFile, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard)
Game::Game(const char * fromFile, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal, int newMaxTurns, bool newPrintBoard, double newTimeoutTime) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0), input(NULL), maxTurns(newMaxTurns), printBoard(newPrintBoard), timeoutTime(newTimeoutTime)
{
gameCreated = false;
if (gameCreated)
......@@ -93,13 +93,13 @@ Piece::Colour Game::Setup(const char * redName, const char * blueName)
{
logMessage("Controller for Player RED is invalid!\n");
if (!red->HumanController())
logMessage("Check that program \"%s\" exists and has executable permissions set.\n", redName);
logMessage("Check that executable \"%s\" exists and has executable permissions set.\n", redName);
}
if (!blue->Valid())
{
logMessage("Controller for Player BLUE is invalid!\n");
if (!blue->HumanController())
logMessage("Check that program \"%s\" exists and has executable permissions set.\n", blueName);
logMessage("Check that executable \"%s\" exists and has executable permissions set.\n", blueName);
}
if (!red->Valid())
{
......@@ -243,13 +243,15 @@ void Game::HandleBrokenPipe(int sig)
if (theGame->turn == Piece::RED)
{
theGame->logMessage("Game ends on RED's turn - REASON: ");
theGame->blue->Message("DEFAULT");
if (theGame->blue->Valid()) //Should probably check this
theGame->blue->Message("DEFAULT");
}
else if (theGame->turn == Piece::BLUE)
{
theGame->logMessage("Game ends on BLUE's turn - REASON: ");
theGame->red->Message("DEFAULT");
if (theGame->red->Valid()) //Should probably check this
theGame->red->Message("DEFAULT");
}
else
{
......@@ -257,7 +259,7 @@ void Game::HandleBrokenPipe(int sig)
}
theGame->logMessage("SIGPIPE - Broken pipe (AI program may have segfaulted)\n");
theGame->logMessage("SIGPIPE - Broken pipe (AI program no longer running)\n");
if (Game::theGame->printBoard)
Game::theGame->theBoard.PrintPretty(stdout, Piece::BOTH);
......@@ -285,7 +287,7 @@ void Game::HandleBrokenPipe(int sig)
else
#endif //BUILD_GRAPHICS
{
if (theGame->log == stdout)
if (theGame->log == stdout || theGame->log == stderr)
{
theGame->logMessage( "PRESS ENTER TO EXIT\n");
theGame->theBoard.Print(theGame->log);
......@@ -354,14 +356,17 @@ void Game::PrintEndMessage(const MovementResult & result)
case MovementResult::POSITION_FULL:
logMessage("Attempted move into square occupied by neutral or allied piece\n");
break;
case MovementResult::VICTORY:
case MovementResult::VICTORY_FLAG:
logMessage("Captured the flag\n");
break;
case MovementResult::VICTORY_ATTRITION:
logMessage("Destroyed all mobile enemy pieces\n");
break;
case MovementResult::BAD_RESPONSE:
logMessage("Unintelligable response\n");
break;
case MovementResult::NO_MOVE:
logMessage("Did not make a move (may have exited)\n");
logMessage("Response timeout after %2f seconds.\n", timeoutTime);
break;
case MovementResult::COLOUR_ERROR:
logMessage("Internal controller error - COLOUR_ERROR\n");
......@@ -432,6 +437,7 @@ void Game::PrintEndMessage(const MovementResult & result)
{
logMessage("PRESS ENTER TO EXIT\n");
while (fgetc(stdin) != '\n');
exit(EXIT_SUCCESS); //Might want to actually exit, you foolish fool
}
}
......@@ -485,7 +491,16 @@ MovementResult Game::Play()
if (Board::HaltResult(result))
break;
if (stallTime > 0)
if (theBoard.MobilePieces(Piece::BLUE) == 0)
{
if (theBoard.MobilePieces(Piece::RED) == 0)
result = MovementResult::DRAW;
else
result = MovementResult::VICTORY_ATTRITION;
break;
}
if (stallTime >= 0)
Wait(stallTime);
else
ReadUserCommand();
......@@ -517,17 +532,24 @@ MovementResult Game::Play()
if (Board::HaltResult(result))
break;
if (theBoard.MobilePieces(Piece::RED) == 0)
result = MovementResult::DRAW;
if (theBoard.MobilePieces(Piece::RED) == 0)
{
if (theBoard.MobilePieces(Piece::BLUE) == 0)
result = MovementResult::DRAW;
else
result = MovementResult::VICTORY_ATTRITION;
break;
}
if (stallTime > 0)
if (stallTime >= 0)
Wait(stallTime);
else
ReadUserCommand();
if (theBoard.MobilePieces(Piece::BOTH) == 0)
result = MovementResult::DRAW;
++turnCount;
}
......@@ -569,12 +591,18 @@ int Game::logMessage(const char * format, ...)
*/
void Game::ReadUserCommand()
{
fprintf(stdout, "Waiting for user to press enter...\n");
fprintf(stdout, "Waiting for user to press enter... (type QUIT to exit)\n");
string command("");
for (char c = fgetc(stdin); c != '\n' && (int)(c) != EOF; c = fgetc(stdin))
{
command += c;
}
if (command == "QUIT")
{
fprintf(stdout, "Ordered to quit... exiting...\n");
exit(EXIT_SUCCESS);
}
}
MovementResult FileController::QuerySetup(const char * opponentName, std::string setup[])
......@@ -610,17 +638,86 @@ MovementResult FileController::QuerySetup(const char * opponentName, std::string
MovementResult FileController::QueryMove(std::string & buffer)
{
//This bit is kind of hacky and terrible, and yes I am mixing C with C++
//Yes I should have used fstream for the whole thing and it would be much easier.
//Oh well.
char buf[BUFSIZ];
fgets(buf, sizeof(buf), file);
char * s = (char*)(buf);
while (*s != ':' && *s != '\0')
++s;
s += 2;
//Move forward to the start of the move information
for (int i=0; i < 2; ++i)
{
if (*s != '\0' && *s != '\n')
++s;
}
//Unfortunately we can't just copy the whole line
buffer = string(s);
//We have to remove the movement result tokens
vector<string> tokens;
Game::Tokenise(tokens, buffer, ' ');
buffer.clear();
if (tokens.size() < 1)
return MovementResult::BAD_RESPONSE;
buffer += tokens[0];
if (tokens[0] == "NO_MOVE") //tokens[0] is either the x coordinate, or "NO_MOVE"
return MovementResult::OK;
if (tokens.size() < 2)
return MovementResult::BAD_RESPONSE;
buffer += " ";
buffer += tokens[1]; //The y coordinate
buffer += " ";
buffer += tokens[2]; //The direction
//Check for a possible multiplier. If tokens[3] is an integer it will be the multiplier, otherwise it won't be.
if (tokens.size() > 3 && atoi(tokens[3].c_str()) != 0)
{
buffer += " ";
buffer += tokens[3];
}
else
{
//(tokens[3] should include a new line)
//buffer += "\n";
}
return MovementResult::OK;
}
/**
* Tokenise a string
*/
int Game::Tokenise(std::vector<string> & buffer, std::string & str, char split)
{
string token = "";
for (unsigned int x = 0; x < str.size(); ++x)
{
if (str[x] == split && token.size() > 0)
{
buffer.push_back(token);
token = "";
}
if (str[x] != split)
token += str[x];
}
if (token.size() > 0)
buffer.push_back(token);
return buffer.size();
}
......@@ -9,12 +9,13 @@
/**
* Class to manage the game
* Bit messy since I keep adding on parameters :P
*/
class Game
{
public:
Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false);
Game(const char * fromFile, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false);
Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false, double newTimeoutTime = 2.0);
Game(const char * fromFile, const bool enableGraphics, double newStallTime = 1.0, const bool allowIllegal=false, FILE * newLog = NULL, const Piece::Colour & newRevealed = Piece::BOTH, int maxTurns = 5000, const bool printBoard = false, double newTimeoutTime = 2.0);
virtual ~Game();
......@@ -34,6 +35,7 @@ class Game
int TurnCount() const {return turnCount;}
static Game * theGame;
static int Tokenise(std::vector<std::string> & buffer, std::string & str, char split = ' '); //Helper - Split a string into tokens
public:
int logMessage(const char * format, ...);
FILE * GetLogFile() const {return log;}
......@@ -63,6 +65,9 @@ class Game
int maxTurns;
const bool printBoard;
private:
double timeoutTime;
};
......
//#define BUILD_GRAPHICS
#define BUILD_GRAPHICS
#ifdef BUILD_GRAPHICS
#ifndef GRAPHICS_H
......
......@@ -62,8 +62,8 @@ int main(int argc, char ** argv)
Piece::Colour SetupGame(int argc, char ** argv)
{
char * red = NULL; char * blue = NULL; double timeout = 0.00001; bool graphics = false; bool allowIllegal = false; FILE * log = NULL;
Piece::Colour reveal = Piece::BOTH; char * inputFile = NULL; int maxTurns = 5000; bool printBoard = false;
char * red = NULL; char * blue = NULL; double stallTime = 0.0; bool graphics = false; bool allowIllegal = false; FILE * log = NULL;
Piece::Colour reveal = Piece::BOTH; char * inputFile = NULL; int maxTurns = 5000; bool printBoard = false; double timeoutTime = 2.0;
for (int ii=1; ii < argc; ++ii)
{
if (argv[ii][0] == '-')
......@@ -73,15 +73,29 @@ Piece::Colour SetupGame(int argc, char ** argv)
case 't':
if (argc - ii <= 1)
{
fprintf(stderr, "ARGUMENT_ERROR - Expected timeout value after -t switch!\n");
fprintf(stderr, "ARGUMENT_ERROR - Expected stall time value after -t switch!\n");
exit(EXIT_FAILURE);
}
if (strcmp(argv[ii+1], "inf") == 0)
timeout = -1;
stallTime = -1;
else
timeout = atof(argv[ii+1]);
stallTime = atof(argv[ii+1]);
++ii;
break;
case 'T':
if (argc - ii <= 1)
{
fprintf(stderr, "ARGUMENT_ERROR - Expected timeout value after -T switch!\n");
exit(EXIT_FAILURE);
}
if (strcmp(argv[ii+1], "inf") == 0)
timeoutTime = -1;
else
timeoutTime = atof(argv[ii+1]);
++ii;
break;
case 'g':
#ifdef BUILD_GRAPHICS
graphics = !graphics;
......@@ -192,7 +206,8 @@ Piece::Colour SetupGame(int argc, char ** argv)
}
}
if (graphics && stallTime == 0.0)
stallTime = 0.00001; //Hack so that SDL events (ie SDL_QUIT) will have time to be captured when graphics are enabled
if (inputFile == NULL)
{
......@@ -201,11 +216,11 @@ Piece::Colour SetupGame(int argc, char ** argv)
fprintf(stderr, "ARGUMENT_ERROR - Did not recieve enough players (did you mean to use the -f switch?)\n");
exit(EXIT_FAILURE);
}
Game::theGame = new Game(red,blue, graphics, timeout, allowIllegal,log, reveal,maxTurns, printBoard);
Game::theGame = new Game(red,blue, graphics, stallTime, allowIllegal,log, reveal,maxTurns, printBoard, timeoutTime);
}
else
{
Game::theGame = new Game(inputFile, graphics, timeout, allowIllegal,log, reveal,maxTurns, printBoard);
Game::theGame = new Game(inputFile, graphics, stallTime, allowIllegal,log, reveal,maxTurns, printBoard, timeoutTime);
}
if (Game::theGame == NULL)
......@@ -247,7 +262,8 @@ void PrintResults(const MovementResult & result, string & buffer)
{
switch (result.type)
{
case MovementResult::VICTORY:
case MovementResult::VICTORY_FLAG:
case MovementResult::VICTORY_ATTRITION: //It does not matter how you win, it just matters that you won!
s << "VICTORY ";
break;
case MovementResult::SURRENDER:
......
......@@ -13,7 +13,7 @@ class Piece;
class MovementResult
{
public:
typedef enum {OK, DIES, KILLS, BOTH_DIE, NO_BOARD, INVALID_POSITION, NO_SELECTION, NOT_YOUR_UNIT, IMMOBILE_UNIT, INVALID_DIRECTION, POSITION_FULL, VICTORY, SURRENDER, BAD_RESPONSE, NO_MOVE, COLOUR_ERROR, ERROR, DRAW_DEFAULT, DRAW, BAD_SETUP} Type;
typedef enum {OK, DIES, KILLS, BOTH_DIE, NO_BOARD, INVALID_POSITION, NO_SELECTION, NOT_YOUR_UNIT, IMMOBILE_UNIT, INVALID_DIRECTION, POSITION_FULL, VICTORY_FLAG, VICTORY_ATTRITION, SURRENDER, BAD_RESPONSE, NO_MOVE, COLOUR_ERROR, ERROR, DRAW_DEFAULT, DRAW, BAD_SETUP} Type;
MovementResult(const Type & result = OK, const Piece::Type & newAttackerRank = Piece::NOTHING, const Piece::Type & newDefenderRank = Piece::NOTHING)
: type(result), attackerRank(newAttackerRank), defenderRank(newDefenderRank) {}
......
......@@ -140,12 +140,13 @@ bool Program::SendMessage(const char * print, ...)
*/
bool Program::GetMessage(string & buffer, double timeout)
{
if (!Running())
if (!Running() || timeout == 0)
return false;
assert(&buffer != NULL);
GetterThread getterThread(input, buffer);
assert(&(getterThread.buffer) != NULL);
TimerThread timerThread(timeout*1000000);
getterThread.Start();
......@@ -164,7 +165,8 @@ bool Program::GetMessage(string & buffer, double timeout)
}
getterThread.Stop();
timerThread.Stop();
if (timeout > 0)
timerThread.Stop();
......
......@@ -375,7 +375,7 @@ MovementResult Board::MovePiece(int x, int y, const Direction & direction, int m
if (defender->type == Piece::FLAG)
{
winner = target->colour;
return MovementResult(MovementResult::VICTORY);
return MovementResult(MovementResult::VICTORY_FLAG);
}
else if (defender->type == Piece::BOMB)
{
......
......@@ -124,12 +124,12 @@ class Board
static bool LegalResult(const MovementResult & result)
{
return (result == MovementResult::OK || result == MovementResult::DIES || result == MovementResult::KILLS || result == MovementResult::BOTH_DIE || result == MovementResult::VICTORY || result == MovementResult::DRAW || result == MovementResult::DRAW_DEFAULT || result == MovementResult::SURRENDER);
return (result == MovementResult::OK || result == MovementResult::DIES || result == MovementResult::KILLS || result == MovementResult::BOTH_DIE || result == MovementResult::VICTORY_FLAG || result == MovementResult::VICTORY_ATTRITION || result == MovementResult::DRAW || result == MovementResult::DRAW_DEFAULT || result == MovementResult::SURRENDER);
}
static bool HaltResult(const MovementResult & result)
{
return (result == MovementResult::VICTORY || result == MovementResult::DRAW || result == MovementResult::DRAW_DEFAULT || result == MovementResult::SURRENDER || !LegalResult(result));
return (result == MovementResult::VICTORY_FLAG || result == MovementResult::VICTORY_ATTRITION || result == MovementResult::DRAW || result == MovementResult::DRAW_DEFAULT || result == MovementResult::SURRENDER || !LegalResult(result));
}
MovementResult MovePiece(int x, int y, const Direction & direction, int multiplier=1,const Piece::Colour & colour=Piece::NONE); //Move piece from position in direction
......
......@@ -27,6 +27,8 @@ baseDirectory = "../.." #Base directory for results, logs, agents
nGames = 2 #Number of games played by each agent against each opponent. Half will be played as RED, half as BLUE. If nGames <= 1, then no games will be played (useful for dry run?)
nRounds = 1
timeoutValue = 2
if len(sys.argv) >= 2:
nRounds = int(sys.argv[1])
if len(sys.argv) >= 3:
......@@ -226,7 +228,7 @@ for roundNumber in range(totalRounds, totalRounds + nRounds):
if verbose:
sys.stdout.write("Agents: \""+red["name"]+"\" and \""+blue["name"]+"\" playing game (ID: " + gameID + ") ... ")
logFile = logDirectory + "round"+str(roundNumber) + "/"+red["name"]+".vs."+blue["name"]+"."+str(gameID)
outline = os.popen(managerPath + " -o " + logFile + " " + red["path"] + " " + blue["path"], "r").read()
outline = os.popen(managerPath + " -o " + logFile + " -T " + str(timeoutValue) + " " + red["path"] + " " + blue["path"], "r").read()
results = outline.split(' ')
if len(results) != 6:
......
......@@ -5,7 +5,7 @@ WARNING
This program is still a work in progress. Consider it a Beta version.
SYNOPSIS
stratego {[-gpirb] [-o output_file ] [-t stall_time] [-m max_turns] {red_player blue_player | -f input_file} | {-h | --help} }
stratego {[-gpirb] [-o output_file ] [-t stall_time] [-T timeout_time] [-m max_turns] {red_player blue_player | -f input_file} | {-h | --help} }
DESCRIPTION
stratego manages a game of Stratego. It stores the state of the board, and uses a simple protocol to interface with AI programs.
......@@ -69,6 +69,15 @@ OPTIONS
It is tentatively planned to allow the user to enter various commands to alter the game or proceed to specified turns.
However this is slightly complicated. So it might never be done.
-T
By default, stratego allows AI programs 2 seconds to respond before declaring their move ILLEGAL due to a timeout.
If the -T switch is present, AI programs will be allowed timeout_time to respond before their move is declared ILLEGAL.
If timeout_time is negative or "inf", stratego will never declare moves illegal due to timeouts.
Human players are never subject to timeouts.
-m
By default, the game is declared a Draw after 5000 turns have ellapsed.
Use this option to change the maximum number of turns.
......@@ -116,11 +125,12 @@ GAME RULES
Each player's pieces are hidden from the other player. When two pieces encounter each other, the ranks will be revealed.
The objective is to destroy all Enemy Pieces (#) or capture the Enemy Flag (also #).
The objective is to either destroy all enemy pieces except the Bombs and Flag, or to capture the Flag.
Since 20/12 Bombs reflect the traditional rules; they are only destroyed by Miners.
In previous versions contact of an attacker other than a Miner with a Bomb destroyed the Bomb as well as the attacking piece.
PROTOCOL
In order to interface with stratego, an AI program must satisfy the following protocol.
......@@ -181,8 +191,10 @@ PROTOCOL
4. TIMEOUTS
If a program fails to respond to a query within 2 (two) seconds, the game will end and that AI will be sent the ILLEGAL result.
If a program fails to respond to a query, the game will end and that AI will be sent the ILLEGAL result.
Human players are not subject to the timeout restriction.
Please see the information on the -T switch.
......
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