lily's recent activity

  1. 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.

  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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.

  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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!

  16. 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
  17. 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
  18. Comment on Day 20: Race Condition in ~comp.advent_of_code

    lily
    (edited )
    Link
    Took some time to make this a little faster. My original solution ran BFS individually for every position on the grid - twice - to fill the distance tables, which was incredibly inefficient. I...

    Took some time to make this a little faster. My original solution ran BFS individually for every position on the grid - twice - to fill the distance tables, which was incredibly inefficient. I eventually realized I could fill each table with only one traversal, by just allowing BFS to search the whole track. Whereas my original solution took around 6 seconds, my new one runs nearly instantaneously.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 20: Race Condition
     */
    
    #import "Basic";
    #import "File";
    #import "Flat_Pool";
    #import "Hash";
    #import "Hash_Table";
    #import "Math";
    #import "String";
    
    Vector2_Int :: struct {
        x, y: int;
    }
    
    Node :: struct {
        cost: int;
        position: Vector2_Int;
    }
    
    Vector2_Int_Set :: Table(
        Vector2_Int,
        void,
        (vector: Vector2_Int) -> u32 {
            return inline sdbm_hash(xx *vector, size_of(Vector2_Int));
        },
        #procedure_of_call(operator ==(Vector2_Int.{}, Vector2_Int.{}))
    );
    
    operator == :: (a: Vector2_Int, b: Vector2_Int) -> bool {
        return a.x == b.x && a.y == b.y;
    }
    
    // Traverse the whole track and fill a (flattened two-dimensional) array with
    // the distances from a given start position to every other position.
    fill_distances_array :: (
        track: [][..] bool,
        width: int,
        start: Vector2_Int,
        distances: [] int
    ) {
        queue: [..] *Node;
        defer array_free(queue);
    
        start_node := New(Node);
        start_node.position = start;
        array_add(*queue, start_node);
    
        seen: Vector2_Int_Set;
        table_add(*seen, start, #run {});
        defer deinit(*seen);
    
        while queue.count {
            current := queue[0];
            array_ordered_remove_by_index(*queue, 0);
    
            index := current.position.y * width + current.position.x;
            if distances[index] == 0 {
                distances[index] = current.cost;
            }
    
            for Vector2_Int.[
                .{current.position.x - 1, current.position.y},
                .{current.position.x + 1, current.position.y},
                .{current.position.x, current.position.y - 1},
                .{current.position.x, current.position.y + 1}
            ] {
                if !track[it.y][it.x] && !table_contains(*seen, it) {
                    new_node := New(Node);
                    new_node.cost = current.cost + 1;
                    new_node.position = it;
    
                    array_add(*queue, new_node);
                    table_add(*seen, it, #run {});
                }
            }
        }
    }
    
    find_efficient_cheats :: (
        track: [][..] bool,
        width: int,
        height: int,
        fastest_time: int,
        distances_from_start: [] int,
        distances_to_end: [] int,
        cheat_distance: int
    ) -> int {
        efficient_cheats := 0;
        for x1: 1..width - 2 {
            for y1: 1..height - 2 {
                for x2: x1 - cheat_distance..x1 + cheat_distance {
                    for y2: y1 - cheat_distance..y1 + cheat_distance {
                        if
                            x2 <= 0 || x2 >= width - 1
                            || y2 <= 0 || y2 >= height - 1
                        {
                            continue;
                        }
    
                        distance := abs(x2 - x1) + abs(y2 - y1);
                        if
                            distance <= cheat_distance
                            && !track[y1][x1] && !track[y2][x2]
                            && fastest_time - (
                                distances_from_start[y1 * width + x1]
                                + distance
                                + distances_to_end[y2 * width + x2]
                            ) >= 100
                        {
                            efficient_cheats += 1;
                        }
                    }
                }
            }
        }
    
        return efficient_cheats;
    }
    
    main :: () {
        input, success := read_entire_file("inputs/day_20.txt");
        assert(success);
    
        lines := split(input, "\n");
        defer array_free(lines);
    
        track: [..][..] bool;
        defer {
            for track {
                array_free(it);
            }
    
            array_free(track);
        }
    
        start, end: Vector2_Int;
    
        for line, y: lines {
            if line == "" {
                continue;
            }
    
            row: [..] bool;
            for char, x: line {
                array_add(*row, char == #char "#");
    
                if char == #char "S" {
                    start = .{x, y};
                } else if char == #char "E" {
                    end = .{x, y};
                }
            }
    
            array_add(*track, row);
        }
    
        width := track[0].count;
        height := track.count;
    
        distances_size := width * height;
        distances_from_start := NewArray(distances_size, int);
        distances_to_end := NewArray(distances_size, int);
        defer array_free(distances_from_start);
        defer array_free(distances_to_end);
    
        // Yet again, it's faster to use a pool when we're doing a bunch of graph
        // traversal (though the performance gain in this case is probably
        // negligable, since we're only running BFS twice).
        pool: Flat_Pool;
        defer reset(*pool);
    
        new_context := context;
        new_context.allocator.proc = flat_pool_allocator_proc;
        new_context.allocator.data = *pool;
    
        push_context new_context {
            fill_distances_array(track, width, start, distances_from_start);
            fill_distances_array(track, width, end, distances_to_end);
        }
    
        fastest_time := distances_to_end[start.y * width + start.x];
        print(
            "Part 1: %\nPart 2: %\n",
            find_efficient_cheats(
                track,
                width,
                height,
                fastest_time,
                distances_from_start,
                distances_to_end,
                2
            ),
            find_efficient_cheats(
                track,
                width,
                height,
                fastest_time,
                distances_from_start,
                distances_to_end,
                20
            )
        );
    }
    
    4 votes
  19. Comment on Day 19: Linen Layout in ~comp.advent_of_code

    lily
    (edited )
    Link
    Still in the cooldown period, I see. Didn't mind the easier puzzle, though. The only "trick" to this was using memoization so it could finish in a reasonable time. Solution (Jai) /* Advent of Code...

    Still in the cooldown period, I see. Didn't mind the easier puzzle, though. The only "trick" to this was using memoization so it could finish in a reasonable time.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 19: Linen Layout
     */
    
    #import "Basic";
    #import "File";
    #import "Hash_Table";
    #import "String";
    
    get_solution :: (
        design: string,
        towels: [] string,
        known_solutions: *Table(string, int)
    ) -> int {
        known_solution, success := table_find(known_solutions, design);
        if success {
            return known_solution;
        }
    
        solution := 0;
        for towels {
            if design == it {
                solution += 1;
            } else if begins_with(design, it) {
                solution += get_solution(
                    slice(design, it.count, design.count - it.count),
                    towels,
                    known_solutions
                );
            }
        }
    
        table_add(known_solutions, design, solution);
        return solution;
    }
    
    main :: () {
        input, success := read_entire_file("inputs/day_19.txt");
        assert(success);
    
        lines := split(input, "\n");
        defer array_free(lines);
    
        towels := split(lines[0], ", ");
        defer array_free(towels);
    
        designs := array_view(lines, 2, lines.count - 3);
    
        // I didn't want this to be global, so I'm passing a pointer to it into the
        // get_solutions() procedure.
        known_solutions: Table(string, int);
        defer deinit(*known_solutions);
    
        possible_designs := 0;
        total_solutions := 0;
    
        for designs {
            solutions := get_solution(it, towels, *known_solutions);
    
            if solutions > 0 {
                possible_designs += 1;
            }
    
            total_solutions += solutions;
        }
    
        print("Part 1: %\nPart 2: %\n", possible_designs, total_solutions);
    }
    
    3 votes
  20. Comment on Day 18: RAM Run in ~comp.advent_of_code

    lily
    Link
    This was a nice breather after yesterday - I definitely should've solved it quicker than I did, but I got caught up on a couple annoying bugs. Just another simple pathfinding problem, though this...

    This was a nice breather after yesterday - I definitely should've solved it quicker than I did, but I got caught up on a couple annoying bugs. Just another simple pathfinding problem, though this one was even easier since it wasn't weighted (so I didn't have to bother with Djikstra). My solution for part 2 is incredibly lazy and pretty slow, but I don't really feel up to optimizing it further right now.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 18: RAM Run
     */
    
    #import "Basic";
    #import "File";
    #import "Flat_Pool";
    #import "Hash";
    #import "Hash_Table";
    #import "String";
    
    // These need to be configurable to make using the sample input easy.
    GRID_SIZE :: 71;
    PART_1_BYTES :: 1024;
    
    Vector2_Int :: struct {
        x, y: int;
    }
    
    Node :: struct {
        parent: *Node;
        position: Vector2_Int;
    }
    
    Vector2_Int_Set :: Table(
        Vector2_Int,
        void,
        (vector: Vector2_Int) -> u32 {
            return inline sdbm_hash(xx *vector, size_of(Vector2_Int));
        },
        #procedure_of_call(operator ==(Vector2_Int.{}, Vector2_Int.{}))
    );
    
    operator == :: (a: Vector2_Int, b: Vector2_Int) -> bool {
        return a.x == b.x && a.y == b.y;
    }
    
    solve_grid :: (grid: [GRID_SIZE][GRID_SIZE] bool) -> int {
        queue: [..] *Node;
        array_add(*queue, New(Node));
        defer array_free(queue);
    
        seen: Vector2_Int_Set;
        table_add(*seen, .{0, 0}, #run {});
        defer deinit(*seen);
    
        while queue.count {
            current := queue[0];
            array_ordered_remove_by_index(*queue, 0);
    
            if current.position == .{GRID_SIZE - 1, GRID_SIZE - 1} {
                path_length := 0;
                while current != null {
                    path_length += 1;
                    current = current.parent;
                }
    
                return path_length - 1;
            }
    
            for Vector2_Int.[
                .{current.position.x - 1, current.position.y},
                .{current.position.x + 1, current.position.y},
                .{current.position.x, current.position.y - 1},
                .{current.position.x, current.position.y + 1}
            ] {
                if
                    it.x >= 0 && it.x < GRID_SIZE && it.y >= 0 && it.y < GRID_SIZE
                    && !grid[it.y][it.x]
                    && !table_contains(*seen, it)
                {
                    new_node := New(Node);
                    new_node.parent = current;
                    new_node.position = it;
    
                    array_add(*queue, new_node);
                    table_add(*seen, it, #run {});
                }
            }
        }
    
        return -1;
    }
    
    main :: () {
        input, success := read_entire_file("inputs/day_18.txt");
        assert(success);
    
        lines := split(input, "\n");
        defer array_free(lines);
    
        grid: [GRID_SIZE][GRID_SIZE] bool;
        dropped_bytes := 0;
    
        for lines {
            position := split(it, ",");
            defer array_free(position);
    
            grid[string_to_int(position[1])][string_to_int(position[0])] = true;
    
            dropped_bytes += 1;
            if (dropped_bytes == PART_1_BYTES) {
                break;
            }
        }
    
        // Using a pool saves us time doing memory allocations in the solving
        // procedure. It's not a huge performance gain, but it's just about
        // noticeable.
        pool: Flat_Pool;
        defer reset(*pool);
    
        context.allocator.proc = flat_pool_allocator_proc;
        context.allocator.data = *pool;
    
        print("Part 1: %\n", solve_grid(grid));
    
        for dropped_bytes..lines.count - 2 {
            position := split(lines[it], ",");
            defer array_free(position);
    
            grid[string_to_int(position[1])][string_to_int(position[0])] = true;
    
            if solve_grid(grid) == -1 {
                print("Part 2: %\n", lines[it]);
                break;
            } else {
                // We'll run out of memory if we don't do this. With a smaller input
                // this might not be required, but for the size we're given the
                // program will crash if we don't do it.
                reset(*pool);
            }
        }
    }
    
    1 vote