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
|
1 2 3 |
cp bot bot-funcs.sh |
Open bot-funcs.sh in your editor and make the following changes:
- Remove/delete the
runroutefunction - 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
|
1 2 3 4 5 6 7 |
# Find the Oolite window and store the ID function find_oolite_window() { WID=$(xdotool search --name "Oolite v1" | head -1) } |
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
|
1 2 3 4 5 6 |
#!/usr/bin/env bash source bot-funcs.sh |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#!/usr/bin/env bash source bot-funcs.sh # # Main trading route # function runroute() { refuel buycomputers markxeoner quicksave launch pitchdown 1.5 fireinjectors 4 enterhyper pitchdown 1.5 fireinjectors 20 jump waitfor 15 pitchup 1.6 echo echo "Maneuver to Xeoner then..." pauseshell "Press [Enter] when you've docked" sellcomputers buyfurs refuel markxexedi quicksave launch pitchdown 1.5 fireinjectors 4 enterhyper pitchdown 1.5 fireinjectors 20 jump waitfor 15 pitchup 1.6 echo echo "Maneuver to Xexedi then..." pauseshell "Press [Enter] when you've docked" sellfurs } echo "To start, you should be located on Xexedi station," echo "have NOT refueled yet," echo "with empty cargo holds," echo echo "Use Ctrl-c to exit" echo find_oolite_window pauseshell "Press [Enter] when you're ready" while true do runroute done |
Creating A Test Script
And we’ll create a test script…
|
1 2 3 4 5 |
touch bot-test chmod +x bot-test |
Edit bot-test to be
bot-test
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo echo "Use Ctrl-c to exit" echo find_oolite_window pauseshell "Press [Enter] when you're ready" launch |
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
|
1 2 3 4 5 |
sudo apt-get update sudo apt-get install imagemagick |
If you receive errors, try
|
1 2 3 4 |
sudo apt-get install imagemagick --fix-missing |
Capturing The Screen
Back to our test script (bot-test)…
Modify bot-test to look as follows:
bot-test
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo echo "Use Ctrl-c to exit" echo find_oolite_window pauseshell "Press [Enter] when you're ready" # Capture a snapshot of the window xwd -root -silent | convert xwd:- -depth 8 screen.png |
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
|
1 2 3 4 5 6 7 8 9 |
#... winname=$(xdotool getwindowname $WID) # Capture a snapshot of the window xwd -name "$winname" -silent | convert xwd:- -depth 8 screen.png |
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
|
1 2 3 4 |
xdotool getwindowgeometry $WID |
after the winname=$(xdootool ... line outputs the following on my system:
|
1 2 3 4 5 6 |
Window 88080404 Position: 1193,365 (screen: 0) Geometry: 727x682 |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/usr/bin/env bash ## Global Settings ## # Oolite window size WIN_WIDTH=727 WIN_HEIGHT=682 # Keystroke delay in ms KEYDELAY=80 #... |
Back in the test script, replace the getwindowgeometry line with
bot-test
|
1 2 3 4 |
xdotool windowsize $WID $WIN_WIDTH $WIN_HEIGHT |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#!/usr/bin/env bash ## Global Settings ## # Oolite window size WIN_WIDTH=727 WIN_HEIGHT=682 # Keystroke delay in ms KEYDELAY=80 # Find and initialize the Oolite window function init_oolite_window(){ find_oolite_window # Resize the window so we have consistent coords xdotool windowsize $WID $WIN_WIDTH $WIN_HEIGHT # Store the window name (title) for later use WIN_NAME=$(xdotool getwindowname $WID) } # ... |
And use the new function in the test script.
bot-test
|
1 2 3 4 5 6 7 8 9 10 |
#... init_oolite_window pauseshell "Press [Enter] when you're ready" #... |
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
|
1 2 3 4 5 6 |
#... xwd -name "$WIN_NAME" -silent | convert xwd:- -depth 8 -crop "120x120+497+629" screen.png |
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.
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:
|
1 2 3 4 |
cp bot-test dumpimg |
Edit dumpimg to look like the following
dumpimg
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#!/usr/bin/env bash source bot-funcs.sh function usage(){ echo 'dumpimg WID HGT X Y OUTPUTFILE' echo echo ' WID: width of image' echo ' HGT: height of image' echo ' X: X coordinate of origin' echo ' Y: Y coordinate of origin' echo ' OUTPUTFILE: file name/path to write image to' echo } # Check for proper number of args. expected_args=5 if [ $# -ne $expected_args ] then usage exit 1 fi # Store the args in named vars width=$1 height=$2 originX=$3 originY=$4 output=$5 # Build the cropping string cropStr="${width}x${height}+${originX}+${originY}" echo "Dumping $cropStr to $output" echo init_oolite_window # Capture a snapshot of the window xwd -name "$WIN_NAME" -silent | convert xwd:- -depth 8 -crop $cropStr $output exit 0 |
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:
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.
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo init_oolite_window pauseshell "Press [Enter] when you're ready" function capture_snapshot() { # Store the args in named vars local width=$1 local height=$2 local originX=$3 local originY=$4 cropStr="${width}x${height}+${originX}+${originY}" # Capture a snapshot of the window xwd -name "$WIN_NAME" -silent | convert xwd:- -depth 8 -crop $cropStr txt:- } function capture_compass(){ capture_snapshot 34 34 497 629 } capture_compass > compass.txt |
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:
|
1 2 3 4 |
14,14: ( 0,238, 0) #00EE00 srgb(0,238,0) |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#... function find_pixel(){ capture_compass | grep -m1 $1 } greenEnough='#[0-5][0-5]F[5-9|A-F][0-5][0-5]' capture_compass > compass.txt echo $(find_pixel $greenEnough) |
The m1 grep option will return the first line that contains a match for the regex.
The script now outputs something like
|
1 2 3 4 5 6 7 |
bot-test Press [Enter] when you're ready 16,13: ( 0,248, 0) #00F800 srgb(0,248,0) |
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
|
1 2 3 4 5 6 |
function to_coords() { eval $(echo $1 | awk 'BEGIN { FS="[,:]" }; { printf("curX=%d\ncurY=%d\n", $1, $2) }') } |
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:
- before we start, our field separators are
{ 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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo init_oolite_window pauseshell "Press [Enter] when you're ready" function capture_snapshot() { # Store the args in named vars local width=$1 local height=$2 local originX=$3 local originY=$4 cropStr="${width}x${height}+${originX}+${originY}" # Capture a snapshot of the window xwd -name "$WIN_NAME" -silent | convert xwd:- -depth 8 -crop $cropStr txt:- } function capture_compass(){ capture_snapshot 34 34 497 629 } function find_pixel(){ capture_compass | grep -m1 $1 } function to_coords() { eval $(echo $1 | awk 'BEGIN { FS="[,:]" }; { printf("curX=%d\ncurY=%d\n", $1, $2) }') } greenEnough='#[0-5][0-5]F[5-9|A-F][0-5][0-5]' capture_compass > compass.txt line=$(find_pixel $greenEnough) echo $line to_coords $line echo curX=$curX echo curY=$curY |
And our output:
|
1 2 3 4 5 6 7 8 9 |
bot-test Press [Enter] when you're ready 16,13: ( 0,248, 0) #00F800 srgb(0,248,0) curX=16 curY=13 |
Looks good. Let’s move these functions to our library.
bot-funcs.sh
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
#!/usr/bin/env bash ## Global Settings ## # Oolite window size WIN_WIDTH=727 WIN_HEIGHT=682 # Ship's compass location COMPASS_WIDTH=34 COMPASS_HEIGHT=34 COMPASS_X=497 COMPASS_Y=629 # Keystroke delay in ms KEYDELAY=80 # Find and initialize the Oolite window function init_oolite_window(){ find_oolite_window # Resize the window so we have consistent coords xdotool windowsize $WID $WIN_WIDTH $WIN_HEIGHT # Store the window name (title) for later use WIN_NAME=$(xdotool getwindowname $WID) } function pauseshell() { read -p "$*" xdotool windowactivate --sync $WID } function waitfor() { xdotool sleep $1 } function leftclick() { xdotool mousedown 1 waitfor 0.5 xdotool mouseup 1 } function sendkey(){ xdotool key --delay $KEYDELAY "$@" } function keypress() { xdotool keydown --delay $KEYDELAY $1 waitfor $2 xdotool keyup --delay $KEYDELAY $1 } function pitchup() { keypress Down $1 } function pitchdown() { keypress Up $1 } function yaw_left() { keypress Left $1 } function yaw_right() { keypress Right $1 } function launch() { sendkey F1 waitfor 2 } function fireinjectors() { keypress i $1 } function jump() { sendkey j } function enterhyper() { sendkey h # Wait for 20 seconds for the ship to hyper and recover waitfor 20 } function gotostatus() { sendkey 5 } function gotomarket() { sendkey 8 } function gotosystemnav() { # Make sure we don't go to the galactic chart by # going to the market screen first. gotomarket sendkey 6 } function quicksave() { sendkey 2 sendkey Return } function refuel() { sendkey 3 sendkey Return } function buyfurs() { gotostatus gotomarket sendkey Down Down Down Down Down Down Down Down Down Down Down sendkey Return } function sellfurs() { buyfurs } function buycomputers() { gotostatus gotomarket sendkey Down Down Down Down Down Down Down sendkey Return } function sellcomputers() { buycomputers } function markxexedi() { gotosystemnav xdotool mousemove --window $WID 292 299 leftclick } function markxeoner() { gotosystemnav xdotool mousemove --window $WID 406 218 leftclick } # Find the Oolite window and store the ID function find_oolite_window() { WID=$(xdotool search --name "Oolite v1.80" | head -1) } # # Screen capture functions # function capture_snapshot() { # Store the args in named vars local width=$1 local height=$2 local originX=$3 local originY=$4 local cropStr="${width}x${height}+${originX}+${originY}" # Capture a snapshot of the window xwd -name "$WIN_NAME" -silent | convert xwd:- -depth 8 -crop $cropStr txt:- } function capture_compass(){ capture_snapshot $COMPASS_WIDTH $COMPASS_HEIGHT $COMPASS_X $COMPASS_Y } function find_pixel(){ capture_compass | grep -m1 $1 } function to_coords() { eval $(echo $1 | awk 'BEGIN { FS="[,:]" }; { printf("curX=%d\ncurY=%d\n", $1, $2) }') } function set_crosshair_coords() { local greenEnough='#[0-5][0-5]F[5-9|A-F][0-5][0-5]' local line=$(find_pixel $greenEnough) to_coords $line } |
And our test script updated to use the library functions:
bot-test
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo init_oolite_window pauseshell "Press [Enter] when you're ready" set_crosshair_coords echo curX=$curX echo curY=$curY |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo init_oolite_window pauseshell "Press [Enter] when you're ready" targetMinX=16 targetMinY=13 function vert_align_with_station() { local delay=.1 set_crosshair_coords until [ $curY -lt $targetMinY ]; do echo Down pitchdown 0.05 waitfor $delay set_crosshair_coords done if [ $curY -lt $targetMinY ]; then until [ $curY -ge $targetMinY ]; do echo Up pitchup 0.05 waitfor $delay set_crosshair_coords done fi echo VERT ALIGNED } function horz_align_with_station() { local delay=.1 set_crosshair_coords until [ $curX -lt $targetMinX ]; do echo Right yaw_right 0.01 waitfor $delay set_crosshair_coords done if [ $curX -lt $targetMinX ]; then until [ $curX -ge $targetMinX ]; do echo Left yaw_left 0.01 waitfor $delay set_crosshair_coords done fi echo HORZ ALIGNED } vert_align_with_station horz_align_with_station |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function rotate_ccw() { keypress Left $1 } function rotate_cw() { keypress Right $1 } function yaw_left() { keypress comma $1 } function yaw_right() { keypress period $1 } |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ... COMPASS_X=497 COMPASS_Y=629 # Crosshair position to center on station/planet TARGET_MIN_X=16 TARGET_MIN_Y=13 # Keystroke delay in ms KEYDELAY=80 # ... |
Add and update the align functions too.
bot-funcs.sh
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# ... function vert_align_with_station() { local delay=.1 set_crosshair_coords until [ $curY -lt $TARGET_MIN_Y ]; do pitchdown 0.05 waitfor $delay set_crosshair_coords done if [ $curY -lt $TARGET_MIN_Y ]; then until [ $curY -ge $TARGET_MIN_Y ]; do pitchup 0.05 waitfor $delay set_crosshair_coords done fi } function horz_align_with_station() { local delay=.1 set_crosshair_coords until [ $curX -lt $TARGET_MIN_X ]; do yaw_right 0.01 waitfor $delay set_crosshair_coords done if [ $curX -lt $TARGET_MIN_X ]; then until [ $curX -ge $TARGET_MIN_X ]; do yaw_left 0.01 waitfor $delay set_crosshair_coords done fi } |
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.
|
1 2 3 4 5 6 7 8 |
# Dump the PNG image ./dumpimg 32 32 198 634 screen.png # Dump the text version ./dumpimg 32 32 198 634 screen.txt |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo init_oolite_window pauseshell "Press [Enter] when you're ready" STATION_IND_WIDTH=32 STATION_IND_HEIGHT=32 STATION_IND_X=198 STATION_IND_Y=632 function capture_station_indicator(){ capture_snapshot $STATION_IND_WIDTH $STATION_IND_HEIGHT $STATION_IND_X $STATION_IND_Y } function find_station_pixel(){ capture_station_indicator | grep -m1 $1 } function set_station_ind_coords(){ local greenEnough='#[[:digit:]][[:digit:]][6-9|A-F][0-9|A-F][[:digit:]][[:digit:]]' local line=$(find_station_pixel $greenEnough) to_coords $line } function is_station_in_range(){ set_station_ind_coords if [ $curX -eq 7 ] && [ $curY -eq 2 ]; then return 0 fi return 1 } while true; do is_station_in_range if [ $? -eq 0 ]; then echo 'in range' else echo 'out of range' fi done |
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_rangewill 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
$curXof 7 and$curYof 2 inis_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
|
1 2 3 4 5 6 7 |
function dock() { sendkey shift+c waitfor 4 } |
Here’s our test script with the navigation function:
bot-test
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#!/usr/bin/env bash source bot-funcs.sh echo "bot-test" echo init_oolite_window pauseshell "Press [Enter] when you're ready" function navigate_to_station() { is_station_in_range while [ $? -eq 1 ]; do waitfor 1 vert_align_with_station waitfor 1 horz_align_with_station is_station_in_range done dock } navigate_to_station |
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
|
1 2 3 4 5 6 |
# Crosshair position to center on station/planet TARGET_MIN_X=15 TARGET_MIN_Y=13 |
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 equalinstead ofless-than.
bot-funcs.sh
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
function vert_align_with_station() { local delay=.1 # Don't bother with alignment if station is in range is_station_in_range if [ $? -eq 0 ]; then return fi set_crosshair_coords until [ $curY -le $TARGET_MIN_Y ]; do pitchdown 0.05 waitfor $delay set_crosshair_coords done if [ $curY -lt $TARGET_MIN_Y ]; then until [ $curY -ge $TARGET_MIN_Y ]; do pitchup 0.05 waitfor $delay set_crosshair_coords done fi } function horz_align_with_station() { local delay=.1 # Don't bother with alignment if station is in range is_station_in_range if [ $? -eq 0 ]; then return fi set_crosshair_coords until [ $curX -le $TARGET_MIN_X ]; do yaw_right 0.01 waitfor $delay set_crosshair_coords done if [ $curX -lt $TARGET_MIN_X ]; then until [ $curX -ge $TARGET_MIN_X ]; do yaw_left 0.01 waitfor $delay set_crosshair_coords done fi } |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#!/usr/bin/env bash source bot-funcs.sh # # Main trading route # function runroute() { refuel buycomputers markxeoner quicksave launch pitchdown 1.5 fireinjectors 4 enterhyper pitchdown 1.5 fireinjectors 20 jump waitfor 15 pitchup 1.6 navigate_to_station sellcomputers buyfurs refuel markxexedi quicksave launch pitchdown 1.5 fireinjectors 4 enterhyper pitchdown 1.5 fireinjectors 20 jump waitfor 15 pitchup 1.6 navigate_to_station sellfurs } echo "To start, you should be located on Xexedi station," echo "have NOT refueled yet," echo "with empty cargo holds," echo echo "Use Ctrl-c to exit" echo init_oolite_window pauseshell "Press [Enter] when you're ready" while true do runroute done |
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…



