lily's recent activity

  1. Comment on Nintendo President on the new Switch 2, tariffs and what's next for the company in ~games

    lily
    Link
    My issue with the Switch 2 right now is mostly that the value proposition at launch just isn't especially appealing to me. Mario Kart is fine, I guess, but I only really play it occasionally with...

    My issue with the Switch 2 right now is mostly that the value proposition at launch just isn't especially appealing to me. Mario Kart is fine, I guess, but I only really play it occasionally with friends or family. It's not enough to make me want to buy a whole new console, especially at these prices. The other first-party games announced at the Direct aren't even launch titles, and didn't stand out to me too much. I feel it might have been a good idea to hold back some of the last couple rounds of Switch titles to be launch titles for the Switch 2, especially considering many of those hardly ran at all on the original Switch anyway...

    3 votes
  2. Comment on The future is Niri in ~comp

    lily
    Link Parent
    Yeah, Arch + KDE Plasma is fantastic. KDE is also a good choice for those coming from Windows, since the UI is familiar. Many things could still use some polish, certainly, but I'd advise those...

    Yeah, Arch + KDE Plasma is fantastic. KDE is also a good choice for those coming from Windows, since the UI is familiar. Many things could still use some polish, certainly, but I'd advise those who have had trouble with Linux in the past to try again, since things are rapidly improving.

    1 vote
  3. Comment on The future is Niri in ~comp

    lily
    Link Parent
    Having used Wayland for over a year now, since I initially switched to Linux (on KDE, if that matters), I've never had any problems with it. Maybe it's down to having never really used X11 for any...

    Having used Wayland for over a year now, since I initially switched to Linux (on KDE, if that matters), I've never had any problems with it. Maybe it's down to having never really used X11 for any considerable amount of time, but honestly my Linux setup is more stable than Windows most of the time now. This is on Nvidia and from someone who plays a lot of games, too.

    (Maybe it makes a difference that I'm on a rolling distro?)

    4 votes
  4. Comment on Have you made a video game? Can I play it? in ~games

    lily
    Link
    I've got all my work on https://python-b5.com/. Most of it is game jam games, but a few of those have been updated since to be more polished. I usually try in game jams to make a small, complete...

    I've got all my work on https://python-b5.com/. Most of it is game jam games, but a few of those have been updated since to be more polished. I usually try in game jams to make a small, complete game, rather than a prototype, since usually I am not interested in continuing work on these games after the deadline. The first few of them I made when I was very young, and am not particularly proud of - from about 2022 on I think I improved.

    This is probably far less interesting to the general crowd here, but I also work on an Undertale fangame as a programmer. If you heard of Undertale Yellow's release last year, it's kind of like that.

    1 vote
  5. Comment on R.E.P.O. | Teaser trailer in ~games

    lily
    Link Parent
    Hey, thanks for letting me know! I'll see about trying it when I have time.

    Hey, thanks for letting me know! I'll see about trying it when I have time.

    3 votes
  6. Comment on R.E.P.O. | Teaser trailer in ~games

    lily
    (edited )
    Link
    The thumbnail used for this game is really unappealing to me. The emoji visual doesn't seem to be present in the game, at least from the trailer, so why use it for promotional materials? Looking...

    The thumbnail used for this game is really unappealing to me. The emoji visual doesn't seem to be present in the game, at least from the trailer, so why use it for promotional materials? Looking at that image doesn't make me want to play the game, even though I'm sure it's perfectly fine.

    (Though, to be fair, I don't find the game to look terribly interesting anyway. There have been a few of these obviously Lethal Company-inspired games since that game hit it big, and they all look fine enough, but I'm not sure why I'd want to play any of them instead of just... playing Lethal Company.)

    5 votes
  7. Comment on SDL 3 official release in ~comp

    lily
    Link
    SDL3 has been usable for a while, so my personal project's already ported to it. It's not a complete paradigm shift or anything, but the API is quite a bit nicer now, and there are some very...

    SDL3 has been usable for a while, so my personal project's already ported to it. It's not a complete paradigm shift or anything, but the API is quite a bit nicer now, and there are some very welcome improvements. SDL is and always has been fantastic; it's my recommendation for anyone looking to write a cross-platform game.

    2 votes
  8. Comment on UFO50AndroidUnofficial: A tool to build your own Android version of UFO 50 in ~games

    lily
    Link Parent
    The main benefit is huge performance gains over VM, though for most GameMaker games, which overwhelmingly tend to be fairly graphically simple 2D fare since that's what GameMaker is good at (it's...

    The main benefit is huge performance gains over VM, though for most GameMaker games, which overwhelmingly tend to be fairly graphically simple 2D fare since that's what GameMaker is good at (it's maybe the best major engine for pixel art games), that doesn't really matter. Some developers may want their source code to be locked down, also - not everyone is quite as mod-positive as me.

    2 votes
  9. Comment on UFO50AndroidUnofficial: A tool to build your own Android version of UFO 50 in ~games

    lily
    Link Parent
    We're lucky in this case that the UFO 50 developers chose to compile the game the way they did. GameMaker has two ways to compile your game: VM and YYC. VM is how the engine initially worked...

    We're lucky in this case that the UFO 50 developers chose to compile the game the way they did.

    GameMaker has two ways to compile your game: VM and YYC. VM is how the engine initially worked (after the initial release of GameMaker: Studio, at least - in the pre-GMS versions, games' source code was stored as plaintext and interpreted directly); source code is compiled to bytecode and packed together with all the game's assets into a data file. It's called data.win on Windows, which is probably the most common name it's referred to as, but it has a different extension on every platform for some reason. At this point, this data format has been completely reverse-engineered, and there is a high-quality decompiler for the bytecode (as a part of UndertaleModTool, a full modding suite for the engine) that can turn it back into readable GML code. This data file is paired with a runner that's the same for every game made with a given GameMaker version, which is what makes UFO 50 relatively easy to port to Android in this case. You just need to get an Android runner for the correct GameMaker version, which is easy to obtain by just compiling a dummy project, and you're good to go.

    YYC still uses a data file with the same format, but source code is no longer stored in it. Instead, it's transpiled to C++, then compiled to native machine code as part of the runner executable. This makes the game not especially moddable, and would make it way harder to port to other platforms.

    I've always used VM for my own GameMaker games to allow mods and ports like this to be possible. I always find it a shame when a game is locked down enough to be difficult to mod. This kind of thing is what gives games longevity, and I think it's important to encourage it.

    Unfortunately, things may change in the future. YoYo Games has been working for a while on a new GameMaker runtime (tentatively called GMRT), which will completely change the format games are stored in and possibly lock modders out. At the very least, it would be a whole lot of work to build back to the point we're at now, where GameMaker games can be completely taken apart and changed at will. I'm sure there will be improvements along with the new runtime, but it's a real shame we might have all that progress reset soon.

    4 votes
  10. Comment on Stimulation Clicker in ~games

    lily
    (edited )
    Link
    Got a decent amount into this, but it is incredibly poorly optimized. It's still running in the background - I can hear it - but the tab won't open anymore. Either the developer has a particularly...

    Got a decent amount into this, but it is incredibly poorly optimized. It's still running in the background - I can hear it - but the tab won't open anymore. Either the developer has a particularly powerful machine, or this wasn't tested properly. I'm not convinced it's even possible to complete on my laptop; I think I was only about halfway through.

    EDIT: With Firefox, I was able to complete the game! Still incredibly laggy, but it was playable.

  11. Comment on Half-Life 3 playtests begin and 2025 reveal “quite possible,” says Valve insider in ~games

    lily
    Link Parent
    I was a teenager not very long ago, and Half-Life wasn't particularly unknown within my social circles... I think the age range for stuff like Skibidi Toilet is more what they're calling...

    I was a teenager not very long ago, and Half-Life wasn't particularly unknown within my social circles... I think the age range for stuff like Skibidi Toilet is more what they're calling Generation Alpha - so, younger children. I'd say for people around my age, if they didn't know about Half-Life specifically, they were probably exposed to it through Garry's Mod, or just from the general influence Half-Life 2 has had on internet/meme culture over the years. I don't think most teenagers today would be caught dead watching Skibidi Toilet; "cringe" content like that is looked down upon.

    4 votes
  12. Comment on Half-Life 3 playtests begin and 2025 reveal “quite possible,” says Valve insider in ~games

    lily
    Link Parent
    I mean, I'm what you'd probably call a "zoomer", and Half-Life 2 is one of my favorite games. I have friends who have played and enjoyed the game as well. I don't know anybody personally who plays...

    I mean, I'm what you'd probably call a "zoomer", and Half-Life 2 is one of my favorite games. I have friends who have played and enjoyed the game as well. I don't know anybody personally who plays either Fortnite or Helldivers (and I'm hardly familiar with them at all, myself). So, it doesn't necessarily disprove your point, but just keep in mind demographics are very broad, and trying to generalize across an entire generation is probably not going to be accurate.

    7 votes
  13. Comment on Day 25: Code Chronicle in ~comp.advent_of_code

    lily
    Link Parent
    It's very procedural / C-like and quite opinionated, so it probably is only appealing to specific types of programmers. I've found some things about it I like, and some I don't - though, maybe...

    It's very procedural / C-like and quite opinionated, so it probably is only appealing to specific types of programmers. I've found some things about it I like, and some I don't - though, maybe some of the latter will be improved by the time the language enters open beta. The compile-time features are probably the language's "big idea", though I didn't find much use for them in Advent of Code.

    1 vote
  14. Comment on Day 25: Code Chronicle in ~comp.advent_of_code

    lily
    Link
    Glad for the easier one today. I wasn't able to solve yesterday's part 2 as I'm not familiar with adders and learning about them didn't feel like a fun way to spend my Christmas Eve. If anyone has...

    Glad for the easier one today. I wasn't able to solve yesterday's part 2 as I'm not familiar with adders and learning about them didn't feel like a fun way to spend my Christmas Eve. If anyone has any hints for that that don't entirely give the solution away, I would appreciate it. Until then, I'll have to leave this year's Advent of Code unfinished.

    As for today's puzzle, it was one of the easiest this year. I wasn't especially fast at solving it, unfortunately, but there really wasn't anything to solve about it - it was immediately clear what I was supposed to do. Most of my time was spent writing input parsing code.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 25: Code Chronicle
     */
    
    #import "Basic";
    #import "File";
    #import "String";
    
    main :: () {
        input, success := read_entire_file("inputs/day_25.txt");
        assert(success);
    
        groups := split(input, "\n\n");
        defer array_free(groups);
    
        keys: [..][5] int;
        locks: [..][5] int;
        defer array_free(keys);
        defer array_free(locks);
    
        for groups {
            lines := split(it, "\n");
            defer array_free(lines);
    
            if lines[0] == "....." {
                key: [5] int;
                for x: 0..4 {
                    height := 0;
                    for #v2 < y: 1..5 {
                        if lines[y][x] == #char "#" {
                            height += 1;
                        } else {
                            break;
                        }
                    }
    
                    key[x] = height;
                }
    
                array_add(*keys, key);
            } else {
                lock: [5] int;
                for x: 0..4 {
                    height := 0;
                    for y: 1..5 {
                        if lines[y][x] == #char "#" {
                            height += 1;
                        } else {
                            break;
                        }
                    }
    
                    lock[x] = height;
                }
    
                array_add(*locks, lock);
            }
        }
    
        fitting_pairs := 0;
        for key: keys {
            for lock: locks {
                fits := true;
                for 0..4 {
                    if key[it] + lock[it] > 5 {
                        fits = false;
                        break;
                    }
                }
    
                if fits {
                    fitting_pairs += 1;
                }
            }
        }
    
        print("Part 1: %\n", fitting_pairs);
    }
    
    1 vote
  15. Comment on Tildes Game Giveaway: Holiday 2024 in ~games

    lily
    Link Parent
    I've heard nothing but effusive praise for this game, so I figure I should give it a try eventually! One of my favorite childhood games was Super Mario Galaxy. I still don't think Nintendo has...

    I've heard nothing but effusive praise for this game, so I figure I should give it a try eventually!

    One of my favorite childhood games was Super Mario Galaxy. I still don't think Nintendo has surpassed it since (maybe in pure gameplay - but not in style or heart), so I would definitely still choose to gift it to a kid today.

    2 votes
  16. Comment on Day 23: LAN Party in ~comp.advent_of_code

    lily
    Link
    I do not know graph theory especially well, and of course Jai has no available libraries to do it for me, so I had to implement everything myself. Part 1 was easy enough, at least, though my...

    I do not know graph theory especially well, and of course Jai has no available libraries to do it for me, so I had to implement everything myself. Part 1 was easy enough, at least, though my original method for finding all 3-length cliques was pretty slow, and I had to go back afterwards to speed it up. I cannot claim to understand everything that's going on in part 2 all that well - I just searched online for algorithms and picked what seemed to be the most common one. Thankfully, implementing it wasn't too bad. The performance of part 2 could definitely stand to be improved a little, but I'm not sure how I would go about doing that, so I'll just leave it as-is.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 23: LAN Party
     */
    
    #import "Basic";
    #import "File";
    #import "Hash_Table";
    #import "Sort";
    #import "String";
    
    Connection_Table :: Table(string, [..] string);
    
    // Finds all maximal cliques using the Bron-Kerbosch algorithm.
    bron_kerbosch :: (
        r: [] string,
        p: [] string,
        x: [] string,
        connections: Connection_Table,
        cliques: *[..][..] string
    ) {
        if p.count == 0 && x.count == 0 {
            // We've found a maximal clique.
            clique: [..] string;
            array_copy(*clique, r);
            array_add(cliques, clique);
        }
    
        // p and x are passed as array views, but we need to modify them.
        mutable_p: [..] string;
        array_copy(*mutable_p, p);
        defer array_free(mutable_p);
    
        mutable_x: [..] string;
        array_copy(*mutable_x, x);
        defer array_free(mutable_x);
    
        for mutable_p {
            new_r: [..] string;
            new_p: [..] string;
            new_x: [..] string;
    
            defer array_free(new_r);
            defer array_free(new_p);
            defer array_free(new_x);
    
            array_copy(*new_r, r);
            array_add(*new_r, it);
    
            // The connections table is guaranteed to contain all computers.
            individual_connections, _ := table_find(*connections, it);
    
            for mutable_p {
                if array_find(individual_connections, it) {
                    array_add(*new_p, it);
                }
            }
    
            for mutable_x {
                if array_find(individual_connections, it) {
                    array_add(*new_x, it);
                }
            }
    
            bron_kerbosch(new_r, new_p, new_x, connections, cliques);
    
            array_add(*mutable_x, it);
            remove it;
        }
    }
    
    main :: () {
        input, success := read_entire_file("inputs/day_23.txt");
        assert(success);
    
        lines := split(input, "\n");
        defer array_free(lines);
    
        connections: Connection_Table;
        defer {
            for connections {
                array_free(it);
            }
    
            deinit(*connections);
        }
    
        for lines {
            if it == "" {
                continue;
            }
    
            computers := split(it, "-");
            defer array_free(computers);
    
            for computers {
                individual_connections := table_find_pointer(*connections, it);
                if individual_connections == null {
                    individual_connections: [..] string;
                    array_add(*individual_connections, computers[xx !it_index]);
                    table_add(*connections, it, individual_connections);
                } else {
                    array_add(individual_connections, computers[xx !it_index]);
                }
            }
        }
    
        computers: [..] string;
        defer array_free(computers);
    
        for connections {
            array_add(*computers, it_index);
        }
    
        triangles_with_t := 0;
        for computer_a, index_a: computers {
            connections_a, _ := table_find(*connections, computer_a);
            for computer_b: connections_a {
                _, index_b := array_find(computers, computer_b);
                if index_b <= index_a {
                    continue;
                }
    
                connections_b, _ := table_find(*connections, computer_b);
                for computer_c: connections_b {
                    _, index_c := array_find(computers, computer_c);
                    if index_c <= index_b {
                        continue;
                    }
    
                    if array_find(connections_a, computer_c) && (
                        computer_a[0] == #char "t"
                        || computer_b[0] == #char "t"
                        || computer_c[0] == #char "t"
                    ) {
                        triangles_with_t += 1;
                    }
                }
            }
        }
    
        print("Part 1: %\n", triangles_with_t);
    
        cliques: [..][..] string;
        defer {
            for cliques {
                array_free(it);
            }
    
            array_free(cliques);
        }
    
        bron_kerbosch(.[], computers, .[], connections, *cliques);
    
        lan_party: [..] string;
        largest_count := 0;
    
        for cliques {
            if it.count > largest_count {
                lan_party = it;
                largest_count = it.count;
            }
        }
    
        print("Part 2: %\n", join(.. quick_sort(lan_party, compare), ","));
    }
    
    1 vote
  17. Comment on Tildes Game Giveaway: Holiday 2024 in ~games

    lily
    Link Parent
    I'd be interested in TIS-100, provided the code works. I've been meaning to try some of the Zachtronics stuff.

    I'd be interested in TIS-100, provided the code works. I've been meaning to try some of the Zachtronics stuff.

    1 vote
  18. Comment on Tildes Game Giveaway: Holiday 2024 in ~games

    lily
    Link Parent
    Games (in order of preference): The Talos Principle 2, Windblown, Jusant Words: warmth, family, coziness, lights, calm Thank you for doing this!

    Games (in order of preference): The Talos Principle 2, Windblown, Jusant
    Words: warmth, family, coziness, lights, calm

    Thank you for doing this!

  19. Comment on Day 22: Monkey Market in ~comp.advent_of_code

    lily
    Link
    Part 2 took me a bit longer than I'd like, but I just barely made top 1000 for part 1, which was nice. The trick to solving part 2 in a reasonable amount of time was just to do everything in a...

    Part 2 took me a bit longer than I'd like, but I just barely made top 1000 for part 1, which was nice. The trick to solving part 2 in a reasonable amount of time was just to do everything in a single iteration, and store the price sums in a table to find the maximum of afterwards. I was glad for the slightly easier puzzle, after yesterday - though I'll admit I'm worried for what the last few days will bring.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 22: Monkey Market
     */
    
    #import "Basic";
    #import "File";
    #import "Hash_Table";
    #import "String";
    
    // There's no point making this an actual == operator; it's never used as such,
    // and doing so would make it harder to use in tables.
    change_sequences_are_equal :: inline (a: [4] int, b: [4] int) -> bool {
        return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
    }
    
    main :: () {
        input, success := read_entire_file("inputs/day_22.txt");
        assert(success);
    
        lines := split(input, "\n");
        defer array_free(lines);
    
        secrets: [..] int;
        defer array_free(secrets);
    
        for lines {
            if it != "" {
                array_add(*secrets, string_to_int(it));
            }
        }
    
        price_sums: Table(
            [4] int,
            int,
            given_compare_function = change_sequences_are_equal
        );
        defer deinit(*price_sums);
    
        secret_sum := 0;
        for secrets {
            prices: [2001] int;
            prices[0] = it % 10;
    
            for 1..2000 {
                secrets[it_index] = (
                    (secrets[it_index] * 64) ^ secrets[it_index]
                ) % 16777216;
    
                secrets[it_index] = (
                    (secrets[it_index] / 32) ^ secrets[it_index]
                ) % 16777216;
    
                secrets[it_index] = (
                    (secrets[it_index] * 2048) ^ secrets[it_index]
                ) % 16777216;
    
                prices[it] = secrets[it_index] % 10;
            }
    
            secret_sum += secrets[it_index];
    
            changes: [2000] int;
            for prices {
                if it_index > 0 {
                    changes[it_index - 1] = it - prices[it_index - 1];
                }
            }
    
            seen_sequences: Table(
                [4] int,
                void,
                given_compare_function = change_sequences_are_equal
            );
    
            for 1..1997 {
                sequence: [4] int;
                for array_view(changes, it - 1, 4) {
                    sequence[it_index] = it;
                }
    
                if !table_contains(*seen_sequences, sequence) {
                    current_price_sum, success := table_find(*price_sums, sequence);
                    if success {
                        table_set(
                            *price_sums,
                            sequence,
                            current_price_sum + prices[it + 3]
                        );
                    } else {
                        table_add(*price_sums, sequence, prices[it + 3]);
                    }
    
                    table_add(*seen_sequences, sequence, #run {});
                }
            }
        }
    
        print("Part 1: %\n", secret_sum);
    
        max_price_sum := 0;
        for price_sums {
            if it > max_price_sum {
                max_price_sum = it;
            }
        }
    
        print("Part 2: %\n", max_price_sum);
    }
    
    2 votes
  20. Comment on Day 21: Keypad Conundrum in ~comp.advent_of_code

    lily
    Link
    Well, it took me a good while, but I finally got this working. Correctly generating the most efficient paths between buttons was one of the sticking points for me - the priorities of each...

    Well, it took me a good while, but I finally got this working. Correctly generating the most efficient paths between buttons was one of the sticking points for me - the priorities of each direction were difficult to wrap my head around. Part 2 wasn't too bad once I realized there was no feasible way to work with the strings directly, luckily. A good majority of my time was spent on part 1. I'm not especially happy with my final solution (I think it's quite messy and could have been structured much better), but I do not want to work on this puzzle any further.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 21: Keypad Conundrum
     */
    
    #import "Basic";
    #import "File";
    #import "Hash";
    #import "Hash_Table";
    #import "Math";
    #import "String";
    
    Vector2_Int :: struct {
        x, y: int;
    }
    
    State :: struct {
        code: string;
        depth: int;
    }
    
    Button_Table :: Table(u8, Vector2_Int);
    
    Path_Table :: Table(
        [2] u8,
        [..] u8,
        given_compare_function = (a: [2] u8, b: [2] u8) -> bool {
            return a[0] == b[0] && a[1] == b[1];
        }
    );
    
    Length_Table :: Table(
        State,
        int,
        (state: State) -> u32 {
            return inline sdbm_hash(xx *state, size_of(State));
        },
        (a: State, b: State) -> bool {
            return a.code == b.code && a.depth == b.depth;
        }
    );
    
    operator == :: inline (a: Vector2_Int, b: Vector2_Int) -> bool {
        return a.x == b.x && a.y == b.y;
    }
    
    sign :: inline (n: int) -> int {
        if n < 0 return -1;
        if n > 0 return 1;
        return 0;
    }
    
    get_path :: (
        start: Vector2_Int,
        end: Vector2_Int,
        gap: Vector2_Int
    ) -> [..] u8 {
        path: [..] u8;
    
        if start != end {
            char_x: u8 = xx (
                ifx sign(end.x - start.x) == -1 then #char "<" else #char ">"
            );
    
            char_y: u8 = xx (
                ifx sign(end.y - start.y) == -1 then #char "^" else #char "v"
            );
    
            // This is a little convoluted, but makes sure we're using the correct
            // direction priorities.
            if char_x == #char "<" {
                if Vector2_Int.{end.x, start.y} == gap {
                    for 1..abs(end.y - start.y) array_add(*path, char_y);
                    for 1..abs(end.x - start.x) array_add(*path, char_x);
                } else {
                    for 1..abs(end.x - start.x) array_add(*path, char_x);
                    for 1..abs(end.y - start.y) array_add(*path, char_y);
                }
            } else if Vector2_Int.{start.x, end.y} == gap {
                for 1..abs(end.x - start.x) array_add(*path, char_x);
                for 1..abs(end.y - start.y) array_add(*path, char_y);
            } else {
                for 1..abs(end.y - start.y) array_add(*path, char_y);
                for 1..abs(end.x - start.x) array_add(*path, char_x);
            }
        }
    
        array_add(*path, #char "A");
        return path;
    }
    
    get_paths :: (buttons: Button_Table, gap: Vector2_Int) -> Path_Table {
        paths: Path_Table;
        for first_position, first: buttons {
            for second_position, second: buttons {
                table_add(*paths, .[first, second], get_path(
                    first_position,
                    second_position,
                    gap
                ));
            }
        }
    
        return paths;
    }
    
    free_paths :: (paths: *Path_Table) {
        for paths {
            array_free(it);
        }
    
        deinit(paths);
    }
    
    get_sequence :: (code: string, depth: int, paths: *Path_Table) -> string {
        sequence: [..] u8;
        previous: u8 = #char "A";
    
        for 0..code.count - 1 {
            path, _ := table_find(paths, .[previous, code[it]]);
            array_add(*sequence, .. path);
            previous = code[it];
        }
    
        return xx sequence;
    }
    
    get_sequence_length :: (
        code: string,
        depth: int,
        paths: *Path_Table,
        known_lengths: *Length_Table
    ) -> int {
        state := State.{code, depth};
        known_length, success := table_find(known_lengths, state);
    
        if success {
            return known_length;
        }
    
        length := 0;
        previous: u8 = #char "A";
    
        for code {
            path, _ := table_find(paths, .[previous, it]);
    
            if depth > 1 {
                length += get_sequence_length(
                    xx path,
                    depth - 1,
                    paths,
                    known_lengths
                );
            } else {
                length += path.count;
            }
    
            previous = it;
        }
    
        table_add(known_lengths, state, length);
        return length;
    }
    
    main :: () {
        input, success := read_entire_file("inputs/day_21.txt");
        assert(success);
    
        codes := split(input, "\n");
        pop(*codes);
        defer array_free(codes);
    
        // Might as well run all this at compile time. These don't change based on
        // the input.
        numeric_buttons :: #run () -> Button_Table {
            buttons: Button_Table;
            table_add(*buttons, #char "0", .{1, 3});
            table_add(*buttons, #char "1", .{0, 2});
            table_add(*buttons, #char "2", .{1, 2});
            table_add(*buttons, #char "3", .{2, 2});
            table_add(*buttons, #char "4", .{0, 1});
            table_add(*buttons, #char "5", .{1, 1});
            table_add(*buttons, #char "6", .{2, 1});
            table_add(*buttons, #char "7", .{0, 0});
            table_add(*buttons, #char "8", .{1, 0});
            table_add(*buttons, #char "9", .{2, 0});
            table_add(*buttons, #char "A", .{2, 3});
    
            return buttons;
        }();
    
        directional_buttons :: #run () -> Button_Table {
            buttons: Button_Table;
            table_add(*buttons, #char "<", .{0, 1});
            table_add(*buttons, #char ">", .{2, 1});
            table_add(*buttons, #char "^", .{1, 0});
            table_add(*buttons, #char "v", .{1, 1});
            table_add(*buttons, #char "A", .{2, 0});
    
            return buttons;
        }();
    
        numeric_gap :: Vector2_Int.{0, 3};
        directional_gap :: Vector2_Int.{0, 0};
    
        numeric_paths := get_paths(numeric_buttons, numeric_gap);
        directional_paths := get_paths(directional_buttons, directional_gap);
        defer free_paths(*numeric_paths);
        defer free_paths(*directional_paths);
    
        directional_start :: Vector2_Int.{2, 0};
    
        complexity_sum_part_1 := 0;
        complexity_sum_part_2 := 0;
    
        known_lengths: Length_Table;
        defer deinit(*known_lengths);
    
        for codes {
            // I don't think there's a way around generating the actual sequence for
            // the robot using the numeric keypad - at least, not without massively
            // complicating the get_sequence_length() procedure by adding the
            // numeric keypad as a base case. Besides, doing it this way means we
            // only need to find the sequence once.
            first_sequence := get_sequence(it, 0, *numeric_paths);
            numeric_part := string_to_int(slice(it, 0, 3));
    
            complexity_sum_part_1 += get_sequence_length(
                first_sequence,
                2,
                *directional_paths,
                *known_lengths
            ) * numeric_part;
    
            complexity_sum_part_2 += get_sequence_length(
                first_sequence,
                25,
                *directional_paths,
                *known_lengths
            ) * numeric_part;
        }
    
        print(
            "Part 1: %\nPart 2: %\n",
            complexity_sum_part_1,
            complexity_sum_part_2
        );
    }
    
    2 votes