Tool-Assisted Code Golf

39

12

TAS Golf

SMB1 1-1 ending

In the style of a tool-assisted speedrun with a code-golf twist, the goal of this challenge is to complete World 1-1 of the original Super Mario Bros game for the NES in your chosen programming language in as few bytes as possible, using only the in-game controller inputs in the format I'll describe below. Your program must output to stdout a list of lines in this format, created specifically for this challenge:

up down left right start select A B

Starting with the first frame, each newline represents the inputs for Controller 1 for a particular frame. The order of buttons per frame does not matter, and they can be separated by any amount of non-newline whitespace. All or none or some of the button names can be included per line. For example, a simple Python program that presses the D-pad right for 3 frames and then presses A might look like this:

for _ in range(3): print('right')
print('A')

And its output (which I would feed into my emulator to verify) would be:

right
right
right
A

Here, we define 'success' as reaching the flag at the end of World 1-1 pictured above. The score for this example Python submission, if it succeeded (which it doesn't), would be 44 bytes, or the original length of the Python program.

For an example working input file I created based on the current fastest TAS, see this Github Gist: https://gist.github.com/anonymous/6f1a73cbff3cd46c9e1cf8d5c2ff58e1 Note that this file completes the entire game.

There is no way to enter subframe inputs. There is also no way to enter inputs in Player 2's controller, but that also shouldn't be necessary (or useful) for completing the level or game.

The version of SMB used will be the original USA / Japan iNES ROM (md5sum 811b027eaf99c2def7b933c5208636de -- the USA version is the exact same as the Japanese version so either will work, the ROM is commonly labeled Super Mario Bros (JU) (PRG 0) or similar).

To test the submissions, I will run the programs, pipe their stdout into an input.txt file, and load them into FCEUX using this Lua script mario.lua I wrote for this challenge:

for line in io.lines('input.txt') do
   local t = {}
   for w in line:gmatch("%S+") do
      t[w] = true;
   end;
   joypad.set(1, t);
   emu.frameadvance();
end;
while (true) do
   emu.frameadvance();
end;

The specific command I'll be using is fceux mario.nes --loadlua mario.lua. There is no time limit to the programs, though they must eventually terminate.

This is a little Bash one-liner I made to convert a FCEUX movie (.fm2) file to an input.txt for my script, if it helps:

cat movie.fm2 | cut -d'|' -f 3 | sed 's/\.//g' | sed 's/R/right /g' | sed 's/L/left /g' | sed 's/D/down /g' | sed 's/U/up /g' | sed 's/T/start /g' | sed 's/S/select /g' | sed 's/B/B /g' | sed 's/A/A /g' | tail -n +13 > input.txt

For reference, here is a full-resolution map of World 1-1 (open the image in a new tab for full resolution): World 1-1
(source: mariouniverse.com)

Note: At first glance, this may seem like a Kolmogorov complexity challenge on my given input.txt file. However, in reality the challenge is more complex than that because (a) the input.txt I provided is definitely not the shortest possible and (b) there has never been an attempt to create the shortest possible set of keypresses for SMB in this format. The 'fewest buttons possible' known TAS is different because it allows for holding of buttons for a long time, which would add length to the desired output in this challenge.

Harry

Posted 2017-02-03T17:53:48.987

Reputation: 1 189

1While you have provided a video of the level, I can't count how many rights are in the video. Could you tell us the moves needed? – None – 2017-02-03T19:59:15.517

@JackBates You can see a map of the level here: http://www.mariouniverse.com/images/maps/nes/smb/1-1.png I'll update the post to include that map.

– Harry – 2017-02-03T20:24:02.913

1Did you post this in the Sandbox? I don't remember it. – None – 2017-02-03T21:17:36.117

@JackBates I did: http://meta.codegolf.stackexchange.com/a/11421/48718

– Harry – 2017-02-03T22:57:10.873

1I think it's quite funny you have 16 upvotes and no answers :) – None – 2017-02-03T23:02:40.193

2@JackBates that's the sign of a good, challengjng, non-trivial question – FlipTack – 2017-02-04T06:10:35.230

1404 on that full resolution map image I think – Liam – 2017-02-04T15:45:10.010

@Liam I think the site is under maintenance, or one of their servers behind the load-balancer is down or something. Keep refreshing the page and you'll get it. – Harry – 2017-02-04T16:23:12.690

Answers

20

Python 2, 69 48 46 44 bytes

print"start\n\n"*19+(27*"A right\n"+"\n")*99

See it in action on youtube!

Automatically found with (a modified version of) this hacky script:

start = 18
oncycle = 21
offcycle = 4


while true do
    emu.poweron()
    -- emu.speedmode("maximum")

    starting = 0
    i = 0
    frames = 0
    last_mario_x = -1

    emu.message(start .. " " .. oncycle .. " ".. offcycle)


    state = 0
    while state ~= 6 and state ~= 11 and frames < 4000 do
        if frames > 500 and frames % 100 == 0 then
            mario_x = memory.readbyte(0x6D) * 0x100 + memory.readbyte(0x86)
            if mario_x == last_mario_x then emu.message("stuck " .. mario_x .. " " .. last_mario_x); break end
            last_mario_x = mario_x
        end

        if starting < start then
            joypad.set(1, {start=1})
            emu.frameadvance(); frames = frames + 1;
            joypad.set(1, {})
            emu.frameadvance(); frames = frames + 1;
            starting = starting + 1
        else
            if i < oncycle then
                joypad.set(1, {A=1, B=1, right=1})
                i = i + 1
            else
                joypad.set(1, {})
                i = i +  1
                if i == oncycle + offcycle then
                    i = 0
                end
            end

            emu.frameadvance()
            frames = frames + 1
        end

        state = memory.readbyte(0x000E)
        if state == 4 then
            emu.message("success!")
            os.exit()
            break
        end

    end

    if start < 30 then
        start = start + 1
    elseif offcycle < 10 then
        start = 18
        offcycle = offcycle + 1
    else
        offcycle = 1
        oncycle = oncycle + 1
    end
end

orlp

Posted 2017-02-03T17:53:48.987

Reputation: 37 067

Tested and working! Bonus points for hitting the mushroom block! Thanks! – Harry – 2017-02-04T04:15:02.787

1@Harry Please verify new version. – orlp – 2017-02-04T04:43:33.923

Confirmed, that's pretty incredible! Having some technical difficulties with recording but if someone else wants to do so with these submissions that would be greatly appreciated! – Harry – 2017-02-04T04:56:38.457

1@Harry I just added another new version that saves 2 more bytes by... not using the B button! It barely fits in the 99 repeats, almost had to waste a byte on doing 100+ repeats. – orlp – 2017-02-04T04:59:08.053

Confirmed again, I really like your Lua script for this! – Harry – 2017-02-04T05:02:08.720

144-byte version also confirmed, fun to watch! – Harry – 2017-02-04T05:12:28.223

1Ahh, this is the kind of answer I was going for, but I couldn’t find the right numbers!! Very nicely done. – Lynn – 2017-02-04T16:47:40.257

1

@Harry This is a recording of mine: https://www.youtube.com/watch?v=2-I1EEOlQYA

– orlp – 2017-02-04T17:55:24.287

5

Python 2, 107 bytes

i=0
print'\n'*33+'start'
for c in'~~22 +  2 2? @  F        . \r0'+'@'*10:print'A B right\n'[i:]*ord(c);i^=2

Lynn

Posted 2017-02-03T17:53:48.987

Reputation: 55 648

Very impressive and already much shorter than I thought! Maybe I should have stuck with the full game after all, haha. Also I tested this and can confirm it completes the level, if I can get it to record maybe I'll upload them all as YouTube videos! – Harry – 2017-02-04T01:57:09.790

1

JavaScript (ES6), 59 characters

_=>`start

`[a="repeat"](19)+(`A right
`[a](27)+`
`)[a](99)

This outputs the same text as orlp's answer. I tried coming up with a better method myself, but the movies I converted to an input.txt file did not always play properly. Whenever I tried running the emulator from cmd, I got an error stating "an unknown error occurred".

Luke

Posted 2017-02-03T17:53:48.987

Reputation: 4 675

Can't run it right now on my end, but if it outputs the same input.txt as orlp's answer then we'll call it verified! – Harry – 2017-02-04T19:06:15.377