-- ./EmuHawkMono.sh --lua=Lua/tas.lua kwirk.gb
SOLUTIONS = {
-- Easy
[ "30" ] = { frames = 222, steps = 24, moves = "WWWWWWNWSWNWSWNWSWWWWWWW" },
[ "30i"] = { frames = 240, steps = 26, moves = "WWSWWWWNWSWNWSWNWSWWWWNWWW" },
[ "31" ] = { frames = 429, steps = 44, moves = "WWWWWNWWWWWWSSWSEEEEESENNNENWWWWWWNWSSWWWWWW" },
[ "31i"] = { frames = 447, steps = 46, moves = "WWSWWWWSWWWWWNNWNEEEEENESSSESWWWWWWSWNNWWWNWWW" },
[ "32" ] = { frames = 228, steps = 24, moves = "WWWWWWNWWWSENWWWSWWWWWWW" },
[ "32i"] = { frames = 246, steps = 26, moves = "WWSWWWWNWWWSENWWWSWWWWNWWW" },
[ "33" ] = { frames = 252, steps = 26, moves = "WWNWWWWSWWNNWWSSWWNWWWSWWW" },
[ "33i"] = { frames = 270, steps = 28, moves = "WWSSWWWWSWWNNWWSSWWNWWWNNWWW" },
[ "34" ] = { frames = 381, steps = 41, moves = "WWWWWWWWWWSEEEEENNWWWWWWNEEEESWWWWWWWWWWW" },
[ "34i"] = { frames = 399, steps = 43, moves = "WWSWWWWWWWWNEEEEESSWWWWWWSEEEENWWWWWWWWNWWW" },
[ "35" ] = { frames = 383, steps = 42, moves = "WWSSSWWWWWNNEWNENWNWWWWWSSSEESSWWWWWNNNWWW" },
[ "35i"] = { frames = 365, steps = 40, moves = "WWNNWWWWWSSEWSESWSWWWWWNNNEENNWWWWWSSWWW" },
[ "36" ] = { frames = 257, steps = 28, moves = "WWWWWSSSWWWWWWNENNNWSWWWWWWW" },
[ "36i"] = { frames = 275, steps = 30, moves = "WWSWWWNNNWWWWWWSESSSWNWWWWNWWW" },
[ "37" ] = { frames = 247, steps = 27, moves = "WWWWWWNNWSENNWWWSSWWWWWWWWW" },
[ "37i"] = { frames = 265, steps = 29, moves = "WWSWWWWSSWNESSWWWNNWWWWWWNWWW" },
[ "38" ] = { frames = 330, steps = 34, moves = "WWNNWWWSSSSSWNNENNNWWWWWWWWWWSSWWW" },
[ "38i"] = { frames = 348, steps = 36, moves = "WWSSSWWWNNNNNWSSESSSWWWWWWWWWWNNNWWW" },
[ "39" ] = { frames = 288, steps = 30, moves = "WWNNWWWWSSWWSSWNNWWNNWWWWSSWWW" },
[ "39i"] = { frames = 306, steps = 32, moves = "WWSSSWWWWNNWWNNWSSWWSSWWWWNNNWWW" },
[ "40" ] = { frames = 273, steps = 30, moves = "WWSWWWWWNNWWEESSSWWNWWWWWWNWWW" },
[ "40i"] = { frames = 255, steps = 28, moves = "WWWWWWWSSWWEENNNWWSWWWWWWWWW" },
[ "41" ] = { frames = 369, steps = 40, moves = "WWWWWNNWWWWWSWEENEEEESSSSWWWWWNWWNWWWWWW" },
[ "41i"] = { frames = 387, steps = 42, moves = "WWSWWWSSWWWWWNWESEEEEENNNNWWWWSWWWSWWWNWWW" },
[ "42" ] = { frames = 278, steps = 30, moves = "WWWWWNNWWWWWWSENSWNESSWWWWWWWW" },
[ "42i"] = { frames = 296, steps = 32, moves = "WWSWWWNNWWWWWWSENSWNESSWWWWWNWWW" },
[ "43" ] = { frames = 406, steps = 42, moves = "WWNNWWWWSESWNWSESWSWWSWNENWSWNNNNWWWWSSWWW" },
[ "43i"] = { frames = 424, steps = 44, moves = "WWSSSWWWWNENWSWNENWNWWNWSESWNWSSSSWWWWNNNWWW" },
[ "44" ] = { frames = 363, steps = 38, moves = "WWSSSWWWWWWNESSWWNWWSSEENNNNWWWWWSSWWW" },
[ "44i"] = { frames = 363, steps = 38, moves = "WWNNWWWWWWSENNWWSWWNNEESSSSWWWWWNNNWWW" },
[ "45" ] = { frames = 258, steps = 28, moves = "WWWWWWWNSSSWWNENNWSWWWWWWWWW" },
[ "45i"] = { frames = 276, steps = 30, moves = "WWSWWWWSNWNNWWSESSWNWWWWWWNWWW" },
[ "46" ] = { frames = 260, steps = 28, moves = "WWWWWWWWENNWSWWESSWNWWWWWWWW" },
[ "46i"] = { frames = 278, steps = 30, moves = "WWSWWWWWWESSWNWWENNWSWWWWWNWWW" },
[ "47" ] = { frames = 328, steps = 36, moves = "WWNNWWWWSSSSWWWEENNNWSWWWNNWWWWSSWWW" },
[ "47i"] = { frames = 346, steps = 38, moves = "WWSSSWWWWNNNNWWWEESSSWNWWWSSWWWWNNNWWW" },
[ "48" ] = { frames = 459, steps = 50, moves = "WWNWWWWWNWWWWWSEEEEEEWWWWWSSEENEESSSWWWWWWWWNNNWWW" },
[ "48i"] = { frames = 459, steps = 50, moves = "WWSSWWWWWSWWWWWNEEEEEEWWWWWNNEESEENNNWWWWWWWWSSWWW" },
[ "49" ] = { frames = 345, steps = 37, moves = "WWWWWWWWEENNWSSESSWNENNNWWWSWSWWWWWWW" },
[ "49i"] = { frames = 363, steps = 39, moves = "WWSWWWWWWEENNNWSSESSWNESSWWWWNNWWWWNWWW" },
[ "50" ] = { frames = 296, steps = 32, moves = "WWWWWWWWNNWSESSSSWWNENWWNWWWWWWW" },
[ "50i"] = { frames = 314, steps = 34, moves = "WWSWWWWWWSSWNENNNNWWSESWWSWWWWNWWW" },
[ "51" ] = { frames = 324, steps = 34, moves = "WWSSSWWWWWNNNWSENWWWWSSSWWWWNNNWWW" },
[ "51i"] = { frames = 306, steps = 32, moves = "WWNNWWWWWSSSWNESWWWWNNNWWWWSSWWW" },
[ "52" ] = { frames = 510, steps = 54, moves = "WWNWWWSWWWWWNEEEEESSWNESSWWWNEEESSWNESSWWWWWWWNWWWSWWW" },
[ "52i"] = { frames = 528, steps = 56, moves = "WWSSWWWNWWWWWSEEEEENNWSENNWWWSEEENNWSENNWWWWWWWSWWWNNWWW" },
[ "53" ] = { frames = 324, steps = 34, moves = "WWNNWWWWWWWSEESSWSWNESWWWWWWNNNWWW" },
[ "53i"] = { frames = 324, steps = 34, moves = "WWSSSWWWWWWWNEENNNWWSENWWWWWWSSWWW" },
[ "54" ] = { frames = 293, steps = 32, moves = "WWNNWWWSSSWNWNWWWSSWSWSWWWNNNWWW" },
[ "54i"] = { frames = 293, steps = 32, moves = "WWSSSWWWNNNWSWSWWWNNWNWNWWWSSWWW" },
[ "55" ] = { frames = 277, steps = 30, moves = "WWSWWWWWWNNWWSENWWSSSWWWWWNWWW" },
[ "55i"] = { frames = 259, steps = 28, moves = "WWWWWWWWSSWWNESWWNNNWWWWWWWW" },
[ "56" ] = { frames = 300, steps = 30, moves = "WWWWWWWSWESEENWWWWNSWWWNWWWWWW" },
[ "56i"] = { frames = 318, steps = 32, moves = "WWSWWWWWNWENEESWWWWSNWWWSWWWNWWW" },
[ "57" ] = { frames = 272, steps = 30, moves = "WWNNWWWWWWWWSWSSESSWWWWWNNNWWW" },
[ "57i"] = { frames = 272, steps = 30, moves = "WWSSSWWWWWWWWNWNNENNWWWWWSSWWW" },
[ "58" ] = { frames = 495, steps = 51, moves = "WWWWWWSWWSWWWNENNEESSESWWWWEENNWSSSWWWNNNNNWWWSSWWW" },
[ "58i"] = { frames = 513, steps = 53, moves = "WWSWWWWNWWWNWWSESSEENNENWWWWEESSWNNWNWSSSSSWWWWNNNWWW" },
[ "59" ] = { frames = 399, steps = 43, moves = "WWSWWWWWSWWEENNWSENNWWNEEWWSENWWSSWWWWWNWWW" },
[ "59i"] = { frames = 381, steps = 41, moves = "WWWWWWWNWWEESSWNESSWWSEEWWNESWWNNWWWWWWWW" },
-- Average
[ "60" ] = { frames = 479, steps = 48, moves = "WWWWWNNWWSSNNEESSSSWNENWWWWEEESSWNENWWWWWWWWWWWW" },
[ "60i"] = { frames = 497, steps = 50, moves = "WWSWWWNNWWSSNNEESSSSWNENWWWWEEESSWNENWWWWWWWWWNWWW" },
[ "61" ] = { frames = 435, steps = 46, moves = "WWWWWWNWWWNEESESESNNWWNWWSEEESSSSWWNWWWNWWWWWW" },
[ "61i"] = { frames = 453, steps = 48, moves = "WWSWWWWSWWWSEENNEENSSWWSWWNEEENNNNWWSWWWSWWWNWWW" },
[ "62" ] = { frames = 585, steps = 63, moves = "WWSWWWNWWNEESSWWWSWENEEENNWWSEENNWWWWSSESEEENNNNWWWWSWWWWWWSWWW" },
[ "62i"] = { frames = 585, steps = 63, moves = "WWWWWWSWSEENNWWWNWESEEESSWWNEESSWWWWNNENEEESSSSWWWWNWWWWWWNNWWW" },
[ "63" ] = { frames = 476, steps = 52, moves = "WWNNWWWWSSSSSWNNNNWWNWWSSSNNNEESWSSEESSWWWWWWWNNNWWW" },
[ "63i"] = { frames = 476, steps = 52, moves = "WWSSSWWWWNNNNNWSSSSWWSWWNNNSSSEENWNNEENNWWWWWWWSSWWW" },
[ "64" ] = { frames = 328, steps = 35, moves = "WWNNWWWWSWWWEEESSSWNWWSWWWWWWNNNWWW" },
[ "64i"] = { frames = 328, steps = 35, moves = "WWSSSWWWWNWWWEEENNNWSWWNWWWWWWSSWWW" },
[ "65" ] = { frames = 342, steps = 36, moves = "WWWWWWWNNWWSEWSENWWNEESSSWWNWWWWWWWW" },
[ "65i"] = { frames = 360, steps = 38, moves = "WWSWWWWWSSWWNEWNESWWSEENNNWSWWWWWWNWWW" },
[ "66" ] = { frames = 246, steps = 26, moves = "WWSWWWWWWWWNEWWNESWWWWWWWW" },
[ "66i"] = { frames = 246, steps = 26, moves = "WWWWWWWWWWSEWWSENWWWWWNWWW" },
[ "67" ] = { frames = 317, steps = 34, moves = "WWNNWWWSWSSWSWWSWWNNEENNWWWWWSSWWW" },
[ "67i"] = { frames = 335, steps = 36, moves = "WWSSSWWWNWNNWNWWNWWSSEESSWWWWWNNNWWW" },
[ "68" ] = { frames = 243, steps = 26, moves = "WWWWWWNWWSWNWWESSWNWWWWWWW" },
[ "68i"] = { frames = 261, steps = 28, moves = "WWSWWWWSWWNWSWWENNWSWWWWNWWW" },
[ "69" ] = { frames = 320, steps = 34, moves = "WWNWWWWSWNSWSWNEENENWWWWSWWWWWSWWW" },
[ "69i"] = { frames = 338, steps = 36, moves = "WWSSWWWNWWSNWNWSEESESWWWWNWWWWWNNWWW" },
[ "70" ] = { frames = 393, steps = 42, moves = "WWNNWWWSSSSSWWNENENNWSNNWWWWWSSSWWWWNNNWWW" },
[ "70i"] = { frames = 393, steps = 42, moves = "WWSSSWWWNNNNNWWSEESSSWNSSWWWWWNNWNWWWSSWWW" },
[ "71" ] = { frames = 382, steps = 42, moves = "WWNNWWWSSSSWWNNENNWWWSSESSWWWNNNNWWWWSSWWW" },
[ "71i"] = { frames = 400, steps = 44, moves = "WWSSSWWWNNNNWWSSESSWWWNNENNWWWSSSSWWWWNNNWWW" },
[ "72" ] = { frames = 360, steps = 39, moves = "WWSWWWWWSWWEENNNNWWSESSWNESSWWWWWWWNWWW" },
[ "72i"] = { frames = 342, steps = 37, moves = "WWWWWWWNWWEESSSSWWNENNWSENNWWWWWWWWWW" },
[ "73" ] = { frames = 353, steps = 36, moves = "WWWWWWNWWSENENWWWEESSWNNWWSSWWWWWWWW" },
[ "73i"] = { frames = 371, steps = 38, moves = "WWSWWWWSWWNESESWWWEENNWSSWWNNWWWWWNWWW" },
[ "74" ] = { frames = 429, steps = 43, moves = "WWWWWSSWSWWWNNESWNNWWNEEENEESSWWWWWWWWWWWWW" },
[ "74i"] = { frames = 447, steps = 45, moves = "WWSWWWNNNWWWWSSENWSSWWSEEESEENNWWWWWWWWWWNWWW" },
[ "75" ] = { frames = 390, steps = 42, moves = "WWNNWWWWWWWWSSEEESENNNWWWWSSESWSWWWWNNNWWW" },
[ "75i"] = { frames = 390, steps = 42, moves = "WWSSSWWWWWWWWNNEEENESSSWWWWNNENWNWWWWSSWWW" },
[ "76" ] = { frames = 238, steps = 26, moves = "WWWWWWWSWNENNWWWSSWWWWWWWW" },
[ "76i"] = { frames = 256, steps = 28, moves = "WWSWWWWWNWSESSWWWNNWWWWWNWWW" },
[ "77" ] = { frames = 306, steps = 33, moves = "WWWWWWSSWWNNNEESSSSWWWWNWWNWWWWWW" },
[ "77i"] = { frames = 324, steps = 35, moves = "WWSWWWNNWWWSSSEENNNNWWWWWSWSWWWNWWW" },
[ "78" ] = { frames = 282, steps = 30, moves = "WWWWWSSSWWWEENNWWNWWWSWWWWNWWW" },
[ "78i"] = { frames = 282, steps = 30, moves = "WWSWWWNNNWWWEESSWWSWWWNWWWWWWW" },
[ "79" ] = { frames = 314, steps = 34, moves = "WWWWWWNNWWWEEESSSSWNWWWSWNNWWWWWWW" },
[ "79i"] = { frames = 332, steps = 36, moves = "WWSWWWWSSWWWEEENNNNWSWWWNWSSWWWWNWWW" },
[ "80" ] = { frames = 314, steps = 30, moves = "WWNNWWWSSSWWWNWNWWSSWSWWWNNWWW" },
[ "80i"] = { frames = 314, steps = 30, moves = "WWSSSWWWNNNWWWSWSWWNNWNWWWSWWW" },
[ "81" ] = { frames = 507, steps = 54, moves = "WWNNWWWSSSWSSWWNWSEENNEENNNWWSSSSWWNESWWNNWSSWWWNNNWWW" },
[ "81i"] = { frames = 507, steps = 54, moves = "WWSSSWWWNNNWNNWWSWNEESSEESSSWWNNNNWWSENWWSSWNNWWWSSWWW" },
[ "82" ] = { frames = 276, steps = 30, moves = "WWWWWWNNWWWEESSWNWWWSSWWWWNWWW" },
[ "82i"] = { frames = 276, steps = 30, moves = "WWSWWWWSSWWWEENNWSWWWNNWWWWWWW" },
[ "83" ] = { frames = 259, steps = 28, moves = "WWWWWWWNWESSWNWNSSWWWNWWWWWW" },
[ "83i"] = { frames = 277, steps = 30, moves = "WWSWWWWWNWESSWNWNSSWWWNWWWNWWW" },
[ "84" ] = { frames = 371, steps = 36, moves = "WWWWWWWWWSNNESEESSWWNNENWWWWSWWWWWWW" },
[ "84i"] = { frames = 389, steps = 38, moves = "WWSWWWWWWWSNNESEESSWWNNENWWWWSWWWWNWWW" },
[ "85" ] = { frames = 353, steps = 36, moves = "WWWWWSSWWNWWWEEENEENNWWSWWWSWWWWWWWW" },
[ "85i"] = { frames = 371, steps = 38, moves = "WWSWWWSSWWNWWWEEENEENNWWSWWWSWWWWWNWWW" },
[ "86" ] = { frames = 1218, steps = 128, moves = "WWNNWWWWSSWNESSWWWNEEESSWNESSWWWWWNEEEEESSWNESSWWWNEEESSWNESSSSWNESSWWWNEEESSWNESSWWWWWNEEEEESSWNESSWWWNEEESSWNESSWWWWWWWWWNNWWW" },
[ "86i"] = { frames = 1218, steps = 128, moves = "WWSSSWWWWNNWSENNWWWSEEENNWSENNWWWWWSEEEEENNWSENNWWWSEEENNWSENNNNWSENNWWWSEEENNWSENNWWWWWSEEEEENNWSENNWWWSEEENNWSENNWWWWWWWWWSWWW" },
[ "87" ] = { frames = 486, steps = 52, moves = "WWWWWNNWWEESSSSWWEENNWSNNWSENNWWWSWWNESWWNNWSSWWWWWW" },
[ "87i"] = { frames = 504, steps = 54, moves = "WWSWWWNNWWEESSSSWWEENNWNSSWNESSWWWNWWSENWWSSWNNWWWNWWW" },
[ "88" ] = { frames = 884, steps = 90, moves = "WWNNWWWWWSSNNWWSSSSSWWWNNEEWWSSEEENNNNSSSSEENENWWENNNWWWEEESSSSSWWNNNNSSEENNNWWWWWWWWSSWWW" },
[ "88i"] = { frames = 902, steps = 92, moves = "WWSSSWWWWWNNSSWWNNNNNEESESWWENNWWSSSSNNEESSSWWWENNNNNWWWSSEEWWNNEEESSSSNNEESSSWWWWWWWWNNNWWW" },
[ "89" ] = { frames = 577, steps = 60, moves = "WWNNWWWSWWWSWWNEEESEENNWSESWWWWNWSSSWSEEENEESWWWWWWWWWNNNWWW" },
[ "89i"] = { frames = 577, steps = 60, moves = "WWSSSWWWNWWWNWWSEEENEESSWNENWWWWSWNNNWNEEESEENWWWWWWWWWSSWWW" },
[ "90" ] = { frames = 388, steps = 41, moves = "WWNNWWWWSSSSSWWWNWSEEENENNWWSWWNWWWWSSWWW" },
[ "90i"] = { frames = 406, steps = 43, moves = "WWSSSWWWWNNNNNWWSWWNEESEESSWWNWWSWWWWNNNWWW" },
[ "91" ] = { frames = 604, steps = 62, moves = "WWWWWNNWWWWWWSSESSSEEEENNWEENNWNWSSNEESSWWWWESSWWNNENWWWWWWWWW" },
[ "91i"] = { frames = 622, steps = 64, moves = "WWSWWWSWSWWWWWNNENNNEEEESSWEESSSWWNNSEENNWWWWENNWWSSESWWWWWWNWWW" },
[ "92" ] = { frames = 483, steps = 46, moves = "WWWWWNNWWSSWSWSEEENENWWWWWSESEENENWWWWWWWWWWWW" },
[ "92i"] = { frames = 501, steps = 48, moves = "WWSWWWNNWWSSWSWSEEENENWWWWWESESENENWWWWWWWWWNWWW" },
[ "93" ] = { frames = 296, steps = 31, moves = "WWSWWWWWSSWWNENNWWNESWWSWWWNWWW" },
[ "93i"] = { frames = 278, steps = 29, moves = "WWWWWWWNNWWSESSWWSENWWNWWWWWW" },
[ "94" ] = { frames = 320, steps = 34, moves = "WWWWWWNWWEESSWWNWSWENNWSWNWSWWWWWW" },
[ "94i"] = { frames = 338, steps = 36, moves = "WWSWWWWNWWEESSWWNWSWENNWSWNWSWWWNWWW" },
[ "95" ] = { frames = 319, steps = 34, moves = "WWWWWNNWWSESNNWWSSWWSSWSWNNNWWWWWW" },
[ "95i"] = { frames = 337, steps = 36, moves = "WWSWWWSSWWNENSSWWNNWWNNWNWSSSWWWNWWW" },
[ "96" ] = { frames = 502, steps = 51, moves = "WWNNWWWWSSWESNNNWWWWSSENSWWWSNNNEEESSWSSWWWWWNNNWWW" },
[ "96i"] = { frames = 502, steps = 51, moves = "WWSSSWWWWNNWENSSSWWWWNNESNWWWNSSSEEENNWNNWWWWWSSWWW" },
[ "97" ] = { frames = 365, steps = 38, moves = "WWNNWWWSSSWWSWWNNNEENNWSENNWWWWWWSSWWW" },
[ "97i"] = { frames = 383, steps = 40, moves = "WWSSSWWWNNNWWNWWSSSEESSWNESSWWWWWWNNNWWW" },
[ "98" ] = { frames = 370, steps = 40, moves = "WWWWWWNNWWSESESSWNNNSSSWWWNENNWSWWWWWWWW" },
[ "98i"] = { frames = 388, steps = 42, moves = "WWSWWWWSSWWNENENNWSSSNNWNWWSESSWNWWWWWNWWW" },
[ "99" ] = { frames = 652, steps = 66, moves = "WWNNWWWSWSWSWWWNNWSESSWWNEEEESENNWNEENESSSWSWWWWSWNNNENWWNWWWSSWWW" },
[ "99i"] = { frames = 670, steps = 68, moves = "WWSSSWWWNWNWNWWWSSWNENNWWSEEEENESSWSEESENNNWNWWWWNWSSSESWWSWWWNNNWWW" },
["100" ] = { frames = 499, steps = 48, moves = "WWWWWNWNWSSNWEEESWWWNWSESWENEESWWWWWWSSWWWNNNWWW" },
["100i"] = { frames = 499, steps = 48, moves = "WWSWWWSWSWWENNSEENWWWSWNENWSEEENWWWWWWNNWWWSSWWW" },
["101" ] = { frames = 873, steps = 82, moves = "WWNNWWWSSWWSWWNWESEENWWWWEESWESSWNESENNENWWWWEESWWWNSEESSEEENNENWWWWWSWNNNWWWSSWWW" },
["101i"] = { frames = 891, steps = 84, moves = "WWSSSWWWNNWWNWWSWENEESWWWWEENWENNWSENESSESWWWWEENWWWSNEEENNEESSESWWWWWNWSSSWWWNNNWWW" },
["102" ] = { frames = 755, steps = 74, moves = "WWSSSWWWNNNWWSSEENNNWWSEWWWNNEEEESSSWWWWNWWNNWSSEEWWSSENENWNWSSSSWWWNNNWWW" },
["102i"] = { frames = 737, steps = 72, moves = "WWNNWWWSSSWWNNEESSSWWNEWWWSSEEEENNNWWWWSWWSSWNNEEWWNNESESWSWNNNNWWWSSWWW" },
["103" ] = { frames = 692, steps = 73, moves = "WWSSSWWWWWWWNNNEEENNWWWSSWWSSSEENNSSWWNNNEEWWNSSSSEENNNNEEESSWWWWWWWWWWWW" },
["103i"] = { frames = 692, steps = 73, moves = "WWNNWWWWWWWSSSEEESSWWWNNWWNNNEESSNNWWSSSEEWWSNNNNEESSSSEEENNWWWWWWWWWNWWW" },
["104" ] = { frames = 600, steps = 60, moves = "WWSSSWWWNWWWWWSWWNEEEEESEENWSWNNNWWWEEENNWSESWWWWWNNWWWSSWWW" },
["104i"] = { frames = 600, steps = 60, moves = "WWNNWWWSWWWWWNWWSEEEEENEESWNWSSSWWWEEESSWNENWWWWWSSWWWNNNWWW" },
["105" ] = { frames = 423, steps = 43, moves = "WWSWWWNNWWWSNWWSSWEEESEESNWWSNWWSWWNWWWNWWW" },
["105i"] = { frames = 405, steps = 41, moves = "WWWWWSSWWWNNWENEENSWWNNWWSWESSSNWWNWWWWWW" },
["106" ] = { frames = 464, steps = 50, moves = "WWNNWWWSWSWSWNESSWWSWWEENEENNWNWWSSSWNESSWWWWWNWWW" },
["106i"] = { frames = 464, steps = 50, moves = "WWSSSWWWNWNWNWSENNWWNWWEESEESSSWWWNNNWSENNWWWWWWWW" },
["107" ] = { frames = 544, steps = 58, moves = "WWSWWWWSNNWWWNNWWWSENEEEEESSWNESSWWWNSESWWNESSWWSWWWWNNWWW" },
["107i"] = { frames = 526, steps = 56, moves = "WWWWWWNSSWWWSSWWWNESEEEEENNWSENNWWWSNENWWSENNWWNWWWWSWWW" },
["108" ] = { frames = 314, steps = 34, moves = "WWSWWWWSWWWEEENNNWWSWNSSWNWWWWWWWW" },
["108i"] = { frames = 314, steps = 34, moves = "WWWWWWNWWWEEESSSWWNWSNNWSWWWWWNWWW" },
["109" ] = { frames = 1083, steps = 115, moves = "WWWWWWWWWEEEESSWNESSWWWNEEESSWNESSWWWWWEEEEENNWSENNWWWSEEENNWSENNWWWWWSWENEEEEENNWSENNWWWSEEENNWSENNWWWWWSWSWWWWWWW" },
["109i"] = { frames = 1101, steps = 117, moves = "WWSWWWWWWWEEEESSWNESSWWWNEEESSWNESSWWWWWEEEEENNWSENNWWWSEEENNWSENNWWWWWSWENEEEEENNWSENNWWWSEEENNWSENNWWWWWSWSWWWWNWWW" },
-- Hard
["110" ] = { frames = 502, steps = 46, moves = "WWWWWNWNWWWSSSSSWWNESENSEENWWWSWNENWSWNNWWWWWW" },
["110i"] = { frames = 520, steps = 48, moves = "WWSWWWSWSWWWNNNNNWWSENESNEESWWWNWSESWNWSSWWWNWWW" },
["111" ] = { frames = 551, steps = 55, moves = "WWWWWWSWWSWEEESWWNNEENNWWSSSWWWENENNNWWWSESSENNWWWWWWWW" },
["111i"] = { frames = 569, steps = 57, moves = "WWSWWWWNWWNWNESEESSSWNNENWSWNWWWESESSSWWWNENNESSWWWWWNWWW" },
["112" ] = { frames = 344, steps = 36, moves = "WWWWWWNNWWESESSWNNENWWWSSSWWNWWWWWWW" },
["112i"] = { frames = 362, steps = 38, moves = "WWSWWWWNNWWESESSWNNENWWWSSSWWNWWWWNWWW" },
["113" ] = { frames = 542, steps = 57, moves = "WWSWWWNWWWWSEEESSWNNWWSWWSSEEENNNEENNWSESSWWNNNWWSSWWWWWW" },
["113i"] = { frames = 542, steps = 57, moves = "WWWWWSWWWWNEEENNWSSWWNWWNNEEESSSEESSWNNENWWSSSWWNNWWWNWWW" },
["114" ] = { frames = 351, steps = 32, moves = "WWSWWWNNWWSWWNWWSESWSSWNNWWWNWWW" },
["114i"] = { frames = 333, steps = 30, moves = "WWWWWSSWWNWWSWWNENWNNWSSWWWWWW" },
["115" ] = { frames = 578, steps = 58, moves = "WWSSSWWWNNWWWEEESSWNENWWWWNWSEEEESSWNENWWWSWNNNNWWWWWSSWWW" },
["115i"] = { frames = 578, steps = 58, moves = "WWNNWWWSSWWWEEENNWSESWWWWSWNEEEENNWSESWWWNWSSSSWWWWWNNNWWW" },
["116" ] = { frames = 466, steps = 49, moves = "WWWWWSSSWWWWWNSEEEEENNNNNWWWSSWWWNEEESSWWWWWWWWWW" },
["116i"] = { frames = 484, steps = 51, moves = "WWSWWWNNNWWWWWSNEEEEESSSSSWWWNNWWWSEEENNWWWWWWWNWWW" },
["117" ] = { frames = 474, steps = 49, moves = "WWNNWWWWWSEESWWSNWWSNNNWWSSSENWWNNEESSSWWNWWWNWWW" },
["117i"] = { frames = 474, steps = 49, moves = "WWSSSWWWWWNEENWWNSWWNSSSWWNNNESWWSSEENNNWWSWWWWWW" },
["118" ] = { frames = 445, steps = 46, moves = "WWSWWWWSWWWWEEEENESWWWWWENNNNWESSWSNNWWSWWWWWW" },
["118i"] = { frames = 445, steps = 46, moves = "WWWWWWNWWWWEEEESENWWWWWESSSSWENNWNSSWWNWWWNWWW" },
["119" ] = { frames = 545, steps = 58, moves = "WWNNWWWWWWSSNNWWWSENEEESWNEESSENNSSWNESSWWWSSWWWWWWWNNNWWW" },
["119i"] = { frames = 545, steps = 58, moves = "WWSSSWWWWWWNNSSEENWSWWWNESEEENNESSNNWSENNWWWNNWWWWWWWSSWWW" },
["120" ] = { frames = 333, steps = 32, moves = "WWWWWSSSWWWNNNSEENNWWWWWWSWWWWWW" },
["120i"] = { frames = 351, steps = 34, moves = "WWSWWWNNNWWWSSSNEESSWWWWWWNWWWNWWW" },
["121" ] = { frames = 629, steps = 58, moves = "WWNNWWWWWWWWSWSENEEEEESSSSWNNENNWWWWNWSESWNWSSSWSWWWNNNWWW" },
["121i"] = { frames = 629, steps = 58, moves = "WWSSSWWWWWWWWNWNESEEEEENNNNWSSESSWWWWSWNENWSWNNNWNWWWSSWWW" },
["122" ] = { frames = 462, steps = 48, moves = "WWWWWWWWNWWNWESESESSESEENWWSWNNSEENNWWWWWWWWWWWW" },
["122i"] = { frames = 480, steps = 50, moves = "WWSWWWWWWSWWSWENENENNENEESWWNWSSNEESSWWWWWWWWWNWWW" },
["123" ] = { frames = 624, steps = 64, moves = "WWSSSWWWNNSSWWNNNESSSSWWNNWSEENNNWWSSSWSENEESSWNENWWWWSWNNWWWWWW" },
["123i"] = { frames = 624, steps = 64, moves = "WWNNWWWSSNNWWSSSENNNNWWSSWNEESSSWWNNNWNESEENNWSESWWWWNWSSWWWNWWW" },
["124" ] = { frames = 624, steps = 62, moves = "WWWWWSSSWWWWWNSEENWSEENNNNNWESEESSWWEESSWWWNNNNENWWWWWSSWWWWWW" },
["124i"] = { frames = 642, steps = 64, moves = "WWSWWWNNNWWWWWSNEESWNEESSSSSWENEENNWWEENNWWWSSSSESWWWWWNNWWWNWWW" },
["125" ] = { frames = 1207, steps = 118, moves = "WWWWWNNWWWSSESWNNNESSNNEESSWWEESSSWNNWWWEENNNWSESSWWWEENNNEESESSWWWWWWEEEEESSWNENWWWWWNWSEEEEESSWNENWWWWNWSSSWWWNNNWWW" },
["125i"] = { frames = 1207, steps = 118, moves = "WWSWWWSSWWWNNENWSSSENNSSEENNWWEENNNWSSWWWEESSSWNENNWWWEESSSEENENNWWWWWWEEEEENNWSESWWWWWSWNEEEEENNWSESWWWWSWNNNWWWSSWWW" },
["126" ] = { frames = 534, steps = 36, moves = "WWWWWNNWWWWWWSSSNNNEESSSSWWWNNWWWWWW" },
["126i"] = { frames = 552, steps = 38, moves = "WWSWWWSSWWWWWWNNNSSSEENNNNWWWSSWWWNWWW" },
["127" ] = { frames = 595, steps = 60, moves = "WWWWWSSSWWNWWNWSENEEENNNWSWWSWEEEESWSSEENWSWNNENWWWWWWWWWWWW" },
["127i"] = { frames = 613, steps = 62, moves = "WWSWWWNNNWWSWWSWNSEEEESSSWNWWNWEEEENWNNEESWNWSSESWWWWWWWWWNWWW" },
["128" ] = { frames = 765, steps = 71, moves = "WWNNWWWSWWNWWSSSEEENNWSSWWWSWNNNEEENESSESSESWWWNENESSESWWWWWWWWWWNNNWWW" },
["128i"] = { frames = 765, steps = 71, moves = "WWSSSWWWNWWSWWNNNEEESSWNNWWWNWSSSEEESENNENNENWWWSESENNENWWWWWWWWWWSSWWW" },
["129" ] = { frames = 601, steps = 60, moves = "WWWWWNWWWWWWSENEEEESESSWWWWSWWNEEEESENNNENWWWWWSWNNWWWWSSWWW" },
["129i"] = { frames = 619, steps = 62, moves = "WWSWWWSWWWWNWSEEEENNENWWWWNWWSEEEENESSSESWWWWNWSESWWWWWWNNNWWW" },
["130" ] = { frames = 611, steps = 56, moves = "WWWWWNNWWWWSSNNEESSWWWSEESSEENENWWWWWEESSWWNNENWWWWWWWWW" },
["130i"] = { frames = 629, steps = 58, moves = "WWSWWWSSWWWWNNSSEENNWWWNEENNEESESWWWWWWWNNEESSESWWWWWWNWWW" },
["131" ] = { frames = 318, steps = 34, moves = "WWSWWWNWNWSWSSESWWWNENNWWWSWWWNWWW" },
["131i"] = { frames = 300, steps = 32, moves = "WWWWWSWSWNWNNENWWWSESSWWWNWWWWWW" },
["132" ] = { frames = 518, steps = 48, moves = "WWWWWWSWWWEENENWNWWSSNNEESSWSWWNNESESWWWWWWWNWWW" },
["132i"] = { frames = 518, steps = 48, moves = "WWSWWWWNWWWEESESWSWWNNSSEENNWNWWSSENENWWWWWWWWWW" },
["133" ] = { frames = 811, steps = 81, moves = "WWWWWWSSSWWWWWNENEESSEENWSEENNNWWNESNWSENNWWWSWWEESESWNNNWWSSSSEENNWNNWWSSWWWWWWW" },
["133i"] = { frames = 829, steps = 83, moves = "WWSWWWWNNNWWWWWESSEENNEESWNEESSSWWSENSWNESSWWWNWWEENENWSSSWWNNNNEESSWSSWWNNWWWWNWWW" },
["134" ] = { frames = 597, steps = 60, moves = "WWWWWSSSWWWNNEWWNESEEENWWSWNENWWSSSENENENWSWNENWWWWWSSWWWWWW" },
["134i"] = { frames = 615, steps = 62, moves = "WWSWWWNNNWWWSSEWWSENEEESWWNWSESWWNNNESESESWNWSESWWWWWNNWWWNWWW" },
["135" ] = { frames = 665, steps = 68, moves = "WWNNWWWSWWWNWWSEENESWSSSNNENEESSWENNWSSSSWWNNENENWSWNENWWWWWWWWSSWWW" },
["135i"] = { frames = 683, steps = 70, moves = "WWSSSWWWNWWWSWWNEESENWNNNSSESEENNWESSWNNNNWWSSESESWNWSESWWWWWWWWNNNWWW" },
["136" ] = { frames = 741, steps = 76, moves = "WWSSSWWWWWWWWNNWWNNENEESEWWSWWSENENNWSSSNNEEEEEESSWNENWWWWWNWSSSSSWWWWNNNWWW" },
["136i"] = { frames = 723, steps = 74, moves = "WWNNWWWWWWWWSSWWSSESEENEWWNWWNESESSWNNNSSEEEEEENNWSESWWWWWSWNNNNNWWWWSSWWW" },
["137" ] = { frames = 1079, steps = 109, moves = "WWSSSWWWNNNNNWWWSESSSSWNENNNEESSSSWESWWWWWWEEEENNWSENNNENNWWSSENENNWWSWNEEWWSENWWSSSNNNWSEENEEESWWWWWWSWWWWWW" },
["137i"] = { frames = 1079, steps = 109, moves = "WWNNWWWSSSSSWWWNENNNNWSESSSEENNNNWENWWWWWWEEEESSWNESSSESSWWNNESESSWWNWSEEWWNESWWNNNSSSWNEESEEENWWWWWWNWWWNWWW" },
["138" ] = { frames = 671, steps = 70, moves = "WWSSSWWWWWNNNNENWSSSSSEENWSWNNNNESWSSWWWNEEEWWWSENSWNEEWWSENNWWWWWWWWW" },
["138i"] = { frames = 671, steps = 70, moves = "WWNNWWWWWSSSSESWNNNNNEESWNWSSSSENWNNWWWSEEEWWWNESNWSEEWWNESSWWWWWWNWWW" },
["139" ] = { frames = 883, steps = 94, moves = "WWWWWWWWWNNWSSEEESSSENNWNNEESWWWWEESSSWWNNSSWWNESEEENNNNNWWWSSSNNNEEESSWWEESSSWWNWWSWNNNWWWWWW" },
["139i"] = { frames = 901, steps = 96, moves = "WWSWWWWWWWSSWNNEEENNNESSWSSEENWWWWEENNNWWSSNNWWSENEEESSSSSWWWNNNSSSEEENNWWEENNNWWSWWNWSSSWWWNWWW" },
["140" ] = { frames = 559, steps = 55, moves = "WWWWWNNWWWSSNNWWWESESSEESEENWWNNWWWEEESSWNENWWWWWWWWWWW" },
["140i"] = { frames = 577, steps = 57, moves = "WWSWWWSSWWWNNSSWWWENENNEENEESWWSSWWWEEENNWSESWWWWWWWWNWWW" },
["141" ] = { frames = 659, steps = 65, moves = "WWWWWSSWWNNNSSSWWWNEESSEENNNWWWWEESSWNESSWSSENNNNENWWWWSSWWWWWWWW" },
["141i"] = { frames = 677, steps = 67, moves = "WWSWWWNNWWSSSNNNWWWSEENNEESSSWWWWEENNWSENNWNNESSSSESWWWWNNWWWWWNWWW" },
["142" ] = { frames = 847, steps = 82, moves = "WWWWWSWENNNWSSSESWNWSESWWWWNWNESSENSEENNWWSWNEEEENNWNWWSSNNEEESSSWWWSWNENWWWWWWWWW" },
["142i"] = { frames = 865, steps = 84, moves = "WWSWWWNWSESSWNNNENWSWNENWWWWSWSENNESNEESSWWNWSEEEESSWSWWNNSSEENENNWWWNWSESWWWWWWNWWW" },
["143" ] = { frames = 709, steps = 74, moves = "WWNNWWWWWSSENSEESSSWWWNNWWNNNEESNWWSSSEEWWSNNNNEESSSSEEENNNWWSWWWNWWWWWWWW" },
["143i"] = { frames = 727, steps = 76, moves = "WWSSSWWWWWNNESNEENNNWWWSSWWSSSEENSWWNNNEEWWNSSSSEENNNNEEESSSWWNWWWWWSWWWNWWW" },
["144" ] = { frames = 1051, steps = 107, moves = "WWWWWWSSSWWWWWNNENENWSSWSSEEENEEENNWWWESSWSWWWNNENSWSSEEEEENNNNNWSESWWESSSWWWWNNENEEEESSWWNSEENNWWWWWWWWWWW" },
["144i"] = { frames = 1069, steps = 109, moves = "WWSWWWWNNNWWWWWSSESESWNNWNNEEESEEESSWWWENNWNWWWSSESNWNNEEESESSESSWNENWWENNNWWWWSSESEEEENNWWSNEESSWWWWWWWWNWWW" },
["145" ] = { frames = 1129, steps = 115, moves = "WWWWWNNWWWWSENWWSSSNNEESSSSEENNWNWSNNNWWSWSENESENENWWWWEESSEESSWWNNNENNWWWSWWESNESWNNWEEENEESWWWWWEESSWNNWWSSWWWWWW" },
["145i"] = { frames = 1147, steps = 117, moves = "WWSWWWSSWWWWNESWWNNNSSEENNNNEESSWSWNSSSWWWNNESENESESWWWWWWENSEEENEENNNWWSSSESSWWWNNWSSWESEEEENWWWWWEENNWSSWWNNWWWNWWW" },
["146" ] = { frames = 1022, steps = 104, moves = "WWWWWSSWSWWEENENNNWNWWEESSWNESSWWWNEEWWSENWSSWEEEENNWWWEEESSWSSENNWWWSEEEWWNNWSENNEWNENESESWWWWWWWWWWWWW" },
["146i"] = { frames = 1040, steps = 106, moves = "WWSWWWNNWNWWEESESSSWSWWEENNWSENNWWWSEEWWNESWNNWEEEESSWWWEEENNWNNESSWWWNEEEWWSSWNESSEWSESENENWWWWWWWWWWNWWW" },
["147" ] = { frames = 968, steps = 100, moves = "WWSSSWWWNNNWWSWENEESSSWWNNEESSSWWWWNNWWSENEEEESSEENWSWNNWNNWSESEENWWWESWSSWWNESENNNENWSWNNWWWWWSSWWW" },
["147i"] = { frames = 968, steps = 100, moves = "WWNNWWWSSSWWNWSEEENNNWWSSEENNNWWWWSSWWNESEEESSWNENWNNWWSENESSESWWNEEENNEESWNWSSESWWWNWSSSWWWWWNNNWWW" },
["148" ] = { frames = 1239, steps = 132, moves = "WWSSSWWWWENNNNNWWWSSEENNWWWWWWSSEESSNNEENNEEESSWWWEESSWWNNEENNWWWWWWSSEEEEEWWWWWNNEEEESNWWWWSSEEEESSEENNWWWWWEEENNWWWWSSSSSWWWNNNWWW" },
["148i"] = { frames = 1221, steps = 130, moves = "WWNNWWWWESSSSSWWWNNEESSWWWWWWNNEENNSSEESSEEENNWWWEENNWWSSEESSWWWWWWNNEEEEEWWWWWSSEEEENSWWWWNNEEEENNEESSWWWWWEEESSWWWWNNNNNWWWSSWWW" },
["149" ] = { frames = 903, steps = 94, moves = "WWWWWSSWWWSWWWWNEENSSEEEENWWWEEEENNWSENNWWWSSEENENNWWWWWSSNNWNSEENNESWSESSSEENENNWWWWWWWWWWWWW" },
["149i"] = { frames = 921, steps = 96, moves = "WWSWWWNNWWWNWWWWSEESNNEEEESWWWEEEESSWNESSWWWNNEESESSWWWWWNNSSWSNEESSENWNENNNEESESSWWWWWWWWWWNWWW" },
}
-- Code addresses.
local TITLE_SCREEN_ADDR = 0x02bc
local SWITCH_PLAYERS_ADDR = 0x1400 -- when switching players with Select, or starting a floor in Heading Out?
local FLOOR_END_ADDR = 0x17dc -- WAY TO GO / TRY AGAIN subroutine
local MENU_ADDR = 0x2cc0 -- inside the menu subroutine, after initialization of MENU_SELECTED_ADDR, MENU_MAX_COL_ADDR, and MENU_MAX_ROW_ADDR
local MENU_RET_ADDR = 0x2df0 -- return from the menu subroutine
local COURSE_MENU_DIGIT_ADDR = 0x3cec -- code that changes the selected digit in the SELECT COURSE menu
local COURSE_MENU_RET_ADDR = 0x3d0d -- return from the SELECT COURSE menu subroutine
-- Variable addresses.
local NUM_FLOORS_ADDR = 0xc2b9
local DISPLAY_ADDR = 0xc2be
local SKILL_ADDR = 0xc2c0
local PLAYER1_POS_INDEX_ADDR = 0xc2d4
local FRAME_COUNTER_ADDR = 0xc2ec
local RNG_SEED_ADDR = 0xc377
local MENU_MAX_COL_ADDR = 0xcf11
local MENU_MAX_ROW_ADDR = 0xcf12
local MENU_SELECTED_ADDR = 0xcf19
local JOYPAD_INPUT_ADDR = 0xff8b -- the current state of joypad buttons as of the most recent poll
local JOYPAD_PRESSED_ADDR = 0xff8c -- just the joypad buttons that became newly pressed in the most recent poll
-- Constants.
local JOYPAD_BUTTON_A = 0x01
local JOYPAD_BUTTON_START = 0x08
local SKILL_EASY = 0
local SKILL_AVERAGE = 1
local SKILL_HARD = 2
local SKILL_LABELS = { [SKILL_EASY] = "easy", [SKILL_AVERAGE] = "average", [SKILL_HARD] = "hard", }
local DISPLAY_DIAGONAL = 0
local DISPLAY_BIRDSEYE = 1
local DISPLAY_LABELS = { [DISPLAY_DIAGONAL] = "diagonal", [DISPLAY_BIRDSEYE] = "birdseye", }
-- BASE_SAVESTATE is a constant (assigned in the "do" block below) that
-- represents the state of the emulator at the point at which this script is
-- run. All other savestates derive from BASE_SAVESTATE.
local BASE_SAVESTATE
-- The contents of this "do" block define a data-oriented immutable savestate
-- manipulation library. The core concept is that of a savestate object, which
-- represents the state of the emulator at a certain point. Once created, a
-- savestate object never changes. The only way to get a new savestate object
-- is to start from an existing savestate object and pass it to one of the
-- functions defined in this block (or a function defined in terms of those
-- functions, etc.). The important thing is that there is no implicit
-- dependency on the current emulator state. The idea is to remove the emulator
-- state as a hidden "global variable", mutable and available to all code, and
-- instead to make function outputs depend only on explicit inputs.
--
-- The "local" functions in the block are for internal use. They are the only
-- ones that are allowed to directly mutate the current emulator state. The
-- non-local functions exported from the block, and all other code, may change
-- the emulator state only indirectly, as an invisible side effect using the
-- safe primitives exported from this block. For example, the local function
-- run_until_event_raw effectively takes the emulator state as one of its
-- inputs and mutates the emulator state as one of its outputs; whereas the
-- non-local function run_until_event takes a savestate object as input and
-- returns a different savestate object as output.
do
-- Hide some modules that can manipulate the emulator state and should
-- not be used outside the savestate functions. This is not meant to be
-- bulletproof, only to safeguard against accidental misuse.
local joypad = joypad
local memory = memory
local memorysavestate = memorysavestate
local savestate_module = savestate
do
local env = _ENV
local t = {}
setmetatable(t, {__index = function (t, key)
if ({
joypad = true,
memory = true,
memorysavestate = true,
savestate = true,
})[key] then
return nil
end
return env[key]
end})
_ENV = t
end
-- Define SAVESTATE_DIRECTORY to save temporary savestates to disk
-- rather than using in-memory core savestates. If you want to save the
-- results of program execution as a movie file, you have to use actual
-- on-disk savestates, not in-memory core savestates. This is because
-- the latter do not store input logs. There is no function to delete
-- the files when they are no longer used, so you will have to delete
-- them yourself.
local SAVESTATE_DIRECTORY = nil -- "/tmp"
local save_emu_savestate, load_emu_savestate, delete_emu_savestate
if SAVESTATE_DIRECTORY ~= nil then
-- Generate temporary savestate filenames from a random
-- basename and an incrementing counter. We're relying on
-- math.random being somehow reasonably seeded.
local digits = {}
for i = 1, 16 do
local r = math.random(16)
digits[i] = string.sub("0123456789abcdef", r, r)
end
digits = table.concat(digits)
local counter = 0
local function generate_emu_savestate_path()
local path = string.format("%s/tempsavestate-%s-%010d",
SAVESTATE_DIRECTORY, digits, counter)
counter = counter + 1
return path
end
-- BizHawk does not have an API for deleting savestate files,
-- or deleting files in general. While we cannot delete
-- savestates that are no longer used (that have had their __gc
-- metamethod called), we can, when creating a new savestate
-- file, prefer to overwrite an existing, no-longer-used file,
-- rather than create a new one. The recycle table holds a set
-- of filenames of previously created savestate files that are
-- no longer needed. save_emu_savestate tries to pull a name
-- from the recycle table before creating a new file.
local recycle = {}
save_emu_savestate = function ()
local path = next(recycle)
if path ~= nil then
recycle[path] = nil
else
path = generate_emu_savestate_path()
end
savestate_module.save(path, true)
return path
end
load_emu_savestate = function (id)
assert(savestate_module.load(id, true))
end
delete_emu_savestate = function (id)
recycle[id] = true
end
else
save_emu_savestate = memorysavestate.savecorestate
load_emu_savestate = memorysavestate.loadcorestate
delete_emu_savestate = memorysavestate.removestate
end
-- The metatable for all savestate objects. The important metamethod is
-- __gc, which tells BizHawk to reclaim the resources of savestates
-- that are no longer referenced.
local savestate_mt = {
__gc = function(savestate)
delete_emu_savestate(savestate.id)
end,
__tostring = function(savestate)
return string.format("savestate{%s}", savestate.id)
end,
}
-- Create a new savestate object from the current state of the
-- emulator. It is the caller's responsibility to ensure that the
-- emulator state is frame-aligned.
local function new_savestate()
-- A savestate object consists of the identifier of an
-- emulator-native savestate, and the joypad state.
local savestate = {
id = save_emu_savestate(),
buttons = joypad.get(),
}
-- Keep the keys from joypad.get, but set all the values to false.
for k in pairs(savestate.buttons) do
savestate.buttons[k] = false
end
setmetatable(savestate, savestate_mt)
return savestate
end
-- Run the emulator from its current state to the next onframestart
-- event. The resulting aligned emulator state is safe to make or load
-- a savestate from. See discussion starting at
-- https://github.com/TASEmulators/BizHawk/issues/1430#issuecomment-1987446287.
local function frame_align()
local co = coroutine.running()
local event_id
event_id = event.onframestart(function ()
event.unregisterbyid(event_id)
assert(coroutine.resume(co))
end)
coroutine.yield()
end
-- Make the emulator state match the given savestate object. The
-- emulator state does not need to be frame-aligned at the point when
-- this function is called.
local function restore_savestate(savestate)
-- Set the current state of the joypad to what is in the
-- savestate.
joypad.set(savestate.buttons)
-- The emulator state must be aligned at a frame boundary
-- before loading a savestate. The emulator's current state is
-- otherwise unimportant, because we throw it away.
frame_align()
load_emu_savestate(savestate.id)
end
-- Return a copy of the given savestate object.
local function clone_savestate(savestate)
restore_savestate(savestate)
local new = new_savestate()
for k, v in pairs(savestate.buttons) do
new.buttons[k] = v
end
return new
end
-- Assume that at the point at which this script is run, it's a safe
-- time to make a savestate (i.e., we're frame-aligned).
BASE_SAVESTATE = new_savestate()
-- Return a new savestate object that is a copy of a given savestate,
-- with modified joypad buttons. The state of each button named by the
-- keys of the buttons table is set to the corresponding value. The
-- state of any button not represented in the buttons table is left
-- unmodified.
function savestate_joypad_set(savestate, buttons)
local new = clone_savestate(savestate)
-- Override the buttons.
for k, v in pairs(buttons) do
new.buttons[k] = v
end
return new
end
-- Run the emulator until the event registered by the register_event
-- function happens, or frame_limit frames elapse, whichever happens
-- first. If the register_event event happens first, return true; if
-- frame_limit is exceeded first, return false. frame_limit may be an
-- integer, or nil to mean "no limit". register_event should be a
-- callback registration function such as event.on_bus_exec,
-- event.onframeend, etc. The ... variable arguments are passed to
-- register_event.
local function run_until_event_raw(frame_limit, register_event, ...)
if frame_limit ~= nil and frame_limit <= 0 then
return false
end
local co = coroutine.running()
-- We register two event callbacks: one for the event the
-- caller is interested in (event_id), and one to count frames
-- and enforce frame_limit (limit_id). We register both
-- callbacks, then yield to let the emulator run until one of
-- the two callbacks calls coroutine.resume. The event_id
-- callback resumes with the value true; the limit_id callback
-- resumes with the value false. After being resumed, we
-- dispose of the callback that was not called. We register
-- event_id before limit_id so that, if both callbacks happen
-- for the same event, the caller's event takes precedence.
local event_id = register_event(function ()
if co ~= nil then
assert(coroutine.resume(co, true))
end
end, ...)
local frame_count = 0
local limit_id = event.onframeend(function ()
frame_count = frame_count + 1
if frame_limit ~= nil and frame_count >= frame_limit then
if co ~= nil then
assert(coroutine.resume(co, false))
end
end
end)
local event_occurred = coroutine.yield()
assert(event.unregisterbyid(event_id))
assert(event.unregisterbyid(limit_id))
-- We want only one of the two callbacks above to run
-- coroutine.resume. At this point, one of them has done so --
-- but just unregistering both callback IDs does not guarantee
-- that the other will not also run coroutine.resume. That is
-- because both callbacks may be looking for the very same
-- event; i.e. register_event is event.onframeend and
-- frame_limit is 1). BizHawk runs all callbacks that were
-- registered at the time the event occurred, even if, in the
-- course of working through the list of callbacks, a later one
-- gets unregistered by an earlier one. We set co to nil as a
-- signal to both callbacks that if they run, they should not
-- call coroutine.resume.
co = nil
return event_occurred
end
-- Starting from the given savestate, return a new savestate that is
-- the result of running until the event registered by register_event
-- happens; or return nil if frame_limit frames elapse without the
-- event happening. frame_limit may be nil for no limit. register_event
-- should be a callback registration function such as
-- event.on_bus_exec, event.onframeend, etc. The ... variable arguments
-- are passed to register_event.
function run_until_event(savestate, frame_limit, register_event, ...)
restore_savestate(savestate)
local event_occurred = run_until_event_raw(frame_limit, register_event, ...)
if event_occurred then
-- The event may not have occurred at a frame boundary,
-- so align the emulator state before creating the new
-- savestate.
frame_align()
return new_savestate()
else
return nil
end
end
-- Return the result of calling f with no arguments, after making the
-- current emulator state match the given savestate.
local function with_savestate(savestate, f)
restore_savestate(savestate)
return f()
end
-- Return the frame count of the given savestate.
function savestate_frame_count(savestate)
return with_savestate(savestate, function () return emu.framecount() end)
end
-- Return the value of the u8 at addr in the given savestate.
function savestate_read_u8(savestate, addr, domain)
return with_savestate(savestate, function () return memory.read_u8(addr, domain) end)
end
end
-- Return a new savestate one frame after the given savestate.
local function run_one_frame(savestate)
-- It should be impossible for run_until_event to return nil
-- (indicating that the frame_limit was exceeded) with these
-- parameters, since event.onframeend.is the same event that
-- run_until_event uses to count frames.
return assert(run_until_event(savestate, 1, event.onframeend))
end
-- Return a new savestate representing the frame boundary after the address
-- addr is executed, or nil if frame_limit is reached.
local function run_until_exec(savestate, frame_limit, addr)
return run_until_event(savestate, frame_limit, event.on_bus_exec, addr)
end
-- Return a new savestate representing the frame boundary after the address
-- addr is written, or nil if frame_limit is reached.
local function run_until_write(savestate, frame_limit, addr)
return run_until_event(savestate, frame_limit, event.on_bus_write, addr)
end
-- Return a new savestate representing the frame boundary after the address
-- addr is written to and the written u8 value satisfies the test predicate, or
-- nil if frame_limit is reached.
local function run_until_u8_becomes_p(savestate, frame_limit, addr, test)
repeat
local frames = savestate_frame_count(savestate)
savestate = run_until_event(savestate, frame_limit, event.on_bus_write, addr)
if savestate == nil then
return nil
end
local new_frames = savestate_frame_count(savestate)
assert(new_frames >= frames)
if frame_limit ~= nil then
frame_limit = frame_limit - (new_frames - frames)
end
until test(savestate_read_u8(savestate, addr))
return savestate
end
-- Return a new savestate representing the frame boundary after the given u8
-- value is written to the address addr, or nil if frame_limit is reached.
local function run_until_u8_becomes(savestate, frame_limit, addr, value)
return run_until_u8_becomes_p(savestate, frame_limit, addr, function (v) return v == value end)
end
-- Return a new savestate representing the frame boundary after the address
-- addr contains a u8 value that satisfies the test predicate, or nil if
-- frame_limit is reached. This may be the same as the input savestate, if addr
-- in the input savestate already contains a satisfactory value.
local function run_until_u8_is_p(savestate, frame_limit, addr, test)
if test(savestate_read_u8(savestate, addr)) then
return savestate
end
return run_until_u8_becomes_p(savestate, frame_limit, addr, test)
end
-- Return a new savestate representing the frame boundary after the address
-- addr contains a the given u8 value, or nil if frame_limit is reached. This
-- may be the same as the input savestate, if addr in the input savestate
-- already contains the given value.
local function run_until_u8_is(savestate, frame_limit, addr, value)
return run_until_u8_is_p(savestate, frame_limit, addr, function (v) return v == value end)
end
-- Find the earliest frame at which running an action function on a savestate
-- produces a savestate that satisfies a test predicate.
--
-- The action function takes a savestate as input and returns a savestate as
-- output. The test function takes a savestate as input and returns a Boolean
-- output (nil|false or otherwise). This function runs the action function on
-- the given savestate, then runs the test predicate on the resulting
-- savestate. If the test function's return value is not nil and not false, it
-- returns two values: the return value of action and the return value of test.
-- If the test function returns nil or false, it goes back to the original
-- savestate, advances one frame, and tries the action and test again. The
-- process continues, advancing one frame at a time, until the test predicate
-- succeeds.
--
-- It is common to call this function with a test predicate whose return value
-- is either a savestate or nil. In that case, you can regard the two return
-- values as "savestate after action performed" and "savestate after test
-- passed".
local function earliest_effective_frame(savestate, action, test)
local action = action or function (savestate) return savestate end
local frames = savestate_frame_count(savestate)
while true do
local action_performed = action(savestate)
local test_result = test(action_performed)
if test_result then
return action_performed, test_result
end
-- No luck with this frame. Try doing the action one frame later.
savestate = run_one_frame(savestate)
local new_frames = savestate_frame_count(savestate)
assert(new_frames == frames + 1, string.format("frames=%d new_frames=%d", frames, new_frames))
frames = new_frames
end
end
local function menu_confirm_button(savestate)
-- Menus may be confirmed by pressing either A or Start. The button
-- must be newly pressed in the most recent poll of the joypad; i.e.,
-- there must have been another poll in which it was released since the
-- last time it was pressed. There are no joypad polls between menus,
-- so when a menu requires no directional inputs, the button used to
-- confirm the previous menu still counts as pressed. Therefore if one
-- of the two buttons is already pressed, we want to press the other
-- one, to avoid having to release and re-press the first button.
local joypad_pressed = savestate_read_u8(savestate, JOYPAD_PRESSED_ADDR)
return ({
[0] = "A", -- if nothing pressed, default to A
[JOYPAD_BUTTON_START] = "A", -- if Start pressed, press A
[JOYPAD_BUTTON_A] = "Start", -- if A pressed, press Start
[JOYPAD_BUTTON_A | JOYPAD_BUTTON_START] = "A", -- if both pressed, default to A
})[joypad_pressed & (JOYPAD_BUTTON_A | JOYPAD_BUTTON_START)]
end
-- Return the value x of the array t that minimizes key(x).
local function min_by_key(t, key)
local min, min_key
for _, x in ipairs(t) do
local k = key(x)
if min_key == nil or k < min_key then
min = x
min_key = k
end
end
return min
end
local function select_menu_item(savestate, target)
-- A menu is a 2d grid. (Most menus have just 1 column, but the SELECT
-- FLOOR menu in Going Up? has 2 columns.) Items are indexed starting
-- at 0, incrementing down the first column, then the second column,
-- and so on.
-- Seek ahead from wherever we are, until MENU_ADDR is executed and we
-- know the menu variables are initialized. Then go back to the
-- original savestate to start the seek forward.
local selected, num_cols, num_rows
do
local menu_savestate = assert(run_until_exec(savestate, nil, MENU_ADDR))
selected = savestate_read_u8(menu_savestate, MENU_SELECTED_ADDR)
num_cols = savestate_read_u8(menu_savestate, MENU_MAX_COL_ADDR) + 1
num_rows = savestate_read_u8(menu_savestate, MENU_MAX_ROW_ADDR) + 1
end
assert(0 <= selected and selected < num_cols * num_rows)
assert(0 <= target and target < num_cols * num_rows)
local selected_col = selected // num_rows
local selected_row = selected % num_rows
local target_col = target // num_rows
local target_row = target % num_rows
-- You can wrap both vertically and horizontally, either of which may
-- be faster than not wrapping.
local v_candidates = {
{ button = "Down", row_inc = 1, count = (target_row - selected_row) % num_rows },
{ button = "Up", row_inc = -1, count = (selected_row - target_row) % num_rows },
}
local h_candidates = {
{ button = "Right", col_inc = 1, count = (target_col - selected_col) % num_cols },
{ button = "Left", col_inc = -1, count = (selected_col - target_col) % num_cols },
}
local v = min_by_key(v_candidates, function (x) return x.count end)
local h = min_by_key(h_candidates, function (x) return x.count end)
-- We can move the cursor once every 3 frames. We want to alternate
-- vertical and horizontal movement as long as we have both horizontal
-- and vertical distance to cover. After that we have to alternate with
-- blank frames for the remaining direction. We want to start with
-- whichever direction has more distance to cover. For example, DRDRD_D
-- is faster than RDRD_D_D.
local first, second
if v.count > h.count then
first = v
second = h
else
first = h
second = v
end
local function expected_update(expected, step)
local col = expected // num_rows
local row = expected % num_rows
col = (col + (step.col_inc or 0)) % num_cols
row = (row + (step.row_inc or 0)) % num_rows
return row * num_cols + col
end
local function selected_becomes(expected)
return function (savestate)
return run_until_u8_becomes(savestate, 20, MENU_SELECTED_ADDR, expected)
end
end
local expected = selected -- Keep track of the selection we expect.
local plan = {}
while first.count > 0 do
first.count = first.count - 1
expected = expected_update(expected, first)
plan[#plan+1] = { button = first.button, test = selected_becomes(expected) }
if second.count > 0 then
second.count = second.count - 1
expected = expected_update(expected, second)
plan[#plan+1] = { button = second.button, test = selected_becomes(expected) }
end
end
for _, step in ipairs(plan) do
savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {[step.button] = true})
end, step.test)
end
if #plan == 0 then
-- If #plan == 0, in other words if target == 0 (the default
-- selection for any menu), we may actually still be in a
-- *previous* menu that had already selected 0. Because no
-- directional inputs are required, waiting just for the menu
-- return MENU_RET_ADDR would find the return of the previous
-- menu. So in this case, we wait until a few frames before
-- *entering* MENU_ADDR, before waiting for the next
-- MENU_RET_ADDR.
savestate = earliest_effective_frame(savestate, nil, function (s)
return run_until_exec(s, 2, MENU_ADDR)
end)
end
local _, savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {[menu_confirm_button(s)] = true})
end, function (s)
s = run_until_u8_is(s, 20, MENU_SELECTED_ADDR, expected)
if s then
s = run_until_exec(s, 20, MENU_RET_ADDR)
end
return s
end)
return savestate
end
local function cancel_menu(savestate)
local _, savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {B = true})
end, function (s)
local t = run_until_exec(s, 20, MENU_RET_ADDR)
if t == nil then
t = run_until_exec(s, 20, COURSE_MENU_RET_ADDR)
end
return t
end)
return savestate
end
local function arrays_concat(...)
local result = {}
for _, a in ipairs({...}) do
table.move(a, 1, #a, #result + 1, result)
end
return result
end
local function select_num_floors(savestate, target)
-- The SELECT COURSE menu in Heading Out? gives you two digits to
-- manipulate. The variable NUM_FLOORS_ADDR tracks the state of the
-- digits and is persistent, remaining set to the same value even
-- across Heading Out? runs.
local num_floors = savestate_read_u8(savestate, NUM_FLOORS_ADDR)
local tens_digit = num_floors // 10
local ones_digit = num_floors % 10
local target_tens_digit = target // 10
local target_ones_digit = target % 10
-- For each digit, it may be faster to go up or down.
local tens_candidates = {
{ button = "Up", inc = 10, count = (target_tens_digit - tens_digit) % 10 },
{ button = "Down", inc = -10, count = (tens_digit - target_tens_digit) % 10 },
}
local ones_candidates = {
{ button = "Up", inc = 1, count = (target_ones_digit - ones_digit) % 10 },
{ button = "Down", inc = -1, count = (ones_digit - target_ones_digit) % 10 },
}
if target == 1 then
-- In the special case of a target of 01, we also have the
-- option of going to 00, which the game converts to 01.
ones_candidates = arrays_concat(ones_candidates, {
{ button = "Up", inc = 1, count = (0 - ones_digit) % 10 },
{ button = "Down", inc = -1, count = (ones_digit - 0) % 10 },
})
end
local tens = min_by_key(tens_candidates, function (x) return x.count end)
local ones = min_by_key(ones_candidates, function (x) return x.count end)
local function num_floors_becomes(expected)
return function (savestate)
return run_until_u8_becomes(savestate, 20, NUM_FLOORS_ADDR, expected)
end
end
local expected = num_floors -- Keep track of the state of num_floors we expect at each step.
local plan = {}
for _ = 1, tens.count do
expected = (expected + tens.inc) // 10 * 10 + expected % 10
plan[#plan+1] = { button = tens.button, test = num_floors_becomes(expected ) }
end
if ones.count > 0 then
plan[#plan+1] = { button = "Right", test = function (s) return run_until_exec(s, 20, COURSE_MENU_DIGIT_ADDR) end }
for _ = 1, ones.count do
expected = expected // 10 * 10 + (expected % 10 + ones.inc) % 10
plan[#plan+1] = { button = ones.button, test = num_floors_becomes(expected ) }
end
end
for _, step in ipairs(plan) do
savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {[step.button] = true})
end, step.test)
end
local _, savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {[menu_confirm_button(s)] = true})
end, function (s)
s = run_until_u8_is(s, 20, NUM_FLOORS_ADDR, expected)
if s then
s = run_until_exec(s, 20, COURSE_MENU_RET_ADDR)
end
return s
end)
return savestate
end
-- Run until the byte at PLAYER1_POS_INDEX_ADDR is written to.
local function run_until_player1_moves(savestate, frame_limit)
return run_until_write(savestate, frame_limit, PLAYER1_POS_INDEX_ADDR)
end
local function play_floor(savestate, schedule)
savestate = run_until_exec(savestate, nil, SWITCH_PLAYERS_ADDR)
local input_savestate = savestate
local after_savestate = savestate
local frame_count_start = nil
local num_steps = 0
for i = 1, string.len(schedule) do
local code = string.sub(schedule, i, i)
local button = ({
N = "Up",
E = "Right",
S = "Down",
W = "Left",
})[code]
assert(button, string.format("unknown code %q", code))
input_savestate, after_savestate = earliest_effective_frame(after_savestate, function (s)
return savestate_joypad_set(s, {[button] = true})
end, function (s)
return run_until_player1_moves(s, 16)
end)
if frame_count_start == nil then
-- Start counting frames at the first button press
-- (ignoring the SWITCH_PLAYERS_ADDR startup cost
-- above).
frame_count_start = savestate_frame_count(input_savestate)
end
num_steps = num_steps + 1
end
-- Run to the end of the frame to record the final input and get the
-- expected frame count.
input_savestate = run_one_frame(input_savestate)
after_savestate = run_one_frame(after_savestate)
-- Count frames based on after_savestate, which is when the player has
-- fully moved. But return input_savestate, from the moment of input.
-- This is needed to avoid empty frames at the end of the final floor.
local num_frames = savestate_frame_count(after_savestate) - (frame_count_start or savestate_frame_count(after_savestate))
return input_savestate, num_frames, num_steps
end
-- Starting at the "Way to go!" post-run screen, use the menus to set the
-- desired settings.
local function post_run_reconfigure(savestate, skill, num_floors, display)
-- "Way to go!" screen.
savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {[menu_confirm_button(s)] = true})
end, function (s)
return run_until_exec(s, 32, MENU_ADDR)
end)
local cur_skill = savestate_read_u8(savestate, SKILL_ADDR)
local cur_num_floors = savestate_read_u8(savestate, NUM_FLOORS_ADDR)
-- The display variable uses the encoding 0 = DISPLAY_BIRDSEYE,
-- 1 = DISPLAY_DIAGONAL which is the opposite of our convention (which
-- uses the order in which the options appear in the menu).
local cur_display = assert(({[0] = 1, [1] = 0})[savestate_read_u8(savestate, DISPLAY_ADDR)])
-- There are two ways to navigate the menus: jumping back to a specific
-- menu using the End menu, or stepping back through the menus one by
-- one with the B Button. We try both, and keep whichever's faster.
-- https://tasvideos.org/Forum/Posts/529194
-- Try the End menu.
local menu_savestate = savestate
if cur_display ~= display or cur_num_floors ~= num_floors or cur_skill ~= skill then
menu_savestate = select_menu_item(menu_savestate, 1) -- End
if cur_skill ~= skill then
menu_savestate = select_menu_item(menu_savestate, 2) -- Select skill
menu_savestate = select_menu_item(menu_savestate, skill)
else
menu_savestate = select_menu_item(menu_savestate, 1) -- Select course
end
menu_savestate = select_num_floors(menu_savestate, num_floors)
menu_savestate = select_menu_item(menu_savestate, display)
end
-- Try the B Button.
local b_savestate = savestate
if cur_display ~= display or cur_num_floors ~= num_floors or cur_skill ~= skill then
b_savestate = cancel_menu(b_savestate)
if cur_num_floors ~= num_floors or cur_skill ~= skill then
b_savestate = cancel_menu(b_savestate)
if cur_skill ~= skill then
b_savestate = cancel_menu(b_savestate)
b_savestate = select_menu_item(b_savestate, skill)
end
b_savestate = select_num_floors(b_savestate, num_floors)
end
b_savestate = select_menu_item(b_savestate, display)
end
-- Break ties in favor of the End menu, because that is usually faster.
if savestate_frame_count(menu_savestate) <= savestate_frame_count(b_savestate) then
savestate = menu_savestate
else
savestate = b_savestate
end
return savestate
end
assert(coroutine.resume(coroutine.create(function ()
console.log("main start")
local savestate = BASE_SAVESTATE
-- Run until the title screen (skipping over the BIOS animation).
savestate = run_until_exec(savestate, nil, TITLE_SCREEN_ADDR)
-- Find the earliest frame at which we can press Start to get past the
-- title screen.
savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {Start = true})
end, function (s)
return run_until_exec(s, 32, MENU_ADDR)
end)
savestate = select_menu_item(savestate, 1) -- Heading Out?
local SEGMENTS = {
-- E42x30-H34x22-H22x10-H58x8-A37x12-A02x15-A04x15-A17x9
{ skill = SKILL_EASY, display = DISPLAY_BIRDSEYE, seed = 42, floors = { "32i", "35i", "55i", "46", "59", "45i", "49i", "58", "56", "33", "30i", "57", "48i", "31i", "47i", "51i", "37", "41", "38", "44i", "50i", "34i", "42i", "53", "43", "54i", "40i", "36", "52i", "39i" } },
{ skill = SKILL_HARD, display = DISPLAY_BIRDSEYE, seed = 34, floors = { "138i", "110i", "139", "113i", "146", "148i", "111i", "144i", "143", "140", "131", "142i", "136i", "149i", "112i", "147", "121", "129i", "127i", "141", "135i", "115i" } },
{ skill = SKILL_HARD, display = DISPLAY_BIRDSEYE, seed = 22, floors = { "128i", "123i", "145i", "117", "114i", "125", "119", "120", "133", "137" } },
{ skill = SKILL_HARD, display = DISPLAY_BIRDSEYE, seed = 58, floors = { "132", "122i", "126", "124i", "134i", "130", "118", "116" } },
{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed = 37, floors = { "109i", "100", "106", "104i", "98i", "65i", "63i", "93", "90", "73i", "95i", "96" } },
{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed = 2, floors = { "89i", "108", "71", "94", "87i", "102", "86", "92i", "80", "103i", "60i", "91", "97", "64", "62i" } },
{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed = 4, floors = { "68", "82i", "83i", "74i", "99", "70i", "76", "84i", "101", "77", "67", "90", "69", "107", "105i" } },
{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed = 17, floors = { "78", "75", "61i", "79", "88", "72", "85", "81i", "66i" } },
}
for i, segment in ipairs(SEGMENTS) do
-- Use the menus to set the skill, number of floors, and display.
if i == 1 then
savestate = select_menu_item(savestate, segment.skill)
savestate = select_num_floors(savestate, #segment.floors)
savestate = select_menu_item(savestate, segment.display)
else
-- Run until we're just about at the "Way to go!"
-- screen. (Saves some trials in post_run_reconfigure.)
savestate = earliest_effective_frame(savestate, function (s) return s end, function (s)
return run_until_exec(s, 4, FLOOR_END_ADDR)
end)
savestate = post_run_reconfigure(savestate, segment.skill, #segment.floors, segment.display)
end
-- At the Start/End menu, wait for the desired RNG seed.
savestate = earliest_effective_frame(savestate, function (s)
return savestate_joypad_set(s, {[menu_confirm_button(s)] = true}) -- Start
end, function (s)
return run_until_u8_becomes(s, 10, RNG_SEED_ADDR, segment.seed)
end)
console.log(string.format("start segment %s%02d%s %5d",
({[SKILL_EASY] = "E", [SKILL_AVERAGE] = "A", [SKILL_HARD] = "H"})[segment.skill],
segment.seed,
({[DISPLAY_DIAGONAL] = "D", [DISPLAY_BIRDSEYE] = "B"})[segment.display],
savestate_frame_count(savestate)
))
-- Play the floors in the segment.
for i, floor in ipairs(segment.floors) do
savestate, num_frames, num_steps = play_floor(savestate, SOLUTIONS[floor].moves)
-- Empirically determined overheads for the predicted
-- number of frames to solve each floor.
assert(num_steps == SOLUTIONS[floor].steps, string.format("steps %d != %d", num_steps, SOLUTIONS[floor].steps))
assert(num_frames == SOLUTIONS[floor].frames, string.format("frames %d != %d", num_frames, SOLUTIONS[floor].frames))
end
end
console.log(string.format("end %5d", savestate_frame_count(savestate)))
movie.stop()
console.log("main end")
end)))