gbadev
Game Boy Advance homebrew development forum
Member
Joined:
Posts: 10

Participating in the gamejam and one of my inspirations (the rainworld programmer/designer Joar Jakobsson) really encouraged making a devlog, and I remember reading that that's encouraged here. The project is super secret, but you can get sneak peeks here. Going to try my hardest to post every day.

Today I started on a rust Morse code data structure. I think you can store all the info for a Morse code sequence in just two bits, which I'm pretty proud of!

Here's my idea:

  • Two bits (e.g. 00) for a dot (one unit of time in duration plus a one unit silence)
  • Two bits (e.g 01 for a dash (three units of duration plus a 1U silence)
  • two bits (10) for a letter terminator (two extra units of silence
  • two bits (11) for a word terminator (6 extra units of silence instead of the letter terminator)

Cya tomorrow!

Member
Joined:
Posts: 10

I just realize it's a different channel that says to start a devlog in... I'll deal with that later.)

Administrator
avatar
Joined:
Posts: 53

It's ok to keep your devlog here, or I can move it for you if you want (the only difference is whether it shows up in the News section of the Portal page)

Now I'm curious if the morse code is part of a secret puzzle or like a core gameplay mechanic. (you don't have to answer!) :P

Good luck and looking forward to future updates!

Member
Joined:
Posts: 10

@exelotl Thanks! I'm fine leaving it here.

I don't mind telling that the morse code is going to be a core gameplay element. I think it's cool and have always wanted to learn it and how better than a mysterious video game? I was reading about methods for learning it and am pretty interested in the Koch method. It seems to really lend itself to gamification and might have caught on more in its day if the gameboy (or even a pc) was a thing. I'm currently figuring out how to cram the beeps and boops into the GBA and had a lot of fun pair programming with my wife Kayla who's a rust wizard and general cutie.

Dunno if I mentioned that my toolchain for the game is agbrs. I like rust a lot more than c and agbrs has lots of good tutorials I could look at if I wanted (being honest here- I'm in full goblin mode and really wish I had the patience for tutorials- it would make my life a lot easier).

Kayla helped me figure out functions for packing and unpacking the 2-bit morse segments I came up with yesterday into 8 bit chunks- since Rust doesn't have a 2-bit primitive and I was floundering. I'm glad I had help since my first solutions were much more janky (Kayla's been programming at least 10 years longer than me and lives and breathes computers so I'm really lucky)

I then wanted to be a good girl and write some tests. I was all set to run cargo test (and almost cried when I learned Rust supports tests inside your modules, and doesn't make you write a separate file for them), but the computer was nice enough to remind me I have no standard library for the GBA, and commenting out ![no_std] whenever I wanted to run tests felt like it would incur the wrath of an outer god or two. Thankfully agbrs has its own pretty slick test setup using mgba. The only problem is the tool I needed to install needed a static mgba library- something the installations available on Fedora linux don't provide as far as I'm aware (I could only find a flatpak and an appimage). So I got to compile mgba from source. After a couple hiccups it was installed and I get to find out tomorrow if I actually get to run tests.

I'm pretty new to all this, and wish I could make progress faster, but I'm having fun, and hopefully this is at least entertaining to read! I'm not sure what detail is helpful and what is extraneous, so I'd be happy to field questions or take constructive criticism.

Till next time! Good luck with your stuff!

  • Jayda
Member
Joined:
Posts: 10

Well, It's been several more hours and I only just got the beautiful, beautiful lights on my computer screen, spelling "Tests Finished Successfully". Why did it take so long? I'm just going to copy-paste the issue I filed to agbrs's github, titled mgba-test-runner compilation fails on Fedora Linux. That's right! I spent over an hour to compile mgba from source myself last night for nothing! I hadn't realized that the mgba-test-runner build was also compiling mgba from scratch, and also that the build I did last night wasn't making the static library the test runner wants. So what was broken? let's read the github issue to learn more:

mgba-test-runner compilation fails on Fedora Linux

I was getting the error: "could not find native static library mgba, perhaps an -L flag is missing?"
I could verify that the libmgba.a file was indeed being made but still couldn't be found. Passing an -L flag didn't seem possible because cargo builds to a different folder every time. I guess I could have copied the file to somewhere else and told the compiler to look there via an -L flag,, but that wouldn't help other people with the same problem and is pretty bodgy.

If you hadn't already guessed, I already figured out the problem (after much suffering and some tears):

The compile() function in agb/emulator/mgba-sys/build.rs doesn't check for libmgba.a in
/tmp/cargo-installATjYGr/release/build/mgba-sys/b1badf9e16f5cee4/out/lib64/
, which is where CMake wants to put it when running on Fedora.

The fix which worked for me was just to add the following line to the compile function:
println!("cargo:rustc-link-search=native={}/lib64", dst.display());
so it'll look in the right place.
(I just put the new line after println!("cargo:rustc-link-search=native={}/lib", dst.display());)

I could then compile by running [cargo install --path emulator/test-runner]

I'd be happy to put up a PR! I wonder if I should be trying to file a fix for mgba-sys instead or in addition though.

Pretty proud of myself for actually figuring this out without needing to ask anyone for help! We have guests coming over in 7 minutes so I guess I'll have to wait to actually continue my game code. big sigh.

Cheers!

  • Jayda
Member
Joined:
Posts: 10

Heya! So I have a static hashmap for storing what characters have which beepboops. It only took this freaking mess:

use core::cell::LazyCell;
use core::ops::Deref;
use agb::hash_map::HashMap;
use alloc::vec::Vec;

static MorseBindings: SuperLazyCell<HashMap<char, Vec<MorseSegment>>> = SuperLazyCell::new(|| {
    let dot = MorseSegment::Dot;
    let dash = MorseSegment::Dash;
    let fin = MorseSegment::LetterEnd;
    let mut map = HashMap::new();
    map.insert('a', alloc::vec![dot, dash, fin]);
    [other characters inserted here]
    map
});

//This super-cursed abomination of sins is just so the compiler will stop yelling about LazyCell not being threadsafe... on the single threaded GBA. Yip-E. I really wish core had LazyLock >.<
struct SuperLazyCell<T, F = fn() -> T>(LazyCell<T, F>);
unsafe impl<T, F: FnOnce() -> T> Sync for SuperLazyCell<T, F> {}
impl<T, F: FnOnce() -> T> SuperLazyCell<T, F> {
    pub const fn new(f: F) -> SuperLazyCell<T, F> {
        SuperLazyCell(LazyCell::new(f))
    }
}
impl<T, F: FnOnce() -> T> Deref for SuperLazyCell<T, F> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &*self.0
    }
}

I did not do this by myself. My wife had to carefully walk me through the whole process, which took hours. For a hashmap. Thank you, Rust, and also what the actual #$@&*@#($$@#($%&@#.

My wife just said "Baby's first deref impl!" and I'm so sorry I know exactly what that is now. (I mean, I was going to have to learn what it is eventually so I'm glad I had a hand to hold.)

"Baby's first unsafe trait, too!"

This isn't what I thought I was going to be writing a log about, lol. I did a lot of design today on a fast typing layout for the GBA which I think is really cool- you can access every character in the english alphabet and all the numbers and a bunch of punctuation in less than 5 key presses each, and I made it so more frequently picked characters are closer to the center. Here, I'll just post a picture.

Member
Joined:
Posts: 10

I... I get to learn what a CDN is so I can paste an image here :'D

Edit: It wasn't that hard- just needed to sign in to digital ocean and make a bucket.
Here's the stuff

The idea is that this will open up when text input is needed, with a cursor starting at the center. I tried to make the tree as alphabetical as I could but sacrificed a bit of symmetry in the name of (hopeful) ergonomics. Again, I tried to make more common letters closer to the center. It might be terrible. I got the idea from Dreamshell's text input system which did something similar, but more compact and using the Dreamcast's two joysticks and a trigger to select a character. It felt and looked really cool but was pretty hard to get used to (I said it felt cool, not necessarily good). Since I'm going to ease people into the Morse code one or two characters at a time my idea is I can also ease people into this system just as slowly.

I don't like most text input systems for the GBA and am hoping this will help make the process less frustrating long-term, since my concept for this game will require lots of picking out letters. I also just think it looks cool. I was initially going for a slightly more fractal design, as you can see in my second iteration, which isn't completely off the table, but I want to prioritize function over fashion since I want the game to be fun first and foremost, and the final design with the character mappings just fit so well.

Member
Joined:
Posts: 10

Took a break for a couple days for mental health, but I'm back in the saddle now! Been digging into how the deref implementation from last week works, and made a fun discovery about the * operator in Rust.
&*self.0
Specifically I learned how much this is doing all at once. I'd been thinking of * and & as opposites, like multiply and divide, which cancel each other out. I thought & means "make a pointer to the thing" and * means "this thing I have is a pointer, go look at what it points at and go get it" which I don't think is necessarily false, but, in Rust at least, it seems to be more complicated than that.
The * operator is apparently exactly the same as saying self.deref(), which is to say "get the thing out of self", and * is going to be a different function based on what type self is. So &*self.0 isn't the same as self.0.

I also learned that a Trait/interface/typeclass is just a way of saying "if it has this trait, it will implement the corresponding function"

Member
Joined:
Posts: 10

Was having a bad time with writing tests until my mentor looked at my code and realized I wasn't deriving Eq or PartialEq on my MorseSegment type, so I wasn't able to use assert_eq! on them, and I was misunderstanding what assert_matches! does and why it was working for just a single morse segment but not a vector of them. I'm glad I struggled with it so the lesson will probably stick and I learned more than if I'd just gotten the answer straightaway, even though it means I've still only written one meaningful test, lol.

About to go to a programming meetup with my wife where I plan to write a bunch more tests (but will likely spend a bunch (most) of the time talking to people, as The Lady intended.) :3

Member
Joined:
Posts: 10

I actually got way more done last night before I let myself fall into conversation! Steadily working my way through implementing tests for all the functions I've completed in my morse code module so far. They even helped me pinpoint a few bugs which would have been much harder to find without them. I also did a minor refactor this afternoon without getting overwhelmed, which feels like a big deal to me- that's been something that historically contributed to projects spaghettifying- I'd try to move a function to its own little home and couldn't figure out how to make it work syntactically, so I gave up. I'm glad I've been getting the hang of Rust's syntax and how to find answers in the docs and elsewhere. The compiler is usually pretty helpful, as long as you know that it has a hard time pinpointing simple stuff like semicolons and unmatched curly braces.

I also learned that when using Helix you can push space + ? to get a searchable list of all key commands. I can't stress how huge this is for me. I've been having a lot of fun but my working memory isn't amazing so I find myself looking up commands very often, or even taking long-cuts so I wouldn't have to break my flow to look up a command by manually thumbing through my notebook I made when doing the :tutor Helix provides (for the third time). There's so many things that are so productive and nice-feeling if you can just remember how to do them. It's funny how even being pretty mediocre at a modal editor is still enough to get some street cred from other developers. Pretty sure it's like playing Celeste or a Sonic game or Kitten Burst (my favorite game of all time, which nobody's heard of) - it feels pretty good to be bad at but (probably) feels amazing to be really good at.

I'd definitely recommend checking out Kitten burst though. I don't know the creator personally but he's always really kind in his forums and put SO much thought into the game. He did the visuals, writing, and music himself and they're all stunning. I still think about it all the time and it's not that expensive. Worth every penny IMO.
Kitten Burst Steam Page

Anyway, I'm pretty much a test convert by now. I just learned proptests are a thing and I want to check them out to see if they'd be worth setting up, but I'm a huge proponent of "something is better than nothing," and "most things worth doing are worth doing poorly." Some tests are way better than no tests.

Thanks for stopping in! I don't know if anyone reads this but I like pretending there's at least one person who's enjoying my rambling, and has learned at least something from my floundering :3

link to the github repo for all interested:

Cheers!
Jayda