Postmortem for "Deduction Quest"
"Deduction Quest" is a rogue-lite puzzle game about deducing the levels of your heroes and monsters that I made for the 7DRL game jam. This is the first game I have made with roguelike elements, and I am pleased with how it turned out.
The concept and inspiration
The idea was an improvement on an idea I had for another game jam quite a few years ago, which itself was based on a delightful series of flash games from long ago called Grow Cube by EyezMaze. In these games you add a new element to a cube each turn, and each turn it will level up and do interesting things. Each element has a different max level, and the game is to figure out the right order to add the elements so that all of them will reach their max levels.
I loved playing these games, and I built "Deduction Quest" around this core idea. In computer science terms, all that the player is doing is sorting two monotonically increasing sequences without any knowledge other than being able to compare elements from each sequence. Luckily this is much more fun than it sounds! Especially when the theme of heroes fighting monsters creates a context and story around the base mechanics.
Another way of looking at it is that each hero is paired with one monster, and the player must find that pair. With a naive approach, this would take at worst n-factorial attempts because for the first of n heroes, there are n choices for pairs, for the second hero there are n-1, then n-2 and so on. So for 3 heroes and monsters, it would name 3 * 2 * 1, or 6 tries at worst to find all of the pairs. For 4 it takes 24 tries, for 5 it takes 120, and for 6 it takes 720. That is a lot of tries! In practice, because the hero and monster levels are increasing, with a little logic and maybe using the bonuses, you can usually figure out all of the levels in around 5 turns, and trying to beat the odds is part of the fun.
The fact that you have to play a battle over and over to figure out the proper order, and the abundance of hidden information seemed to resonate with some roguelike properties, so I pushed further in that direction, adding randomness so that each play is unique, but still gives you insight into how to play better, even if you lose the battle or the whole game. The randomized bonus choices factor into the roguelike concept of using all the tools you have to succeed, and having to choose wisely which tools to use when. I also like how each new lineup of heroes and monsters is like a mini "starting over."
The other concept that excited me about making this game initially was the humorous randomly generated character names. I really enjoyed building the generator and found some of the results quite comical, which gave the whole game a less-serious slant, which works since it also sort of feels like a parody of roguelike/rpg tropes. I was pleased how well a simple random name and random sprite could build up a character in the minds of my test players, and how they would think of them in terms of weak or strong.
The tech
All of my games are browser based HTML5 games. I made this game using Elm which is a pure functional programming language that compiles to Javascript. I like working in Elm because I like the functional paradigm, and Elm provides a pleasant developer experience. Also, the core Elm loop (state + action = new state + side effects) works very nicely with event based games. Another benefit is the elm debugger which lets you inspect that game state at any time and even "rewind time" to replay what happened, which is very useful for tracking down bugs and unexpected behavior. Also Elm guarantees no runtime exceptions (like "undefined is not a function"), which let's me focus on the business logic instead of silly Javascript errors. This comes at the cost of needing to please the strict type system when compiling, and some of the foibles of using a pure language (notably, working with randomness, discussed below).
I used Sass for the styles and css animations, mostly because it was built into the starting webpack template I used. I don't usually like Sass, but it worked well here, probably because the project is small, and the syntax mirrored the game concepts surprisingly closely, like "character cards located in the heroes zone during the hero selection phase should include these extra properties ...". Also variables for colors were very important.
I used howler.js for the audio, which I trigger from "Commands" from the Elm runtime (that is the "side effects" part mentioned above). This separation of game logic and side effects handled by a native Javascript library worked well, and is something I have used in other games before.
Finally, I use my own Elm Narrative Engine which is a tool I built for embedding interactive stories in browser games. It has some interesting properties, like a schemaless world model, custom views, and a declarative rules-matching system for querying and updating the world model. Usually I use this tool on more narrative games but I originally thought it would be useful on this game too. I am about to release a visual editor for the engine on itch.io, so I wanted to test it out in this game. Ultimately, that part didn't apply at all, but I still used the library, mostly as a handy persistence container and query engine. More on this below. Even in this limited use case, it still made the overall development easier, at the cost of exposing some runtime errors due to the string parsing nature of it.
Dev decisions
Elm Narrative Engine
Originally I planned on using the beta version of my Elm Narrative Engine visual editor to populate the game state and author the rules that control the game logic. I imagined writing a little bit of code to import this content and glue it together, and spending the majority of development writing this content in the editor instead of writing code. This fell short right away. The first problem was that aside from my initial concept testing that used a handful of static character names, everything needed to be randomly generated at run-time, instead of created as content to be imported. The second problem was that rules I envisioned writing required more functionality than what is normally needed when writing rules for an interactive narrative game. For example, querying more than one entity in the same rule to compare and update. I considered adding functionality to the engine, but ultimately decided these requirements were beyond the use cases that the engine was designed for, and fell back to writing the game logic in my code directly.
I still used the narrative engine to hold all of my characters after they were generated. While I could have used a different store mechanism, this was very helpful, because it has functionality to easily add metadata to your entities, such as a "fighting" or "defeated" tag. I could then write very simple queries against this state, such as "*.hero.!fighting.!defeated.!victorious" to get all of the hero characters that were not fighting, and had not been defeated or victorious, to render in the hero section. I could also do bulk updates like "(*.fighting.victorious).-fighting" to remove the fighting tag from any character that was currently fighting and had just won the battle. This is how I cleared the battlefield each time. I used queries and updates like these all over the place, including in the bonuses to easily accomplish what I needed.
State machine vs declarative ticks
One of the hold-overs of initially trying to write the game logic in declarative rules in my narrative engine was that I kept the declarative approach. So instead of having a finite state machine that would go between the different phases of play (choose a monster, choose a hero, fight and determine outcome, etc), I query the state of the world and decide what should happen based on that. Specifically, I see if a hero or monster is currently fighting, and how many heroes and monsters are left. If a hero has just won the current battle and no monsters remain, then the player won the round. Or if a monster is fighting, but no hero is fighting, and heroes remain, then the player must choose a hero to fight. And so on.
This was an interesting approach, but had some big implications. I liked the simplicity of only having a single phase to manage, which was my game's tick. I also sort of liked how the declarative approach lined up very closely with the wording of the actual business logic (like the examples above). However, this had a few problems too. The biggest problem was that if I didn't cover all of the cases, the game wouldn't know what to do and would break. A variation of this was that I had to make sure to be as specific as possible and be careful about my ordering to make sure the right case triggered. This was particularly tricky in the edge cases, like if a battle finishes and no heroes or no monsters remain. I had to be very specific to figure out if the player won or lost the battle at that point. I also ended up having to track the current phase in some cases anyways, like knowing when to visually indicate for the player to select a hero, so I probably would have been better off just using a state machine from the start.
Randomization
Generating random content is interesting in and of itself, but in a pure language like Elm, simply doing anything that is random is surprisingly tricky. The reason is that a pure language always gives the same outputs for the same inputs, and randomization would break that. Elm provides two approaches to randomization. The first involves tracking and maintaining a randomizing seed which gets updated each time you use it. The second is to build up a data structure representing all of the random work you want to do, and sending it out of the app boundary as a "side effect" that will use these instructions you created to do the actual random work, and then send the result back to your app as an external input to handle. I did the latter, which meant constructing an impressive chunk of code that included a recursive monad, lots of applicatives, and a few functors, if that means anything to you. It would have been much easier in a non-pure language like plain Javascript, but it got the job done, and I got to feel fancy with my FP powers.
The basic approach to make character names was that I defined a long list of prefixes, primary names, and two sets of suffixes for heroes and for monsters, then I loop through and grab random ones for each character, sometimes skipping the prefix or suffix, then combining them. The tricky part as making sure not to repeat a name once used, which is where the recursive monad came into play. The character levels and image indexes were just shuffled lists of the possible values, which ensured each level and each image would get used once.
Design decisions and iterations
Look
I had the basic game play in mind from the start, and I was able to quickly get a playable text-only version of just lists of static character names with randomized hidden levels to see if the game would be any fun to play. Luckily, that was sufficient to experience the main game loop and hook, and I found it fun enough to continue.
After that was getting assets, which involved finding just about every free character sprite I could on itch.io and chopping them up to fit. The downside of generating logs of characters is you need lots of sprites to choose from, but luckily I found enough, though this process took way longer than getting to the initial playable version. I had always had the idea of representing the characters as cards, which seemed to work well with the basic game play idea. Also, these types of cards usually have a character level somewhere in their design, and I liked the idea of featuring that, but as a large question mark to visually show the point of the game.
The game layout stayed pretty consistent with my initial sketch, though I struggled with the colors for a while, and it was only at the very end of the process that I decided to add the forest background. I think this made the whole game come together by hinting at a setting that fit with what I imagined, and the cool background colors interact with the otherwise warm colors very nicely. Plus I have wanted to use that parallax forest background for quite a while. I do find it sad that I am using only static versions of the background and character sprites, and toyed with the idea of animating them, but didn't get to that.
The other thing that was always difficult was fitting everything on screen without text being too small to read, especially on the cards. I added the preview-on-hover feature to prompt players to focus on the main battle area when trying to read the card text, and I think it works, but maybe it could be improved more.
Game play
Difficulty was always a question for me. Each round gets exponentially more difficult, and I wasn't sure what was too hard. Initially I didn't have any bonuses, so it was challenging to figure out how much of a difference those would make. I added the HP system both as a score, but also as a way to limit how many times a player could replay a level before losing. It also added a sense of small-scale wins and loses to each battle, which keeps it engaging.
I did a tiny bit of play testing and the main feedback was around bringing experience from previous levels forward to the harder ones to avoid just starting over at the beginning of each one. One suggestion was to be able to bring some heroes forward each level, so you have some knowledge starting out. Or hinting at a possible level range for each character. I experimented with these ideas a little, but once I got the bonus system in place, I felt that it provided that function in an enjoyable way.
The bonuses were something I always had in mind, but only added at the very end. Some of them were new ideas, but some of them took features I had already built in, like showing the battle history so players don't have to memorize or write down everything, and turning it into a bonus. This worked out well because it kept the easier early rounds simple. I also like how the player is forced to choose what kind of advantage they want, and when to use that advantage. Some ideas, like always showing the hero levels or revealing monster levels after beating them, seemed like they made game play too easy, but it also made it more fun, so I added them as bonuses, which meant sometimes players would have an easier time than others based on what bonuses were available.
One other piece of important feedback was that players were trying to figure out a character's level based on what they've seen of that character or a similar character before. The levels were completely random, so this was the wrong approach for players to take. I realized that I was repeating some names or images between levels, so I changed my randomization approach to make sure to avoid any duplication for the entire game. I think this helped avoid this kind of confusion, but it meant that I was limited on how may rounds the game could last by the number of names and assets I had. I scrambled to add a handful more assets at the very end to get up to 6 heroes/monsters in the final round.
I made a couple small adjustments after the game jam concluded. Aside from a bug fix and a few tweaks to make some bonuses more clear, the main change I made was to show all the levels of all characters after you beat a round. I think this was essential, as the game is about trying to figure out these levels, and when you succeed, it gives the desired closure or revealing all of the question marks on the cards.
Conclusion
This game has a tight concept and limited scope of play, but I feel like it works very well and in some ways feels like more of a full game than some of my other games. I especially like how the randomization and hidden information makes it fun for me to play, where other puzzle games I have made don't work on myself because I already know all of the solutions. I haven't made a roguelike game before, and even though this is only loosely in that genre, I enjoyed thinking of it through that lens. In the end, I am very pleased with Deduction Quest, and I hope you enjoy playing it.
Leave a comment
Log in with itch.io to leave a comment.