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!
#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
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 ";
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)
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)
{
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())
{
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
{
}
- 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);
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);
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");
{
logMessage("PRESS ENTER TO EXIT\n");
while (fgetc(stdin) != '\n');
+ exit(EXIT_SUCCESS); //Might want to actually exit, you foolish fool
}
}
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();
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;
}
*/
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[])
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();
+}
+
/**
* 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();
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;}
int maxTurns;
const bool printBoard;
+
+ private:
+ double timeoutTime;
};
-//#define BUILD_GRAPHICS
+#define BUILD_GRAPHICS
#ifdef BUILD_GRAPHICS
#ifndef GRAPHICS_H
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] == '-')
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;
}
}
-
+ 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)
{
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)
{
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:
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) {}
*/
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();
}
getterThread.Stop();
- timerThread.Stop();
+ if (timeout > 0)
+ timerThread.Stop();
if (defender->type == Piece::FLAG)
{
winner = target->colour;
- return MovementResult(MovementResult::VICTORY);
+ return MovementResult(MovementResult::VICTORY_FLAG);
}
else if (defender->type == Piece::BOMB)
{
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
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:
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:
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.
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.
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.
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.