Implementation notes Improvement through imitation. ---------------------------------------------------------------------- 0. Concept I spent quite a bit of time in recent weeks playing the Genesis Mini, spending most of that time ignoring the 40 or so games on that console and playing Puyo Puyo almost exclusively. I like the chain building aspects of that game, but not so much the fast reflexes needed to enjoy it in later levels. Being a software engineer, obviously every problem can be solved by writing more software. In this case, I decided the way to go is to write my own Puyo Puyo simulator and practice on that. ---------------------------------------------------------------------- 1. Simulator Most of these falling block type puzzle games have the same structure: - Generate random blocks. - Process user input. - Apply game rules and scoring. - Display game status for all of the above. Generating random blocks: every game gets a fixed set of 100 blocks. I wanted the number of blocks to be fixed so that the game time would also be fixed, and 100 blocks means each game lasts less than 10 minutes, which felt like the right amount of Puyo Puyo when I want to take a break from something. Since it's 100 blocks fixed, I just generate the same 100 blocks with a reasonable distribution of block types, and then do Fisher-Yates shuffle. There were no considerations for flood or drought protection like you might find in Tetris games. Note that 100*2 tiles is more than double the 6*12 tiles that can be held in the grid, so you will need at least 3 long chains to complete each game. Processing input: only four actions are accepted (move left/right, rotate in a single direction, drop). Because there is only one direction of rotation, some of the block placements are not possible. The upshot of limiting to four actions is that only 4 keys are needed, so the game can be played one-handed using arrow keys. Ruby's "io/console" library is used to handle input, and this library is the reason why I chose Ruby for this project -- it seems of all the scripting languages that are pre-installed on various machines I use, only Ruby has this IO library as part of its standard distribution. Thus distributing a Ruby game should require the least amount of setup. Game rules and scoring: the cluster-removal bits are done with a floodfill algorithm, and this process is done repeatedly to account for chain reactions. This is obviously a very different game mechanic from something like Tetris, and in my opinion it makes Puyo Puyo a much deeper game. The floodfill bits were a lot of fun to write, and it's what differentiates Puyo Puyo gameplay from Tetris. The scoring bits are needed to have a sense of accomplishment, although initially I just had a single "time to first longest chain" metric, since in class Puyo Puyo, that's pretty much the only thing that mattered. Runtime display: standard ANSI cursor positioning and color codes. I thought I only used the standard 4-bit colors, but on my Windows 10 console they don't show up at all. All other terminals (including mintty for Cygwin) seem to work just fine. ---------------------------------------------------------------------- 2. Extra features Classic Puyo Puyo scoring rules are used, so that the kind of score I get from this practice simulator should be a reasonable predictor I get with the real game. Besides the fact that my simulator uses only 4 colors instead of 5. All later versions of Puyo Puyo are played with 4 colors, so I figured that's the way it's meant to be. The score is something of a motivation to play a game to completion. I also attached some cheesy ranking text for each score level on the ending screen, and also included a graph showing a history of all chains that were made. This is to provide some sense of accomplishment. Initially I had just "time to first longest chain" because in classic Puyo Puyo, that's pretty much the only thing that mattered. But since I wrote my own simulator to avoid all the timing elements, it seems wrong to bring those back. Block generator is guaranteed to produce the same series if given the same seed. This is done by implementing a custom pseudorandom number generator (XOR-shift) as opposed to relying on the built-in rand() function, since there is no guarantee that the latter will remain stable across all distributions and vintages of Ruby. Since block generation is deterministic and there is no timing element associated with processing user input, every game can be reproduced just by recording the seed number and all the keystrokes, so I got a replay function practically for free. This could be useful for sharing high scores, but it's definitely useful for regression testing. ---------------------------------------------------------------------- 3. Solver Even better than regression testing -- since I can run a game deterministically and non-interactively, I don't actually have to play it myself. Also, since I am not very good at Puyo Puyo, it makes sense to have a computer play it to exercise the longer chains that I would not be able to make myself. The solver runs in two modes: - Maximize score. - Maximize scoring potential. The first mode just simply scans a few moves ahead and find the branch that leads to the maximum possible score, and take the first move. The second mode runs in the same way, except the scoring function is replaced by one that give points for increasing number of potential clusters. There is no deliberate chain building here, basically it just tries to get as many connected groups of 2 or 3 as possible and hope for the best. It's not too different from the way I actually play, but the fact that the solver looks a few moves further ahead and that it executes more consistently meant the solver almost always score better than me. This part was actually a lot more fun to write than the simulator, particularly the optimization aspects on getting the solver to produce good results in under a minute. ---------------------------------------------------------------------- 4. Finally... So I started from playing a different game, to playing my own game, to finally not playing my own game. Maybe I just really like watching video games more than I like playing them. Despite that, I now have the ability to get my Puyo Puyo fix anywhere I have a terminal. I might even be able to get higher scores than my own solver one day.