Beating Level 1-4 in Bit.Trip runner with no hands!

This is an example of how the built in keyboard support for Arduino Leonardo can be used to interact with computer programs (in this case a computer game). This way computer interfaces can quickly be prototyped and Arduino/Computer don't need serial libraries and parsing to work.

Final result should look something like this youtube movie

This has tested on a Paperduino Leonardo, which was built using the (spanish) guide here as well as on a Vinciduino.

Enabling button presses and releases

The current version of the IDE (22/2/12) didn't support holding down keyboard keys, so a bit of hacking is required to make this work. In windows, open your arduino folder -> hardware -> arduino -> cores -> arduino. If you are using osx, right click on your arduino.app files, click on "Show Package Contents" and open Contents -> Resources -> Java -> hardware -> arduino -> cores -> arduino. In the folder there are two files we need to modify, HID.cpp and USBAPI.h, so take a backup of those before beginning. Below the edits are described, but if that isn't interesting, then edited files can be downloaded here

The downloadable core files also has two other functions, one for pushing down arrow and ESC buttons, but they are not functional in Bit.Trip Runner, and therefore not included in the code below

Editing HID.cpp

First off HID.cpp (describes mouse and keyboard functions) needs to get two new functions, one for pushing down a button and one for releasing it. This implementation is based on the current 'write' function for the keyboard (which had just the right amount of comments). Put the following functions after the write function or download an already modified file.

void Keyboard_::press(uint8_t c) {

	// Keydown
	{
		KeyReport keys = {0};
		if (_keyMap)
			_keyMap->charToKey(c,&keys);
		else
		{
			if (c >= 128) {
				setWriteError();
				//return 0;
			}
			c = pgm_read_byte(_asciimap + c);
			if (!c) {
				setWriteError();
				//return 0;
			}
			if (c & 0x80)
			{
				keys.modifiers |= KEY_MODIFIER_LEFT_SHIFT;
				c &= 0x7F;
			}
			keys.keys[0] = c;
		}
		sendReport(&keys);
	}
	//return 1;

}

void Keyboard_::up(uint8_t c) {

	//	Keyup
	{
		KeyReport keys = {0};
		sendReport(&keys);
	}
	//return 1;

}

Editing USBAPI.h

The next file we need to edit is the USBAPI.h, which is in the same folder - this defines the keyboard class, so we need to make the functions we just added public. Find the place where the keyboard class is defined:

class Keyboard_ : public Print { private:

	KeyMap* _keyMap;
	void sendReport(KeyReport* keys);
	void setKeyMap(KeyMap* keyMap);	

public:

	Keyboard_();
	virtual size_t write(uint8_t);

}; extern Keyboard_ Keyboard;

And replace with this:

class Keyboard_ : public Print { private:

	KeyMap* _keyMap;
	void sendReport(KeyReport* keys);
	void setKeyMap(KeyMap* keyMap);	

public:

	Keyboard_();
	virtual size_t write(uint8_t);
	virtual void up(uint8_t);
	virtual void press(uint8_t);

}; extern Keyboard_ Keyboard;

Or download premodified file. Now we are able to send continuous button presses and button releases.

Using it

Get Bit.Trip Runner if you haven't already and get to lvl 1-4. Load the stage, press escape and leave the cursor on restart. Now plug in the Arduino and watch the magic happen.

Word of caution!!!

the Arduino doesn't detect what you are doing when it is plugged in, and will send the sequence of strokes no matter what you are doing, so be aware of which program is active, and that it won't mess anything up.

Code

/* Beating Bit.Trip Runner lvl 1-4 with Arduino Leonardo

    By: Lars Knudsen
    Date: February 22nd, 2012

    Load up Bit.Trip Runner lvl 1-4, press escape and put the cursor on restart
    To watch the magic happen. Word of caution, the Arduino doesn't detect what
    you are doing when it is plugged in, and will send the sequence of strokes
    no matter what you are doing, so be aware that it will not mess anything up.
  • /

//Putting all code in the setup as we only need it to make the sequence once void setup() {

  Serial.begin(9600);           //Adding these makes autoreset work in OSX - don't know why
  Serial1.begin(9600);          //Assume this is a bug which will be fixed later on

  delay(1000);                  //Wait for the driver to be loaded in os - If timing seems off, try increasing this delay (thank you Marco for noticing)
  button(500, 32);              //Press restart button
  delay(4600);                  //Wait for restart to finish - might need editing depending on how fast your computer is

  for(int j = 1; j < 5; j++) {  //Level 1-4 has a pattern recurring four times
    button(500, 32); 
    delay(1000);
    button(500, 32); 
    delay(1000);
    button(500, 32);
    delay(1000);
    button(500, 32);
    button(750, 115);
    delay(100);
    button(500, 32);
    button(1150, 115);

    button(200, 32);
    delay(200);
    button(200, 32);
    delay(200);
    button(200, 32);
    delay(200);
    button(200, 32);
    delay(200);
    button(200, 115);
    delay(200);
    button(750, 115);
    delay(200);
    button(200, 32);
    delay(500);
    button(200, 32);
    delay(550);
    button(200, 32); 
    delay(550);
    button(200, 32);
    delay(550);
    button(200, 32);

    delay(1405);
  }

  //End sequence of level 1-4
  button(500, 32); 
  delay(1000);
  button(500, 32); 
  delay(1000);
  button(500, 32);
  delay(1000);
  button(500, 32);
  button(10000,115); //Sliiiiide into goal

}

//Function for holding down a button for a set amount of time //requires modified core files for the press and up functions to work void button(int time, int button) {

    Keyboard.press(button);
    delay(time);
    Keyboard.up(1);

}

void loop() { }

Share