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...
#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
--- /dev/null
+#include <sstream>
+
+#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
+}
+
--- /dev/null
+#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
+++ /dev/null
-#ifndef COMMON_H
-#define COMMON_H
-
-#define GRAPHICS
-
-#endif //COMMON_H
-
-//EOF
-
-
-#include <sstream>
-
-#include "stratego.h"
-
#include "controller.h"
+#include <sstream>
+#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;
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:
}
- if (!Board::LegalResult(moveResult))
+ if (Game::theGame->allowIllegalMoves && !Board::LegalResult(moveResult))
return MovementResult::OK; //HACK - Legal results returned!
else
return moveResult;
}
-
#define CONTROLLER_H
#include "stratego.h"
-#include "program.h"
+#include <string>
/**
- * 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
--- /dev/null
+#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;
+}
--- /dev/null
+#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
--- /dev/null
+#include "human_controller.h"
+
+#include "game.h"
+
+#include <iostream> //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;
+
+}
--- /dev/null
+#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
#include <stdlib.h>
#include <stdio.h>
-#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);
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
--- /dev/null
+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
+
+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
+
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) {}
-#include "common.h"
+
#include "stratego.h"
/**
* 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()
{
}
return *(Array<Texture*>::operator[](at));
}
-#endif //GRAPHICS
+
/**
* Gets the type of a piece, based off a character token
}
-#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.
{
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();
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));
Graphics::UpdateScreen();
}
-#endif //GRAPHICS
/**
* Adds a piece to the board
#include <assert.h>
-#ifdef GRAPHICS
+
#include "graphics.h"
#include "array.h"
-#endif //GRAPHICS
+
/**
* Contains classes for a game of Stratego
const Type type;
const Colour colour;
- #ifdef GRAPHICS
public:
class TextureManager : public Graphics::TextureManager<LUint>, private Array<Texture*>
- #endif //GRAPHICS
+
};
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
}
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;
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
{
* 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','?'};
/**
* 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
{
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)
{
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<int>(board->Width(), board->Height()))));
+ double multiplier = (double)(max<int>(board->Width(), board->Height()) - Board::NumberOfMoves(closestEnemy->x, closestEnemy->y, x2, y2)) / (double)(max<int>(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)
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;
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;
//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";
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;
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);
}
}
- //Convert printed ranks into internal Piece::Type ranks
- Piece::Type attackerRank = Piece::Type(Piece::BOMB - attackerVal);
- Piece::Type defenderRank = Piece::Type(Piece::BOMB - defenderVal);
+
+
return 0.9;
}
//Return average of normalised defender ranks
- return max<double>(((defender->maxRank / Piece::BOMB) + (defender->minRank / Piece::BOMB))/2, 0.6);
+ return max<double>(((defender->maxRank / Piece::BOMB) + (defender->minRank / Piece::BOMB))/2, 1);
}
/**
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;
<p> The manager program takes two executable paths, one to each of the AI programs to pit against each other. <p>
<p> 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. </p>
<p> The first program is Red (top of the board), the second is Blue (bottom of the board). </p>
-<p> I have now added graphics, but you can disable them by commenting out the "#define GRAPHICS" in the file "common.h" </p>
-<p> 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 </p>
-
+<p> The manager program now has switches to enable/disable graphics and set the move speed of the match. </p>
+<p> Use "-g" to enable graphics (by default disabled) and "-t TIMEOUT" to set the move speed (default 0 (as fast as possible)). </p>
+<p> <b> This program now has a <a href="../manager/manual.txt"/>manual page</a> </b> </p>
<h2> Messaging Protocol and Rules </h2>
+<p> <b> The rules are now also available in the <a href="../manager/manual.txt"/>manual page</a> of the manager program </b> </p>
<h3> Setup </h3>
<h4> Query </h4>
<p> The manager program prints one line in the following format to each program: </p>
<h4> Response </h4>
<p> The AI program queried must print <i>four</i> (4) lines containing its initial setup. Each character represents a unit or empty space. The characters are as follows: </p>
<p> </p>
+
+<p><b>Warning:</b> I have changed the characters from mostly letters to mostly digits, indicating the actual worth of a piece</p>
<table border="1">
-<tr> <th>Rank</th> <th>Character</th> <th>Number</th> </tr>
-<tr> <td>Marshal</td> <td>M</td> <td>1</td> </tr>
-<tr> <td>General</td> <td>G</td> <td>1</td> </tr>
-<tr> <td>Colonel</td> <td>C</td> <td>2</td> </tr>
-<tr> <td>Major</td> <td>m</td> <td>3</td> </tr>
-<tr> <td>Captain</td> <td>C</td> <td>4</td> </tr>
-<tr> <td>Lietenant</td> <td>L</td> <td>4</td> </tr>
-<tr> <td>Sergeant</td> <td>S</td> <td>4</td> </tr>
-<tr> <td>Miner</td> <td>n</td> <td>5</td> </tr>
-<tr> <td>Scout</td> <td>s</td> <td>8</td> </tr>
-<tr> <td>Spy</td> <td>y</td> <td>1</td> </tr>
-<tr> <td>Bomb</td> <td>B</td> <td>6</td> </tr>
-<tr> <td>Flag</td> <td>F</td> <td>1</td> </tr>
+<tr> <th>Name</th> <th>Character</th> <th> Number </th> <th> Notes </th> </tr>
+<tr> <td>Marshal</td> <td>1</td> <td>1</td> <td> Dies if attacked by the Spy </td> </tr>
+<tr> <td>General</td> <td>2</td> <td>1</td> <td> </td> </tr>
+<tr> <td>Colonel</td> <td>3</td> <td>2</td> <td> </td> </tr>
+<tr> <td>Major</td> <td>4</td> <td>3</td> <td> </td> </tr>
+<tr> <td>Captain</td> <td>5</td> <td>4</td> <td> </td> </tr>
+<tr> <td>Lietenant</td> <td>6</td> <td>4</td> <td> </td> </tr>
+<tr> <td>Sergeant</td> <td>7</td> <td>4</td> <td> </td> </tr>
+<tr> <td>Miner</td> <td>8</td> <td>5</td> <td> Defuses Bombs and survives. </td> </tr>
+<tr> <td>Scout</td> <td>9</td> <td>8</td> <td> May move any number of times in a single direction. </td> </tr>
+<tr> <td>Spy </td> <td>s</td> <td>1</td> <td> If the Spy attacks the Marshal, the Marshal is destroyed. </td> </tr>
+<tr> <td>Bomb</td> <td>B</td> <td>6</td> <td> Immobile. Destroys both self and any unit that attacks the Bomb. </td> </tr>
+<tr> <td>Flag</td> <td>F</td> <td>1</td> <td> Immobile. Capture the opponent's flag to win. </td> </tr>
<tr> <td>Unoccupied</td> <td>.</td> <td> </td> </tr>
-<tr> <td>Obstacle</td> <td>+</td> <td> </td> </tr>
+<tr> <td>Obstacle</td> <td>*</td> <td> </td> </tr>
</table>
<p> The AI program can't place any obstacles, and must at least place the Flag for its setup to be valid. </p>
<p> RED will always occupy the top four rows of the board, and BLUE occupies the bottom four rows. </p>
+<p> All pieces except the Scout, Bomb and Flag can move 1 square horizontally or vertically (not diagonally). </p>
+<p> 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. </p>
+<p> When two pieces of <i>equal</i> rank encounter each other, the victor is randomly chosen </p>
+<p> Pieces of the same colour may <i>not</i> pass through the same squares </p>
<h3> Turns </h3>
<h4> Query </h4>
<tr> <td> ILLEGAL </td> <td> The moving player attempted to make an illegal move, and has hence lost the game. The game will end shortly. </td> </tr>
</table>
<p> If printed, RANK1 and RANK2 indicate the ranks of the moved piece and the defending piece (the piece who occupied the destination square) respectively. </p>
+<p> 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 </p>
<h4> Additional Turns </h4>
<p> This is a description of the signals the AI programs recieve, in order:
<ol>
<li> Previous turn's outcome (other player's move) OR "START" if it is the first turn </li>
- <li> A WIDTH*HEIGHT grid indicating the board state, with the AI program's own pieces revealed </li>
+ <li> A BOARD_WIDTH*BOARD_HEIGHT grid indicating the board state, with the AI program's own pieces revealed </li>
<li> After the AI program makes a move, the outcome is printed to it, and the other program, which continues from step 1 </li>
</ol>
+<p> I am considering removing Step 2, since <i>all</i> information needed for an AI to keep the board state is contained in the initial board state and then the movement result messages </p>
<h3> End Game </h3>
<h4> Query </h4>
<p> 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. </p>
<p> 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. </p>
-<h2> Example Program </h2>
-<p> I have written a spectacularly boring AI which randomly selects a unit, and then randomly selects a direction in which to move it. </p>
-<p> I should probably make a more interesting example if I want people to actually care about this. </p>
-
-<p> I am working on another AI, but things seem to die and explode every time I try to use it... </p>
+<h2> Example Programs </h2>
+<h3>"Dummy" AI</h3>
+<p> Dummy randomly moves pieces. It attempts to not make illegal moves (although sometimes it still does). </p>
+<h3>"Forfax" AI</h3>
+<p> 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. </p>
+<p> Forfax is pretty terrible, but can beat the Dummy AI <i>most</i> of the time. </p>
+<p> It is possible Forfax can be greatly improved by altering the scoring algorithms. </p>
+<p> 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. </p>
+<p> If you are writing an AI in C++, Forfax already contains a lot of useful functions. </p>
<h2> Longterm Scoring </h2>
<p> I haven't started a system for pairing AI's and keeping track of scores and winners etc yet </p>