OpenBW / openbw

OpenBW - free and open-source Best Wargame
152 stars 24 forks source link

Use OpenBW for deep reinforcement learning #18

Open schroederdewitt opened 6 years ago

schroederdewitt commented 6 years ago

Hi,

I would like to use OpenBW for deep reinforcement learning. For this, I need

I am happy to contribute a Boost::Python3 binding for BWAPI myself, but BWAPI does not allow to extract game screenshots. My questions are as follows:

Thanks and regards

Christian Schroeder de Witt

N00byEdge commented 6 years ago

I guess there maybe could be a way to expose the draw buffer through the C++ interface. But maybe this should exist in the BWAPI interface instead, and let OpenBW fullfill that? I can imagine people would want to do the same thing on all BW platforms?

And yes, it will most certainly run faster than pysc2 as long as the marshalling doesn't completely destroy performance. This game is from 1998 when it even ran on mediocre PCs and this is even an optimized headless version of it.

Also, out of a developers' perspective, it should never be underestimated to write ML code in C++, even if it's easier to prototype in Python, which should most definitely be available.

schroederdewitt commented 6 years ago

Thanks for your response. I have had a look at the code, it seems that the getScreenBuffer() BWAPI method is not yet implemented. However, maybe it is possible to extract the GameScreenBuffer from the Game.h?

tscmoo commented 6 years ago

There is actually now a std::tuple<int, int, uint32_t*> drawGameScreen(int x, int y, int width, int height) function in Game (Broodwar->drawGameScreen). It returns a buffer (rgb data) along with the width and height of the buffer (could be slightly bigger than requested for memory alignment).

Here's some dirty example code to save a .bmp of some hardcoded position in the game:


  typedef int LONG;
  typedef unsigned char BYTE;
  typedef unsigned int DWORD;
  typedef unsigned short WORD;

  typedef struct tagBITMAPFILEHEADER
  {
      WORD    bfType; // 2  /* Magic identifier */
      DWORD   bfSize; // 4  /* File size in bytes */
      WORD    bfReserved1; // 2
      WORD    bfReserved2; // 2
      DWORD   bfOffBits; // 4 /* Offset to image data, bytes */
  } __attribute__((packed)) BITMAPFILEHEADER;

  typedef struct tagBITMAPINFOHEADER
  {
      DWORD    biSize; // 4 /* Header size in bytes */
      LONG     biWidth; // 4 /* Width of image */
      LONG     biHeight; // 4 /* Height of image */
      WORD     biPlanes; // 2 /* Number of colour planes */
      WORD     biBitCount; // 2 /* Bits per pixel */
      DWORD    biCompress; // 4 /* Compression type */
      DWORD    biSizeImage; // 4 /* Image size in bytes */
      LONG     biXPelsPerMeter; // 4
      LONG     biYPelsPerMeter; // 4 /* Pixels per meter */
      DWORD    biClrUsed; // 4 /* Number of colours */
      DWORD    biClrImportant; // 4 /* Important colours */
  } __attribute__((packed)) BITMAPINFOHEADER;

  auto save = [&](const char *filename, int width, int height, unsigned char *data) {
      BITMAPFILEHEADER bmp_head;
      BITMAPINFOHEADER bmp_info;
      int size = width * height * 3;

      bmp_head.bfType = 0x4D42; // 'BM'
      bmp_head.bfSize= size + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // 24 + head + info no quad
      bmp_head.bfReserved1 = bmp_head.bfReserved2 = 0;
      bmp_head.bfOffBits = bmp_head.bfSize - size;
      // finish the initial of head

      bmp_info.biSize = 40;
      bmp_info.biWidth = width;
      bmp_info.biHeight = height;
      bmp_info.biPlanes = 1;
      bmp_info.biBitCount = 24;
      bmp_info.biCompress = 0;
      bmp_info.biSizeImage = size;
      bmp_info.biXPelsPerMeter = 0;
      bmp_info.biYPelsPerMeter = 0;
      bmp_info.biClrUsed = 0 ;
      bmp_info.biClrImportant = 0;
      // finish the initial of infohead;

      // copy the data
      FILE *fp;
      if (!(fp = fopen(filename,"wb"))) return 0;

      fwrite(&bmp_head, 1, sizeof(BITMAPFILEHEADER), fp);
      fwrite(&bmp_info, 1, sizeof(BITMAPINFOHEADER), fp);
      fwrite(data, 1, size, fp);
      fclose(fp);
  };

  int pitch;
  int height;
  uint32_t* buf;
  std::tie(pitch, height, buf) = Broodwar->drawGameScreen(100, 100, 256, 256);

  std::array<uint8_t, 256 * 256 * 3> bmp_buf;
  for (size_t i = 0; i != height * pitch; ++i) {
    uint32_t r = buf[i] & 0xff;
    uint32_t g = (buf[i] >> 8) & 0xff;
    uint32_t b = (buf[i] >> 16) & 0xff;
    bmp_buf[i * 3] = b;
    bmp_buf[i * 3 + 1] = g;
    bmp_buf[i * 3 + 2] = r;
  }
  save("test.bmp", pitch, height, (unsigned char*)bmp_buf.data());

The screen position doesn't matter, this function only draws the requested area. No real guarantees about speed or stability (dont request areas outside of the map). But it should work.

N00byEdge commented 6 years ago
int pitch;
int height;
uint32_t* buf;
std::tie(pitch, height, buf) = Broodwar->drawGameScreen(100, 100, 256, 256);

or, in C++17 ;)

auto [pitch, height, buf] = Broodwar->drawGameScreen(100, 100, 256, 256);