Udostępnij za pośrednictwem


Building Arcade Game Player (first step in long path)

For few months I have been thinking of building arcade game player. I mean that I would create application that would then play some old classic arcade game (i.e. Blues Brothers, Rick Dangerous, Super Mario Bros. etc.). Previously I have created applications that play puzzle games (i.e. Survo, MineSweeper) and since puzzle games are fairly straightforward I thought I'll take a bit challenging task. And of course all actions between my "player application" and the actual game should be done through user interface and not by tweaking the process memory (this time :-). It's definitely challenging task. So I thought that I'll start with baby steps and try to first solve the graphical user interface challenge.

Currently I'm thinking following approach: I need to take "snapshots" out of the game and then find the differences between last snapshot and current snapshot. From that I could then analyze the differences and probably get location of the character and all the nasty enemies more easily than I could using other methods. But of course that's not enough... I need to do other image recognition things in order to sort out other issues. I'll worry those things later :-)

Okay let's create Live Image Diff as I call it so that we can find the differences between frames. First step is to grab image from the foreground window. Here's code clip for that (I have this code in my Win32Helper -class):

 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
 public static readonly int SRCCOPY = 0xCC0020;[StructLayout(LayoutKind.Sequential)]public struct RECT{  public int left;  public int top;  public int right;  public int bottom;}[DllImport("user32.dll")]public static extern IntPtr GetForegroundWindow();[DllImport("user32.dll")]public static extern bool GetClientRect(IntPtr hWnd, ref RECT lpRect);[DllImport("gdi32.dll")]public static extern bool BitBlt(  IntPtr hdcDest, // handle to destination DC  int nXDest,   // x-coord of destination upper-left corner  int nYDest,   // y-coord of destination upper-left corner  int nWidth,   // width of destination rectangle  int nHeight,  // height of destination rectangle  IntPtr hdcSrc, // handle to source DC  int nXSrc,   // x-coordinate of source upper-left corner  int nYSrc,   // y-coordinate of source upper-left corner  System.Int32 dwRop // raster operation code  );static public Bitmap GetSnapShot(){  IntPtr hWnd = GetForegroundWindow();  RECT rect = new RECT();  if (GetClientRect(hWnd, ref rect) == true)  {    int width = rect.right - rect.left;    int height = rect.bottom - rect.top;    Graphics graphWindow = Graphics.FromHwnd(hWnd);    Bitmap bitmap = new Bitmap(width, height, graphWindow);    Graphics graphFile = Graphics.FromImage(bitmap);    IntPtr dcWindow = graphWindow.GetHdc();    IntPtr dcFile = graphFile.GetHdc();    BitBlt(dcFile, 0, 0, width, height, dcWindow, 0, 0, SRCCOPY);    graphWindow.ReleaseHdc(dcWindow);    graphFile.ReleaseHdc(dcFile);    return bitmap;  }  return null;}

So now we have image (=.NET Bitmap) of the foreground application.

Now I want to compare previous image to the current snapshot and mark everything that has been changed with red color. Here's clip that does that:

 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
 using System.Drawing.Imaging;// ...Bitmap previousSnapshot;Bitmap currentSnapshot;BitmapData currentSnapshotCleanData;Rectangle rectangle; //...previousSnapshot = currentSnapshot;currentSnapshot = Win32Helper.GetSnapShot();if (currentSnapshot == null){  return;}if (previousSnapshot == null ||     previousSnapshot.Width != currentSnapshot.Width ||     previousSnapshot.Height != currentSnapshot.Height){  previousSnapshot = currentSnapshot.Clone() as Bitmap;  rectangle = new Rectangle(0, 0, currentSnapshot.Width, currentSnapshot.Height);} //...BitmapData previousSnapshotData = previousSnapshot.LockBits(  rectangle, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);BitmapData currentSnapshotData = currentSnapshot.LockBits(  rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);currentSnapshotCleanData = currentSnapshotData;IntPtr ptrPreviousSnapshot = previousSnapshotData.Scan0;IntPtr ptrCurrentSnapshot = currentSnapshotData.Scan0;int bufferSize = previousSnapshotData.Stride * previousSnapshotData.Height - 1;byte[] previousSnapshotBuffer = new byte[bufferSize];byte[] currentSnapshotBuffer = new byte[bufferSize];Marshal.Copy(ptrPreviousSnapshot, previousSnapshotBuffer, 0, previousSnapshotBuffer.Length);Marshal.Copy(ptrCurrentSnapshot, currentSnapshotBuffer, 0, currentSnapshotBuffer.Length);for (int x = 0; x < bufferSize - 4; x += 4){  if (previousSnapshotBuffer[x] != currentSnapshotBuffer[x] ||      previousSnapshotBuffer[x + 1] != currentSnapshotBuffer[x + 1] ||      previousSnapshotBuffer[x + 2] != currentSnapshotBuffer[x + 2] ||      previousSnapshotBuffer[x + 3] != currentSnapshotBuffer[x + 3])  {    // This has been changed! Let's make it red:    previousSnapshotBuffer[x] = 0;    previousSnapshotBuffer[x + 1] = 0;    previousSnapshotBuffer[x + 2] = 0xff;    previousSnapshotBuffer[x + 3] = 0xff;  }}Marshal.Copy(previousSnapshotBuffer, 0, ptrPreviousSnapshot, previousSnapshotBuffer.Length);previousSnapshot.UnlockBits(previousSnapshotData);currentSnapshot.UnlockBits(currentSnapshotData);// Now draw "previousSnapshot" to the screen:pictureBoxAtTheForm.Image = previousSnapshot;

On lines 10 to 22 I'll just grab current snapshot and try to make sure that my snapshots stay in sync. I need to do some checks since user actually could have changed the foreground window and my snapshots can be from different windows and therefore different sizes. If something goes wrong in this bad things will happen later.

On lines 40 to 53 I'll just check that is pixel at the current snapshot different from the one in previous snapshot. And if that is the case then I'll just fill the value to the buffer so that it makes the pixel red. I don't even try to make this code portable to different pixelformats etc. It's okay if it just runs on my machine with my display settings :-)

But the coolest thing is yet to come. I used method described in here to create Silverlight screencast from my application. I used IE in my demonstration of Live Image Diff. I browsed to my blog and then selected some text etc. in order to make changes to the screen. My application is running at the lower right corner and it displays differences in the user interface. Here is screenshot from that:

Now you can see my application in action: Live Image Diff !

What are next steps in this one? Well I don't know... so if you have ideas how to proceed on this challenge then drop me email or post comment to this post. I would like to here your ideas.

Anyways... Happy hacking!

J

Comments

  • Anonymous
    January 30, 2008
    The comment has been removed