From: Sam Moore Date: Tue, 29 Nov 2011 09:02:13 +0000 (+0800) Subject: First actual commit X-Git-Url: https://git.ucc.asn.au/?p=progcomp2012.git;a=commitdiff_plain;h=f91a915d6f64f9d35e867d26e8ddb9c1b1ab0c1e First actual commit More details to follow :P --- diff --git a/Test b/Test deleted file mode 100644 index ce05747..0000000 --- a/Test +++ /dev/null @@ -1,2 +0,0 @@ -This is a test -SPAM diff --git a/manager/Makefile b/manager/Makefile new file mode 100644 index 0000000..7d81a98 --- /dev/null +++ b/manager/Makefile @@ -0,0 +1,27 @@ +#Makefile for Stratego + +CPP = g++ -Wall -pedantic -lSDL -lGL -g +OBJ = main.o controller.o program.o thread_util.o stratego.o graphics.o + +BIN = stratego + + + +$(BIN) : $(OBJ) + $(CPP) -o $(BIN) $(OBJ) + + + + +%.o : %.cpp %.h + $(CPP) -c $< + +clean : + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + +clean_full: #cleans up all backup files + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + $(RM) *.*~ + $(RM) *~ + + diff --git a/manager/array.h b/manager/array.h new file mode 100644 index 0000000..2a1d29f --- /dev/null +++ b/manager/array.h @@ -0,0 +1,128 @@ +#ifndef ARRAY_H +#define ARRAY_H + +typedef long unsigned int LUint; +#include + +template +class Array +{ + public: + Array() : start(NULL), size(0), reserved(0) {} + Array(LUint newSize) : start(new T[newSize]), size(newSize), reserved(newSize) {} + ~Array() {delete [] start;} + + void Empty() {size = 0;} + void Add(const T & add); + void Reserve(LUint reserve); + void Resize(LUint newSize); + void RemoveBack(); + + LUint Size() const {return size;} + LUint Capacity() const {return reserved;} + + void operator=(const Array & equ); + bool operator==(const Array & equ) const; + bool operator!=(const Array & equ) const {return !operator==(equ);} + + class Iterator + { + public: + Iterator(const Array & from) : parent(from), index(0) {} + Iterator(const Iterator & cpy) : parent(cpy.parent), index(cpy.index) {} + ~Iterator() {} + + bool Good() const {return index < parent.Size();} + + T & operator*() const {return parent.start[index];} + + void operator++() {++index;} + void operator--() {--index;} + void operator++(int) {operator++();} + void operator--(int) {operator--();} + Iterator & operator+=(int amount) {index += amount;} + Iterator & operator-=(int amount) {index -= amount;} + Iterator operator+(int amount) {return Iterator(*this) += amount;} + Iterator operator-(int amount) {return Iterator(*this) -= amount;} + + void operator=(const Iterator & set) {index = set.index;} + bool operator==(const Iterator & set) {return (&parent == &(set.parent) && index == set.index);} + private: + const Array & parent; + LUint index; + }; + + Iterator First() const {return Iterator(*this);} + Iterator Last() const {return Iterator(*this) -= (size-1);} + + + T & operator[](LUint at) const + { + #ifdef DEBUGALL + printf(" Array::operator[] - called for index %lu/%lu (reserved %lu)\n", at, size, reserved); + + #endif //DEBUG + assert(at < size); return start[at]; + } + + int Find(const T & find) + { + + LUint result; + for (result = 0; result < size; result++) + { + //printf("%p %lu/%lu\n", (void*)(start), result, size); + if (start[result] == find) + return (int)(result); + } + return -1; + } + + private: + T * start; + LUint size; LUint reserved; +}; + +template void Array::Add(const T & add) +{ + if (size >= reserved) + { + T * old = start; + reserved *= 2; ++reserved; + start = new T[reserved]; + for (LUint ii=0; ii < size; ++ii) + start[ii] = old[ii]; + delete [] old; + } + start[size++] = add; +} + +template void Array::RemoveBack() +{ + if (size > 0) + --size; +} + +template void Array::Resize(LUint newSize) +{ + T * old = start; + start = new T[newSize]; + for (LUint ii=0; ii < size; ++ii) + start[ii] = old[ii]; + size = newSize; reserved = newSize; + delete [] old; +} + +template void Array::Reserve(LUint newReserve) +{ + if (newReserve > reserved) + { + T * old = start; + start = new T[newReserve]; + for (LUint ii=0; ii < size; ++ii) + start[ii] = old[ii]; + reserved = newReserve; + } +} + +#endif //ARRAY_H diff --git a/manager/common.h b/manager/common.h new file mode 100644 index 0000000..42588a7 --- /dev/null +++ b/manager/common.h @@ -0,0 +1,10 @@ +#ifndef COMMON_H +#define COMMON_H + +#define GRAPHICS + +#endif //COMMON_H + +//EOF + + diff --git a/manager/controller.cpp b/manager/controller.cpp new file mode 100644 index 0000000..dad6e44 --- /dev/null +++ b/manager/controller.cpp @@ -0,0 +1,172 @@ +#include + +#include "stratego.h" + +#include "controller.h" + +using namespace std; + +/** + * Queries the AI program to setup its pieces + * @returns the result of the response + */ +Board::MovementResult Controller::Setup(const char * opponentName) +{ + int y; + switch (colour) + { + case Piece::RED: + assert(SendMessage("RED %s %d %d", opponentName, Board::theBoard.Width(), Board::theBoard.Height())); + y = 0; + + break; + case Piece::BLUE: + assert(SendMessage("BLUE %s %d %d", opponentName, Board::theBoard.Width(), Board::theBoard.Height())); + y = Board::theBoard.Height()-4; + + break; + case Piece::NONE: + case Piece::BOTH: + //Should never see this; + assert(false); + 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) + { + string line=""; + if (!GetMessage(line, 2.5)) + { + fprintf(stderr, "Timeout on setup\n"); + return Board::BAD_RESPONSE; + } + if ((int)(line.size()) != Board::theBoard.Width()) + { + fprintf(stderr, "Bad length of \"%s\" on setup\n", line.c_str()); + return Board::BAD_RESPONSE; + } + + for (int x = 0; x < (int)(line.size()); ++x) + { + Piece::Type type = Piece::GetType(line[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; + if (usedUnits[type] > Piece::maxUnits[(int)type]) + { + fprintf(stderr, "Too many units of type %c\n", Piece::tokens[(int)(type)]); + return Board::BAD_RESPONSE; + } + + Board::theBoard.AddPiece(x, y+ii, type, colour); + } + } + } + + if (usedUnits[(int)Piece::FLAG] <= 0) + { + return Board::BAD_RESPONSE; //You need to include a flag! + } + + return Board::OK; +} + + +/** + * Queries the AI program to respond to a state of Board::theBoard + * @returns The result of the response and/or move if made + */ +Board::MovementResult Controller::MakeMove(string & buffer) +{ + + if (!Running()) + return Board::NO_MOVE; //AI has quit + Board::theBoard.Print(output, colour); + + + + + buffer.clear(); + if (!GetMessage(buffer,2)) + { + return Board::NO_MOVE; //AI did not respond. It will lose by default. + } + + int x; int y; string direction=""; + stringstream s(buffer); + s >> x; + s >> y; + + + s >> direction; + Board::Direction dir; + if (direction == "UP") + { + dir = Board::UP; + } + else if (direction == "DOWN") + { + dir = Board::DOWN; + } + else if (direction == "LEFT") + { + dir = Board::LEFT; + } + else if (direction == "RIGHT") + { + dir = Board::RIGHT; + } + else + { + fprintf(stderr, "BAD_RESPONSE \"%s\"\n", buffer.c_str()); + return Board::BAD_RESPONSE; //AI gave bogus direction - it will lose by default. + } + + int multiplier = 1; + if (s.peek() != EOF) + s >> multiplier; + Board::MovementResult moveResult = Board::theBoard.MovePiece(x, y, dir, multiplier, colour); + switch (moveResult) + { + case Board::OK: + buffer += " OK"; + break; + case Board::VICTORY: + buffer += " FLAG"; + break; + case Board::KILLS: + buffer += " KILLS"; + break; + case Board::DIES: + buffer += " DIES"; + break; + case Board::BOTH_DIE: + buffer += " BOTHDIE"; + break; + default: + buffer += " ILLEGAL"; + break; + + } + + if (!Board::LegalResult(moveResult)) + return Board::OK; //HACK - Legal results returned! + else + return moveResult; + +} + diff --git a/manager/controller.h b/manager/controller.h new file mode 100644 index 0000000..0e2e5cc --- /dev/null +++ b/manager/controller.h @@ -0,0 +1,29 @@ +#ifndef CONTROLLER_H +#define CONTROLLER_H + +#include "stratego.h" +#include "program.h" + +/** + * Class to control an AI program for a game of Stratego + * Inherits most features from the Program class + */ + +class Controller : public Program +{ + public: + Controller(const Piece::Colour & newColour, const char * executablePath) : Program(executablePath), colour(newColour) {} + virtual ~Controller() {} + + Board::MovementResult Setup(const char * opponentName); //Requests the AI program for the initial positioning of its pieces. + + Board::MovementResult MakeMove(std::string & buffer); //Queries the AI program for a response to the state of Board::theBoard + + const Piece::Colour colour; //Colour identifying the side of the AI program. + + +}; + +#endif //CONTROLLER_H + + diff --git a/manager/dummy b/manager/dummy new file mode 120000 index 0000000..5347eb2 --- /dev/null +++ b/manager/dummy @@ -0,0 +1 @@ +../samples/dummy \ No newline at end of file diff --git a/manager/graphics.cpp b/manager/graphics.cpp new file mode 100644 index 0000000..5b708df --- /dev/null +++ b/manager/graphics.cpp @@ -0,0 +1,449 @@ +#include "graphics.h" +#include +#include +#include + + +#undef DEBUG +//#define DEBUG + +std::list Graphics::allTextures = std::list(); +Screen * Graphics::screen = NULL; + +int Graphics::screenWidth = 0; +int Graphics::screenHeight = 0; +bool Graphics::initialised = false; + +using namespace std; + +Texture::Texture(const char * filename, bool newDrawCentred) : surface(NULL), texture(0), drawCentred(newDrawCentred) +{ + #ifdef DEBUG + printf("Texture::Texture - loading \"%s\".\n", filename); + #endif //DEBUG + + surface = Graphics::LoadTextureBMP(filename); + if (surface == NULL) + { + fprintf(stderr, "Texture::Texture - Could not open texture from file \"%s\"! ABORT\n", filename); + exit(EXIT_FAILURE); + } + + GLenum texture_format; + GLint nOfColours = surface->format->BytesPerPixel; + switch (nOfColours) + { + case 4: //contains alpha + texture_format = (surface->format->Rmask == 0x000000FF) ? GL_RGBA : GL_BGRA; + break; + case 3: //does not contain alpha + texture_format = (surface->format->Rmask == 0x000000FF) ? GL_RGB : GL_BGR; + break; + default: + fprintf(stderr,"Texture::Texture - Could not understand SDL_Surface format (%d colours)! ABORT\n", nOfColours); + exit(EXIT_FAILURE); + break; + } + + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexImage2D(GL_TEXTURE_2D, 0, nOfColours, surface->w, surface->h,0, texture_format, GL_UNSIGNED_BYTE, surface->pixels); + +} + +Texture::~Texture() +{ + #ifdef DEBUG + printf("Texture::~Texture - %p has been deleted. glDeleteTexture and SDL_FreeSurface here.\n", (void*)(this)); + #endif //DEBUG + glDeleteTextures(1, &texture); + //SDL_FreeSurface(surface); +} + +void Texture::DrawColour(int x, int y, double angle, double scale, Colour colour) +{ + if (scale > surface->w || scale > surface->h) + { + Graphics::DrawPixel(x/scale,y/scale,colour); + } + else + { + glColor3f(colour.r,colour.g,colour.b); + Draw(x,y,angle,scale); + glColor3f(1,1,1); + } +} + +void Texture::Draw(int x, int y, double angle , double scale ) +{ + //Draws the CENTRE of the texture at x, y, rotated by angle + + #ifdef DEBUG + printf(" Texture::Draw - Drawing %p at (%d, %d) ; angle %2f ; scale % 2f\n", (void*)(this), x, y, angle, scale); + #endif //DEBUG + + //if (x/scale < 0 || x/scale > Graphics::ScreenWidth() || y/scale < 0 || y/scale > Graphics::ScreenHeight() ) + // return; + + glPushMatrix(); //NOT deprecated + + + glTranslatef(x/scale, y/scale,0); + + if (scale > surface->w || scale > surface->h) + { + Graphics::DrawPixel(0,0, Colour(255,255,255)); + } + else + { + glRotated(angle, 0, 0, 1); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture); + glBegin(GL_QUADS); + + //scale /= 2; + if (drawCentred) + { + glTexCoord2i(0,0); glVertex3f(-0.5f/scale*surface->w ,-0.5f/scale*surface->h,0); //bottom left + glTexCoord2i(1,0); glVertex3f(0.5f/scale*surface->w,-0.5f/scale*surface->h,0); //bottom right + glTexCoord2i(1,1); glVertex3f(0.5f/scale*surface->w,0.5f/scale*surface->h,0); //top right + glTexCoord2i(0,1); glVertex3f(-0.5f/scale*surface->w,0.5f/scale*surface->h,0); //top left + } + else + { + glTexCoord2i(0,0); glVertex3f(0 ,0,0); //bottom left + glTexCoord2i(1,0); glVertex3f(1.0f/scale*surface->w,0,0); //bottom right + glTexCoord2i(1,1); glVertex3f(1.0f/scale*surface->w,1.0f/scale*surface->h,0); //top right + glTexCoord2i(0,1); glVertex3f(0,1.0f/scale*surface->h,0); //top left + } + + glEnd(); + glDisable(GL_TEXTURE_2D); + } + glPopMatrix(); + +} + + +Font::Font(const char * filename, int newWidth, int newHeight) : Texture(filename), width(newWidth), height(newHeight) +{ + +} + +Font::~Font() +{ + +} + +void Font::DrawText(const char * string, int x, int y, double angle, double scale) +{ + #ifdef DEBUG + printf("Font::DrawText - drawing \"%s\"\n", string); + #endif //DEBUG + glPushMatrix(); //NOT deprecated + glTranslatef(x, y,0); + glRotated(angle, 0, 0, 1); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture); + + + for (int ii=0; string[ii] != '\0'; ++ii) + { + if (string[ii] != ' ') + { + glPushMatrix(); + glTranslatef(ii*(float)(width)/(float)(scale),0,0); + + int index = (int)(string[ii]) - (int)('!'); + if (index < 0 || index > (int)('~') - (int)('!')) + index = (int)('~') - (int)('!') + 1; + + float start = (float)(((((float)(index))*((float)(width)))-3.0f)/((float)surface->w)); + float end = (float)(((((float)(index+1))*((float)(width)))-4.0f)/((float)surface->w)); + if (start < 0) {start = 0;} if (end > 1) {end = 1;} + glBegin(GL_QUADS); + glTexCoord2f(start,0); glVertex3f(-0.5f/scale*width ,-0.5f/scale*height,0); //bottom left + glTexCoord2f(end,0); glVertex3f(0.5f/scale*width,-0.5f/scale*height,0); //bottom right + glTexCoord2f(end,1); glVertex3f(0.5f/scale*width,0.5f/scale*height,0); //top right + glTexCoord2f(start,1); glVertex3f(-0.5f/scale*width,0.5f/scale*height,0); //top left + //printf("Index %d - Drawing %c - maps to %f->%f\n", index,string[ii],start,end); + + glEnd(); + glPopMatrix(); + } + } + + + glDisable(GL_TEXTURE_2D); + glPopMatrix(); + +} + + +void Graphics::Initialise(const char * caption, int newWidth, int newHeight) +{ + if (Initialised()) + { + std::cerr << "Graphics have already been initialised! Fatal Error\n"; + exit(EXIT_FAILURE); + } + screenWidth = newWidth; screenHeight = newHeight; + + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + std::cerr << "Couldn't init SDL!\n"; + exit(EXIT_FAILURE); + } + // atexit(Graphics::Destroy); BREAKS THINGS + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); //According to sulix does not matter. (much) + + + + + screen = SDL_SetVideoMode(screenWidth,screenHeight, 32, SDL_OPENGL); + if ( screen == NULL ) + { + std::cerr << "Couldn't set " << screenWidth << "x" << screenHeight << "x32 video mode: " << SDL_GetError() << "\n"; + exit(EXIT_FAILURE); + } + + //COMES AFTER SETVIDEO MODE + glEnable(GL_TEXTURE_2D); + glClearColor(1,1,1,0); //Set clear colour (white) here + glViewport(0,0,screenWidth,screenHeight); //DOES matter + glClear(GL_COLOR_BUFFER_BIT); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0,screenWidth,screenHeight,0,-1,1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glDisable(GL_DEPTH_TEST); + SDL_WM_SetCaption( caption, NULL); + + Graphics::initialised = true; + +} + +void Graphics::Destroy() +{ + list::iterator i(allTextures.begin()); + while (i != allTextures.end()) + { + SDL_FreeSurface((*i)); + ++i; + } + SDL_Quit(); +} + +SDL_Surface * Graphics::LoadTextureBMP(const char * file) +{ + SDL_Surface * tmp = SDL_LoadBMP(file); + if (tmp == NULL) + return NULL; + //assert(tmp != NULL); + + + if (Graphics::screen != NULL) + { + SDL_Surface * tex = SDL_DisplayFormat(tmp); + SDL_FreeSurface(tmp); + + allTextures.push_back(tex); + return tex; + } + return tmp; +} + +void Graphics::SaveTextureBMP(SDL_Surface * tex, const char * file) +{ + SDL_SaveBMP(tex, file); +} + + +void Graphics::DrawTexture(SDL_Surface * tex, int destX, int destY, int srcX, int srcY, int w, int h) +{ + if (w < 0) {w = tex->w - srcX;} + if (h < 0) {h = tex->h - srcY;} + Graphics::DrawTexture(screen, tex, destX, destY, srcX, srcY, w, h); +} + +void Graphics::DrawTexture(SDL_Surface * dest, SDL_Surface * tex, int destX, int destY, int srcX, int srcY, int width, int height) +{ + if ((destX < 0)||(destX >= dest->w)||(destY < 0)||(destY >= dest->h) + ||(srcX < 0)||(srcX >= tex->w)||(srcY < 0)||(srcY >= tex->h)) + return; + + assert(dest->format->BitsPerPixel == 32); + assert(tex->format->BitsPerPixel == 32); + + if (SDL_MUSTLOCK(tex)) + SDL_LockSurface(tex); + + if (SDL_MUSTLOCK(dest)) + SDL_LockSurface(dest); + + + + Colour transparent = Graphics::GetPixel(tex, srcX, srcY); + //printf("transparent from %d %d\n", srcX, srcY); + + for (int xOff = 0; xOff < width; xOff++) + + { + for (int yOff = 0; yOff < height; yOff++) + { + Colour nextColour = Graphics::GetPixel(tex, srcX+xOff, srcY+yOff); + if (nextColour != transparent) + { + Graphics::DrawPixel(dest, destX + xOff, destY + yOff, nextColour); + } + } + } + + if (SDL_MUSTLOCK(tex)) + SDL_UnlockSurface(tex); + + if (SDL_MUSTLOCK(dest)) + SDL_UnlockSurface(dest); + +} + +void Graphics::ClearScreen() +{ + //SDL_FillRect(screen, NULL ,Graphics::MakeColour(0,0,0)); + glClear(GL_COLOR_BUFFER_BIT); + +} + +void Graphics::UpdateScreen() +{ + SDL_GL_SwapBuffers(); + //SDL_Flip(screen); +} + +void Graphics::DrawPixel(int x, int y, Colour colour) +{ + DrawPixel(screen, x, y, colour); +} + +void Graphics::DrawPixel(SDL_Surface * dest, int x, int y, Colour colour) +{ + glBegin(GL_POINTS); + glColor4f(colour.r/255, colour.g/255, colour.b/255, colour.a); + glVertex2f(x, y); + glColor3f(1,1,1); + glEnd(); +} + +void Graphics::DrawGrid(int gridWidth, int gridHeight, Colour colour) +{ + for (int x = 0; x < screen->w; x+=gridWidth) + { + Graphics::DrawLine(x,0, x,screen->h - 1, colour); + } + for (int y = 0; y < screen->h; y+=gridHeight) + { + Graphics::DrawLine(0,y, screen->w - 1,y, colour); + } +} + +Uint8 Graphics::MakeColour(int R, int G, int B, int Alpha) +{ + return SDL_MapRGB(screen->format,R,G,B); +} + +Colour Graphics::GetPixel(int x, int y) +{ + return Graphics::GetPixel(screen, x, y); +} + +Colour Graphics::GetPixel(SDL_Surface * src, int x, int y) +{ + //Convert the pixels to 32 bit + Uint8 * pixels = (Uint8*)src->pixels; + //Get the requested pixel + + if (((y > 0)&&(y < src->h)) && ((x > 0)&&(x < src->w))) + return ConvertColour(pixels[ ( y * src->w ) + x ]); + return Colour(0,0,0,0); + +} + + + +void Graphics::DrawLine(int x1, int y1, int x2, int y2, Colour colour,double scale) +{ + //printf("DRAW LINE\n"); + glColor4f(colour.r/255,colour.g/255,colour.b/255,colour.a); + glBegin(GL_LINES); + glVertex2f(x1/scale, y1/scale); // origin of the line + glVertex2f(x2/scale, y2/scale); // ending point of the line + glColor3f(1,1,1); + glEnd(); + + +} + +void Graphics::DrawLineDashed(int x1, int y1, int x2, int y2, Colour colour, double scale) +{ + glLineStipple(8, 0xAAAA); + glEnable(GL_LINE_STIPPLE); + DrawLine(x1,y1,x2,y2,colour,scale); + glDisable(GL_LINE_STIPPLE); + glEnd(); +} + +void Graphics::DrawRectangle(int topX, int topY, int botX, int botY, Colour colour, double scale) +{ + glColor4f(colour.r/255,colour.g/255,colour.b/255,colour.a); + glBegin(GL_LINES); + glVertex2f(topX/scale, topY/scale); // origin of the rectangle + glVertex2f(botX/scale, topY/scale); // point1 + glVertex2f(botX/scale, botY/scale); // point2 + glVertex2f(topX/scale, botY/scale); // point3 + glVertex2f(topX/scale, topY/scale); // point4 + glEnd(); +} + +Colour Graphics::ConvertColour(Uint8 from) +{ + SDL_PixelFormat * fmt=screen->format; + Colour result; + + Uint8 temp; + + //Get red + temp=from&fmt->Rmask; /* Isolate red component */ + temp=temp>>fmt->Rshift;/* Shift it down to 8-bit */ + temp=temp<Rloss; /* Expand to a full 8-bit number */ + result.r = (float)(temp); + + //Get green + temp=from&fmt->Gmask; /* Isolate red component */ + temp=temp>>fmt->Gshift;/* Shift it down to 8-bit */ + temp=temp<Gloss; /* Expand to a full 8-bit number */ + result.g = (float)(temp); + + //Get blue + temp=from&fmt->Bmask; /* Isolate red component */ + temp=temp>>fmt->Bshift;/* Shift it down to 8-bit */ + temp=temp<Bloss; /* Expand to a full 8-bit number */ + result.b = (float)(temp); + + //Get alpha + temp=from&fmt->Amask; /* Isolate red component */ + temp=temp>>fmt->Ashift;/* Shift it down to 8-bit */ + temp=temp<Aloss; /* Expand to a full 8-bit number */ + result.a = (float)(temp); + return result; +} + + + + + + diff --git a/manager/graphics.h b/manager/graphics.h new file mode 100644 index 0000000..f81bdb3 --- /dev/null +++ b/manager/graphics.h @@ -0,0 +1,148 @@ +#ifndef GRAPHICS_H +#define GRAPHICS_H + +#include +#include + + +#include +#include + + +typedef SDL_Surface Screen; +typedef SDL_Rect Rectangle; + + +typedef short unsigned int SUint; + +class Texture; +class Font; + + + +class Graphics +{ + public: + + class Colour + { + public: + Colour(float red=0, float green=0, float blue=0, float alpha=0) : r(red), g(green), b(blue), a(alpha) {} + Colour(const Colour & cpy) : r(cpy.r), g(cpy.g), b(cpy.b), a(cpy.a) {} + + Colour & operator=(const Colour & s) {r = s.r; g = s.g; b = s.b; a = s.a; return *this;} + bool operator==(const Colour & s) const {return (r == s.r && g == s.g && b == s.b && a == s.a);} + bool operator!=(const Colour & s) const {return !operator==(s);} + float r; + float g; + float b; + float a; + + }; + static int ScreenWidth() {return screenWidth;} + static int ScreenHeight() {return screenHeight;} + + static void Initialise(const char * caption, int width=640, int height=480); + static void Destroy(); + + static SDL_Surface * LoadTextureBMP(const char * file); + static void SaveTextureBMP(SDL_Surface * tex, const char * file); + + static void ClearScreen(); + static void UpdateScreen(); + static void DrawGrid(int gridWidth, int gridHeight, Colour colour); + static Uint8 MakeColour(int R, int G, int B, int Alpha = 0); + static Colour ConvertColour(Uint8 colour); + + static void DrawTexture(SDL_Surface * src, int destX, int destY, int srcX=0, int srcY=0, int width=-1, int height=-1); + static void DrawTexture(SDL_Surface * src, int destX, int destY, double rotate, double scale); + static void DrawPixel(int x, int y, Colour colour); + + static Colour GetPixel(int x, int y); + static void DrawLine(int x1, int y1, int x2, int y2, Colour colour, double scale=1.0); + static void DrawLineDashed(int x1, int y1, int x2, int y2, Colour colour, double scale=1.0); + static void DrawRectangle(int x1, int y1, int x2, int y2, Colour colour, double scale=1.0); + + static void GetColourData(SDL_Surface * src, std::vector * R, std::vector * G, std::vector * B, std::vector * A = NULL); + static void GetColourData(SDL_Surface * src, std::vector > * R, std::vector > * G, std::vector > * B, std::vector > * A = NULL); + + static void DrawColourData(int destX, int destY, std::vector * R, std::vector * G, std::vector * B, std::vector * A = NULL) {DrawColourData(screen, destX, destY, R, G, B, A);} + + static void DrawColourData(int destX, int destY, std::vector > * R, std::vector > * G, std::vector > * B, std::vector > * A = NULL) {DrawColourData(screen, destX, destY, R, G, B, A);} + + static SDL_Surface * TextureFromColours(std::vector * R, std::vector * G, std::vector * B, std::vector * A = NULL); + static SDL_Surface * TextureFromColours(std::vector > * R, std::vector > * G, std::vector > * B, std::vector > * A = NULL); + + static void Wait(int n) {SDL_Delay(n);} + + template + class TextureManager + { + public: + TextureManager() {} + virtual ~TextureManager() {} + + virtual Texture & operator[](const T & at) = 0; + }; + + static bool Initialised() {return initialised;} + + protected: + static void DrawColourData(SDL_Surface * dest, int destX, int destY, std::vector * R, std::vector * G, std::vector * B, std::vector * A = NULL); + static void DrawColourData(SDL_Surface * dest, int destX, int destY, std::vector > * R, std::vector > * G, std::vector > * B, std::vector > * A = NULL); + static void DrawTexture(SDL_Surface * dest, SDL_Surface * src, int srcX, int srcY, int destX, int destY, int width, int height); + static void DrawPixel(SDL_Surface * dest, int x, int y, Colour colour); + static Colour GetPixel(SDL_Surface * dest, int x, int y); + static void DrawLine(SDL_Surface * dest, int x1, int y1, int x2, int y2, Colour colour); + + private: + Graphics() {} + ~Graphics() {} + static std::list allTextures; + static Screen * screen; + + static int screenWidth; + static int screenHeight; + static bool initialised; + + +}; +typedef Graphics::Colour Colour; + +class Texture +{ + public: + Texture(const char * fileName, bool newDrawCentred = true); + virtual ~Texture(); + + void Draw(int x, int y, double angle=0, double scale=1); + void DrawColour(int x, int y, double angle, double scale, Colour colour); + + int width() const {return surface->w;} + int height() const {return surface->h;} + protected: + SDL_Surface * surface; + GLuint texture; + + private: + bool drawCentred; + +}; + +class Font : private Texture +{ + public: + Font(const char * fileName, int newWidth, int newHeight); + virtual ~Font(); + + void DrawTextColour(const char * string, int x, int y, double angle, double scale, Colour colour); + void DrawText(const char * string, int x, int y, double angle=0, double scale=1); + private: + int width; + int height; +}; + + +#endif //GRAPHICS_H + +//EOF diff --git a/manager/images/piece0.bmp b/manager/images/piece0.bmp new file mode 100644 index 0000000..bd67766 Binary files /dev/null and b/manager/images/piece0.bmp differ diff --git a/manager/images/piece1.bmp b/manager/images/piece1.bmp new file mode 100644 index 0000000..85fd5ba Binary files /dev/null and b/manager/images/piece1.bmp differ diff --git a/manager/images/piece10.bmp b/manager/images/piece10.bmp new file mode 100644 index 0000000..cfeda8a Binary files /dev/null and b/manager/images/piece10.bmp differ diff --git a/manager/images/piece11.bmp b/manager/images/piece11.bmp new file mode 100644 index 0000000..f4ebeb2 Binary files /dev/null and b/manager/images/piece11.bmp differ diff --git a/manager/images/piece12.bmp b/manager/images/piece12.bmp new file mode 100644 index 0000000..6c9544e Binary files /dev/null and b/manager/images/piece12.bmp differ diff --git a/manager/images/piece13.bmp b/manager/images/piece13.bmp new file mode 100644 index 0000000..0878912 Binary files /dev/null and b/manager/images/piece13.bmp differ diff --git a/manager/images/piece14.bmp b/manager/images/piece14.bmp new file mode 100644 index 0000000..3145270 Binary files /dev/null and b/manager/images/piece14.bmp differ diff --git a/manager/images/piece2.bmp b/manager/images/piece2.bmp new file mode 100644 index 0000000..ef4927b Binary files /dev/null and b/manager/images/piece2.bmp differ diff --git a/manager/images/piece3.bmp b/manager/images/piece3.bmp new file mode 100644 index 0000000..4209648 Binary files /dev/null and b/manager/images/piece3.bmp differ diff --git a/manager/images/piece4.bmp b/manager/images/piece4.bmp new file mode 100644 index 0000000..63161fb Binary files /dev/null and b/manager/images/piece4.bmp differ diff --git a/manager/images/piece5.bmp b/manager/images/piece5.bmp new file mode 100644 index 0000000..9163c12 Binary files /dev/null and b/manager/images/piece5.bmp differ diff --git a/manager/images/piece6.bmp b/manager/images/piece6.bmp new file mode 100644 index 0000000..321bf55 Binary files /dev/null and b/manager/images/piece6.bmp differ diff --git a/manager/images/piece7.bmp b/manager/images/piece7.bmp new file mode 100644 index 0000000..e690190 Binary files /dev/null and b/manager/images/piece7.bmp differ diff --git a/manager/images/piece8.bmp b/manager/images/piece8.bmp new file mode 100644 index 0000000..8ada911 Binary files /dev/null and b/manager/images/piece8.bmp differ diff --git a/manager/images/piece9.bmp b/manager/images/piece9.bmp new file mode 100644 index 0000000..e22c4b6 Binary files /dev/null and b/manager/images/piece9.bmp differ diff --git a/manager/main.cpp b/manager/main.cpp new file mode 100644 index 0000000..96569eb --- /dev/null +++ b/manager/main.cpp @@ -0,0 +1,224 @@ +#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; + +void cleanup(); +int main(int argc, char ** argv) +{ + assert(argc == 3); + + for (int y = 5; y < 9; ++y) + { + for (int x = 3; x < 5; ++x) + { + theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); + } + for (int x = 9; x < 11; ++x) + { + theBoard.AddPiece(x,y,Piece::BOULDER, Piece::NONE); + } + } + + red = new Controller(Piece::RED, argv[1]); + blue = new Controller(Piece::BLUE, argv[2]); + atexit(cleanup); + + Board::MovementResult redSetup = red->Setup(argv[2]); + Board::MovementResult blueSetup = blue->Setup(argv[1]); + if (redSetup != Board::OK) + { + fprintf(stderr, "Blue wins by DEFAULT!\n"); + red->SendMessage("ILLEGAL"); + blue->SendMessage("DEFAULT"); + exit(EXIT_SUCCESS); + } + if (blueSetup != Board::OK) + { + fprintf(stderr, "Red wins by DEFAULT!\n"); + red->SendMessage("DEFAULT"); + blue->SendMessage("ILLEGAL"); + exit(EXIT_SUCCESS); + } + + Board::MovementResult result = Board::OK; + system("clear"); + int count = 1; + + #ifdef GRAPHICS + if (!Graphics::Initialised()) + Graphics::Initialise("Stratego", theBoard.Width()*32, theBoard.Height()*32); + + #endif //GRAPHICS + + string buffer; + + red->SendMessage("START"); + Colour turn = Piece::RED; + while (Board::LegalResult(result)) + { + + fprintf(stderr, "This is move %d...\n", count); + fprintf(stderr,"---RED's turn---\n"); + turn = Piece::RED; + result = red->MakeMove(buffer); + red->SendMessage(buffer); + blue->SendMessage(buffer); + + if (!Board::LegalResult(result)) + break; + #ifdef GRAPHICS + Board::theBoard.Draw(); + if (CheckForQuitWhilstWaiting(0.2)) + { + red->SendMessage("QUIT"); + blue->SendMessage("QUIT"); + exit(EXIT_SUCCESS); + } + #endif //GRAPHICS + fprintf(stderr,"---BLUE's turn---\n"); + turn = Piece::BLUE; + result = blue->MakeMove(buffer); + blue->SendMessage(buffer); + red->SendMessage(buffer); + + if (!Board::LegalResult(result)) + break; + + + + #ifdef GRAPHICS + Board::theBoard.Draw(); + if (CheckForQuitWhilstWaiting(0.2)) + { + red->SendMessage("QUIT"); + blue->SendMessage("QUIT"); + exit(EXIT_SUCCESS); + } + #else + Board::theBoard.Print(stderr); + sleep(1); + system("clear"); + #endif //GRAPHICS + + ++count; + } + + 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) + { + case Board::NO_BOARD: + fprintf(stderr,"Board does not exit?!\n"); + break; + case Board::INVALID_POSITION: + fprintf(stderr,"Coords outside board\n"); + break; + case Board::NO_SELECTION: + fprintf(stderr,"Move does not select a piece\n"); + break; + case Board::NOT_YOUR_UNIT: + fprintf(stderr,"Selected piece belongs to other player\n"); + break; + case Board::IMMOBILE_UNIT: + fprintf(stderr,"Selected piece is not mobile (FLAG or BOMB)\n"); + break; + case Board::INVALID_DIRECTION: + fprintf(stderr,"Selected unit cannot move that way\n"); + break; + case Board::POSITION_FULL: + fprintf(stderr,"Attempted move into square occupied by allied piece\n"); + break; + case Board::VICTORY: + fprintf(stderr,"Captured the flag\n"); + break; + case Board::BAD_RESPONSE: + fprintf(stderr,"Unintelligable response\n"); + break; + case Board::NO_MOVE: + fprintf(stderr,"Did not make a move (may have exited)\n"); + 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; +} + +#endif //GRAPHICS diff --git a/manager/program.cpp b/manager/program.cpp new file mode 100644 index 0000000..ea5304c --- /dev/null +++ b/manager/program.cpp @@ -0,0 +1,163 @@ +#include + +#include + +#include + +#include "thread_util.h" +#include "program.h" + + +using namespace std; + +/** + * Constructor + * @param executablePath - path to the program that will be run + * + * Creates two pipes - one for each direction between the parent process and the AI program + * Forks the process. + * The child process closes unused sides of the pipe, and then calls exec to replace itself with the AI program + * The parent process closes unused sides of the pipe, and sets up member variables - associates streams with the pipe fd's for convenience. + */ +Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0) +{ + int readPipe[2]; int writePipe[2]; + assert(pipe(readPipe) == 0); + assert(pipe(writePipe) == 0); + + pid = fork(); + if (pid == 0) + { + close(readPipe[0]); //close end that parent reads from + close(writePipe[1]); //close end that parent writes to + + //TODO: Fix possible bug here if the process is already a daemon + assert(writePipe[0] != 0 && readPipe[1] != 1); + dup2(writePipe[0],0); close(writePipe[0]); //pipe end child reads from goes to STDIN + dup2(readPipe[1], 1); close(readPipe[1]); //pipe end child writes to goes to STDOUT + + //TODO: Somehow force the exec'd process to be unbuffered + setbuf(stdin, NULL); //WARNING: These lines don't appear to have any affect + setbuf(stdout, NULL); //You should add them at the start of the wrapped program. + //If your wrapped program is not written in C/C++, you will probably have a problem + + + + execl(executablePath, executablePath, (char*)(NULL)); ///Replace process with desired executable + fprintf(stderr, "Program::Program - Could not run program \"%s\"!\n", executablePath); + exit(EXIT_FAILURE); //We will probably have to terminate the whole program if this happens + } + else + { + close(writePipe[0]); //close end that child writes to + close(readPipe[1]); //close end that child reads from + + input = fdopen(readPipe[0],"r"); output = fdopen(writePipe[1],"w"); + setbuf(input, NULL); + setbuf(output, NULL); + } + +} + +/** + * Destructor + * Writes EOF to the wrapped program and then closes all streams + * Kills the wrapped program if it does not exit within 1 second. + */ +Program::~Program() +{ + if (kill(pid, 0) == 0) //Check if the process created is still running... + { + fputc(EOF, output); //If it was, tell it to stop with EOF + sleep(1); //Give it 1 second to respond... + if (kill(pid, 0) == 0) //Check if its still running + { + kill(pid, 9); //Slay the infidel mercilessly! + } + } + fclose(input); + fclose(output); + +} + + + + + +/** + * Sends a message to the wrapped AI program + * WARNING: Always prints a new line after the message (so don't include a new line) + * This is because everything is always line buffered. + */ +bool Program::SendMessage(const char * print, ...) +{ + if (kill(pid, 0) != 0) //Is the process running... + return false; + + va_list ap; + va_start(ap, print); + + vfprintf(output, print, ap); + fprintf(output, "\n"); + + + + va_end(ap); + + return true; +} + + +/** + * Retrieves a message from the wrapped AI program, waiting a maximum amount of time + * @param buffer - C++ string to store the resultant message in + * @param timeout - Maximum amount of time to wait before failure. If timeout <= 0, then GetMessage will wait indefinately. + * @returns true if the response was recieved within the specified time, false if it was not, or an EOF was recieved, or the process was not running. + */ +bool Program::GetMessage(string & buffer, double timeout) +{ + if (kill(pid, 0) != 0) + return false; + + assert(&buffer != NULL); + GetterThread getterThread(input, buffer); + assert(&(getterThread.buffer) != NULL); + TimerThread timerThread(timeout*1000000); + + getterThread.Start(); + if (timeout > 0) + timerThread.Start(); + + + while (!getterThread.Finished()) + { + if (timeout > 0 && timerThread.Finished()) + { + getterThread.Stop(); + timerThread.Stop(); + return false; + } + } + + getterThread.Stop(); + timerThread.Stop(); + + + + if (buffer.size() == 1 && buffer[0] == EOF) + return false; + return true; + + +} + +/** + * Returns true iff the process is running + * @returns what I just said, fool + */ +bool Program::Running() const +{ + return (kill(pid,0) == 0); +} + + diff --git a/manager/program.h b/manager/program.h new file mode 100644 index 0000000..406d954 --- /dev/null +++ b/manager/program.h @@ -0,0 +1,37 @@ +#ifndef PROGRAM_H +#define PROGRAM_H + +#include "thread_util.h" + +#include + +/** + * A wrapping class for an external program, which can exchange messages with the current process through stdin/stdout + * Useful for attaching control of an operation to an external process - for example, AI for a game + */ +class Program +{ + public: + Program(const char * executeablePath); //Constructor + virtual ~Program(); //Destructor + + + + + bool SendMessage(const char * print, ...); //Sends a formated message (NOTE: Prints a newline) + bool SendMessage(const std::string & buffer) {return SendMessage(buffer.c_str());} //Sends a C++ string message + bool GetMessage(std::string & buffer, double timeout=-1); //Retrieves a message, or waits for a timeout (if positive) + + bool Running() const; + + protected: + FILE * input; //Stream used for sending information TO the process + FILE * output; //Stream used for retrieving information FROM the process + + private: + pid_t pid; //Process ID of the program wrapped +}; + +#endif //PROGRAM_H + + diff --git a/manager/stratego.cpp b/manager/stratego.cpp new file mode 100644 index 0000000..8ffad72 --- /dev/null +++ b/manager/stratego.cpp @@ -0,0 +1,361 @@ +#include "common.h" + +#include "stratego.h" + +using namespace std; + +/** + * Static variables + */ +Board Board::theBoard(14,14); +//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','?'}; +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 + + +#ifdef GRAPHICS + +Piece::TextureManager::~TextureManager() +{ + Array::Iterator i(*this); + while (i.Good()) + { + delete (*i); + ++i; + } +} + +Texture & Piece::TextureManager::operator[](const LUint & at) +{ + while (Array::Size() <= at) + { + char buffer[BUFSIZ]; + sprintf(buffer, "images/piece%lu.bmp", Array::Size()); + Array::Add(new Texture(buffer, false)); + + } + return *(Array::operator[](at)); +} +#endif //GRAPHICS + +/** + * Gets the type of a piece, based off a character token + * @param fromToken - character identifying the piece + * @returns The type of the piece + */ +Piece::Type Piece::GetType(char fromToken) +{ + for (int ii=0; ii <= (int)(Piece::BOMB); ++ii) + { + if (tokens[ii] == fromToken) + { + return Type(Piece::NOTHING + ii); + } + } + return Piece::BOULDER; +} + +/** + * Construct a new, empty board + * @param newWidth - the width of the board + * @param newHeight - the height of the board + */ +Board::Board(int newWidth, int newHeight) : winner(Piece::NONE), width(newWidth), height(newHeight), board(NULL) +{ + board = new Piece**[width]; + for (int x=0; x < width; ++x) + { + board[x] = new Piece*[height]; + for (int y=0; y < height; ++y) + board[x][y] = NULL; + } +} + +/** + * Cleanup a board + */ +Board::~Board() +{ + for (int x=0; x < width; ++x) + { + for (int y=0; y < height; ++y) + delete board[x][y]; + delete [] board[x]; + } +} + +/** + * Print textual representation of the board to a stream + * @param stream - the stream to print information to + * @param reveal - Pieces matching this colour will have their identify revealed, other pieces will be shown as '#' or '*' for RED or BLUE respectively. + */ +void Board::Print(FILE * stream, const Piece::Colour & reveal) +{ + for (int y=0; y < height; ++y) + { + for (int x=0; x < width; ++x) + { + Piece * piece = board[x][y]; + if (piece == NULL) + { + fprintf(stream, "."); + } + else if (piece->colour != Piece::NONE && (piece->colour == reveal || reveal == Piece::BOTH)) + { + fprintf(stream, "%c", Piece::tokens[piece->type]); + } + else + { + switch (piece->colour) + { + case Piece::RED: + fprintf(stream, "#"); + break; + case Piece::BLUE: + fprintf(stream, "*"); + break; + case Piece::NONE: + fprintf(stream, "+"); + break; + case Piece::BOTH: + fprintf(stream, "$"); //Should never see these! + break; + } + } + } + fprintf(stream, "\n"); + } + +} + + +#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. + */ +void Board::Draw(const Piece::Colour & reveal) +{ + if (!Graphics::Initialised()) + { + Graphics::Initialise("Stratego", width*32, height*32); + } + + Graphics::ClearScreen(); + + for (int y=0; y < height; ++y) + { + for (int x=0; x < width; ++x) + { + Piece * piece = board[x][y]; + if (piece == NULL) + { + //Don't display anything + + } + else if (piece->colour != Piece::NONE && (piece->colour == reveal || reveal == Piece::BOTH)) + { + //Display the piece + Piece::textures[(int)(piece->type)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); + + } + else + { + switch (piece->colour) + { + case Piece::RED: + Piece::textures[(int)(Piece::BOULDER)].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)); + break; + case Piece::NONE: + Piece::textures[(int)(Piece::BOULDER)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); + break; + case Piece::BOTH: + Piece::textures[(int)(Piece::BOULDER)].DrawColour(x*32,y*32,0,1, Piece::GetGraphicsColour(piece->colour)); + break; + } + } + } + + } + Graphics::UpdateScreen(); + +} +#endif //GRAPHICS + +/** + * Adds a piece to the board + * @param x - x-coord to place the piece at, starting at zero, must be less than board width + * @param y - y-coord to place the piece at, starting at zero, must be less than board height + * @param newType - the Type of the piece + * @param newColour - the Colour of the piece + * @returns true if and only if the piece could be successfully added. + */ +bool Board::AddPiece(int x, int y, const Piece::Type & newType, const Piece::Colour & newColour) +{ + if (board == NULL || x < 0 || y < 0 || x >= width || y >= width || board[x][y] != NULL) + return false; + + Piece * piece = new Piece(newType, newColour); + board[x][y] = piece; + return true; +} + +/** + * Gets a pointer to a piece at a board location + * UNUSED + * @param x - x-coord of the piece + * @param y - y-coord of the piece + * @returns pointer to the piece, or NULL if the board location was empty + * @throws error if board is null or coords are invalid + */ +Piece * Board::GetPiece(int x, int y) +{ + assert(board != NULL); + assert(x >= 0 && x < width && y >= 0 && y < height); + return board[x][y]; +} + +/** + * Moves a piece at a specified position in the specified direction, handles combat if necessary + * @param x - x-coord of the piece + * @param y - y-coord of the piece + * @param direction - Direction in which to move (UP, DOWN, LEFT or RIGHT) + * @param colour - Colour which the piece must match for the move to be valid + * @returns A MovementResult which indicates the result of the move - OK is good, VICTORY means that a flag was captured, anything else is an error + */ +Board::MovementResult Board::MovePiece(int x, int y, const Direction & direction, int multiplier,const Piece::Colour & colour) +{ + if (board == NULL) + { + return NO_BOARD; + } + if (!(x >= 0 && x < width && y >= 0 && y < height)) + { + return INVALID_POSITION; + } + Piece * target = board[x][y]; + if (target == NULL) + { + return NO_SELECTION; + } + if (!(colour == Piece::NONE || target->colour == colour)) + { + return NOT_YOUR_UNIT; + } + if (target->type == Piece::FLAG || target->type == Piece::BOMB || target->type == Piece::BOULDER) + { + return IMMOBILE_UNIT; + } + if (multiplier > 1 && target->type != Piece::SCOUT) + { + return INVALID_DIRECTION; //Can only move a scout multiple times. + } + int x2 = x; int y2 = y; + + for (int ii=0; ii < multiplier; ++ii) + { + switch (direction) + { + case UP: + --y2; + break; + case DOWN: + ++y2; + break; + case LEFT: + --x2; + break; + case RIGHT: + ++x2; + break; + } + if (!(x2 >= 0 && x2 < width && y2 >= 0 && y2 < height)) + { + return INVALID_DIRECTION; + } + if (ii < multiplier-1 && board[x2][y2] != NULL) + { + return POSITION_FULL; + } + } + Piece * defender = board[x2][y2]; + if (defender == NULL) + { + board[x][y] = NULL; + board[x2][y2] = target; + } + else if (defender->colour != target->colour) + { + if (defender->colour == Piece::NONE) + { + return POSITION_FULL; + } + if (defender->type == Piece::FLAG) + { + winner = target->colour; + return VICTORY; + } + else if (defender->type == Piece::BOMB) + { + if (target->type == Piece::MINER) + { + delete defender; + board[x][y] = NULL; + board[x2][y2] = target; + return KILLS; + } + else + { + delete defender; + delete target; + board[x][y] = NULL; + board[x2][y2] = NULL; + return BOTH_DIE; + } + } + else if (defender->type == Piece::MARSHAL && target->type == Piece::SPY) + { + delete defender; + board[x][y] = NULL; + board[x2][y2] = target; + return KILLS; + } + else if (target->operator > (*defender)) + { + delete defender; + board[x][y] = NULL; + board[x2][y2] = target; + return KILLS; + } + else if (target->operator==(*defender) && rand() % 2 == 0) + { + delete defender; + board[x][y] = NULL; + board[x2][y2] = target; + return KILLS; + } + else + { + delete target; + board[x][y] = NULL; + return DIES; + } + } + else + { + return POSITION_FULL; + } + return OK; +} + + + diff --git a/manager/stratego.h b/manager/stratego.h new file mode 100644 index 0000000..877af2d --- /dev/null +++ b/manager/stratego.h @@ -0,0 +1,140 @@ +#ifndef STRATEGO_H +#define STRATEGO_H + +#include +#include + +#include + +#ifdef GRAPHICS + #include "graphics.h" + #include "array.h" +#endif //GRAPHICS + +/** + * Contains classes for a game of Stratego + */ + + +/** + * Class for a game piece + */ +class Piece +{ + public: + typedef enum {ERROR=14,BOMB=13,MARSHAL=12, GENERAL=11, COLONEL=10, MAJOR=9, CAPTAIN=8, LIEUTENANT=7, SERGEANT=6, MINER=5, SCOUT=4, SPY=3, FLAG=2,BOULDER=1, NOTHING=0} Type; //Type basically defines how strong the piece is + + + typedef enum {RED=0, BLUE=1, NONE=2, BOTH=3} Colour; //Used for the allegiance of the pieces - terrain counts as NONE. + + Piece(const Type & newType, const Colour & newColour) : type(newType), colour(newColour) {} + virtual ~Piece() {} + + + //Operators compare the strength of two pieces + bool operator==(const Piece & equ) const {return type == equ.type;} + bool operator<(const Piece & equ) const {return type < equ.type;} + bool operator>(const Piece & equ) const {return type > equ.type;} + + bool operator!=(const Piece & equ) const {return !operator==(equ);} + bool operator<=(const Piece & equ) const {return (operator<(equ) || operator==(equ));} + bool operator>=(const Piece & equ) const {return (operator>(equ) || operator==(equ));} + + //Contains the characters used to identify piece types when the board is printed to a stream + static char tokens[]; + static int maxUnits[]; + + static Type GetType(char fromToken); + + + //Attributes of the piece + const Type type; + const Colour colour; + + #ifdef GRAPHICS + public: + + class TextureManager : public Graphics::TextureManager, private Array + { + public: + TextureManager() : Graphics::TextureManager(), Array() {} + virtual ~TextureManager(); + + virtual Texture & operator[](const LUint & at); + }; + static TextureManager textures; + + static Graphics::Colour GetGraphicsColour(const Piece::Colour & colour) + { + switch (colour) + { + case RED: + return Graphics::Colour(1,0,0); + break; + case BLUE: + return Graphics::Colour(0,0,1); + break; + case NONE: + return Graphics::Colour(0.5,0.5,0.5); + break; + case BOTH: + return Graphics::Colour(1,0,1); + break; + } + } + + + + #endif //GRAPHICS + +}; + +/** + * A Stratego board + */ +class Board +{ + public: + Board(int newWidth, int newHeight); //Constructor + + 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 + + bool AddPiece(int x, int y, const Piece::Type & newType, const Piece::Colour & newColour); //Add piece to board + + + Piece * GetPiece(int x, int y); //Retrieve piece from a location on the board + + + typedef enum {UP, DOWN, LEFT, RIGHT} Direction; + + typedef enum {OK, DIES, KILLS, BOTH_DIE, NO_BOARD, INVALID_POSITION, NO_SELECTION, NOT_YOUR_UNIT, IMMOBILE_UNIT, INVALID_DIRECTION, POSITION_FULL, VICTORY, BAD_RESPONSE, NO_MOVE} MovementResult; //The possible results from a move + //WARNING: Some of the MovementResults are returned by the Controller class in "controller.h", in Controller::MakeMove + + static bool LegalResult(const MovementResult & result) + { + return (result == OK || result == DIES || result == KILLS || result == BOTH_DIE); + } + + 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; + + int Width() const {return width;} + int Height() const {return height;} + private: + int width; + int height; + Piece ** * board; +}; + +#endif //STRATEGO_H + +//EOF + + diff --git a/manager/thread_util.cpp b/manager/thread_util.cpp new file mode 100644 index 0000000..2d12438 --- /dev/null +++ b/manager/thread_util.cpp @@ -0,0 +1,47 @@ +#include "thread_util.h" + +#include +#include + +using namespace std; + +pthread_mutex_t GetterThread::mutex = PTHREAD_MUTEX_INITIALIZER; + +void * GetterThread::GetMessage(void * p) +{ + + GetterThread * getter = (GetterThread*)(p); + + stringstream inputStream; + + char s = fgetc(getter->stream); + while (s != '\n' && s != EOF) + { + + inputStream << s; + s = fgetc(getter->stream); + } + if (s == EOF) + { + getter->buffer = ""; + getter->buffer += s; + return NULL; + } + + pthread_mutex_lock(&mutex); + getter->buffer = inputStream.str(); + pthread_mutex_unlock(&mutex); + + getter->finished = true; + + return NULL; +} + +void * TimerThread::Timeout(void * p) +{ + TimerThread * timer = (TimerThread*)(p); + usleep(timer->count); + timer->finished = true; + return NULL; +} + diff --git a/manager/thread_util.h b/manager/thread_util.h new file mode 100644 index 0000000..95a16e0 --- /dev/null +++ b/manager/thread_util.h @@ -0,0 +1,115 @@ +#ifndef THREAD_UTIL_H +#define THREAD_UTIL_H + +#include +#include + +#include +#include +#include //Needed for threading +#include //Needed for killing the threads (why not in pthread.h?) + +#include + + +class Thread +{ + public: + Thread() : finished(false), thread(0), started(false) + { + + } + + virtual void Start() = 0; + protected: + void Start(void* (ThreadFunction)(void*)) + { + assert(!started); + started = true; + pthread_create(&(thread), NULL, ThreadFunction, (void*)(this)); + pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + } + + public: + void Stop() + { + assert(started); + if (!finished) + pthread_cancel(thread); + + pthread_join(thread, NULL); + started = false; + } + + virtual ~Thread() + { + if (started) + Stop(); + } + + bool Finished() const {return finished;} + protected: + + bool finished; + + private: + pthread_t thread; + protected: + bool started; +}; + +class GetterThread : public Thread +{ + public: + GetterThread(FILE * newStream, std::string & newBuffer) : Thread(), stream(newStream), buffer(newBuffer) + { + + } + + virtual ~GetterThread() + { + + } + + virtual void Start() {assert(&buffer != NULL); Thread::Start(GetMessage);} + + private: + FILE * stream; + public: + std::string & buffer; + + pthread_t thread; + static pthread_mutex_t mutex; + static void * GetMessage(void * p); + +}; + + +class TimerThread : public Thread +{ + public: + TimerThread(int newCount) : Thread(), count(newCount) + { + + } + + virtual ~TimerThread() + { + + } + + virtual void Start() {Thread::Start(Timeout);} + + private: + int count; + + static void * Timeout(void * p); + +}; + +#endif //THREAD_UTIL_H + +//EOF + + + diff --git a/samples/Makefile b/samples/Makefile new file mode 100644 index 0000000..5a05c00 --- /dev/null +++ b/samples/Makefile @@ -0,0 +1,17 @@ +#Makefile for the sample AI programs for UCC progcomp 2012 + +CPP = g++ -Wall -pedantic -lSDL -lGL -g + +dummy : dummy.o + $(CPP) -o dummy dummy.o + +clean : + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + +clean_full: #cleans up all backup files + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + $(RM) *.*~ + $(RM) *~ + + + diff --git a/samples/README b/samples/README new file mode 100644 index 0000000..446e850 --- /dev/null +++ b/samples/README @@ -0,0 +1 @@ +This directory contains sample AI programs. Currently they all suck. diff --git a/samples/dummy.cpp b/samples/dummy.cpp new file mode 100644 index 0000000..4f9c33f --- /dev/null +++ b/samples/dummy.cpp @@ -0,0 +1,176 @@ +#include +#include + +#include +#include +#include +#include +#include +using namespace std; + +/** + * A suitably terrible program which combines C style IO with C++ style IO + * Enjoy! + * Mwuhahaha + */ + +int main(int argc, char ** argv) +{ + setbuf(stdout, NULL); + setbuf(stdin, NULL); + + + //Read in the colour, and choose a layout + + int width = 14; int height = 14; + + string colour; string opponent; + cin >> colour; cin >> opponent; cin >> width; cin >> height; + fgetc(stdin); + + //fprintf(stderr, "Colour is \"%s\", width and height are (%d, %d), opponent is \"%s\"\n", colour.c_str(), width, height, opponent.c_str()); + + assert(width == 14 && height == 14); //Can't deal with other sized boards + if (colour == "RED") + { + fprintf(stdout, "FB..........B.\n"); + fprintf(stdout, "BBCM....cccc.C\n"); + fprintf(stdout, "LSGmnsBmSsnsSm\n"); + fprintf(stdout, "sLSBLnLssssnyn\n"); + } + else if (colour == "BLUE") + { + fprintf(stdout, "sLSBLnLssssnyn\n"); + fprintf(stdout, "LSGmnsBmSsnsSm\n"); + fprintf(stdout, "BBCM....cccc.C\n"); + fprintf(stdout, "FB..........B.\n"); + } + else + { + return 1; + } + + + + char board[width][height]; + + vector > choices; + + int myPid = (int)(getpid()); + + while (true) + { + //fprintf(stderr, "%s [%d] looping\n", argv[0], myPid); + choices.clear(); + + // fprintf(stderr, "%s Waiting for status line...\n", colour.c_str()); + while (fgetc(stdin) != '\n') + { + //fprintf(stderr,"."); + } + //fprintf(stderr, "%s Got status, waiting for board line...\n", colour.c_str()); + + //Read in board + for (int y=0; y < height; ++y) + { + for (int x=0; x < width; ++x) + { + board[x][y] = fgetc(stdin); + if (board[x][y] == EOF) + exit(EXIT_SUCCESS); + + if (board[x][y] != '.' && board[x][y] != '*' && board[x][y] != '#' && board[x][y] != '+') + { + choices.push_back(pair(x, y)); + } + } + assert(fgetc(stdin) == '\n'); + } + + + + int dir = 0; int startDir = 0; int choice = rand() % choices.size(); int startChoice = choice; + int x1 = 0; int y1 = 0; + do + { + + + pair pear = choices[choice]; + x1 = pear.first; + y1 = pear.second; + //fprintf(stderr,"Trying unit at %d %d...\n", x1, y1); + + if (board[x1][y1] == 'B' || board[x1][y1] == 'F') + { + choice = (choice+1) % choices.size(); + continue; + } + + int x2 = x1; + int y2 = y1; + dir = rand() % 4; startDir = dir; int lastDir = dir; + + bool okay = false; + while (!okay) + { + //fprintf(stderr," Trying direction %d...\n", dir); + x2 = x1; y2 = y1; + switch (dir) + { + case 0: + --y2; + break; + case 1: + ++y2; + break; + case 2: + --x2; + break; + case 3: + ++x2; + break; + } + + okay = !(x2 < 0 || y2 < 0 || x2 >= width || y2 >= height || (board[x2][y2] != '.' && board[x2][y2] != '*' && board[x2][y2] != '#')); + if (!okay) + { + dir = (dir+1) % 4; + if (dir == startDir) + break; + } + + } + + + + choice = (choice+1) % choices.size(); + if (dir != startDir) + break; + } + while (choice != startChoice); + + + string direction=""; + switch (dir) + { + case 0: + direction = "UP"; + break; + case 1: + direction = "DOWN"; + break; + case 2: + direction = "LEFT"; + break; + case 3: + direction = "RIGHT"; + break; + } + printf("%d %d %s\n", x1, y1, direction.c_str()); + //fprintf(stderr,"%s Made move, waiting for confirmation line\n", colour.c_str()); + while (fgetc(stdin) != '\n'); //Read in result line + //fprintf(stderr, "%s Done turn\n", colour.c_str()); + //fprintf(stderr,"%s - %d %d %s\n",colour.c_str(), x1, y1, direction.c_str() ); + //fprintf(stderr, "%s [%d] computed move\n", argv[0], myPid); + } +} diff --git a/samples/forfax/Makefile b/samples/forfax/Makefile new file mode 100644 index 0000000..10fa915 --- /dev/null +++ b/samples/forfax/Makefile @@ -0,0 +1,25 @@ +#Makefile for Forfax + +CPP = g++ -Wall -pedantic -lSDL -lGL -g +OBJ = main.o forfax.o + +BIN = forfax + + + +$(BIN) : $(OBJ) + $(CPP) -o $(BIN) $(OBJ) + + + + +%.o : %.cpp %.h + $(CPP) -c $< + +clean : + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + +clean_full: #cleans up all backup files + $(RM) $(BIN) $(OBJ) $(LINKOBJ) + $(RM) *.*~ + $(RM) *~ diff --git a/samples/forfax/forfax.cpp b/samples/forfax/forfax.cpp new file mode 100644 index 0000000..e27af9f --- /dev/null +++ b/samples/forfax/forfax.cpp @@ -0,0 +1,699 @@ +#include "forfax.h" + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace std; + +/** + * Static variables + */ + +//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','?'}; +int Piece::maxUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; + + +int Board::redUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; +int Board::blueUnits[] = {0,0,1,1,8,5,4,4,4,3,2,1,1,6,0}; + + +/** + * Constructor for a piece + * @param newX - x coord + * @param newY - y coord + * @param newColour - colour + */ +Piece::Piece(int newX, int newY,const Colour & newColour) + : x(newX), y(newY), colour(newColour), lastMove(0) +{ + minRank[RED] = Piece::FLAG; + minRank[BLUE] = Piece::FLAG; + maxRank[RED] = Piece::BOMB; + maxRank[BLUE] = Piece::BOMB; +} + +/** + * Constructor for a piece + * @param newX - x coord + * @param newY - y coord + * @param newColour - colour + * @param rankKnownBy - Colour that knows the piece's rank + * @param fixedRank - Rank the piece has + */ +Piece::Piece(int newX, int newY,const Colour & newColour, const Colour & rankKnownBy, const Type & fixedRank) + : x(newX), y(newY), colour(newColour), lastMove(0) +{ + if (rankKnownBy == BOTH) + { + minRank[RED] = fixedRank; + minRank[BLUE] = fixedRank; + maxRank[RED] = fixedRank; + maxRank[BLUE] = fixedRank; + } + else + { + minRank[rankKnownBy] = fixedRank; + maxRank[rankKnownBy] = fixedRank; + + Colour opposite = Opposite(rankKnownBy); + minRank[opposite] = Piece::FLAG; + maxRank[opposite] = Piece::BOMB; + + } + +} + + + + + + +/** + * Returns the Piece::Type matching a given character + * @param token - The character to match + * @returns A Piece::Type corresponding to the character, or Piece::ERROR if none was found + */ +Piece::Type Piece::GetType(char token) +{ + for (int ii=0; ii < Piece::ERROR; ++ii) + { + if (Piece::tokens[ii] == token) + return (Type)(ii); + } + return Piece::ERROR; +} + +/** + * Constructor for the board + * @param newWidth - width of the board + * @param newHeight - height of the board + * + */ +Board::Board(int newWidth, int newHeight) : width(newWidth), height(newHeight), board(NULL), red(), blue() +{ + board = new Piece**[width]; + for (int x=0; x < width; ++x) + { + board[x] = new Piece*[height]; + for (int y=0; y < height; ++y) + board[x][y] = NULL; + } +} + +/** + * Destroy the board + */ +Board::~Board() +{ + for (int x=0; x < width; ++x) + { + for (int y=0; y < height; ++y) + delete board[x][y]; + delete [] board[x]; + } +} + +/** + * Retrieve a piece from the board + * @param x - x coord of the piece + * @param y - y coord of the piece + * @returns Piece* to the piece found at (x,y), or NULL if there was no piece, or the coords were invalid + */ +Piece * Board::Get(int x, int y) const +{ + if (board == NULL || x < 0 || y < 0 || x > width || y > height) + return NULL; + return board[x][y]; +} + +/** + * Add a piece to the board + * Also updates the red or blue arrays if necessary + * @param x - x coord of the piece + * @param y - y coord of the piece + * @param newPiece - pointer to the piece to add + * @returns newPiece if the piece was successfully added, NULL if it was not + * + */ +Piece * Board::Set(int x, int y, Piece * newPiece) +{ + if (board == NULL || x < 0 || y < 0 || x > width || y > height) + return NULL; + board[x][y] = newPiece; + + //if (newPiece->GetColour() == Piece::RED) + // red.push_back(newPiece); + //else if (newPiece->GetColour() == Piece::BLUE) + // blue.push_back(newPiece); + + return newPiece; +} + + +/** + * Convert a string to a direction + * @param str - The string to convert to a direction + * @returns The equivelent Direction + */ +Board::Direction Board::StrToDir(const string & str) +{ + if (str == "UP") + return UP; + else if (str == "DOWN") + return DOWN; + else if (str == "LEFT") + return LEFT; + else if (str == "RIGHT") + return RIGHT; + + return NONE; +} + +/** + * Convert a Direction to a string + * @param dir - the Direction to convert + * @param str - A buffer string + */ +void Board::DirToStr(const Direction & dir, string & str) +{ + str.clear(); + switch (dir) + { + case UP: + str = "UP"; + break; + case DOWN: + str = "DOWN"; + break; + case LEFT: + str = "LEFT"; + break; + case RIGHT: + str = "RIGHT"; + break; + default: + str = "NONE"; + break; + } +} + +/** + * Moves the co-ords in the specified direction + * @param x - x coord + * @param y - y coord + * @param dir - Direction to move in + * @param multiplier - Number of times to move + * + */ +void Board::MoveInDirection(int & x, int & y, const Direction & dir, int multiplier) +{ + switch (dir) + { + case UP: + y -= multiplier; + break; + case DOWN: + y += multiplier; + break; + case LEFT: + x -= multiplier; + break; + case RIGHT: + x += multiplier; + break; + default: + break; + } +} + +/** + * Returns the best direction to move in to get from one point to another + * @param x1 - x coord of point 1 + * @param y1 - y coord of point 1 + * @param x2 - x coord of point 2 + * @param y2 - y coord of point 2 + * @returns The best direction to move in + */ +Board::Direction Board::DirectionBetween(int x1, int y1, int x2, int y2) +{ + + + double xDist = (x2 - x1); + double yDist = (y2 - y1); + if (abs(xDist) >= abs(yDist)) + { + if (xDist < 0) + return LEFT; + else + return RIGHT; + } + else + { + if (yDist < 0) + return UP; + else + return DOWN; + } + return NONE; + + + + +} + +/** + * Searches the board's red and blue arrays for the piece, and removes it + * DOES NOT delete the piece. Calling function should delete piece after calling this function. + * @param forget - The Piece to forget about + * @returns true if the piece was actually found + */ +bool Board::ForgetPiece(Piece * forget) +{ + if (forget == NULL) + return false; + + vector & in = GetPieces(forget->colour); bool result = false; + for (vector::iterator i=in.begin(); i != in.end(); ++i) + { + + if ((*i) == forget) + { + i = in.erase(i); + result = true; + + continue; + } + + + } + + + return result; +} + +/** + * Construct Forfax + */ +Forfax::Forfax() : board(NULL), colour(Piece::NONE), strColour("NONE"), turnNumber(0) +{ + for (int ii=0; ii <= Piece::BOMB; ++ii) + { + remainingUnits[ii][Piece::RED][Piece::RED] = Piece::maxUnits[ii]; + remainingUnits[ii][Piece::RED][Piece::BLUE] = Piece::maxUnits[ii]; + remainingUnits[ii][Piece::BLUE][Piece::RED] = Piece::maxUnits[ii]; + remainingUnits[ii][Piece::BLUE][Piece::BLUE] = Piece::maxUnits[ii]; + + + } +} + +/** + * Destroy Forfax + */ +Forfax::~Forfax() +{ + //fprintf(stderr,"Curse you mortal for casting me into the fires of hell!\n"); + //Errr... + if (board != NULL) + delete board; +} + +/** + * Calculate the probability that attacker beats defender in combat, from the point of view of a certain player + */ + +double Forfax::CombatSuccessChance(Piece * attacker, Piece * defender, const Piece::Colour & accordingTo) const +{ + double probability=1; + for (Piece::Type aRank = attacker->minRank[accordingTo]; aRank <= attacker->maxRank[accordingTo]; aRank = (Piece::Type)((int)(aRank) + 1)) + { + double lesserRanks; double greaterRanks; + for (Piece::Type dRank = defender->minRank[accordingTo]; dRank <= defender->maxRank[accordingTo]; dRank = (Piece::Type)((int)(dRank) + 1)) + { + if (dRank < aRank) + lesserRanks++; + else if (dRank > aRank) + greaterRanks++; + else + { + lesserRanks++; greaterRanks++; + } + } + probability *= lesserRanks/(lesserRanks + greaterRanks); + } + return probability; +} + +/** + * Calculate the base score of a move + * @param piece - The Piece to move + * @param dir - The direction in which to move + * @param accordingTo - the colour to use for assumptions + */ +double Forfax::MovementBaseScore(Piece * piece, const Board::Direction & dir, const Piece::Colour & accordingTo) const +{ + int x2 = piece->x; int y2 = piece->y; + Board::MoveInDirection(x2, y2, dir); + + if (board->Get(x2, y2) == NULL) + return 1; + else if (board->Get(x2, y2)->colour == piece->colour) + return 0; + else + return CombatSuccessChance(piece, board->Get(x2, y2), accordingTo); +} + +/** + * Calculate the total score of a move according to certain colour + * @param piece - the piece to move + * @param dir - the direction to move in + * @param accordingTo - the colour to use + */ +double Forfax::MovementTotalScore(Piece * piece, const Board::Direction & dir, const Piece::Colour & accordingTo) const +{ + double base = MovementBaseScore(piece, dir, accordingTo); + + if (base == 0) + return base; + + + int x = piece->x; int y = piece->y; + Board::MoveInDirection(x, y, dir); + Piece * old = board->Get(x, y); + board->Set(x, y, piece); + board->Set(piece->x, piece->y, NULL); + + list opponentMoves; + vector & enemies = board->GetPieces(Piece::Opposite(accordingTo)); + for (vector::iterator i = enemies.begin(); i != enemies.end(); ++i) + { + opponentMoves.push_back(MovementChoice((*i), Board::UP, *this,Piece::Opposite(accordingTo))); + opponentMoves.push_back(MovementChoice((*i), Board::DOWN, *this,Piece::Opposite(accordingTo))); + opponentMoves.push_back(MovementChoice((*i), Board::LEFT, *this,Piece::Opposite(accordingTo))); + opponentMoves.push_back(MovementChoice((*i), Board::RIGHT, *this,Piece::Opposite(accordingTo))); + } + + opponentMoves.sort(); + + + + MovementChoice & best = opponentMoves.back(); + + board->Set(x, y, old); + board->Set(piece->x, piece->y, piece); + + return base / best.score; + + + +} + + + +/** + * Forfax sets himself up + * Really should just make input and output stdin and stdout respectively, but whatever + */ +bool Forfax::Setup() +{ + //The first line is used to identify Forfax's colour, and the size of the board + //Currently the name of the opponent is ignored + + strColour.clear(); + string strOpponent; int boardWidth; int boardHeight; + + cin >> strColour; cin >> strOpponent; cin >> boardWidth; cin >> boardHeight; + if (cin.get() != '\n') + return false; + + if (strColour == "RED") + { + colour = Piece::RED; + cout << "FB..........B.\n"; + cout << "BBCM....cccc.C\n"; + cout << "LSGmnsBmSsnsSm\n"; + cout << "sLSBLnLssssnyn\n"; + } + else if (strColour == "BLUE") + { + colour = Piece::BLUE; + cout << "sLSBLnLssssnyn\n"; + cout << "LSGmnsBmSsnsSm\n"; + cout << "BBCM....cccc.C\n"; + cout << "FB..........B.\n"; + + + + + } + else + return false; + + + + board = new Board(boardWidth, boardHeight); + return (board != NULL); +} + +/** + * Forfax makes a move + * + */ +bool Forfax::MakeMove() +{ + ++turnNumber; + cerr << "Forfax " << strColour << " making move number " << turnNumber << "...\n"; + if (turnNumber == 1) + { + if (!MakeFirstMove()) + { + return false; + } + + } + else + { + if (!InterpretMove()) + { + + return false; + } + + + //Forfax ignores the board state; he only uses the move result lines + for (int y=0; y < board->Height(); ++y) + { + for (int x = 0; x < board->Width(); ++x) + cin.get(); + if (cin.get() != '\n') + return false; + } + } + + //Make move here + + list choices; + vector & allies = board->GetPieces(colour); + for (vector::iterator i = allies.begin(); i != allies.end(); ++i) + { + choices.push_back(MovementTotalChoice((*i), Board::UP, *this, colour)); + choices.push_back(MovementTotalChoice((*i), Board::DOWN, *this, colour)); + choices.push_back(MovementTotalChoice((*i), Board::LEFT, *this, colour)); + choices.push_back(MovementTotalChoice((*i), Board::RIGHT, *this, colour)); + + } + + MovementTotalChoice & choice = choices.back(); + + string direction; Board::DirToStr(choice.dir, direction); + cerr << "Forfax %s computed optimum move of " << choice.piece->x << " " << choice.piece->y << " " << direction << " [score=" << choice.score << "]\n"; + cout << choice.piece->x << " " << choice.piece->y << " " << direction << "\n"; + + + return InterpretMove(); + + +} + +bool Forfax::InterpretMove() +{ + int x; int y; string direction; string result; int multiplier = 1; + + cerr << "Forfax " << strColour << " waiting for movement information...\n"; + cin >> x; cin >> y; cin >> direction; cin >> result; + if (cin.peek() != '\n') + { + cerr << "Forfax " << strColour << " reading multiplier\n"; + stringstream s(result); + s >> multiplier; + result.clear(); + cin >> result; + } + if (cin.get() != '\n') + { + cerr << "Forfax " << strColour << " didn't recieve new line. Very angry.\n"; + cerr << "Read result so far: " << x << " " << y << " " << direction << " " << result << " ...\n"; + return false; + } + + cerr << "Forfax " << strColour << " interpreting movement result of " << x << " " << y << " " << direction << " " << result << " ...\n"; + + + int x2 = x; int y2 = y; + Board::Direction dir = Board::StrToDir(direction); + + Board::MoveInDirection(x2, y2, dir, multiplier); + + Piece * attacker = board->Get(x, y); + Piece * defender = board->Get(x2, y2); + + if (attacker == NULL) + return false; + + + Piece::Colour oppositeColour = Piece::Opposite(attacker->colour); + if (attacker->minRank[oppositeColour] == Piece::FLAG) + attacker->minRank[oppositeColour] = Piece::SPY; + if (attacker->maxRank[oppositeColour] == Piece::BOMB) + attacker->maxRank[oppositeColour] = Piece::MARSHAL; + if (multiplier > 1) + { + attacker->maxRank[oppositeColour] = Piece::SCOUT; + attacker->minRank[oppositeColour] = Piece::SCOUT; + } + + + + + + if (result == "OK") + { + if (defender != NULL) + return false; + board->Set(x2, y2, attacker); + board->Set(x, y, NULL); + attacker->x = x2; + attacker->y = y2; + } + else if (result == "KILLS") + { + if (defender == NULL || defender->colour == attacker->colour) + return false; + + + + + board->Set(x2, y2, attacker); + board->Set(x, y, NULL); + attacker->x = x2; + attacker->y = y2; + + if (attacker->minRank[oppositeColour] < defender->maxRank[oppositeColour]) + attacker->minRank[oppositeColour] = defender->maxRank[oppositeColour]; + + + if (!board->ForgetPiece(defender)) + return false; + delete defender; + + } + else if (result == "DIES") + { + + if (defender == NULL || defender->colour == attacker->colour) + return false; +cerr << "Forfax - Unit " << attacker << " dies \n"; + if (!board->ForgetPiece(attacker)) + return false; + delete attacker; + + board->Set(x, y, NULL); + } + else if (result == "BOTHDIE") + { + if (defender == NULL || defender->colour == attacker->colour) + return false; + if (board->ForgetPiece(attacker) == false) + return false; + if (board->ForgetPiece(defender) == false) + return false; + delete attacker; + delete defender; + board->Set(x2, y2, NULL); + board->Set(x, y, NULL); + } + else if (result == "VICTORY") + { + return false; + } + return true; +} + +/** + * First move only + * + */ +bool Forfax::MakeFirstMove() +{ + if (colour == Piece::RED) + { + string derp; + cin >> derp; + if (derp != "START") + return false; + if (cin.get() != '\n') + return false; + } + else + { + //TODO: Fix hack where BLUE ignores RED's first move + while (cin.get() != '\n'); + } + + for (int y=0; y < board->Height(); ++y) + { + for (int x = 0; x < board->Width(); ++x) + { + char c = cin.get(); + switch (c) + { + case '.': + break; + case '+': + board->Set(x, y, new Piece(x, y, Piece::NONE, Piece::BOTH, Piece::BOULDER)); + break; + case '#': + case '*': + { + Piece * toAdd = new Piece(x, y, Piece::Opposite(colour)); + board->Set(x, y, toAdd); + board->GetPieces(toAdd->colour).push_back(toAdd); + break; + } + default: + { + Piece::Type type = Piece::GetType(c); + Piece * toAdd = new Piece(x, y, colour, colour, type); + board->Set(x, y, toAdd); + board->GetPieces(toAdd->colour).push_back(toAdd); + break; + } + } + } + if (cin.get() != '\n') + return false; + } + + return true; +} + +//EOF + diff --git a/samples/forfax/forfax.h b/samples/forfax/forfax.h new file mode 100644 index 0000000..fe370a9 --- /dev/null +++ b/samples/forfax/forfax.h @@ -0,0 +1,159 @@ +#ifndef FORFAX_H +#define FORFAX_H + +#include //Uses C++ std::vectors to store pieces +#include //Uses C++ std::string +/** + * Header for the sample Stratego AI "forfax" + * @author Sam Moore 2011 + */ +class Board; +/** + * Class to represent a piece on the board + */ +class Piece +{ + public: + typedef enum {ERROR=14,BOMB=13,MARSHAL=12, GENERAL=11, COLONEL=10, MAJOR=9, CAPTAIN=8, LIEUTENANT=7, SERGEANT=6, MINER=5, SCOUT=4, SPY=3, FLAG=2,BOULDER=1, NOTHING=0} Type; //Type basically defines how strong the piece is + typedef enum {RED=0, BLUE=1, NONE, BOTH} Colour; //Used for the allegiance of the pieces - terrain counts as NONE. + + Piece(int newX, int newY,const Colour & newColour); + Piece(int newX, int newY,const Colour & newColour, const Colour & rankKnownBy, const Type & fixedRank); + virtual ~Piece() {} + + void SetCoords(int newX, int newY) {x = newX; y = newY;} + void GetCoords(int & storeX, int & storeY) const {storeX = x; storeY = y;} + const Colour & GetColour() const {return colour;} + + static char tokens[]; //The tokens used to identify various pieces + static int maxUnits[]; //The maximum allowed number of units of each piece + + static Type GetType(char fromToken); //Retrieves the type of a piece given its character token + static Colour Opposite(const Colour & colour) {return colour == RED ? BLUE : RED;} + + + int x; int y; + const Colour colour; //The colour of the piece + Type minRank[2]; //The minimum possible rank of the piece, according to each colour + Type maxRank[2]; //The maximum possible rank of the piece, according to each colour + int lastMove; + + + +}; + +/** + * Class to represent a board + */ +class Board +{ + public: + Board(int width, int height); + virtual ~Board(); + + + std::vector & GetPieces(const Piece::Colour & colour) {return colour == Piece::RED ? red : blue;} //retrieve array of pieces + + Piece * Get(int x, int y) const; //Retrieve single piece + Piece * Set(int x, int y, Piece * newPiece); //Add piece to board + + int Width() const {return width;} + int Height() const {return height;} + + typedef enum {UP=0, DOWN=1, LEFT=2, RIGHT=3, NONE=4} Direction; + static Direction StrToDir(const std::string & str); + static void DirToStr(const Direction & dir, std::string & buffer); + + static void MoveInDirection(int & x, int & y, const Direction & dir, int multiplier = 1); + static Direction DirectionBetween(int x1, int y1, int x2, int y2); + + + + static int redUnits[]; + static int blueUnits[]; + + + bool ForgetPiece(Piece * forget); //removes piece from the red and blue vectors + + private: + friend class Forfax; + + int width; + int height; + Piece ** * board; + + std::vector red; //Store all red pieces + std::vector blue; //Store all blue pieces + + +}; + +/** + * Small class to manage the Forfax AI + */ +class Forfax +{ + public: + Forfax(); + virtual ~Forfax(); + bool Setup(); //Waits for input to determine colour and board size, and then responds with setup + bool MakeMove(); //Should be called each turn - determines Forfax's move + + double CombatSuccessChance(Piece * attacker, Piece * defender, const Piece::Colour & accordingTo) const; + double MovementBaseScore(Piece * move, const Board::Direction & dir, const Piece::Colour & accordingTo) const; + double MovementTotalScore(Piece * move, const Board::Direction & dir, const Piece::Colour & accordingTo) const; + + protected: + bool MakeFirstMove(); //Should only be called on the first turn + bool InterpretMove(); + private: + Board * board; //Forfax stores the state on a board + Piece::Colour colour; //Forfax needs to know his colour + std::string strColour; //String of colour + int turnNumber; //Forfax needs to know what turn number it is + + int remainingUnits[14][2][2]; //Known remaining units, accessed by (type, colour, accordingTo) + +}; + +class MovementChoice +{ + public: + MovementChoice(Piece * newPiece, const Board::Direction & newDir, const Forfax & forfax, const Piece::Colour & accordingTo) : piece(newPiece), dir(newDir) + { + score = forfax.MovementBaseScore(piece, dir, accordingTo); + } + + MovementChoice(const MovementChoice & cpy) : piece(cpy.piece), dir(cpy.dir), score(cpy.score) + { + + } + + bool operator<(const MovementChoice & a) const {return score < a.score;} + bool operator>(const MovementChoice & a) const {return score > a.score;} + bool operator<=(const MovementChoice & a) const {return score <= a.score;} + bool operator>=(const MovementChoice & a) const {return score >= a.score;} + bool operator==(const MovementChoice & a) const {return score == a.score;} + bool operator!=(const MovementChoice & a) const {return score != a.score;} + + Piece * piece; + Board::Direction dir; + double score; + + +}; + +class MovementTotalChoice : public MovementChoice +{ + public: + MovementTotalChoice(Piece * newPiece, const Board::Direction & newDir, const Forfax & forfax, const Piece::Colour & accordingTo) : MovementChoice(newPiece, newDir, forfax, accordingTo) + { + score = score/(forfax.MovementTotalScore(piece, dir, Piece::Opposite(accordingTo))); + } +}; + +#endif //FORFAX_H + +//EOF + + diff --git a/samples/forfax/main.cpp b/samples/forfax/main.cpp new file mode 100644 index 0000000..d0cea95 --- /dev/null +++ b/samples/forfax/main.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include "forfax.h" + +using namespace std; + +#include + +Forfax forfax; +int main(int argc, char ** argv) +{ + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + if (!forfax.Setup()) + exit(EXIT_SUCCESS); + + while (forfax.MakeMove()); + + cerr << "Forfax threw a hissy fit, and exited!\n"; + + exit(EXIT_SUCCESS); + return 0; + + +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..25c2dae --- /dev/null +++ b/web/index.html @@ -0,0 +1,148 @@ + + + Stratego Based Programming Competition + + + + +

Quick Details

+

The git repository is listed on The UCC git page as "progcomp2012.git"

+

We will use the same mailing list as last year (progcomp).

+

+

There is a #progcomp irc channel on the ucc irc server where you can ask questions or help with setting things up.

+ +

Stratego

+

This site explains what Stratego is.

+

I have never played this game. But it sounds cool. So naturally I decided to make a competition based on it.

+

My original idea was to force people to write AI for Astral, but then I realised that that was a terrible idea.

+ +

There is in fact, already a World Stratego Championship. However, you have to use Metaforge. Using stdin/stdout will probably make it slightly easier for us. And its better to work out how to do these things ourselves.

+ +

Programming Competition

+

Create an AI to play Stratego.

+

Yes, I know people are inherently lazy and won't be bothered to do this. But so far in setting this up I have learn quite a lot about inter-process communication, so thats good.

+

Programs are written independently and interface through stdin/stdout with a manager program, which queries them on setup and moves.

+

The Manager Program

+

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

+ + +

Messaging Protocol and Rules

+

Setup

+

Query

+

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

+

COLOUR OPPONENT BOARD_WIDTH BOARD_HEIGHT

+

At the moment BOARD_WIDTH and BOARD_HEIGHT are always 14. The arguments are followed by a newline.

+

OPPONENT will be the identity of the opposing AI program.

+

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:

+

+ + + + + + + + + + + + + + + + +
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
Unoccupied .
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.

+ + +

Turns

+

Query

+

RED starts the game. The manager queries RED to make the first move by printing:

+

START

+

Followed by a WIDTH*HEIGHT grid. '*' or '#' characters are used to indicate positions of unknown (BLUE/RED) enemy units.

+ +

Response

+

The AI program must respond with a single line of the following form:

+

X Y DIRECTION [MULTIPLIER]

+

Where X and Y represent valid co-ordinates, upon which there is a piece of the AI program's colour.

+

DIRECTION must be either "UP", "DOWN", "LEFT", or "RIGHT", and is, obviously, the way the piece is supposed to move

+

MULTIPLIER is optional, and should only be given if the AI program is moving a Scout. Scouts may move multiple times in the same direction if possible.

+

+ +

Outcome

+

The manager program will indicate the result of a move by responding with:

+

X Y DIRECTION [MULTIPLIER] OUTCOME

+

Where X, Y, DIRECTION and MULTIPLIER are as above.

+

OUTCOME signals the result of the turn, and will be one of the following:

+ + + + + + + + +
OUTCOME Description
OK The piece was successfully moved, nothing eventful happened
FLAG The piece moved onto the opposing Flag; the game will end shortly
KILLS The piece landed on a square occupied by an enemy, and destroyed it, moving into the square
DIES The piece landed on a square occupied by an enemy, and was destroyed. The piece is removed from the board.
BOTHDIE The piece landed on a square occupied by an enemy, and both pieces were destroyed.
ILLEGAL The moving player attempted to make an illegal move, and has hence lost the game. The game will end shortly.
+ + +

Additional Turns

+

The Outcome of each turn is printed to both AI programs.

+

The state of the board is then printed to BLUE, who makes a move, then the process repeats.

+ +

+ +

Overview

+

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. After the AI program makes a move, the outcome is printed to it, and the other program, which continues from step 1
  6. +
+ +

End Game

+

Query

+

At the end of the game, the manager program outputs the following:

+

VICTORY

+

To the winning AI program, and

+

DEFEAT

+

To the losing program.

+

Both programs then have 2 seconds to exit succesfully, or the manager will kill them.

+

Invalid Responses and Timeouts

+

If any program fails to respond correctly, or gives an invalid move, or does not respond within 1 second, it will lose by default.

+

In this case, the message

+

ILLEGAL

+

will be sent to the malfunctioning program, and

+

DEFAULT

+

to the other program

+

Both programs then have 2 seconds to exit succesfully, or the manager will kill them.

+ +

Modifications/Clarifications to Rules

+

Refer to This site for the original rules again

+

Currently, the pieces taking part in the combat are not revealed; only the outcome of the combat is revealed. In a human game, the pieces would normally be revealed. I am planning to reveal the pieces, since not revealing pieces significantly reduces the value of low ranked pieces (normally used for working out enemy piece values).

+

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...

+ +

Longterm Scoring

+

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

+

I'll probably have a bash script that chooses who will play, and stores all the scores in files. I've done this before for a BrainF*** based "survival of the fittest" style thing, so I can probably recycle it

+

+

I believe it will make things more interesting if programs are allowed to keep state of who they have played, and the various tactics used.

+

Its kind of hard to implement this... at the moment programs are killed every time a game finishes

+

Perhaps it will be easier if each program is allowed access to one directory, where it can create and read files?

+

This would allow me to keep the manager program which actually plays the games seperate from the program for scoring and matching of opponents

+

There'd probably be security issues with that though.

+ + +