lily's recent activity
-
Comment on CGA-2026-11 🌍️🪀 INSERT CARTRIDGE 🟢 Mother 3 in ~games
-
Comment on CGA-2026-11 🌍️🪀 INSERT CARTRIDGE 🟢 Mother 3 in ~games
lily (edited )Link ParentI would recommend playing Mother 2 eventually, at least, but you won't be lost in Mother 3 without playing it first. A couple characters make a return, but the game doesn't really expect that you...I would recommend playing Mother 2 eventually, at least, but you won't be lost in Mother 3 without playing it first. A couple characters make a return, but the game doesn't really expect that you know who they are already, and the main plot is entirely standalone. As for difficulty, Mother 2 is probably harder than Mother 3 (or at least less well-balanced), so you won't have any issues in that respect.
As for Mother 1: I personally enjoyed it, but it may be a hard sell to many people. It's an NES RPG through and through (a near direct clone of Dragon Quest, down to the interface), and can be clunkily designed at times. It's not as grindy as people say it is - as long as you don't run away from most encounters and level up new party members a decent amount after they join you, you'll be fine - but the encounter rate is rough, and the last area wasn't playtested so can be difficult to get through without just fleeing from every encounter, which luckily you can do unconditionally through a couple methods. While Mother 1 is still technically canon to the series, the other games work just fine without playing it. Mother 2 is more or less a reimagining of its concept and structure, just more polished. If you do want to play the original, check out the MOTHER Restored hack, which uses a translation by the same person who spearheaded the Mother 3 translation project that reads more naturally than the official English script (and includes an optional "Easy Ring" to reduce the game's difficulty somewhat, though if you're going through the trouble of playing this game in the first place I'd recommend that you at least give it a try without the ring first).
-
Comment on CGA-2026-11 🌍️🪀 INSERT CARTRIDGE 🟢 Mother 3 in ~games
lily LinkThis is one of my favorite games of all time! I hope everyone playing it this month enjoys it. The pacing is a little strange sometimes, almost certainly due to its fractured development, but the...This is one of my favorite games of all time! I hope everyone playing it this month enjoys it. The pacing is a little strange sometimes, almost certainly due to its fractured development, but the story is fantastic nonetheless. Earthbound is great (and you should definitely play it too), but Mother 3 has a stronger emotional core, I think.
For people starting the game now, I'd like to share a link to this romhack that improves the mixing of the game's music. Mother 3 has a lovely soundtrack, but being on the GBA hurts its sound quality somewhat, and this helps with that. It works with fan translations as well as the original Japanese game.
-
Comment on Anyone playing Subnautica 2? in ~games
lily (edited )LinkI just finished the current story content yesterday (in about 12 hours, though I was playing with someone else, which probably helped), and really enjoyed it! It's quite linear right now, which I...I just finished the current story content yesterday (in about 12 hours, though I was playing with someone else, which probably helped), and really enjoyed it! It's quite linear right now, which I don't love, but that's probably just a result of it still being under development - I assume the map will open up more as early access progresses. It's a lot closer to the original than Below Zero, which I'm happy about. It has a more engaging story than the first game, but delivers it in a way that feels natural. I'm interested to see where it goes.
More game design critique (including gameplay spoilers for late-game).
The progression does feel perhaps a little strange right now. After leaving the first region to cross over to the second, there's little reason to ever go back beyond getting the last few biomods (and most of the biomods are of little use, I've found - especially the additional active ones, since the dash is so useful I don't want to get rid of it). All major resources can be found in the second region, albeit occasionally in lesser quantities, like with fibrous pulp. Crossing the trench between the two regions is a bit of a gamble, due to the Collector Leviathan, so since everything needed for late-game crafting is present in the second region I never saw much of a reason to take that risk. This is also something that feels like an artifact of the game being in early access, though, so it's possible it'll change as the map expands.
The game is also maybe a little too easy. There are plenty of predators, but the vast majority of them don't do enough damage to be more than a nuisance. Even the new Leviathan can only be found in a couple places, the second of which you only need to visit briefly (the metal farms, or whatever that biome is called; you only need to go there to get troilite, and enough of it to repair the power plant can be found on the edge of the biome, outside of the Collector's range). For the most part, I felt completely safe while exploring, which I hope changes later in the game. Funnily enough, the biggest dangers in the game are probably hunger and thirst, which increase quite quickly and can be a chore to take care of. I would appreciate something like the water filtration machine from the first game, so I don't have to keep manually collecting water slugs / fibrous pulp all the time.
-
Comment on The woes of writing markdown in ~tech
lily LinkI've been working on my own lightweight markup language recently so this is relevant for me. There are some interesting suggestions here, but some of them (like e.g. <details> support) come in...I've been working on my own lightweight markup language recently so this is relevant for me. There are some interesting suggestions here, but some of them (like e.g.
<details>support) come in conflict with my desire to not be completely tied to HTML. There's a bit of a weird balance there, where in reality I'm only ever rendering as HTML and don't have any use for other output formats, but at the same time don't feel right effectively making my language an HTML shorthand in the way Markdown is. I've found it tough to settle on a good set of semantics because of that. -
Comment on Game testers wanted for science fiction game in ~games
lily LinkI'd be happy to give it a try, if you don't already have too many volunteers!I'd be happy to give it a try, if you don't already have too many volunteers!
-
Comment on Anyone else using the Zed editor? in ~comp
lily Link ParentI had this issue too, actually! It was one of the reasons I gave up trying Zed and moved back to Sublime Text. I was trying to figure out how to disable the go to definition feature entirely, but...I had this issue too, actually! It was one of the reasons I gave up trying Zed and moved back to Sublime Text. I was trying to figure out how to disable the go to definition feature entirely, but couldn't figure it out. I don't think it was Ctrl+D for me (it was some other Ctrl binding, I don't remember), but what I remember matches up with your description here. My laptop's touchpad also has poor palm rejection, and is offset to the left, which I think exacerbated the issue.
-
Comment on Tildes Game Giveaway: Holiday 2025 in ~games
lily Link ParentThanks for doing this! This is a fun way to go about it. I think I've figured out what some of these are, but I'm not 100% confident. These ones sound interesting to me, though (in no particular...Thanks for doing this! This is a fun way to go about it. I think I've figured out what some of these are, but I'm not 100% confident. These ones sound interesting to me, though (in no particular order):
3. Girl, Homunculus, City 8. Grids, Paintings, Cave 14. Plans, Keys, Letters -
Comment on Day 13: Purge the Heretic in ~comp.advent_of_code
lily LinkThis almost made me go and double-check there wasn't a surprise thirteenth puzzle released, until I realized the scheduled topic is probably just still set up for the old 25-day events. Phew! I'm...This almost made me go and double-check there wasn't a surprise thirteenth puzzle released, until I realized the scheduled topic is probably just still set up for the old 25-day events. Phew! I'm happy to have the rest of my December evenings free.
-
Comment on Day 12: Christmas Tree Farm in ~comp.advent_of_code
lily Link ParentIt's on the word "help" in the part 2 description. There's hidden hover text somewhere in the text for every puzzle; it's highlighted once you get all the stars.It's on the word "help" in the part 2 description. There's hidden hover text somewhere in the text for every puzzle; it's highlighted once you get all the stars.
-
Comment on Day 12: Christmas Tree Farm in ~comp.advent_of_code
lily LinkThis was a strange one to end things off with. The problem, as described, seems incredibly hard - I'm not sure at all how I'd go about solving it. I spent about half an hour at first hopelessly...This was a strange one to end things off with. The problem, as described, seems incredibly hard - I'm not sure at all how I'd go about solving it. I spent about half an hour at first hopelessly writing a naive bruteforce search solution that had no chance of ever finishing, just to try and make some kind of progress. It turns out the input is really, really convenient, though, and so just checking for the absolute upper bound on required space is enough (not for the test input, only the real input). This seems to be intentional, given the puzzle's hover text. When taking advantage of that, the puzzle becomes trivial; the shape definitions can actually be completely ignored, even. Almost all of my code is just for parsing the input.
Solution (Lily)
print("Part 1: {}".format( File .read_to_string("inputs/day_12.txt") .split("\n\n")[-1] .split("\n") .count(|line| var parts = line.split(": ") var size = parts[0].split("x") parts[1].split(" ").map(|count| count.parse_i().unwrap() * 9).fold( 0, (|sum, count| sum + count) ) <= size[0].parse_i().unwrap() * size[1].parse_i().unwrap() ) )) -
Comment on Day 11: Reactor in ~comp.advent_of_code
lily LinkGlad to have an easy day for a change! Just a simple recursive search with memoization (not needed for part 1, but it doesn't hurt). Part 2 is just the product of the solutions to three separate...Glad to have an easy day for a change! Just a simple recursive search with memoization (not needed for part 1, but it doesn't hurt). Part 2 is just the product of the solutions to three separate problems solved the same way as part 1. Would've finished this quicker if I hadn't wasted time writing a naive BFS solution that would take forever to finish executing, but oh well. I wonder what tomorrow will be like... this feels a bit like the calm before the storm.
Solution (Lily)
var outputs: Hash[String, List[String]] = [] for line in File.read_to_string("inputs/day_11.txt").split("\n"): { var parts = line.split(": ") outputs[parts[0]] = parts[1].split(" ") } var cached_paths: Hash[String, Integer] = [] define paths_between(start: String, end: String): Integer { if start == end: { return 1 } if start == "out": { return 0 } var key = start ++ end with cached_paths.get(key) as Some(paths): { return paths } var paths = 0 for output in outputs[start]: { paths += paths_between(output, end) } cached_paths[key] = paths return paths } print("Part 1: {}".format(paths_between("you", "out"))) var queue = ["svr"] var first_midpoint = "" do: { var device = queue.shift() if device == "dac": { first_midpoint = "dac" break } if device == "fft": { first_midpoint = "fft" break } for output in outputs[device]: { queue.push(output) } } while queue var second_midpoint = (first_midpoint == "dac" ? "fft" : "dac") print("Part 2: {}".format( paths_between("svr", first_midpoint) * paths_between(first_midpoint, second_midpoint) * paths_between(second_midpoint, "out") )) -
Comment on Day 10: Factory in ~comp.advent_of_code
lily (edited )LinkWhew, this was difficult... I came very close to throwing in the towel. I'm not the strongest with linear algebra, so part 2 took me a lot of Googling and looking at examples of Gaussian...Whew, this was difficult... I came very close to throwing in the towel. I'm not the strongest with linear algebra, so part 2 took me a lot of Googling and looking at examples of Gaussian elimination. I eventually arrived at a solution that runs in just over a second on my machine, which I'm happy enough with. I ended up with a lot more code than I'd prefer for an Advent of Code problem, but maybe that's just the way it has to be with today's puzzle.
Explanation
For part 1, I just iterated through all the combinations until I found one that worked. I wanted to iterate through them in ascending order of number of presses, so that I didn't have to go through every combination - after some searching I discovered Gosper's hack, which let me do it with bitsets (which is maybe more efficient than the alternative?).
Part 2 was rough. I realized I needed to solve linear systems almost immediately, but actually doing so was another story. I've never really done linear algebra before, so it took me a while (and a couple hints taken from other people's descriptions of their solutions) to figure out how to even start. What I ended up doing is:
- Build an augmented matrix for the given machine.
- Use Gaussian elimination over the matrix, and find the pivot and free columns.
- Iterate through all possible combinations of values for the free variables and back-substitute. Find the solution with the smallest sum of its coefficients, and add that sum to the total. This is the inefficient part - I know each button can't be pressed any more times than the counter it affects with the lowest required joltage, but those bounds seem to be a little too large, which slows things down a lot. I couldn't figure out how to get the bounds any lower, though.
Solution (Lily)
var total_presses_for_lights = 0 var total_presses_for_joltages = 0 for line in File.read_to_string("inputs/day_10.txt").split("\n"): { var parts = line.split(" ") var lights: List[Boolean] = [] parts.shift().slice(1, -1).to_bytestring().each_byte(|char| lights.push(char == '#') ) var max_light_index = lights.size() - 1 var required_state = 0 for i, light in lights.reverse(): { if light: { required_state |= 1 << i } } var buttons: List[List[Integer]] = [] var required_joltages: List[Integer] = [] for part in parts: { var numbers = part .slice(1, -1) .split(",") .map(String.parse_i) .map(Option.unwrap) if part[0] == '(': { buttons.push(numbers) else: required_joltages = numbers break } } var button_count = buttons.size() var max_button_index = button_count - 1 var button_mask_limit = 1 << button_count var reached_required_state = false for presses in 1...button_count: { var button_mask = (1 << presses) - 1 while button_mask < button_mask_limit: { var state = 0 for button_index in 0...max_button_index: { if button_mask & 1 << max_button_index - button_index: { for light_index in buttons[button_index]: { state ^= 1 << max_light_index - light_index } } } if state == required_state: { total_presses_for_lights += presses reached_required_state = true break } # Use Gosper's hack to find the next combination of presses with the # same length. This lets us avoid having to iterate through every # combination to find the shortest working one. var c = button_mask & -button_mask var r = button_mask + c button_mask = (((r ^ button_mask) >> 2) / c) | r } if reached_required_state: { break } } var rows = required_joltages.map_with_index(|joltage, index| buttons.map(|button| button.any(|i| i == index).to_i()).push(joltage) ) var width = buttons.size() var height = rows.size() var row = 0 var pivot_columns: List[Integer] = [] var free_columns: List[Integer] = [] for column in 0...width - 1: { var pivot = -1 for i in row...height - 1: { if rows[i][column]: { pivot = i break } } if pivot == -1: { free_columns.push(column) continue } var temp = rows[row] rows[row] = rows[pivot] rows[pivot] = temp for i in row + 1...height - 1: { var value = rows[i][column] if value: { var pivot_value = rows[row][column] for j in 0...width: { rows[i][j] = rows[i][j] * pivot_value - rows[row][j] * value } } } row += 1 pivot_columns.push(column) } var free_variable_combinations = 1 var free_variable_bounds = free_columns.map(|column| # Buttons can't possibly be pushed more times than the counter they # affect with the lowest required joltage. var bound = buttons[column].fold(-1, (|minimum, index| var joltage = required_joltages[index] if joltage < minimum || minimum == -1: { return joltage } minimum )) + 1 free_variable_combinations *= bound bound ) var free_column_count = free_columns.size() var free_variables = List.repeat(free_column_count, 0) var minimum_presses = -1 for _ in 1...free_variable_combinations: { var solution = List.repeat(width, 0) for i, column in free_columns: { solution[column] = free_variables[i] } var valid = true for i in pivot_columns.size() - 1...0 by -1: { var column = pivot_columns[i] var joltage = rows[i][width] for j in column + 1...width - 1: { joltage -= rows[i][j] * solution[j] } var value = rows[i][column] if joltage % value: { valid = false break } joltage /= value if joltage < 0: { valid = false break } solution[column] = joltage } for i in 0...free_column_count - 1: { free_variables[i] += 1 if free_variables[i] == free_variable_bounds[i]: { free_variables[i] = 0 else: break } } if !valid: { continue } var presses = solution.fold(0, (|sum, value| sum + value)) if presses < minimum_presses || minimum_presses == -1: { minimum_presses = presses } } total_presses_for_joltages += minimum_presses } print("Part 1: {}\nPart 2: {}".format( total_presses_for_lights, total_presses_for_joltages )) -
Comment on Day 9: Movie Theater in ~comp.advent_of_code
lily (edited )Link ParentThe general case of this would be pretty tricky, yeah (and probably pretty slow, at least with a naive approach... my solution isn't the fastest as it is). I can confirm my program returns the...The general case of this would be pretty tricky, yeah (and probably pretty slow, at least with a naive approach... my solution isn't the fastest as it is). I can confirm my program returns the wrong answer for your test input here. Since these kinds of quirks in the input are generally consistent across all inputs for a given puzzle, though, I don't mind taking advantage of them where possible.
-
Comment on Day 9: Movie Theater in ~comp.advent_of_code
lily LinkNot especially happy with my solution, but it does the job. Runs in just under a second on my machine. The idea is, if none of the lines between red tiles are overlapping with the rectangle, then...Not especially happy with my solution, but it does the job. Runs in just under a second on my machine. The idea is, if none of the lines between red tiles are overlapping with the rectangle, then the rectangle is inside the loop. The time complexity of this seems pretty bad, but I unfortunately don't have the time to figure out how to make it more efficient right now.
Solution (Lily)
import (abs) math var tiles_x: List[Integer] = [] var tiles_y: List[Integer] = [] var tile_count = 0 for line in File.read_to_string("inputs/day_09.txt").split("\n"): { var parts = line.split(",") tiles_x.push(parts[0].parse_i().unwrap()) tiles_y.push(parts[1].parse_i().unwrap()) tile_count += 1 } var lines = List.fill(tile_count, (|index| var next_index = index + 1 if next_index == tile_count: { next_index = 0 } var x1 = tiles_x[index] var y1 = tiles_y[index] var x2 = tiles_x[next_index] var y2 = tiles_y[next_index] if x1 == x2: { return (y1 <= y2 ? <[x1, x2, y1, y2]> : <[x1, x2, y2, y1]>) } (x1 < x2 ? <[x1, x2, y1, y2]> : <[x2, x1, y1, y2]>) )) var max_index = tile_count - 1 var largest_area_all_tiles = 0 var largest_area_red_and_green_tiles = 0 for i in 0...max_index: { for j in i + 1...max_index: { var x1 = tiles_x[i] var y1 = tiles_y[i] var x2 = tiles_x[j] var y2 = tiles_y[j] var area = (abs(x2 - x1) + 1) * (abs(y2 - y1) + 1) if area > largest_area_all_tiles: { largest_area_all_tiles = area } if area <= largest_area_red_and_green_tiles: { continue } if x1 > x2: { var temp = x1 x1 = x2 x2 = temp } if y1 > y2: { var temp = y1 y1 = y2 y2 = temp } var inside = true for line in lines: { if line[1] > x1 && line[0] < x2 && line[3] > y1 && line[2] < y2: { inside = false break } } if inside: { largest_area_red_and_green_tiles = area } } } print("Part 1: {}\nPart 2: {}".format( largest_area_all_tiles, largest_area_red_and_green_tiles )) -
Comment on Day 8: Playground in ~comp.advent_of_code
lily (edited )LinkReally enjoyed this one! Definitely the hardest so far this year. I finished an initial solution in just under an hour, but took some extra time to clean it up and improve performance (switching...Really enjoyed this one! Definitely the hardest so far this year. I finished an initial solution in just under an hour, but took some extra time to clean it up and improve performance (switching to a minheap rather than pushing to a list and running quicksort afterward sped things up quite a bit). I think it could still stand to be a little more efficient, but it is what it is; the vast majority of the time is spent calculating and inserting the distances into the minheap, and I'm not sure how to optimize that code any further.
I'm glad I thought ahead and wrote a binary heap in advance. Most recent years have had at least one pathfinding puzzle, which is what I intended it for, but it came in handy for today!
Solution (Lily)
import (Heap) "modules/heap" var boxes = File.read_to_string("inputs/day_08.txt").split("\n").map(|line| line.split(",").map(String.parse_i).map(Option.unwrap) ) var max_index = boxes.size() - 1 var tuples: Heap[Tuple[Integer, Integer, Integer]] = Heap(|a, b| a[2] < b[2]) for i in 0...max_index: { for j in i + 1...max_index: { var box_1 = boxes[i] var box_2 = boxes[j] var delta_x = box_2[0] - box_1[0] var delta_y = box_2[1] - box_1[1] var delta_z = box_2[2] - box_1[2] tuples.push(<[i, j, ( delta_x * delta_x + delta_y * delta_y + delta_z * delta_z )]>) } } var circuits = List.fill(boxes.size(), (|index| [index => unit])) var circuit_indices: Hash[Integer, Integer] = [] for i in 0...max_index: { circuit_indices[i] = i } var connections = 0 while tuples.size(): { var tuple = tuples.pop() var index_1 = circuit_indices[tuple[0]] var index_2 = circuit_indices[tuple[1]] if index_1 != index_2: { var circuit = circuits[index_1] circuits[index_2].keys().each(|key| circuit[key] = unit circuit_indices[key] = index_1 ) circuits.delete_at(index_2) circuit_indices.each_pair(|key, index| if index > index_2: { circuit_indices[key] -= 1 } ) } connections += 1 if connections == 1000: { print("Part 1: {}".format(circuits .sort(|a, b| b.size() - a.size()) .slice(0, 3) .fold(1, (|product, circuit| product * circuit.size())) )) } if circuits.size() == 1: { print("Part 2: {}".format(boxes[tuple[0]][0] * boxes[tuple[1]][0])) break } }Heap implementation
# order_test should return whether the first value should be given higher # priority than the second value. It determines whether the heap is a min or max # heap. Operators aren't functions in Lily, and we can't fully qualify generic # types, so unfortunately the function has to be defined manually even for # primitive types supporting the < and > operators. class Heap[A](private var @order_test: Function(A, A => Boolean)) { private var @values: List[A] = [] public define size: Integer { return @values.size() } public define push(value: A): self { @values.push(value) var current_index = @values.size() - 1 while current_index > 0: { var parent_index = (current_index - 1) / 2 var current_value = @values[current_index] var parent_value = @values[parent_index] if @order_test(parent_value, current_value): { break } @values[current_index] = parent_value @values[parent_index] = current_value current_index = parent_index } return self } public define pop: A { var size = @values.size() if size == 0: { raise RuntimeError("Cannot pop from an empty heap.") } if size == 1: { return @values.pop() } var root_value = @values[0] @values[0] = @values.pop() size -= 1 var current_index = 0 do: { var left_index = current_index * 2 + 1 var right_index = left_index + 1 var has_left = left_index < size var has_right = right_index < size if !has_left && !has_right: { break } var child_index = (has_left ? (has_right ? ( @order_test(@values[left_index], @values[right_index]) ? left_index : right_index ) : left_index) : right_index) var current_value = @values[current_index] var child_value = @values[child_index] if @order_test(current_value, child_value): { break } @values[current_index] = child_value @values[child_index] = current_value current_index = child_index } while true return root_value } } -
Comment on Day 7: Laboratories in ~comp.advent_of_code
lily Link ParentI didn't think about that case, actually! A splitter on the left edge wouldn't crash my program, since Lily supports Python-style negative indexing on lists, but the answer would be wrong. Another...I didn't think about that case, actually! A splitter on the left edge wouldn't crash my program, since Lily supports Python-style negative indexing on lists, but the answer would be wrong.
Another convenience of the input I noticed is that the starting location is always in the exact center of the first row. So, I could simplify that line by just dividing the size by 2 instead of manually searching for the first
Scharacter. -
Comment on Day 7: Laboratories in ~comp.advent_of_code
lily LinkSurprisingly easy problem - I'd thought we would be getting to the more involved ones by now. I liked the part 2 twist, though. The trick here is one that's been used in prior puzzles, so I...Surprisingly easy problem - I'd thought we would be getting to the more involved ones by now. I liked the part 2 twist, though. The trick here is one that's been used in prior puzzles, so I figured it out pretty quickly, but I did have to scrap all my original part 1 code.
Solution (Lily)
var lines = File.read_to_string("inputs/day_07.txt").split("\n") var beam_counts = List.repeat(lines[0].size(), 0) beam_counts[lines[0].find("S").unwrap()] = 1 var splits = 0 for line in lines: { for i, char in line: { if char != '^': { continue } var count = beam_counts[i] if count > 0: { beam_counts[i - 1] += count beam_counts[i + 1] += count beam_counts[i] = 0 splits += 1 } } } print("Part 1: {}\nPart 2: {}".format( splits, beam_counts.fold(0, (|sum, count| sum + count)) )) -
Comment on Day 6: Trash Compactor in ~comp.advent_of_code
lily Link ParentI had to copy the input into Notepad++ instead of Sublime Text, since my Sublime config automatically trims trailing whitespace. It pays to have more than one editor around, I suppose. (I think if...I had to copy the input into Notepad++ instead of Sublime Text, since my Sublime config automatically trims trailing whitespace. It pays to have more than one editor around, I suppose. (I think if you put a cursor at the end of every line Sublime won't do it, though - maybe that's also the case in VS Code?)
-
Comment on Day 6: Trash Compactor in ~comp.advent_of_code
lily Link ParentPossibly that would be cleaner, yeah, though maybe less efficient. I did update my code today to parse from right-to-left rather than left-to-right, which simplified things a little bit.Possibly that would be cleaner, yeah, though maybe less efficient. I did update my code today to parse from right-to-left rather than left-to-right, which simplified things a little bit.
The released Earthbound Beginnings should be the same ROM as the original English port, from what I recall, unless they made further censorship changes I'm unaware of. But I'm pretty sure either should work. Hope you enjoy the game!