• Howdy! This is supposed to be my weekly post where I talk about whatever programming stuff I’ve been up to. But if you’ve been paying attention, it’s been an entire year since I last wrote a blog post. That’s because I haven’t been doing any programming for the past year, outside of my job anyway (winky face). But this week I did start doing some programming stuff again, so… here I am, writing another post about it.


    Streaming

    I’ve been thinking about streaming. After a couple trial runs playing video games, I realized I don’t think I would enjoy streaming video games, so instead I decided I would try out streaming while working on programming stuff. I haven’t streamed much so far, though I am streaming right now as I write this, but I feel like it’s kind of enjoyable, even if no one is watching outside of two of my friends.

    So, as Paymoney Wubby would say, I’m streaming right now live at Twitch.com/amiantos. I’m probably not, but I am going to try to stream every time I am doing programming stuff, so there’s no schedule at the moment, but if it’s night time or the weekend there may be a good a chance that I am online and streaming programming junk.

    Or, by the time you’ve read this, I’ve already decided it’s too much work and makes my throat dry when I have to talk continuously on stream to nobody.


    Gamebook Engine

    Over the weekend I got an email from someone that said:

    all my friends used to play games on gamebooks but now we cant find how to download it anymore.

    theres a testflight link on https://github.com/amiantos/gamebookengine but my friend cant get it to work.

    how do i download gamebook engine in 2021??

    thanks so much for making this cool app!!!!

    This is pretty cool, because I didn’t honestly think anyone ever used Gamebook Engine. But it turns out some people have, and have even developed a little habit of making gamebooks with or for their friends. That’s pretty fuckin’ neat if you ask me!

    So yesterday I loaded up Xcode and pushed out a new build so that you can now download Gamebook Engine to your iOS devices if you so choose.

    In a response, this user brought up some feature suggestions that I added to the Issues on GitHub, because I think they’re pretty decent ideas. And besides, who are you going to listen to if not the people who use your own app more than you do?

    do you think you will add a way to organise variables (like put them in folders for them or something)? it would also be cool to separate a game into parts or chapters or something so the overview isnt so complicated for big games


    Life Saver

    I got a second email over the weekend from a fan of my Life Saver screensaver and wondered if there was any chance I would release an M1 compatible version.

    I had been previously contacted by a user of PiBar asking for an M1 version and doing so was simply a matter of opening Xcode and doing an new build of the app. So I figured I could do this for Life Saver.

    Since the last release of Life Saver nearly two years ago, someone submitted a pull request to add the additional grid sizes from the Apple TV version to the screensaver. They did it in kind of a sloppy way, so I had to do a tiiiiiny bit of work to clean up the UI, thanks to a suggestion from my friend Dan while I was streaming the session. But other than that it was no work at all, and I pretty quickly stumbled my way through code-signing the new version of the screensaver and punted it off to the person who emailed me.

    However as I started writing this post, I got an email from them saying that the screensaver still does not work on their M1 mac. So that’s too bad. I’m going to have to look back into that and see if there is some special build option or something else I’m going to have to do to get M1 compatibility.


    Laravel?

    I recently started playing an idle game called Melvor Idle, which I like quite a bit. I went poking around the website version only to see that it was built somewhat similarly some of my earliest PHP-based projects, just with more javascript.

    I’ve been hankering for several months for a new project to work on, and Melvor inspired me to pick up web development again. I like iOS development, but I just want to fuck around, and I don’t want to have to learn SwiftUI and Combine and whatever other new stuff is going on in the world of iOS. I also feel like web apps can reach more people more easily, so more people will mess with my stuff than those who have to download an app and so on. And besides, if I build something awesome for the web, I am more than capable of building an iOS version, so there’s no real loss here.

    So, inspired by Melvor’s use of PHP, I decided I wanted to learn Laravel.

    Laravel looks really cool, and with stuff like Laravel Livewire you can do a lot of dynamic stuff without manually writing a bunch of unique APIs and dealing with AJAX calls everywhere. So it seems well-suited to building some sort of web game, either an idle game or some other sort of interface-based game.

    I’ve only started dipping my toes in, but it seems really cool so far, except for the fact that I have to start writing semi-colons everywhere in my code, which is pretty damn jarring as a python developer. What can you do?


    Well, I think that’s it for me this week. I’ll probably continue learning Laravel and faffing about on my stream. So, if you’re around and bored, and want to listen to my incredible voice, it’s free of charge over on Twitch. Have fun!


  • Cyberpunk 2077 is quite an achievement. Is it absolutely perfect? No. But the ways it’s not perfect can (and likely will) be fixed with patches. Will it ever live up the absurd levels of marketing hype? Probably not, but that’s okay. The hype was ridiculous, and the ways it has fallen short of it are fairly unimportant to the gameplay in my opinion.

    The main thing about Cyberpunk 2077 is how immaculately set-dressed the world is. Nearly every accessible part of the world is just a huge mess of garbage and grime, but also things of technological beauty. The amount of detail and design work put into the drivable vehicles alone is mind-boggling. Cyberpunk’s rendering of a large capitalist dystopia is akin to Red Dead Redemption 2’s rendering of the American west. It’s basically the perfect idea of these places. Is it 100% realistic? No, it’s the theme park version, but isn’t that better?

    The nicest graphics in the world wouldn’t save the game if the gameplay wasn’t fun, and Cyberpunk delivers there as well. The gunplay feels great, very punchy and visceral. The RPG progression eventually turns you into an overpowered unstoppable murder machine, but isn’t that how it should be? You start off having to hit people 5 times in the head to kill them, and by the end you’re doing somersaults and slicing people’s heads off with retractable mantis blades. Sounds about right to me.

    The main story and the sub-stories that compose the bulk of your first 40 hours in Night City are the glue that holds the whole thing together. The main storyline is fairly interesting, and some of the other storylines definitely go interesting places, but it’s mainly the characters that actually make you feel things here and there. Sometimes that might be annoyance when the characters make mistakes despite your best efforts, or admiration when you see another side of a character you didn’t expect.

    Cyberpunk is unfortunately a buggy game, with some rough edges here and there. I don’t think the bugs I’ve encountered are anything worse than you usually get with a game of this scale, but we’re in an age of the internet where every minor complaint must be amplified until it becomes a scandal that must be brought up every time the game is mentioned. That’s too bad, because it almost prevented me from getting the game. That said, maybe all the negative hype on release helped me adjust my expectations, so that I’ve been able to enjoy what is a monumental achievement, instead of sitting around nitpicking it to death.

    As a bonus, here’s some pictures I took with photo mode while I played the game. There shouldn’t be any spoilers.


  • When I started developing for PICO-8 a couple weeks ago, I quickly realized that it wasn’t really built in a way to be very portable. I like to develop on a couple different computers, so it’s important to me that I can quickly transfer files around and maintain a consistent development environment between machines. So, I quickly hacked out an easy way to do that, and along the way made my life a lot easier. I figured I would share my tips for PICO-8 development in case it helps out other people.

    First up, fair warning, I develop on macOS, so this post will be macOS-centric. I’m sure many of these tips extend to Windows, but it’s an exercise to the Windows-using reader to figure out how. I also admit that I’m an established developer who is already very familiar with most development concepts, so I may gloss over things that seem simple to me, but are impossibly difficult for a newbie. I hope not, but it might happen!

    One Folder to Rule Them All

    By default, PICO-8 stores all your carts in a folder tucked away in your ~/Library. Sure, you can type folder in the CLI to open it up, but that’s not really my style. I keep all of my programming projects in ~/Coding, and I wanted to keep my PICO-8 projects in there as well. On top of that, I didn’t want to have to put PICO-8 in my Applications directory on each machine I develop on, because I’m super lazy.

    I created a pico-8 folder in my ~/Coding folder and put my PICO-8.app in there. I also created a carts directory to store all the carts I make or download. In the end, my folder structure looks something like this:

    pico-8/
    ┣ PICO-8.app
    ┣ carts/
    ┃ ┣ bob/
    ┃ ┃ ┣ bob.p8
    ┃ ┃ ┣ comms.lua
    ┃ ┃ ┣ jrpg.lua
    ┃ ┃ ┣ overworld.lua
    ┃ ┃ ┣ shmup.lua
    ┃ ┃ ┗ transitions.lua
    ┃ ┣ demos/
    ┃ ┣ utils/
    ┃ ┣ zines/
    ┃ ┣ life.p8
    ┃ ┗ template.p8
    ┣ .gitignore
    ┣ PICO-8_CheatSheet.png
    ┣ README.md
    ┣ license.txt
    ┣ pico-8.code-workspace
    ┗ pico-8.txt

    In order to make PICO-8 realize you’ve changed your carts directory, you’ll need to venture into the main place it stores your config.txt on each computer you develop on. If you want to be fancy about it, open up your Terminal and type in nano "~/Library/Application Support/pico-8/config.txt"

    Pro-tip: If you do not see a config.txt, you need to close PICO-8. It’s only generated the first time you close PICO-8.

    Scroll down and find the header root_path and change that path to the new location of your carts folder. For me, that looks like this:

    // Location of pico-8's root folder
    root_path /Users/amiantos/Coding/pico-8/carts/

    Save the config file by hitting Ctrl+X, Shift+Y, and then Enter (if you’re editing with nano).

    Note: this config.txt file does not support the tilde shortcut, so you can not put in root_path ~/Coding/pico-8/carts/ for example.

    Re-open PICO-8 and type ls to confirm your new carts folder is there. If you got nothing in it, put something in it by typing save tempfile then type ls to see the file in the PICO-8 CLI and then confirm it appeared in your proper carts file. Did it? Great!

    Easier Debugging

    Every developer worth their salt knows that the best way to debug anything is to simply print or console.log stuff. Sure, in some environments you’ve got breakpoints and all that, but with PICO-8 we’re kicking it old school. Unfortunately the print command in PICO-8 draws to the screen, which isn’t really ideal because the screen probably has other stuff being drawn to it that we’d like to see instead.

    Luckily, PICO-8 has a printh command, which will print logs out to the command line. But you need to launch PICO-8 from the command line to benefit from this behavior, which isn’t totally straight forward. Let’s go from 0 to 60 and make it real easy, real quick. We’re going to create an CLI alias to launch PICO-8 from the Terminal whenever we want.

    Step one: Open Terminal

    Step two: Type in nano ~/.zshrc

    Step three: In this file, add this line somewhere, replacing the folder name with the path to your PICO-8.app.

    alias -g pico8="~/Coding/pico-8/PICO-8.app/Contents/MacOS/pico8"

    As above, hit Ctrl+X, then Shift+Y, then Enter to save your changes. Close your Terminal and open a new window. Type in pico8, and pico-8 will launch! Let’s test it out to make sure it’s working. His ESC to bring up the PICO-8 editor and type in printh("hello world"), then hit Cmd+R to run the file. In your Terminal window, you should see the text hello world.

    Now when you’re building your game, if you’re curious about what is happening as you play, you can put printh statements all over the place. This is an invaluable tool for all pico-8 developers, and makes it really easy to debug issues with your game loops and so on.

    Step Up Your Editor Game

    PICO-8 comes with a code editor built in, and while it’s fun to use when you’re learning or building small projects, it becomes kind of hard to stay organized. It’s a lot nicer to use a code editor like VS Code to program, but you can run into conflicts if you edit your .p8 files in VS Code while you’re also drawing sprites or making music within PICO-8. On top of that, editing .p8 files directly in VS Code is just kind of cumbersome and while there are PICO-8 extensions for VS Code, they’re not absolutely ideal.

    Luckily, you can use #import statements to make life a little easier. Look back up at that directory structure posted above. Do you see that bob/ folder? That’s one of my projects. The bob.p8 file is the core project file, but all the code for the project is actually stored in .lua files. This lets you split your code up into multiple files, so you can separate your files by game mode or more. When you run or export your project, set up correctly, PICO-8 knows to automatically import all the code from those files. It’s seamless!

    It’s also very easy. Just create a .lua file in the same directory as your .p8 file, let’s call it test.lua. Put some code in it, maybe printh("hello world") from above? Now, go into the code editor in PICO-8 and type in #include test.lua and run your project. If everything went according to plan, you should see hello world in your Terminal. Congrats!

    You can #include as many .lua files as you need for your project. The sky is the limit! So far I like to keep each of my ‘game modes’ split up into separate files, just to stay organized. Now you can safely edit your code in VS Code, while editing sprites and music in PICO-8, and never worry about losing code or graphics. Just be sure to keep your code all in these Lua files!

    Gotta Git Up to Get Down

    If you’re a newbie developer, you probably have no idea what Git is. That’s okay. But you should know, and you will know if you’re going to make a career out of development.

    I’m not going to go deep into how git works or how to use it. There’s plenty of tutorials out there, like this one, for beginners. The main thing you should know is this: Git makes it very easy for you to backup your code, and when you start collaborating with other people, it makes it extremely easy to collaborate in a safe way where you’re never in danger of losing precious code. If you’ve ever made changes to a program that you regret and cannot recover from, smart usage of Git could have saved you; if you’ve ever accidentally deleted a file or had storage die on you, git would have saved you.

    How does this apply to this guide? Well, my entire pico-8 folder I showed you above is also stored within a private GitHub repo. This means whenever I want to develop on a new computer, I just have to pull down the git repository from GitHub, and all of my files as well as my PICO-8 executable are there waiting for me. If I make changes to one of my projects, I push it to the repo, and then I can pull those changes down to my other computers. No need to worry about file transfers, or syncing folders to the cloud, or any of that nonsense. I am always in control.

    Note: Notice that I said my repo is private. If you don’t want other people looking at your code, keep it private! On top of that, do not ever store your PICO-8.app in a public repo, by doing so you’d be giving PICO-8 away for free to everyone, and that’s unethical!

    If you want to make other developers look at you funny, use a GUI client for git. I really like GitUp for macOS. As with all things related to software development, there are people who think using a GUI for git is lame and weird. But, you do you! I like how it looks.

    If you don’t intend on ever collaborating with other people, or you don’t care about backing up your code files, then don’t bother with any of this!


    That’s about it! The combination of “one folder” and “importing lua files” makes PICO-8 development a lot easier to manage.


  • A couple of years ago, at another job, I hired this guy for a data entry position. He seemed like a decently smart guy who could type fairly quickly, even if maybe he was a bit of a bro. He was at the company for well over a year, I’m pretty sure, but my memory is pretty shoddy. He’d earned “Employee of the Month” twice in one year, which was something we debated about and wasn’t just a random pick!

    Then one morning, he didn’t show up to work. No call, no email, nothing. The next day, he still wasn’t there. I’m pretty sure it was this day, or the next, I called his emergency contact: his girlfriend; just to make sure he was okay! I wasn’t trying to be a nosey up-your-ass manager or anything, it was unusual for him to just no show… plus we needed to know if we needed to hire someone else…

    His girlfriend answered the phone. I told her who I was, her boyfriend’s manager, and that we hadn’t heard from him in two or three days and we’re just checking in to make sure he’s okay? She said, “Uhh, hold on a second,” and hung up the phone. When I called her back (maybe we’d gotten disconnected?) it went to voicemail.

    At this point it seemed pretty clear that he wasn’t going to come back to work, so we set about looking for his replacement. Pretty lame that he did that, but who knows why people do the things they do? A week or two later, the company got the paperwork from the unemployment office for him. He’s trying to get money from the EDD!

    The company explains to EDD: No, he no-called, no-showed, he probably shouldn’t get unemployment. And then the guy tells EDD: I was actually secretly fired by my manager on the morning of *insert date here*. But *I* was his manager! And I didn’t do that. I don’t know if he elected for it or if it just automatically happened, but a hearing in front of a judge was scheduled for a week later. Normally the company wouldn’t have gone to such great lengths to deny someone unemployment, but he just flaked out. Very uncool.

    The first absurd thing about the story is that he was a *good* employee. I had been a manager long enough to know that it’s a pain in the ass to hire people, and it sucks when they’re not good employees because it’s such a waste of time and energy. The last thing I’d want to do is willingly get rid of a good employee. And we had proof he was a good employee: emails I’d sent to others about him; emails I’d sent *to* him directly, praising him; and the aforementioned two time Employee of the Month receipts.

    I also had a bit of an ace up my sleeve: we had security cameras everywhere. He knew this, because he used them sometimes as part of his training. So, trying to be well prepared for the ‘hearing’ or whatever it would be, I sat down and I exhaustively recorded my whereabouts on the day in question. I noted down every time I left my desk, where I went, how long I was gone, and who was in the rooms with me where I was. I was almost never alone, and always on camera. I’m pretty sure I also prepared a statement, in addition to the email ‘evidence packet’, just to be thorough.

    Unfortunately, the truth about this guy is that while he was a good employee, he wasn’t a happy one. He had bigger aspirations. When getting to know him, I’d asked him what he most wanted to do, for work, with his life, or whatever. He said he wanted to be an entrepreneur. An entrepreneur of what? I asked. He didn’t have an answer, it didn’t matter, he just wanted to be *an entrepreneur*.

    He wasn’t happy that he was a ‘lowly’ data entry person. We’d tried to get him involved in the inventory security program we had, but looking at camera footage bored him. He asked at one point if the company would pay him while he learned how to grow marijuana, so he could become a sales person and make more money. The company said no, we won’t pay you to learn to grow, but we’ll give you access to our training program documents so you can learn on your own. Disappointed, he declined the offer. Unfortunately for him, there just weren’t any open positions in the company he’d be a good fit for, so he was stuck doing data entry.

    This is mostly just making fun of him, but I’ll allow it: one day a nearby company put out a pallet of 3M foam earplugs, boxes within boxes, a couple hundred earplugs a piece. We all took a couple boxes. He came to work the next day saying he was going to sell the earplugs online, with photos of Hilary Clinton and Donald Trump glued to them, as a novelty gift, with statements like, “the holidays might get loud this year!” or something. This idea never actually got off the ground.

    The day of the hearing, I arrived with the HR assistant and we sat at a long table. My former data-entry clerk was sitting directly across the table from us. Some judge sat down at the end of the table taking notes. I honestly don’t remember who spoke first, but the story was pretty simple: He alleged that, sometime on the morning in question, I walked upstairs to where he was working, walked him down through the building, through the back offices, out to the back of building by the street, and told him we’d no longer be needing his services, and he immediately walked down to the bus stop and left.

    So I asked him, what time did this happen? And I don’t remember what time he said, but it didn’t matter, I responded: Well, I have this chart here I made of where I was, and who I was with, for every moment of the day, and at no point was he anywhere to be seen. I submitted that shit into evidence, which wasn’t as dramatic as it sounds when you say it that way, all I did was hand it to the judge. The judge asked him if he had any objection to it, and he said of course, and explained that I was in charge of security cameras–true–so I could fake the whole thing or just make up a list. The judge didn’t find that very convincing.

    I also shared the fact that we knew he wasn’t happy, but that otherwise he was a good employee as evidenced by the internal emails, emails to him, and the Employee of the Month awards. He said that we gave out Employee of the Month awards just to quiet down unhappy employees, which is only true sometimes, and may have been why he received it twice in one year without us noticing…

    I also asked him some of the more obvious questions: who else witnessed me walk through the building with you? Why didn’t you go back upstairs to grab your huge bag of pistachios and other snacks, or to say bye to any of your co-workers? And when I called two days later, and your girlfriend answered, why didn’t you talk to me or call me back?

    The judge really jumped on that last one, saying: yeah, if this guy fired you, and then called your girlfriend ‘pretending’ you no-showed, wouldn’t you wanna cuss him out or something? Give him a piece of your mind? He didn’t have great answers to any of these questions, beyond that he was upset and confused about what happened, so he didn’t want to talk to me or anyone.

    At the end of the hearing the judge said, I’m pretty sure this is verbatim, “This is a peculiar situation. Two people who have entirely, completely different stories. But you both seem credible. He seems credible, and he seems credible. Very interesting.”

    Obviously, we received word the judge ruled in our favor a short time later, and rejected his claim for unemployment. He then appealed, this time with a new story. If I remember correctly, he suggested that one of his former managers (before me) didn’t like him, and that they were using me to fire him because of it. None of that came up in the hearing, though, and after review of the case by another judge his appeal was rejected.

    I don’t manage people anymore, and I’m kind of very glad that I don’t. I might have had a worse than average experience, because the company wanted to pay people the bare minimum amount needed to hire someone, *anyone*, and I ended up with employees who were often lazy, dishonest, and manipulative. You’d think being dishonest would take so much effort it’d be better to just do the job you’re meant to do, but no.

    Prior to this guy, I was used to employees telling bald-faced lies directly to my face, usually ones I couldn’t quite prove. But this was the first time that those lies directly involved me, saying I went and did things that I definitely didn’t. Luckily, I was sitting in front of an impartial judge, and at absolutely no personal risk whatsoever, so if I failed to prove that fact it wasn’t that big of a deal.

    But, in different circumstances (like if I worked for a larger company), he could have written a Medium post, or talked to a reporter, and voiced his fabricated mistreatment to the world at large. I could just imagine how the article would account, in detail, his alleged mistreatment (“they wouldn’t pay me to learn…”) and the day I led him outside and fired him, maliciously, leaving him destitute and dependent on his girlfriend.

    And then there’d be a single sentence at the end of the article: “The company denies these allegations.” That’d be all the defense I’d get, that the company would be legally allowed to give itself. I mean, I probably wouldn’t be named, but I and anyone else working there would know he was talking about me, which could hurt me. Luckily, I’m awesome, and that wouldn’t hurt my feelings too much. But there’s other people out there, less awesome, and more susceptible to hurt feelings.

    The point is: when it comes down to it, sometimes people can be dishonest about work. That can be work they did in the past for other people, or work they’re doing right now, for you. People who seem to have a relatively normal level of dysfunctional habits can suddenly, one day, just do something completely unexpected when there’s money on the line. It’s important to be aware of this, and not just blindly trust everyone who cries mistreatment at the hands of a company. *Sometimes*, not always, they’re just a grifter, looking for sympathy and a handout.


  • Last week I bought a Raspberry Pi Zero W so that I could set up a Pi-hole on my home network. If you don’t know what Pi-hole is: it’s a network-level ad and tracking script blocker. It’s different from using something like AdBlock or the Brave browser, because it works across all devices on your network automatically. Scripts and ads are blocked before the request can even be made, so in some cases it can really speed up your web browsing.

    After getting it going and seeing the nifty admin panel that shows you query stats, I realized I would really like to see those query stats very easily on my Mac. Long story short, I ended up building the app I envisioned, and even added some extra features I hadn’t planned on originally, like multiple Pi-hole support and the ability to toggle your Pi-hole(s) on and off as you please.

    PiBar is free open source software (FOSS) like everything else I’ve built, so you can grab it over on the PiBar GitHub repo. It’s also available on the App Store, if you want to show your support (and get automatic updates): Purchase PiBar on the App Store.

    I’ll be writing a WIUT post tomorrow about my development process for PiBar over the past week, so you can look forward to that, if you need something to look forward to. (If my post tomorrow is all you have to look forward to, reach out to me and we can get you the help you need. You’re not alone, we can get through this together.)


  • Recently we decided over at Lingo that we want to allow our customers to download multiple asset files at once. We store all assets in AWS S3, which doesn’t offer a method of retrieving multiple files at the same time in a single zip file. (Why not!?)

    I did some Googling around and found a Python solution that purported to work in the most ideal way: no memory usage, and no disk usage. How can this be? It sounds like magic. Basically the idea is that you *stream* the files from S3, directly into a zip file which is streaming to S3 as you add the files to it. (Still sounds like black magic.)

    Long story short, this did *not* work in Python. I don’t know why, as I followed a couple examples from the internet. Whoever suggested that it did work must not have tested it thoroughly, or was delusional, because it definitely used memory as it was building the zip file. With sufficiently large files, or a large quantity of small files, the whole thing crashed.

    Distraught that my precious Python was failing me, I found several sources on the internet that suggested this idea *did* work when using Node, and worked especially well as an AWS Lambda function. This sounded like a good idea to me, because a Lambda function will definitely not allow you to use too much memory, disk space, or time. Only problem was that I knew little to nothing about Node/JavaScript development. I guess it was time to learn!

    So… I learned. Long story short (again), it took a little bit of research, but I came up with this Lambda function which successfully zips up hundreds of files directly into a zip file on S3, without any real memory usage and definitely no disk usage. It took some research, because none of the examples people had posted online actually worked flawlessly from beginning to end. For all I know, this script has some issues, but I haven’t run into them yet, and when I do, I will update it to avoid them.

    Compared to Python, Javascript syntax is a horrific mess that makes my eyes bleed. But I can’t argue with results, this worked out while the Python solution did not.

    If you need an explanation of what this code does, just give yourself a year or two as a professional developer and you’ll realize understanding what it does isn’t particularly important, but by that time you’ll be able to figure it out.

    Good luck!


  • After three years of Numu Tracker being a part of my life (the first year of it spent slowly developing it), I am moving on and shutting it down as of today. There’s several reasons for it, so I figure I will briefly talk about them as honestly as I can, and also reflect on what I learned along the way.

    Why shut it down?

    As Numu got more popular, I ended up getting forced into a fairly expensive VPS package by Namecheap. Originally it cost about $10/mo to host, and then it skyrocketed up to $60/mo. This is mostly because I wasn’t aware of CDNs (like S3 or DigitalOcean’s Spaces) when I was building Numu and was hosting all the artist and cover art on my server directly. When I needed more space, Namecheap balked at the amount of files I had, or perhaps at my bandwidth/cpu usage, and pushed me into a VPS package.

    At this point I had my first real programming job and had begun working on a Python-based API. I liked it a lot, so I decided to get around this crazy hosting cost for Numu by rewriting Numu Tracker from scratch, to fix some of the issues it had as well as get on some different hosting. I even reached out to some people to see if I could get free hosting, since Numu was open source, and DigitalOcean agreed to host it for free and immediately gave me some credit to use for it. Things were looking up!

    However, I procrastinated on building the new version. When I eventually worked on it again, I got the new API into a semi-working state but eventually turned away to work on other things. I was just having a lot of trouble summoning enthusiasm for a project that was originally “hacked together” and was now turning into something I wanted to make more “professional”. Over the course of a year and a half I would come back to Numu 2.0, put a little work into it, get bored or overwhelmed, and stop. I ended up working on other stuff instead (like Life Saver, Aeon Garden, and Gamebook Engine). My desire to build something truly impressive was outmatched by my desire to work on other, momentarily more interesting things, that I could sloppily wander through.

    The final straw came about a week or two ago when I received some bad financial news. I decided to let it inspire me to finally finish Numu 2.0 and I sat down to start to work on it with clear eyes and a full heart, but quickly lost steam. The new version was shaping up to be not _quite_ as good as the current version, and it started to look like a lot of work, and not fun work, but hard work. I started to ask myself: I don’t make any money on this, and I don’t really want to ask anybody for money for this, and I probably shouldn’t be spending my time working on something that isn’t going to make me any money, so should I be doing this?

    The answer was, obviously, no. I felt pretty bad about it and was morose for a day or two. It’s depressing to kill off something you’ve built, that you are (or were) proud of, that you know other people like a lot. I felt a bit like I was letting down all the serious music fans like myself who regularly used Numu, especially because there had been points I “promised” that Numu wouldn’t shut down. That’s kind of a big downside to program development as your “artistic hobby” in contrast to other mediums: paintings, recorded music, the written word, film or video; once published and given to someone, these things essentially last forever. You can’t “take it away”, typically, from someone who has it and loves it, and its continued existence costs you nothing personally.

    The regret I felt about shutting it down was made worse by the fact that I had numerous opportunities to make different choices, to try to migrate v1.0 to a new host instead of insisting that I’d build v2.0 instead, or to just actually put my head down and finish it. Instead I let it stagnate and burn money. This regret was further compounded by the flood of supportive words and thanks that I received on the subreddit when I announced that Numu would be shutting down. One person reached out to me privately and offered to pay for 3 years of Numu hosting, assuming he could recoup his costs later on down the road. (After I told him how many users Numu had, and what growth was like, he agreed with me that it would likely not be worth it for him.)

    As of today, Numu is gone. The app is a useless carcass only fit for deletion. There are a couple other apps on the app store that do similar things, but it’s mostly with sadness and not pride I say that they are not nearly as good as Numu Tracker was. They’re not as well designed, they’re not as thorough, and they just don’t feel as solid and nice. Numu Tracker was one of a kind, and music fans are worse off without it.

    What did I learn?

    I learned a couple things about running an app that I think would be valuable for anyone else considering running their own app.

    Encourage people to give you money

    This is a personal failing. I like to be generous and altruistic, perhaps to a fault. I have a “hacker ethos”, according to a friend of mine, where I believe that all software (or artistic creation) should be free for all, and that money will naturally flow from that. I think this is a symptom of my privilege, and my “youthful mindset”. I’ve never been so hungry that I’ve felt the need to charge for things I build, and on top of that, my impostor syndrome leads me to believe that nothing I’ve built is worth money to other people.

    I was mistaken. Numu was worth money, at least to some people. Eventually I started to ask for money, but I did it gently and in a way most people wouldn’t notice, so it did me no good. It would have been better for me to build some sort of donation option into the app using in-app purchases. Instead I tried linking people to my blog in the hope they’d then go to my Patreon, and feel generous.

    This is too much to expect of people. At a sufficient scale, it can work, but you need millions of fans or users. You have to expect that only 0.5% of your users are enthusiastic enough to go out of their way to figure out how to give you money. If you’ve got millions of fans, this is fine because that’s still thousands of people. But when you only have thousand users, that’s maybe five people if you’re *lucky*.

    When I first published Numu, I did have IAP that were needed to unlock push notifications. But nearly no one downloaded the app, and no one paid for the notifications. I should have stood my ground, but instead I ended up making the notifications free and open sourcing the app for some publicity. Eventually, RecordBird shut down, and a flood of users came into Numu, and it would have been valuable to have an IAP around at that time because RecordBird users really wanted to find a service that wasn’t going to shut down, so I bet they would have been happy to pay for it. But they had no option to.

    In short, you should charge money for your app if it costs you any money to run. It’s great to think that you’ll always be financially capable and willing to foot the bill for all your users, but that can get out of hand especially if your app grows considerably. Even if the option is just voluntary donations, it’s better than nothing, and if you get into a tight spot you might find that your users are compassionate and generous, willing to donate when they hear you really need the financial support to keep the app alive.

    Other things I learned…

    The main other thing I learned is that users are not very vocal. If your app stops working or has bugs, they will stay quiet about it for a while. At one point I’d completely broken the app for several days, and only one person (out of a thousand or so) reached out to me to ask what was going on. You can’t rely on your users to notify you about problems in your app. Some will, but most won’t, so you need to be proactive in trying to find and fix these bugs on your own.

    App development is hard work, especially on iOS. iOS is growing more complicated as time goes on. As of April 2020, apps must have an “iPad version”, which Numu never had (though it came close to having a very ugly one last year). To make a “first class” iOS app, you have to worry about other stuff like accessibility concerns, state restoration (another coming requirement), and some other stuff I can’t think of now.

    It can also be unrewarding: I had to sit by and watch worse apps get “Featured” in the App Store, on Product Hunt, or show up on Apple blogs, because they somehow knew the right connections to make or had other advantages. I think Apple never “Featured” Numu on the App Store because it promoted services other than Apple Music (similar apps that *only* work with Apple Music have been featured, and they were much worse than Numu).

    What did I do right? I built an app I wanted for myself, and I built it so that it would do the job I needed it to do. I had faith that there were other users out there like me, who would want the same kind of app that I wanted, and that faith bore out. It was very gratifying to receive feedback from users who appreciated the “to-do list” aspect of app, considering I had several friends tell me that they didn’t understand who that was for. It was for me! Luckily, it was for some other people out there too who took music listening as seriously as I did.

    In short: If you build an app, build something you want and need. It will inspire you to struggle through the hard times, and it’ll feel extremely satisfying when you learn that you’re not alone in the world, other people have the same needs.

    What’s next?

    So what’s next for me? I don’t know exactly just yet. I think I am going to try to focus on a bigger project that I can charge money for right out the gate, some sort of game. I’ve got a solid idea, but I’m not ready to talk about it just yet.

    Will Numu ever return? I haven’t ruled it out. If I find success with some other projects, and I continue to miss Numu, maybe I will raise it from the dead as a subscription service (so I do not repeat the same mistakes). I think it’ll be a year or several years, and I hope in the meantime Apple Music or Spotify can pull their heads out of their asses and provide their users with new release lists that render an app like Numu unnecessary. I guess we’ll see.


  • I recently started working more with AWS Lambda functions, some of them with external dependencies, and I quickly ran into an issue where dependencies that are built on MacOS sometimes don’t play nicely with Amazon Linux on Lambda.

    The solution, back in the early days of Lambda, was that you would have to spin up an EC2 box, and then build and zip up your dependencies there. Luckily, things are much easier now because Amazon offers a Docker image for Amazon Linux that you can use to build your dependencies.

    I found a blog post by Quilt that discusses automating the process of creating your Lambda deployment package, but their solution stops at building the zip file you need. I wanted to take it a bit further and get the entire deployment process down to just one command, so I made some changes and additions.

    First up, you have your Dockerfile. This is basically identical to Quilt’s, except that I’ve added the awscli package to be installed.

    FROM amazonlinux:2017.03
    RUN yum -y install git \
        python36 \
        python36-pip \
        zip \
        && yum clean all
    RUN python3 -m pip install --upgrade pip \
        && python3 -m pip install boto3 awscli

    Then you need your package.sh file, this is the script that’ll get run inside your Docker container once it’s built. Note the $LAMBDA_FUNC environment variable. We’ll be setting that when we run the container via the Makefile.

    #!/bin/bash
    
    mkdir tmp666
    [ -f /io/requirements.txt ] && python3 -m pip install -r /io/requirements.txt -t tmp666
    rm -f /io/lambda.zip
    cp -r /io/* tmp666
    cd tmp666
    zip -r /io/lambda.zip *
    
    cd /io
    aws lambda update-function-code --function-name $LAMBDA_FUNC --zip-file fileb://lambda.zip
    
    rm -f /io/lambda.zip

    Before we tie it all together with the Makefile, I should clarify that I wanted this automation to be set up so that I could use it to deploy multiple different Lamdba functions to different places, so the directory structure is pretty important. In my setup, the directory structure looks like this:

    lambda
    ├── hash-function
    │   └── lambda_function.py
    ├── preview-function
    │   ├── lambda_function.py
    │   └── requirements.txt
    ├── Dockerfile
    ├── Makefile
    └── package.sh

    You’ll notice that a function may or may not have dependencies. Even without dependencies, this automation can still make developing in VSCode and deploying to Lambda slightly easier and faster (than copying, pasting, and saving). The main trick to this is that my folder names exactly match the name of the function in Lambda. So hash-function will deploy to hash-function in Lambda, and so on.

    So our final step is to create our Makefile.

    deploy-lambda:
    ifdef p
    	cp -f package.sh $(shell pwd)/$(p)/package.sh
    	docker build -t lambda .
    	docker run --rm -e LAMBDA_FUNC=$(p) -e AWS_DEFAULT_REGION=us-east-1 -e AWS_ACCESS_KEY_ID=$(shell aws --profile default configure get aws_access_key_id) -e AWS_SECRET_ACCESS_KEY=$(shell aws --profile default configure get aws_secret_access_key) -v $(shell pwd)/$(p):/io -t lambda bash /io/package.sh
    	rm -f $(shell pwd)/$(p)/package.sh
    endif

    Note that we’re automatically grabbing the necessary AWS access keys from our local machine and injecting them into the docker container, so that it can deploy the package to Lambda for us.

    Then to deploy to lambda it’s as simple as writing…

    $ make deploy-lambda p=preview-function

    That’s about it! Doing this has saved me a lot of time that would have otherwise been spent running a couple different commands to deploy my Lambda functions. Now it’s fast, easy, and worry-free. Again, kudos to Quilt and their blog post for doing more than half the work for me 😉


  • When I finished Life Saver it seemed obvious to host the downloads on S3, like the rest of the site. But I wanted to be able to track how many times it had been downloaded, and S3 doens’t have this functionality built in. Coming from a traditional web background (Apache, cPanel, etc) I wasn’t sure where to start, but luckily I saw a StackOverflow answer that pointed me toward S3stat.

    What’s really great about S3stat is that it has a nice macOS utility to help setup all the complicated AWS stuff for you. One S3stat up and running on your bucket, you get a nice looking dashboard showing you tons of info. As an example, here’s the current daily stats for my bucket. (Note that I really only host two files there.)

    My S3 bucket download stats from S3stat

    You can drill down into individual files to see stats for that specific file:

    Single-file download stats from S3stat

    One feature I like a lot is that it also shows you referrers, so I can see how people are getting to my file just in case any blogs or websites directly link to it (which hasn’t happened yet). Between this, GitHub’s traffic stats, and Google Analytics, I have a pretty complete view of where my traffic is coming from and how effective my site is.

    Referral stats from S3stat

    The S3stat service is normally $10/mo, and as an open source developer with no commercial products under my belt, I can’t really bring myself to shell out even that amount of money for it. Luckily, the owner of S3stat is a generous soul and is willing to extend my free trial into perpetuity (or until I am rolling in open source riches and can pay for it myself). Many thanks to S3stat for giving me a way to gain some insight into how frequently my files are downloaded!

    Check it out at https://www.s3stat.com!


  • Since I was a kid I’ve been pretty intrigued by artificial life simulations. It started most predictably with Tamagotchi, but when I got a PC in the mid-90’s I learned that there were people out there interested in making their own, more elaborate, versions of artificial life. One that’s still available today is Gene Pool, and I remember one that consisted of a 3D modelled woman who wandered around a sparsely populated landscape (though I can’t remember what it was called).

    My favorite was called Alife Tank HAKONIWA, which is supposedly still available on some download sites if you’re running Windows. It gave you a side-view of an extendible tank that housed a lot of randomly generated creatures that swam around, ate, and mated. Something about this one really appealed to me, though the creatures were often too small to identify easily and whether there was any actual decision making going on was pretty inscrutable.

    About two years ago, I decided to tackle my own simple version as a learning project to strengthen my programming skills. I’d watched a tutorial and learned that SpriteKit was extremely easy to work with, so it seemed like a no brainer to use it. I spent about a week getting a basic version working, showed it off a bit to co-workers, then forgot about it until I decided to flesh out my GitHub profile with more projects. I decided to play with it a bit at that point, but the code was such a horrible mess that I got discouraged and forgot about it yet again.

    Since then, it’s been forked twice, and starred twice, on GitHub. It’s not a lot of attention, but I figured if people were going to play with it, I might as well clean up the code using the skills I have now.

    Wait, so… what is Aeon Garden?

    I suppose trying to describe what Aeon Garden is in text is kind of silly, it’s probably easier to get what it is by seeing it in action, so here’s a video.

    You can see the resemblance to Gene Pool almost certainly. With that out of the way, here’s some of what I’ve done to try to clean up the code base and why. If you don’t care about any of that, just go check out the project on GitHub!

    Refactoring Creature Generation

    In my first draft, creature generation was extremely sloppy with a lot of repeated code. Basically, a creature could be generated either “from scratch” or “from parents”, and both of these init methods have one key difference between them: in one case, the creature is generated from scratch, and in another it’s generated using attributes belonging to its parents.

    When I originally wrote the code, I was in a rush so instead of taking the time to consider what aspects of the process are shared, and carefully creating reusable blocks of code to share between them, I hastily ended up writing two entirely separate, long-winded functions. You can see the original “create from scratch” method on line 93 through 352, and the original “create with parents” on line 354 through 692. Yup, that’s two functions totalling nearly 600 lines.

    Not only are these functions simply way too long which makes understanding and maintaining them difficult, having two separate functions that do nearly the same thing means that when I wanted to make a change to how creatures were generated, I would likely have to make the change in two places. This means I’d always be at risk of forgetting to make the change in both places, introducing inconsistencies.

    I decided that I would tackle limb generation first, since this was essentially the entirety of the `init` methods, and it didn’t make a lot of sense that I was doing the exact same thing, repeated four times for each limb. I honestly don’t know what I was thinking years ago, I mean, aside from “Who cares how bad this code is, no one will ever see it”. After thinking about what goes into each limb, I ended up with a very clean Limb class totaling ~70 lines. Yes, I replaced roughly 300 lines of repetitive code with just 70.

    After replacing the limb generation with the classes, it was relatively simple to break the rest of the creature init methods into reusable methods. These pertain to placing the limbs on the body: setupLimbs() and creating the physics body: setupBodyPhysics(). At the end of my refactoring, _both_ creature generation methods now run from line 72 through 127, which is an improvement of ~550 lines!

    Not only did this make the creature creation process easier to understand, but it makes changing aspects of limb generation far easier than before. Instead of changing one thing in several different places, I can make one change in one file and rest easy that I didn’t miss anything.

    If I Only Had a Brain…

    Another big problem I had was with the way my creatures made decisions. All of this was baked into one large think() method, which ran from line 694 through 827. Only 133 lines, but SwiftLint was very unhappy about the large number of if statements in it. It’s actually hard to count them, especially once you factor in the various for loops further nested inside of it, but there’s more than 10.

    I figured if I was tackling this as an attempt to design the creatures in a more object-oriented way by separating the limbs from the rest of the body, I should probably refactor all the decision-making into a separate class that we can call a brain.

    Creating the brain ended up being more involved, mostly because I had to make some decisions (which are still in flux) about how to divide up the responsibilities between the brain and the body. It made some sense to me that the body should be in charge of locomotion, health, and sensory data; while the brain should be in charge of analysis and decision making.

    In practice, I ended up using the delegate pattern to set up a communication channel between the brain and the body, as the brain needs to be able to request sensory data from the body (like current health, and details of creatures or food around it), while also sending the details of decisions back to the body (like current food or creature target).

    On top of that, I started to explore whether using a GKStateMachine would be more beneficial to use over my implementation of an extremely simple state machine. Long story short, at the moment there’s no particular benefit, but it was a fun exercise and did result in some better code organization. In the current generation my AeonCreatureBrain class contains all the methods pertaining to decision making, and every brain has a (largely unnecessary) state machine that uses the states listed in AeonCreatureStates.swift to decide which decision making methods need to be called.

    In the end, this ends up being a larger number of lines total, but has made the code easier to maintain and understand. It’s now much easier to make changes to how creatures make decisions about their behavior, because all the logic is compartmentalized into separate methods instead of being jumbled together into one large series of if statements.

    The Future?

    While I feel like I have done a lot to make Aeon Garden easier to read and maintain, there’s still a long way to go. In some cases the creature’s body is still involved in some decision making, and there’s still code in there I have yet to try to clean up.

    Cleaning up the code base will not only make it easier for me to explore ideas I have for making the creatures more interesting to watch, but it will make it easier to add the features necessary to turn Aeon Garden into a full-fledged app (complete with the ability to save a tank, manually modify tank attributes to engage in your own experiments, save and copy creatures between tanks, and so on).

    I’ve already made many adjustments to how creatures think and interact. In the past I had a big problem with tanks becoming far too homogenous when left running overnight, but some of the changes I’ve made recently have brought about more interesting outcomes, with creatures of various colors managing to survive and forming into natural little groups.

    Here are some screenshots showing how a tank can progress, with a little over an hour of run time separating them.

    Aeon Garden, 1 hour in
    Aeon Garden, 1 hour later

    If you enjoyed this or are interested in trying out Aeon Garden for yourself, please star the repo on GitHub!