Not too long ago, I made this asteroids game I developed in my spare time on my 2007 macbook running Lubuntu and now I want to put it on a website I own. I've been trying for the last several days to compile this so that it'll run on other computers with no luck. I can't get a static build working, and I am having trouble sharing the shared libraries. I'm new to C in general and I haven't ever distributed anything like this before, so this is my first project that I actually want other people to be able to play, but I don't know how to compile it for everyone else's computer, only my own. Here's a couple pointers so you know what I mean:
I want it to be simple to download and run. I don't want to have to explain to my non tech savvy friends and family how to compile a C program.
I want to have a version for Linux, Windows, and Mac. I don't have access to a Windows machine, so I hope to be able to cross compile for Windows from Linux, but I do have access to a Mac.
I don't care too much about how this gets done. I would be happy with a statically compiled version that has just the executable and game assets. I would be happy with a version that was compiled with shared libraries but those shared libraries are in a folder distributed with the program. As long as it works on most people's computers, I'm happy.
I've been programming since I was around 6 or 7 years old, and I'm 16 now. I have always wanted to create a game most people can enjoy at least a little, and can download and play or play off of a website. Now I've finally got the first part done, as I consider my Asteroids clone a game of some quality, even if it is just a clone, but now, even though I have a finished product, I can't get it working on anyone else's computer without demanding that people install Linux, install Allegro, and get familiar with GCC. Can someone please explain to me simply how to do this? I'm competent enough to get working builds on my machine, but I still don't always understand the compilation process as a whole.
For distributing on OSX, I use dynamic linking and manually create an OSX bundle. Then, I copy over all the necessary dylibs using https://github.com/auriamg/macdylibbundler (be mindful of the issue here https://github.com/auriamg/macdylibbundler/issues/22).
For cross-compiling, you can use the mingw64 cross-compiler, I know people have had success with it.
For Linux I am not very familiar with how to get that working... I typically just static link Allegro and call it a day, but you probably can do something like what is done in OSX and distribute the necessary shared libraries.
Now as for the details... I can only be of help with the OSX process since I do it regularly (for Windows I have access to a Windows OS, and compile things there). I'll try to write up a tutorial.
I'd be willing to make a static Windows build, if your code and resources are organized correctly so I can make sure it runs. If you're willing to share code that is. Don't know if you have your project on github or what.
Oh, you need to fix your link. You're missing a closing parenthesis on your URL in the <a> tag.
EDIT
I built it. Not terribly hard, but you have some odd conventions. When run it crashes when I press the space key to fire the laser and an assertion fails (spl) which means a sample was null, hence your sound didn't load. I had the exe in the same directory as the media files, in the root.
Also, the ship blinked on and off.
Hey Edgar, here's a bit more info about what you experienced with my game:
The ship is supposed to flash for around 5 seconds after you spawn, to signify temporary invincibility to asteroids, so you don't get spawn-killed by an asteroid that moves so fast to the spawn that you don't get a chance to move or shoot it. It's not supposed to flash for much longer than that, though.
I don't know why it crashes when you fire the laser. I downloaded my Github version and compiled it using my script, and it works fine. Make sure the following 3 sound files exist in the same directory as the executable:
laser.wav
thrust.wav
explosion.ogg
Did you test any of the other sound effects? To trigger thrust.wav, push the up arrow key to fly your ship forward. To trigger explosion.ogg, you either shoot an asteroid with a laser (which isn't possible if the game crashes when it can't find the laser sound effect), or fly your ship into an asteroid to blow it up after your invincibility period is over. Maybe we'll get more insight into this problem if we check whether the other sounds work.
As for the malformed link in my original post, I don't see an edit button for my post. Sorry.
SiegeLord, I have tried doing a static build before but it never works. I was able to build a static library of Allegro just fine, but whenever I try to use it and compile a static version, I get a billion errors and I don't know what a single one of them means.
Here's the command I use to do static compilations:
gcc -static src/main.c -lm -lallegro-static -lallegro_primitives-static -lallegro_main-static -lallegro_audio-static -lallegro_acodec-static -lallegro_font-static -lallegro_ttf-static -oStaticAsteroids
The error message is so big and long that I can't put it here. I put it on pastebin instead. Click here to see the hell error message at your own risk.. I'm surprised Pastebin even allows me to host a paste that huge.
I've tried doing a "Monolithic build" where I distribute a monolithic library with the program, but it never works. It builds fine, but whenever I run the program, it complains it can't find the library, even though it's still there, and I directed GCC to compile with the one in that directory, and link to the one in that directory.
I don't even think I'm going to compile for Windows just yet. Compiling a portable build for Linux has proven to be a hell nightmare for me, and it's supposed to be the easiest. I think I will get a working Linux build going and only then will I transition to Windows.
Thanks for all your help so far, guys. Hopefully I'll get a build working for everyone eventually.
blehmeh98,
Ive got a game I made out of Allegro4. Actually with Edgar's help and going through portable DevCpp, my Windows static link worked like a champ. I've been able to play it on Win7 and Win 10 to test.
Linux on the other hand is given me grief, but I think it's because I don't have the static versions of the dependent libraries like Xcursor and X11 (among 4-5 more).
I'm not sure how Allegro 5 works, but you might want to see if you have the static versions of non Allegro libraries you might need. Also, I wouldn't discount a static Windows version so quickly. It took me a week or so (with much help from Edgar) but that's proven a LOT easier and shorter than my Linux attempts to date.
Good luck
Ace
I think that to get shared libraries working on other Linux systems you need to distribute all dependencies, including the C runtime itself. That's because of compatibility issues between different versions of GCC, etc. If you try to run the binaries that you built on your system without providing everything then they'll likely get linking errors or crashes when they execute your game as a result of a runtime incompatibility.
ldd(1) should list the dynamic link dependencies for an executable or library. In theory, you could script up a program to recursively follow them all the way back, and copy the appropriate files into a folder to tarball up. For example, here's the output for my core Allegro 5 shared object:
linux-vdso.so.1 => (0x00007fffb2b87000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f177cca5000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f177ca88000) libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f177c74e000) libXcursor.so.1 => /usr/lib/x86_64-linux-gnu/libXcursor.so.1 (0x00007f177c544000) libXpm.so.4 => /usr/lib/x86_64-linux-gnu/libXpm.so.4 (0x00007f177c332000) libXi.so.6 => /usr/lib/x86_64-linux-gnu/libXi.so.6 (0x00007f177c122000) libXinerama.so.1 => /usr/lib/x86_64-linux-gnu/libXinerama.so.1 (0x00007f177bf1f000) libXrandr.so.2 => /usr/lib/x86_64-linux-gnu/libXrandr.so.2 (0x00007f177bd14000) libGL.so.1 => /usr/lib/x86_64-linux-gnu/mesa/libGL.so.1 (0x00007f177ba9f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f177b6d5000) /lib64/ld-linux-x86-64.so.2 (0x00007f177d2ae000) libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f177b4ad000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f177b2a9000) libXrender.so.1 => /usr/lib/x86_64-linux-gnu/libXrender.so.1 (0x00007f177b09f000) libXfixes.so.3 => /usr/lib/x86_64-linux-gnu/libXfixes.so.3 (0x00007f177ae99000) libXext.so.6 => /usr/lib/x86_64-linux-gnu/libXext.so.6 (0x00007f177ac87000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f177aa6d000) libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f177a844000) libxcb-dri3.so.0 => /usr/lib/x86_64-linux-gnu/libxcb-dri3.so.0 (0x00007f177a640000) libxcb-present.so.0 => /usr/lib/x86_64-linux-gnu/libxcb-present.so.0 (0x00007f177a43d000) libxcb-sync.so.1 => /usr/lib/x86_64-linux-gnu/libxcb-sync.so.1 (0x00007f177a236000) libxshmfence.so.1 => /usr/lib/x86_64-linux-gnu/libxshmfence.so.1 (0x00007f177a033000) libglapi.so.0 => /usr/lib/x86_64-linux-gnu/libglapi.so.0 (0x00007f1779e02000) libXdamage.so.1 => /usr/lib/x86_64-linux-gnu/libXdamage.so.1 (0x00007f1779bff000) libX11-xcb.so.1 => /usr/lib/x86_64-linux-gnu/libX11-xcb.so.1 (0x00007f17799fd000) libxcb-glx.so.0 => /usr/lib/x86_64-linux-gnu/libxcb-glx.so.0 (0x00007f17797e2000) libxcb-dri2.so.0 => /usr/lib/x86_64-linux-gnu/libxcb-dri2.so.0 (0x00007f17795dd000) libXxf86vm.so.1 => /usr/lib/x86_64-linux-gnu/libXxf86vm.so.1 (0x00007f17793d7000) libdrm.so.2 => /usr/lib/x86_64-linux-gnu/libdrm.so.2 (0x00007f17791c6000) libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007f1778fc2000) libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f1778dbc000)
You will likely need to write a shell script launcher that sets some environment variables so that when the user executes what they think is your game, it sets up an environment that loads libraries from your subdirectory, and then executes your game using the new environment.
The LD_LIBRARY_PATH variable should instruct the runtime loader to search that path for libraries. I'm not sure if that works for the C runtime itself, but I don't see why it wouldn't. I've just never attempted this before. For example:
It took me all of a minute to build your game statically on Windows once I figured out that you were #including C files. That's not how you're supposed to do it. You need proper headers with function declarations.
All I know is your program fails with an assertion 'spl' is NULL. It's inside allegro code. You're probably trying to play a NULL sample. laser.wav and thrust.wav are both in the same folder as the exe. thrust.wav plays fine, and the ship moves, even if it is not around a central point. Your rotation is off. However when the player hits an asteroid or fires the laser it fails with the same assertion and crashes.
Here's the command I use to do static compilations:
Well, you forgot a lot. There are all the static libraries you need to link with. That's what the error messages are. Libraries you didn't link to. On Linux you're supposed to use pkg-config if you don't know what you're doing.
EDIT
What are you doing? First you leak the memory created by al_fopen, then you pass it to load_sample_f and tell it the extension when its already specified?
explosionSound = al_load_sample("explosion.ogg"); shotSound = al_load_sample("laser.wav"); thrustSound = al_load_sample("thrust.wav");
After I changed it to this, it worked.
On Linux itself, I've found that (perhaps unfortunately) the most surefire way of doing this is to just build traditional packages - that is, RPM or DEB packages - for your game.
You'd build a dynamically linked binary per target Linux, add each to a package, specify a dependency for Allegro in those packages (eg. liballegro5.2 on Ubuntu), then distribute them.
A better (and newer) way might be to use Snaps, which apparently make building easy across Linuxes. Haven't tried these yet though. Might be a good idea to add a section to the Wiki a la "how to package and distribute your game".
On that note, learning to build software from source is a valuable skill even for regular users. Programming was never meant to be an exclusive club. Users were fully intended to participate too. When you get into a big company you'd be surprised by the different types of people that have some programming experience from yesteryear. That said, they probably don't want to learn all of the things they'd need to just to get your game running.
The good news is that Linux is pretty easy to build on. Especially distributions that already have Allegro packages (Debian, Ubuntu, Fedora, Arch I think, and maybe a couple extra). You can basically write a build script that detects the distribution by a few tricks that are easy to Google (or make it up yourself with some testing), and install the distro-specific packages that your program depends on (likely just Allegro 5, and maybe one more) using sudo. Then build your own program with a Makefile or some other build system of your choosing.
Contrived example:
You'd need to test it out on each platform, or have a competent friend test it for you, and work out the bugs, but once you do it should be a pretty reliable way of distributing your game: distribute the source of your game with a build script and tell friends to just execute the build script first. You may want to write output to a log file instead and have them share back the log file when things go wrong to work out the problem for them.
You could even wrap this up into a "game" script that looks like the actual game, but that detects if the game has been built yet, and if not displays a "first time setup..." message that attempts this build. There are no rules. It's up to you how friendly you want to make it, and how much you want to shield them from.
Also, it sounds like `lsb_release -i -s` may be a better way of detecting distros these days, but I'm unsure if it is installed by default on each supported distro so I left this basic script alone.
Edgar,
I use .h headers for just about every C file inside src/, and I can't see anywhere where I included a .c file directly. The only C file that does not have a corresponding header is main.c. I will admit that I probably don't do enough with the header files. Most of the time, I use them for the sole purpose of having a safe and easy way of making sure I only include a file once. I'm pretty new to C, coming from other languages, and this was my first full project.
As for the audio thing, it worked fine on my machine and many other linux machines before. I went ahead and implemented your suggested change and recompiled, and it still works fine on my machine, so I implemented it into the Github repo. I'm surprised I've never had a problem with it before; I did compile this program on a few different Linux installations, and I've never had a problem with the sound before. I was kind of working on a deadline with this code (I wrote it for an assignment in AP computer science at my highschool), and I think I got the information for those functions from the Allegro 5 documentation. I must have misinterpreted it. Thanks for catching that for me.
When you say the rotation is off, what exactly do you mean? Here's a couple pointers about how rotation should work inside of my game:
The ship is supposed to rotate around its but, not around the center.
The ship should fly straight where it's pointed and fire lasers straight where it's pointed.
As long as those two things are happening, I'm happy with it. I wrote the game so the ship would rotate around its but because you can use it to dodge asteroids in a slick and kind of funny way.
As for pkg-config, I haven't used it in a little while and I probably don't know the full extent of its power. I'll look for a good guide to it and try to get a static build going with it.
dthompson, snaps look really interesting. I think I will learn to use those, alongside learning to make regular packages, too.
Thanks everyone for all your support so far.
Edgar,
I use .h headers for just about every C file inside src/, and I can't see anywhere where I included a .c file directly. The only C file that does not have a corresponding header is main.c. I will admit that I probably don't do enough with the header files. Most of the time, I use them for the sole purpose of having a safe and easy way of making sure I only include a file once. I'm pretty new to C, coming from other languages, and this was my first full project.
Well, here's a crash course in C and C++ for you.
1. You should never #include "X.c" or #include "Y.cpp" files in your source files or in your header files.
2. This leads to header files and source files. There is usually a matching pair, one with the function and variable declarations, and another with their definitions.
3. Source files naturally include their matching header and are compiled into object files.
3a. When you need access to the functions in the source file, you #include "theHeader.h"
4. Object files are assembled into an executable by the linker, along with any object archives that you link to which are also known as static or dynamic link libraries (archives), depending on whether they require a matching dll file to run.
6. Learn the difference between extern and static storage.
7. Learn how to condense your code. This init code is nuts.
You can safely ignore all of the destruction and shutdown code. Allegro takes care of most of that for you when main exits and al_uninstall_system is called.
if (!al_init() || !al_init_image_addon() || !al_init_primitives_addon() || !al_install_keyboard() || !al_install_mouse() ) { return 1; }
As for the audio thing, it worked fine on my machine and many other linux machines before.
Well thats fine if you're on Linux, which I'm not. There may be a bug in al_fopen or al_load_sample_f with a ".wav" parameter on Windows. The driver is totally different.
When you say the rotation is off, what exactly do you mean? Here's a couple pointers about how rotation should work inside of my game:
The ship is supposed to rotate around its but, not around the center.
Well that's pretty strange to me. Guess I'll just have to get used to it.
if (!al_init() || !al_init_image_addon() || !al_init_primitives_addon() || !al_install_keyboard() || !al_install_mouse() ) { return 1; }
This is pretty poor advice if you ask me. Either these things "cannot" fail in which case there's no point checking, or they can fail, and if they do fail, the user will have no way of knowing what failed, and no way of diagnosing it.
It's also a good habit to get into cleaning up after himself, even though Allegro tries to do this automatically. Technically the kernel will also do a good job of cleaning up after him. You should still try to do it as well.
There's nothing "nuts" about that init code.
The only thing that can be criticized there is the ever growing "failure" code. Since he has variables to track which things failed already he could condense that down into a single cleanup section at the end of the function using goto (or, for purely religious reasons, write it in a separate function).
This has two advantages: it's less repetative and less code, and it's more reliable. Every time you add an initialization you only have to clean it up once instead of many times, of which you are likely to miss some (as did the OP).
The only disadvantage is the use of goto, which many will frown upon, but in this usage style is perfectly reasonable. Again, this could be moved into a function instead. That could save on the many fprintf calls too (have one inside the function operating on parameters). The downside would be having to pass so many parameters (unless they're global, but that's a whole new can of worms).
Well thats fine if you're on Linux, which I'm not.
There's no way for a noob to expect the platforms to differ. Allegro is supposed to be cross-platform so this should have worked the same in Windows. The memory leak was still a bug, but he's right to be confused by it.
That's just poor advice.
Generally, ALL of allegro setup will either work or fail, because of the way you compiled the library. If it doesn't get past al_init, you have a version mismatch.
If you really have to be a pedantic f**k about it ;
Also, ALL you need to call is al_uninstall_system. All this verbosity is just a monstrosity.
I use a pattern like this for initialisation:
Because of the large number of assertions you'll need to make during your init phase, it's good to use a helper function (must_init in my case) to DRY up the code, making it more readable and easier to change.
As for your shutdown phase, al_uninstall_system is normally called implicitly when your program exits. This takes care of shutting down all of Allegro's subsystems, but there may be some things that you want to explicitly destroy (like filehandles) before the OS tosses away your heap.
Generally, ALL of allegro setup will either work or fail, because of the way you compiled the library. If it doesn't get past al_init, you have a version mismatch.
If you built Allegro without audio support then everything will succeed until you try al_init_audio_addon(). Since Allegro's build system fails gracefully for things missing like that by default most users will not even realize they didn't build audio support and will not understand why the game is crashing. If your program just exits or gives a useless Windows-style "something failed" message then they have no idea what failed. Now they have to pull out a debugger because you were too lazy to tell them what failed, and they might be too inexperienced to even know how to do that. They might not even be programmers. They might just be gamers.
Of course, you can always build upon what you suggested:
typedef bool (*BOOLFUNC)(); typedef struct { BOOLFUNC func; char msg[255]; } INSTALLER; #define INSTALLER_ELEMENT(func) { func, #func } INSTALLER installers[] = { INSTALLER_ELEMENT(al_init), INSTALLER_ELEMENT(al_init_image_addon), INSTALLER_ELEMENT(al_init_primitives_addon), INSTALLER_ELEMENT(al_init_font_addon), INSTALLER_ELEMENT(al_init_ttf_addon), INSTALLER_ELEMENT(al_init_acodec_addon), INSTALLER_ELEMENT(al_install_audio), INSTALLER_ELEMENT(al_install_keyboard), INSTALLER_ELEMENT(al_install_mouse) };
That said, I'm not sure if all of those things are even expected to work in the order you've defined..
Because of the large number of assertions you'll need to make during your init phase, it's good to use a helper function (must_init in my case) to DRY up the code, making it more readable and easier to change.
This is good advice. I support this design, but didn't want to suggest it to the OP because it's a bit overkill for such a simple one-time routine that's only ever going to get so complex. Especially for a beginner. What the OP has done is perfectly acceptable.
It's al_install_audio, not al_init_audio_addon.
Fair enough, but the point stands.
Not really. Not when I gave a clear example of how to see exactly which function call failed, without making the code a verbose mess.
That sums everything I feel about this perfectly. 
We aim to please. 
What the OP has done is perfectly acceptable.
Yep, I'll agree with that
further to this, I'm glad that they're not prematurely optimising. Copy+paste is all well and good until a) you're nearing production or b) the code starts dancing about in front of your eyes.
Not when I gave a clear example of how to see exactly which function call failed, without making the code a verbose mess.
I must have missed it. Please explain.
Get some glasses.
In my own code, I always check for errors for each function, as bam correctly pointed out, you want to know what failed. I then have a shutdown() function which de-allocates anything that needs it so it is fairly clean looking rather than having increasingly lengthy code for each failure.
I would have something like:
This has the added advantage that shutdown() can be called from anywhere in your program. I will also often use something like...
fprintf(stderr, "%s(%d): somefunction() failed", __func__,__LINE__);
this will output the function that failed and the line number. (__func__ is C99 and I think C++11)
I actually have my own error functions to handle this for Allegro.
dthompson, I love your init code! Very clean, I may adopt it in the future.
If you really have to be a pedantic f**k about it ;
That's an interesting and compact solution, but I really tend to cringe just looking at it.
I say, use what works for you, if that is what you prefer, go for it. But I like things to be as human readable as possible. I try and consider that if someone else looks at my code, they can know what is happening at first glance and won't have to stare at it for a minute figuring out what I did (I don't always succeed at this). This also helps me in the future.
As you correctly stated, most problems with allegro specific init will be due to how you (or someone else) compiled the library, with the rare bug introduced. But that is exactly why you should check for errors, in case you compiled it wrong or there is a bug. Otherwise you could have problems in your game later on and pull your hair out trying to track it down when it was a failure in initialization, possibly due to something you messed up when you compiled the library. It's simply not a good idea to ignore return values, and I will not. They're there for a reason and only take a millisecond to check. My init code are in their own separate functions as is my shutdown code so it won't clutter anything up.
I must have missed it. Please explain.
He's talking about this code he posted earlier...
Which works, but it isn't immediately obvious what it does, which is part of why I don't like it. But it is an interesting solution if you like this sort of thing.
I use this in my library - EAGLE_ASSERT : https://github.com/EdgarReynaldo/EagleGUI/blob/master/include/Eagle/Exception.hpp
All I have to do is :
Allegro5System* sys = GetAllegro5System(); EAGLE_ASSERT(sys); if (sys->Init(EAGLE_FULL_SETUP) != EAGLE_FULL_SETUP)) { return -1; }
And then EVERYTHING is ready. And all failures are automatically logged. EAGLE_ASSERT gives the expression, line, function and source file it came from.
And then EVERYTHING is ready. And all failures are automatically logged. EAGLE_ASSERT gives the expression, line, function and source file it came from.
Very nice!
Which works, but it isn't immediately obvious what it does, which is part of why I don't like it. But it is an interesting solution if you like this sort of thing.
It's obvious what it does, but it's not obvious how the user is supposed to know what went wrong. It reports a different error code for each function, but it doesn't show how it would be used to inform the user what's wrong. Of course, error code is enough if they're documented. And obviously they could be easily mapped to a second array of error messages, but it would be easier to store the error messages next to the functions. None of this was explained by Edgar so unless he expects us all to assume what he meant I still have no idea what he meant. I don't know what obvious solution it is that I'm missing because there's nothing obvious about an array index telling the user what function failed.
All I have to do is :
If you check how Eagle initializes everything here it looks pretty much exactly like the OP did, except perhaps "worse" because it's broken up into separate functions whose sole purpose is to initialize a single addon and report an error message to the log. I'm not even saying that what Eagle is doing here is wrong or bad. But it's basically the same thing the OP did. Why give him balls for it? Having a bad day or something? I'd hope that the "log" goes to the console by default because otherwise the user probably will have no idea why the program insta-quit without figuring out that there's a log somewhere.
Basically you've wrapped up the necessary ugly Allegro initialization into an interface that allows you to hide it from your main program/game. That's fine, but it doesn't get rid of the ugly initialization. It just hides it.
He's talking about this code he posted earlier...
...
Which works, but it isn't immediately obvious what it does, which is part of why I don't like it. But it is an interesting solution if you like this sort of thing.
If that code isn't perfectly clear, you need glasses. Or to brush up on your coding skills.
You fail to grasp the idea. That concise, condensed, organized code is far easier to read and interpret quickly.
The code is set up that way because it needs a virtual driver. The real code for system initialization is here : https://github.com/EdgarReynaldo/EagleGUI/blob/55c10821ee85bca747b2d5b837aecf7340bf8fbd/src/System.cpp#L242-L267
Edgar, thank you for your pointers.
Everyone else, thank you for the entertaining debate on initialization functions for starting a program. 
I'll find a way to improve it later, probably by storing the init functions and shutdown functions in a different file and calling those functions from main. For now, I want to distribute my program.
Anyways, back to distributing a working program.
Here's where I am at with this process:
I'm having more success with a monolithic build. I just have to include all the libraries with my program, too. I'll see how it goes, and if it works, I'll roll with it and describe how I did it for people from the future.
Someone mentioned making DEB or RPM or Snap packages for my game. Originally I did not want to make people "install" anything because I figured my program was small enough anyways, but clearly this is a little more complicated to do than I thought. Maybe I'll make those packages to see how it's done and distribute the packages alongside the monolithic build just in case the monolithic one doesn't work for some people. When I make bigger projects (or even slightly more complex small ones), I'll probably only distribute the packages and the source, with no monolithic build.
Static compilations seem a little too complex and redundant compared to these two things I am considering. That said, I will try to make it happen, and if I can figure it out, I will consider using it in the future.
The "monolithic" build as you describe it is probably the most successful way to package software. That's why Windows software is bundled with everything. It's just easier. At least in Linux systems you can rely on system packages to satisfy dependencies, but due to binary incompatibilities with different versions you either need to target the packages for the system specifically or bundle everything. Creating the packages for every supported distribution will be a headache unless there is a service online that can do this for you (but even that will likely be harder than you'd like). If you can get a simple tarball with bundled binaries working on many different distributions that will be the easiest method. But if the source isn't private you should still ship a source distribution in case people don't trust executing code from strangers.
Ok, I made a tutorial about how to make OSX bundles with Allegro: here it is!