From 138637d8f367bd066a4ad5f3c592299c5cab35e4 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 8 Sep 2013 11:06:13 -0500 Subject: move vba tools into vba/ --- vba.py | 1181 ------------------------------------------------- vba/vba.py | 1181 +++++++++++++++++++++++++++++++++++++++++++++++++ vba/vba_autoplayer.py | 495 +++++++++++++++++++++ vba/vba_config.py | 12 + vba/vba_keyboard.py | 562 +++++++++++++++++++++++ vba_autoplayer.py | 495 --------------------- vba_config.py | 12 - vba_keyboard.py | 562 ----------------------- 8 files changed, 2250 insertions(+), 2250 deletions(-) delete mode 100644 vba.py create mode 100644 vba/vba.py create mode 100644 vba/vba_autoplayer.py create mode 100644 vba/vba_config.py create mode 100644 vba/vba_keyboard.py delete mode 100644 vba_autoplayer.py delete mode 100644 vba_config.py delete mode 100644 vba_keyboard.py diff --git a/vba.py b/vba.py deleted file mode 100644 index ea7bb2e..0000000 --- a/vba.py +++ /dev/null @@ -1,1181 +0,0 @@ -#!/usr/bin/jython -# -*- encoding: utf-8 -*- -""" -vba-clojure (but really it's jython/python/jvm) - -This is jython, not python. Use jython to run this file. Before running this -file, some of the dependencies need to be constructed. These can be obtained -from the vba-clojure project. - sudo apt-get install g++ libtool openjdk-6-jre openjdk-6-jdk libsdl1.2-dev mercurial ant autoconf jython - - export JAVA_INCLUDE_PATH=/usr/lib/jvm/java-6-openjdk-amd64/include/ - export JAVA_INCLUDE_PATH2=/usr/lib/jvm/java-6-openjdk-amd64/include/ - - hg clone http://hg.bortreb.com/vba-clojure - cd vba-clojure/ - ./dl-libs.sh - cd java/ - ant all - cd .. - autoreconf -i - ./configure - make - sudo make install - -Make sure vba-clojure bindings are in $CLASSPATH: - export CLASSPATH=$CLASSPATH:java/dist/gb-bindings.jar - -Make sure vba-clojure is available within "java.library.path": - sudo ln -s \ - $HOME/local/vba-clojure/vba-clojure/src/clojure/.libs/libvba.so.0.0.0 \ - /usr/lib/jni/libvba.so - -(In the above command, substitute the first path with the path of the vba-clojure -directory you made, if it is different.) - -Also make sure VisualBoyAdvance.cfg is somewhere in the $PATH for VBA to find. -A default configuration is provided in vba-clojure under src/. - -Usage (in jython, not python): - import vba - - # activate the laser beam - vba.load_rom("/path/to/baserom.gbc") - - # make the emulator eat some instructions - vba.nstep(300) - - # save the state because we're paranoid - copyrights = vba.get_state() - # or ... - vba.save_state("copyrights") - # >>> vba.load_state("copyrights") == copyrights - # True - - # play for a while, then press F12 - vba.run() - - # let's save the game again - vba.save_state("unknown-delete-me") - - # and let's go back to the other state - vba.set_state(copyrights) - - # or why not the other way around? - vba.set_state(vba.load_state("unknown-delete-me")) - - vba.get_memory_at(0xDCDA) - vba.set_memory_at(0xDCDB, 0xFF) - vba.get_memory_range(0xDCDA, 10) - -TOOD: - [ ] set a specific register - [ ] get a specific register - [ ] breakpoints - [ ] vgm stuff - [ ] gbz80disasm integration - [ ] pokecrystal.extras integration - -""" - -import os -import sys -import re -from array import array -import string -from copy import copy -import unittest - -# for converting bytes to readable text -from chars import chars - -from map_names import map_names - -# for _check_java_library_path -from java.lang import System - -# for passing states to the emulator -from java.nio import ByteBuffer - -# For getRegisters and other times we have to pass a java int array to a -# function. -import jarray - -# load in the vba-clojure bindings -import com.aurellem.gb.Gb as Gb - -# load the vba-clojure library -Gb.loadVBA() - -from vba_config import * - -try: - import vba_keyboard as keyboard -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? " + str(rom_path)) - -def _check_java_library_path(): - """ - Returns the value of java.library.path. - - The vba-clojure library must be compiled - and linked from this location. - """ - return System.getProperty("java.library.path") - -class RomList(list): - - """ - Simple wrapper to prevent a giant rom from being shown on screen. - """ - - def __init__(self, *args, **kwargs): - list.__init__(self, *args, **kwargs) - - def __repr__(self): - """ - Simplifies this object so that the output doesn't overflow stdout. - """ - return "RomList(too long)" - -button_masks = { - "a": 0x0001, - "b": 0x0002, - "r": 0x0010, - "l": 0x0020, - "u": 0x0040, - "d": 0x0080, - "select": 0x0004, - "start": 0x0008, - "restart": 0x0800, - "listen": -1, # what? -} - -# useful for directly stating what to press -a, b, r, l, u, d, select, start, restart = "a", "b", "r", "l", "u", "d", "select", "start", "restart" - -def button_combiner(buttons): - """ - Combines multiple button presses into an integer. - - This is used when sending a keypress to the emulator. - """ - result = 0 - - # String inputs need to be cleaned up so that "start" doesn't get - # recognized as "s" and "t" etc.. - if isinstance(buttons, str): - if "restart" in buttons: - buttons = buttons.replace("restart", "") - result |= button_masks["restart"] - if "start" in buttons: - buttons = buttons.replace("start", "") - result |= button_masks["start"] - if "select" in buttons: - buttons = buttons.replace("select", "") - result |= button_masks["select"] - - # allow for the "a, b" and "a b" formats - if ", " in buttons: - buttons = buttons.split(", ") - elif " " in buttons: - buttons = buttons.split(" ") - - if isinstance(buttons, list): - if len(buttons) > 9: - raise Exception("can't combine more than 9 buttons at a time") - - for each in buttons: - result |= button_masks[each] - - #print "button: " + str(result) - return result - -def load_rom(path=None): - """ - Starts the emulator with a certain ROM. - - Defaults to rom_path if no parameters are given. - """ - if path == None: - path = rom_path - try: - root = load_state("root") - except: - # "root.sav" is required because if you create it in the future, you - # will have to shutdown the emulator and possibly lose your state. Some - # functions require there to be at least one root state available to do - # computations between two states. - sys.stderr.write("ERROR: unable to read \"root.sav\", please run" - " generate_root() or get_root() to make an initial save.\n") - Gb.startEmulator(path) - -def shutdown(): - """ - Stops the emulator. Closes the window. - - The "opposite" of this is the load_rom function. - """ - Gb.shutdown() - -def step(): - """ - Advances the emulator forward by one step. - """ - Gb.step() - -def nstep(steplimit): - """ - Step the game forward by a certain number of instructions. - """ - for counter in range(0, steplimit): - Gb.step() - -def step_until_capture(): - """ - Loop step() until SDLK_F12 is detected. - """ - Gb.stepUntilCapture() - -# just some aliases for step_until_capture -run = go = step_until_capture - -def translate_chars(charz): - result = "" - for each in charz: - result += chars[each] - return result - -def _create_byte_buffer(data): - """ - Converts data into a ByteBuffer. - - This is useful for interfacing with the Gb class. - """ - buf = ByteBuffer.allocateDirect(len(data)) - if isinstance(data[0], int): - for byte in data: - buf.put(byte) - else: - for byte in data: - buf.put(ord(byte)) - return buf - -def set_state(state, do_step=False): - """ - Injects the given state into the emulator. - - Use do_step if you want to call step(), which also allows - SDL to render the latest frame. Note that the default is to - not step, and that the screen (if it is enabled) will appear - as if it still has the last state loaded. This is normal. - """ - Gb.loadState(_create_byte_buffer(state)) - if do_step: - step() - -def get_state(): - """ - Retrieves the current state of the emulator. - """ - buf = Gb.saveState() - state = [buf.get(x) for x in range(0, buf.capacity())] - arr = array("b") - arr.extend(state) - return arr.tostring() # instead of state - -def save_state(name, state=None, override=False): - """ - Saves the given state to save_state_path. - - The file format must be ".sav" - (and this will be appended to your string if necessary). - """ - if state == None: - state = get_state() - if len(name) < 4 or name[-4:] != ".sav": - name += ".sav" - save_path = os.path.join(save_state_path, name) - if not override and os.path.exists(save_path): - raise Exception("oops, save state path already exists: " + str(save_path)) - else: - # convert the state into a reasonable output - data = array('b') - data.extend(state) - output = data.tostring() - - file_handler = open(save_path, "wb") - file_handler.write(output) - file_handler.close() - -def load_state(name): - """ - Reads a state from file based on name. - - Looks in save_state_path for a file - with this name (".sav" is optional). - """ - save_path = os.path.join(save_state_path, name) - if not os.path.exists(save_path): - if len(name) < 4 or name[-4:] != ".sav": - name += ".sav" - save_path = os.path.join(save_state_path, name) - file_handler = open(save_path, "rb") - state = file_handler.read() - file_handler.close() - return state - -def generate_root(): - """ - Restarts the emulator and saves the initial state to "root.sav". - """ - shutdown() - load_rom() - root = get_state() - save_state("root", state=root, override=True) - return root - -def get_root(): - """ - Loads the root state. - - (Or restarts the emulator and creates a new root state.) - """ - try: - root = load_state("root") - except: - root = generate_root() - -def get_registers(): - """ - Returns a list of current register values. - """ - register_array = jarray.zeros(Gb.NUM_REGISTERS, "i") - Gb.getRegisters(register_array) - return list(register_array) - -def set_registers(registers): - """ - Applies the set of registers to the CPU. - """ - Gb.writeRegisters(registers) -write_registers = set_registers - -def get_rom(): - """ - Returns the ROM in bytes. - """ - rom_array = jarray.zeros(Gb.ROM_SIZE, "i") - Gb.getROM(rom_array) - return RomList(rom_array) - -def get_ram(): - """ - Returns the RAM in bytes. - """ - ram_array = jarray.zeros(Gb.RAM_SIZE, "i") - Gb.getRAM(ram_array) - return RomList(ram_array) - -def say_hello(): - """ - Test that the VBA/GB bindings are working. - """ - Gb.sayHello() - -def get_memory(): - """ - Returns memory in bytes. - """ - memory_size = 0x10000 - memory = jarray.zeros(memory_size, "i") - Gb.getMemory(memory) - return RomList(memory) - -def set_memory(memory): - """ - Sets memory in the emulator. - - Use get_memory() to retrieve the current state. - """ - Gb.writeMemory(memory) - -def get_pixels(): - """ - Returns a list of pixels on the screen display. - - Broken, probably. Use screenshot() instead. - """ - sys.stderr.write("ERROR: seems to be broken on VBA's end? Good luck. Use" - " screenshot() instead.\n") - size = Gb.DISPLAY_WIDTH * Gb.DISPLAY_HEIGHT - pixels = jarray.zeros(size, "i") - Gb.getPixels(pixels) - return RomList(pixels) - -def screenshot(filename, literal=False): - """ - Saves a PNG screenshot to the file at filename. - - Use literal if you want to store it in the current directory. - Default is to save it to screenshots/ under the project. - """ - screenshots_path = os.path.join(project_path, "screenshots/") - filename = os.path.join(screenshots_path, filename) - if len(filename) < 4 or filename[-4:] != ".png": - filename += ".png" - Gb.nwritePNG(filename) - print "Screenshot saved to: " + str(filename) -save_png = screenshot - -def read_memory(address): - """ - Read an integer at an address. - """ - return Gb.readMemory(address) -get_memory_at = read_memory - -def get_memory_range(start_address, byte_count): - """ - Returns a list of bytes. - - start_address - address to start reading at - byte_count - how many bytes (0 returns just 1 byte) - """ - bytez = [] - for counter in range(0, byte_count): - byte = get_memory_at(start_address + counter) - bytez.append(byte) - return bytez - -def set_memory_at(address, value): - """ - Sets a byte at a certain address in memory. - - This directly sets the memory instead of copying - the memory from the emulator. - """ - Gb.setMemoryAt(address, value) - -def press(buttons, holdsteps=1, aftersteps=1): - """ - Press a button. - - Use steplimit to say for how many steps you want to press - the button (try leaving it at the default, 1). - """ - if hasattr(buttons, "__len__"): - number = button_combiner(buttons) - elif isinstance(buttons, int): - number = buttons - else: - number = buttons - for step_counter in range(0, holdsteps): - Gb.step(number) - - # clear the button press - if aftersteps > 0: - for step_counter in range(0, aftersteps): - Gb.step(0) - -def get_buttons(): - """ - Returns the currentButtons[0] value - - (an integer with bits set for which - buttons are currently pressed). - """ - return Gb.getCurrentButtons() - -class State(RomList): - name = None - -class Recording: - def __init__(self): - self.frames = [] - self.states = {} - - def _get_frame_count(self): - return len(self.frames) - - frame_count = property(fget=_get_frame_count) - - def save(self, name=None): - """ - Saves the current state. - """ - state = State(get_state()) - state.name = name - self.states[self.frame_count] = state - - def load(self, name): - """ - Loads a state by name in the state list. - """ - for state in self.states.items(): - if state.name == name: - set_state(state) - return state - return False - - def step(self, stepcount=1, first_frame=0, replay=False): - """ - Records button presses for each frame. - """ - if replay: - stepcount = len(self.frames[first_name:]) - - for counter in range(first_frame, stepcount): - if replay: - press(self.frames[counter], steplimit=0) - else: - self.frames.append(get_buttons()) - nstep(1) - - def replay_from(self, thing): - """ - Replays based on a State or the name of a saved state. - """ - if isinstance(thing, State): - set_state(thing) - else: - thing = self.load(thing) - frame_id = self.states.index(thing) - self.step(first_frame=frame_id, replay=True) - -class Registers: - order = [ - "pc", - "sp", - "af", - "bc", - "de", - "hl", - "iff", - "div", - "tima", - "tma", - "tac", - "if", - "lcdc", - "stat", - "scy", - "scx", - "ly", - "lyc", - "dma", - "wy", - "wx", - "vbk", - "hdma1", - "hdma2", - "hdma3", - "hdma4", - "hdma5", - "svbk", - "ie", - ] - - def __setitem__(self, key, value): - current_registers = get_registers() - current_registers[Registers.order.index(key)] = value - set_registers(current_registers) - - def __getitem__(self, key): - current_registers = get_registers() - return current_registers[Registers.order.index(key)] - - def __list__(self): - return get_registers() - - def _get_register(id): - def constructed_func(self, id=copy(id)): - return get_registers()[id] - return constructed_func - - def _set_register(id): - def constructed_func(self, value, id=copy(id)): - current_registers = get_registers() - current_registers[id] = value - set_registers(current_registers) - return constructed_func - - pc = property(fget=_get_register(0), fset=_set_register(0)) - sp = property(fget=_get_register(1), fset=_set_register(1)) - af = property(fget=_get_register(2), fset=_set_register(2)) - bc = property(fget=_get_register(3), fset=_set_register(3)) - de = property(fget=_get_register(4), fset=_set_register(4)) - hl = property(fget=_get_register(5), fset=_set_register(5)) - iff = property(fget=_get_register(6), fset=_set_register(6)) - div = property(fget=_get_register(7), fset=_set_register(7)) - tima = property(fget=_get_register(8), fset=_set_register(8)) - tma = property(fget=_get_register(9), fset=_set_register(9)) - tac = property(fget=_get_register(10), fset=_set_register(10)) - _if = property(fget=_get_register(11), fset=_set_register(11)) - lcdc = property(fget=_get_register(12), fset=_set_register(12)) - stat = property(fget=_get_register(13), fset=_set_register(13)) - scy = property(fget=_get_register(14), fset=_set_register(14)) - scx = property(fget=_get_register(15), fset=_set_register(15)) - ly = property(fget=_get_register(16), fset=_set_register(16)) - lyc = property(fget=_get_register(17), fset=_set_register(17)) - dma = property(fget=_get_register(18), fset=_set_register(18)) - wy = property(fget=_get_register(19), fset=_set_register(19)) - wx = property(fget=_get_register(20), fset=_set_register(20)) - vbk = property(fget=_get_register(21), fset=_set_register(21)) - hdma1 = property(fget=_get_register(22), fset=_set_register(22)) - hdma2 = property(fget=_get_register(23), fset=_set_register(23)) - hdma3 = property(fget=_get_register(24), fset=_set_register(24)) - hdma4 = property(fget=_get_register(25), fset=_set_register(25)) - hdma5 = property(fget=_get_register(26), fset=_set_register(26)) - svbk = property(fget=_get_register(27), fset=_set_register(27)) - ie = property(fget=_get_register(28), fset=_set_register(28)) - - def __repr__(self): - spacing = "\t" - output = "Registers:\n" - for (id, each) in enumerate(self.order): - output += spacing + each + " = " + hex(get_registers()[id]) - #hex(self[each]) - output += "\n" - return output - -registers = Registers() - -def call(bank, address): - """ - Jumps into a function at a certain address. - - Go into the start menu, pause the game and try call(1, 0x1078) to see a - string printed to the screen. - """ - push = [ - registers.pc, - registers.hl, - registers.de, - registers.bc, - registers.af, - 0x3bb7, - ] - - for value in push: - registers.sp -= 2 - set_memory_at(registers.sp + 1, value >> 8) - set_memory_at(registers.sp, value & 0xFF) - if get_memory_range(registers.sp, 2) != [value & 0xFF, value >> 8]: - print "desired memory values: " + str([value & 0xFF, value >> 8] ) - print "actual memory values: " + str(get_memory_range(registers.sp , 2)) - print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(get_memory_at(registers.sp)) - - if bank != 0: - registers["af"] = (bank << 8) | (registers["af"] & 0xFF) - registers["hl"] = address - registers["pc"] = 0x2d63 # FarJump - else: - registers["pc"] = address - -class cheats: - """ - Helpers to manage the cheating infrastructure. - - import vba; vba.load_rom(); vba.cheats.add_gameshark("0100CFCF", "text speedup 1"); vba.cheats.add_gameshark("0101CCCF", "text speedup 2"); vba.go() - """ - - @staticmethod - def enable(id): - """ - void gbCheatEnable(int i) - """ - Gb.cheatEnable(id) - - @staticmethod - def disable(id): - """ - void gbCheatDisable(int i) - """ - Gb.cheatDisable(id) - - @staticmethod - def load_file(filename): - """ - Loads a .clt file. By default each cheat is disabled. - """ - Gb.loadCheatsFromFile(filename) - - @staticmethod - def remove_all(): - """ - Removes all cheats from memory. - - void gbCheatRemoveAll() - """ - Gb.cheatRemoveAll() - - @staticmethod - def remove_cheat(id): - """ - Removes a specific cheat from memory by id. - - void gbCheatRemove(int i) - """ - Gb.cheatRemove(id) - - @staticmethod - def add_gamegenie(code, description=""): - """ - void gbAddGgCheat(const char *code, const char *desc) - """ - Gb.cheatAddGamegenie(code, description) - - @staticmethod - def add_gameshark(code, description=""): - """ - gbAddGsCheat(const char *code, const char *desc) - """ - Gb.cheatAddGameshark(code, description) - -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=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 - """ - while max_wait > 0: - hi = get_memory_at(registers.sp + 1) - lo = get_memory_at(registers.sp) - address = ((hi << 8) | lo) - - 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(): - memory = get_memory() - memory[0xC2FA] = 0 - memory[0xC2FB] = 0 - memory[0xC2FC] = 0 - memory[0xC2FD] = 0 - set_memory(memory) - - @staticmethod - def walk_through_walls(): - """ - Lets the player walk all over the map. - - These values are probably reset by some of the map/collision - functions when you move on to a new location, so this needs - to be executed each step/tick if continuous walk-through-walls - is desired. - """ - set_memory_at(0xC2FA, 0) - set_memory_at(0xC2FB, 0) - set_memory_at(0xC2FC, 0) - set_memory_at(0xC2FD, 0) - - #@staticmethod - #def set_enemy_level(level): - # set_memory_at(0xd213, level) - - @staticmethod - def nstep(steplimit=500): - """ - Steps the CPU forward and calls some functions in between each step. - - (For example, to manipulate memory.) This is pretty slow. - """ - for step_counter in range(0, steplimit): - crystal.walk_through_walls() - #call(0x1, 0x1078) - step() - - @staticmethod - def disable_triggers(): - set_memory_at(0x23c4, 0xAF) - set_memory_at(0x23d0, 0xAF); - - @staticmethod - def disable_callbacks(): - set_memory_at(0x23f2, 0xAF) - set_memory_at(0x23fe, 0xAF) - - @staticmethod - def get_map_group_id(): - """ - Returns the current map group. - """ - return get_memory_at(0xdcb5) - - @staticmethod - def get_map_id(): - """ - Returns the map number of the current map. - """ - return get_memory_at(0xdcb6) - - @staticmethod - def get_map_name(): - """ - Figures out the current map name. - """ - map_group_id = crystal.get_map_group_id() - map_id = crystal.get_map_id() - return map_names[map_group_id][map_id]["name"] - - @staticmethod - def get_xy(): - """ - (x, y) coordinates of player on map. - - Relative to top-left corner of map. - """ - x = get_memory_at(0xdcb8) - y = get_memory_at(0xdcb7) - return (x, y) - - @staticmethod - def menu_select(id=1): - """ - Sets the cursor to the given pokemon in the player's party. - - This is under Start -> PKMN. This is useful for selecting a - certain pokemon with fly or another skill. - - This probably works on other menus. - """ - set_memory_at(0xcfa9, id) - - @staticmethod - def is_in_battle(): - """ - Checks whether or not we're in a battle. - """ - return (get_memory_at(0xd22d) != 0) or crystal.is_in_link_battle() - - @staticmethod - def is_in_link_battle(): - return get_memory_at(0xc2dc) != 0 - - @staticmethod - def unlock_flypoints(): - """ - Unlocks different destinations for flying. - - Note: this might start at 0xDCA4 (minus one on all addresses), but not - sure. - """ - set_memory_at(0xDCA5, 0xFF) - set_memory_at(0xDCA6, 0xFF) - set_memory_at(0xDCA7, 0xFF) - set_memory_at(0xDCA8, 0xFF) - - @staticmethod - def get_gender(): - """ - Returns 'male' or 'female'. - """ - gender = get_memory_at(0xD472) - if gender == 0: - return "male" - elif gender == 1: - return "female" - else: - return gender - - @staticmethod - def get_player_name(): - """ - Returns the 7 characters making up the player's name. - """ - bytez = get_memory_range(0xD47D, 7) - name = translate_chars(bytez) - return name - - @staticmethod - def warp(map_group_id, map_id, x, y): - set_memory_at(0xdcb5, map_group_id) - set_memory_at(0xdcb6, map_id) - set_memory_at(0xdcb7, y) - set_memory_at(0xdcb8, x) - set_memory_at(0xd001, 0xFF) - set_memory_at(0xff9f, 0xF1) - set_memory_at(0xd432, 1) - set_memory_at(0xd434, 0 & 251) - - @staticmethod - def warp_pokecenter(): - crystal.warp(1, 1, 3, 3) - crystal.nstep(200) - - @staticmethod - def masterballs(): - # masterball - set_memory_at(0xd8d8, 1) - set_memory_at(0xd8d9, 99) - - # ultraball - set_memory_at(0xd8da, 2) - set_memory_at(0xd8db, 99) - - # pokeballs - set_memory_at(0xd8dc, 5) - set_memory_at(0xd8dd, 99) - - @staticmethod - def get_text(): - """ - Returns alphanumeric text on the screen. - - Other characters will not be shown. - """ - output = "" - tiles = get_memory_range(0xc4a0, 1000) - for each in tiles: - if each in chars.keys(): - thing = chars[each] - acceptable = False - - if len(thing) == 2: - portion = thing[1:] - else: - portion = thing - - if portion in string.printable: - acceptable = True - - if acceptable: - output += thing - - # remove extra whitespace - output = re.sub(" +", " ", output) - output = output.strip() - - return output - - @staticmethod - def keyboard_apply(button_sequence): - """ - Applies a sequence of buttons to the on-screen keyboard. - """ - for buttons in button_sequence: - press(buttons) - nstep(2) - press([]) - - @staticmethod - def write(something="TrAiNeR"): - """ - Types out a word. - - Uses a planning algorithm to do this in the most efficient way possible. - """ - button_sequence = keyboard.plan_typing(something) - crystal.keyboard_apply([[x] for x in button_sequence]) - - @staticmethod - def set_partymon2(): - """ - This causes corruption, so it's not working yet. - """ - memory = get_memory() - memory[0xdcd7] = 2 - memory[0xdcd9] = 0x7 - - memory[0xdd0f] = 0x7 - memory[0xdd10] = 0x1 - - # moves - memory[0xdd11] = 0x1 - memory[0xdd12] = 0x2 - memory[0xdd13] = 0x3 - memory[0xdd14] = 0x4 - - # id - memory[0xdd15] = 0x1 - memory[0xdd16] = 0x2 - - # experience - memory[0xdd17] = 0x2 - memory[0xdd18] = 0x3 - memory[0xdd19] = 0x4 - - # hp - memory[0xdd1a] = 0x5 - memory[0xdd1b] = 0x6 - - # current hp - memory[0xdd31] = 0x10 - memory[0xdd32] = 0x25 - - # max hp - memory[0xdd33] = 0x10 - memory[0xdd34] = 0x40 - - 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") - except: - if "__name__" == "__main__": - raise Exception("failed to setup unit tests because no save state found") - - def setUp(self): - load_rom() - set_state(self.state) - - def tearDown(self): - shutdown() - - def test_PlaceString(self): - call(0, 0x1078) - - # where to draw the text - registers["hl"] = 0xc4a0 - - # what text to read from - registers["de"] = 0x1276 - - nstep(10) - - text = crystal.get_text() - - self.assertTrue("TRAINER" in text) - -class TestWriter(unittest.TestCase): - def test_very_basic(self): - button_sequence = keyboard.plan_typing("an") - expected_result = ["select", "a", "d", "r", "r", "r", "r", "a"] - - self.assertEqual(len(expected_result), len(button_sequence)) - self.assertEqual(expected_result, button_sequence) - -if __name__ == "__main__": - unittest.main() diff --git a/vba/vba.py b/vba/vba.py new file mode 100644 index 0000000..ea7bb2e --- /dev/null +++ b/vba/vba.py @@ -0,0 +1,1181 @@ +#!/usr/bin/jython +# -*- encoding: utf-8 -*- +""" +vba-clojure (but really it's jython/python/jvm) + +This is jython, not python. Use jython to run this file. Before running this +file, some of the dependencies need to be constructed. These can be obtained +from the vba-clojure project. + sudo apt-get install g++ libtool openjdk-6-jre openjdk-6-jdk libsdl1.2-dev mercurial ant autoconf jython + + export JAVA_INCLUDE_PATH=/usr/lib/jvm/java-6-openjdk-amd64/include/ + export JAVA_INCLUDE_PATH2=/usr/lib/jvm/java-6-openjdk-amd64/include/ + + hg clone http://hg.bortreb.com/vba-clojure + cd vba-clojure/ + ./dl-libs.sh + cd java/ + ant all + cd .. + autoreconf -i + ./configure + make + sudo make install + +Make sure vba-clojure bindings are in $CLASSPATH: + export CLASSPATH=$CLASSPATH:java/dist/gb-bindings.jar + +Make sure vba-clojure is available within "java.library.path": + sudo ln -s \ + $HOME/local/vba-clojure/vba-clojure/src/clojure/.libs/libvba.so.0.0.0 \ + /usr/lib/jni/libvba.so + +(In the above command, substitute the first path with the path of the vba-clojure +directory you made, if it is different.) + +Also make sure VisualBoyAdvance.cfg is somewhere in the $PATH for VBA to find. +A default configuration is provided in vba-clojure under src/. + +Usage (in jython, not python): + import vba + + # activate the laser beam + vba.load_rom("/path/to/baserom.gbc") + + # make the emulator eat some instructions + vba.nstep(300) + + # save the state because we're paranoid + copyrights = vba.get_state() + # or ... + vba.save_state("copyrights") + # >>> vba.load_state("copyrights") == copyrights + # True + + # play for a while, then press F12 + vba.run() + + # let's save the game again + vba.save_state("unknown-delete-me") + + # and let's go back to the other state + vba.set_state(copyrights) + + # or why not the other way around? + vba.set_state(vba.load_state("unknown-delete-me")) + + vba.get_memory_at(0xDCDA) + vba.set_memory_at(0xDCDB, 0xFF) + vba.get_memory_range(0xDCDA, 10) + +TOOD: + [ ] set a specific register + [ ] get a specific register + [ ] breakpoints + [ ] vgm stuff + [ ] gbz80disasm integration + [ ] pokecrystal.extras integration + +""" + +import os +import sys +import re +from array import array +import string +from copy import copy +import unittest + +# for converting bytes to readable text +from chars import chars + +from map_names import map_names + +# for _check_java_library_path +from java.lang import System + +# for passing states to the emulator +from java.nio import ByteBuffer + +# For getRegisters and other times we have to pass a java int array to a +# function. +import jarray + +# load in the vba-clojure bindings +import com.aurellem.gb.Gb as Gb + +# load the vba-clojure library +Gb.loadVBA() + +from vba_config import * + +try: + import vba_keyboard as keyboard +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? " + str(rom_path)) + +def _check_java_library_path(): + """ + Returns the value of java.library.path. + + The vba-clojure library must be compiled + and linked from this location. + """ + return System.getProperty("java.library.path") + +class RomList(list): + + """ + Simple wrapper to prevent a giant rom from being shown on screen. + """ + + def __init__(self, *args, **kwargs): + list.__init__(self, *args, **kwargs) + + def __repr__(self): + """ + Simplifies this object so that the output doesn't overflow stdout. + """ + return "RomList(too long)" + +button_masks = { + "a": 0x0001, + "b": 0x0002, + "r": 0x0010, + "l": 0x0020, + "u": 0x0040, + "d": 0x0080, + "select": 0x0004, + "start": 0x0008, + "restart": 0x0800, + "listen": -1, # what? +} + +# useful for directly stating what to press +a, b, r, l, u, d, select, start, restart = "a", "b", "r", "l", "u", "d", "select", "start", "restart" + +def button_combiner(buttons): + """ + Combines multiple button presses into an integer. + + This is used when sending a keypress to the emulator. + """ + result = 0 + + # String inputs need to be cleaned up so that "start" doesn't get + # recognized as "s" and "t" etc.. + if isinstance(buttons, str): + if "restart" in buttons: + buttons = buttons.replace("restart", "") + result |= button_masks["restart"] + if "start" in buttons: + buttons = buttons.replace("start", "") + result |= button_masks["start"] + if "select" in buttons: + buttons = buttons.replace("select", "") + result |= button_masks["select"] + + # allow for the "a, b" and "a b" formats + if ", " in buttons: + buttons = buttons.split(", ") + elif " " in buttons: + buttons = buttons.split(" ") + + if isinstance(buttons, list): + if len(buttons) > 9: + raise Exception("can't combine more than 9 buttons at a time") + + for each in buttons: + result |= button_masks[each] + + #print "button: " + str(result) + return result + +def load_rom(path=None): + """ + Starts the emulator with a certain ROM. + + Defaults to rom_path if no parameters are given. + """ + if path == None: + path = rom_path + try: + root = load_state("root") + except: + # "root.sav" is required because if you create it in the future, you + # will have to shutdown the emulator and possibly lose your state. Some + # functions require there to be at least one root state available to do + # computations between two states. + sys.stderr.write("ERROR: unable to read \"root.sav\", please run" + " generate_root() or get_root() to make an initial save.\n") + Gb.startEmulator(path) + +def shutdown(): + """ + Stops the emulator. Closes the window. + + The "opposite" of this is the load_rom function. + """ + Gb.shutdown() + +def step(): + """ + Advances the emulator forward by one step. + """ + Gb.step() + +def nstep(steplimit): + """ + Step the game forward by a certain number of instructions. + """ + for counter in range(0, steplimit): + Gb.step() + +def step_until_capture(): + """ + Loop step() until SDLK_F12 is detected. + """ + Gb.stepUntilCapture() + +# just some aliases for step_until_capture +run = go = step_until_capture + +def translate_chars(charz): + result = "" + for each in charz: + result += chars[each] + return result + +def _create_byte_buffer(data): + """ + Converts data into a ByteBuffer. + + This is useful for interfacing with the Gb class. + """ + buf = ByteBuffer.allocateDirect(len(data)) + if isinstance(data[0], int): + for byte in data: + buf.put(byte) + else: + for byte in data: + buf.put(ord(byte)) + return buf + +def set_state(state, do_step=False): + """ + Injects the given state into the emulator. + + Use do_step if you want to call step(), which also allows + SDL to render the latest frame. Note that the default is to + not step, and that the screen (if it is enabled) will appear + as if it still has the last state loaded. This is normal. + """ + Gb.loadState(_create_byte_buffer(state)) + if do_step: + step() + +def get_state(): + """ + Retrieves the current state of the emulator. + """ + buf = Gb.saveState() + state = [buf.get(x) for x in range(0, buf.capacity())] + arr = array("b") + arr.extend(state) + return arr.tostring() # instead of state + +def save_state(name, state=None, override=False): + """ + Saves the given state to save_state_path. + + The file format must be ".sav" + (and this will be appended to your string if necessary). + """ + if state == None: + state = get_state() + if len(name) < 4 or name[-4:] != ".sav": + name += ".sav" + save_path = os.path.join(save_state_path, name) + if not override and os.path.exists(save_path): + raise Exception("oops, save state path already exists: " + str(save_path)) + else: + # convert the state into a reasonable output + data = array('b') + data.extend(state) + output = data.tostring() + + file_handler = open(save_path, "wb") + file_handler.write(output) + file_handler.close() + +def load_state(name): + """ + Reads a state from file based on name. + + Looks in save_state_path for a file + with this name (".sav" is optional). + """ + save_path = os.path.join(save_state_path, name) + if not os.path.exists(save_path): + if len(name) < 4 or name[-4:] != ".sav": + name += ".sav" + save_path = os.path.join(save_state_path, name) + file_handler = open(save_path, "rb") + state = file_handler.read() + file_handler.close() + return state + +def generate_root(): + """ + Restarts the emulator and saves the initial state to "root.sav". + """ + shutdown() + load_rom() + root = get_state() + save_state("root", state=root, override=True) + return root + +def get_root(): + """ + Loads the root state. + + (Or restarts the emulator and creates a new root state.) + """ + try: + root = load_state("root") + except: + root = generate_root() + +def get_registers(): + """ + Returns a list of current register values. + """ + register_array = jarray.zeros(Gb.NUM_REGISTERS, "i") + Gb.getRegisters(register_array) + return list(register_array) + +def set_registers(registers): + """ + Applies the set of registers to the CPU. + """ + Gb.writeRegisters(registers) +write_registers = set_registers + +def get_rom(): + """ + Returns the ROM in bytes. + """ + rom_array = jarray.zeros(Gb.ROM_SIZE, "i") + Gb.getROM(rom_array) + return RomList(rom_array) + +def get_ram(): + """ + Returns the RAM in bytes. + """ + ram_array = jarray.zeros(Gb.RAM_SIZE, "i") + Gb.getRAM(ram_array) + return RomList(ram_array) + +def say_hello(): + """ + Test that the VBA/GB bindings are working. + """ + Gb.sayHello() + +def get_memory(): + """ + Returns memory in bytes. + """ + memory_size = 0x10000 + memory = jarray.zeros(memory_size, "i") + Gb.getMemory(memory) + return RomList(memory) + +def set_memory(memory): + """ + Sets memory in the emulator. + + Use get_memory() to retrieve the current state. + """ + Gb.writeMemory(memory) + +def get_pixels(): + """ + Returns a list of pixels on the screen display. + + Broken, probably. Use screenshot() instead. + """ + sys.stderr.write("ERROR: seems to be broken on VBA's end? Good luck. Use" + " screenshot() instead.\n") + size = Gb.DISPLAY_WIDTH * Gb.DISPLAY_HEIGHT + pixels = jarray.zeros(size, "i") + Gb.getPixels(pixels) + return RomList(pixels) + +def screenshot(filename, literal=False): + """ + Saves a PNG screenshot to the file at filename. + + Use literal if you want to store it in the current directory. + Default is to save it to screenshots/ under the project. + """ + screenshots_path = os.path.join(project_path, "screenshots/") + filename = os.path.join(screenshots_path, filename) + if len(filename) < 4 or filename[-4:] != ".png": + filename += ".png" + Gb.nwritePNG(filename) + print "Screenshot saved to: " + str(filename) +save_png = screenshot + +def read_memory(address): + """ + Read an integer at an address. + """ + return Gb.readMemory(address) +get_memory_at = read_memory + +def get_memory_range(start_address, byte_count): + """ + Returns a list of bytes. + + start_address - address to start reading at + byte_count - how many bytes (0 returns just 1 byte) + """ + bytez = [] + for counter in range(0, byte_count): + byte = get_memory_at(start_address + counter) + bytez.append(byte) + return bytez + +def set_memory_at(address, value): + """ + Sets a byte at a certain address in memory. + + This directly sets the memory instead of copying + the memory from the emulator. + """ + Gb.setMemoryAt(address, value) + +def press(buttons, holdsteps=1, aftersteps=1): + """ + Press a button. + + Use steplimit to say for how many steps you want to press + the button (try leaving it at the default, 1). + """ + if hasattr(buttons, "__len__"): + number = button_combiner(buttons) + elif isinstance(buttons, int): + number = buttons + else: + number = buttons + for step_counter in range(0, holdsteps): + Gb.step(number) + + # clear the button press + if aftersteps > 0: + for step_counter in range(0, aftersteps): + Gb.step(0) + +def get_buttons(): + """ + Returns the currentButtons[0] value + + (an integer with bits set for which + buttons are currently pressed). + """ + return Gb.getCurrentButtons() + +class State(RomList): + name = None + +class Recording: + def __init__(self): + self.frames = [] + self.states = {} + + def _get_frame_count(self): + return len(self.frames) + + frame_count = property(fget=_get_frame_count) + + def save(self, name=None): + """ + Saves the current state. + """ + state = State(get_state()) + state.name = name + self.states[self.frame_count] = state + + def load(self, name): + """ + Loads a state by name in the state list. + """ + for state in self.states.items(): + if state.name == name: + set_state(state) + return state + return False + + def step(self, stepcount=1, first_frame=0, replay=False): + """ + Records button presses for each frame. + """ + if replay: + stepcount = len(self.frames[first_name:]) + + for counter in range(first_frame, stepcount): + if replay: + press(self.frames[counter], steplimit=0) + else: + self.frames.append(get_buttons()) + nstep(1) + + def replay_from(self, thing): + """ + Replays based on a State or the name of a saved state. + """ + if isinstance(thing, State): + set_state(thing) + else: + thing = self.load(thing) + frame_id = self.states.index(thing) + self.step(first_frame=frame_id, replay=True) + +class Registers: + order = [ + "pc", + "sp", + "af", + "bc", + "de", + "hl", + "iff", + "div", + "tima", + "tma", + "tac", + "if", + "lcdc", + "stat", + "scy", + "scx", + "ly", + "lyc", + "dma", + "wy", + "wx", + "vbk", + "hdma1", + "hdma2", + "hdma3", + "hdma4", + "hdma5", + "svbk", + "ie", + ] + + def __setitem__(self, key, value): + current_registers = get_registers() + current_registers[Registers.order.index(key)] = value + set_registers(current_registers) + + def __getitem__(self, key): + current_registers = get_registers() + return current_registers[Registers.order.index(key)] + + def __list__(self): + return get_registers() + + def _get_register(id): + def constructed_func(self, id=copy(id)): + return get_registers()[id] + return constructed_func + + def _set_register(id): + def constructed_func(self, value, id=copy(id)): + current_registers = get_registers() + current_registers[id] = value + set_registers(current_registers) + return constructed_func + + pc = property(fget=_get_register(0), fset=_set_register(0)) + sp = property(fget=_get_register(1), fset=_set_register(1)) + af = property(fget=_get_register(2), fset=_set_register(2)) + bc = property(fget=_get_register(3), fset=_set_register(3)) + de = property(fget=_get_register(4), fset=_set_register(4)) + hl = property(fget=_get_register(5), fset=_set_register(5)) + iff = property(fget=_get_register(6), fset=_set_register(6)) + div = property(fget=_get_register(7), fset=_set_register(7)) + tima = property(fget=_get_register(8), fset=_set_register(8)) + tma = property(fget=_get_register(9), fset=_set_register(9)) + tac = property(fget=_get_register(10), fset=_set_register(10)) + _if = property(fget=_get_register(11), fset=_set_register(11)) + lcdc = property(fget=_get_register(12), fset=_set_register(12)) + stat = property(fget=_get_register(13), fset=_set_register(13)) + scy = property(fget=_get_register(14), fset=_set_register(14)) + scx = property(fget=_get_register(15), fset=_set_register(15)) + ly = property(fget=_get_register(16), fset=_set_register(16)) + lyc = property(fget=_get_register(17), fset=_set_register(17)) + dma = property(fget=_get_register(18), fset=_set_register(18)) + wy = property(fget=_get_register(19), fset=_set_register(19)) + wx = property(fget=_get_register(20), fset=_set_register(20)) + vbk = property(fget=_get_register(21), fset=_set_register(21)) + hdma1 = property(fget=_get_register(22), fset=_set_register(22)) + hdma2 = property(fget=_get_register(23), fset=_set_register(23)) + hdma3 = property(fget=_get_register(24), fset=_set_register(24)) + hdma4 = property(fget=_get_register(25), fset=_set_register(25)) + hdma5 = property(fget=_get_register(26), fset=_set_register(26)) + svbk = property(fget=_get_register(27), fset=_set_register(27)) + ie = property(fget=_get_register(28), fset=_set_register(28)) + + def __repr__(self): + spacing = "\t" + output = "Registers:\n" + for (id, each) in enumerate(self.order): + output += spacing + each + " = " + hex(get_registers()[id]) + #hex(self[each]) + output += "\n" + return output + +registers = Registers() + +def call(bank, address): + """ + Jumps into a function at a certain address. + + Go into the start menu, pause the game and try call(1, 0x1078) to see a + string printed to the screen. + """ + push = [ + registers.pc, + registers.hl, + registers.de, + registers.bc, + registers.af, + 0x3bb7, + ] + + for value in push: + registers.sp -= 2 + set_memory_at(registers.sp + 1, value >> 8) + set_memory_at(registers.sp, value & 0xFF) + if get_memory_range(registers.sp, 2) != [value & 0xFF, value >> 8]: + print "desired memory values: " + str([value & 0xFF, value >> 8] ) + print "actual memory values: " + str(get_memory_range(registers.sp , 2)) + print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(get_memory_at(registers.sp)) + + if bank != 0: + registers["af"] = (bank << 8) | (registers["af"] & 0xFF) + registers["hl"] = address + registers["pc"] = 0x2d63 # FarJump + else: + registers["pc"] = address + +class cheats: + """ + Helpers to manage the cheating infrastructure. + + import vba; vba.load_rom(); vba.cheats.add_gameshark("0100CFCF", "text speedup 1"); vba.cheats.add_gameshark("0101CCCF", "text speedup 2"); vba.go() + """ + + @staticmethod + def enable(id): + """ + void gbCheatEnable(int i) + """ + Gb.cheatEnable(id) + + @staticmethod + def disable(id): + """ + void gbCheatDisable(int i) + """ + Gb.cheatDisable(id) + + @staticmethod + def load_file(filename): + """ + Loads a .clt file. By default each cheat is disabled. + """ + Gb.loadCheatsFromFile(filename) + + @staticmethod + def remove_all(): + """ + Removes all cheats from memory. + + void gbCheatRemoveAll() + """ + Gb.cheatRemoveAll() + + @staticmethod + def remove_cheat(id): + """ + Removes a specific cheat from memory by id. + + void gbCheatRemove(int i) + """ + Gb.cheatRemove(id) + + @staticmethod + def add_gamegenie(code, description=""): + """ + void gbAddGgCheat(const char *code, const char *desc) + """ + Gb.cheatAddGamegenie(code, description) + + @staticmethod + def add_gameshark(code, description=""): + """ + gbAddGsCheat(const char *code, const char *desc) + """ + Gb.cheatAddGameshark(code, description) + +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=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 + """ + while max_wait > 0: + hi = get_memory_at(registers.sp + 1) + lo = get_memory_at(registers.sp) + address = ((hi << 8) | lo) + + 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(): + memory = get_memory() + memory[0xC2FA] = 0 + memory[0xC2FB] = 0 + memory[0xC2FC] = 0 + memory[0xC2FD] = 0 + set_memory(memory) + + @staticmethod + def walk_through_walls(): + """ + Lets the player walk all over the map. + + These values are probably reset by some of the map/collision + functions when you move on to a new location, so this needs + to be executed each step/tick if continuous walk-through-walls + is desired. + """ + set_memory_at(0xC2FA, 0) + set_memory_at(0xC2FB, 0) + set_memory_at(0xC2FC, 0) + set_memory_at(0xC2FD, 0) + + #@staticmethod + #def set_enemy_level(level): + # set_memory_at(0xd213, level) + + @staticmethod + def nstep(steplimit=500): + """ + Steps the CPU forward and calls some functions in between each step. + + (For example, to manipulate memory.) This is pretty slow. + """ + for step_counter in range(0, steplimit): + crystal.walk_through_walls() + #call(0x1, 0x1078) + step() + + @staticmethod + def disable_triggers(): + set_memory_at(0x23c4, 0xAF) + set_memory_at(0x23d0, 0xAF); + + @staticmethod + def disable_callbacks(): + set_memory_at(0x23f2, 0xAF) + set_memory_at(0x23fe, 0xAF) + + @staticmethod + def get_map_group_id(): + """ + Returns the current map group. + """ + return get_memory_at(0xdcb5) + + @staticmethod + def get_map_id(): + """ + Returns the map number of the current map. + """ + return get_memory_at(0xdcb6) + + @staticmethod + def get_map_name(): + """ + Figures out the current map name. + """ + map_group_id = crystal.get_map_group_id() + map_id = crystal.get_map_id() + return map_names[map_group_id][map_id]["name"] + + @staticmethod + def get_xy(): + """ + (x, y) coordinates of player on map. + + Relative to top-left corner of map. + """ + x = get_memory_at(0xdcb8) + y = get_memory_at(0xdcb7) + return (x, y) + + @staticmethod + def menu_select(id=1): + """ + Sets the cursor to the given pokemon in the player's party. + + This is under Start -> PKMN. This is useful for selecting a + certain pokemon with fly or another skill. + + This probably works on other menus. + """ + set_memory_at(0xcfa9, id) + + @staticmethod + def is_in_battle(): + """ + Checks whether or not we're in a battle. + """ + return (get_memory_at(0xd22d) != 0) or crystal.is_in_link_battle() + + @staticmethod + def is_in_link_battle(): + return get_memory_at(0xc2dc) != 0 + + @staticmethod + def unlock_flypoints(): + """ + Unlocks different destinations for flying. + + Note: this might start at 0xDCA4 (minus one on all addresses), but not + sure. + """ + set_memory_at(0xDCA5, 0xFF) + set_memory_at(0xDCA6, 0xFF) + set_memory_at(0xDCA7, 0xFF) + set_memory_at(0xDCA8, 0xFF) + + @staticmethod + def get_gender(): + """ + Returns 'male' or 'female'. + """ + gender = get_memory_at(0xD472) + if gender == 0: + return "male" + elif gender == 1: + return "female" + else: + return gender + + @staticmethod + def get_player_name(): + """ + Returns the 7 characters making up the player's name. + """ + bytez = get_memory_range(0xD47D, 7) + name = translate_chars(bytez) + return name + + @staticmethod + def warp(map_group_id, map_id, x, y): + set_memory_at(0xdcb5, map_group_id) + set_memory_at(0xdcb6, map_id) + set_memory_at(0xdcb7, y) + set_memory_at(0xdcb8, x) + set_memory_at(0xd001, 0xFF) + set_memory_at(0xff9f, 0xF1) + set_memory_at(0xd432, 1) + set_memory_at(0xd434, 0 & 251) + + @staticmethod + def warp_pokecenter(): + crystal.warp(1, 1, 3, 3) + crystal.nstep(200) + + @staticmethod + def masterballs(): + # masterball + set_memory_at(0xd8d8, 1) + set_memory_at(0xd8d9, 99) + + # ultraball + set_memory_at(0xd8da, 2) + set_memory_at(0xd8db, 99) + + # pokeballs + set_memory_at(0xd8dc, 5) + set_memory_at(0xd8dd, 99) + + @staticmethod + def get_text(): + """ + Returns alphanumeric text on the screen. + + Other characters will not be shown. + """ + output = "" + tiles = get_memory_range(0xc4a0, 1000) + for each in tiles: + if each in chars.keys(): + thing = chars[each] + acceptable = False + + if len(thing) == 2: + portion = thing[1:] + else: + portion = thing + + if portion in string.printable: + acceptable = True + + if acceptable: + output += thing + + # remove extra whitespace + output = re.sub(" +", " ", output) + output = output.strip() + + return output + + @staticmethod + def keyboard_apply(button_sequence): + """ + Applies a sequence of buttons to the on-screen keyboard. + """ + for buttons in button_sequence: + press(buttons) + nstep(2) + press([]) + + @staticmethod + def write(something="TrAiNeR"): + """ + Types out a word. + + Uses a planning algorithm to do this in the most efficient way possible. + """ + button_sequence = keyboard.plan_typing(something) + crystal.keyboard_apply([[x] for x in button_sequence]) + + @staticmethod + def set_partymon2(): + """ + This causes corruption, so it's not working yet. + """ + memory = get_memory() + memory[0xdcd7] = 2 + memory[0xdcd9] = 0x7 + + memory[0xdd0f] = 0x7 + memory[0xdd10] = 0x1 + + # moves + memory[0xdd11] = 0x1 + memory[0xdd12] = 0x2 + memory[0xdd13] = 0x3 + memory[0xdd14] = 0x4 + + # id + memory[0xdd15] = 0x1 + memory[0xdd16] = 0x2 + + # experience + memory[0xdd17] = 0x2 + memory[0xdd18] = 0x3 + memory[0xdd19] = 0x4 + + # hp + memory[0xdd1a] = 0x5 + memory[0xdd1b] = 0x6 + + # current hp + memory[0xdd31] = 0x10 + memory[0xdd32] = 0x25 + + # max hp + memory[0xdd33] = 0x10 + memory[0xdd34] = 0x40 + + 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") + except: + if "__name__" == "__main__": + raise Exception("failed to setup unit tests because no save state found") + + def setUp(self): + load_rom() + set_state(self.state) + + def tearDown(self): + shutdown() + + def test_PlaceString(self): + call(0, 0x1078) + + # where to draw the text + registers["hl"] = 0xc4a0 + + # what text to read from + registers["de"] = 0x1276 + + nstep(10) + + text = crystal.get_text() + + self.assertTrue("TRAINER" in text) + +class TestWriter(unittest.TestCase): + def test_very_basic(self): + button_sequence = keyboard.plan_typing("an") + expected_result = ["select", "a", "d", "r", "r", "r", "r", "a"] + + self.assertEqual(len(expected_result), len(button_sequence)) + self.assertEqual(expected_result, button_sequence) + +if __name__ == "__main__": + unittest.main() diff --git a/vba/vba_autoplayer.py b/vba/vba_autoplayer.py new file mode 100644 index 0000000..9aa8f4a --- /dev/null +++ b/vba/vba_autoplayer.py @@ -0,0 +1,495 @@ +# -*- coding: utf-8 -*- +""" +Programmatic speedrun of Pokémon Crystal +""" +import os + +# 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) + +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. + """ + def wrapped_function(*args, **kwargs): + skip = True + + if "skip" in kwargs.keys(): + skip = kwargs["skip"] + del kwargs["skip"] + + # 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)): + skip = False + + return_value = None + + if not skip: + vba.save_state(func.__name__ + "-start", override=True) + return_value = func(*args, **kwargs) + vba.save_state(func.__name__ + "-end", override=True) + elif skip: + vba.set_state(vba.load_state(func.__name__ + "-end")) + + return return_value + return wrapped_function + +@skippable +def skip_intro(): + """ + Skip the game boot intro sequence. + """ + + # copyright sequence + vba.nstep(400) + + # skip the ditto sequence + vba.press("a") + vba.nstep(100) + + # skip the start screen + vba.press("start") + vba.nstep(100) + + # click "new game" + vba.press("a", holdsteps=50, aftersteps=1) + + # 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(): + """ + A basic check for when the game starts. + """ + return vba.get_memory_at(0xcfb1) != 0 + + # go until the introduction is done + vba.crystal.text_wait(callback=overworldcheck) + + return + +@skippable +def handle_mom(): + """ + Walk to mom. Handle her speech and questions. + """ + + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + + vba.crystal.move("d") + vba.crystal.move("d") + + # 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=10) # Sunday + + vba.crystal.text_wait() + + # "SUNDAY, is it?" yes/no + vba.press("a", holdsteps=10) # yes + + vba.crystal.text_wait() + + # "Is it Daylight Saving Time now?" yes/no + vba.press("a", holdsteps=10) # yes + + vba.crystal.text_wait() + + # "AM DST, is that OK?" yes/no + vba.press("a", holdsteps=10) # yes + + # text until "know how to use the PHONE?" yes/no + vba.crystal.text_wait() + + # 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 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(): + """ + Move to just above the grass from outside Elm's lab. + """ + + vba.crystal.move("d") + vba.crystal.move("d") + + 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() diff --git a/vba/vba_config.py b/vba/vba_config.py new file mode 100644 index 0000000..4433f16 --- /dev/null +++ b/vba/vba_config.py @@ -0,0 +1,12 @@ +#!/usr/bin/jython +# -*- encoding: utf-8 -*- +import os + +# by default we assume the user has vba in pokecrystal/extras +project_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..')) + +# save states are in pokecrystal/save-states/ +save_state_path = os.path.join(project_path, "save-states") + +# where is your rom? +rom_path = os.path.join(project_path, "baserom.gbc") diff --git a/vba/vba_keyboard.py b/vba/vba_keyboard.py new file mode 100644 index 0000000..7d57953 --- /dev/null +++ b/vba/vba_keyboard.py @@ -0,0 +1,562 @@ +# -*- encoding: utf-8 -*- +""" +This file constructs a networkx.DiGraph object called graph, which can be used +to find the shortest path of keypresses on the keyboard to type a word. +""" + +import itertools +import networkx + +graph = networkx.DiGraph() + +graph_data = """ +A a select +A B r +A I l +A lower-upper-column-1 u +A J d + +B b select +B A l +B C r +B lower-upper-column-2 u +B K d + +C c select +C D r +C B l +C lower-upper-column-3 u +C L d + +D d select +D E r +D C l +D del-upper-column-1 u +D M d + +E e select +E del-upper-column-2 u +E N d +E D l +E F r + +F f select +F del-upper-column-3 u +F O d +F E l +F G r + +G g select +G end-upper-column-1 u +G P d +G F l +G H r + +H h select +H end-upper-column-2 u +H Q d +H G l +H I r + +I i select +I end-upper-column-3 u +I R d +I H l +I A r + +J j select +J A u +J S d +J R l +J K r + +K k select +K B u +K T d +K J l +K L r + +L l select +L C u +L U d +L K l +L M r + +M m select +M D u +M V d +M L l +M N r + +N n select +N E u +N W d +N M l +N O r + +O o select +O F u +O X d +O N l +O P r + +P p select +P G u +P Y d +P O l +P Q r + +Q q select +Q H u +Q Z d +Q P l +Q R r + +R r select +R I u +R space-upper-x8-y2 d +R Q l +R J r + +S s select +S J u +S - d +S space-upper-x8-y2 l + +T t select +T K u +T ? d +T S l +T U r + +U u select +U L u +U ! d +U T l +U V r + +V v select +V M u +V / d +V U l +V W r + +W w select +W N u +W . d +W V l +W X r + +X x select +X O u +X , d +X W l +X Y r + +Y y select +Y P u +Y space-upper-x6-y3 d +Y X l +Y Z r + +Z z select +Z Q u +Z space-upper-x7-y3 d +Z Y l +Z space-upper-x8-y2 r + +end-upper-column-1 lower-upper-column-1 r +end-upper-column-2 lower-upper-column-1 r +end-upper-column-3 lower-upper-column-1 r +end-upper-column-1 del-upper-column-1 l +end-upper-column-2 del-upper-column-1 l +end-upper-column-3 del-upper-column-1 l +lower-upper-column-1 end-upper-column-1 l +lower-upper-column-2 end-upper-column-1 l +lower-upper-column-3 end-upper-column-1 l +lower-upper-column-1 del-upper-column-1 r +lower-upper-column-2 del-upper-column-1 r +lower-upper-column-3 del-upper-column-1 r +del-upper-column-1 lower-upper-column-1 l +del-upper-column-2 lower-upper-column-1 l +del-upper-column-3 lower-upper-column-1 l +del-upper-column-1 end-upper-column-1 r +del-upper-column-2 end-upper-column-1 r +del-upper-column-3 end-upper-column-1 r + +lower-upper-column-1 A d +lower-upper-column-2 B d +lower-upper-column-3 C d +lower-upper-column-1 - u +lower-upper-column-2 ? u +lower-upper-column-3 ! u + +del-upper-column-1 D d +del-upper-column-2 E d +del-upper-column-3 F d +del-upper-column-1 / u +del-upper-column-2 . u +del-upper-column-3 , u + +end-upper-column-1 G d +end-upper-column-2 H d +end-upper-column-3 I d +end-upper-column-1 space-upper-x6-y3 u +end-upper-column-2 space-upper-x7-y3 u +end-upper-column-3 space-upper-x8-y3 u + +space-upper-x8-y2 space-lower-x8-y2 select +space-upper-x8-y2 R u +space-upper-x8-y2 space-upper-x8-y3 d +space-upper-x8-y2 Z l +space-upper-x8-y2 S r + +space-upper-x8-y3 MN select +space-upper-x8-y3 space-upper-x8-y2 u +space-upper-x8-y3 end-upper-column-3 d +space-upper-x8-y3 space-upper-x7-y3 l +space-upper-x8-y3 - r + +space-upper-x7-y3 PK select +space-upper-x7-y3 Z u +space-upper-x7-y3 end-upper-column-2 d +space-upper-x7-y3 space-upper-x6-y3 l +space-upper-x7-y3 space-upper-x8-y3 r + +space-upper-x6-y3 ] select +space-upper-x6-y3 Y u +space-upper-x6-y3 end-upper-column-1 d +space-upper-x6-y3 , l +space-upper-x6-y3 space-upper-x7-y3 r + +end-upper-column-1 end-lower-column-1 select +end-upper-column-2 end-lower-column-2 select +end-upper-column-3 end-lower-column-3 select +lower-upper-column-1 lower-lower-column-1 select +lower-upper-column-2 lower-lower-column-2 select +lower-upper-column-3 lower-lower-column-3 select +del-upper-column-1 del-lower-column-1 select +del-upper-column-2 del-lower-column-2 select +del-upper-column-3 del-lower-column-3 select + +lower-lower-column-1 × u +lower-lower-column-2 ( u +lower-lower-column-3 ) u +lower-lower-column-1 a d +lower-lower-column-2 b d +lower-lower-column-3 c d + +end-lower-column-1 ] u +end-lower-column-2 PK u +end-lower-column-3 MN u +end-lower-column-1 g d +end-lower-column-2 h d +end-lower-column-3 i d + +del-lower-column-1 : u +del-lower-column-2 ; u +del-lower-column-3 [ u +del-lower-column-1 d d +del-lower-column-2 e d +del-lower-column-3 f d + +- × select +- S u +- lower-upper-column-1 d +- space-upper-x8-y3 l +- ? r + +? ( select +? T u +? lower-upper-column-2 d +? - l +? ! r + +! ) select +! U u +! lower-upper-column-3 d +! ? l +! / r + +/ : select +/ V u +/ del-upper-column-1 d +/ ! l +/ . r + +. ; select +. W u +. del-upper-column-2 d +. / l +. , r + +, [ select +, X u +, del-upper-column-3 d +, . l +, space-upper-x6-y3 r + +× - select +× s u +× upper-lower-column-1 d +× MN l +× ( r + +( ? select +( t u +( upper-lower-column-2 d +( × l +( ) r + +) ! select +) u u +) upper-lower-column-3 d +) ( l +) : r + +: / select +: v u +: del-lower-column-1 d +: ) l +: ; r + +; . select +; w u +; del-lower-column-2 d +; : l +; [ r + +[ , select +[ x u +[ del-lower-column-3 d +[ ; l +[ ] r + +] space-upper-x6-y3 select +] y u +] end-lower-column-1 d +] [ l +] PK r + +PK space-upper-x7-y3 select +PK z u +PK end-lower-column-2 d +PK ] l +PK MN r + +MN space-upper-x8-y3 select +MN space-lower-x8-y2 u +MN end-lower-column-3 d +MN PK l +MN × r + +space-lower-x8-y2 space-upper-x8-y2 select +space-lower-x8-y2 r u +space-lower-x8-y2 MN d +space-lower-x8-y2 z l +space-lower-x8-y2 s r + +a A select +a upper-lower-column-1 u +a j d +a i l +a b r + +b B select +b upper-lower-column-2 u +b k d +b a l +b c r + +c C select +c upper-lower-column-3 u +c l d +c b l +c d r + +d D select +d del-lower-column-1 u +d m d +d c l +d e r + +e E select +e del-lower-column-2 u +e n d +e d l +e f r + +f F select +f del-lower-column-3 u +f o d +f e l +f g r + +g G select +g end-lower-column-1 u +g p d +g f l +g h r + +h H select +h end-lower-column-2 u +h q d +h g l +h i r + +i I select +i end-lower-column-3 u +i r d +i h l +i a r + +j J select +j a u +j s d +j r l +j k r + +k K select +k b u +k t d +k j l +k l r + +l L select +l c u +l u d +l k l +l m r + +m M select +m d u +m v d +m l l +m n r + +n N select +n e u +n w d +n m l +n o r + +o O select +o f u +o x d +o n l +o p r + +p P select +p g u +p y d +p o l +p q r + +q Q select +q h u +q z d +q p l +q r r + +r R select +r i u +r space-lower-x8-y2 d +r q l +r j r + +s S select +s j u +s × d +s space-lower-x8-y2 l +s t r + +t T select +t k u +t ( d +t s l +t u r + +u U select +u l u +u ) d +u t l +u v r + +v V select +v m u +v : d +v u l +v w r + +w W select +w n u +w ; d +w v l +w x r + +x X select +x o u +x [ d +x w l +x y r + +y Y select +y p u +y ] d +y x l +y z r + +z Z select +z q u +z PK d +z y l +z space-lower-x8-y2 r""" + +for line in graph_data.split("\n"): + if line == "": + continue + elif line[0] == "#": + continue + + (node1, node2, edge_name) = line.split(" ") + graph.add_edge(node1, node2, key=edge_name) + + #print "Adding edge ("+edge_name+") "+node1+" -> "+node2 + +def shortest_path(node1, node2): + """ + Figures out the shortest list of button presses to move from one letter to + another. + """ + buttons = [] + last = None + path = networkx.shortest_path(graph, node1, node2) + for each in path: + if last != None: + buttons.append(convert_nodes_to_button_press(last, each)) + last = each + return buttons + #return [convert_nodes_to_button_press(node3, node4) for (node3, node4) in zip(*(iter(networkx.shortest_path(graph, node1, node2)),) * 2)] + +def convert_nodes_to_button_press(node1, node2): + """ + Determines the button necessary to switch from node1 to node2. + """ + print "getting button press for state transition: " + node1 + " -> " + node2 + return graph.get_edge_data(node1, node2)["key"] + +def plan_typing(text, current="A"): + """ + Plans a sequence of button presses to spell out the given text. + """ + buttons = [] + for target in text: + if target == current: + buttons.append("a") + else: + print "Finding the shortest path between " + current + " and " + target + more_buttons = shortest_path(current, target) + buttons.extend(more_buttons) + buttons.append("a") + current = target + return buttons diff --git a/vba_autoplayer.py b/vba_autoplayer.py deleted file mode 100644 index 9aa8f4a..0000000 --- a/vba_autoplayer.py +++ /dev/null @@ -1,495 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Programmatic speedrun of Pokémon Crystal -""" -import os - -# 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) - -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. - """ - def wrapped_function(*args, **kwargs): - skip = True - - if "skip" in kwargs.keys(): - skip = kwargs["skip"] - del kwargs["skip"] - - # 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)): - skip = False - - return_value = None - - if not skip: - vba.save_state(func.__name__ + "-start", override=True) - return_value = func(*args, **kwargs) - vba.save_state(func.__name__ + "-end", override=True) - elif skip: - vba.set_state(vba.load_state(func.__name__ + "-end")) - - return return_value - return wrapped_function - -@skippable -def skip_intro(): - """ - Skip the game boot intro sequence. - """ - - # copyright sequence - vba.nstep(400) - - # skip the ditto sequence - vba.press("a") - vba.nstep(100) - - # skip the start screen - vba.press("start") - vba.nstep(100) - - # click "new game" - vba.press("a", holdsteps=50, aftersteps=1) - - # 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(): - """ - A basic check for when the game starts. - """ - return vba.get_memory_at(0xcfb1) != 0 - - # go until the introduction is done - vba.crystal.text_wait(callback=overworldcheck) - - return - -@skippable -def handle_mom(): - """ - Walk to mom. Handle her speech and questions. - """ - - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") - - vba.crystal.move("d") - vba.crystal.move("d") - - # 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=10) # Sunday - - vba.crystal.text_wait() - - # "SUNDAY, is it?" yes/no - vba.press("a", holdsteps=10) # yes - - vba.crystal.text_wait() - - # "Is it Daylight Saving Time now?" yes/no - vba.press("a", holdsteps=10) # yes - - vba.crystal.text_wait() - - # "AM DST, is that OK?" yes/no - vba.press("a", holdsteps=10) # yes - - # text until "know how to use the PHONE?" yes/no - vba.crystal.text_wait() - - # 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 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(): - """ - Move to just above the grass from outside Elm's lab. - """ - - vba.crystal.move("d") - vba.crystal.move("d") - - 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() diff --git a/vba_config.py b/vba_config.py deleted file mode 100644 index 4433f16..0000000 --- a/vba_config.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/jython -# -*- encoding: utf-8 -*- -import os - -# by default we assume the user has vba in pokecrystal/extras -project_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..')) - -# save states are in pokecrystal/save-states/ -save_state_path = os.path.join(project_path, "save-states") - -# where is your rom? -rom_path = os.path.join(project_path, "baserom.gbc") diff --git a/vba_keyboard.py b/vba_keyboard.py deleted file mode 100644 index 7d57953..0000000 --- a/vba_keyboard.py +++ /dev/null @@ -1,562 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -This file constructs a networkx.DiGraph object called graph, which can be used -to find the shortest path of keypresses on the keyboard to type a word. -""" - -import itertools -import networkx - -graph = networkx.DiGraph() - -graph_data = """ -A a select -A B r -A I l -A lower-upper-column-1 u -A J d - -B b select -B A l -B C r -B lower-upper-column-2 u -B K d - -C c select -C D r -C B l -C lower-upper-column-3 u -C L d - -D d select -D E r -D C l -D del-upper-column-1 u -D M d - -E e select -E del-upper-column-2 u -E N d -E D l -E F r - -F f select -F del-upper-column-3 u -F O d -F E l -F G r - -G g select -G end-upper-column-1 u -G P d -G F l -G H r - -H h select -H end-upper-column-2 u -H Q d -H G l -H I r - -I i select -I end-upper-column-3 u -I R d -I H l -I A r - -J j select -J A u -J S d -J R l -J K r - -K k select -K B u -K T d -K J l -K L r - -L l select -L C u -L U d -L K l -L M r - -M m select -M D u -M V d -M L l -M N r - -N n select -N E u -N W d -N M l -N O r - -O o select -O F u -O X d -O N l -O P r - -P p select -P G u -P Y d -P O l -P Q r - -Q q select -Q H u -Q Z d -Q P l -Q R r - -R r select -R I u -R space-upper-x8-y2 d -R Q l -R J r - -S s select -S J u -S - d -S space-upper-x8-y2 l - -T t select -T K u -T ? d -T S l -T U r - -U u select -U L u -U ! d -U T l -U V r - -V v select -V M u -V / d -V U l -V W r - -W w select -W N u -W . d -W V l -W X r - -X x select -X O u -X , d -X W l -X Y r - -Y y select -Y P u -Y space-upper-x6-y3 d -Y X l -Y Z r - -Z z select -Z Q u -Z space-upper-x7-y3 d -Z Y l -Z space-upper-x8-y2 r - -end-upper-column-1 lower-upper-column-1 r -end-upper-column-2 lower-upper-column-1 r -end-upper-column-3 lower-upper-column-1 r -end-upper-column-1 del-upper-column-1 l -end-upper-column-2 del-upper-column-1 l -end-upper-column-3 del-upper-column-1 l -lower-upper-column-1 end-upper-column-1 l -lower-upper-column-2 end-upper-column-1 l -lower-upper-column-3 end-upper-column-1 l -lower-upper-column-1 del-upper-column-1 r -lower-upper-column-2 del-upper-column-1 r -lower-upper-column-3 del-upper-column-1 r -del-upper-column-1 lower-upper-column-1 l -del-upper-column-2 lower-upper-column-1 l -del-upper-column-3 lower-upper-column-1 l -del-upper-column-1 end-upper-column-1 r -del-upper-column-2 end-upper-column-1 r -del-upper-column-3 end-upper-column-1 r - -lower-upper-column-1 A d -lower-upper-column-2 B d -lower-upper-column-3 C d -lower-upper-column-1 - u -lower-upper-column-2 ? u -lower-upper-column-3 ! u - -del-upper-column-1 D d -del-upper-column-2 E d -del-upper-column-3 F d -del-upper-column-1 / u -del-upper-column-2 . u -del-upper-column-3 , u - -end-upper-column-1 G d -end-upper-column-2 H d -end-upper-column-3 I d -end-upper-column-1 space-upper-x6-y3 u -end-upper-column-2 space-upper-x7-y3 u -end-upper-column-3 space-upper-x8-y3 u - -space-upper-x8-y2 space-lower-x8-y2 select -space-upper-x8-y2 R u -space-upper-x8-y2 space-upper-x8-y3 d -space-upper-x8-y2 Z l -space-upper-x8-y2 S r - -space-upper-x8-y3 MN select -space-upper-x8-y3 space-upper-x8-y2 u -space-upper-x8-y3 end-upper-column-3 d -space-upper-x8-y3 space-upper-x7-y3 l -space-upper-x8-y3 - r - -space-upper-x7-y3 PK select -space-upper-x7-y3 Z u -space-upper-x7-y3 end-upper-column-2 d -space-upper-x7-y3 space-upper-x6-y3 l -space-upper-x7-y3 space-upper-x8-y3 r - -space-upper-x6-y3 ] select -space-upper-x6-y3 Y u -space-upper-x6-y3 end-upper-column-1 d -space-upper-x6-y3 , l -space-upper-x6-y3 space-upper-x7-y3 r - -end-upper-column-1 end-lower-column-1 select -end-upper-column-2 end-lower-column-2 select -end-upper-column-3 end-lower-column-3 select -lower-upper-column-1 lower-lower-column-1 select -lower-upper-column-2 lower-lower-column-2 select -lower-upper-column-3 lower-lower-column-3 select -del-upper-column-1 del-lower-column-1 select -del-upper-column-2 del-lower-column-2 select -del-upper-column-3 del-lower-column-3 select - -lower-lower-column-1 × u -lower-lower-column-2 ( u -lower-lower-column-3 ) u -lower-lower-column-1 a d -lower-lower-column-2 b d -lower-lower-column-3 c d - -end-lower-column-1 ] u -end-lower-column-2 PK u -end-lower-column-3 MN u -end-lower-column-1 g d -end-lower-column-2 h d -end-lower-column-3 i d - -del-lower-column-1 : u -del-lower-column-2 ; u -del-lower-column-3 [ u -del-lower-column-1 d d -del-lower-column-2 e d -del-lower-column-3 f d - -- × select -- S u -- lower-upper-column-1 d -- space-upper-x8-y3 l -- ? r - -? ( select -? T u -? lower-upper-column-2 d -? - l -? ! r - -! ) select -! U u -! lower-upper-column-3 d -! ? l -! / r - -/ : select -/ V u -/ del-upper-column-1 d -/ ! l -/ . r - -. ; select -. W u -. del-upper-column-2 d -. / l -. , r - -, [ select -, X u -, del-upper-column-3 d -, . l -, space-upper-x6-y3 r - -× - select -× s u -× upper-lower-column-1 d -× MN l -× ( r - -( ? select -( t u -( upper-lower-column-2 d -( × l -( ) r - -) ! select -) u u -) upper-lower-column-3 d -) ( l -) : r - -: / select -: v u -: del-lower-column-1 d -: ) l -: ; r - -; . select -; w u -; del-lower-column-2 d -; : l -; [ r - -[ , select -[ x u -[ del-lower-column-3 d -[ ; l -[ ] r - -] space-upper-x6-y3 select -] y u -] end-lower-column-1 d -] [ l -] PK r - -PK space-upper-x7-y3 select -PK z u -PK end-lower-column-2 d -PK ] l -PK MN r - -MN space-upper-x8-y3 select -MN space-lower-x8-y2 u -MN end-lower-column-3 d -MN PK l -MN × r - -space-lower-x8-y2 space-upper-x8-y2 select -space-lower-x8-y2 r u -space-lower-x8-y2 MN d -space-lower-x8-y2 z l -space-lower-x8-y2 s r - -a A select -a upper-lower-column-1 u -a j d -a i l -a b r - -b B select -b upper-lower-column-2 u -b k d -b a l -b c r - -c C select -c upper-lower-column-3 u -c l d -c b l -c d r - -d D select -d del-lower-column-1 u -d m d -d c l -d e r - -e E select -e del-lower-column-2 u -e n d -e d l -e f r - -f F select -f del-lower-column-3 u -f o d -f e l -f g r - -g G select -g end-lower-column-1 u -g p d -g f l -g h r - -h H select -h end-lower-column-2 u -h q d -h g l -h i r - -i I select -i end-lower-column-3 u -i r d -i h l -i a r - -j J select -j a u -j s d -j r l -j k r - -k K select -k b u -k t d -k j l -k l r - -l L select -l c u -l u d -l k l -l m r - -m M select -m d u -m v d -m l l -m n r - -n N select -n e u -n w d -n m l -n o r - -o O select -o f u -o x d -o n l -o p r - -p P select -p g u -p y d -p o l -p q r - -q Q select -q h u -q z d -q p l -q r r - -r R select -r i u -r space-lower-x8-y2 d -r q l -r j r - -s S select -s j u -s × d -s space-lower-x8-y2 l -s t r - -t T select -t k u -t ( d -t s l -t u r - -u U select -u l u -u ) d -u t l -u v r - -v V select -v m u -v : d -v u l -v w r - -w W select -w n u -w ; d -w v l -w x r - -x X select -x o u -x [ d -x w l -x y r - -y Y select -y p u -y ] d -y x l -y z r - -z Z select -z q u -z PK d -z y l -z space-lower-x8-y2 r""" - -for line in graph_data.split("\n"): - if line == "": - continue - elif line[0] == "#": - continue - - (node1, node2, edge_name) = line.split(" ") - graph.add_edge(node1, node2, key=edge_name) - - #print "Adding edge ("+edge_name+") "+node1+" -> "+node2 - -def shortest_path(node1, node2): - """ - Figures out the shortest list of button presses to move from one letter to - another. - """ - buttons = [] - last = None - path = networkx.shortest_path(graph, node1, node2) - for each in path: - if last != None: - buttons.append(convert_nodes_to_button_press(last, each)) - last = each - return buttons - #return [convert_nodes_to_button_press(node3, node4) for (node3, node4) in zip(*(iter(networkx.shortest_path(graph, node1, node2)),) * 2)] - -def convert_nodes_to_button_press(node1, node2): - """ - Determines the button necessary to switch from node1 to node2. - """ - print "getting button press for state transition: " + node1 + " -> " + node2 - return graph.get_edge_data(node1, node2)["key"] - -def plan_typing(text, current="A"): - """ - Plans a sequence of button presses to spell out the given text. - """ - buttons = [] - for target in text: - if target == current: - buttons.append("a") - else: - print "Finding the shortest path between " + current + " and " + target - more_buttons = shortest_path(current, target) - buttons.extend(more_buttons) - buttons.append("a") - current = target - return buttons -- cgit v1.2.3