My Inflation RPG Plays Harvester

I’ve been playing Inflation RPG for a while now – it’s a fairly casual game, although there are times I take it a little too seriously. Anyway, the game has gem items that reward the number of times you have played through the game, as a bonus to all of your stats for each time you have played through: 3x, 17x, 41x. You can buy the 3x Play Gems, and farm the 17x and 41x gems. So, of course these are theoretically the most powerful items in the game, provided you play the game often enough. Not really practical to play through thousands of times?  Probably not for a human…

At work we make extensive use of a program called AHK, or Auto Hot Key. AHK allows us to interact with the computer as if a user were typing input and using the mouse. The scripting language leaves a lot to be desired, but it pretty much allows you to code what you will. AHK also has image/pixel recognition support, along with COM and ODBC. At work, for example, a common application of AHK is for complex data entry, where a program would work off of a database, pulling down information, then interacting with another program, entering data, committing changes, then querying back out the data as a validation step, and finally marking an entry as complete. The most common complication is in achieving optimal speed of input while maintaining 100% accuracy. That is where AHK’s image recognition comes in – it allows us to wait for a confirmation by waiting for an image to appear on the screen.

So, we have this wonderful tool that allows us to interact with the computer, but Android is running on a phone, so to bridge the gap, we need to somehow control the phone from a computer, and also see what the phone is showing on the computer. Enter adbcontrol. Marian Schedenig’s little tool will allow you to capture and display screenshots on the screen, and also allow you to interact with your phone using taps, swipes, and the back and home buttons, among others.

All the pieces seem to be in place. The program should run through a gameplay as fast as possible, which, in this case, will be to start the game, and fight an enemy that is much stronger than I am (so I die quickly), 10 times, to deplete my battle points to 0.

Adbcontrol allowed me to take coordinates of all the buttons and areas I would need to use to interact with Inflation RPG in order to harvest gameplays. Starting a game would be the easy part – simply clicking buttons one after another to begin the game, with an appropriate delay between each click. The sequence of starting a game is to click Game Start, Try Again*, Confirm OK*, select a character, and finally selecting Normal difficulty. Note that the Try Again/Confirm OK dialogs come up when restarting in the middle of a run, so I click on those buttons, even if they’re not there.

inflation_title

The Inflation RPG Start Screen

I ensure the character is equipped with the weakest armor and weapons prior to starting. I also equip gems that make a random encounter less likely to happen, as the game starts on the 1st level field, where you always win the fight. I must move the character up to the 710 level field, where he’d be guaranteed to lose a fight.

Adbcontrol registers swipes and taps, but no click-holds, so I need to use MouseClickDrag a number of times to move the character up.

inflation_start

The Inflation RPG Starting Field

Once the character is in the correct field (note the red danger border radiating in from the edges of the screen), I can click on the Encounter button (make sure to equip the Instant Encounter gem), start the fight by clicking the screen after an appropriate delay, then returning to the field by clicking again after an appropriate delay. Yes, there is unfortunately a lot of waiting you have to do in Inflation RPG.

inflation_danger

The Inflation RPG LV 710 Field

After 10 iterations of this (you start with a total of 30 Battle Points and lose 3 Battle Points for every fight you lose), the scene transitions to the ending screen, which means just some more button clicking with some delays, then you end up at the start screen again.

That’s fine and dandy, but I want to go more in-depth into the difficulties I experienced during the development, and how I made this fast and robust.

The End Game Advertisement

This one perplexed me at the time. At the end of every game, prior to getting back to the start menu, Inflation RPG displayed one full-screen ad, possibly different every time. Not only that, but there were two distinct types of ads, one completely full screen, with an X on the top left, which could also be closed using the back button, and another that was 1/3rd of the screen, centered in the middle of the screen, with a larger X about 1/3rd of the way down on the top left. This smaller ad could not be closed with the back button – clicking the back button at this point would close the game.

How could I know which X I had to press, or whether I could use the back button, or had to click on the large X? Not only that, but the screenshots were JPG, the placement of the X would differ slightly depending on the ad, etc. I could not rely on a direct image comparison. If you examine the code, I wrote some functions that would sample pixels and roughly compare their colors against what I expected. It could find the X if it looked around enough, and was reliable, but it was slow, mainly because of the lag – adbcontrol has a 4+ second lag between screenshots.

It turned out that this was unnecessary. If I click on the expected position of each X button, starting with the larger ad, then the smaller ad, it clears whichever ad had appeared nicely.

Increasing Reliability

Every once in a while the automation would fail for unknown reasons. So I introduced a loop to kill and restart Inflation RPG every hour or so. To kill Inflation RPG, I used adb:

> adb shell am force-stop air.infurerpgkuesuto

I found the package name using

> adb shell ps

Teamviewer Detour

Prior to finding adbcontrol, I tried using Teamviewer. This is fantastic software, but unsatisfactory for my purposes – the lag grew longer the longer I was connected to the phone, and it wouldn’t stay open for more than one or two hours.

Farming Settings

I recommend doing the following when farming:

  • Turn off WIFI on your PC to avoid unwanted restarts/updates
  • Turn on blocking mode on your phone
  • Disable sync or other power-draining services on your phone
  • Turn brightness down to 0
  • Buy a y-cable to help keep your phone charged – mine will drain down to 0% after roughly 10 hours

Results

I’m able to farm gameplays roughly at a rate of 30 per hour. I have the software installed on an old but terribly reliable netbook, and have stopped after farming 9000 gameplays, equivalent to 153000 points to each stat with a single 17x crystal. 🙂 Having this helped me farm a number of items quickly, but so far a 41x play crystal eludes me. Oh well. I had fun writing the script and improving it to the point it is at now.

Source

Below is the AHK source code for the gameplay miner. It’s specific to a 1366×768 resolution, with adbcontrol occupying half the screen. (Windows 7, drag to right of screen/auto maximize). Getting it to work on another screen size will likely mean changing the vertical click values.

Sorry for the lack of comments, this one was quick & dirty. 🙂

Des

Here’s the script:

#SingleInstance force

RestartInflationRPG()
{
    ; Restart Inflation RPG - need to do a few things:
    ; 1. The Home key will wake up the phone, in case it's asleep
    ; 2. Additional presses on the Home key will navigate to the
    ;    home screen
    ; 3. If the home key is pressed too often, then we may get into
    ;    the "multi-home" screen, which we will bypass by pressing
    ;    Escape

    ; Before doing anything, kill Inflation RPG on the phone:
    Run, kill_irpg.bat
    Sleep 1000
    MyClick( 1, 1 )
    Send {Home}
    Sleep, 2000    ; In case the phone needs to wake up
    Send {Home}
    Sleep, 200  ; Allow the phone to respond
    Send {Home}
    Sleep, 200
    Send {Escape}
    ; Run Inflation RPG:
    MyClick( 52, 603 )
    Sleep, 4000 ; In case it has closed, give Inflation RPG time to open
}

MyClick(X,Y)
{ 
    EnsureWinActive()
    Click %X%,%Y%
}

EnsureWinActive()
{
    global
    IfWinNotActive, %WinName%
    {
        WinActivate, %WinName%
    }
}

MyClickS(X,Y)
{
    global
    MyClick(X,Y)
    Sleep, %ClickDelay%
}
MyClickSF(X,Y)
{
    global
    MyClick(X,Y)
    Sleep, %FastClickDelay%
}

ClickBack()
{
    EnsureWinActive()
    send {Escape}
}
ClickGameStart()
{
    MyClickSF(268,410)
}
ClickTryAgain()
{
    MyClickSF(279,387)
}
ClickConfirmOk()
{
    MyClickSF(215,341)
}
ClickCharacter()
{
    ; Pick the character least likely to combo:
    MyClickSF(360,271)
}
ClickNormal()
{
    MyClickS(219,241)
}
ClickEncounter()
{
    MyClick(162,111)
    Sleep, 50
    MyClick(162,111)
}

ClickConfirmRegisterNo()
{
    MyClickSF(211,345)    
}

ClickPostTitle()
{
    MyClickSF(323,684)
}

MyMouseClickDrag( Button, X1,Y1, X2,Y2, Speed )
{
    EnsureWinActive()
    ;MouseMove, X1,Y1,0
    MouseClickDrag, %Button%, X1, Y1, X2, Y2, Speed
}

InitialMoveUp()
{
    global
    MyMouseClickDrag( Left, 211, 282, 209,135, 1 )
    MyMouseClickDrag( Left, 211, 282, 209,135, 5 )
    MyMouseClickDrag( Left, 211, 282, 209,135, 5 )
    MyMouseClickDrag( Left, 211, 282, 209,135, 5 )
;    MyMouseClickDrag, Left, 211, 282, 209,135, 15
    Sleep, 200
    Sleep, %ClickDelay%
}

Fight()
{
    sleep, 2000
    X:=210
    Y:=425
    MyClick(%X%,%Y%)
;    MyClick(%X%,%Y%)
;    MyClick(%X%,%Y%)
    sleep, 5500
    MyClick(%X%,%Y%)
    MyClick(%X%,%Y%)
    Sleep, 1500    
}

ColorCompare(C1, C2, precision)
{
    global
    C1B:=C1>>16
    C1R:=(C1>>8) & 0xff
    C1G:=C1 & 0xff

    C2B:=C2>>16
    C2R:=(C2>>8) & 0xff
    C2G:=C2 & 0xff
    ; Calculate differences:
    DB:=abs(C1B-C2B)
    DR:=abs(C1R-C2R)
    DG:=abs(C1G-C2G)
    
    ;MsgBox %DB%,%DR%,%DG%
    if( DB > precision or DR > precision or DG > precision )
    {
        ;MsgBox Returning false
        return false
    }
    ;MsgBox Returning true
    return true
}

ClickAd1X()
{
    ; Clicks the top-most X
    MyClickSF(22,44)
}

ClickAd2X()
{
    ; This one clicks off the alternate ad format, which requires clicking the X to dismiss
    MyClickSF(45,153)
}

ClickBackOrX()
{
    ClickAd1X()
    Sleep, 50
    ClickAd2X()
    return
    ; This function needs to do some image reccognition to determine if the back
    ; button should be pressed, or the X button should be pressed

    Loop 40 ; 40 * 0.5 seconds = 20 seconds of checking
    {
        if IsType1Ad()
        {
            ClickAd1X()
            return
        }
        if IsType2Ad()
        {
            ClickAd2X()
            return
        }
        sleep, 500 ; Sleep for half a second
    }
    ; If we don't find anything, assume it's a google ad:
    ClickBack()
    return
}

DBGMouseMove(X,Y)
{
    global
    ;MsgBox %Debugging%, %X%, %Y%, X, Y
    If %Debugging%
    {
        MouseMove %X%, %Y%
        Sleep, 500
    }
}

DBGMsgBox(Words)
{
    global
    If %Debugging%
    {
        MsgBox %Words%
    }
}


IsType1Ad()
{
    ; Type 1 ads can use the back button to exit, or
    ; button press on the X, which is at the top left corner

    ; Possible Y positions:
    PossibleY1:=40
    PossibleY2:=43
    PossibleY3:=46
    Delta:=10
    PossibleX1:=23
    PossibleX2:=26
    PossibleX3:=29
    
    Loop 3 ; Iterate over PossibleY
    {
        PY:=a_index
        Y:=PossibleY%PY%
      
        Loop 1 ; Iterate over PossibleX
        {
            PX:=a_index
            X:=PossibleX%PX%-Delta
            
            EnsureWinActive()
            DBGMouseMove( X, Y )
            PixelGetColor, xbox1, %X%, %Y%, ALT
            DBGMsgBox( xbox1 )
            X:=X+Delta
            DBGMouseMove( X, Y )
            PixelGetColor, xbox2, %X%, %Y%, ALT
            DBGMsgBox( xbox2 )
            X:=X+Delta
            DBGMouseMove( X, Y )
            PixelGetColor, xbox3, %X%, %Y%, ALT
            DBGMsgBox( xbox3 )
            if( ColorCompare( xbox1, 0x7e7e7e, 25 ) 
                and ColorCompare( xbox2, 0x000000, 25 )
                and ColorCompare( xbox3, 0x7e7e7e, 25 ) )
            {
                DBGMsgBox( "Found X on top left" )
                return True
            }
      }
    }
    DBGMsgBox( "Did not find X on top left" )
    return False        
}

IsType2Ad()
{
    ; Type 2 ads are the 'internal' ads that have the large X box more
    ; towards the middle of the screen. These ads cannot be bypassed
    ; with the back button, and the X must be pressed

    PossibleY1:=153
    PossibleY2:=156
    PossibleY3:=159
    PossibleY4:=162
    
    Delta:=8
    PossibleX1:=44
    PossibleX2:=41
    
    Loop 4 ; Iterate over PossibleY
    {
        PY:=a_index
        Y:=PossibleY%PY%
      
        Loop 2 ; Iterate over PossibleX
        {
            PX:=a_index
            X:=PossibleX%PX%-Delta
            
            EnsureWinActive()
            DBGMouseMove( X, Y )
            PixelGetColor, xbox1, %X%, %Y%, SLOW
            DBGMsgBox( xbox1 )
            X:=X+Delta
            DBGMouseMove( X, Y )
            PixelGetColor, xbox2, %X%, %Y%, SLOW
            DBGMsgBox( xbox2 )
            X:=X+Delta
            DBGMouseMove( X, Y )
            PixelGetColor, xbox3, %X%, %Y%, SLOW
            DBGMsgBox( xbox3 )
            if( ColorCompare( xbox1, 0x404040, 12 ) 
                and ColorCompare( xbox2, 0xc7c7c7, 12 )
                and ColorCompare( xbox3, 0x404040, 12 ) )
            {
                return True
            }
      }
    }
    ; Otherwise, the X is not found:
    return False
}

MainLoop() {
    global
    ; Begin:
    Sleep, 500
    ClickGameStart()
    ClickTryAgain()
    ClickConfirmOK()
    ClickCharacter()
    ClickNormal()
    Sleep, 1000
    InitialMoveUp()

    ; Do the encounters:
    loop 10 {
        ClickEncounter()
        Fight()
    }

    ; Post-battles
    Sleep, 3500
    ClickConfirmRegisterNo()
    ClickPostTitle()
    Sleep, 1500 ; 19000
    ClickBackOrX()
}

;;; Start of program:
; Absolute screen coordinates:
CoordMode Mouse, Relative
CoordMode Pixel, Relative

;;; Variables:
BaseDelta:=748-1231
BaseDelta:=1350-1525
ClickDelay=1000
FastClickDelay=250
TVXImage=TVX.PNG
InitialMoveUpTime=2200
WinName=ADB Control
Debugging:=False ;True
;Debugging=True
;;; Start:
WinActivate %WinName%

;RestartInflationRPG()
;exit



;InitialMoveUp()
;return
;ClickBackOrX()
;exit

;X:=CalcBase(1000)
;MsgBox %X%
;return
;DBGMsgBox( "Testing" )
;IsType1Ad()
;return

loop {
    ; roughly every 30 runs (1 hour) restart inflation RPG
    loop 60 {
        MainLoop()
    }
    RestartInflationRPG()
}
#p::Pause

#Escape::
ExitApp
Return

Here’s my kill_irpg.bat batch file:

:: Batch file to kill inflation RPG using ADB!!!
C:\adt-bundle-windows-x86-20140702\sdk\platform-tools\adb shell am force-stop air.infurerpgkuesuto

Leave a comment