Welcome to the technical devlog of Wondering Witches!
In case you haven't played it yet, visit Wondering Witches (Game Page).
Read the rules, gather some friends and play yourself a fun little game :)
What is the game about? It's a cooperative, logical puzzling boardgame which only requires you to grab a single piece of (empty) paper. The game takes place on this paper, but there is an important digital component.
The idea of the game is that, through experimentation, growing the right ingredients, and logic deduction, you must discover the secret recipe!
What's the digital component? When you go to the official page, you can press a button to generate a random starting setup. Scroll down and you can press another button to generate the actual puzzle.
This website, even though it's really simple and only a few button clicks, contains a LOT of ideas and algorithms to make the game experience as smooth and exciting as possible.
In this article I'll explain how I created this "online component" of the game. I'll also share why I added this component and the things I learned along the way (whenever necessary).
Remark: I also wrote a more general devlog about the game design process. It's rather long, but if you are interested in boardgames or game design, I'm sure you'll find it interesting: [Devlog] Wondering Witches
Remark: of course, the explanations and code samples here are only a small simplified fraction of the full code. You can visit the website and check the source code to see exactly how it all works. (I apologize in advance for pieces of code that are messy, unoptimized or weirdly structured. I'm an "if it works, it works" kind of guy. I do, however, comment and space out everything neatly.)
The website has two distinct elements: the board generation and the puzzle generation. (In fact, you can check the source code and see that these algorithms reside in two separate files.)
Board Generator: this designs a random layout (based on player count and difficulty) of cauldrons and gardens, adds some special cells and ingredients on top, and draws this to the screen.
Puzzle Generator: this generates a random puzzle and gives you an interface to test your solutions.
A "puzzle" in this game is a secret recipe. Some ingredients are good, some are bad. The good ingredients get a number, and you must put them in the correct order. The bad ingredients try their best to mess with your puzzle, by obscuring the results.
Testing a solution simply means that you input a set of ingredients, press "Use Potion", and it gives you feedback about the results. For example, if two of the ingredients had a number that was too low, it reports "2 undergrown ingredients".
I'll talk about the Puzzle Generator first.
This is by far the simplest part of this system! Which is why I wanted to start here.
A potion is represented by a list of ingredients, an array if you wish.
The order matters a lot. To win the game, ingredients need to be in the correct order. (Ingredient with number 2 must come directly after number 1, for example.)
The computer simply loops through the list from start to finish, checking the numbers and executing any effects of each ingredient.
Because of this linear structure, I needed to be very careful about what "effects" could do. Once an ingredient has been checked, it's never checked again. Which means I cannot change anything in a potion that comes BEFORE my current ingredient. I can only change stuff that comes after it.
What does this mean? An effect can only ...
Report the results from ingredients BEFORE it.
Alter the values of the ingredients AFTER it.
"Report" example: the Hugger effect reports whether the ingredient BEFORE itself has a value within a range of 1. (So if the Hugger has value 6, and the one before it has value 5, it will report yes.)
"Alter" example: the Spicy effect changes the value of the ingredient AFTER itself by 1. (So if the element after Spicy has a value of 3, it's now changed to 4.)
By implementing the effects this way, I ensure that there are never mistakes or inconsistencies about the results.
But, why not change the loop? Why go through the potion linearly? Because of simplicity, both in code and in human deduction. This constraint made the game much easier to play and grasp, and the code much cleaner.
(That's usually how it works: a creative constraint should be viewed as a good thing you can use to simplify your piece of art. Whatever that may be.)
Of course, there were some ideas I had to throw out, but it was a trade-off I could live with.
After several playtests, I noticed something annoying:
Effects were evenly distributed (intentionally), but this made the game significantly easier. If you knew an ingredient could have, at most, one effect, it became much easier to solve that puzzle.
Effects were picked completely at random. This meant that there were games where all effects had the same type, which made the game way less fun changed the dynamic too much.
The first problem was fixed by distributing effects completely randomly. On average, the distribution will be somewhat fair, but it could happen that one ingredient has 2 or 3 effects, while others have none.
The second problem was fixed by splitting the effects into categories. Instead of one giant list, I made several lists ("investigative effects", "change cauldron effects", etc.)
First, it grabs one random element from each list. If that is not enough ( = we need more effects), it concatenates all the lists (turning it into one huge list again) and picks randomly from that.
This ensures we have at least one effect of each type, but also keeps some randomness.
Here's the most important thing: I shuffle this array. In the first implementations, I forgot to do this, which meant the order of feedback gave away which ingredients had which effects.
Implementation Remark 2: each effect is thrown through a huge switch statement in the code. It's perhaps not ideal, but it was a very fast and clean way to implement many different effects.
However, there were some "direct effects" that had a delayed execution. For example, the "Enthusiastic" effect means you skip the next ingredient. This effect needed to be remembered and only applied at the end of a loop iteration.
For this, I also added a list of direct effects to that object I described above. At the end of each loop iteration, I check for these effects and handle them accordingly.
This devlog continues at part 2: Devlog WW (Part 2)