Wednesday, March 19, 2025

Using Balatro to Explain Structured Programming

(ORIGINAL POST DATE: FEBRUARY 16, 2025)
 
 

Damn near all my friends have started playing Balatro, or at least have become aware of it in some capacity, over the recent months. I tell you: the Balatro curse really is real.

If you are somehow one of the few left untouched by the Bala-symp-trom, the basic gameplay loop is this: every round, your goal is to defeat the enemy (called Blind, from Poker) by obtaining a TOTAL score higher than the Blind’s HP. TOTAL score is calculated from the CHIPS score and the MULT score, which are multiplied together. The overall goal is, from there, pretty simple: maximize the CHIPS and MULT score, done by optimizing the game’s Poker hands and Jokers.

A screenshot of Balatro’s side menu, showing the Blind’s HP, the TOTAL score, and the CHIPS (80) and MULT (10) score.

Out of sheer curiosity, I asked some of my friends why they thought Balatro was fun, and why they thought it was addicting. For the latter, pretty much all of them cited the “Big Number Go Up” phenomenon as part of what makes LocalThunk’s work so hard to let go of. I mean, can I blame them? Who doesn’t love a little lottery logic? The game even exacerbates this drowsy, gripping feeling by upping its exaggerated movements and sounds every time a big number is achieved, along with literal flames to underscore the importance of the event. It’s like as JPEGMafia says, “I need the maximum digits, blame it on capitalism.” Under that particular reading, Balatro rightfully deserves an 18+ PEGI rating.1

But as for why they (as well as I) find Balatro fun is its element of strategy. Discovering strategies, whether through literal Joker card unlocks or through gradual understanding of the game is one thing, but effecting and executing those strategies is another. One of my Balatro-skeptic friends thinks of this peculiar card game as just another “gambling simulator” that relies solely on “high number good” psychology to be fun, but truthfully, actually building toward high numbers is just as fun, or more. If you’ve actually sat with the game for awhile, you would know how not very easy it is to attain a solid starting setup, and how the challenge (and frustration) sets in when you inevitably lose a good run — on the flip-side, the challenge (and frustration) notably eases off the better you get at the game, and, indeed, the deeper you understand its strategies.

One part of the strategic side behind Balatro’s core gameplay loop is in its Joker sequencing. I’ve always likened the gameplay of Balatro to that of a Rube Goldberg machine: a chain-reaction-based contraption of sorts that starts off from one particular Joker being activated, into the next, into the one after, and so on. It’s why veterans of the card game will always tell you to mind the sequencing of your Jokers; they matter. Put +MULT Jokers before *MULT Jokers, they say. It maximizes the MULT score, they say. In a way, the underlying goal of Balatro (beyond simple maximization of two numbers) is to take the random pieces it throws at you and, from those lousy scraps alone, design a Rube Goldberg machination strong enough to beat the casino-themed opponents.

Linear/Sequence Control Structure

We can take this analogy further by comparing the sequencing of Balatro’s Jokers, and the concept’s importance, to structured programming. As it stands, the sequencing of Balatro’s Jokers follows a mostly linear (or “sequence”) control structure; that is to say, the Jokers are executed left-to-right. Analogically, this would be like lines of code being ran one after another. Let’s see a simple example below of Balatro’s linear structure in question:

If we were to take the set of Jokers above, which are (from left to right) Misprint, Gros Michael, and Stencil Joker, and proceed to play a hand, each Joker would be activated one-by-one in the order that they appear. Misprint would add anywhere from 0-23 MULT randomly; Gros Michael would add 15 MULT; Stencil Joker would multiply the MULT score by 3x, given that there are three empty slots on the board. This would result to a MULT score of anywhere from 45 to 114, depending on the value of Misprint.2

Translating this sequence of Jokers to pseudocode, we would get something that looks like:

If we were to, say, switch the positions of Stencil and Misprint (this works in game too), then Lines 3 and 1 would have to be swapped in the pseudocode.

NOTE: The existence of “Blueprint” and “Brainstorm” Jokers add a little bit of flavor to this structure actually, with the former copying the succeeding Joker, and the latter copying the first Joker on the deck. It’s like a line of code that says “Copy the next line” or “Copy the first line” which I think is rather interesting. Despite what it might seem, though, this is not a goto structure. The sequence carries on beyond the Blueprint/Brainstorm, though they are typically pivotal in long-lasting runs.

Modularity/Subroutine

Of course, Joker activation sequences are almost never this simple. For one, not all Jokers activate upon playing a hand. Some, like the Ceremonial Dagger and Riff-Raff, activate upon selecting an enemy blind. Others, like Trading Card, activate upon the first discarded hand. Economy-based Jokers, like Cloud 9 or To The Moon, often trigger at the end of each round. All Jokers activate sequentially, but not all Jokers are triggered by the same thing. To see what I mean, consider the following example below.

If we blindly followed the linear structure discussed above, we would think that all of these Jokers activate sequentially when a hand is played. However, this is only true for Popcorn (3rd) and Driver’s License (5th). Cloud 9 (1st), Gift Card (2nd), and To The Moon (4th) activate at the end of the round. Meanwhile, Ceremonial Dagger (6th) and Burglar (7th) activate when a Blind is selected (i.e., when a round starts).

We don’t need to know what these Jokers specifically do, but we could categorize them based on how they activate, as seen below.

This categorization of Jokers based on their activation method is fascinating, because it creates, in a way, a modular (or “subroutine”) structure to how a player designs their build. Rather than viewing a deck of Jokers as a singular long sequence, it can instead be viewed as multiple sequences, with each sequence being triggered differently and often for a different purpose. To hearken the concept back to programming, it would be like having multiple functions, or methods, activated through specific events, all containing within them functionally different lines of code. The above example can be translated to pseudocode of three varying functions as follows:

NOTE: Technically, Burglar won’t activate in this scenario because Ceremonial Dagger3 would destroy it first. Remember that within each function, the lines of code (or Jokers) are still activated sequentially. If Ceremonial Dagger were to be placed after Burglar, then it would not have a chance to destroy Burglar.

Branching/Selection Control Structure

Jokers also straight up don’t have to activate, at all. Often Jokers require a specific condition to be met before it will perform its desired effect. Among the aforementioned examples, you can see Driver’s License, which requires 16 enhanced playing cards before effecting x3 mult. Other examples include the various hand-type Jokers, which give a certain number of CHIPS or MULT if a specific hand-type is played (e.g., Jolly Joker gives +8 MULT when a Pair is played4). Creating Jokers with binary conditions in this manner is a direct implementation of the selection (or “branching”) control structure. In code, it’s akin to using an “if” statement to determine whether or not a Joker will activate.

If we imagine having the deck above, we’d find a set of Jokers with presupposing conditions before activation. Jolly Joker only activates if the hand contains5 a pair; The Duo only activates if the hand is pair; Séance only activates if the hand is straight flush; and Driver’s License only activates if the deck has at least 16 enhanced cards. Naturally, there are more examples than these, but we could see this particular deck translated into pseudocode below:

However, the applications of the branching structure within the game remains limited, as conditions are only binary, and there’s typically no other effect when a condition fails. In other words, it’s either a Joker activates, or it doesn’t. In pseudocode, that means a lack of “else” or “else if” statements.

How cool would it be if there was, say, a Joker that acted as an explicit branching path? For instance, if the hand has at least three playing cards, take the top path, else, take the bottom path. Such an implementation would obviously make for a far more complicated game, but we can still imagine said hypothetical:

A hypothetical example of an explicit branching Joker that literally diverges the path of execution, depending on the condition.

In pseudocode, it would look like:

Looping/Iteration Control Structure

As we’ll see, the relative simplicity in the implementation of the branching structure is not unlike that of the iterative (or “looping”) control structure, which has mainly been manifested by way of the game’s “Retrigger” effects. Retrigger essentially repeats the Joker sequence. Even though retriggering Jokers only provides for a very primitive method of looping, it’s basically where the shit goes nuts in Balatro. To see why, let’s examine a simplified example below:

The setup above consists of Scary Face, which adds 30 CHIPS for each face card; Smiley Face, which adds 5 MULT for each face card; Triboulet, which adds x2 MULT for each King or Queen; and two Jokers yet to be revealed. If we hypothetically play a single King of Hearts in the abovementioned scenario, it would activate all of the Jokers once, which is basically the same as a simple linear structure, resulting in 30 CHIPS and 5(x2) MULT (TOTAL: 300). In pseudocode, it would look like:

Now, consider the scenario where we play all four Kings from every suit.

Since Scary Face, Smiley Face, and Triboulet all activate on every played King, the whole deck will actually repeat 4 times, resulting in 30(x4) CHIPS and (((5*2 + 5)*2 + 5)*2 + 5)*2 MULT (TOTAL: 18,000). In case you didn’t notice, the TOTAL score went from 300 to 18,000. The change is reflected in the pseudocode like so:

In programming languages, you could imagine this to be a “for” or “while” loop that iterates 4 times.

But now, we can add nesting, which is when loops exist inside of loops. The 4th Joker, if revealed, is Sock and Buskin, which retriggers every face card.

Since Scary Face, Smiley Face, and Triboulet all activate twice on every played King, and there are a total of four Kings, the resulting score would be 30(x2)(x4) and 2,550 MULT6, or a TOTAL score of 612,000. We went from 300 TOTAL, to 18,000, to 612,000. In pseudocode, this would look like:

Indeed, the pseudocode above reflects the nested nature of our paradigm.

Finally, let’s cap off the insanity by revealing the last Joker, Dusk, which retriggers all cards on the final hand. If we now assume that our quad-Kings are the final hand, they would be retriggered twice each.

Since Scary Face, Smiley Face, and Triboulet all activate thrice on every played King, and there are four Kings in total, the resulting score would be 30(x3)(x4) CHIPS and 40,950 MULT, or a TOTAL of 14,742,000 score. In pseudocode, the final setup above looks like:

The underlying reason for such fast-growing numbers can be explained by the naturally exponential growth that comes about from nested loops. Nested loops themselves are multiplicative in nature, so if you have an outer loop that repeats 4 times, and an inner loop that repeats 3 times, the sentence inside will run 12 times. If you then pair this with a Joker that multiplies the score by 2, the multiplication on top of multiplication gives rise to exponential growth.

Just from this primitive, constant-based implementation of iteration, we can already achieve some B.S. high numbers. But why don’t we make Balatro even more insane? Like in the branching structure, how cool would it be to have a Joker that creates an explicit iteration structure? For example, what if there was a Joker that retriggers the 3 Jokers to its left N number of times? What if N increases whenever some condition is met? A loop that grows in bounds already somewhat exists in Balatro (e.g. Observatory runs), but having it be stated explicitly would allow for even more complicated play.

NOTE: The fact that this hypothetical Joker would repeat the “last 3 Jokers” is an example of a “block”, or a collection of statements (in this case, Jokers) treated as one. In modern programming languages, this would be the curly braces that enclose if-blocks, functions, and the like. Blocks are yet another element of structured programming, though it only exists within this hypothetical.

Conclusion

By closely analyzing the order of activation (or sequencing) and manner of activation of Balatro’s Jokers, we were able to draw a close analogy between them and the various simple tenets of structured programming that exist in most programs today; namely, linear, branching, and looping programming control structures, as well as their modularity. We’ve found that the left-to-right activation of Jokers mimics the linear and sequential runtime of code; that the designation of Jokers to categories based on their activation method mimics the modularity of code; that the binary condition of Joker effects mimics branching structures; and that the game’s Retrigger effects and scoring card-based Jokers mimic the looping structure seen in parts of programs. We even used the nested looping concept to showcase the insane exponential numbers this game can reach, thereby highlighting one of the reasons people find it addicting.

By analyzing the structures and systems of games, we have the opportunity to learn genuine technological and scientific concepts brought about by their design, in this case, by way of analogy and interwoven relationships between concepts. I even showed some hypothetical features LocalThunk could add, as tangents, that was brought about by the general concept at hand, showing that this kind of analytical thinking can make way for creativity, and for ways to push the envelope, even if only by a tiny smidget of a millimeter.

When I asked my friends what made Balatro so “addicting”, no one rejected the notion that the game even was addicting — they accepted it as truth. We should always be careful of what gaming media can do to our minds, but if we make the effort to apply what we’ve learned, we can come out of it with a deeper appreciation and understanding for our lessons, or, at the very least, a cool fact to tell friends. This article came about not because I’ve been obsessing over Balatro itself, but because I’ve been obsessing over its design. You could say it’s a different kind of Balatro curse, one that says, don’t be addicted — be enlightened, instead.


Footnotes

  1. Discovering that Balatro had been designated an 18+ rating by PEGI was rather disappointing. The act was a strong statement indeed by the authorities above, who by now are only quite interested in upkeeping an image rather than directly confronting a rising gambling issue. Balatro was deemed 18+ because it looked gambling-ey-ish, and because it “had real Poker hands” that, if knowledge be gained, could be “transferred to real life games of Poker;” yet, the corporate video games, sports gambling websites, slot machine apps — all that which involve draining away real money? You know what the authorities say in response? Fuck it! 3 and above! Anyone can join! Advertise ’em to children, in TV, broad daylight! We’ve stopped eating dinners?! We just paint dogshit in gold and munch on it like a bunch of pigs! ↩︎
  2. This also excludes the base Mult from the hand played. ↩︎
  3. Ceremonial Dagger destroys the Joker to the right of it, in exchange of +MULT. ↩︎
  4. More specifically, +8 MULT when a hand containing a Pair is played. So a Three of a Kind, Four of a Kind, Two Pair, Full House, and so on, all count because those hands contain two cards of the same rank — they all “contain” a Pair. A Straight doesn’t contain a Pair, so it will not activate Jolly Joker. ↩︎
  5. See Footnote 4. ↩︎
  6. The resulting mathematical expression is too long to be written in full, and this number was obtained through computation. ↩︎


No comments:

Post a Comment

Post-Contextualization: Chants of Sennaar and Artificial Intelligence

/*** THIS POST CONTAINS MAJOR SPOILERS FOR  CHANTS OF SENNAAR ***/   Did you know that I got into a Twitter disagreement with one of  Chants...