Commit 2120cc40 authored by Sam Moore's avatar Sam Moore

Modified manager output/protocol, added "basic" AI, made Asmodeus better

Thats all
parent fe470c01
......@@ -80,6 +80,16 @@ MovementResult Controller::MakeMove(string & buffer)
if (query != MovementResult::OK)
return query;
if (buffer == "NO_MOVE")
{
buffer += " OK";
return MovementResult::OK;
}
if (buffer == "SURRENDER")
{
buffer += " OK";
return MovementResult::SURRENDER;
}
int x; int y; string direction="";
stringstream s(buffer);
......@@ -149,7 +159,7 @@ MovementResult Controller::MakeMove(string & buffer)
}
if (Game::theGame->allowIllegalMoves && !Board::LegalResult(moveResult))
return MovementResult::OK; //HACK - Legal results returned!
return MovementResult::OK; //HACK - Illegal results returned as legal!
else
return moveResult;
......
......@@ -21,7 +21,7 @@ class Controller
void Message(std::string & buffer) {Message(buffer.c_str());}
void Message(const std::string & buffer) {Message(buffer.c_str());}
virtual void Message(const char * string) = 0;
virtual MovementResult QuerySetup(const char * opponentName, std::string setup[]) = 0;
......
......@@ -74,7 +74,16 @@ Game::~Game()
fclose(input);
}
bool Game::Setup(const char * redName, const char * blueName)
/**
* Attempts to setup the board and controllers
* @param redName the name of the red AI
* @param blueName the name of the blue AI
* @returns A colour, indicating if there were any errors
Piece::NONE indicates no errors
Piece::BOTH indicates errors with both AI
Piece::RED / Piece::BLUE indicates an error with only one of the two AI
*/
Piece::Colour Game::Setup(const char * redName, const char * blueName)
{
if (!red->Valid())
......@@ -85,8 +94,16 @@ bool Game::Setup(const char * redName, const char * blueName)
{
logMessage("Controller for Player BLUE is invalid!\n");
}
if (!red->Valid() || !blue->Valid())
return false;
if (!red->Valid())
{
if (!blue->Valid())
return Piece::BOTH;
return Piece::RED;
}
else if (!blue->Valid())
{
return Piece::BLUE;
}
for (int y = 4; y < 6; ++y)
{
......@@ -104,35 +121,38 @@ bool Game::Setup(const char * redName, const char * blueName)
MovementResult redSetup = red->Setup(blueName);
MovementResult blueSetup = blue->Setup(redName);
Piece::Colour result = Piece::NONE;
if (redSetup != MovementResult::OK)
{
if (blueSetup != MovementResult::OK)
{
logMessage("BOTH players give invalid setup!\n");
red->Message("ILLEGAL");
blue->Message("ILLEGAL");
result = Piece::BOTH;
}
else
{
logMessage("Player RED gave an invalid setup!\n");
red->Message("ILLEGAL");
blue->Message("DEFAULT");
result = Piece::RED;
}
return false;
}
else if (blueSetup != MovementResult::OK)
{
logMessage("Player BLUE gave an invalid setup!\n");
red->Message("DEFAULT");
blue->Message("ILLEGAL");
return false;
result = Piece::BLUE;
}
logMessage("%s RED SETUP\n", red->name.c_str());
for (int y=0; y < 4; ++y)
{
for (int x=0; x < theBoard.Width(); ++x)
logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, y)->type)]);
{
if (theBoard.GetPiece(x, y) != NULL)
logMessage("%c", Piece::tokens[(int)(theBoard.GetPiece(x, y)->type)]);
else
logMessage(".");
}
logMessage("\n");
}
......@@ -145,7 +165,7 @@ bool Game::Setup(const char * redName, const char * blueName)
}
return true;
return result;
}
......@@ -241,18 +261,25 @@ void Game::HandleBrokenPipe(int sig)
void Game::PrintEndMessage(const MovementResult & result)
{
if (turn == Piece::RED)
{
logMessage("Game ends on RED's turn - REASON: ");
}
else if (turn == Piece::BLUE)
if (turnCount == 0)
{
logMessage("Game ends on BLUE's turn - REASON: ");
logMessage("Game ends in the SETUP phase - REASON: ");
}
else
{
logMessage("Game ends on ERROR's turn - REASON: ");
if (turn == Piece::RED)
{
logMessage("Game ends on RED's turn - REASON: ");
}
else if (turn == Piece::BLUE)
{
logMessage("Game ends on BLUE's turn - REASON: ");
}
else
{
logMessage("Game ends on ERROR's turn - REASON: ");
}
}
switch (result.type)
{
......@@ -287,7 +314,7 @@ void Game::PrintEndMessage(const MovementResult & result)
logMessage("Selected unit cannot move that way\n");
break;
case MovementResult::POSITION_FULL:
logMessage("Attempted move into square occupied by allied piece\n");
logMessage("Attempted move into square occupied by neutral or allied piece\n");
break;
case MovementResult::VICTORY:
logMessage("Captured the flag\n");
......@@ -304,9 +331,32 @@ void Game::PrintEndMessage(const MovementResult & result)
case MovementResult::ERROR:
logMessage("Internal controller error - Unspecified ERROR\n");
break;
case MovementResult::DRAW:
case MovementResult::DRAW_DEFAULT:
logMessage("Game declared a draw after %d turns\n", turnCount);
break;
case MovementResult::DRAW:
logMessage("Game declared a draw because neither player has mobile pieces\n");
break;
case MovementResult::SURRENDER:
logMessage("This player has surrendered!\n");
break;
case MovementResult::BAD_SETUP:
switch (turn)
{
case Piece::RED:
logMessage("An illegal setup was made by RED\n");
break;
case Piece::BLUE:
logMessage("An illegal setup was made by BLUE\n");
break;
case Piece::BOTH:
logMessage("An illegal setup was made by BOTH players\n");
break;
case Piece::NONE:
logMessage("Unknown internal error.\n");
break;
}
break;
}
......@@ -350,7 +400,7 @@ void Game::PrintEndMessage(const MovementResult & result)
MovementResult Game::Play()
{
MovementResult result = MovementResult::OK;
turnCount = 1;
string buffer;
......@@ -359,7 +409,7 @@ MovementResult Game::Play()
red->Message("START");
//logMessage("START\n");
while (Board::LegalResult(result) && (turnCount < maxTurns || maxTurns < 0))
while (!Board::HaltResult(result) && (turnCount < maxTurns || maxTurns < 0))
{
......@@ -369,7 +419,7 @@ MovementResult Game::Play()
red->Message(buffer);
blue->Message(buffer);
logMessage( "%s\n", buffer.c_str());
if (!Board::LegalResult(result))
if (Board::HaltResult(result))
break;
if (graphicsEnabled)
theBoard.Draw(reveal);
......@@ -389,7 +439,7 @@ MovementResult Game::Play()
red->Message(buffer);
logMessage( "%s\n", buffer.c_str());
if (!Board::LegalResult(result))
if (Board::HaltResult(result))
break;
......@@ -406,13 +456,15 @@ MovementResult Game::Play()
Wait(stallTime);
if (theBoard.MobilePieces(Piece::BOTH) == 0)
result = MovementResult::DRAW;
++turnCount;
}
if ((maxTurns >= 0 && turnCount >= maxTurns) && result == MovementResult::OK)
{
result = MovementResult::DRAW;
turn = Piece::BOTH;
result = MovementResult::DRAW_DEFAULT;
}
......
......@@ -21,7 +21,7 @@ class Game
void Wait(double wait);
bool Setup(const char * redName, const char * blueName);
Piece::Colour Setup(const char * redName, const char * blueName);
MovementResult Play();
void PrintEndMessage(const MovementResult & result);
......@@ -30,6 +30,7 @@ class Game
const Piece::Colour Turn() const {return turn;}
void ForceTurn(const Piece::Colour & newTurn) {turn = newTurn;}
int TurnCount() const {return turnCount;}
static Game * theGame;
......
......@@ -10,9 +10,9 @@
using namespace std;
void CreateGame(int argc, char ** argv);
Piece::Colour SetupGame(int argc, char ** argv);
void DestroyGame();
void PrintResults(const MovementResult & result);
void PrintResults(const MovementResult & result, string & buffer);
int main(int argc, char ** argv)
{
......@@ -26,22 +26,40 @@ int main(int argc, char ** argv)
exit(EXIT_SUCCESS);
}
CreateGame(argc, argv);
if (Game::theGame == NULL)
Piece::Colour setupError = SetupGame(argc, argv);
MovementResult result = MovementResult::OK;
if (setupError == Piece::NONE)
{
fprintf(stderr, "ERROR: Couldn't create a game!\n");
exit(EXIT_FAILURE);
result = Game::theGame->Play();
}
MovementResult result = Game::theGame->Play();
else
{
result = MovementResult::BAD_SETUP;
Game::theGame->ForceTurn(setupError);
}
Game::theGame->PrintEndMessage(result);
PrintResults(result);
string buffer = "";
PrintResults(result, buffer);
//Message the AI's the quit message
Game::theGame->red->Message("QUIT " + buffer);
Game::theGame->blue->Message("QUIT " + buffer);
//Log the message
if (Game::theGame->GetLogFile() != stdout)
Game::theGame->logMessage("%s\n", buffer.c_str());
fprintf(stdout, "%s\n", buffer.c_str());
exit(EXIT_SUCCESS);
return 0;
}
void CreateGame(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;
......@@ -54,7 +72,7 @@ void CreateGame(int argc, char ** argv)
case 't':
if (argc - ii <= 1)
{
fprintf(stderr, "Expected timeout value after -t switch!\n");
fprintf(stderr, "ARGUMENT_ERROR - Expected timeout value after -t switch!\n");
exit(EXIT_FAILURE);
}
timeout = atof(argv[ii+1]);
......@@ -73,12 +91,12 @@ void CreateGame(int argc, char ** argv)
case 'o':
if (argc - ii <= 1)
{
fprintf(stderr, "Expected filename or \"stdout\" after -o switch!\n");
fprintf(stderr, "ARGUMENT_ERROR - Expected filename or \"stdout\" after -o switch!\n");
exit(EXIT_FAILURE);
}
if (log != NULL)
{
fprintf(stderr, "Expected at most ONE -o switch!\n");
fprintf(stderr, "ARGUMENT_ERROR - Expected at most ONE -o switch!\n");
exit(EXIT_FAILURE);
}
if (strcmp(argv[ii+1], "stdout") == 0)
......@@ -105,10 +123,10 @@ void CreateGame(int argc, char ** argv)
case 'm':
if (argc - ii <= 1)
{
fprintf(stderr, "Expected max_turns value after -m switch!\n");
fprintf(stderr, "ARGUMENT_ERROR - Expected max_turns value after -m switch!\n");
exit(EXIT_FAILURE);
}
if (strcmp(argv[ii+1], "inf"))
if (strcmp(argv[ii+1], "inf") == 0)
maxTurns = -1;
else
maxTurns = atoi(argv[ii+1]);
......@@ -117,12 +135,12 @@ void CreateGame(int argc, char ** argv)
case 'f':
if (argc - ii <= 1)
{
fprintf(stderr, "Expected filename after -f switch!\n");
fprintf(stderr, "ARGUMENT_ERROR - Expected filename after -f switch!\n");
exit(EXIT_FAILURE);
}
if (log != NULL)
{
fprintf(stderr, "Expected at most ONE -f switch!\n");
fprintf(stderr, "ARGUMENT_ERROR - Expected at most ONE -f switch!\n");
exit(EXIT_FAILURE);
}
red = (char*)("file");
......@@ -144,7 +162,7 @@ void CreateGame(int argc, char ** argv)
}
else
{
fprintf(stderr, "Unrecognised switch \"%s\"...\n", argv[ii]);
fprintf(stderr, "ARGUMENT_ERROR - Unrecognised switch \"%s\"...\n", argv[ii]);
exit(EXIT_FAILURE);
}
}
......@@ -158,67 +176,93 @@ void CreateGame(int argc, char ** argv)
blue = argv[ii];
else
{
fprintf(stderr, "Unexpected argument \"%s\"...\n", argv[ii]);
fprintf(stderr, "ARGUMENT_ERROR - Unexpected argument \"%s\"...\n", argv[ii]);
exit(EXIT_FAILURE);
}
}
}
if (inputFile == NULL)
{
if (red == NULL || blue == NULL) //Not enough arguments
{
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);
}
else
{
Game::theGame = new Game(inputFile, graphics, timeout, allowIllegal,log, reveal,maxTurns, printBoard);
}
if (!Game::theGame->Setup(red, blue))
if (Game::theGame == NULL)
{
fprintf(stdout, "NONE %d\n",Game::theGame->TurnCount());
exit(EXIT_SUCCESS);
fprintf(stderr,"INTERNAL_ERROR - Error creating Game!\n");
exit(EXIT_FAILURE);
}
atexit(DestroyGame);
return Game::theGame->Setup(red, blue);
}
void PrintResults(const MovementResult & result)
void PrintResults(const MovementResult & result, string & buffer)
{
Piece::Colour winner = Game::theGame->Turn();
if (Board::LegalResult(result))
{
if (winner == Piece::BOTH)
winner = Piece::NONE;
else
{
if (winner == Piece::RED)
winner = Piece::BLUE;
else
winner = Piece::RED;
}
}
switch (winner)
stringstream s("");
switch (Game::theGame->Turn())
{
case Piece::RED:
fprintf(stdout, "%s RED %d\n", Game::theGame->red->name.c_str(),Game::theGame->TurnCount());
Game::theGame->logMessage("%s RED %d\n", Game::theGame->red->name.c_str(),Game::theGame->TurnCount());
s << Game::theGame->red->name << " RED ";
break;
case Piece::BLUE:
fprintf(stdout, "%s BLUE %d\n", Game::theGame->blue->name.c_str(),Game::theGame->TurnCount());
Game::theGame->logMessage("%s BLUE %d\n", Game::theGame->blue->name.c_str(),Game::theGame->TurnCount());
s << Game::theGame->blue->name << " BLUE ";
break;
case Piece::BOTH:
fprintf(stdout, "DRAW %d\n",Game::theGame->TurnCount());
Game::theGame->logMessage("DRAW %d\n",Game::theGame->TurnCount());
s << "neither BOTH ";
break;
case Piece::NONE:
fprintf(stdout, "NONE %d\n",Game::theGame->TurnCount());
Game::theGame->logMessage("NONE %d\n",Game::theGame->TurnCount());
s << "neither NONE ";
break;
}
if (!Board::LegalResult(result) && result != MovementResult::BAD_SETUP)
s << "ILLEGAL ";
else if (!Board::HaltResult(result))
s << "INTERNAL_ERROR ";
else
{
switch (result.type)
{
case MovementResult::VICTORY:
s << "VICTORY ";
break;
case MovementResult::SURRENDER:
s << "SURRENDER ";
break;
case MovementResult::DRAW:
s << "DRAW ";
break;
case MovementResult::DRAW_DEFAULT:
s << "DRAW_DEFAULT ";
break;
case MovementResult::BAD_SETUP:
s << "BOTH_ILLEGAL ";
break;
default:
s << "INTERNAL_ERROR ";
break;
}
}
s << Game::theGame->TurnCount() << " " << Game::theGame->theBoard.TotalPieceValue(Piece::RED) << " " << Game::theGame->theBoard.TotalPieceValue(Piece::BLUE);
buffer = s.str();
}
void DestroyGame()
......
......@@ -64,7 +64,7 @@ OPTIONS
GAME RULES
Each player sets up 40 pieces on the Board. The pieces consist of the following:
Each player controls up to 40 pieces on the Board. The pieces consist of the following:
Piece Name Rank Number Abilities
1 Marshal 1 1 Dies if attacked by Spy
......@@ -77,16 +77,24 @@ GAME RULES
8 Miner 8 5 Destroys Bombs without being killed
9 Scout 9 8 May move more through multiple empty squares
s Spy 10 1 If the Spy attacks the Marshal, the Marshal dies
B Bomb NA 6 Immobile. If an enemy piece (except a Miner) encounters a Bomb, both pieces are destroyed
F Flag NA 1 Immobile. If any enemy piece encounters the Flag, the controlling player wins.
B Bomb NA 6 Immobile. If any piece (except a Miner) encounters an enemy Bomb, both pieces are destroyed
F Flag NA 1 Immobile. If any piece encounters the enemy Flag, the controlling player wins.
Additional pieces, not controlled by the player:
Piece Name Number Notes
+ Obstacle 8 Immobile. Do not belong to either player. Can't be passed through.
# Enemy Piece 0 - 40 Indicates that the position on the board is occupied by an enemy piece.
. Empty NA Indicates that the position on the board is empty.
Players take turns to move their pieces. RED begins the game.
Pieces may move one square horizontally or vertically unless otherwise stated.
Pieces may not move through squares occupied by allied pieces.
Pieces may move into squares occupied by enemy pieces, in which case the piece with the lower rank (higher number) is destroyed.
Pieces may only move one square horizontally or vertically unless otherwise stated.
Pieces may not move through squares occupied by allied pieces, or Obstacle (+) pieces.
Pieces may move into squares occupied by Enemy Pieces (#), in which case the piece with the lower rank (higher number) is destroyed.
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 Flag.
The objective is to destroy all Enemy Pieces (#) or capture the Enemy Flag (also #).
PROTOCOL
......@@ -100,32 +108,46 @@ PROTOCOL
RESPONSE: 4 lines, each of length BOARD_WIDTH, of characters. Each character represents a piece. The characters are shown above.
2. TURN
QUERY: START | CONFIRMATION
QUERY: START | CONFIRMATION
BOARD_STATE
On the first turn, "START" is printed to the Red player.
On subsequent turns, the CONFIRMATION of the opponent's last turn is printed (see below).
RESPONSE: X Y DIRECTION [MULTIPLIER=1]
BOARD_STATE consists of a BOARD_HEIGHT lines of length BOARD_WIDTH characters, each of which represents a single piece
as described in the GAME_RULES section. Each line ends with the newline character.
RESPONSE: X Y DIRECTION [MULTIPLIER=1] | NO_MOVE
X and Y are the coords (starting from 0) of the piece to move
DIRECTION is either UP, DOWN, LEFT or RIGHT
MULTIPLIER is optional and only valid for units of type Scout. Scouts may move through any number of unblocked squares
in one direction.
CONFIRMATION: X Y DIRECTION [MULTIPLIER=1] OUTCOME
The AI program should print "NO_MOVE" if it is unable to determine a move.
This will typically occur when the only pieces belonging to the AI program are Bombs and the Flag.
CONFIRMATION: X Y DIRECTION [MULTIPLIER=1] OUTCOME | NO_MOVE | QUIT RESULT
OUTCOME may be either OK, ILLEGAL, KILLS or DIES
OK - Move was successful
ILLEGAL - Move was not allowed. If stratego was not started with the -i switch, the game will end.
KILLS ATTACKER_RANK DEFENDER_RANK - The piece moved into an occupied square and killed the defender.
DIES ATTACKER_RANK DEFENDER_RANK - The piece moved into an occupied square and was killed by the defender.
A confirmation of "NO_MOVE" occurs when the AI program made no move for a legitimate reason.
"NO_MOVE ILLEGAL" is printed if the AI program made no move for an illegitimate reason.
If both AI programs successively make a "NO_MOVE" response, then the game will end.
The player with the highest piece value will win, or a draw will be declared if the values are equal.
3. END GAME
QUERY: VICTORY | DEFEAT | ILLEGAL | DEFAULT
VICTORY - This player won the game
DEFEAT - The other player won the game
ILLEGAL - Game ended because this player made a bad response or timed out
(NOTE: Even if the -i option is provided, the game may end with an ILLEGAL signal if a bad response is made)
DEFAULT - Game ended because the other player made a bad response.
No response is necessary; the program should exit or it will be sent a SIGKILL signal.
If the CONFIRMATION line is of the form:
QUIT RESULT
Then the game is about to end.
If present, RESULT will be a direct copy of the message to stdout described in the EXIT/OUTPUT section below.
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.
......@@ -136,15 +158,30 @@ PROTOCOL
EXIT/OUTPUT
If the game ends due to a player either winning, or making an illegal move, stratego will print one of the following result messages to stdout.
1.
WINNING_NAME WINNING_COLOUR TURNS
2.
DRAW TURNS
When the result was a draw
NAME COLOUR OUTCOME TURN_NUMBER OUTCOME RED_PIECE_VALUE BLUE_PIECE_VALUE
Where: