Creating a Simple Bot – Part 3

Building a Bot in Bash

In this installment, we’ll figure out how to read the Oolite screen so we can automatically direct the ship to its destination.

Already read this installment? Here are all the articles in this series:
Part 1 – Establish control of Oolite
Part 2 – Automating a trading route
Part 3 – Reading Oolite’s state
Part 4 – Adding polish

We’re going to be creating some scripts to test out small portions of the main script. To make this easier, let’s break the functions out into a separate file.

Creating A Function Library

Copy your bot script to another file named bot-funcs.sh

Open bot-funcs.sh in your editor and make the following changes:

  • Remove/delete the runroute function
  • Remove/delete the start up instructions
  • Remove the do/while game loop

What we’re left with should be all of the functions and the line that finds the Oolite window and sets $WID.

We’ll turn the window search line into a function too.

bot-funcs.sh

Back in your original bot script, at the top of the script, just after the shebang line, pull in the function library using the source command.

bot

Now strip out the functions that we’ve ‘moved’ to the bot-funcs.sh file, and replace the window finding command with the new function to leave us with this:

bot

Creating A Test Script

And we’ll create a test script…

Edit bot-test to be

bot-test

Run bot-test and verify it works as expected, launching your ship from the station. Go ahead and re-dock with the station.

Listening To Oolite

So far, we’ve created code to talk at Oolite. xdotool doesn’t contain much functionality to listen to a program. In fact, the only listening xdotool seems to be able to do is read mouse coordinates.

We need a way to see (in our code) what is going on with the game. If we can do this, we can create a feedback loop where we tell the game to do something (write), then see what the game did (read), and based on the state of the game, make further adjustments (more writing).

The main gap in our existing bot script is when we come out of hyperspace. After we’ve maneuvered around, we have to manually point our ship towards the planet/station, then, when in range, dock our ship.

I want to automate this.

The basic idea is to capture parts of the screen, and based on what we see in those captured pixels, determine what we tell Oolite to do.

After applying some google-fu, I found a possible solution in the form of xwd and convert (which uses ImageMagick).

xwd

I believe xwd is provided as part of the X-Windows package that is already installed on Ubuntu.

xwd will capture screen shots. It can capture a screen shot of an individual window (that we click on) too.

convert

convert is part of the imagemagick package, a program that should allow us to crop our captured image to only include the small section we need to look at (the compass).

Install imagemagick (if not already installed) with

If you receive errors, try

Capturing The Screen

Back to our test script (bot-test)…

Modify bot-test to look as follows:

bot-test

The -root option tells xwd to capture the root window and (important to us) not to require a mouse click to select the window to dump.

-silent tells it to skip ringing the bell (which is rung by default).

We pipe (|) the dump to ImageMagick’s convert function, reduce it to 8-bit color (256 different colors), and write it out to disk as screen.png. ImageMagick understands the file extension and outputs the file in the correct format.

When you run the script, it should take a screen shot of the screen and write it to screen.png.

Open screen.png and verify it is a valid file. Note that xwd captured the entire desktop. I’m running dual displays so it captured both displays in the same screenshot.

We only want to see the Oolite screen. Looking into xwd’s documentation we see that we can provide the name of a window to dump.

Update the test script to capture the window name and use it to tell xwd what window to capture.

bot-test

Running the script again gives us the output we need.

During these tests, I’ve noticed that my Oolite window size sometimes changes between startups. This will affect our relative coordinates so I think we should size the window to a specific size so our coordinates are always the same.

Adding

bot-test

after the winname=$(xdootool ... line outputs the following on my system:

Resize your Oolite screen to your preferred size and re-run the script. Note the geometry settings.

Add the geometry settings to the top of your bot-funcs.sh file (below the shebang line).

bot-funcs.sh

Back in the test script, replace the getwindowgeometry line with

bot-test

To test the resize, drag your Oolite window to be a different size, run the script and verify it resizes appropriately.

Let’s create a window setup function in bot-funcs.sh.

bot-funcs.sh

And use the new function in the test script.

bot-test

Alright, it’s time to try cropping our images. We’ll fire up the showmouse script from the previous installment and use it to determine a starting point (origin) to crop from. At this point, we don’t care where the origin is, we just want to know that the output image looks similar to what we expect, and rather than guessing screen coordinates, we’ll enter known coordinates instead.

Change the xwd line to

bot-test

replacing 497 and 629 with coordinates you determined with showmouse.

Running the script should produce an image 120 pixels square, starting at the location you set.

compass1

Because 120 vertical pixels is outside of the window we’re capturing, the image is truncated, so it’s not square.

We’re going to be generating many snapshots so we should turn this into a handy helper script. We’ll update the test script to allow the user to enter the coordinates and size of the snapshot to generate.

Make of copy of bot-test:

Edit dumpimg to look like the following

dumpimg

Using the dumpimg script, we can find the optimal image to capture. We want to capture only the compass with as little extraneous data as possible.

Here, you can see that the upper left part of the image has some of the radar in it:

compass2

Now I’ve reduced the size to 34 x 34 and positioned the capture so there’s no extra space. Keep in mind that we may be capturing a snapshot many times a second, so the less to capture, the faster it will be.

compass3

While we can use our new dump script to determine what part of the image we’ll need to capture, we still need to figure out how to parse the output image.

For changing the ship’s course, we want to determine if we see the green cross-hairs (a circle) on the compass, and if we do, where it’s located on the compass. If we can tell where it’s located, we can adjust the attitude of the ship to center the cross-hairs.

convert has the ability to output our snapshot as text. Let’s see what that looks like.

Back in the test script, we’ll update it to use the same type of xwd command as in dumpimg. But we’ll have it output text instead.

bot-test

This will write the image out to compass.txt as text. Open compass.txt and take a look at it. Here’s an example line:

The layout is X,Y: ( R,G,B ) #RRGGBB srgb(R,G,B).
The #RRGGBB is the hex formatted color (Red/Green/Blue).

We want to know where the first pixel of green is located. Using grep we can parse the text output looking for a pixel that is green enough, then return the X and Y coordinates. I say green enough because if the background of the compass is a planet, the planet colors will be blended with the cross-hairs.

FF (255 in decimal) is the max amount of green possible. From testing, I’ve determined that any pixel that is 55F555 or ‘greener’ will work for our purposes. The grep regex for this looks like #[0-5][0-5]F[5-9|A-F][0-5][0-5].

Add a new function to find a pixel:

bot-test

The m1 grep option will return the first line that contains a match for the regex.

The script now outputs something like

Now we need to parse the X and Y out of the result.

awk can help us parse the result. The GNU Awk User’s Guide is a good starting point for learning awk. It has a lot of power while being easy to use.

Here is a function using awk:

bot-test

The awk program code is:

'BEGIN { FS="[,:]" }; { printf("curX=%d\ncurY=%d\n", $1, $2) }'

Broken down:

  • BEGIN { FS="[,:]" };
    • before we start, our field separators are , and :
  • { printf("curX=%d\ncurY=%d\n", $1, $2) }
    • after we’ve parsed the line, print:
    • curX=field 1
    • curY=field 2

We evaluate the result so that the curX=something and curY=something statements are evaluated as if we typed them on the command line. After calling this function, curX and curY will be set.

Let’s update the test script so we can see what our output looks like:

bot-test

And our output:

Looks good. Let’s move these functions to our library.

bot-funcs.sh

And our test script updated to use the library functions:

bot-test

Running the test script gives us the result we’re expecting.

Let’s see some auto-navigation!

Modify the test script to add some navigation functions:

bot-test

In Oolite, launch from the station and move away from it. Come to a full stop and make sure you are pointing in a direction away from the station.

Run the test script.

While running the script, I’ve noticed that we have an error in our libraries. When we yaw_left we are actually rotating, not ‘yawing’. Lets fix that.

bot-funcs.sh

I’ve renamed yaw_left to rotate_ccw and yaw_right to rotate_cw, and added a new yaw_left and yaw_right.

And we test it again…

Alright! Close enough for government work.

We can now read the game, and update the game based on the results.

Add the targetMinX and targetMinY settings to the Global Settings of the library.

bot-funcs.sh

Add and update the align functions too.

bot-funcs.sh

Now remove the function definitions from the test script, reposition your ship and run the script again to make sure we haven’t broken anything.

Before we can add the navigation commands to our bot, we need to be able to tell when we’re within the station’s interdiction zone, and be able to dock.

Going back to our showmouse and dumpimg helper scripts, use them to determine the snapshot needed to capture the S graphic that indicates we’re within the interdiction zone of the station.

On my screen, the coordinates are 198×634.

station-indicator

Once I’ve verified I have the right location by looking at the PNG, I dump the text version and see what color the S is. In this case, it is #006000.

To account for a planet in the background, I’ll try the following regex:

#[0-5][0-5]6[0-9|A-F][0-5][0-5]

Back to the test script to try it out.

bot-test

Notes:

  • #[0-5][0-5]6[0-9|A-F][0-5][0-5] didn’t work as well as I wanted. I went with #[[:digit:]][[:digit:]][6-9|A-F][0-9|A-F][[:digit:]][[:digit:]] instead.
  • is_station_in_range will some times return a false positive when a particular color of planet is in the background. I think this is acceptable given that we should be aiming at the station when we get close enough to the planet anyway.
  • Notice that I check for $curX of 7 and $curY of 2 in is_station_in_range. This is actually the first colored pixel in my PNG capture of the station indicator. I’m specifying this location to help cut down on false positives as well.

Overall, I’m happy with the result.

Add the functions to the library and create a dock function too (you’ll need to have a docking computer installed).

bot-funcs.sh

Here’s our test script with the navigation function:

bot-test

The test results look good! I’ll add it to the library and incorporate it into the bot.

After running the bot for awhile, the navigation tweaks that the bot performs to align the compass bother me. I’m going to make 2 changes.

Change 1: Tweak the cross-hair target X value (change to 15)

bot-funcs.sh

Change 2: This is a multi-part change.

  • Change 2a: Check for the station interdiction zone prior to aligning the ship and exit the alignment function if we’re in range of the station.
  • Change 2b: Change the alignment checks to be less-than or equal instead of less-than.
bot-funcs.sh

These changes make the alignment much less jerky and short-circuit the maneuvering when the station is in range.

Here’s our bot script after adding the navigation commands:

bot

In our next (and last) installment, we’ll do some more cleanup and tweaking to wrap up our bot. Read it here.

Comments, criticism, or discussion? Leave it after the beep…

Leave a Reply

Your email address will not be published. Required fields are marked *