Commit 2ab27eb6 authored by Sam Moore's avatar Sam Moore

Modified Turn Response Protocol, added handling for SIGPIPE, changed placeholder images

The "outcome" of a move is now listed as:

TYPE [ATTACKER_RANK] [DEFENDER_RANK]

Where ATTACKER_RANK and DEFENDER_RANK will be present if TYPE is one of: KILLS, DIES, BOTHDIE, and indicate the ranks of the pieces involved.
This involved adding a class MovementResult, which stores the ranks of pieces in addition to an enum, replacing the enum Board::MovementResult

The sample agent "forfax" was causing broken pipes, which caused the manager program to exit.
I added a handler for SIGPIPE in manager/main.cpp to ensure that the manager program reports a DEFAULT victory to the other AI, and exits gracefully.
However, I still don't know WHY forfax causes broken pipes, but hopefully its a problem with forfax and not with the manager program.

I edited the images used by the graphical display to show the ordered ranks of the pieces, rather than some obscure characters.
Unfortunately I have just realised that the enum used for Piece::Type stores ranks in the wrong order.
In the actual game, LOWER numbers are better, in my enum, HIGHER numbers are better.
To make things more confusing, I made the printed ATTACKER_RANK and DEFENDER_RANK correspond to the traditional numbering, not the enum numbering...
parent f91a915d
......@@ -8,9 +8,10 @@ using namespace std;
/**
* Queries the AI program to setup its pieces
* @param opponentName - string containing the name/id of the opponent AI program
* @returns the result of the response
*/
Board::MovementResult Controller::Setup(const char * opponentName)
MovementResult Controller::Setup(const char * opponentName)
{
int y;
switch (colour)
......@@ -48,12 +49,12 @@ Board::MovementResult Controller::Setup(const char * opponentName)
if (!GetMessage(line, 2.5))
{
fprintf(stderr, "Timeout on setup\n");
return Board::BAD_RESPONSE;
return MovementResult::BAD_RESPONSE;
}
if ((int)(line.size()) != Board::theBoard.Width())
{
fprintf(stderr, "Bad length of \"%s\" on setup\n", line.c_str());
return Board::BAD_RESPONSE;
return MovementResult::BAD_RESPONSE;
}
for (int x = 0; x < (int)(line.size()); ++x)
......@@ -69,7 +70,7 @@ Board::MovementResult Controller::Setup(const char * opponentName)
if (usedUnits[type] > Piece::maxUnits[(int)type])
{
fprintf(stderr, "Too many units of type %c\n", Piece::tokens[(int)(type)]);
return Board::BAD_RESPONSE;
return MovementResult::BAD_RESPONSE;
}
Board::theBoard.AddPiece(x, y+ii, type, colour);
......@@ -79,10 +80,10 @@ Board::MovementResult Controller::Setup(const char * opponentName)
if (usedUnits[(int)Piece::FLAG] <= 0)
{
return Board::BAD_RESPONSE; //You need to include a flag!
return MovementResult::BAD_RESPONSE; //You need to include a flag!
}
return Board::OK;
return MovementResult::OK;
}
......@@ -90,11 +91,11 @@ Board::MovementResult Controller::Setup(const char * opponentName)
* Queries the AI program to respond to a state of Board::theBoard
* @returns The result of the response and/or move if made
*/
Board::MovementResult Controller::MakeMove(string & buffer)
MovementResult Controller::MakeMove(string & buffer)
{
if (!Running())
return Board::NO_MOVE; //AI has quit
return MovementResult::NO_MOVE; //AI has quit
Board::theBoard.Print(output, colour);
......@@ -103,7 +104,7 @@ Board::MovementResult Controller::MakeMove(string & buffer)
buffer.clear();
if (!GetMessage(buffer,2))
{
return Board::NO_MOVE; //AI did not respond. It will lose by default.
return MovementResult::NO_MOVE; //AI did not respond. It will lose by default.
}
int x; int y; string direction="";
......@@ -133,29 +134,39 @@ Board::MovementResult Controller::MakeMove(string & buffer)
else
{
fprintf(stderr, "BAD_RESPONSE \"%s\"\n", buffer.c_str());
return Board::BAD_RESPONSE; //AI gave bogus direction - it will lose by default.
return MovementResult::BAD_RESPONSE; //AI gave bogus direction - it will lose by default.
}
int multiplier = 1;
if (s.peek() != EOF)
s >> multiplier;
Board::MovementResult moveResult = Board::theBoard.MovePiece(x, y, dir, multiplier, colour);
switch (moveResult)
MovementResult moveResult = Board::theBoard.MovePiece(x, y, dir, multiplier, colour);
s.clear(); s.str("");
//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";
switch (moveResult.type)
{
case Board::OK:
case MovementResult::OK:
buffer += " OK";
break;
case Board::VICTORY:
case MovementResult::VICTORY:
buffer += " FLAG";
break;
case Board::KILLS:
buffer += " KILLS";
case MovementResult::KILLS:
buffer += " KILLS ";
buffer += s.str();
break;
case Board::DIES:
buffer += " DIES";
case MovementResult::DIES:
buffer += " DIES ";
buffer += s.str();
break;
case Board::BOTH_DIE:
buffer += " BOTHDIE";
case MovementResult::BOTH_DIE:
buffer += " BOTHDIE ";
buffer += s.str();
break;
default:
buffer += " ILLEGAL";
......@@ -164,7 +175,7 @@ Board::MovementResult Controller::MakeMove(string & buffer)
}
if (!Board::LegalResult(moveResult))
return Board::OK; //HACK - Legal results returned!
return MovementResult::OK; //HACK - Legal results returned!
else
return moveResult;
......
......@@ -15,9 +15,9 @@ class Controller : public Program
Controller(const Piece::Colour & newColour, const char * executablePath) : Program(executablePath), colour(newColour) {}
virtual ~Controller() {}
Board::MovementResult Setup(const char * opponentName); //Requests the AI program for the initial positioning of its pieces.
MovementResult Setup(const char * opponentName); //Requests the AI program for the initial positioning of its pieces.
Board::MovementResult MakeMove(std::string & buffer); //Queries the AI program for a response to the state of Board::theBoard
MovementResult MakeMove(std::string & buffer); //Queries the AI program for a response to the state of Board::theBoard
const Piece::Colour colour; //Colour identifying the side of the AI program.
......
../samples/forfax/forfax
\ No newline at end of file
manager/images/piece10.bmp

1.29 KB | W: | H:

manager/images/piece10.bmp

1.3 KB | W: | H:

manager/images/piece10.bmp
manager/images/piece10.bmp
manager/images/piece10.bmp
manager/images/piece10.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece11.bmp

1.32 KB | W: | H:

manager/images/piece11.bmp

1.27 KB | W: | H:

manager/images/piece11.bmp
manager/images/piece11.bmp
manager/images/piece11.bmp
manager/images/piece11.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece12.bmp

1.34 KB | W: | H:

manager/images/piece12.bmp

1.13 KB | W: | H:

manager/images/piece12.bmp
manager/images/piece12.bmp
manager/images/piece12.bmp
manager/images/piece12.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece13.bmp

1.34 KB | W: | H:

manager/images/piece13.bmp

1.09 KB | W: | H:

manager/images/piece13.bmp
manager/images/piece13.bmp
manager/images/piece13.bmp
manager/images/piece13.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece2.bmp

1.21 KB | W: | H:

manager/images/piece2.bmp

1.07 KB | W: | H:

manager/images/piece2.bmp
manager/images/piece2.bmp
manager/images/piece2.bmp
manager/images/piece2.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece3.bmp

1.28 KB | W: | H:

manager/images/piece3.bmp

1.38 KB | W: | H:

manager/images/piece3.bmp
manager/images/piece3.bmp
manager/images/piece3.bmp
manager/images/piece3.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece4.bmp

1.28 KB | W: | H:

manager/images/piece4.bmp

1.31 KB | W: | H:

manager/images/piece4.bmp
manager/images/piece4.bmp
manager/images/piece4.bmp
manager/images/piece4.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece5.bmp

1.23 KB | W: | H:

manager/images/piece5.bmp

1.34 KB | W: | H:

manager/images/piece5.bmp
manager/images/piece5.bmp
manager/images/piece5.bmp
manager/images/piece5.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece6.bmp

1.22 KB | W: | H:

manager/images/piece6.bmp

1.2 KB | W: | H:

manager/images/piece6.bmp
manager/images/piece6.bmp
manager/images/piece6.bmp
manager/images/piece6.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece7.bmp

1.31 KB | W: | H:

manager/images/piece7.bmp

1.35 KB | W: | H:

manager/images/piece7.bmp
manager/images/piece7.bmp
manager/images/piece7.bmp
manager/images/piece7.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece8.bmp

1.18 KB | W: | H:

manager/images/piece8.bmp

1.24 KB | W: | H:

manager/images/piece8.bmp
manager/images/piece8.bmp
manager/images/piece8.bmp
manager/images/piece8.bmp
  • 2-up
  • Swipe
  • Onion skin
manager/images/piece9.bmp

1.25 KB | W: | H:

manager/images/piece9.bmp

1.19 KB | W: | H:

manager/images/piece9.bmp
manager/images/piece9.bmp
manager/images/piece9.bmp
manager/images/piece9.bmp
  • 2-up
  • Swipe
  • Onion skin
......@@ -18,8 +18,12 @@ using namespace std;
Controller * red;
Controller * blue;
Colour turn;
void cleanup();
void BrokenPipe(int sig);
int main(int argc, char ** argv)
{
assert(argc == 3);
......@@ -39,17 +43,18 @@ int main(int argc, char ** argv)
red = new Controller(Piece::RED, argv[1]);
blue = new Controller(Piece::BLUE, argv[2]);
atexit(cleanup);
signal(SIGPIPE, BrokenPipe);
Board::MovementResult redSetup = red->Setup(argv[2]);
Board::MovementResult blueSetup = blue->Setup(argv[1]);
if (redSetup != Board::OK)
MovementResult redSetup = red->Setup(argv[2]);
MovementResult blueSetup = blue->Setup(argv[1]);
if (redSetup != MovementResult::OK)
{
fprintf(stderr, "Blue wins by DEFAULT!\n");
red->SendMessage("ILLEGAL");
blue->SendMessage("DEFAULT");
exit(EXIT_SUCCESS);
}
if (blueSetup != Board::OK)
if (blueSetup != MovementResult::OK)
{
fprintf(stderr, "Red wins by DEFAULT!\n");
red->SendMessage("DEFAULT");
......@@ -57,7 +62,7 @@ int main(int argc, char ** argv)
exit(EXIT_SUCCESS);
}
Board::MovementResult result = Board::OK;
MovementResult result(MovementResult::OK);
system("clear");
int count = 1;
......@@ -70,17 +75,17 @@ int main(int argc, char ** argv)
string buffer;
red->SendMessage("START");
Colour turn = Piece::RED;
turn = Piece::RED;
while (Board::LegalResult(result))
{
fprintf(stderr, "This is move %d...\n", count);
fprintf(stderr,"---RED's turn---\n");
turn = Piece::RED;
fprintf(stderr, "%d RED: ", count);
result = red->MakeMove(buffer);
red->SendMessage(buffer);
blue->SendMessage(buffer);
fprintf(stderr, "%s\n", buffer.c_str());
if (!Board::LegalResult(result))
break;
#ifdef GRAPHICS
......@@ -92,11 +97,13 @@ int main(int argc, char ** argv)
exit(EXIT_SUCCESS);
}
#endif //GRAPHICS
fprintf(stderr,"---BLUE's turn---\n");
turn = Piece::BLUE;
fprintf(stderr, "%d BLU: ", count);
result = blue->MakeMove(buffer);
blue->SendMessage(buffer);
red->SendMessage(buffer);
fprintf(stderr, "%s\n", buffer.c_str());
if (!Board::LegalResult(result))
break;
......@@ -148,36 +155,36 @@ int main(int argc, char ** argv)
fprintf(stderr,"Game ends on ERROR's turn - REASON: ");
}
switch (result)
switch (result.type)
{
case Board::NO_BOARD:
case MovementResult::NO_BOARD:
fprintf(stderr,"Board does not exit?!\n");
break;
case Board::INVALID_POSITION:
case MovementResult::INVALID_POSITION:
fprintf(stderr,"Coords outside board\n");
break;
case Board::NO_SELECTION:
case MovementResult::NO_SELECTION:
fprintf(stderr,"Move does not select a piece\n");
break;
case Board::NOT_YOUR_UNIT:
case MovementResult::NOT_YOUR_UNIT:
fprintf(stderr,"Selected piece belongs to other player\n");
break;
case Board::IMMOBILE_UNIT:
case MovementResult::IMMOBILE_UNIT:
fprintf(stderr,"Selected piece is not mobile (FLAG or BOMB)\n");
break;
case Board::INVALID_DIRECTION:
case MovementResult::INVALID_DIRECTION:
fprintf(stderr,"Selected unit cannot move that way\n");
break;
case Board::POSITION_FULL:
case MovementResult::POSITION_FULL:
fprintf(stderr,"Attempted move into square occupied by allied piece\n");
break;
case Board::VICTORY:
case MovementResult::VICTORY:
fprintf(stderr,"Captured the flag\n");
break;
case Board::BAD_RESPONSE:
case MovementResult::BAD_RESPONSE:
fprintf(stderr,"Unintelligable response\n");
break;
case Board::NO_MOVE:
case MovementResult::NO_MOVE:
fprintf(stderr,"Did not make a move (may have exited)\n");
break;
}
......@@ -221,4 +228,24 @@ void cleanup()
delete blue;
}
void BrokenPipe(int sig)
{
if (turn == Piece::RED)
{
fprintf(stderr,"Game ends on RED's turn - REASON: Broken pipe\n");
blue->SendMessage("DEFAULT");
}
else if (turn == Piece::BLUE)
{
fprintf(stderr,"Game ends on BLUE's turn - REASON: Broken pipe\n");
red->SendMessage("DEFAULT");
}
else
{
fprintf(stderr,"Game ends on ERROR's turn - REASON: Broken pipe\n");
}
exit(EXIT_SUCCESS);
}
#endif //GRAPHICS
/**
* Contains declaration for MovementResult class
*/
#ifndef MOVERESULT_H
#define MOVERESULT_H
class Board;
class Piece;
/**
* Class used to indicate the result of a move in stratego
*/
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, BAD_RESPONSE, NO_MOVE} Type;
MovementResult(const Type & result = OK, const Piece::Type & newAttackerRank = Piece::NOTHING, const Piece::Type & newDefenderRank = Piece::NOTHING)
: type(result), attackerRank(newAttackerRank), defenderRank(newDefenderRank) {}
MovementResult(const MovementResult & cpy) : type(cpy.type), attackerRank(cpy.attackerRank), defenderRank(cpy.defenderRank) {}
virtual ~MovementResult() {}
bool operator==(const Type & equType) const {return type == equType;}
bool operator!=(const Type & equType) const {return type != equType;}
Type type;
Piece::Type attackerRank;
Piece::Type defenderRank;
};
#endif //MOVERESULT_H
//EOF
......@@ -10,6 +10,7 @@
using namespace std;
/**
* Constructor
* @param executablePath - path to the program that will be run
......@@ -21,6 +22,8 @@ using namespace std;
*/
Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0)
{
int readPipe[2]; int writePipe[2];
assert(pipe(readPipe) == 0);
assert(pipe(writePipe) == 0);
......@@ -88,6 +91,7 @@ Program::~Program()
* Sends a message to the wrapped AI program
* WARNING: Always prints a new line after the message (so don't include a new line)
* This is because everything is always line buffered.
* @returns true if the message was successfully sent; false if it was not (ie: the process was not running!)
*/
bool Program::SendMessage(const char * print, ...)
{
......@@ -97,12 +101,15 @@ bool Program::SendMessage(const char * print, ...)
va_list ap;
va_start(ap, print);
vfprintf(output, print, ap);
fprintf(output, "\n");
if (vfprintf(output, print, ap) < 0 || fprintf(output, "\n") < 0)
{
va_end(ap);
return false;
}
va_end(ap);
va_end(ap);
return true;
}
......@@ -161,3 +168,5 @@ bool Program::Running() const
}
......@@ -24,6 +24,8 @@ class Program
bool Running() const;
protected:
FILE * input; //Stream used for sending information TO the process
FILE * output; //Stream used for retrieving information FROM the process
......
......@@ -232,32 +232,32 @@ Piece * Board::GetPiece(int x, int y)
* @param colour - Colour which the piece must match for the move to be valid
* @returns A MovementResult which indicates the result of the move - OK is good, VICTORY means that a flag was captured, anything else is an error
*/
Board::MovementResult Board::MovePiece(int x, int y, const Direction & direction, int multiplier,const Piece::Colour & colour)
MovementResult Board::MovePiece(int x, int y, const Direction & direction, int multiplier,const Piece::Colour & colour)
{
if (board == NULL)
{
return NO_BOARD;
return MovementResult(MovementResult::NO_BOARD);
}
if (!(x >= 0 && x < width && y >= 0 && y < height))
{
return INVALID_POSITION;
return MovementResult(MovementResult::INVALID_POSITION);
}
Piece * target = board[x][y];
if (target == NULL)
{
return NO_SELECTION;
return MovementResult(MovementResult::NO_SELECTION);
}
if (!(colour == Piece::NONE || target->colour == colour))
{
return NOT_YOUR_UNIT;
return MovementResult(MovementResult::NOT_YOUR_UNIT);
}
if (target->type == Piece::FLAG || target->type == Piece::BOMB || target->type == Piece::BOULDER)
{
return IMMOBILE_UNIT;
return MovementResult(MovementResult::IMMOBILE_UNIT);
}
if (multiplier > 1 && target->type != Piece::SCOUT)
{
return INVALID_DIRECTION; //Can only move a scout multiple times.
return MovementResult(MovementResult::INVALID_DIRECTION); //Can only move a scout multiple times.
}
int x2 = x; int y2 = y;
......@@ -280,11 +280,11 @@ Board::MovementResult Board::MovePiece(int x, int y, const Direction & direction
}
if (!(x2 >= 0 && x2 < width && y2 >= 0 && y2 < height))
{
return INVALID_DIRECTION;
return MovementResult(MovementResult::INVALID_DIRECTION);
}
if (ii < multiplier-1 && board[x2][y2] != NULL)
{
return POSITION_FULL;
return MovementResult(MovementResult::POSITION_FULL);
}
}
Piece * defender = board[x2][y2];
......@@ -295,23 +295,27 @@ Board::MovementResult Board::MovePiece(int x, int y, const Direction & direction
}
else if (defender->colour != target->colour)
{
Piece::Type defenderType = defender->type;
Piece::Type attackerType = target->type;
if (defender->colour == Piece::NONE)
{
return POSITION_FULL;
return MovementResult(MovementResult::POSITION_FULL);
}
if (defender->type == Piece::FLAG)
{
winner = target->colour;
return VICTORY;
return MovementResult(MovementResult::VICTORY);
}
else if (defender->type == Piece::BOMB)
{
if (target->type == Piece::MINER)
{
delete defender;
board[x][y] = NULL;
board[x2][y2] = target;
return KILLS;
return MovementResult(MovementResult::KILLS, attackerType, defenderType);
}
else
{
......@@ -319,7 +323,7 @@ Board::MovementResult Board::MovePiece(int x, int y, const Direction & direction
delete target;
board[x][y] = NULL;
board[x2][y2] = NULL;
return BOTH_DIE;
return MovementResult(MovementResult::BOTH_DIE, attackerType, defenderType);
}
}
else if (defender->type == Piece::MARSHAL && target->type == Piece::SPY)
......@@ -327,34 +331,34 @@ Board::MovementResult Board::MovePiece(int x, int y, const Direction & direction
delete defender;
board[x][y] = NULL;
board[x2][y2] = target;
return KILLS;
return MovementResult(MovementResult::KILLS, attackerType, defenderType);
}
else if (target->operator > (*defender))
{
delete defender;
board[x][y] = NULL;
board[x2][y2] = target;
return KILLS;
return MovementResult(MovementResult::KILLS, attackerType, defenderType);
}
else if (target->operator==(*defender) && rand() % 2 == 0)
{
delete defender;
board[x][y] = NULL;
board[x2][y2] = target;
return KILLS;
return MovementResult(MovementResult::KILLS, attackerType, defenderType);
}
else
{
delete target;
board[x][y] = NULL;
return DIES;
return MovementResult(MovementResult::DIES, attackerType, defenderType);
}
}
else
{
return POSITION_FULL;
return MovementResult(MovementResult::POSITION_FULL);
}
return OK;
return MovementResult(MovementResult::OK);
}
......
......@@ -89,6 +89,8 @@ class Piece
};
#include "movementresult.h"
/**
* A Stratego board
*/
......@@ -112,12 +114,11 @@ class Board
typedef enum {UP, DOWN, LEFT, RIGHT} Direction;
typedef enum {OK, DIES, KILLS, BOTH_DIE, NO_BOARD, INVALID_POSITION, NO_SELECTION, NOT_YOUR_UNIT, IMMOBILE_UNIT, INVALID_DIRECTION, POSITION_FULL, VICTORY, BAD_RESPONSE, NO_MOVE} MovementResult; //The possible results from a move
//WARNING: Some of the MovementResults are returned by the Controller class in "controller.h", in Controller::MakeMove
static bool LegalResult(const MovementResult & result)
{
return (result == OK || result == DIES || result == KILLS || result == BOTH_DIE);
return (result == MovementResult::OK || result == MovementResult::DIES || result == MovementResult::KILLS || result == MovementResult::BOTH_DIE);
}
MovementResult MovePiece(int x, int y, const Direction & direction, int multiplier=1,const Piece::Colour & colour=Piece::NONE); //Move piece from position in direction
......
......@@ -525,7 +525,7 @@ bool Forfax::MakeMove()
bool Forfax::InterpretMove()
{
int x; int y; string direction; string result; int multiplier = 1;
int x; int y; string direction; string result; int multiplier = 1; int attackerVal = (int)(Piece::BOMB); int defenderVal = (int)(Piece::BOMB);
cerr << "Forfax " << strColour << " waiting for movement information...\n";
cin >> x; cin >> y; cin >> direction; cin >> result;
......@@ -536,6 +536,20 @@ bool Forfax::InterpretMove()
s >> multiplier;
result.clear();
cin >> result;