Merge pull request #19 from kanzure/vba-automation

More VBA automation.
This commit is contained in:
Bryan Bishop 2013-09-08 17:11:17 -07:00
commit 87ab004d70
3 changed files with 545 additions and 297 deletions

106
tests/vba_tests.py Normal file
View File

@ -0,0 +1,106 @@
"""
Tests for VBA automation tools
"""
import unittest
import vba
try:
import vba_autoplayer
except ImportError:
import autoplayer as vba_autoplayer
vba_autoplayer.vba = vba
def setup_wram():
"""
Loads up some default addresses. Should eventually be replaced with the
actual wram parser.
"""
wram = {}
wram["PlayerDirection"] = 0xd4de
wram["PlayerAction"] = 0xd4e1
wram["MapX"] = 0xd4e6
wram["MapY"] = 0xd4e7
return wram
def bootstrap():
"""
Every test needs to be run against a certain minimum context. That context
is constructed by this function.
"""
# reset the rom
vba.shutdown()
vba.load_rom()
# skip=False means run the skip_intro function instead of just skipping to
# a saved state.
vba_autoplayer.skip_intro()
state = vba.get_state()
# clean everything up again
vba.shutdown()
return state
class VbaTests(unittest.TestCase):
# unittest in jython2.5 doesn't seem to have setUpClass ?? Man, why am I on
# jython2.5? This is ancient.
#@classmethod
#def setUpClass(cls):
# # get a good game state
# cls.state = bootstrap()
#
# # figure out addresses
# cls.wram = setup_wram()
# FIXME: work around jython2.5 unittest
state = bootstrap()
wram = setup_wram()
def get_wram_value(self, name):
return vba.get_memory_at(self.wram[name])
def setUp(self):
# clean the state
vba.shutdown()
vba.load_rom()
# reset to whatever the bootstrapper created
vba.set_state(self.state)
def tearDown(self):
vba.shutdown()
def test_movement_changes_player_direction(self):
player_direction = self.get_wram_value("PlayerDirection")
vba.crystal.move("u")
# direction should have changed
self.assertNotEqual(player_direction, self.get_wram_value("PlayerDirection"))
def test_movement_changes_y_coord(self):
first_map_y = self.get_wram_value("MapY")
vba.crystal.move("u")
# y location should be different
second_map_y = self.get_wram_value("MapY")
self.assertNotEqual(first_map_y, second_map_y)
def test_movement_ends_in_standing(self):
# should start with standing
self.assertEqual(self.get_wram_value("PlayerAction"), 1)
vba.crystal.move("l")
# should be standing
player_action = self.get_wram_value("PlayerAction")
self.assertEqual(player_action, 1) # 1 = standing
if __name__ == "__main__":
unittest.main()

133
vba.py
View File

@ -115,7 +115,7 @@ except ImportError:
print "Not loading the keyboard module (which uses networkx)."
if not os.path.exists(rom_path):
raise Exception("rom_path is not configured properly; edit vba_config.py?")
raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path))
def _check_java_library_path():
"""
@ -736,30 +736,112 @@ class cheats:
"""
Gb.cheatAddGameshark(code, description)
class crystal:
def get_stack():
"""
Return a list of functions on the stack.
"""
addresses = []
sp = registers.sp
for x in range(0, 11):
sp = sp - (2 * x)
hi = get_memory_at(sp + 1)
lo = get_memory_at(sp)
address = ((hi << 8) | lo)
addresses.append(address)
return addresses
class crystal:
"""
Just a simple namespace to store a bunch of functions for Pokémon Crystal.
"""
@staticmethod
def text_wait(step_size=10, max_wait=500):
def text_wait(step_size=1, max_wait=200, sfx_limit=0, debug=False, callback=None):
"""
Presses the "A" button when text is done being drawn to screen.
The `debug` parameter is only useful when debugging this function. It
enables the `max_wait` feature, which causes the function to exit
instead of hanging around.
The `sfx_limit` parameter is useful for when the player is given an
item during the text. Set it to 1 to not treat the sound as the end of
text. The next loop around it will return to the normal behavior of the
function.
:param step_size: number of steps per wait loop
:param max_wait: number of wait loops to perform
"""
for x in range(0, max_wait):
while max_wait > 0:
hi = get_memory_at(registers.sp + 1)
lo = get_memory_at(registers.sp)
address = ((hi << 8) | lo)
if address == 0xaef:
break
else:
nstep(step_size)
press("a", holdsteps=50, aftersteps=1)
if address in range(0xa1b, 0xa46) + range(0xaaf, 0xaf5): # 0xaef:
print "pressing, then breaking.. address is: " + str(hex(address))
# set CurSFX
set_memory_at(0xc2bf, 0)
press("a", holdsteps=10, aftersteps=1)
# check if CurSFX is SFX_READ_TEXT_2
if get_memory_at(0xc2bf) == 0x8:
print "cursfx is set to SFX_READ_TEXT_2, looping.."
return crystal.text_wait(step_size=step_size, max_wait=max_wait, debug=debug, callback=callback, sfx_limit=sfx_limit)
else:
if sfx_limit > 0:
sfx_limit = sfx_limit - 1
print "decreasing sfx_limit"
else:
# probably the last textbox in a sequence
print "cursfx is not set to SFX_READ_TEXT_2, so: breaking"
break
else:
stack = get_stack()
# yes/no box or the name selection box
if address in range(0xa46, 0xaaf):
print "probably at a yes/no box.. exiting."
break
# date/time box (day choice)
# 0x47ab is the one from the intro, 0x49ab is the one from mom.
elif 0x47ab in stack or 0x49ab in stack: # was any([x in stack for x in range(0x46EE, 0x47AB)])
print "probably at a date/time box ? exiting."
break
# "How many minutes?" selection box
elif 0x4826 in stack:
print "probably at a \"How many minutes?\" box ? exiting."
break
else:
nstep(step_size)
# if there is a callback, then call the callback and exit when the
# callback returns True. This is especially useful during the
# OakSpeech intro where textboxes are running constantly, and then
# suddenly the player can move around. One way to detect that is to
# set callback to a function that returns
# "vba.get_memory_at(0xcfb1) != 0".
if callback != None:
result = callback()
if result == True:
print "callback returned True, exiting"
return
# only useful when debugging. When this is left on, text that
# takes a while to print to screen will cause this function to
# exit.
if debug == True:
max_wait = max_wait - 1
if max_wait == 0:
print "max_wait was hit"
@staticmethod
def walk_through_walls_slow():
@ -1025,6 +1107,39 @@ class crystal:
set_memory(memory)
@staticmethod
def wait_for_script_running(debug=False, limit=1000):
"""
Wait until ScriptRunning isn't -1.
"""
while limit > 0:
if get_memory_at(0xd438) != 255:
print "script is done executing"
return
else:
step()
if debug:
limit = limit - 1
if limit == 0:
print "limit ran out"
@staticmethod
def move(cmd):
"""
Attempt to move the player.
"""
press(cmd, holdsteps=10, aftersteps=0)
press([])
memory = get_memory()
#while memory[0xd4e1] == 2 and memory[0xd042] != 0x3e:
while memory[0xd043] in [0, 1, 2, 3]:
#while memory[0xd043] in [0, 1, 2, 3] or memory[0xd042] != 0x3e:
nstep(10)
memory = get_memory()
class TestEmulator(unittest.TestCase):
try:
state = load_state("cheating-12")

View File

@ -19,6 +19,9 @@ def main():
# walk to mom and handle her text
handle_mom()
# walk outside into new bark town
walk_into_new_bark_town()
# walk to elm and do whatever he wants
handle_elm("totodile")
@ -62,6 +65,7 @@ def skip_intro():
"""
Skip the game boot intro sequence.
"""
# copyright sequence
vba.nstep(400)
@ -76,79 +80,42 @@ def skip_intro():
# click "new game"
vba.press("a", holdsteps=50, aftersteps=1)
# Are you a boy? Or are you a girl?
vba.nstep(145)
# skip text up to "Are you a boy? Or are you a girl?"
vba.crystal.text_wait()
# pick "boy"
# select "Boy"
vba.press("a", holdsteps=50, aftersteps=1)
# ....
# text until "What time is it?"
vba.crystal.text_wait()
vba.crystal.text_wait()
# What time is it?
vba.crystal.text_wait()
# DAY 10 o'clock
# select 10 o'clock
vba.press("a", holdsteps=50, aftersteps=1)
# WHAT? DAY 10 o'clock? YES/NO.
# yes i mean it
vba.press("a", holdsteps=50, aftersteps=1)
# How many minutes? 0 min.
# "How many minutes?" 0 min.
vba.press("a", holdsteps=50, aftersteps=1)
# Whoa! 0 min.? YES/NO.
# "Who! 0 min.?" yes/no select yes
vba.press("a", holdsteps=50, aftersteps=1)
vba.crystal.text_wait()
# read text until name selection
vba.crystal.text_wait()
# People call me
vba.crystal.text_wait()
vba.crystal.text_wait()
# tures that we call
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# everything about pokemon yet.
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# Now, what did you say your name was?
vba.crystal.text_wait()
# move down to "CHRIS"
vba.press("d")
vba.nstep(50)
# select "Chris"
vba.press("d", holdsteps=10, aftersteps=1)
vba.press("a", holdsteps=50, aftersteps=1)
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
def overworldcheck():
"""
A basic check for when the game starts.
"""
return vba.get_memory_at(0xcfb1) != 0
# wait until playable
# could be 150, but it sometimes takes longer??
vba.nstep(200)
# go until the introduction is done
vba.crystal.text_wait(callback=overworldcheck)
return
@ -157,280 +124,195 @@ def handle_mom():
"""
Walk to mom. Handle her speech and questions.
"""
vba.press("r"); vba.nstep(50)
vba.press("r"); vba.nstep(50)
vba.press("r"); vba.nstep(50)
vba.press("r"); vba.nstep(50)
vba.press("r"); vba.nstep(50)
vba.press("u"); vba.nstep(50)
vba.press("u"); vba.nstep(50)
vba.press("u"); vba.nstep(50)
vba.press("u"); vba.nstep(50)
# wait for next map to load
vba.nstep(50)
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.press("d"); vba.nstep(50)
vba.press("d"); vba.nstep(50)
vba.press("d"); vba.nstep(50)
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
# walk into mom's line of sight
vba.press("d"); vba.nstep(50)
vba.crystal.move("d")
vba.crystal.move("d")
vba.nstep(50)
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# move into mom's line of sight
vba.crystal.move("d")
# let mom talk until "What day is it?"
vba.crystal.text_wait()
# What day is it? SUNDAY.
vba.press("a", holdsteps=50, aftersteps=1)
# SUNDAY, is it? YES/NO
vba.press("a", holdsteps=50, aftersteps=1)
vba.nstep(200)
# is it DST now? YES/NO.
vba.press("a", holdsteps=50, aftersteps=1)
# 10:06 AM DST, is that OK? YES/NO.
vba.press("a", holdsteps=50, aftersteps=1)
# "What day is it?" Sunday
vba.press("a", holdsteps=10) # Sunday
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# know how to use the PHONE? YES/NO.
vba.press("a", holdsteps=50, aftersteps=1)
# "SUNDAY, is it?" yes/no
vba.press("a", holdsteps=10) # yes
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# "Is it Daylight Saving Time now?" yes/no
vba.press("a", holdsteps=10) # yes
vba.crystal.text_wait()
vba.press("a", holdsteps=50, aftersteps=1)
# "AM DST, is that OK?" yes/no
vba.press("a", holdsteps=10) # yes
# have to wait for her to move back :(
vba.nstep(50)
# text until "know how to use the PHONE?" yes/no
vba.crystal.text_wait()
# face down
vba.press("d"); vba.nstep(50)
# press yes
vba.press("a", holdsteps=10)
# wait until mom is done talking
vba.crystal.text_wait()
# wait until the script is done running
vba.crystal.wait_for_script_running()
return
@skippable
def walk_into_new_bark_town():
"""
Walk outside after talking with mom.
"""
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("l")
vba.crystal.move("l")
# walk outside
vba.crystal.move("d")
@skippable
def handle_elm(starter_choice):
"""
Walk to Elm's Lab and get a starter.
"""
# walk down
vba.press("d"); vba.nstep(50)
vba.press("d"); vba.nstep(50)
# walk to the lab
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("u")
vba.crystal.move("u")
# face left
vba.press("l"); vba.nstep(50)
# walk left
vba.press("l"); vba.nstep(50)
vba.press("l"); vba.nstep(50)
# face down
vba.press("d"); vba.nstep(50)
# walk down
vba.press("d"); vba.nstep(50)
# walk down to warp to outside
vba.press("d"); vba.nstep(50)
vba.nstep(10)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("u", holdsteps=10, aftersteps=50)
vba.press("u", holdsteps=10, aftersteps=50)
# warp into elm's lab (bottom left warp)
vba.press("u", holdsteps=5, aftersteps=50)
# let the script play
vba.nstep(200)
# walk into the lab
vba.crystal.move("u")
# talk to elm
vba.crystal.text_wait()
# I needed to ask you a fa
# "that I recently caught." yes/no
vba.press("a", holdsteps=10) # yes
# talk to elm some more
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# talking isn't done yet..
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.nstep(50)
# wait until the script is done running
vba.crystal.wait_for_script_running()
# YES/NO.
vba.press("a")
# move toward the pokeballs
vba.crystal.move("r")
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# move to cyndaquil
vba.crystal.move("r")
vba.press("a")
moves = 0
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.press("a")
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
for x in range(0, 8): # was 15
vba.crystal.text_wait()
vba.nstep(50)
vba.press("a")
vba.nstep(100)
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# Go on! Pick one.
vba.nstep(100)
vba.press("a"); vba.nstep(50)
vba.press("r"); vba.nstep(50)
vba.press("r"); vba.nstep(50)
right = 0
if starter_choice in [1, "cyndaquil"]:
right = 0
elif starter_choice in [2, "totodile"]:
right = 1
elif starter_choice in [3, "chikorita"]:
right = 2
if starter_choice.lower() == "cyndaquil":
moves = 0
if starter_choice.lower() == "totodile":
moves = 1
else:
raise Exception("bad starter")
moves = 2
for each in range(0, right):
vba.press("r"); vba.nstep(50)
for each in range(0, moves):
vba.crystal.move("r")
# look up
vba.press("u", holdsteps=5, aftersteps=50)
# face the pokeball
vba.crystal.move("u")
# get menu
vba.press("a", holdsteps=5, aftersteps=50)
# select it
vba.press("a", holdsteps=10, aftersteps=0)
# let the image show
vba.nstep(100)
# wait for the image to pop up
vba.crystal.text_wait()
# wait for the image to close
vba.crystal.text_wait()
# wait for the yes/no box
vba.crystal.text_wait()
# press yes
vba.press("a", holdsteps=10, aftersteps=0)
# wait for elm to talk a bit
vba.crystal.text_wait()
# TODO: why didn't that finish his talking?
vba.crystal.text_wait()
# give a nickname? yes/no
vba.press("d", holdsteps=10, aftersteps=0) # move to "no"
vba.press("a", holdsteps=10, aftersteps=0) # no
# TODO: why didn't this wait until he was completely done?
vba.crystal.text_wait()
vba.crystal.text_wait()
# YES/NO.
vba.press("a")
# get the phone number
vba.crystal.text_wait()
# talk with elm a bit more
vba.crystal.text_wait()
# TODO: and again.. wtf?
vba.crystal.text_wait()
# wait until the script is done running
vba.crystal.wait_for_script_running()
# move down
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
# move into the researcher's line of sight
vba.crystal.move("d")
# get the potion from the person
vba.crystal.text_wait()
vba.crystal.text_wait()
# received.. music is playing.
vba.press("a")
vba.crystal.text_wait()
# wait for the script to end
vba.crystal.wait_for_script_running()
# YES/NO (nickname)
vba.crystal.text_wait()
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.press("b")
# go outside
vba.crystal.move("d")
# get back to elm
vba.nstep(100)
vba.nstep(100)
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# phone number..
vba.press("a")
vba.crystal.text_wait()
vba.nstep(100)
vba.press("a")
vba.nstep(300)
vba.press("a")
vba.nstep(100)
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.crystal.text_wait()
# I'm counting on you!
vba.nstep(200)
vba.press("a")
vba.nstep(50)
# look down
vba.press("d", holdsteps=5, aftersteps=50)
# walk down
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.press("a")
vba.nstep(50)
vba.press("a")
vba.crystal.text_wait()
vba.crystal.text_wait()
vba.press("a")
vba.nstep(50)
vba.crystal.text_wait()
vba.press("a")
vba.nstep(100)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
# step outside
vba.nstep(40)
return
@skippable
def new_bark_level_grind(level):
@ -445,24 +327,169 @@ def new_bark_level_grind(level):
new_bark_level_grind_walk_to_grass(skip=False)
# TODO: walk around in grass, handle battles
# TODO: heal at the lab, then repeat entire function
walk = ["d", "d", "u", "d", "u", "d"]
for direction in walk:
vba.crystal.move(direction)
# wait for wild battle to completely start
vba.crystal.text_wait()
attacks = 5
while attacks > 0:
# FIGHT
vba.press("a", holdsteps=10, aftersteps=1)
# wait to select a move
vba.crystal.text_wait()
# SCRATCH
vba.press("a", holdsteps=10, aftersteps=1)
# wait for the move to be over
vba.crystal.text_wait()
hp = ((vba.get_memory_at(0xd218) << 8) | vba.get_memory_at(0xd217))
print "enemy hp is: " + str(hp)
if hp == 0:
print "enemy hp is zero, exiting"
break
else:
print "enemy hp is: " + str(hp)
attacks = attacks - 1
while vba.get_memory_at(0xd22d) != 0:
vba.press("a", holdsteps=10, aftersteps=1)
# wait for the map to finish loading
vba.nstep(50)
print "okay, back in the overworld"
# move up
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
# move into new bark town
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
# move up
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
# move to the door
vba.crystal.move("r")
vba.crystal.move("r")
vba.crystal.move("r")
# walk in
vba.crystal.move("u")
# move up to the healing thing
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("u")
vba.crystal.move("l")
vba.crystal.move("l")
# face it
vba.crystal.move("u")
# interact
vba.press("a", holdsteps=10, aftersteps=1)
# wait for yes/no box
vba.crystal.text_wait()
# press yes
vba.press("a", holdsteps=10, aftersteps=1)
# TODO: when is healing done?
# wait until the script is done running
vba.crystal.wait_for_script_running()
# wait for it to be really really done
vba.nstep(50)
vba.crystal.move("r")
vba.crystal.move("r")
# move to the door
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
# walk out
vba.crystal.move("d")
# check partymon1 level
if vba.get_memory_at(0xdcfe) < level:
new_bark_level_grind(level, skip=False)
else:
return
@skippable
def new_bark_level_grind_travel_to_grass():
def new_bark_level_grind_walk_to_grass():
"""
Move to just above the grass.
Move to just above the grass from outside Elm's lab.
"""
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.press("l", holdsteps=10, aftersteps=50)
vba.crystal.move("d")
vba.crystal.move("d")
vba.press("d", holdsteps=10, aftersteps=50)
vba.press("d", holdsteps=10, aftersteps=50)
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("l")
vba.crystal.move("l")
# move to route 29 past the trees
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
vba.crystal.move("l")
# move to just above the grass
vba.crystal.move("d")
vba.crystal.move("d")
vba.crystal.move("d")
if __name__ == "__main__":
main()