On Tromi

It's been a little over a year since I released Tromi to the world, from which time maybe 100 people have played it. It's received uniformly good reviews from some such people, which leads me to the completely fair and not at all hyperbolic conclusion that I am the world's greatest game designer. I really like talking about Tromi, so let's do some of that right now!

300 lines was chosen as the limit for the game as it's a nice round number that divides evenly into 1, 2, 3, or 4 (lines). This made it easier to come up with fair and understandable scoring. It also yields a substantial but not overlong game length, should one reach 300 lines. The slowest players shouldn't take much longer than 20 minutes, and the best players will likely finish in 8 or less.

999,999 was chosen as the absolute top possible score, more or less arbitrarily. Or rather, I chose it because Sega's 1988 Tetris counterstops at 999,999, and Tromi could be viewed as somewhat of an alternative successor to that game (see also: Arika's Tetris The Grand Master, Jaleco's Tetris Plus, Sega's own 1999 sequel). I also wanted to have a fixed maximum score that, for all practical purposes, is impossible for a human being to achieve. However, the scoring is granular enough that it's unclear exactly how high one could go. As such, one's score in Tromi could be conceived of as a percentage of perfection; if one gets 500,000 points, they had a (roughly) 50% perfect game.

It is conceivable that a player could get 75 4-line clears and thus achieve a perfect game in those terms, but the lines component only accounts for just a hair over half of one's score (500,100). The other half comes from the "Efficiency" bonus. A running average of the number of moves used to place a given piece divided by the number of frames spent placing it, for the last 50 pieces placed, is taken. If the number of fully enclosed holes has increased since the last piece placement, this piece's result is discarded and a 0 is added in its place. Every time one or more lines is cleared, the current 50 piece average value is multiplied by 1666, and the result is then multiplied by the number of lines cleared. This will be added to the points gained from the flat lines bonus, which is then finally added to your score. If one were able to maintain complete, absolute frame-perfect control over every piece without making any holes, then the number of points one would yield over 300 lines is 499,800. 50 pieces was chosen for the amount averaged because a completely full field of tetrominoes would equal that number of pieces, tying into the old polyomino puzzles where the objective was to place shapes to precisely fill a given space.

Adding the two score potentials together, we have 999,900 possible points. The remaining 99 points comes from a sneaky rounding mechanism. Tromi has an internal speed level running from 0 to 99, and is directly tied to the player's score, divided by 10000 and rounded down. The speed levels are distributed evenly among the 10 displayed levels (0-9), with the speed actually capping out at 55, or as soon as one reaches level 9, though the internal level will continue increasing every 10,000 points. When the game ends, whether through topping out or reaching 300 lines, the current speed level is just added to the player's score, secretly. There is very little point to this mechanic other than to give me peace of mind that 999,999 is a technically possible score.

The flat bonus awarded for lines cleared at one time is set up to give the player 250,050, 375,000 and 500,100 points, if they were to finish the game with all 2-, 3-, and 4-line clears, respectively; so, roughly 125,000 points apart. Singles will only award whatever the current Efficiency bonus is times one. In the course of regular play, Efficiency will never be a flat zero; the player will always get something from a line clear. In this way, high line clears are subtly prioritized over pure placement efficiency, as it would be quite possible to finish the game with all singles, and thus gain 0 points from line clears. The path to improvement is first and foremost stack cleanliness. I would estimate that even if one were very slow in obtaining 75 4-line clears, the result would nonetheless approach 700,000 points. It is also worth noting here that once one approaches maximum gravity, Efficiency is going to bottom out even for the best players; it is a struggle to maintain anything approaching a 0.40 average. This further pushes clean stacking to the fore among avenues to score improvement. All of this takes after Sega's Tetris not timing the player at all; it was a pure score attack.

The kyu/Dan system in Go and Shogi is based on wins and losses against players in 1v1 matches; to accommodate this system to Tromi I decided on a sliding 5-point scale wherein meeting a particular score threshold means a "win", and failing to meet a particular threshold means a "loss". Excruciating testing and spreadsheeting led me to a value of around 26,666 points per "win" threshold; E.g., to "win" when your current grade is 2 kyu, you must achieve at least 479,999 points in a game. "Win" enough times that your Promotion Meter ticks over beyond 5 points, you will be promoted and your Promotion Meter reset to 3. Falling below a particular score threshold is a "loss", and these thresholds are tied directly to the level thresholds. If your current grade is 1 Dan, for example, you would need to at least reach level 7 in a game (420,000 points) to avoid a "loss". This means there's a pretty comfortable middle ground between gaining a promotion point and losing one. Finally, if you score enough points in a game to match the "win" threshold of at least two grades ahead of your current one, you will be automatically promoted until that isn't the case. For instance, if you're just starting the game at 19 kyu and you score 320,000 points on your first game, you'll auto-promote to 9 kyu. This is to mitigate some players' tendency to throw away lots of games, as one big PB will grant a grade at least somewhat close to their true skill level. Similarly, if one tops out before 50 pieces have been placed, the game doesn't count one way or the other; this is actually the only gameplay-related tweak made post-release.

Through the same rigorous testing and spreadsheeting process I arrived at 55 internal speed levels, spread out 6 per displayed level, each occurring every 10,000 points. With an ideal line clear yielding just over that amount, someone clearing 4 lines at a time at optimal efficiency will clear a speed level each time they do so, sometimes skipping one. Tying level directly to score was the best way I could think of to have an "ungameable" system, wherein there's nothing for the player to do but play well. Just getting 300 lines confers no benefit on its own, so it always pays well to try one's best; it's a limit, not a goal. The speed curve was determined programatically, ramping up the gravity smoothly within each displayed level, and in bursts every displayed level boundary, until level 6, where the gravity maxes out and entry delay and lock delay are decreased gradually in turn every internal speed level, until finally reaching displayed level 9, wherein the internal speed level nominally increases but does not affect the actual speed of the game. The speed at each internal level began as fractional seconds of clock time, representing the time it would take for a flat I piece to spawn in an empty field and lock at the bottom of it without any player intervention; the resulting seconds were then translated by a script into gravity, entry delay and lock delay values. Delayed auto-shift trigger values were determined and added manually. Like many other aspects of Tromi, determining precise speed levels was a laborious iterative process of trial-and-error.

For a brief amount of time, Tromi's basic rotation system was up in the air, and Sega rotation was not a foregone conclusion. In my mind, flat side down spawn orientations made logical sense, and I nudged pieces around in their bounding boxes and generally did a lot of repetitive plotting and testing. In the end, I deferred to tradition and just implemented Sega rotation, colors and all. There is a sentimental aspect to this project, and as mentioned earlier I was approaching the whole thing as somewhat of an alternate successor to Sega's Tetris. So much so that I left the hard ceiling in, much to the chagrin of various theabsolute.plus Discord server members.

However, the hard ceiling left open the door to justifying greater maneuverability. To make the ceiling not super annoying in low gravity as it is in Sega's Tetris, instead of including the vastly more common bodge of having invisible rows at the top that pieces can only rotate into, I added ceiling kicks (compare horizontal wallkicks being present in other games to mitigate not being able to rotate against the wall of the playfield). This kind of thinking led to a somewhat complicated (but not convoluted) system of situational one-attempt kicks; unlike the TGM series, in which a blocked valid rotation will have the piece move one space right and then left from its basic position before failing, Tromi will instead only try one movement based on exactly which mino of the piece is being blocked. The precise gory details can be found in the tetris.wiki page for Tromi, but in short, if the top is blocked, it will try to kick down; if the left side is blocked, it will try to kick right; if the right side is blocked, it will try to kick left; and various other specific conditions will result in a floor kick or ceiling kick. Blockages of the center column, handled for instance by Arika in their "center-column rule", are also handled specially to leave some flexibility without allowing unnatural-looking "teleport" kicks. These specifically conditional kicks along with the hard rotation ceiling gives a kind of common-sensical physicality to piece maneuvering. I still get surprised sometimes when I have only 3 rows left in the playfield and the I piece won't rotate, but that's just how it is. Can't squish a 4-tall block into a 3-tall space. Ultimately, even as I hewed to tradition with the choice of basic rotation, I achieved what I felt like was an ideal system.

Piece randomization was, for a time, left to the standard Tetris Guideline 7-bag randomizer. It's an algorithm that makes perfect intuitive sense and yields a pleasingly even distribution of pieces, but even in Tromi's context of 1 piece preview with no hold function it's a little too predictable and subject to "card-counting" strategies. Less specifically the distribution just feels kind of disjointed, to me. So approaching the problem of piece randomization began from these principles: What is wrong with pure random? Really only two things: flooding, wherein you get several of the same piece or set of pieces in a row, and droughting, wherein you don't get a particular piece or set of pieces for a really long time. The problem with only solving the flooding problem, as the first two games in the TGM series do, is that long droughts occur probably more often than is really fun for the player. Solving only the droughting problem means that one gets several of the same piece or pieces in a row, followed by a predictable line of every single piece they haven't seen in sequence, which is the very definition of disjointed.

So my first attempt at solving these problems was to brute-force them: The randomizer would draw a piece at random, check if it's been dealt within the past three deals, and if not, deal it. If it has been dealt recently, let there be a 1-in-75 chance that it gets dealt anyway, otherwise draw again. This mitigates the problem of flooding while allowing for some repeats to sneak in from time to time. Clamping down on droughting was much simpler: Just see if there's a piece that hasn't been dealt in 10 deals and deal it immediately. It still felt a little wonky but intuitively it seemed the best way to go. Nail down flooding and droughting and let everything inbetween be pure random, I thought.

But then, long-time friend and western TGM community grandfather colour_thief presented to me his fascinating research into finite state-based randomizers, which allowed him to model all kinds of different randomizers and, along with generating really cool looking graphs, show a percentage representing their "entropy", which ultimately means "randomness", which ultimately means "unpredictability". Through looking at his page and having several conversations with him I added a condition to my randomizer: When it draws a piece that's been recently dealt and it fails the 1-in-75 chance, instead of drawing randomly again, deal the currently most-droughted piece. This definitely evened things out a bit, but the result was barely less predictable than 7-bag. It made me wonder why I was even bothering with such a convoluted algorithm in the first place.

This aspect, like others in Tromi, was one that I agonized over. I lost sleep over these things. I would hyperfocus on the rotation system, mentally compartmentalizing scoring and the speed curve and randomization as "done deals" or at least "good enough", finish what I was doing, and then look back in dissatisfaction at the scoring system or the speed curve or the randomizer and start again on one of those. It was this cycle that occupied effectively all of my free time for months and months. It was utterly fascinating. Tromi is the most absorbed I've ever been into any project, and I am extremely proud of it, but it is also something that I was very glad to be done with by the end. It's not healthy to be completely obsessed with one thing for too long.

Anyway, through a few more "cycles" I made my way back to the randomizer and decided that this ridiculous algorithm was no good. You can't just common-sensically brute force "pure random except no floods or droughts" and end up with something that isn't seriously flawed in some way. As an interstitial phase I ported over some of colour_thief's code and implemented his "seamless bag" randomizer (detailed in the aforementioned page), which retains the floodless/droughtless qualities of 7-bag while being far less predictable by walking through a black box of a file containing a finite number of randomizer states, a process I can only conceive of as magic. Maybe it's my vanity speaking here, but I wasn't comfortable with plugging something into my game that I didn't have a full understanding of, and didn't have full control over. These randomizer state files may as well be binary blobs, as they're indeciperhable, a product of handing over various desired statistical properties to a generator. It's "compiled randomness" in a sense; if there's a way to implement seamless bag in practical terms, no one yet knows how to do it.

Through all of this reading and discussion and implementation, though, I learned enough to feel confident in starting again from scratch. At this point I felt I could come up with a practical, yet simple, yet fair, yet unpredictable, randomizer. I eventually arrived at what I like to call a kind of "deferred randomness", wherein the drought values of each piece, that is the number of pieces dealt since a given piece was last dealt, are kept track of. With this, a random number is generated from 0 to the highest current drought value (e.g. if the I piece hasn't been seen in 8 deals and every other piece has been seen since then, generate a random number between 0 and 8). With this random number in place, the randomizer will then draw pieces randomly until it finds one with that drought value or higher. This process by itself gets us most of the way to the design goals of "low predictability, low flooding, low droughting", and is a huge improvement over what I was trying to do.

But Tromi really pushes the player toward clean stacking and making lots of 4-line clears, which require at least 10 pieces to build and clear, so I was willing to let a little bit of jank sneak in and re-added the 10-piece drought hard stop from my first attempt. I initialized the drought values at the beginning of each game to only deal I, J, L or T as the first piece, and with all of that, I had my randomizer. It fit my design goal for Tromi while being simple, practically implemented, entirely my own, and very nearly as unpredictable as seamless bag. For his invaluable help, without which I would never have reached this point, colour_thief got an "RNG Consultant" credit.

The aforementioned cycles of obsessing over the particulars of all these mechanics likely wouldn't have gone as long as they did if I hadn't decided to also do some jazzing-up of the aesthetics. Version 1 of Tromi was approached with a vague desire for it to reflect "board game", in some way. This led me to use a lot of static backgrounds of...wood. Yeah, I don't know. Anyway, I thought for version 2 I could have animated backgrounds via short looping video clips, and I had always kind of liked the aesthetic of the Philips CD-i Tetris game, and there's certainly no shortage of royalty-free video clips of landscapes and nature stuff, so this was mostly a grind of finding good clips, editing them, and plugging them into the game. This was a very active process though, and wasn't the reason I obsessed for so long over the actual mechanics of the game. I needed some music, and so in a "what the hell, why not" moment I sent an email to Jerry Martin, lead composer on some of my favorite game soundtracks of all time, and asked him if he could give me some The Sims-style solo improvisational piano music for a free, completely unmonetized, labor-of-love game I was making. Also, it being such, I couldn't justify (to myself) offering very much money.

To my surprise, he said yes, under the conditions that he retain the rights and that the music also appear on an upcoming album of his. I said Hell Yes and we were off to the races. He enlisted the help of Juraj Stanik and, after about four months or so, gave me two beautiful pieces of music, with the longer one (about 11 minutes) split into two loops. He really went above and beyond. The longer piece became the title music and the background music of the first part of the game, and the other became the background music for the second part of the game. To this day I doubt Jerry even knows the name of the game his music is in, as it was just a commission of something for which he ultimately had his own future use in mind; I say this because I'll occasionally get "I can't believe you got The Sims guy to do your music!". And, to be fair, me either, lol. But it's not like I have any connections, he's a musician for hire. You too can send him an email. In any case, it was just such an email that led me to have lots of time to tweak every little thing about the game, as the new music took a while to arrive and I certainly wasn't going to release the game without it.

Various others helped in various ways. Most notably switchpalacecorner, one of the greatest Tetris players in the world full-stop, tested the game quite a bit and was instrumental in its overall balance. The others listed in the credits either tested the game or at least made a suggestion or two, whether or not I actually used them. And of course, Cambridge, the swiss-army knife of tetromino game engines, made for a very comfy base on which to build. There's likely some stray vestiges of Cambridge code lying unused among Tromi's that I never got around to taking out.

I'm greatly appreciative of those who have played Tromi, and especially of those who have said nice or not nice things about it, and especially also of those who have submitted scores to the official leaderboard. I can't come up with anything more profound to say than I'm glad this game exists, and that people like it.

Discuss this article