I like audiobooks a lot. My brother Shawn introduced me to them when I was fairly young, around age 12. Back then I used audio cassettes, and I’d listen to them in bed before falling asleep. Now I use my phone, and every single night I fall asleep listening to them.
I don’t fall asleep to books I’ve never read. When I listen to a new book, it has my full attention. But when it’s time to drift away, I have a small collection that I always come back to.
I want to briefly talk about some of my favorite books that, despite having read hundreds of times, never get old for me. These are my “comfort listens”. Before bed, I pick one, and jump to whatever part I’m in the mood for, without concern for a full or linear experience of the story. Often I will even listen to the same part several nights in a row.
I will try and keep this short, as I could go into too much depth about each of these.
I'll start with the first three books in the Myth Adventures series (1978-1982), written by Robert Asprin and read by Jeff Woodman. They’re a quasi-parody of fantasy books, and are quite light in tone and substance. They were all I knew of the fantasy genre, and at the time defined it for me. The first book, Another Fine Myth, may have been my first audiobook. I’ve enjoyed re-reading this tale for over 20 years. I discovered the recordings of the next two books roughly a decade later. These first three are the only books in the series narrated by Jeff Woodman. The way he portrays the mentor figure gives the character an extra dimension of reassuring confidence that always gives me a warm feeling. The releases of the series on Audible are read by Noah Michael Levine, who does a great job, but nothing can replace the Woodman performances that I grew up with.
Next, The Face In The Frost (1969) written by John Bellairs and read by the legendary George Guidall. This is another short fantasy novel with whimsical elements. It also contains some of the most terrifying passages out of anything I’ve read. Even as an adult, I find said passages so frightful that I avoid them when trying to fall asleep. When I do listen to them, wide awake, I’m gripped. I’m captured. I become so involved with the main character’s struggles, that I need to see them through, or the imagery and ideas would sit in my mind—no condition for sleep.
The nightmarish sections don't cast a shadow over the whole book, though. There is plenty of cozy adventure within, especially when the protagonist has his companion along to share his experiences. George Guidall’s grandfatherly timbre perfectly suits the rich and colorful setting and its inhabitants.
The first six books in The Chronicles of Amber series (1970-1985) written and read by Roger Zelazny, are much more expansive and closer to your typical fantasy books. The first book begins with the main character Corwin waking up with amnesia in a non-fantasy setting. This framing allows for gradual world- (and universe-) building as the story progresses.
I can’t quite say why I love these books so much. There are aspects I know I like, like the dialog and Corwin’s thoughts and attitude. The first book has a part near the end that I just love to fall asleep to. When the wheels of fate don’t leave Corwin with many options, I too can resign myself, bringing me to an unburdened, peaceful state of mind.
The whole series is 10 books long, but I’m most familiar the first half of the series that tells Corwin’s tale.
Moving to more modern publications, there's the President’s Vampire series (2010-2012) by Christopher Farnsworth and read by Bronson Pinchot.
What's that? Vampires? Presidents? Why yes, it’s a series about a vampire that has been secretly working for The White House for over 140 years. This is one that my dad introduced me to. Some bits are as campy as you might expect, but nonetheless I have a real affection for Farnsworth's writing style and Pinchot’s narration. I don’t listen to these quite as much as the fantasy novels, but they’ve become a late and permanent entry.
There are some other books I want to mention that I like to re-listen to every now and then. I usually read these more linearly, and continue where I left off each night.
The Accidental Time Machine (2007) by Joe Haldeman, read by Kevin R. Free: The first sci-fi novel I fell in love with. It’s one of those books where I become so invested that I think the characters’ misfortunes might be avoided with each new telling.
Ready Player One (2011) by Ernest Cline, read by Wil Wheaton: This book gets a lot of crap for all the 80s pop culture nostalgia crammed into it, as well as some cringe-worthy dialogue, but I think the story as a whole rises above these faults. The obsessive nature of the protagonist also strikes a chord with me, with his detailed descriptions of his possessions real and virtual, and the single-minded focus he dedicates to his goals.
Life of Pi (2001) by Yann Martel, read by Jeff Woodman: I discovered this while looking for more books read by Woodman. I grew to love the scenario of a character stranded in a seemingly hopeless situation. I’m always a fan of stories where the character is focused on the practical aspects of a dilemma, and the narrative describes the mechanics of their solution in detail. Speaking of which…
The Martian (2011) by Andy Weir, read by R. C. Bray: I saw the film first, and was really hesitant about picking this up. But it’s excellent! Another recording was released on Audible with Wil Wheaton, but despite my love for Wheaton’s other work, I find it severely lacks character.
What If? (2014) by Randall Munroe, read by Wil Wheaton, and How To Invent Everything (2018) written and read by Ryan North: These are not novels, but they are still cozy! The latter in particular, since it does have something of a meta-narrative, in that the book is written as a guide for time travelers who are stranded in the past. I like to imagine that I’m one of those travelers, lying in my time machine and reading this book, as a kind of escapism from the dire circumstances and the weighty responsibility of putting what I’ve learned into practice. At the end of the book, North encourages us to step out of our time machines and begin applying what we have learned, but all I want is to stay inside and let the gentle stream of knowledge go on.
Yesterday I dove into a new 3D modelling program from the creator of MagicaVoxel.
It's called MagicaCSG! It's a "signed distance field" (SDF) editor. I don't fully understand the technical details, but it's a fun and novel way of creating 3D objects using primitive shapes and procedural morphing.
Look, it's me!
You can use metallic materials for some very attractive results.
This is an approximation of a hound-eye from Half-Life.
If you know the significance of this bottle, you're probably a pretty cool person!
Next day update:
Final update: I've created a new Instagram account for my digital art! https://www.instagram.com/vegeta897art/ For now my obsession is MagicaCSG creations.
Yesterday morning, I submitted my game at the last minute. I was able to execute the core mechanic, but just didn't have time to make anything very interesting with it. But it is a "complete" game, in that you can win and lose.
What you see in this gif is the best it has to offer. It’s a typical roguelike but with rails that you can grind on to obliterate skeletons. If you get to the end of the dungeon, you ride the rail all the way back to the start and that’s the end.
I started toying with the rail concept the night before. In the hours leading up to the start, I was expanding upon my starter repo (mentioned in my last post) and created a private itch.io page for it, just to become familiar with that process, and make sure my technology stack worked fine on Itch. Eventually I reached a point where I felt doing any more work on it wouldn’t be in the spirit of the jam.
This is what I had before I started the clock. I was just familiarizing myself with rot.js’s dungeon generation. There was no collision, FOV, or anything else except a camera that followed the player. The sprites are from Kenney’s 1-Bit pack, and are colorized with Pixi’s sprite tinting.
So I created the new repo and began working immediately. It was 8:32 AM on Saturday. I think this was my first mistake. I rushed into things when I had all of that day and the next to refine my gameplay concept into a fleshed out game. There could have been some benefit to diving in like I did, to start playing with ideas immediately, but I don’t think it was worth it.
Two hours in, I had my Level class that stored the wall/floor tiles, and an FOV system that used rot.js’s shadowcasting. This function returns a distance from the cast point which I used to make further tiles less visible.
Here’s a playable build: https://pixelatomy.com/dungeon-grinder/dev/1
By noon, grinding rails was in the game. This was when I really started wondering how I was going to make the rails into fun gameplay. Where do I put the rails? How long should they be? What do they do for the player? What is the challenge?
When grinding, all input is ignored and you’re just along for the ride until you reach the end of the rail. It’s effectively the same as any other single tile movement. However, to program this, it made more sense to tick the game loop for each rail tile traversed. The update loop is the backbone of the entire game and it would be pretty hacky to get the systems in it to run for each rail tile if the whole rail was only one tick. So the game auto-ticks while you’re on a rail, and that’s just fine.
Here’s what I had before going to sleep for the rest of the day: https://pixelatomy.com/dungeon-grinder/dev/2/. I actually hadn’t added the input blocking at this point, so if you spam the movement keys while on a rail it cues up more auto-ticks and you go super fast.
I got up at 10pm that night and put in a couple more hours. This is when I began using the tween.js library, which is a brilliant tool. It was so easy to just apply it to anything I needed to, and not have to worry (much) about side-effects. That’s a very good thing to have when rapid prototyping.
Behold the tweening: https://pixelatomy.com/dungeon-grinder/dev/3/
Sunday was spent restructuring systems, improving tweening, and touching up the graphics. By the end of the day I had something that resembles the final product: https://pixelatomy.com/dungeon-grinder/dev/4/ This says a lot about how much time I wasted not making a game for the next 5 days.
I soon began worrying about what the game was going to be. I needed to start brainstorming, so I pulled out my notepad.
My first impulse was puzzle mechanics. Sets of rails that you need to cross to advance in the level. Follow the rails with your eyes to figure out which one is the right one. Slide rows of tiles to line up a rail that leads across a pit.
This seemed a little too complex for me to be able to pull off in a week, and none of it was exciting to me.
What if grinding on rails increased your “heat” meter, and this made you more powerful in combat? This sounded more fun, but in the following days there was enough engine development struggle that I never got close to having the time to do any of this.
In desperation, I read some articles about how to “follow the fun.” Was there fun in my game idea to follow? I mean, grinding on the rails was kind of fun. I was really trying to figure out what it was that made it fun, so that I could expand it.
That kind of never happened, but I did come up with a level structure that seemed achievable.
The level has one long rail running through it, but it’s missing tiles. You have to fight enemies to get the pieces to fix it up. Then you can grind it all the way to gain enough speed to ramp up to the next floor.
But before I even started on that, I distracted myself further with rot.js’s maze generation algorithms.
The mazes seemed like a fun way to generate random arrangements of rail that I could use to spice up rooms in the dungeon. Here’s a build with them: https://pixelatomy.com/dungeon-grinder/dev/5/ (Hold shift for debug movement)
This is when I added the grinding particles. It was another very easy-to-use library called pixi-particles. I figured that if grinding was the main thing in my game, it ought to look pretty.
I got back on track (ugh) and began writing the code for generating the dungeon itself. It would be structured around the long rail that you had to repair (the “mainline” as I called it in my code). I wrote a loop that recursively added segments of rail to the track with random lengths and directions, and each segment had a room of random dimensions and offset position.
The segments are colorized here for easier debugging. You may also notice that the rails sprites are wrong too. This was because I had to change the underlying data structure for rail connectivity to suit the new generation algorithm, and had not gotten around to updating the texture-picking code to interpret it.
Around this time was when I decided that grinding should have diminishing momentum instead of being infinite. I decided that it would be frustrating for the player to accidentally start grinding on a long rail that they didn’t intend to. That was only fun at the end when it took you to the next level. I also added “sneaking” with the shift key to walk on rails without grinding.
It wasn’t until Wednesday morning that I started working on a basic enemy entity.
And that afternoon:
My update loop was a mess. I didn’t know what order my systems should be in, but what I had was constantly giving me unexpected results that were laborious to debug. Adding features to it, like attacking and following, was painful. In roguelikes you need to have a very rigid execution order for player and enemy movement and attacking. In retrospect I should have nailed those two things down on day one. I cringe now at how much fluff (or rather, time) I put in the game before tackling that.
Thursday morning was spent overhauling the update loop. I was seriously feeling the pressure after that. Two days left and I’m just now getting my loop in order! Ahh! The rest of that morning, I focused on the things I needed to make the game a game. Start with health, and a HUD to show it. Adding that went fairly smooth, which made the overhaul feel worth it.
Next came ending the game when your health reached zero. Easy enough, but restarting the game with a new level was a terrifying idea. Would my code hold up after clearing and resetting all the data during runtime? Surprisingly, it did. There were a few bugs with the camera and FOV systems but they weren’t too difficult.
Here’s a build from Thursday afternoon: https://pixelatomy.com/dungeon-grinder/dev/6/
Friday morning, one day left. Hey you know what would be a good idea? Redoing the entire rail gen algorithm and adding a new merging-rail type that I spent way too long writing trial-and-error texture code for! I also had to redo the data format for determining where a rail tile grinds from and to to handle these merge rails! And the final game only uses them in two places!!!
But it wasn’t all bad. The next thing I did (that afternoon) was creating the “tutorial” room, which was one of the most fun parts of the jam for me. Pen and paper weren’t good enough because I needed to be able to easily revise the layout as I went, so I used Aseprite.
The goal was to gradually teach the player about how grinding works and what the “rules” are. I like to think that it teaches you these things, in this order:
Rails can be grinded on
Grinding has a limited distance, and you can’t control that.
Grinding will follow the course of the track.
Grinding can begin on a corner piece.
Grinding can begin on a corner piece that isn’t open on the player’s side
Grinding will not begin if you’re perpendicular to a rail
There are other things in this dungeon, and you can attack them
These things can be grinded through
Grinding through things is fun
The shift key lets you move on rails without grinding
Bonus: Remember that room you thought you couldn’t get into?
I never had time to put anything in that secret room, but oh well.
After I had the design, I had to put it in the game. It would be way too hard to do it with hard coded coordinates, tile by tile. ASCII to the rescue!
Even writing the loops that turned the characters into tiles in the game was fun. It was a great moment to see it in the game for the first time. It played exactly as I imagined when designing it.
Adding the dummies was easy. They’re exactly like the enemies but with a different sprite and no “follow” component. Yay ECS!
I worked until early evening that Friday. This was when the realization came that having multiple levels was not worth the work required, especially when I had no plan for how to distinguish them other than upping the enemy count. Unfortunately I spent a good couple hours struggling with handling multiple levels before making this decision. One of the main struggles I had with that was a design struggle: I wanted it to begin with you grinding in the same direction you were grinding when exiting the previous level, but didn’t want you to feel like the end of that level was taking you “back” to the previous level when you reached the end booster that took you back. Bleh.
The YOU WIN and DEAD screens were created like the tutorial room.
These were actually fun distractions from a task that was more boring but necessary, which was adding the little merge tile after the long start rail which diverged you away from the rest of the dungeon. Without that, you’d grind all the way to the final booster and that would push you right back to your guaranteed win. It’s too bad that the game did end up being just as easy as that, because you can stay on the rail the whole time as you go through the dungeon with practically no consequences other than dying of boredom.
I woke up very very early Saturday morning with about 6 hours to go. That’s when I added the above-mentioned diverging rail. I also decided to tweak the dungeon/rail generation more. It was too tightly coiled. I had to add in logic that made it prefer moving in one direction.
I am actually a little proud of the generator. It’s capable of some really intricately knotted rails without that directional bias making it avoid that.
The last hour was frantic and stressful. I rushed some cover art even though I could have done that after submitting the game to Itch. I didn’t even need to make the page on Itch before the deadline, as long as I stopped developing the game itself. But I really didn’t have the time to add anything substantial to the game anyway.
After submitting, I was a little sheepish about sharing it with my friends. If I went back in time 2 weeks and gave myself the game I made, my past self would have been pretty underwhelmed. I know this sounds negative but I’ll get over that, and be left with what really mattered. It was 100% worth the experience. The entire week I felt so focused and productive, despite all the struggles. There was never a time where I woke up and felt like going back to sleep. I was jumping out of bed every day. I also got some biking in when the weather was nice, which was a fantastic stress reliever.
I feel better equipped as a developer and designer. I thought I understood the importance of attaining a minimum viable product ASAP, but nothing teaches you that like actually trying to do it. I hope to participate again in next year’s jam.
To my own surprise, later that day I already felt like making another game, despite being exhausted. I want to chase that goal of completing a game in a short time.
This weekend I will begin my first ever game jam, the 7 Day Roguelike Challenge.
I just spent a couple hours putting together a project starter that is set up with webpack, typescript, prettier, and itch.io publish scripts. I plan to use rot.js, ape-ecs, and probably PixiJS.
The motivation behind this began when I saw this tweet from a developer I follow. Even though that starter template won't end up being made in time for the jam, it was enough to make me realize a game jam is exactly what I need.
I've never really finished a game before. Most of what I've made can barely be called games. Now I have motivation and a timeframe to put out a game, however simple (or even bad) it may be.
I haven't decided on an idea yet but I still have time; I am allowed to start as late as Sunday night. The starting time I pick must also be the ending time in the following weekend.
I know that I want to keep my idea as simple as possible and prioritize making it playable before anything else. That's the advice given here and elsewhere, and I will try my best to follow it.
My vague plan is to start with the basic Rogue format and add a twist to it. I found the replies in this thread to be intruiging. But even those ideas may be too ambitious to design and develop for my first roguelike and first jam.
I don't plan on blogging my progress during the week since I want to minimize distractions as much as possible. Maybe I'll just post small notes. I enjoy recapping all the things I've done in a day but I should try to avoid spending that much time during the jam and save it for the end. Even writing this post feels like too much time spent when I should be planning. See you soon!
I have finally completed the animation I started 9 months ago. It’s about my friend being stranded in the woods. It’s almost based on a true story and the audio comes from actual videos of his ordeal.
Notice I said “started 9 months ago ” and not “worked on for 9 months.” There were long periods of inactivity. But I did know the whole time that I would finish it eventually. Unlike many projects of mine! I am very excited to premier this to my friends (and some family!) tonight and feeling very fulfilled.
It started with my friend Milly (Mills) posting several short videos he recorded while waiting for a towtruck to pick him up when he got his motorcycle stuck in the mud.
Something about the videos and his monologue made me want to create an animation based on them. So I cut and stitched various parts together in Renoise, and came up with the whole audio track.
The principle of my workflow is to get the entire audio track, including music, finalized before ever picking up my pen. Timing and pacing scenes by audio only is a good way to get it right, I think. If I can make something that flows just by listening to it, it’s a good bet the final animation will flow too.
With the audio track exported, my first step was storyboarding. A quick sketch for every scene or cut, sometimes with arrows or rudimentary animation to illustrate the intent. It’s during this storyboarding that most of the ideas were established. I really enjoy how I have to be creative to come up with visuals to match an audio track that was created without much regard for what it would all look like.
Here’s a rendering of the animation with the storyboards overlaid on top:
Holy heck are there a lot of cuts in this animation. It feels like a shot is rarely held for more than 5 seconds. I could go and count exactly how many shots there are but I’m lazy.
So I had the storyboard and audio track for an animation that weighed in at about 4.5 minutes, spanning almost 4000 frames. My New Years animation was longer, but that was barely animated. This was definitely going to be up there in terms of work required.
My next step was drawing in very angular outlines for the backgrounds. The triangular/polygonal style in the final product didn’t come until much later. I really didn’t know what I wanted the backgrounds to look like, and this loomed over me for almost the entire time I spent doing everything else.
I was itching to get down to the raw animation work, so I began taking on scene after scene of character animation.
The flat-color-with-no-outlines style was a choice I made quickly when I considered how much extra work it would be to give it my usual treatment. It was also refreshing to try something new.
The animation is 15 frames per second, but many parts were done at 1/2 or 1/3 rate (twos and threes, in animation lingo). Sometimes the decision came down to how "in the mood” I was to animate at the time.
Guides are important. A lot of scenes were free-handed though. When you draw a character enough times you get pretty good at it.
When I crack open a Rockstar, you know it’s time for some serious animating.
With most of the character art done, I moved on to the motorcycle. Oh, that friggin’ bike. A Benelli TnT135. Even after studying literally dozens of photos and videos of it, it’s still a tricky 3D shape to grasp at some angles, particularly the tail end of the seat.
And then I had to animate it in that penultimate shot of our hero driving away. I shamelessly took as many shortcuts as I could to avoid as much redrawing as possible. The end result is okay. I hope the significance of what the bike is doing means the viewer isn’t focused on how realistic it looks.
At last I had to face the backgrounds. My first attempts failed miserably.
So, Adobe Animate does not really have great tools for coloring (by hand, anyway). Trying and failing to come up with a workable style was discouraging. Forests have a lot of variety in texture, all around. There’s all kinds of colors and shapes. How could I convey all that?
Go abstract.
No, Adobe Animate does not have some cool 3D mesh feature or fractal generator. These triangles were drawn line by line, fill by fill. That includes the animated water.
I managed to re-use some backgrounds in many of the simpler shots, but some locations, like the bike and road, had angles too varied to copy and paste.
I had determination, though, because working on these backgrounds was part of the final stretch of getting this thing done. I could see the finish line.
When the backgrounds were done, I made pretty quick work of drawing and animating the various props. Basically anything that wasn’t the character or bike, like the log, the can, the foil cup, and so on.
The last few days were spent creating the title and credits, as well as polishing stuff like the color correction used to illustrate times of day. Yesterday I sent a preview of the whole video to my good friend Viper, whose critical yet supportive feedback I value the most. I must say, his generally positive response helped me sleep that night. The night before, I was tossing and turning, stuck in a mind loop of drawing triangles in my imagination. That is not a joke.
Thanks for reading! After keeping this mostly-secret for so long, it’s nice to get it all out there.
Another patch I made on the Hydrasynth. Just started with experimenting and noodling, and ended up with this. It’s got a vintage synth vibe to it, doesn’t it? The Hydra is really good at doing these complex and harmonically rich sounds. A simple trance beat felt like appropriate backing.
Here’s the full video. I tried to embed but tumblr kept grabbing the wrong video (???)
Inspired by Akasha System, I tried putting together a minimal house jam. Dark, short chord stabs are the order of the day. The majority of the synths sounds are sampled right from the Hydrasynth. The 16n Faderbank controls filter and EQ on the ZOIA drum bus.
The drums and percussion lack the punch and cohesion I would have liked. I’d like to do more jams like this and improve.
I received this last week but have been neglecting it until today. After hearing another amazing demo of the UDO Super 6, I wanted to see how the Hydrasynth could compare in stereo dynamics. I ended up with this patch, and recorded a quick demonstration of some chords that were probably very unintentionally lifted from Interstellar.
Creating the patch was really easy, even with my lack of experience on this particular device. Things were in obvious places and I only had to reference the manual a couple times. It was also just plain fun at every step. I only used 3 LFOs, 2 envelopes, 2 mutators, and some extra routings for velocity and aftertouch. Static oscillators, too. There's so much more sculping and modulation possibility I've yet to tap.
Long time no blog. This year I've upped my Instagram jam output. I've acquired more hardware and I'm doing 1-2 hour sessions just to upload short clips. Though lately I'm recording full jams for Youtube too.
I also started working on an ambitious animation in the middle of this year that I was hoping to finish before 2020 but I'm not so sure now. By the way, Animate CC has been getting cool updates.
I put off this remix request from Mary long enough to make it into a christmas present. I used samples from Otis Redding’s The Dock of the Bay along with live sequenced TR-8S and recorded riffs from my new MODX, compiled together in Renoise.
I am happy with how it turned out! Deciding how to approach this kind of remix was difficult, and there were several false starts. When I first played the synth line that comes in at 0:40, I knew I was getting somewhere.
Programming the drums was a whole lot of fun. I modulated parameters like snare decay and kick tuning, see if you can hear them changing. The 808 sounds on the TR-8S are just so good that they didn’t need any more processing to fit in the mix.
The rhodes patch, along with the synth, come from the MODX. They’re both excellent. I spent almost 2 hours playing along to the original track on repeat with different patches, both to find good chord progressions as well as sounds. The rhodes is really special with the physical key sounds and the warmness, so I had to give it time to shine in the intro. The synth patch was originally polyphonic but I switched to mono for a clearer melody. The way the filter opens up on it is really dreamy.
My sister is creeped out by deer, so I helped her face her fears by creating this poster as a christmas gift.
You can buy a print of it here https://www.deviantart.com/vegeta897/art/Christmas-Deer-776106829
I made it in Animate CC and got a framed 20x30" print from DeviantArt. It was somewhat rushed; if I had a few more days to work on it I would add some details and perhaps improve the background.
VocalSynth 2 is a new voice effects processor that can do all kinds of crazy stuff. I made 3 tracks in 3 days with it, mangling voice recordings of my friends and backing it with drums and chopped up synths.
https://pixelatomy.com/stuff/the-wall.mp3
https://pixelatomy.com/stuff/chitin.mp3
https://pixelatomy.com/stuff/swamp-trax.mp3
The last track is a throwback to my faster breakbeat days. I have a tendency to get louder and noisier when working at that tempo and energy level. Some time I’d like to attempt keeping the mix cleaner, and/or try a more minimal style.
My trial of VocalSynth is over now, but I may purchase it later. They offer a cool rent-to-buy plan where you pay $10/mo until you own it, and can cancel or resume payments at any time.
I started a large drawing last month but haven’t worked on it in a while. I may post some WIP previews later.
I've been doing some drawing lately, mostly to finish up a Discord bot feature that I abandoned years ago.
It’s a comic strip generator that uses actual conversations from our chat history.
Everyone has their own custom drawn character with several state variations (alone, listening, talking, etc) and are programmatically placed based on who’s talking, or who was talking. In the above strip, even though Viper never says anything, he’s there because he had said something previous to where the comic begins, and thus is part of the conversation.
I programmed all this years ago, but lack of artistic motivation to draw all the characters and their variations prevented me from finishing it. I’m glad I finally did, because people really like it and it’s been a great source of laughs.
Sometimes you just get a monologue:
Can’t leave Taco out of this post:
Of less interest, I also created a unit conversion command by request from Milly.
It uses the convert-units module. You can see the code here if you want. This module is very strict about unit names, so I added a bunch of aliases which allow e.g. “miles” instead of only “mi”. I also do some rounding based on how many whole digits the final result has. If it’s less than 1, then 4 decimals will show. If it’s less than 10, 3 decimals will show. And so on. This seems like a good method of providing reasonable accuracy.
Oh yeah, and I tried to make dealing with our diverse timezones easier.
You can assign yourself to the appropriate zone:
See the code here!
That’s about all. I’ve made some big music hardware purchases, but do not yet have any new music to post. Hopefully soon!
As you may know, I maintain my own bot in a small Discord server with my friends. I enjoy adding functions to it because they’re like mini-projects that I can knock out in a couple hours or less, and I can get instant gratification from seeing people try out the new features.
Over the past few days I was working in secret on a special one-time chat-based game, to be played on April Fools. I called it a riddle, but it was really just a series of Half-Life trivia questions accompanied by images.
The twist was that there were special rule modifiers that randomly activated during play, and every time someone said the wrong thing, the riddle would reset to the beginning.
It was meant to be frustrating, but I knew my audience and they were up to the challenge. It was immensely entertaining for me to watch their struggle, as well as gratifying to have made a game that kept them trying repeatedly to win. After 25 attempts in just as many minutes, they made it through the 16 questions without messing up. I only had to fix a single bug during play, too.
Development of the game was slow-going at first, because I wasn’t sure how to design the game. All I had decided was that there would be a series of questions, but I wanted there to be more to it. I tried coming up with ways of requiring cooperation among players so that one person couldn’t just run through it. I eventually abandoned that pursuit, and it worked out fine in the end simply because I made sure enough people were around to play when the time came.
When a player sent a message, it would be compared against the current stage’s answer with fuzzyset.js. If it was at least 90% similar, it was considered correct. This allowed minor differences in punctuation or spelling.
The random modifiers came in 2 flavors, answer modifications and chat rules. Answer mods transformed the actual text of the questions, and required that the user type their answer in the same style. For example, if the question showed up backwards, you had to answer backwards. Chat rules were arbitrary restrictions on all non-answer messages, like banning the usage of a letter or requiring capitalization.
The definitions for these were set up in a way that would easily let me add a bunch, but unfortunately I couldn’t think of very many. Here’s what the backwards answer modifier looks like:
The transform function is applied to the question and the answer, and submitted answers are checked against the transformed answer. validGuess is for any special checks that might need to be run on the answer to consider it valid, aside from the fuzzy string matching. This was necessary with certain answer mods like all-caps. The fuzzy matching ignored case, so it considered "THIS ANSWER" to be the same as "this answer". The validGuess function can check that the submitted answer actually was in all-caps.
validGuess: (a, g) => g === g.toUpperCase()
The chat rules were defined in a similar manner, but some required more complexity. The forbidden letter rule worked like this:
chatRules.set('banned-letter', { init: d => d.letter = util.pickInArray([ ...'abcdefghilmnoprstw' ]), message: d => `Don't use the letter ${d.letter.toUpperCase()}`, violates: ({ message: m }, d) => m.toLowerCase().includes(d.letter) });
Since it picked a random letter, there had to be some data persistence so it would know what letter to check for in messages. The d variable holds this data. The rule was initialized with init, which picked a letter and saved it to d.letter. The description of the rule was generated with message which is able to reference this saved data. violates is the function that checks for a violation of the rule.
Here’s a gist of the entire mods file.
Speaking of data persistence, I rewrote my custom JSON storage module during this process. The entire game’s state is kept in a JSON file so that the bot could be rebooted at any time without breaking or restarting the game.
Some of these properties ended up unused due to iterations on the rule design.
My storage module used to work by just providing the data object and and a save() method. This felt redundant because there would never be a time when you want to change data without saving. So instead of making modifications directly on the object, I added set(key, value) and get(key) methods, like a JS Map. When writing data with set, the module internally calls the save() method.
This made certain kinds of data changes a little more complicated. For example, incrementing a number had to be done like this: data.set('stage', data.get('stage') + 1);. Manipulating arrays or child objects was awkward too. So I created a transaction method that provided the value you want to change, and let you return the changed value to be saved. Now incrementing looks like this: data.trans('stage', s => s++).
Most of the game’s code is kind of messy, so I don’t have much else worth showing. Here’s a gist of the whole thing, in case anybody out there is curious. The 2 entry points are the listen and tick functions. The former is a listener that handles messages sent in Discord, and the latter gets called every 1 second to handle time-based logic.
All the testing during development had to be done by me alone, in a hidden channel. A lot more could have gone wrong when it went live, and I’m very lucky it did not, since my complex bot commands usually are full of bugs when I think they’re ready for deploying. Not only were there no game-breaking bugs, but the players really enjoyed it. I couldn’t be more happy and satisfied with the end result.
I’m certainly considering expanding on the format with a new set of questions and some changes on the mechanics based on what worked well and what did not. Just like a real game developer doing a play-test session!
I know it’s trite to thank people for reading, but I am sincerely grateful to anyone who gives my writing any attention. Leave a comment 😊