Nikoloz Otiashvili
ნიკოლოზ ოთიაშვილი

Queue Queen Devlog

I'm going to be setting tighter limits on myself for this game. Make it quick, make it small and release a playable demo to Steam before the Summer ends. The demo should be 1 month away from a full release, so it will reflect the finished game very closely, with limited content, of course.

Day 1 - Stack

Technically, it's my 7th day of work on this game as I've been scrapping and remaking designs for it for a while now, but I've finally arrived at a design that I'm satisfied with, so here it is.

Stack

You draw a card and make a decision: place it on your queue or discard it. Then after you fill the queue or run out of cards or the time runs out, each card is scored individually based on your items (not shown in the video).

You acquire items through the shop. Each item is a dynamically generated "rule" which gives you points when a condition is met. For eample:

If you go above the score threshold, you get to the next round, if not you restart the run. Then add in a shop, score progression and... we have a game. It's definitely inspired by Balatro and Nubby. I'll also steal the NG+ system from SNKRX, another good one. I absolutely admire the devs of all three of those games.

Let's see how this game pans out.

Day 2 - Eval

Figured out how the logic for evaluating conditions and applying item effects will work. In short we have:

  foreach card in stack do
     foreach item in inventory do
        if condition_true(item.cond) then
           apply_effect(item.effect)
        end
     end
  end
and an item like this:
  {
     effect = "plus_const_points",
     effect_arg1 = 1,
     cond = { "upper_card_value", "<", "this_card_value" },
  }
will grant +1 point every time a card is evaluated that has a greater value than its upper card. The video shows how that works:

Eval

Also added woosh in/out animations and sfx (temporarily borrowed from my previous game) to each of the cards. Looks acceptable.

Day 3 - Multiple Items

Rewrote the eval loop using two functions that recursively call each other through a delay queue. The previous naiive solution was too limiting in terms of timing control. Now I can adjust the delays between animations as I wish.

Most importantly, we now have multiple item evaluation. Our testing inventory now looks like this:

inventory = {
   [1] = {
      pos = 1,
      effect = "plus_const_points",
      effect_arg1 = 1,
      cond = { "upper_card_value", "<", "this_card_value" },
   },
   [2] = {
      pos = 2,
      effect = "plus_ref_points",
      effect_arg1 = "last_scored_card_value",
      cond = { "this_card_suit", "==", "last_card_suit" },
   },
}
So, every time we evaluate a card, the two items activate in order:
  1. If its value is higher than the value of the card above it, gain 1 point
  2. If it has the same suit as the bottommost (last) card in the stack, gain points equal to the value of the last scored card
So if our stack looks like this:
3 of Hearts
5 of Spades
Jack of Hearts
10 of Spades
6 of Diamonds
10 of Diamonds
Our eval will look like this:
  1. Item 1 grants 0 points from the first card (3 of Hearts) because it doesn't have a card above it, so it can't be larger than it
  2. Item 2 grants 0 points from the first card (3 of Hearts) because it doesn't have the same suit as the last card in the stack
  3. Item 1 grants 1 point from the second card (5 of Spades) because it is larger than the card above it
  4. Item 2 grants 0 points from the second card (5 of Spades) because it doesn't have the same suit as the last card in the stack
  5. Item 1 grants 1 point from the third card (Jack of Hearts) because it is larger than the card above it
  6. Item 2 grants 0 points from the third card (Jack of Hearts) because it doesn't have the same suit as the last card in the stack
  7. Item 1 grants 0 points from the fourth card (10 of Spades) because it is not larger than the card above it
  8. Item 2 grants 0 points from the fourth card (10 of Spades) because it doesn't have the same suit as the last card in the stack
  9. Item 1 grants 0 points from the fifth card (6 of Diamonds) because it is not larger than the card above it
  10. Item 2 grants 11 points from the fifth card (6 of Diamonds) because it has the same suit as the last card in the stack (10 of Diamonds) and the value of the last scored card (Jack of Hearts) is 11. SIDE NOTE: the values of the face cards start from 11. So Jack is 11, Queen is 12 and King is 13. Oh, and Aces are 14.
  11. Item 1 grants 1 point from the sixth card (10 of Diamonds) because it is larger than the card above it
  12. Item 2 grants 6 points from the sixth card (10 of Diamonds) because it has the same suit as the last card in the stack (10 of Diamonds, itself) and the value of the last scored card (6 of Diamonds) is 6
So, that's 20 points. The video below shows that specific scenario play out.

That second item is a bit complicated, but already I can see myself planning and calculating the order of the cards when testing the game. Initial sparks of a potentially fun game?

Multiple Items

Days 4 and 5

Took the last 2 days implementing the inventory UI and the Stack/Skip buttons. The changes are mostly visual, so the video will tell more than anything I can express with words alone.

Inventory and Buttons

Days 6 and 7

Juiced up everything, almost finished with the Shop and ironed out some inconsistencies. I'm going for a style where everything just reacts to mouse hovers, so that just screwing around with the UI feels good to the player.

I wanted to have a complete game loop with a fully functioning shop by day 7, but it seems I'm late by one day, as I think all the remaining stuff will only take 8-10 hours of work, so I'll lock in hard tomorrow. When I mispredict the timeline of a project, I usuall miss by double or triple the time, but this time just a single day delay, so I'm only off by 14%. Rare.

Inventory and Buttons

Thankfully, there will be no content grind for this game, as all the items will be dynamically generated. The significant challenges ahead will mostly consist of playtesting and balancing: determining the required points for each round, gold rewards, time limit, NG+ progression (probably just +1 item slot, +1 deck size and x10 required points per NG+) and whether to add more mechanics (like buying/generating/copying/destroying cards in the deck that persist throughout the run).

Shop item generation is simple stuff, but what's difficult is pricing the items. I need to be able to evaulate a randomly generated item by its usefulness so I can price it appropriately.

I'm thinking of lerping between a range of $1-$20 based on the rarity of a generated item. That's still not the perfect solution for a few reasons. Some items may be largely useless and rare at the same time, so their price will be too high. Some items may be completely useless and will just take up space in the shop, forcing the player to reroll a bunch before he can get to the good items. Some common items may make for overpowered combos, but because they're not rare, they will be cheap and not priced according to their potentiality for strong synergies. And so on.

I can price OP items individually, but that may be an extremely time consuming task. There are thousands of possible items already and millions of combinations - no way I can price even 1% of them, unless I use some scheme for pricing a range of items.

Maybe I'll have a base price and add to it depending on the raw numbers in each item. For example, an item like gain current gold x2 points if last card suit == "Hearts" should cost less than gain current gold x3 points if last card suit == "Hearts". The random generator picks a number from 2-10 in this scenario, so we'd add more value based on how high that number rolls etc.

Overcomplicating

Now, I think I need to remark on an extremely important point which I've learned while working on both Gnomber and Glass Cannon: all of this balancing scheme and algorithmic solution pontification is 99% bullshit. The pricing schemes above may seem good on paper, but my spidey sense is telling me that all of this theorizing is masturbatory.

If you're experienced in game design, you know where I'm going with this, but if not, prepare for a bitter pill. Doing all this complex algorithmic balancing stuff is probably overcomplicating and overengineering.

I made this mistake for Gnomber when I spent a week on making a spawning scheme for a single level which would dynamically spawn more/less creatures based on the current time or the player's kill count and many other factors. Ideally, with some tweaks, it would also work for all the other 7 levels in the game. I was very proud of my clever big boy solution, but as I tested it, I kept encountering spots where the schema needed manual adjustments at specific spots.

After getting fed up by all the edge cases I had to account for, I just sat down and manually designed the entire level, which only took me a day and gave me way better results. If I had intended to have 1000 levels in the game, the algorithmic solution would've been the way to go, since manual design would've been too time-consuming. But, I only had 8 levels in the game, so with only 1 extra day I would've had good levels, but instead I just wasted a week writing code that provided zero value... Lesson learned? Nope! I repeated the same mistake in Glass Cannon!

I came up with some bullshit theory on how I had to balance the number of enemies spawned and their healths in Glass Cannon. So, for variety, we'd have the first 3 rounds with many enemies, but small healths, then a boss fight and then we'd have 3 rounds of very few big health enemies. Both the number and average health of the enemies would slowly be going up, but they'd switch their rate of increase in between bosses and this would continue for all the 24 rounds.

The idea was to test the player's build against many sparsely positioned enemies, many enemies in clusters and few enemies with high healths. This core idea proved to be good, but the implementation with its interchanging linear progression schemes was overcomplicated. If I wanted to do 2400 rounds, I could just give my complicated function 2400 as an argument and it would generate this perfect progression, but of course I never needed more than 24 rounds...

I even had a beautiful and professional graph that looked like a double helix with the healths and the enemy numbers intertwining. I had that shit drawn out on my whiteboard with different colors. I event took a fucking picture of it with my phone because I was so proud of my big brain. I invented a cool name for the function that would generate the tables: generate_interlerp_tables(slope1, slope2, rounds).
Interlerp! Now this is sophistication!

Interlerp Graph
A low effort recreation of what the graph looked like. The original was more symmetrical and "sophisticated", but I can't be bothered to retrace my steps exactly, so this is all we have.

It was wonderful, it was genius and it was all in my head. After spending two weeks on this, I playtested it and learned it was largely useless. It sucked in most places and where it didn't suck, it wasn't any different from just a linear progression. Writing 3 lines of code would've been just as useful.

Then, as the pattern goes, I took a day to design the progression manually and towards the end of the day, I ended up with a better progression curve. It turned out that the enemy healths needed to increase exponentialy, not linearly, to keep the game challenging at all times - never too hard and never too easy. I only noticed the exponential curve after iterating on it manually.

Now if I want to make some kind of infinite mode that generates the difficulty automatically, I know the curve will have to be exponential and not "interlerped" or whatever dumb thing I invented. So, even when going for procgen, making stuff manually at first will give you a better picture of the lay of the land you're trying to explore than YOLOing into a complicated and specific schema.

To put it frankly: prefer manual unscalable design over generalized perfect algorithms. This is basically the same lesson as "complexity bad" or "premature generalization/optimization/abstraction bad". I slowly converged on a grug-brained approach to programming after 7 years of coding, which was like 8 years ago, but despite this, it is only now that I am grug-pilled in my approach to game design!

Even when you want your solution to scale, it's better to do it manually at first to at least discover patterns that you'll have to automate when designing the algorithm for it. A manually designed level will always be better than anything you can generate with code, so even if you're doing procgen, make a few levels manually at first to see the upper bound of quality that's possible, which your algorithms will have to eventually approximate.

The $3 solution

So, instead of making the same mistake a third time and doing all that complicated pricing scheme stuff, I'll start with the simplest solution and work my way up from there. Price everything at $3, if exceptions need to be made, put them manually in the code and as they pile up and start forming patterns, extrapolate those patterns to apply to the entire range of possible items. Iterate on those patterns and expand/contract them as needed. Ideally and most likely, just the first step will be enough for the entirety of the game.

Day 8

Core game is complete. This is all that's left for the demo:

All of the above seem like 2 weeks of work combined. After that, I can launch the demo on Steam.

With item generation out of the way, the core game loop is complete

Days 9-22

Now it's almost two weeks later. I managed to get done all of the above except:

But in addition to that, I added a lot more content, namely:

Blue Cards

Blue Items

Let's get the easy one out of the way. Blue cards are simply passives. I added these for variety and a bit more fun. They can increase your shop item count and play with other variables in the game. (They won't increase deck/inventory size though! That increases the complexity of the game too much and I'd rather leave that to the NG+ modes).

One blue effect I haven't implemented yet is multiple triggering. Something like, "trigger the item left of this x2 times". This is simply due to how laborious it would be in code. For now, I'm holding off on it and other similar mechanics. They definitely enrich the game, but they're not necessary for the demo, so I'll put them off until I validate the game.

Yellow Cards

Yellow Items

Yellow cards get triggered on an event and apply some effect. The events are chosen from a random pool based on different weights and the effects are randomly generated like those of the white items. Yellow effects also include other, more powerful bonuses, like granting extra time, restarting scoring, evaluating specific cards, triggering different items (and causing infinite loops, which are a deliberate and fun part of the game).

Yellow Item Events
These are all the possible events for yellow items. I know it seems like they're too few, but when combined with the ~50 effects, they produce huge variety.

Take note that some yellow items can be triggered for hundreds of time within a single round, like this one: "On time tick -> gain 1 point". While this item seems very powerful on the surface, it's not super efficient because of two reasons:

  1. The required score for later rounds goes into the thousands, so in order to milk this item, you'd have to pair it with "gain seconds" or similar items which grant you more time.
  2. While the item is triggering and granting you that point (which takes around 600 milliseconds) you are unable to play. The "stack" and "skip" buttons are both disabled while the animations play, so you're not left with much time to get points in different ways, so there's a tradeoff to be made.
Similarly, creating an infinite loop where items keep triggering each other and keep granting you points is by no means an instant win or a crash. Because items get triggered with a delay, we can't have a spinlock that just crashes the game. In addition, now you get a limited time for the evaluation phase as well, so if your infinite loop doesn't get you to enough points, the game won't wait for every scheduled task to resolve when the time runs out - it'll just do a game over.

More importantly though, we now have the ability to disable items at will. This is because when you get a triggering cycle going, you can get stuck in a loop that either grants you no points or grants you very little. In order to break out of it, you can right click one of the items that runs in the loop and it will stop getting triggered, thus terminating the loop.

This adds a new layer to the game where you could have a few disabled items that you only enable when they're about to grant you big points so as to conserve time that would be spent waiting for the triggering animations. Or, you could disable a blue item temporarily. For example, you disable the blue item with the text "+35% chance for blue items" because you need white/yellow items in your inventory and after a few rerolls and purchases, you can reenable it.

So Far So Good

I like how the three different item types have worked out. The white items are your core, the ones that give you a base score to play with. They trigger for each evaluated card. Then we have the yellow items which play with every part of the game and let you multiply your base points to huge amounts when used cleverly. And finally, the blue items simply grant static bonuses which usually yellow items couldn't do (imagine getting +1 shop slot every time a card is skipped - wouldn't work as a dynamically generated yellow card!)

I'll start setting up the Steam page, then as I'm playtesting I'll take videos and screenshots to put on the Steam page. After that I'll add the missing features (NG+, settings, help, tutorial). For the tutorial, I'm thinking of having a unique mascot that guides the player instead of just the queen of hearts card or something. I'll look for an artist to draw me some portraits of the queen card character.

If I can really get this game out on steam before September, I'll be more motivated to follow this 1-3 month dev cycle in the future.

Noticed a bug at the end: skipping the card didn't grant points!

Days 23-28

Did a lot of testing, bugfixing and balancing. This game needs a lot of balancing. As always, I try to keep the difficulty curve challenging, but not hard. This seems to be a particularly preplexing problem for this game for a few reasons, all of which revolve around the fact that the items are not designed, rather generated.

Some generated items are extremely powerful with the correct combination. Some are completely useless because they have a condition that's hard or impossible to satisfy. Since the aim of the game is to get the required points each round, the game becomes too easy if you roll a strong item and too hard if you never roll good ones.

I foresaw this and added an autoscaler to the required points. So, if the player scores above the limit, the next required points also increase by the total's 30% on top of its normal scripted increase. This makes the game more golf-like. You are rewarded for scoring closer to the required points. Go under and you lose, go over too much and the game gets harder. Not sure if I want to keep this because it nudges you towards playing conservatively - disable some items at the right time or play unoptimal cards which don't cause you to overshoot.

It nudges you towards conservative play because there is no reward for overperforming. Yes, the game becomes harder to better match your skill/build, but if there was a reward, you'd be motivated to maximise your points. A good example of difficulty scaling is the devil room mechanic in Binding of Isacc.

Devil rooms spawn in The Binding of Isaac when you clear a given floor. They contain really powerful items, but have a very small base chance to spawn (~1%). That chance increases by 35% if you took zero damage during the boss fight. It increases by 99% if you manage to take zero damage thoughout the entire floor. Both of the above are hard to do and require significant skill, so it's safe to assume that only high-skill players will ever even see a devil room.

So, you are rewarded for challenging yourself and displaying skill. In other words, you are nudged towards learning and honing your skills (both of which are fun) and then rewarded for it (which makes the experience even more enjoyable). In even simpler terms, you have fun and then the game rewards you for it, making the experience even more fun.

You might ask:
But Niko, won't obtaining the powerful items now make the game too easy and therefore boring for the skilled player?

No, because Edmund McMillen is a fucking genius and he made the devil room items cost health instead of money. Permanent health. Meaning, they take away your maximum health forever. Maximum health in Binding of Isaac is a very scarce resource. The less you have of it, the fewer mistakes you can make before you lose (and get punished by going right back to floor 1).

So, to recap: skilled player challenges himself, gets reward, but it also costs health, which raises the stakes and makes the game more intense and fun for him. In addition, having a powerful item in the early game allows him to clear the early game floors quickly (but not the late game ones). This means, the skilled player spends less time clearing the starting floors (which are boring to him because of his high skill) and gets to the actually challenging part of the game sooner.

There are other orthogonal fun aspects to this mechanic, like the powerful items being randomized like most other item drops in the game, which introduces chance and anticipaiton ("I wonder what other powerful item will be in the devil room this time?"), or a chance for angel rooms to replace devil rooms and add even more intrigue and scale to the game... This entire mechanic is like a win-win-win-win-win scenario and a big reason why I am Edmund's number 1 fanboy.

So, going back to our problem where performing well in Queue Queen isn't necessarily rewarded, I'm trying to think of a similar win-win solution or at least one that keeps the game challenging during both lucky and unlucky runs for both unskilled and skilled players. Here's my idea so far:

Getting a x2 overshoot on the required score increases the chance for a "hex booth". x3 overshoot increases that chance by an extra 20% or something similar and so on. In the hex booth, you meet a wizard who can cast spells for you which let you do things like:

Here's the kicker: This service will cost your time limit (a scarce resource) instead of gold. The hex booth will show up as a button in the shop so you can nagivate to it through the shop UI. Allowing the time limit to be used as a resource for buying things means I'll have to drop the scripted time limits I have now in the game and just turn it into a player stat which increases after boss fights or something similar.

Misc ideas

Days 30-31

Did visual work on the backroom printer. Most of the UI for it is done. Also did some scaffolding for the cardmage parlor and added speed up/reset speed buttons during gameplay that just ups/resets the game speed. Too lazy to record a video of such a minor change and upload it - I'd rather have 3 or 4 new things to show off for a video.

Days 31-37

Wrote 0 code and did absolutely 0 work. This is not good in terms of keeping to my self-imposed deadline, but the amount of socialization I did in this time may make up for it in the future in unforeseen ways (or maybe I'm just rationalizing having wasted time?). I realized that this amount of socialization is what normal sociable people go through regularly and that I'm not really missing out on much in terms of creativity gains if I'm not at gatherings, bars or parties talking with people.

I'm an extroverted, talkative, sociable person. I find it easy to talk to strangers both in groups or one-on-one. That being said, I can't productively discuss things which most matter to me with the normal people around me. I'd have to explain so much and they'd have to understand so much that it's not feasible. I can extract value from any conversation, but I mostly end up thinking that I would have better spent time working on my game instead.

This stems from the fact that what I'm doing here is so detatched from what people usually do that there's barely any connection there. I care about programming and game design but I am not able to discuss either with the people around me.

Other programmers around me have nothing new or interesting to say to me - I've been there and done that or it's not relevant to the kind of programming I do. I don't care about a novel microservice architecture, I'm making a singleplayer indie game where even DRY doesn't apply. I will repeat myself, stupid! (IWRMS? I-worms!) And there's nothing you can do to stop me. And in the end, it will be better for both the programmer and the user. If you're scoffing right now, forget about it, you wouldn't understand, you are below me (and we're both below all the actually successful indie gamedevs BTW).

I also feel like the gamedevs in my IRL circle are all completely lost, so the same issue applies there. I don't care about your low quality rehash of an existing formula. Make something novel or something high quality. The jump in your metroidvania is sluggish and makes me want to bite off my thumbs every time I press B. Stop talking about your game so much and release it already so you can learn and improve! Nobody fucking listens to me because I have no hits I guess, so I can't fault them.

So this week I thought about life, read a bunch of articles, watched Youtube and did a lot of socializing. Here's my conclusion:

I may be able to do some work next week, but I know for sure that the game's going to be late already. I'll do my best. I can't really do more than my best, so that's what I'll do.