From 4a3c0478160e7e9b637a12e7cf22f8da61b66ad2 Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Sat, 3 Dec 2011 16:26:36 +0800 Subject: [PATCH] Revamped manager program and added manual page Seperated controllers into AI and human controllers, which inherit from the same base class Using "human" as an argument instead of an AI program will allow human player. However, the human player has to use the CLI. I have tried playing a few games, and it is incredibly annoying using the CLI (especially when each turn was printed to stdout - now suppressed). Usually I accidentally enter the wrong coordinates, or spend 30 seconds trying to work out the coordinates of a piece. Then when I switch the focus, the SDL window goes blank, and I don't know what the hell is going on. In conclusion: Should probably add GUI for human players! Created Game class to manage playing the game, rather than hacking everything into main.cpp Added argument switches for timeouts, graphics, output file, help, allowing illegal moves, revealing colours etc Added result lines (output who actually wins... amazing!) The massive spamming output that used to be printed is suppressed (enable with -o stdout/file). Created manual.txt which is the manual page for stratego (the manager program). stratego --help will display the page (using "less" - should probably fix for systems without "less"). Changed tokens used for pieces from alphabet characters to digits for the ranked pieces, 's' for the Spy, 'B' for Bombs and 'F' for the Flag. This makes things clearer. The Spy would be "10", except thats 2 characters, which is a bit awkward! Didn't change the order of the enum, because thats just asking for trouble and besides, it works. Changed stratego to output the characters for the piece, instead of an integer rank (except for Flag, Bomb and Spy, no difference). Need to handle situations where a player has lost all their mobile pieces. Do they lose? Currently they will be forced to make an illegal move, and the other player wins by "default". Found mistake in forfax move score calculation that lead to moves having negative scores, and hence occasionally an illegal move would be chosen as more valuable than legal moves. Probably fixed. Illegal moves now score -1, so should NEVER be made! Ironically the change seemed to decrease forfax's performance against dummy. Forfax still seems to make really stupid moves, and I can't see why. Occasionally it does something smart (attacks Marshal with Spy just after the Marshal reveals itself), but I'm not sure how often these are coincidences. Even with the devaluing of moves that don't end in combat, Forfax still gets into long cycles of repeated paths with no purpose. And Forfax NEVER attacks Bombs or the Flag... even if thats all the enemy has, and even if the attacking piece would be a miner. Updated web page. Considering replacing Protocol Description as is with that written for manual.txt, which I feel is clearer. Need to make next git commit message shorter... --- manager/Makefile | 2 +- manager/ai_controller.cpp | 64 +++++++ manager/ai_controller.h | 30 +++ manager/common.h | 10 - manager/controller.cpp | 109 ++++------- manager/controller.h | 28 ++- manager/game.cpp | 357 +++++++++++++++++++++++++++++++++++ manager/game.h | 57 ++++++ manager/human_controller.cpp | 63 +++++++ manager/human_controller.h | 25 +++ manager/main.cpp | 344 ++++++++++++--------------------- manager/manual.txt | 155 +++++++++++++++ manager/movementresult.h | 2 +- manager/stratego.cpp | 26 +-- manager/stratego.h | 15 +- samples/dummy/dummy.cpp | 16 +- samples/forfax/forfax.cpp | 59 +++--- web/index.html | 59 +++--- 18 files changed, 1035 insertions(+), 386 deletions(-) create mode 100644 manager/ai_controller.cpp create mode 100644 manager/ai_controller.h delete mode 100644 manager/common.h create mode 100644 manager/game.cpp create mode 100644 manager/game.h create mode 100644 manager/human_controller.cpp create mode 100644 manager/human_controller.h create mode 100644 manager/manual.txt diff --git a/manager/Makefile b/manager/Makefile index 7d81a98..fe319df 100644 --- a/manager/Makefile +++ b/manager/Makefile @@ -1,7 +1,7 @@ #Makefile for Stratego CPP = g++ -Wall -pedantic -lSDL -lGL -g -OBJ = main.o controller.o program.o thread_util.o stratego.o graphics.o +OBJ = main.o controller.o ai_controller.o human_controller.o program.o thread_util.o stratego.o graphics.o game.o BIN = stratego diff --git a/manager/ai_controller.cpp b/manager/ai_controller.cpp new file mode 100644 index 0000000..40ee3df --- /dev/null +++ b/manager/ai_controller.cpp @@ -0,0 +1,64 @@ +#include + +#include "game.h" +#include "stratego.h" + +#include "ai_controller.h" + +using namespace std; + + +/** + * Queries the AI program to setup its pieces. Stores the setup in a st + * @implements Controller::QuerySetup + * @param + * @returns A MovementResult + */ + +MovementResult AI_Controller::QuerySetup(const char * opponentName, std::string setup[]) +{ + switch (colour) + { + case Piece::RED: + if (!SendMessage("RED %s %d %d", opponentName, Game::theGame->theBoard.Width(), Game::theGame->theBoard.Height())) + return MovementResult::BAD_RESPONSE; + break; + case Piece::BLUE: + if (!SendMessage("BLUE %s %d %d", opponentName, Game::theGame->theBoard.Width(), Game::theGame->theBoard.Height())) + return MovementResult::BAD_RESPONSE; + break; + case Piece::NONE: + case Piece::BOTH: + return MovementResult::COLOUR_ERROR; + break; + } + + for (int y = 0; y < 4; ++y) + { + if (!GetMessage(setup[y], timeout)) + return MovementResult::BAD_RESPONSE; + } + + return MovementResult::OK; +} + + +/** + * Queries the AI program to make a move + * @implements Controller::QueryMove + * @param buffer String which stores the AI program's response + * @returns A MovementResult which will be MovementResult::OK if a move was made, or MovementResult::NO_MOVE if the AI did not respond + */ +MovementResult AI_Controller::QueryMove(string & buffer) +{ + if (!Running()) + return MovementResult::NO_MOVE; //AI has quit + Game::theGame->theBoard.Print(output, colour); + + if (!GetMessage(buffer,timeout)) + { + return MovementResult::NO_MOVE; //AI did not respond (within the timeout). It will lose by default. + } + return MovementResult::OK; //Got the message +} + diff --git a/manager/ai_controller.h b/manager/ai_controller.h new file mode 100644 index 0000000..005043e --- /dev/null +++ b/manager/ai_controller.h @@ -0,0 +1,30 @@ +#ifndef AI_CONTROLLER_H +#define AI_CONTROLLER_H + +#include "controller.h" +#include "program.h" + +/** + * Class to control an AI program playing Stratego + * Inherits mostly from Program + */ +class AI_Controller : public Controller, private Program +{ + public: + AI_Controller(const Piece::Colour & newColour, const char * executablePath, const double newTimeout = 2.0) : Controller(newColour), Program(executablePath), timeout(newTimeout) {} + virtual ~AI_Controller() {} + + + + virtual MovementResult QuerySetup(const char * opponentName,std::string setup[]); + virtual MovementResult QueryMove(std::string & buffer); + + virtual void Message(const char * message) {Program::SendMessage(message);} + + + private: + const double timeout; //Timeout in seconds for messages from the AI Program + +}; + +#endif //AI_CONTROLLER_H diff --git a/manager/common.h b/manager/common.h deleted file mode 100644 index 42588a7..0000000 --- a/manager/common.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef COMMON_H -#define COMMON_H - -#define GRAPHICS - -#endif //COMMON_H - -//EOF - - diff --git a/manager/controller.cpp b/manager/controller.cpp index 7325ec7..1b70fb6 100644 --- a/manager/controller.cpp +++ b/manager/controller.cpp @@ -1,112 +1,86 @@ -#include - -#include "stratego.h" - #include "controller.h" +#include +#include "game.h" + 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 + * Queries the player to setup their pieces + * */ + MovementResult Controller::Setup(const char * opponentName) { - int y; + string setup[4] = {"","","",""}; + MovementResult query = this->QuerySetup(opponentName, setup); + if (query != MovementResult::OK) + return query; + + + + int usedUnits[(int)(Piece::BOMB)]; + for (int ii = 0; ii <= (int)(Piece::BOMB); ++ii) + usedUnits[ii] = 0; + + int yStart = 0; switch (colour) { case Piece::RED: - assert(SendMessage("RED %s %d %d", opponentName, Board::theBoard.Width(), Board::theBoard.Height())); - y = 0; - + yStart = 0; break; case Piece::BLUE: - assert(SendMessage("BLUE %s %d %d", opponentName, Board::theBoard.Width(), Board::theBoard.Height())); - y = Board::theBoard.Height()-4; - + yStart = Game::theGame->theBoard.Height()-4; break; - case Piece::NONE: - case Piece::BOTH: - //Should never see this; - assert(false); + default: + return MovementResult::COLOUR_ERROR; break; } - int usedUnits[(int)(Piece::BOMB)]; - for (int ii = 0; ii <= (int)(Piece::BOMB); ++ii) - usedUnits[ii] = 0; - - //The setup is spread across 4 lines of the board - blue at the top, red at the bottom. AI has 2.5s for each line. - - - - - for (int ii=0; ii < 4; ++ii) + for (int y = 0; y < 4; ++y) { - string line=""; - if (!GetMessage(line, 2.5)) - { - fprintf(stderr, "Timeout on setup\n"); - return MovementResult::BAD_RESPONSE; - } - if ((int)(line.size()) != Board::theBoard.Width()) - { - fprintf(stderr, "Bad length of \"%s\" on setup\n", line.c_str()); + if ((int)setup[y].length() != Game::theGame->theBoard.Width()) return MovementResult::BAD_RESPONSE; - } - - for (int x = 0; x < (int)(line.size()); ++x) + + for (int x = 0; x < Game::theGame->theBoard.Width(); ++x) { - Piece::Type type = Piece::GetType(line[x]); + Piece::Type type = Piece::GetType(setup[y][x]); if (type != Piece::NOTHING) { -//fprintf(stderr, "x y %d %d\n", x, y+ii); -// fprintf(stderr, "Found unit of type '%c' (%d '%c') %d vs %d\n", line[x], (int)(type), Piece::tokens[(int)(type)], usedUnits[(int)(type)], Piece::maxUnits[(int)type]); - /// fprintf(stderr, "Marshal is %d '%c', flag is %d '%c'\n", (int)Piece::MARSHAL, Piece::tokens[(int)(Piece::MARSHAL)], (int)Piece::FLAG, Piece::tokens[(int)(Piece::FLAG)]); - - usedUnits[(int)(type)] += 1; + usedUnits[(int)(type)]++; if (usedUnits[type] > Piece::maxUnits[(int)type]) { fprintf(stderr, "Too many units of type %c\n", Piece::tokens[(int)(type)]); return MovementResult::BAD_RESPONSE; } - - Board::theBoard.AddPiece(x, y+ii, type, colour); + Game::theGame->theBoard.AddPiece(x, yStart+y, type, colour); } - } + } } - if (usedUnits[(int)Piece::FLAG] <= 0) { return MovementResult::BAD_RESPONSE; //You need to include a flag! } return MovementResult::OK; + } /** - * Queries the AI program to respond to a state of Board::theBoard + * Queries the player to respond to a state of Game::theGame->theBoard + * @param buffer String which is used to store the player's responses * @returns The result of the response and/or move if made */ MovementResult Controller::MakeMove(string & buffer) { - - if (!Running()) - return MovementResult::NO_MOVE; //AI has quit - Board::theBoard.Print(output, colour); - - - - buffer.clear(); - if (!GetMessage(buffer,2)) - { - return MovementResult::NO_MOVE; //AI did not respond. It will lose by default. - } + MovementResult query = this->QueryMove(buffer); + if (query != MovementResult::OK) + return query; + int x; int y; string direction=""; stringstream s(buffer); s >> x; @@ -134,19 +108,19 @@ MovementResult Controller::MakeMove(string & buffer) else { fprintf(stderr, "BAD_RESPONSE \"%s\"\n", buffer.c_str()); - return MovementResult::BAD_RESPONSE; //AI gave bogus direction - it will lose by default. + return MovementResult::BAD_RESPONSE; //Player gave bogus direction - it will lose by default. } int multiplier = 1; if (s.peek() != EOF) s >> multiplier; - MovementResult moveResult = Board::theBoard.MovePiece(x, y, dir, multiplier, colour); + MovementResult moveResult = Game::theGame->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); + s << Piece::tokens[(int)(moveResult.attackerRank)] << " " << Piece::tokens[(int)(moveResult.defenderRank)]; switch (moveResult.type) { case MovementResult::OK: @@ -174,10 +148,9 @@ MovementResult Controller::MakeMove(string & buffer) } - if (!Board::LegalResult(moveResult)) + if (Game::theGame->allowIllegalMoves && !Board::LegalResult(moveResult)) return MovementResult::OK; //HACK - Legal results returned! else return moveResult; } - diff --git a/manager/controller.h b/manager/controller.h index 1fac88d..c7fe3d6 100644 --- a/manager/controller.h +++ b/manager/controller.h @@ -2,28 +2,40 @@ #define CONTROLLER_H #include "stratego.h" -#include "program.h" +#include /** - * Class to control an AI program for a game of Stratego - * Inherits most features from the Program class + * Class to control a player for Stratego + * Abstract base class */ -class Controller : public Program +class Controller { public: - Controller(const Piece::Colour & newColour, const char * executablePath) : Program(executablePath), colour(newColour) {} + Controller(const Piece::Colour & newColour) : colour(newColour) {} virtual ~Controller() {} - MovementResult Setup(const char * opponentName); //Requests the AI program for the initial positioning of its pieces. + MovementResult Setup(const char * opponentName); - MovementResult MakeMove(std::string & buffer); //Queries the AI program for a response to the state of Board::theBoard + MovementResult MakeMove(std::string & buffer); - const Piece::Colour colour; //Colour identifying the side of the AI program. + + + void Message(std::string & buffer) {Message(buffer.c_str());} + virtual void Message(const char * string) = 0; + + virtual MovementResult QuerySetup(const char * opponentName, std::string setup[]) = 0; + virtual MovementResult QueryMove(std::string & buffer) = 0; + + const Piece::Colour colour; }; + + + + #endif //CONTROLLER_H diff --git a/manager/game.cpp b/manager/game.cpp new file mode 100644 index 0000000..005983b --- /dev/null +++ b/manager/game.cpp @@ -0,0 +1,357 @@ +#include "game.h" + +using namespace std; + + + +Game* Game::theGame = NULL; + +Game::Game(const char * redPath, const char * bluePath, const bool enableGraphics, double newStallTime, const bool allowIllegal, FILE * newLog, const Piece::Colour & newReveal) : red(NULL), blue(NULL), turn(Piece::RED), theBoard(10,10), graphicsEnabled(enableGraphics), stallTime(newStallTime), allowIllegalMoves(allowIllegal), log(newLog), reveal(newReveal), turnCount(0) +{ + static bool gameCreated = false; + if (gameCreated) + { + if (log != NULL) + fprintf(log, "ERROR - Game has already been created!\n"); + exit(EXIT_FAILURE); + } + gameCreated = true; + Game::theGame = this; + signal(SIGPIPE, Game::HandleBrokenPipe); + + + if (graphicsEnabled && (!Graphics::Initialised())) + Graphics::Initialise("Stratego", theBoard.Width()*32, theBoard.Height()*32); + + if (strcmp(redPath, "human") == 0) + red = new Human_Controller(Piece::RED, graphicsEnabled); + else + red = new AI_Controller(Piece::RED, redPath); + + + if (strcmp(bluePath, "human") == 0) + blue = new Human_Controller(Piece::BLUE, graphicsEnabled); + else + blue = new AI_Controller(Piece::BLUE, redPath); + + +} + +Game::~Game() +{ + fprintf(stderr, "Killing AI\n"); + delete red; + delete blue; + + if (log != NULL && log != stdout && log != stderr) + fclose(log); +} + +bool Game::Setup(const char * redName, const char * blueName) +{ + + for (int y = 4; y < 6; ++y) + { + for (int x = 2; x < 4; ++x) + { + theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); + } + for (int x = 6; x < 8; ++x) + { + theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); + } + } + + + MovementResult redSetup = red->Setup(blueName); + MovementResult blueSetup = blue->Setup(redName); + + if (redSetup != MovementResult::OK) + { + if (blueSetup != MovementResult::OK) + { + if (log != NULL) + fprintf(log, "BOTH players give invalid setup!\n"); + red->Message("ILLEGAL"); + blue->Message("ILLEGAL"); + } + else + { + if (log != NULL) + fprintf(log, "Player RED gave an invalid setup!\n"); + red->Message("ILLEGAL"); + blue->Message("DEFAULT"); + } + return false; + } + else if (blueSetup != MovementResult::OK) + { + if (log != NULL) + fprintf(log, "Player BLUE gave an invalid setup!\n"); + red->Message("DEFAULT"); + blue->Message("ILLEGAL"); + return false; + } + return true; + +} + +void Game::Wait(double wait) +{ + if (wait <= 0) + return; + + TimerThread timer(wait*1000000); //Wait in seconds + timer.Start(); + + if (!graphicsEnabled) + { + while (!timer.Finished()); + timer.Stop(); + return; + } + + + while (!timer.Finished()) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + timer.Stop(); + exit(EXIT_SUCCESS); + break; + } + } + } + timer.Stop(); + +} + +void Game::HandleBrokenPipe(int sig) +{ + if (theGame->turn == Piece::RED) + { + theGame->logMessage("Game ends on RED's turn - REASON: "); + theGame->blue->Message("DEFAULT"); + } + else if (theGame->turn == Piece::BLUE) + { + + theGame->logMessage("Game ends on BLUE's turn - REASON: "); + theGame->red->Message("DEFAULT"); + } + else + { + theGame->logMessage("Game ends on ERROR's turn - REASON: "); + + } + + theGame->logMessage("SIGPIPE - Broken pipe (AI program may have segfaulted)\n"); + + + + if (Game::theGame->graphicsEnabled && theGame->log == stdout) + { + theGame->logMessage("CLOSE WINDOW TO EXIT\n"); + Game::theGame->theBoard.Draw(Piece::BOTH); + while (true) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + exit(EXIT_SUCCESS); + break; + } + } + } + } + else + { + if (theGame->log == stdout) + { + theGame->logMessage( "PRESS ENTER TO EXIT\n"); + theGame->theBoard.Print(theGame->log); + while (fgetc(stdin) != '\n'); + } + } + + + exit(EXIT_SUCCESS); +} + +void Game::PrintEndMessage(const MovementResult & result) +{ + 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) + { + case MovementResult::OK: + logMessage("Status returned OK, unsure why game halted...\n"); + break; + case MovementResult::DIES: + logMessage("Status returned DIES, unsure why game halted...\n"); + break; + case MovementResult::KILLS: + logMessage("Status returned KILLS, unsure why game halted...\n"); + break; + case MovementResult::BOTH_DIE: + logMessage("Status returned BOTH_DIE, unsure why game halted...\n"); + break; + case MovementResult::NO_BOARD: + logMessage("Board does not exit?!\n"); + break; + case MovementResult::INVALID_POSITION: + logMessage("Coords outside board\n"); + break; + case MovementResult::NO_SELECTION: + logMessage("Move does not select a piece\n"); + break; + case MovementResult::NOT_YOUR_UNIT: + logMessage("Selected piece belongs to other player\n"); + break; + case MovementResult::IMMOBILE_UNIT: + logMessage("Selected piece is not mobile (FLAG or BOMB)\n"); + break; + case MovementResult::INVALID_DIRECTION: + logMessage("Selected unit cannot move that way\n"); + break; + case MovementResult::POSITION_FULL: + logMessage("Attempted move into square occupied by allied piece\n"); + break; + case MovementResult::VICTORY: + logMessage("Captured the flag\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"); + break; + case MovementResult::COLOUR_ERROR: + logMessage("Internal controller error - COLOUR_ERROR\n"); + break; + case MovementResult::ERROR: + logMessage("Internal controller error - Unspecified ERROR\n"); + break; + + } + + if (graphicsEnabled && log == stdout) + { + logMessage("CLOSE WINDOW TO EXIT\n"); + theBoard.Draw(Piece::BOTH); + while (true) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + exit(EXIT_SUCCESS); + break; + } + } + } + } + else + { + if (log == stdout) + { + logMessage("PRESS ENTER TO EXIT\n"); + while (fgetc(stdin) != '\n'); + } + } + +} + + + +MovementResult Game::Play() +{ + + MovementResult result = MovementResult::OK; + turnCount = 1; + string buffer; + + + + red->Message("START"); + logMessage("START"); + while (Board::LegalResult(result)) + { + + + turn = Piece::RED; + logMessage( "%d RED: ", turnCount); + result = red->MakeMove(buffer); + red->Message(buffer); + blue->Message(buffer); + logMessage( "%s\n", buffer.c_str()); + if (!Board::LegalResult(result)) + break; + if (graphicsEnabled) + theBoard.Draw(reveal); + Wait(stallTime); + + turn = Piece::BLUE; + logMessage( "%d BLU: ", turnCount); + result = blue->MakeMove(buffer); + blue->Message(buffer); + red->Message(buffer); + logMessage( "%s\n", buffer.c_str()); + + if (!Board::LegalResult(result)) + break; + + + + if (graphicsEnabled) + theBoard.Draw(reveal); + Wait(stallTime); + + ++turnCount; + } + + + return result; + + + +} + +/** + * Logs a message to the game's log file if it exists + * @param format the format string + * @param additional parameters - printed using va_args + * @returns the result of vfprintf or a negative number if the log file does not exist + */ +int Game::logMessage(const char * format, ...) +{ + if (log == NULL) + return -666; + va_list ap; + va_start(ap, format); + + int result = vfprintf(log, format, ap); + va_end(ap); + + return result; +} diff --git a/manager/game.h b/manager/game.h new file mode 100644 index 0000000..9a02044 --- /dev/null +++ b/manager/game.h @@ -0,0 +1,57 @@ +#ifndef MAIN_H +#define MAIN_H + +#include "stratego.h" +#include "ai_controller.h" +#include "human_controller.h" + + + +/** + * Class to manage the game + */ +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); + virtual ~Game(); + + + + void Wait(double wait); + + bool Setup(const char * redName, const char * blueName); + MovementResult Play(); + void PrintEndMessage(const MovementResult & result); + + + static void HandleBrokenPipe(int signal); + + + const Piece::Colour Turn() const {return turn;} + int TurnCount() const {return turnCount;} + + static Game * theGame; + private: + int logMessage(const char * format, ...); + Controller * red; + Controller * blue; + Piece::Colour turn; + + public: + Board theBoard; + private: + const bool graphicsEnabled; + double stallTime; + public: + const bool allowIllegalMoves; + + private: + FILE * log; + Piece::Colour reveal; + int turnCount; + +}; + + +#endif //MAIN_H diff --git a/manager/human_controller.cpp b/manager/human_controller.cpp new file mode 100644 index 0000000..5e3e176 --- /dev/null +++ b/manager/human_controller.cpp @@ -0,0 +1,63 @@ +#include "human_controller.h" + +#include "game.h" + +#include //Really I can't be bothered with fscanf any more + +using namespace std; + +MovementResult Human_Controller::QuerySetup(const char * opponentName, string setup[]) +{ + + static bool shownMessage = false; + if (!shownMessage) + { + if (graphicsEnabled) + fprintf(stderr, "GUI not yet supported! Please use CLI\n"); + fprintf(stdout,"Enter %d x %d Setup grid\n", Game::theGame->theBoard.Width(), 4); + fprintf(stdout,"Please enter one line at a time, using the following allowed characters:\n"); + for (Piece::Type rank = Piece::FLAG; rank <= Piece::BOMB; rank = Piece::Type((int)(rank) + 1)) + { + fprintf(stdout,"%c x %d\n", Piece::tokens[(int)rank], Piece::maxUnits[(int)rank]); + } + fprintf(stdout, "You must place at least the Flag (%c). Use '%c' for empty squares.\n", Piece::tokens[(int)Piece::FLAG], Piece::tokens[(int)Piece::NOTHING]); + fprintf(stdout, "NOTE: Player RED occupies the top four rows, and BLUE occupies the bottom four rows.\n"); + + shownMessage = true; + } + + + + for (int y = 0; y < 4; ++y) + cin >> setup[y]; + assert(cin.get() == '\n'); + + return MovementResult::OK; +} + +MovementResult Human_Controller::QueryMove(string & buffer) +{ + static bool shownMessage = false; + if (!shownMessage) + { + if (graphicsEnabled) + fprintf(stderr, "GUI not yet supported! Please use the CLI\n"); + fprintf(stdout, "Please enter your move in the format:\n X Y DIRECTION [MULTIPLIER=1]\n"); + fprintf(stdout, "Where X and Y indicate the coordinates of the piece to move;\n DIRECTION is one of UP, DOWN, LEFT or RIGHT\n and MULTIPLIER is optional (and only valid for scouts (%c))\n", Piece::tokens[(int)(Piece::SCOUT)]); + shownMessage = true; + } + + + + + buffer.clear(); + for (char in = fgetc(stdin); in != '\n'; in = fgetc(stdin)) + { + buffer += in; + } + + + + return MovementResult::OK; + +} diff --git a/manager/human_controller.h b/manager/human_controller.h new file mode 100644 index 0000000..c0df811 --- /dev/null +++ b/manager/human_controller.h @@ -0,0 +1,25 @@ +#ifndef HUMAN_CONTROLLER_H +#define HUMAN_CONTROLLER_H + +#include "controller.h" + +/** + * Class to control a human player playing Stratego + */ +class Human_Controller : public Controller +{ + public: + Human_Controller(const Piece::Colour & newColour, const bool enableGraphics) : Controller(newColour), graphicsEnabled(enableGraphics) {} + virtual ~Human_Controller() {} + + virtual MovementResult QuerySetup(const char * opponentName, std::string setup[]); + virtual MovementResult QueryMove(std::string & buffer); + virtual void Message(const char * message) {fprintf(stderr, "Recieved message \"%s\" from manager.\n", message);} + + private: + const bool graphicsEnabled; + + +}; + +#endif //AI_CONTROLLER_H diff --git a/manager/main.cpp b/manager/main.cpp index e3e435e..6adc34d 100644 --- a/manager/main.cpp +++ b/manager/main.cpp @@ -1,198 +1,162 @@ #include #include -#include "common.h" -#include "controller.h" -#include "stratego.h" -using namespace std; - - - -#define theBoard Board::theBoard -#ifdef GRAPHICS - bool CheckForQuitWhilstWaiting(int wait); -#endif //GRAPHICS -Controller * red; -Controller * blue; -Colour turn; -void cleanup(); +#include "game.h" -void BrokenPipe(int sig); +using namespace std; int main(int argc, char ** argv) { - assert(argc == 3); - - - for (int y = 4; y < 6; ++y) + + char * red = NULL; char * blue = NULL; double timeout = 0.00001; bool graphics = false; bool allowIllegal = false; FILE * log = NULL; + Piece::Colour reveal = Piece::BOTH; + for (int ii=1; ii < argc; ++ii) { - for (int x = 2; x < 4; ++x) + if (argv[ii][0] == '-') { - theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); + switch (argv[ii][1]) + { + case 't': + if (argc - ii <= 1) + { + fprintf(stderr, "Expected timeout value after -t switch!\n"); + exit(EXIT_FAILURE); + } + timeout = atof(argv[ii+1]); + ++ii; + break; + case 'g': + graphics = true; + break; + case 'i': + allowIllegal = true; + break; + + case 'o': + if (argc - ii <= 1) + { + fprintf(stderr, "Expected filename or \"stdout\" after -o switch!\n"); + exit(EXIT_FAILURE); + } + if (log != NULL) + { + fprintf(stderr, "Expected at most ONE -o switch!\n"); + exit(EXIT_FAILURE); + } + if (strcmp(argv[ii+1], "stdout") == 0) + log = stdout; + else + log = fopen(argv[ii+1], "w"); + setbuf(log, NULL); + + ++ii; + break; + + case 'r': + if (reveal == Piece::BOTH) + reveal = Piece::BLUE; + else + reveal = Piece::NONE; + break; + case 'b': + if (reveal == Piece::BOTH) + reveal = Piece::RED; + else + reveal = Piece::NONE; + break; + case 'h': + system("clear"); + system("less manual.txt"); + exit(EXIT_SUCCESS); + break; + case '-': + if (strcmp(argv[ii]+2, "help") == 0) + { + system("clear"); + system("less manual.txt"); + exit(EXIT_SUCCESS); + } + else + { + fprintf(stderr, "Unrecognised switch \"%s\"...\n", argv[ii]); + exit(EXIT_FAILURE); + } + } + } - for (int x = 6; x < 8; ++x) + else { - theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); + if (red == NULL) + red = argv[ii]; + else if (blue == NULL) + blue = argv[ii]; + else + { + fprintf(stderr, "Unexpected argument \"%s\"...\n", argv[ii]); + exit(EXIT_FAILURE); + } } } - - - red = new Controller(Piece::RED, argv[1]); - blue = new Controller(Piece::BLUE, argv[2]); - atexit(cleanup); - signal(SIGPIPE, BrokenPipe); - - MovementResult redSetup = red->Setup(argv[2]); - MovementResult blueSetup = blue->Setup(argv[1]); - if (redSetup != MovementResult::OK) + if (argc == 1) { - fprintf(stderr, "Blue wins by DEFAULT!\n"); - red->SendMessage("ILLEGAL"); - blue->SendMessage("DEFAULT"); + fprintf(stderr, "Usage: stratego [options] red blue\n"); + fprintf(stderr, " stratego --help\n"); exit(EXIT_SUCCESS); + } - if (blueSetup != MovementResult::OK) + + Game game(red, blue, graphics, timeout, allowIllegal, log, reveal); + + + if (!game.Setup(red, blue)) { - fprintf(stderr, "Red wins by DEFAULT!\n"); - red->SendMessage("DEFAULT"); - blue->SendMessage("ILLEGAL"); + fprintf(stdout, "NONE %d\n",game.TurnCount()); exit(EXIT_SUCCESS); } - MovementResult result(MovementResult::OK); - system("clear"); - int count = 1; - - #ifdef GRAPHICS - if (!Graphics::Initialised()) - Graphics::Initialise("Stratego", theBoard.Width()*32, theBoard.Height()*32); - - #endif //GRAPHICS - - string buffer; + MovementResult result = game.Play(); + game.PrintEndMessage(result); - red->SendMessage("START"); - turn = Piece::RED; - while (Board::LegalResult(result)) + Piece::Colour winner = game.Turn(); + if (Board::LegalResult(result)) { - - - 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 - Board::theBoard.Draw(); - if (CheckForQuitWhilstWaiting(0.5)) - { - red->SendMessage("QUIT"); - blue->SendMessage("QUIT"); - exit(EXIT_SUCCESS); - } - #endif //GRAPHICS - - 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; - - - - #ifdef GRAPHICS - Board::theBoard.Draw(); - if (CheckForQuitWhilstWaiting(0.5)) - { - red->SendMessage("QUIT"); - blue->SendMessage("QUIT"); - exit(EXIT_SUCCESS); - } - #else - Board::theBoard.Print(stderr); - sleep(1); - system("clear"); - #endif //GRAPHICS - - ++count; + if (winner == Piece::BOTH) + winner = Piece::NONE; + else + { + if (winner == Piece::RED) + winner = Piece::BLUE; + else + winner = Piece::RED; + } } + - printf("Final board state\n"); - #ifdef GRAPHICS - Board::theBoard.Draw(); - - if (CheckForQuitWhilstWaiting(4)) - { - red->SendMessage("QUIT"); - blue->SendMessage("QUIT"); - exit(EXIT_SUCCESS); - } - - #else - Board::theBoard.Print(stderr); - #endif //GRAPHICS - sleep(2); - - - if (turn == Piece::RED) - { - fprintf(stderr,"Game ends on RED's turn - REASON: "); - } - else if (turn == Piece::BLUE) - { - fprintf(stderr,"Game ends on BLUE's turn - REASON: "); - } - else - { - fprintf(stderr,"Game ends on ERROR's turn - REASON: "); - - } - switch (result.type) + switch (winner) { - case MovementResult::NO_BOARD: - fprintf(stderr,"Board does not exit?!\n"); - break; - case MovementResult::INVALID_POSITION: - fprintf(stderr,"Coords outside board\n"); - break; - case MovementResult::NO_SELECTION: - fprintf(stderr,"Move does not select a piece\n"); + case Piece::RED: + fprintf(stdout, "%s RED %d\n", red,game.TurnCount()); break; - case MovementResult::NOT_YOUR_UNIT: - fprintf(stderr,"Selected piece belongs to other player\n"); + case Piece::BLUE: + fprintf(stdout, "%s BLUE %d\n", blue,game.TurnCount()); break; - case MovementResult::IMMOBILE_UNIT: - fprintf(stderr,"Selected piece is not mobile (FLAG or BOMB)\n"); + case Piece::BOTH: + fprintf(stdout, "DRAW %d\n",game.TurnCount()); break; - case MovementResult::INVALID_DIRECTION: - fprintf(stderr,"Selected unit cannot move that way\n"); - break; - case MovementResult::POSITION_FULL: - fprintf(stderr,"Attempted move into square occupied by allied piece\n"); - break; - case MovementResult::VICTORY: - fprintf(stderr,"Captured the flag\n"); - break; - case MovementResult::BAD_RESPONSE: - fprintf(stderr,"Unintelligable response\n"); - break; - case MovementResult::NO_MOVE: - fprintf(stderr,"Did not make a move (may have exited)\n"); + case Piece::NONE: + fprintf(stdout, "NONE %d\n",game.TurnCount()); break; + } + + + exit(EXIT_SUCCESS); @@ -200,66 +164,4 @@ int main(int argc, char ** argv) return 0; } -#ifdef GRAPHICS - -bool CheckForQuitWhilstWaiting(int wait) -{ - - - TimerThread timer(wait*1000000); //Wait in seconds - timer.Start(); - while (!timer.Finished()) - { - SDL_Event event; - while (SDL_PollEvent(&event)) - { - switch (event.type) - { - case SDL_QUIT: - timer.Stop(); - return true; - break; - } - } - } - timer.Stop(); - return false; -} - -void cleanup() -{ - delete red; - 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"); - - } - Board::theBoard.Draw(); - while (true) - { - if (CheckForQuitWhilstWaiting(4000)) - { - red->SendMessage("QUIT"); - blue->SendMessage("QUIT"); - exit(EXIT_SUCCESS); - } - } - exit(EXIT_SUCCESS); -} - -#endif //GRAPHICS diff --git a/manager/manual.txt b/manager/manual.txt new file mode 100644 index 0000000..5eb1e8a --- /dev/null +++ b/manager/manual.txt @@ -0,0 +1,155 @@ +NAME + stratego - Interface to manage games of stratego between AI programs and/or human players + +WARNING + This program is still a work in progress. Consider it a Beta version. + +SYNOPSIS + stratego {[-girb] [-o= output_file ] [-t= stall_time] red_player blue_player | {-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. + By itself, stratego does not "play" the game. An external AI program must be used. stratego is intended to be used for the testing of + various AI strategies, written in any programming language. It will be used for the UCC Programming Competition 2012. + + Unless -h, or --help is given, both red_player and blue_player must be supplied. + + red_player + Should be either a path to an executable file which will control the Red player, or "human". + If set to "human", stratego will request the user to make moves for the Red player using stdin. + NOTES + 1. There is no plan to support AI programs named "human". Deal with it. + 2. As of writing, human players must use stdin. A graphical interface may be added later. + + blue_player + As red_player, except for controlling the Blue player. + +OPTIONS + -g + By default, graphics are disabled. If the -g switch is present, stratego will draw the game as it is played using OpenGL + -i + By default, stratego will exit if a move which is deemed "illegal" is made. If the -i switch is present, illegal moves will be ignored. + That is, the move will not be made (effectively the player making the illegal move loses a turn). + -r + By default, the identities of all pieces are shown. If the -r switch is present, and graphics are enabled, red pieces will be disguised. + If graphics are disabled, the -r switch has no effect. + -b + As -r, except blue pieces will be disguised. + NOTE: Both -r and -b may be used. + -o + By default, stratego is silent. If the -o switch is present, the result of each move is printed to a file. If output_file is "stdout" + then stdout will be used instead of a text file. + -t + By default, stratego executes moves as fast as they are recieved. If the -t switch is present, a delay of stall_time will be introduced + between each move. + -h, --help + If the -h switch is present, this page will be printed and stratego will exit. + +GAME RULES + Each player sets up 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 + 2 General 2 1 + 3 Colonel 3 2 + 4 Major 4 3 + 5 Captain 5 4 + 6 Lieutenant 6 4 + 7 Sergeant 7 4 + 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. + + 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. + + 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. + + +PROTOCOL + In order to interface with stratego, an AI program must satisfy the following protocol. + Each query is followed by a newline, and responses are expected to be followed with a newline. + The queries are recieved through stdin, and responses should be written to stdout. + + 1. SETUP + QUERY: YOUR_COLOUR OPPONENT_ID BOARD_WIDTH BOARD_HEIGHT + + 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 + 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] + 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 + 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. + + 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. + + 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. + Human players are not subject to the timeout restriction. + + + +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 + + 3. + NONE TURNS + When for some reason both the AI programs failed to respond or crashed. + + stratego will then return exit code 0. + + If an error occurs within stratego itself, an error message will be printed to stderr and return exit code 1. + If possible, stratego will print the message "QUIT" to both AI programs, and they should exit as soon as possible. + + +BUGS + There are no known bugs at this time. However, stratego is still a work in progress. Report a bug to the AUTHOR (see below). + +AUTHORS + Sam Moore (for the UCC Programming Competition 2012) + +NOTES + 0. This program is still a work in progress and subject to changes. + + 1. UCC Programming Competition 2012 Description + http://matches.ucc.asn.au/stratego/ + + 2. UCC Programming Competition 2012 Git repository + git://git.ucc.asn.au/progcomp2012.git + + + 3. IRC Channel + irc://irc.ucc.asn.au #progcomp + diff --git a/manager/movementresult.h b/manager/movementresult.h index 8df375c..4056a71 100644 --- a/manager/movementresult.h +++ b/manager/movementresult.h @@ -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, BAD_RESPONSE, NO_MOVE} Type; + 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, COLOUR_ERROR, ERROR} Type; MovementResult(const Type & result = OK, const Piece::Type & newAttackerRank = Piece::NOTHING, const Piece::Type & newDefenderRank = Piece::NOTHING) : type(result), attackerRank(newAttackerRank), defenderRank(newDefenderRank) {} diff --git a/manager/stratego.cpp b/manager/stratego.cpp index ba59c5f..37df4d2 100644 --- a/manager/stratego.cpp +++ b/manager/stratego.cpp @@ -1,4 +1,4 @@ -#include "common.h" + #include "stratego.h" @@ -7,19 +7,18 @@ using namespace std; /** * Static variables */ -Board Board::theBoard(10,10); + //nothing, boulder, flag, spy, scout, miner, sergeant, lietenant, captain, major, colonel, general, marshal, bomb, error -char Piece::tokens[] = {'.','+','F','y','s','n','S','L','c','m','C','G','M','B','?'}; +char Piece::tokens[] = {'.','*','F','s','9','8','7','6','5','4','3','2','1','B','?'}; int Piece::maxUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; -#ifdef GRAPHICS - Piece::TextureManager Piece::textures; -#endif //GRAPHICS + +Piece::TextureManager Piece::textures; + -#ifdef GRAPHICS Piece::TextureManager::~TextureManager() { @@ -42,7 +41,7 @@ Texture & Piece::TextureManager::operator[](const LUint & at) } return *(Array::operator[](at)); } -#endif //GRAPHICS + /** * Gets the type of a piece, based off a character token @@ -135,7 +134,7 @@ void Board::Print(FILE * stream, const Piece::Colour & reveal) } -#ifdef GRAPHICS + /** * Draw the board state to graphics * @param reveal - Pieces matching this colour will be revealed. All others will be shown as blank coloured squares. @@ -144,7 +143,9 @@ void Board::Draw(const Piece::Colour & reveal) { if (!Graphics::Initialised()) { - Graphics::Initialise("Stratego", width*32, height*32); + fprintf(stderr, "ERROR - Board::Draw called whilst graphics disabled!!!\n"); + exit(EXIT_FAILURE); + } Graphics::ClearScreen(); @@ -170,10 +171,10 @@ void Board::Draw(const Piece::Colour & reveal) switch (piece->colour) { case Piece::RED: - Piece::textures[(int)(Piece::BOULDER)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); + Piece::textures[(int)(Piece::NOTHING)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); break; case Piece::BLUE: - Piece::textures[(int)(Piece::BOULDER)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); + Piece::textures[(int)(Piece::NOTHING)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); break; case Piece::NONE: Piece::textures[(int)(Piece::BOULDER)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); @@ -189,7 +190,6 @@ void Board::Draw(const Piece::Colour & reveal) Graphics::UpdateScreen(); } -#endif //GRAPHICS /** * Adds a piece to the board diff --git a/manager/stratego.h b/manager/stratego.h index 25aa5cc..6f0f8fe 100644 --- a/manager/stratego.h +++ b/manager/stratego.h @@ -6,10 +6,10 @@ #include -#ifdef GRAPHICS + #include "graphics.h" #include "array.h" -#endif //GRAPHICS + /** * Contains classes for a game of Stratego @@ -51,7 +51,6 @@ class Piece const Type type; const Colour colour; - #ifdef GRAPHICS public: class TextureManager : public Graphics::TextureManager, private Array @@ -85,7 +84,7 @@ class Piece - #endif //GRAPHICS + }; @@ -102,9 +101,9 @@ class Board virtual ~Board(); //Destructor void Print(FILE * stream, const Piece::Colour & reveal=Piece::BOTH); //Print board - #ifdef GRAPHICS - void Draw(const Piece::Colour & reveal=Piece::BOTH); //Draw board - #endif //GRAPHICS + + void Draw(const Piece::Colour & reveal=Piece::BOTH); //Draw board + bool AddPiece(int x, int y, const Piece::Type & newType, const Piece::Colour & newColour); //Add piece to board @@ -122,7 +121,7 @@ class Board } MovementResult MovePiece(int x, int y, const Direction & direction, int multiplier=1,const Piece::Colour & colour=Piece::NONE); //Move piece from position in direction - static Board theBoard; + Piece::Colour winner; diff --git a/samples/dummy/dummy.cpp b/samples/dummy/dummy.cpp index 4e39b74..9d28b67 100644 --- a/samples/dummy/dummy.cpp +++ b/samples/dummy/dummy.cpp @@ -34,17 +34,17 @@ int main(int argc, char ** argv) assert(width == 10 && height == 10); //Can't deal with other sized boards if (colour == "RED") { - fprintf(stdout, "FBnyBmSsBn\n"); - fprintf(stdout, "BBCMccccnC\n"); - fprintf(stdout, "LSGmnsnsSm\n"); - fprintf(stdout, "sLSBLLssss\n"); + fprintf(stdout, "FB8sB479B8\n"); + fprintf(stdout, "BB31555583\n"); + fprintf(stdout, "6724898974\n"); + fprintf(stdout, "967B669999\n"); } else if (colour == "BLUE") { - fprintf(stdout, "sLSBLLssss\n"); - fprintf(stdout, "LSGmnsnsSm\n"); - fprintf(stdout, "BBCMccccnC\n"); - fprintf(stdout, "FBnyBmSsBn\n"); + fprintf(stdout, "967B669999\n"); + fprintf(stdout, "6724898974\n"); + fprintf(stdout, "BB31555583\n"); + fprintf(stdout, "FB8sB479B8\n"); } else { diff --git a/samples/forfax/forfax.cpp b/samples/forfax/forfax.cpp index ede3156..eb44960 100644 --- a/samples/forfax/forfax.cpp +++ b/samples/forfax/forfax.cpp @@ -29,7 +29,7 @@ using namespace std; * NOTHING, BOULDER, FLAG, SPY, SCOUT, MINER, SERGEANT, LIETENANT, CAPTAIN, MAJOR, COLONEL, GENERAL, MARSHAL, BOMB, ERROR */ -char Piece::tokens[] = {'.','+','F','y','s','n','S','L','c','m','C','G','M','B','?'}; +char Piece::tokens[] = {'.','*','F','s','9','8','7','6','5','4','3','2','1','B','?'}; /** @@ -416,7 +416,7 @@ double Forfax::CombatSuccessChance(Piece * attacker, Piece * defender) const * TODO: Alter this to make it better * @param piece - The Piece to move * @param dir - The direction in which to move - * @returns a number between 0 and 1, indicating how worthwhile the move is + * @returns a number between 0 and 1, indicating how worthwhile the move is, or a negative number (-1) if the move is illegal */ double Forfax::MovementScore(Piece * piece, const Board::Direction & dir) const { @@ -432,7 +432,7 @@ double Forfax::MovementScore(Piece * piece, const Board::Direction & dir) const if (!board->ValidPosition(x2, y2) || !piece->Mobile()) { - basevalue = 0; //No point attempting to move immobile units, or moving off the edges of the board + return -1; //No point attempting to move immobile units, or moving off the edges of the board } else if (board->Get(x2, y2) == NULL) { @@ -445,18 +445,22 @@ double Forfax::MovementScore(Piece * piece, const Board::Direction & dir) const 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(board->Width(), board->Height())))); + double multiplier = (double)(max(board->Width(), board->Height()) - Board::NumberOfMoves(closestEnemy->x, closestEnemy->y, x2, y2)) / (double)(max(board->Width(), board->Height())); + + basevalue = CombatScore(closestEnemy->x, closestEnemy->y, piece)*multiplier*multiplier; + } } else if (board->Get(x2, y2)->colour != Piece::Opposite(piece->colour)) { - basevalue = 0; //The position is occupied by an ally, and so its pointless to try and move there + return -1; //The position is occupied by an ally, and so its pointless to try and move there } else { basevalue = CombatScore(x2, y2, piece); //The position is occupied by an enemy; compute combat score + } if (basevalue > 0) @@ -471,7 +475,7 @@ double Forfax::MovementScore(Piece * piece, const Board::Direction & dir) const if (x2 == piece->lastx && y2 == piece->lasty) //Hack which decreases score for backtracking moves - basevalue = basevalue/1.5; + basevalue = basevalue/100; if (rand() % 10 == 0) //Hack which introduces some randomness by boosting one in every 10 scores basevalue *= 4; @@ -507,18 +511,18 @@ Forfax::Status Forfax::Setup() if (strColour == "RED") { colour = Piece::RED; - cout << "FBmSsnsnBn\n"; - cout << "BBCMccccyC\n"; - cout << "LSGmnsBsSm\n"; - cout << "sLSBLnLsss\n"; + cout << "FB8sB479B8\n"; + cout << "BB31555583\n"; + cout << "6724898974\n"; + cout << "967B669999\n"; } else if (strColour == "BLUE") { colour = Piece::BLUE; - cout << "sLSBLnLsss\n"; - cout << "LSGmnsBsSm\n"; - cout << "BBCMccccyC\n"; - cout << "FBmSsnsnBn\n"; + cout << "967B669999\n"; + cout << "6724898974\n"; + cout << "BB31555583\n"; + cout << "FB8sB479B8\n"; } else return INVALID_QUERY; @@ -601,7 +605,7 @@ Forfax::Status Forfax::MakeMove() //Print chosen move to stdout cout << choice.piece->x << " " << choice.piece->y << " " << direction << "\n"; - + cerr << "\nForfax move " << choice.piece->x << " " << choice.piece->y << " " << direction << " [score = " << choice.score << "]\n"; @@ -621,8 +625,9 @@ Forfax::Status Forfax::MakeMove() Forfax::Status Forfax::InterpretMove() { //Variables to store move information - int x; int y; string direction; string result = ""; int multiplier = 1; int attackerVal = (int)(Piece::BOMB); int defenderVal = (int)(Piece::BOMB); - + int x; int y; string direction; string result = ""; int multiplier = 1; + Piece::Type attackerRank = Piece::NOTHING; + Piece::Type defenderRank = Piece::NOTHING; //Read in information from stdin cin >> x; cin >> y; cin >> direction; cin >> result; @@ -634,13 +639,15 @@ Forfax::Status Forfax::InterpretMove() stringstream s(buf); cin >> buf; s.clear(); s.str(buf); - s >> attackerVal; - + char temp; + s >> temp; + attackerRank = Piece::GetType(temp); buf.clear(); cin >> buf; s.clear(); s.str(buf); - s >> defenderVal; + s >> temp; + defenderRank = Piece::GetType(temp); } @@ -654,9 +661,8 @@ Forfax::Status Forfax::InterpretMove() } - //Convert printed ranks into internal Piece::Type ranks - Piece::Type attackerRank = Piece::Type(Piece::BOMB - attackerVal); - Piece::Type defenderRank = Piece::Type(Piece::BOMB - defenderVal); + + @@ -910,7 +916,7 @@ double Forfax::VictoryScore(Piece * attacker, Piece * defender) const return 0.9; } //Return average of normalised defender ranks - return max(((defender->maxRank / Piece::BOMB) + (defender->minRank / Piece::BOMB))/2, 0.6); + return max(((defender->maxRank / Piece::BOMB) + (defender->minRank / Piece::BOMB))/2, 1); } /** @@ -964,7 +970,10 @@ double Forfax::DefeatScore(Piece * attacker, Piece * defender) const if (totalRanks > 0) - result = (possibleRanks/totalRanks) - 0.8*(double)((double)(attacker->minRank) / (double)(Piece::BOMB)); + { + double multiplier = ((double)(Piece::BOMB) - (double)(attacker->minRank)) / (double)(Piece::BOMB); + result = (possibleRanks/totalRanks) * multiplier * multiplier; + } result += bonus / totalRanks; diff --git a/web/index.html b/web/index.html index 7736907..6329e80 100644 --- a/web/index.html +++ b/web/index.html @@ -26,11 +26,12 @@

The manager program takes two executable paths, one to each of the AI programs to pit against each other.

It first requests each program to print the setup of its pieces to stdout. Then the programs take turns (Red, then blue, etc) to move one piece.

The first program is Red (top of the board), the second is Blue (bottom of the board).

-

I have now added graphics, but you can disable them by commenting out the "#define GRAPHICS" in the file "common.h"

-

EDIT: You also have to remove graphics.o from the Makefile dependencies, and there are probably other annoying things. Its probably easiest just to install the SDL and OpenGL libraries to be honest

- +

The manager program now has switches to enable/disable graphics and set the move speed of the match.

+

Use "-g" to enable graphics (by default disabled) and "-t TIMEOUT" to set the move speed (default 0 (as fast as possible)).

+

This program now has a manual page

Messaging Protocol and Rules

+

The rules are now also available in the manual page of the manager program

Setup

Query

The manager program prints one line in the following format to each program:

@@ -40,26 +41,32 @@

Response

The AI program queried must print four (4) lines containing its initial setup. Each character represents a unit or empty space. The characters are as follows:

+ +

Warning: I have changed the characters from mostly letters to mostly digits, indicating the actual worth of a piece

- - - - - - - - - - - - - + + + + + + + + + + + + + - +
Rank Character Number
Marshal M 1
General G 1
Colonel C 2
Major m 3
Captain C 4
Lietenant L 4
Sergeant S 4
Miner n 5
Scout s 8
Spy y 1
Bomb B 6
Flag F 1
Name Character Number Notes
Marshal 1 1 Dies if attacked by the Spy
General 2 1
Colonel 3 2
Major 4 3
Captain 5 4
Lietenant 6 4
Sergeant 7 4
Miner 8 5 Defuses Bombs and survives.
Scout 9 8 May move any number of times in a single direction.
Spy s 1 If the Spy attacks the Marshal, the Marshal is destroyed.
Bomb B 6 Immobile. Destroys both self and any unit that attacks the Bomb.
Flag F 1 Immobile. Capture the opponent's flag to win.
Unoccupied .
Obstacle +
Obstacle *

The AI program can't place any obstacles, and must at least place the Flag for its setup to be valid.

RED will always occupy the top four rows of the board, and BLUE occupies the bottom four rows.

+

All pieces except the Scout, Bomb and Flag can move 1 square horizontally or vertically (not diagonally).

+

When two pieces of different ranks encounter each other, the piece with the highest rank (lowest numbered rank!) is the victor, unless a special rule applies.

+

When two pieces of equal rank encounter each other, the victor is randomly chosen

+

Pieces of the same colour may not pass through the same squares

Turns

Query

@@ -90,6 +97,7 @@ ILLEGAL The moving player attempted to make an illegal move, and has hence lost the game. The game will end shortly.

If printed, RANK1 and RANK2 indicate the ranks of the moved piece and the defending piece (the piece who occupied the destination square) respectively.

+

Originally RANK1 and RANK2 were never printed, but when I actually tried to write an AI myself I got very annoyed at this restriction... indicating it was probably a bad idea :P

Additional Turns

@@ -102,9 +110,10 @@

This is a description of the signals the AI programs recieve, in order:

  1. Previous turn's outcome (other player's move) OR "START" if it is the first turn
  2. -
  3. A WIDTH*HEIGHT grid indicating the board state, with the AI program's own pieces revealed
  4. +
  5. A BOARD_WIDTH*BOARD_HEIGHT grid indicating the board state, with the AI program's own pieces revealed
  6. After the AI program makes a move, the outcome is printed to it, and the other program, which continues from step 1
+

I am considering removing Step 2, since all information needed for an AI to keep the board state is contained in the initial board state and then the movement result messages

End Game

Query

@@ -129,11 +138,15 @@

It is up to the AI program to keep track of pieces. The manager program will only reveal the identity of the AI program's own pieces; the other player's pieces will be marked with * or # characters.

In a traditional game, two pieces of equal value will both be destroyed in combat. Currently, only one piece is destroyed and the outcome is randomly chosen.

-

Example Program

-

I have written a spectacularly boring AI which randomly selects a unit, and then randomly selects a direction in which to move it.

-

I should probably make a more interesting example if I want people to actually care about this.

- -

I am working on another AI, but things seem to die and explode every time I try to use it...

+

Example Programs

+

"Dummy" AI

+

Dummy randomly moves pieces. It attempts to not make illegal moves (although sometimes it still does).

+

"Forfax" AI

+

Forfax iterates through all possible moves and allocates a score between 0 and 1 to each move based on how desirable the move is. It then selects the highest scoring move.

+

Forfax is pretty terrible, but can beat the Dummy AI most of the time.

+

It is possible Forfax can be greatly improved by altering the scoring algorithms.

+

Since we don't want the Sample AIs to beat every entrance, I'm not really going to try and improve Forfax unless I get bored.

+

If you are writing an AI in C++, Forfax already contains a lot of useful functions.

Longterm Scoring

I haven't started a system for pairing AI's and keeping track of scores and winners etc yet

-- 2.20.1