mirror of
https://github.com/pret/pokemon-reverse-engineering-tools.git
synced 2026-03-21 17:24:42 -05:00
Merge branch 'github/master' into master
This commit is contained in:
commit
e0991710ef
|
|
@ -4,492 +4,580 @@ Programmatic speedrun of Pokémon Crystal
|
|||
"""
|
||||
import os
|
||||
|
||||
import pokemontools.configuration as configuration
|
||||
|
||||
# bring in the emulator and basic tools
|
||||
import vba
|
||||
|
||||
def main():
|
||||
"""
|
||||
Start the game.
|
||||
"""
|
||||
vba.load_rom()
|
||||
|
||||
# get past the opening sequence
|
||||
skip_intro()
|
||||
|
||||
# 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")
|
||||
|
||||
new_bark_level_grind(10, skip=False)
|
||||
import vba as _vba
|
||||
|
||||
def skippable(func):
|
||||
"""
|
||||
Makes a function skippable.
|
||||
|
||||
Saves the state before and after the function runs.
|
||||
Pass "skip=True" to the function to load the previous save
|
||||
state from when the function finished.
|
||||
Saves the state before and after the function runs. Pass "skip=True" to the
|
||||
function to load the previous save state from when the function finished.
|
||||
"""
|
||||
def wrapped_function(*args, **kwargs):
|
||||
self = args[0]
|
||||
skip = True
|
||||
override = True
|
||||
|
||||
if "skip" in kwargs.keys():
|
||||
skip = kwargs["skip"]
|
||||
del kwargs["skip"]
|
||||
|
||||
if "override" in kwargs.keys():
|
||||
override = kwargs["override"]
|
||||
del kwargs["override"]
|
||||
|
||||
# override skip if there's no save
|
||||
if skip:
|
||||
full_name = func.__name__ + "-end.sav"
|
||||
if not os.path.exists(os.path.join(vba.save_state_path, full_name)):
|
||||
if not os.path.exists(os.path.join(self.config.save_state_path, full_name)):
|
||||
skip = False
|
||||
|
||||
return_value = None
|
||||
|
||||
if not skip:
|
||||
vba.save_state(func.__name__ + "-start", override=True)
|
||||
if override:
|
||||
self.cry.save_state(func.__name__ + "-start", override=override)
|
||||
|
||||
return_value = func(*args, **kwargs)
|
||||
vba.save_state(func.__name__ + "-end", override=True)
|
||||
|
||||
if override:
|
||||
self.cry.save_state(func.__name__ + "-end", override=override)
|
||||
elif skip:
|
||||
vba.set_state(vba.load_state(func.__name__ + "-end"))
|
||||
self.cry.vba.state = self.cry.load_state(func.__name__ + "-end")
|
||||
|
||||
return return_value
|
||||
return wrapped_function
|
||||
|
||||
@skippable
|
||||
def skip_intro():
|
||||
class Runner(object):
|
||||
"""
|
||||
Skip the game boot intro sequence.
|
||||
``Runner`` is used to represent a set of functions that control an instance
|
||||
of the emulator. This allows for automated runs of games.
|
||||
"""
|
||||
pass
|
||||
|
||||
# copyright sequence
|
||||
vba.nstep(400)
|
||||
class SpeedRunner(Runner):
|
||||
def __init__(self, cry=None, config=None):
|
||||
super(SpeedRunner, self).__init__()
|
||||
|
||||
# skip the ditto sequence
|
||||
vba.press("a")
|
||||
vba.nstep(100)
|
||||
self.cry = cry
|
||||
|
||||
# skip the start screen
|
||||
vba.press("start")
|
||||
vba.nstep(100)
|
||||
if not config:
|
||||
config = configuration.Config()
|
||||
|
||||
# click "new game"
|
||||
vba.press("a", holdsteps=50, aftersteps=1)
|
||||
self.config = config
|
||||
|
||||
# skip text up to "Are you a boy? Or are you a girl?"
|
||||
vba.crystal.text_wait()
|
||||
|
||||
# select "Boy"
|
||||
vba.press("a", holdsteps=50, aftersteps=1)
|
||||
|
||||
# text until "What time is it?"
|
||||
vba.crystal.text_wait()
|
||||
|
||||
# select 10 o'clock
|
||||
vba.press("a", holdsteps=50, aftersteps=1)
|
||||
|
||||
# yes i mean it
|
||||
vba.press("a", holdsteps=50, aftersteps=1)
|
||||
|
||||
# "How many minutes?" 0 min.
|
||||
vba.press("a", holdsteps=50, aftersteps=1)
|
||||
|
||||
# "Who! 0 min.?" yes/no select yes
|
||||
vba.press("a", holdsteps=50, aftersteps=1)
|
||||
|
||||
# read text until name selection
|
||||
vba.crystal.text_wait()
|
||||
|
||||
# select "Chris"
|
||||
vba.press("d", holdsteps=10, aftersteps=1)
|
||||
vba.press("a", holdsteps=50, aftersteps=1)
|
||||
|
||||
def overworldcheck():
|
||||
def setup(self):
|
||||
"""
|
||||
A basic check for when the game starts.
|
||||
Configure this ``Runner`` instance to contain a reference to an active
|
||||
emulator session.
|
||||
"""
|
||||
return vba.get_memory_at(0xcfb1) != 0
|
||||
if not self.cry:
|
||||
self.cry = _vba.crystal(config=self.config)
|
||||
|
||||
# go until the introduction is done
|
||||
vba.crystal.text_wait(callback=overworldcheck)
|
||||
def main(self):
|
||||
"""
|
||||
Main entry point for complete control of the game as the main player.
|
||||
"""
|
||||
# get past the opening sequence
|
||||
self.skip_intro(skip=True)
|
||||
|
||||
return
|
||||
# walk to mom and handle her text
|
||||
self.handle_mom(skip=True)
|
||||
|
||||
@skippable
|
||||
def handle_mom():
|
||||
"""
|
||||
Walk to mom. Handle her speech and questions.
|
||||
"""
|
||||
# walk outside into new bark town
|
||||
self.walk_into_new_bark_town(skip=True)
|
||||
|
||||
vba.crystal.move("r")
|
||||
vba.crystal.move("r")
|
||||
vba.crystal.move("r")
|
||||
vba.crystal.move("r")
|
||||
# walk to elm and do whatever he wants
|
||||
self.handle_elm("totodile", skip=True)
|
||||
|
||||
vba.crystal.move("u")
|
||||
vba.crystal.move("u")
|
||||
vba.crystal.move("u")
|
||||
self.new_bark_level_grind(17, skip=False)
|
||||
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("d")
|
||||
@skippable
|
||||
def skip_intro(self, stop_at_name_selection=False):
|
||||
"""
|
||||
Skip the game boot intro sequence.
|
||||
"""
|
||||
|
||||
# move into mom's line of sight
|
||||
vba.crystal.move("d")
|
||||
# copyright sequence
|
||||
self.cry.nstep(400)
|
||||
|
||||
# let mom talk until "What day is it?"
|
||||
vba.crystal.text_wait()
|
||||
# skip the ditto sequence
|
||||
self.cry.vba.press("a")
|
||||
self.cry.nstep(100)
|
||||
|
||||
# "What day is it?" Sunday
|
||||
vba.press("a", holdsteps=10) # Sunday
|
||||
# skip the start screen
|
||||
self.cry.vba.press("start")
|
||||
self.cry.nstep(100)
|
||||
|
||||
vba.crystal.text_wait()
|
||||
# click "new game"
|
||||
self.cry.vba.press("a", hold=50, after=1)
|
||||
|
||||
# "SUNDAY, is it?" yes/no
|
||||
vba.press("a", holdsteps=10) # yes
|
||||
# skip text up to "Are you a boy? Or are you a girl?"
|
||||
self.cry.text_wait()
|
||||
|
||||
vba.crystal.text_wait()
|
||||
# select "Boy"
|
||||
self.cry.vba.press("a", hold=50, after=1)
|
||||
|
||||
# "Is it Daylight Saving Time now?" yes/no
|
||||
vba.press("a", holdsteps=10) # yes
|
||||
# text until "What time is it?"
|
||||
self.cry.text_wait()
|
||||
|
||||
vba.crystal.text_wait()
|
||||
# select 10 o'clock
|
||||
self.cry.vba.press("a", hold=50, after=1)
|
||||
|
||||
# "AM DST, is that OK?" yes/no
|
||||
vba.press("a", holdsteps=10) # yes
|
||||
# yes i mean it
|
||||
self.cry.vba.press("a", hold=50, after=1)
|
||||
|
||||
# text until "know how to use the PHONE?" yes/no
|
||||
vba.crystal.text_wait()
|
||||
# "How many minutes?" 0 min.
|
||||
self.cry.vba.press("a", hold=50, after=1)
|
||||
|
||||
# press yes
|
||||
vba.press("a", holdsteps=10)
|
||||
# "Who! 0 min.?" yes/no select yes
|
||||
self.cry.vba.press("a", hold=50, after=1)
|
||||
|
||||
# wait until mom is done talking
|
||||
vba.crystal.text_wait()
|
||||
# read text until name selection
|
||||
self.cry.text_wait()
|
||||
|
||||
# wait until the script is done running
|
||||
vba.crystal.wait_for_script_running()
|
||||
if stop_at_name_selection:
|
||||
return
|
||||
|
||||
return
|
||||
# select "Chris"
|
||||
self.cry.vba.press("d", hold=10, after=1)
|
||||
self.cry.vba.press("a", hold=50, after=1)
|
||||
|
||||
@skippable
|
||||
def walk_into_new_bark_town():
|
||||
"""
|
||||
Walk outside after talking with mom.
|
||||
"""
|
||||
def overworldcheck():
|
||||
"""
|
||||
A basic check for when the game starts.
|
||||
"""
|
||||
return self.cry.vba.memory[0xcfb1] != 0
|
||||
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("l")
|
||||
vba.crystal.move("l")
|
||||
# go until the introduction is done
|
||||
self.cry.text_wait(callback=overworldcheck)
|
||||
|
||||
# walk outside
|
||||
vba.crystal.move("d")
|
||||
|
||||
@skippable
|
||||
def handle_elm(starter_choice):
|
||||
"""
|
||||
Walk to Elm's Lab and get a starter.
|
||||
"""
|
||||
|
||||
# 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")
|
||||
|
||||
# walk into the lab
|
||||
vba.crystal.move("u")
|
||||
|
||||
# talk to elm
|
||||
vba.crystal.text_wait()
|
||||
|
||||
# "that I recently caught." yes/no
|
||||
vba.press("a", holdsteps=10) # yes
|
||||
|
||||
# talk to elm some more
|
||||
vba.crystal.text_wait()
|
||||
|
||||
# talking isn't done yet..
|
||||
vba.crystal.text_wait()
|
||||
vba.crystal.text_wait()
|
||||
vba.crystal.text_wait()
|
||||
|
||||
# wait until the script is done running
|
||||
vba.crystal.wait_for_script_running()
|
||||
|
||||
# move toward the pokeballs
|
||||
vba.crystal.move("r")
|
||||
|
||||
# move to cyndaquil
|
||||
vba.crystal.move("r")
|
||||
|
||||
moves = 0
|
||||
|
||||
if starter_choice.lower() == "cyndaquil":
|
||||
moves = 0
|
||||
if starter_choice.lower() == "totodile":
|
||||
moves = 1
|
||||
else:
|
||||
moves = 2
|
||||
|
||||
for each in range(0, moves):
|
||||
vba.crystal.move("r")
|
||||
|
||||
# face the pokeball
|
||||
vba.crystal.move("u")
|
||||
|
||||
# select it
|
||||
vba.press("a", holdsteps=10, aftersteps=0)
|
||||
|
||||
# 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()
|
||||
|
||||
# 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()
|
||||
|
||||
# wait for the script to end
|
||||
vba.crystal.wait_for_script_running()
|
||||
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("d")
|
||||
|
||||
# go outside
|
||||
vba.crystal.move("d")
|
||||
|
||||
return
|
||||
|
||||
@skippable
|
||||
def new_bark_level_grind(level):
|
||||
"""
|
||||
Do level grinding in New Bark.
|
||||
|
||||
Starting just outside of Elm's Lab, do some level grinding until the first
|
||||
partymon level is equal to the given value..
|
||||
"""
|
||||
|
||||
# walk to the grass area
|
||||
new_bark_level_grind_walk_to_grass(skip=False)
|
||||
|
||||
# TODO: walk around in grass, handle battles
|
||||
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_walk_to_grass():
|
||||
@skippable
|
||||
def handle_mom(self):
|
||||
"""
|
||||
Walk to mom. Handle her speech and questions.
|
||||
"""
|
||||
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
|
||||
# move into mom's line of sight
|
||||
self.cry.move("d")
|
||||
|
||||
# let mom talk until "What day is it?"
|
||||
self.cry.text_wait()
|
||||
|
||||
# "What day is it?" Sunday
|
||||
self.cry.vba.press("a", hold=10) # Sunday
|
||||
|
||||
self.cry.text_wait()
|
||||
|
||||
# "SUNDAY, is it?" yes/no
|
||||
self.cry.vba.press("a", hold=10) # yes
|
||||
|
||||
self.cry.text_wait()
|
||||
|
||||
# "Is it Daylight Saving Time now?" yes/no
|
||||
self.cry.vba.press("a", hold=10) # yes
|
||||
|
||||
self.cry.text_wait()
|
||||
|
||||
# "AM DST, is that OK?" yes/no
|
||||
self.cry.vba.press("a", hold=10) # yes
|
||||
|
||||
# text until "know how to use the PHONE?" yes/no
|
||||
self.cry.text_wait()
|
||||
|
||||
# press yes
|
||||
self.cry.vba.press("a", hold=10)
|
||||
|
||||
# wait until mom is done talking
|
||||
self.cry.text_wait()
|
||||
|
||||
# wait until the script is done running
|
||||
self.cry.wait_for_script_running()
|
||||
|
||||
return
|
||||
|
||||
@skippable
|
||||
def walk_into_new_bark_town(self):
|
||||
"""
|
||||
Walk outside after talking with mom.
|
||||
"""
|
||||
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
|
||||
# walk outside
|
||||
self.cry.move("d")
|
||||
|
||||
@skippable
|
||||
def handle_elm(self, starter_choice):
|
||||
"""
|
||||
Walk to Elm's Lab and get a starter.
|
||||
"""
|
||||
|
||||
# walk to the lab
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
|
||||
# walk into the lab
|
||||
self.cry.move("u")
|
||||
|
||||
# talk to elm
|
||||
self.cry.text_wait()
|
||||
|
||||
# "that I recently caught." yes/no
|
||||
self.cry.vba.press("a", hold=10) # yes
|
||||
|
||||
# talk to elm some more
|
||||
self.cry.text_wait()
|
||||
|
||||
# talking isn't done yet..
|
||||
self.cry.text_wait()
|
||||
self.cry.text_wait()
|
||||
self.cry.text_wait()
|
||||
|
||||
# wait until the script is done running
|
||||
self.cry.wait_for_script_running()
|
||||
|
||||
# move toward the pokeballs
|
||||
self.cry.move("r")
|
||||
|
||||
# move to cyndaquil
|
||||
self.cry.move("r")
|
||||
|
||||
moves = 0
|
||||
|
||||
if starter_choice.lower() == "cyndaquil":
|
||||
moves = 0
|
||||
elif starter_choice.lower() == "totodile":
|
||||
moves = 1
|
||||
else:
|
||||
moves = 2
|
||||
|
||||
for each in range(0, moves):
|
||||
self.cry.move("r")
|
||||
|
||||
# face the pokeball
|
||||
self.cry.move("u")
|
||||
|
||||
# select it
|
||||
self.cry.vba.press("a", hold=10, after=0)
|
||||
|
||||
# wait for the image to pop up
|
||||
self.cry.text_wait()
|
||||
|
||||
# wait for the image to close
|
||||
self.cry.text_wait()
|
||||
|
||||
# wait for the yes/no box
|
||||
self.cry.text_wait()
|
||||
|
||||
# press yes
|
||||
self.cry.vba.press("a", hold=10, after=0)
|
||||
|
||||
# wait for elm to talk a bit
|
||||
self.cry.text_wait()
|
||||
|
||||
# TODO: why didn't that finish his talking?
|
||||
self.cry.text_wait()
|
||||
|
||||
# give a nickname? yes/no
|
||||
self.cry.vba.press("d", hold=10, after=0) # move to "no"
|
||||
self.cry.vba.press("a", hold=10, after=0) # no
|
||||
|
||||
# TODO: why didn't this wait until he was completely done?
|
||||
self.cry.text_wait()
|
||||
self.cry.text_wait()
|
||||
|
||||
# get the phone number
|
||||
self.cry.text_wait()
|
||||
|
||||
# talk with elm a bit more
|
||||
self.cry.text_wait()
|
||||
|
||||
# wait until the script is done running
|
||||
self.cry.wait_for_script_running()
|
||||
|
||||
# move down
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
|
||||
# move into the researcher's line of sight
|
||||
self.cry.move("d")
|
||||
|
||||
# get the potion from the person
|
||||
self.cry.text_wait()
|
||||
self.cry.text_wait()
|
||||
|
||||
# wait for the script to end
|
||||
self.cry.wait_for_script_running()
|
||||
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
|
||||
# go outside
|
||||
self.cry.move("d")
|
||||
|
||||
return
|
||||
|
||||
@skippable
|
||||
def new_bark_level_grind(self, level, walk_to_grass=True):
|
||||
"""
|
||||
Do level grinding in New Bark.
|
||||
|
||||
Starting just outside of Elm's Lab, do some level grinding until the
|
||||
first partymon level is equal to the given value..
|
||||
"""
|
||||
|
||||
# walk to the grass area
|
||||
if walk_to_grass:
|
||||
self.new_bark_level_grind_walk_to_grass(skip=False)
|
||||
|
||||
last_direction = "u"
|
||||
|
||||
# walk around in the grass until a battle happens
|
||||
while self.cry.vba.memory[0xd22d] == 0:
|
||||
if last_direction == "u":
|
||||
direction = "d"
|
||||
else:
|
||||
direction = "u"
|
||||
|
||||
self.cry.move(direction)
|
||||
|
||||
last_direction = direction
|
||||
|
||||
# wait for wild battle to completely start
|
||||
self.cry.text_wait()
|
||||
|
||||
attacks = 5
|
||||
|
||||
while attacks > 0:
|
||||
# FIGHT
|
||||
self.cry.vba.press("a", hold=10, after=1)
|
||||
|
||||
# wait to select a move
|
||||
self.cry.text_wait()
|
||||
|
||||
# SCRATCH
|
||||
self.cry.vba.press("a", hold=10, after=1)
|
||||
|
||||
# wait for the move to be over
|
||||
self.cry.text_wait()
|
||||
|
||||
hp = self.cry.get_enemy_hp()
|
||||
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 self.cry.vba.memory[0xd22d] != 0:
|
||||
self.cry.vba.press("a", hold=10, after=1)
|
||||
|
||||
# wait for the map to finish loading
|
||||
self.cry.vba.step(count=50)
|
||||
|
||||
# This is used to handle any additional textbox that might be up on the
|
||||
# screen. The debug parameter is set to True so that max_wait is
|
||||
# enabled. This might be a textbox that is still waiting around because
|
||||
# of some faint during the battle. I am not completely sure why this
|
||||
# happens.
|
||||
self.cry.text_wait(max_wait=30, debug=True)
|
||||
|
||||
print "okay, back in the overworld"
|
||||
|
||||
cur_hp = ((self.cry.vba.memory[0xdd01] << 8) | self.cry.vba.memory[0xdd02])
|
||||
move_pp = self.cry.vba.memory[0xdcf6] # move 1 pp
|
||||
|
||||
# if pokemon health is >20, just continue
|
||||
# if move 1 PP is 0, just continue
|
||||
if cur_hp > 20 and move_pp > 5 and self.cry.vba.memory[0xdcfe] < level:
|
||||
self.cry.move("u")
|
||||
return self.new_bark_level_grind(level, walk_to_grass=False, skip=False)
|
||||
|
||||
# move up
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
|
||||
# move into new bark town
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
|
||||
# move up
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
|
||||
# move to the door
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
|
||||
# walk in
|
||||
self.cry.move("u")
|
||||
|
||||
# move up to the healing thing
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("u")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
|
||||
# face it
|
||||
self.cry.move("u")
|
||||
|
||||
# interact
|
||||
self.cry.vba.press("a", hold=10, after=1)
|
||||
|
||||
# wait for yes/no box
|
||||
self.cry.text_wait()
|
||||
|
||||
# press yes
|
||||
self.cry.vba.press("a", hold=10, after=1)
|
||||
|
||||
# TODO: when is healing done?
|
||||
|
||||
# wait until the script is done running
|
||||
self.cry.wait_for_script_running()
|
||||
|
||||
# wait for it to be really really done
|
||||
self.cry.vba.step(count=50)
|
||||
|
||||
self.cry.move("r")
|
||||
self.cry.move("r")
|
||||
|
||||
# move to the door
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
|
||||
# walk out
|
||||
self.cry.move("d")
|
||||
|
||||
# check partymon1 level
|
||||
if self.cry.vba.memory[0xdcfe] < level:
|
||||
self.new_bark_level_grind(level, skip=False)
|
||||
else:
|
||||
return
|
||||
|
||||
@skippable
|
||||
def new_bark_level_grind_walk_to_grass(self):
|
||||
"""
|
||||
Move to just above the grass from outside Elm's lab.
|
||||
"""
|
||||
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
|
||||
# move to route 29 past the trees
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
self.cry.move("l")
|
||||
|
||||
# move to just above the grass
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
self.cry.move("d")
|
||||
|
||||
def bootstrap(runner=None, cry=None):
|
||||
"""
|
||||
Move to just above the grass from outside Elm's lab.
|
||||
Setup the initial game and return the state. This skips the intro and
|
||||
performs some other actions to get the game to a reasonable starting state.
|
||||
"""
|
||||
if not runner:
|
||||
runner = SpeedRunner(cry=cry)
|
||||
runner.setup()
|
||||
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("d")
|
||||
# skip=False means always run the skip_intro function regardless of the
|
||||
# presence of a saved after state.
|
||||
runner.skip_intro(skip=True)
|
||||
|
||||
vba.crystal.move("l")
|
||||
vba.crystal.move("l")
|
||||
# keep a reference of the current state
|
||||
state = runner.cry.vba.state
|
||||
|
||||
vba.crystal.move("d")
|
||||
vba.crystal.move("d")
|
||||
runner.cry.vba.shutdown()
|
||||
|
||||
vba.crystal.move("l")
|
||||
vba.crystal.move("l")
|
||||
return state
|
||||
|
||||
# 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")
|
||||
def main():
|
||||
"""
|
||||
Setup a basic ``SpeedRunner`` instance and then run the runner.
|
||||
"""
|
||||
runner = SpeedRunner()
|
||||
runner.setup()
|
||||
return runner.main()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
193
pokemontools/vba/battle.py
Normal file
193
pokemontools/vba/battle.py
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
"""
|
||||
Code that attempts to model a battle.
|
||||
"""
|
||||
|
||||
from pokemontools.vba.vba import crystal as emulator
|
||||
import pokemontools.vba.vba as vba
|
||||
|
||||
class BattleException(Exception):
|
||||
"""
|
||||
Something went terribly wrong in a battle.
|
||||
"""
|
||||
|
||||
class EmulatorController(object):
|
||||
"""
|
||||
Controls the emulator. I don't have a good reason for this.
|
||||
"""
|
||||
|
||||
class Battle(EmulatorController):
|
||||
"""
|
||||
Wrapper around the battle routine inside of the game. This object controls
|
||||
the emulator and provides a sanitized interface for interacting with a
|
||||
battle through python.
|
||||
"""
|
||||
|
||||
def __init__(self, emulator=None):
|
||||
"""
|
||||
Setup the battle.
|
||||
"""
|
||||
self.emulator = emulator
|
||||
|
||||
def is_in_battle(self):
|
||||
"""
|
||||
@rtype: bool
|
||||
"""
|
||||
return self.emulator.is_in_battle()
|
||||
|
||||
def is_input_required(self):
|
||||
"""
|
||||
Detects if the battle is waiting for player input.
|
||||
"""
|
||||
return self.is_player_turn() or self.is_mandatory_switch()
|
||||
|
||||
def is_fight_pack_run_menu(self):
|
||||
"""
|
||||
Attempts to detect if the current menu is fight-pack-run. This is only
|
||||
for whether or not the player needs to choose what to do next.
|
||||
"""
|
||||
signs = ["FIGHT", "PACK", "RUN"]
|
||||
screentext = self.emulator.get_text()
|
||||
return all([sign in screentext for sign in signs])
|
||||
|
||||
def is_player_turn(self):
|
||||
"""
|
||||
Detects if the battle is waiting for the player to choose an attack.
|
||||
"""
|
||||
return self.is_fight_pack_run_menu()
|
||||
|
||||
def is_mandatory_switch(self):
|
||||
"""
|
||||
Detects if the battle is waiting for the player to choose a next
|
||||
pokemon.
|
||||
"""
|
||||
# TODO: test when "no" fails to escape for wild battles.
|
||||
# trainer battles: menu asks to select the next mon
|
||||
# wild battles: yes/no box first
|
||||
# The following conditions are probably sufficient:
|
||||
# 1) current pokemon hp is 0
|
||||
# 2) game is polling for input
|
||||
|
||||
if "CANCEL Which ?" in self.emulator.get_text():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def skip_start_text(self, max_loops=20):
|
||||
"""
|
||||
Skip any initial conversation until the player can select an action.
|
||||
This includes skipping any text that appears on a map from an NPC as
|
||||
well as text that appears prior to the first time the action selection
|
||||
menu appears.
|
||||
"""
|
||||
if not self.is_in_battle():
|
||||
while not self.is_in_battle() and max_loops > 0:
|
||||
self.emulator.text_wait()
|
||||
max_loops -= 1
|
||||
|
||||
if max_loops <= 0:
|
||||
raise Exception("Couldn't start the battle.")
|
||||
else:
|
||||
self.emulator.text_wait()
|
||||
|
||||
def skip_end_text(self, loops=20):
|
||||
"""
|
||||
Skip through any text that appears after the final attack.
|
||||
"""
|
||||
if not self.is_in_battle():
|
||||
# TODO: keep talking until the character can move? A battle can be
|
||||
# triggered inside of a script, and after the battle is ver the
|
||||
# player may not be able to move until the script is done. The
|
||||
# script might only finish after other player input is given, so
|
||||
# using "text_wait() until the player can move" is a bad idea here.
|
||||
self.emulator.text_wait()
|
||||
else:
|
||||
while self.is_in_battle() and loops > 0:
|
||||
self.emulator.text_wait()
|
||||
loops -= 1
|
||||
|
||||
if loops <= 0:
|
||||
raise Exception("Couldn't get out of the battle.")
|
||||
|
||||
def skip_until_input_required(self):
|
||||
"""
|
||||
Waits until the battle needs player input.
|
||||
"""
|
||||
while not self.is_input_required():
|
||||
self.emulator.text_wait()
|
||||
|
||||
# let the text draw so that the state is more obvious
|
||||
self.emulator.vba.step(count=10)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Step through the entire battle.
|
||||
"""
|
||||
# Advance to the battle from either of these states:
|
||||
# 1) the player is talking with an npc
|
||||
# 2) the battle has already started but there's initial text
|
||||
# xyz wants to battle, a wild foobar appeared
|
||||
self.skip_start_text()
|
||||
|
||||
while self.is_in_battle():
|
||||
self.skip_until_input_required()
|
||||
|
||||
if self.is_player_turn():
|
||||
# battle hook provides input to handle this situation
|
||||
self.handle_turn()
|
||||
elif self.is_mandatory_switch():
|
||||
# battle hook provides input to handle this situation too
|
||||
self.handle_mandatory_switch()
|
||||
else:
|
||||
raise BattleException("unknown state, aborting")
|
||||
|
||||
# "how did i lose? wah"
|
||||
self.skip_end_text()
|
||||
|
||||
# TODO: return should indicate win/loss (blackout)
|
||||
|
||||
def handle_mandatory_switch(self):
|
||||
"""
|
||||
Something fainted, pick the next mon.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def handle_turn(self):
|
||||
"""
|
||||
Take actions inside of a battle based on the game state.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
class BattleStrategy(Battle):
|
||||
"""
|
||||
Throw a pokeball until everyone dies.
|
||||
"""
|
||||
|
||||
def handle_mandatory_switch(self):
|
||||
"""
|
||||
Something fainted, pick the next mon.
|
||||
"""
|
||||
for pokemon in self.emulator.party:
|
||||
if pokemon.hp > 0:
|
||||
break
|
||||
else:
|
||||
# the game failed to do a blackout.. not sure what to do now.
|
||||
raise BattleException("No partymons left. wtf?")
|
||||
|
||||
return pokemon.id
|
||||
|
||||
def handle_turn(self):
|
||||
"""
|
||||
Take actions inside of a battle based on the game state.
|
||||
"""
|
||||
self.throw_pokeball()
|
||||
|
||||
class SimpleBattleStrategy(BattleStrategy):
|
||||
"""
|
||||
Attack the enemy with the first move.
|
||||
"""
|
||||
|
||||
def handle_turn(self):
|
||||
"""
|
||||
Always attack the enemy with the first move.
|
||||
"""
|
||||
self.attack(self.battle.party[0].moves[0].name)
|
||||
File diff suppressed because it is too large
Load Diff
54
tests/bootstrapping.py
Normal file
54
tests/bootstrapping.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
Functions to bootstrap the emulator state
|
||||
"""
|
||||
|
||||
from setup_vba import (
|
||||
vba,
|
||||
autoplayer,
|
||||
)
|
||||
|
||||
def bootstrap():
|
||||
"""
|
||||
Every test needs to be run against a certain minimum context. That context
|
||||
is constructed by this function.
|
||||
"""
|
||||
|
||||
cry = vba.crystal(config=None)
|
||||
runner = autoplayer.SpeedRunner(cry=cry)
|
||||
|
||||
# skip=False means run the skip_intro function instead of just skipping to
|
||||
# a saved state.
|
||||
runner.skip_intro(skip=True)
|
||||
|
||||
state = cry.vba.state
|
||||
|
||||
# clean everything up again
|
||||
cry.vba.shutdown()
|
||||
|
||||
return state
|
||||
|
||||
def bootstrap_trainer_battle():
|
||||
"""
|
||||
Start a trainer battle.
|
||||
"""
|
||||
# setup
|
||||
cry = vba.crystal(config=None)
|
||||
runner = autoplayer.SpeedRunner(cry=cry)
|
||||
|
||||
runner.skip_intro(skip=True)
|
||||
runner.handle_mom(skip=True)
|
||||
runner.walk_into_new_bark_town(skip=True)
|
||||
runner.handle_elm("totodile", skip=True)
|
||||
|
||||
# levelgrind a pokemon
|
||||
# TODO: make new_bark_level_grind able to figure out how to construct its
|
||||
# initial state if none is provided.
|
||||
runner.new_bark_level_grind(17, skip=True)
|
||||
|
||||
cry.givepoke(64, 31, "kAdAbRa")
|
||||
cry.givepoke(224, 60, "OcTiLlErY")
|
||||
cry.givepoke(126, 87, "magmar")
|
||||
|
||||
cry.start_trainer_battle()
|
||||
|
||||
return runner.cry.vba.state
|
||||
4
tests/setup_vba.py
Normal file
4
tests/setup_vba.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import pokemontools.vba.vba as vba
|
||||
import pokemontools.vba.keyboard as keyboard
|
||||
import pokemontools.vba.autoplayer as autoplayer
|
||||
autoplayer.vba = vba
|
||||
|
|
@ -4,81 +4,96 @@ Tests for VBA automation tools
|
|||
|
||||
import unittest
|
||||
|
||||
import pokemontools.vba.vba as vba
|
||||
from setup_vba import (
|
||||
vba,
|
||||
autoplayer,
|
||||
keyboard,
|
||||
)
|
||||
|
||||
try:
|
||||
import pokemontools.vba.vba_autoplayer
|
||||
except ImportError:
|
||||
import pokemontools.vba.autoplayer as vba_autoplayer
|
||||
|
||||
vba_autoplayer.vba = vba
|
||||
from bootstrapping import (
|
||||
bootstrap,
|
||||
bootstrap_trainer_battle,
|
||||
)
|
||||
|
||||
def setup_wram():
|
||||
"""
|
||||
Loads up some default addresses. Should eventually be replaced with the
|
||||
actual wram parser.
|
||||
"""
|
||||
# TODO: this should just be parsed straight out of wram.asm
|
||||
wram = {}
|
||||
wram["PlayerDirection"] = 0xd4de
|
||||
wram["PlayerAction"] = 0xd4e1
|
||||
wram["MapX"] = 0xd4e6
|
||||
wram["MapY"] = 0xd4e7
|
||||
|
||||
wram["WarpNumber"] = 0xdcb4
|
||||
wram["MapGroup"] = 0xdcb5
|
||||
wram["MapNumber"] = 0xdcb6
|
||||
wram["YCoord"] = 0xdcb7
|
||||
wram["XCoord"] = 0xdcb8
|
||||
|
||||
return wram
|
||||
|
||||
def bootstrap():
|
||||
"""
|
||||
Every test needs to be run against a certain minimum context. That context
|
||||
is constructed by this function.
|
||||
"""
|
||||
class OtherVbaTests(unittest.TestCase):
|
||||
def test_keyboard_planner(self):
|
||||
button_sequence = keyboard.plan_typing("an")
|
||||
expected_result = ["select", "a", "d", "r", "r", "r", "r", "a"]
|
||||
|
||||
# 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
|
||||
self.assertEqual(len(expected_result), len(button_sequence))
|
||||
self.assertEqual(expected_result, button_sequence)
|
||||
|
||||
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()
|
||||
cry = None
|
||||
wram = None
|
||||
|
||||
# FIXME: work around jython2.5 unittest
|
||||
state = bootstrap()
|
||||
wram = setup_wram()
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.bootstrap_state = bootstrap()
|
||||
|
||||
def get_wram_value(self, name):
|
||||
return vba.get_memory_at(self.wram[name])
|
||||
cls.wram = setup_wram()
|
||||
|
||||
cls.cry = vba.crystal()
|
||||
cls.vba = cls.cry.vba
|
||||
|
||||
cls.vba.state = cls.bootstrap_state
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.vba.shutdown()
|
||||
|
||||
def setUp(self):
|
||||
# clean the state
|
||||
vba.shutdown()
|
||||
vba.load_rom()
|
||||
|
||||
# reset to whatever the bootstrapper created
|
||||
vba.set_state(self.state)
|
||||
self.vba.state = self.bootstrap_state
|
||||
|
||||
def tearDown(self):
|
||||
vba.shutdown()
|
||||
def get_wram_value(self, name):
|
||||
return self.vba.memory[self.wram[name]]
|
||||
|
||||
def check_movement(self, direction="d"):
|
||||
"""
|
||||
Check if (y, x) before attempting to move and (y, x) after attempting
|
||||
to move are the same.
|
||||
"""
|
||||
start = (self.get_wram_value("MapY"), self.get_wram_value("MapX"))
|
||||
self.cry.move(direction)
|
||||
end = (self.get_wram_value("MapY"), self.get_wram_value("MapX"))
|
||||
return start != end
|
||||
|
||||
def bootstrap_name_prompt(self):
|
||||
runner = autoplayer.SpeedRunner(cry=None)
|
||||
runner.setup()
|
||||
runner.skip_intro(stop_at_name_selection=True, skip=False, override=False)
|
||||
|
||||
self.cry.vba.press("a", hold=20)
|
||||
|
||||
# wait for "Your name?" to show up
|
||||
while "YOUR NAME?" not in self.cry.get_text():
|
||||
self.cry.step(count=50)
|
||||
|
||||
def test_movement_changes_player_direction(self):
|
||||
player_direction = self.get_wram_value("PlayerDirection")
|
||||
|
||||
vba.crystal.move("u")
|
||||
self.cry.move("u")
|
||||
|
||||
# direction should have changed
|
||||
self.assertNotEqual(player_direction, self.get_wram_value("PlayerDirection"))
|
||||
|
|
@ -86,7 +101,7 @@ class VbaTests(unittest.TestCase):
|
|||
def test_movement_changes_y_coord(self):
|
||||
first_map_y = self.get_wram_value("MapY")
|
||||
|
||||
vba.crystal.move("u")
|
||||
self.cry.move("u")
|
||||
|
||||
# y location should be different
|
||||
second_map_y = self.get_wram_value("MapY")
|
||||
|
|
@ -96,11 +111,176 @@ class VbaTests(unittest.TestCase):
|
|||
# should start with standing
|
||||
self.assertEqual(self.get_wram_value("PlayerAction"), 1)
|
||||
|
||||
vba.crystal.move("l")
|
||||
self.cry.move("l")
|
||||
|
||||
# should be standing
|
||||
player_action = self.get_wram_value("PlayerAction")
|
||||
self.assertEqual(player_action, 1) # 1 = standing
|
||||
|
||||
def test_PlaceString(self):
|
||||
self.cry.call(0, 0x1078)
|
||||
|
||||
# where to draw the text
|
||||
self.cry.registers["hl"] = 0xc4a0
|
||||
|
||||
# what text to read from
|
||||
self.cry.registers["de"] = 0x1276
|
||||
|
||||
self.cry.vba.step(count=10)
|
||||
|
||||
text = self.cry.get_text()
|
||||
|
||||
self.assertTrue("TRAINER" in text)
|
||||
|
||||
def test_speedrunner_constructor(self):
|
||||
runner = autoplayer.SpeedRunner(cry=self.cry)
|
||||
|
||||
def test_speedrunner_handle_mom(self):
|
||||
# TODO: why can't i pass in the current state of the emulator?
|
||||
runner = autoplayer.SpeedRunner(cry=None)
|
||||
runner.setup()
|
||||
runner.skip_intro(skip=True)
|
||||
runner.handle_mom(skip=False)
|
||||
|
||||
# confirm that handle_mom is done by attempting to move on the map
|
||||
self.assertTrue(self.check_movement("d"))
|
||||
|
||||
def test_speedrunner_walk_into_new_bark_town(self):
|
||||
runner = autoplayer.SpeedRunner(cry=None)
|
||||
runner.setup()
|
||||
runner.skip_intro(skip=True)
|
||||
runner.handle_mom(skip=True)
|
||||
runner.walk_into_new_bark_town(skip=False)
|
||||
|
||||
# test that the game is in a state such that the player can walk
|
||||
self.assertTrue(self.check_movement("d"))
|
||||
|
||||
# check that the map is correct
|
||||
self.assertEqual(self.get_wram_value("MapGroup"), 24)
|
||||
self.assertEqual(self.get_wram_value("MapNumber"), 4)
|
||||
|
||||
def test_speedrunner_handle_elm(self):
|
||||
runner = autoplayer.SpeedRunner(cry=None)
|
||||
runner.setup()
|
||||
runner.skip_intro(skip=True)
|
||||
runner.handle_mom(skip=True)
|
||||
runner.walk_into_new_bark_town(skip=False)
|
||||
|
||||
# go through the Elm's Lab sequence
|
||||
runner.handle_elm("cyndaquil", skip=False)
|
||||
|
||||
# test again if the game is in a state where the player can walk
|
||||
self.assertTrue(self.check_movement("u"))
|
||||
|
||||
# check that the map is correct
|
||||
self.assertEqual(self.get_wram_value("MapGroup"), 24)
|
||||
self.assertEqual(self.get_wram_value("MapNumber"), 5)
|
||||
|
||||
def test_moving_back_and_forth(self):
|
||||
runner = autoplayer.SpeedRunner(cry=None)
|
||||
runner.setup()
|
||||
runner.skip_intro(skip=True)
|
||||
runner.handle_mom(skip=True)
|
||||
runner.walk_into_new_bark_town(skip=False)
|
||||
|
||||
# must be in New Bark Town
|
||||
self.assertEqual(self.get_wram_value("MapGroup"), 24)
|
||||
self.assertEqual(self.get_wram_value("MapNumber"), 4)
|
||||
|
||||
runner.cry.move("l")
|
||||
runner.cry.move("l")
|
||||
runner.cry.move("l")
|
||||
runner.cry.move("d")
|
||||
runner.cry.move("d")
|
||||
|
||||
for x in range(0, 10):
|
||||
runner.cry.move("l")
|
||||
runner.cry.move("d")
|
||||
runner.cry.move("r")
|
||||
runner.cry.move("u")
|
||||
|
||||
# must still be in New Bark Town
|
||||
self.assertEqual(self.get_wram_value("MapGroup"), 24)
|
||||
self.assertEqual(self.get_wram_value("MapNumber"), 4)
|
||||
|
||||
def test_crystal_move_list(self):
|
||||
runner = autoplayer.SpeedRunner(cry=None)
|
||||
runner.setup()
|
||||
runner.skip_intro(skip=True)
|
||||
runner.handle_mom(skip=True)
|
||||
runner.walk_into_new_bark_town(skip=False)
|
||||
|
||||
# must be in New Bark Town
|
||||
self.assertEqual(self.get_wram_value("MapGroup"), 24)
|
||||
self.assertEqual(self.get_wram_value("MapNumber"), 4)
|
||||
|
||||
first_map_x = self.get_wram_value("MapX")
|
||||
|
||||
runner.cry.move(["l", "l", "l"])
|
||||
|
||||
# x location should be different
|
||||
second_map_x = self.get_wram_value("MapX")
|
||||
self.assertNotEqual(first_map_x, second_map_x)
|
||||
|
||||
# must still be in New Bark Town
|
||||
self.assertEqual(self.get_wram_value("MapGroup"), 24)
|
||||
self.assertEqual(self.get_wram_value("MapNumber"), 4)
|
||||
|
||||
def test_keyboard_typing_dumb_name(self):
|
||||
self.bootstrap_name_prompt()
|
||||
|
||||
name = "tRaInEr"
|
||||
self.cry.write(name)
|
||||
|
||||
# save this selection
|
||||
self.cry.vba.press("a", hold=20)
|
||||
|
||||
self.assertEqual(name, self.cry.get_player_name())
|
||||
|
||||
def test_keyboard_typing_cap_name(self):
|
||||
names = [
|
||||
"trainer",
|
||||
"TRAINER",
|
||||
"TrAiNeR",
|
||||
"tRaInEr",
|
||||
"ExAmPlE",
|
||||
"Chris",
|
||||
"Kris",
|
||||
"beepaaa",
|
||||
"chris",
|
||||
"CHRIS",
|
||||
"Python",
|
||||
"pYthon",
|
||||
"pyThon",
|
||||
"pytHon",
|
||||
"pythOn",
|
||||
"pythoN",
|
||||
"python",
|
||||
"PyThOn",
|
||||
"Zot",
|
||||
"Death",
|
||||
"Hiro",
|
||||
"HIRO",
|
||||
]
|
||||
|
||||
self.bootstrap_name_prompt()
|
||||
start_state = self.cry.vba.state
|
||||
|
||||
for name in names:
|
||||
print "Writing name: " + name
|
||||
|
||||
self.cry.vba.state = start_state
|
||||
|
||||
sequence = self.cry.write(name)
|
||||
|
||||
print "sequence is: " + str(sequence)
|
||||
|
||||
# save this selection
|
||||
self.cry.vba.press("start", hold=20)
|
||||
self.cry.vba.press("a", hold=20)
|
||||
|
||||
pname = self.cry.get_player_name().replace("@", "")
|
||||
self.assertEqual(name, pname)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
93
tests/test_vba_battle.py
Normal file
93
tests/test_vba_battle.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
Tests for the battle controller
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from setup_vba import (
|
||||
vba,
|
||||
autoplayer,
|
||||
)
|
||||
|
||||
from pokemontools.vba.battle import (
|
||||
Battle,
|
||||
BattleException,
|
||||
)
|
||||
|
||||
from bootstrapping import (
|
||||
bootstrap,
|
||||
bootstrap_trainer_battle,
|
||||
)
|
||||
|
||||
class BattleTests(unittest.TestCase):
|
||||
cry = None
|
||||
vba = None
|
||||
bootstrap_state = None
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.cry = vba.crystal()
|
||||
cls.vba = cls.cry.vba
|
||||
|
||||
cls.bootstrap_state = bootstrap_trainer_battle()
|
||||
cls.vba.state = cls.bootstrap_state
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.vba.shutdown()
|
||||
|
||||
def setUp(self):
|
||||
# reset to whatever the bootstrapper created
|
||||
self.vba.state = self.bootstrap_state
|
||||
self.battle = Battle(emulator=self.cry)
|
||||
self.battle.skip_start_text()
|
||||
|
||||
def test_is_in_battle(self):
|
||||
self.assertTrue(self.battle.is_in_battle())
|
||||
|
||||
def test_is_player_turn(self):
|
||||
self.battle.skip_start_text()
|
||||
self.battle.skip_until_input_required()
|
||||
|
||||
# the initial state should be the player's turn
|
||||
self.assertTrue(self.battle.is_player_turn())
|
||||
|
||||
def test_is_mandatory_switch_initial(self):
|
||||
# should not be asking for a switch so soon in the battle
|
||||
self.assertFalse(self.battle.is_mandatory_switch())
|
||||
|
||||
def test_is_mandatory_switch(self):
|
||||
self.battle.skip_start_text()
|
||||
self.battle.skip_until_input_required()
|
||||
|
||||
# press "FIGHT"
|
||||
self.vba.press(["a"], after=20)
|
||||
|
||||
# press the first move ("SCRATCH")
|
||||
self.vba.press(["a"], after=20)
|
||||
|
||||
# set partymon1 hp to very low
|
||||
self.vba.write_memory_at(0xc63c, 0)
|
||||
self.vba.write_memory_at(0xc63d, 1)
|
||||
|
||||
# let the enemy attack and kill the pokemon
|
||||
self.battle.skip_until_input_required()
|
||||
|
||||
self.assertTrue(self.battle.is_mandatory_switch())
|
||||
|
||||
def test_attack_loop(self):
|
||||
self.battle.skip_start_text()
|
||||
self.battle.skip_until_input_required()
|
||||
|
||||
# press "FIGHT"
|
||||
self.vba.press(["a"], after=20)
|
||||
|
||||
# press the first move ("SCRATCH")
|
||||
self.vba.press(["a"], after=20)
|
||||
|
||||
self.battle.skip_until_input_required()
|
||||
|
||||
self.assertTrue(self.battle.is_player_turn())
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue
Block a user