Submission #3303: FractalFusion's NES Solitaire in 00:42.80

(Link to video)
Nintendo Entertainment System
baseline
FCEUX 2.1.4a
2572
60.0988138974405
689
Unknown
Solitaire (Unl) (REV1.1).nes
Submitted by FractalFusion on 9/24/2011 10:42:33 PM
Submission Comments
Time for another (Klondike) Solitaire submission! 264 frames (~4.5 seconds) faster than the published run on the same layout. I'm tired of not submitting stuff.
  • Music ON
  • Aims for fastest completion of one game (same layout as in mmbossman's run).
  • Plays at hardest level (3 card draw).
Version: FCEUX v2.1.4a.
Note that it works on both ROMS, so don't worry about the checksum.
If music is turned off, it saves like 3.5 seconds since it doesn't play the jingle at the beginning. But you wouldn't be able to get this layout, so who knows? I leave it on, because having the music off is never good.
It is entirely possible that the hardest level (3 card draw) is faster than 1 card draw, since you need less flips to get to where you want.

About the run

Well, I started out by disassembling the RNG in this game. Difficult, but it didn't take me more than 3 days. Details on that later.
So I decided to take a "break" (relatively speaking) and improve the published run.
Just as in the previous run, holding start at the beginning glitches out the intro. Since I had a frame to burn (just like mmbossman), I used it to change the card back image. Alternatively, I had the option to change the color, but decided against it since seeing blue "red" cards flying around is a rather alien concept. Also, nothing beats actual red. Except maybe purple or pink.
First, I looked for a sequence of play that reduces downtime (e.g. transferring large stacks takes a lot of time). Once I had that, I did it again and optimized movement. Some methods include the following:
  • You can press A at the same time as one or more direction keys to select a card.
  • You can press diagonals (e.g. up+left) and the cursor will go diagonally.
  • When going from one side to the other, it is faster to go through the very top row (with the deck and home slots), rather than the second row. Just the way the cursor works.
  • Often when transferring cards, it is faster to alternate left-right and right-left actions, if possible.
  • Near the end, it is faster to work off the part of the deck nearest the beginning (the first three cards on the flip after restoring the deck), since otherwise it takes a bit more time.

About the layout

Although the layout has a lot of cards that can easily be played, there are some undesirable things such has having both red jacks and both black sixes hidden in the upper right section of the pile. Furthermore, there is a black ten on top of one of the jacks. As anyone who plays Klondike Solitaire can tell you, this is not a good thing, as it severely restricts your options (indeed, playing the other black ten on the red jack that is not covered by the first black ten will pretty much ruin your game).
Is there a better layout that can make this faster? Possibly. But I haven't tried.

Possible improvements

Aside from a better layout, there might have been a strategy on the original layout that I missed.

Screenshots

Disassembly

Here's how the initial deck is determined:
  1. The initial deck is determined from address 0006-0007, which is two bytes (16-bit) and little-endian. This "RNG" is merely a counter that advances by 1 every frame.
  2. When the deck is to be generated (in this run, when address 0006 = 0x180), start with an empty 52-cell array and do the following.
  3. 16-bit-multiply address 0006-0007 by 0x28.
  4. Then add 0xE39. Then subtract 0x2D9.
  5. Look at this new 16-bit value. If the upper byte is between 0x03 and 0x82 inclusive, or the lower byte is greater or equal to 0xDA, or less or equal to 0x59, subtract 0x2D9 and repeat this step. Otherwise, continue.
  6. Take the low byte and mod it by the number of currently empty cells in the array (first time is 52, second is 51, ...). Call this number i. Starting from the end of the array (52nd cell), take the (i+1)th empty cell from the end (e.g. if i=0, take the first empty cell), skipping over any filled cells. Fill the (i+1)th empty cell so it is no longer empty, and the index of that cell (i.e. if it is the jth cell of the array, the index is j) is the value of the last card. Next time through is the second last card, then the third last card, etc.
  7. Take the last 16-bit number produced in step 5. 16-bit-multiply it with 0x28.
  8. Go back to step 4 until all cells in the array are filled (this determines the whole 52-card deck)
The values (1-52) of the cards correspond as follows, hearts are 1-13, diamonds 14-26, clubs 27-39, spades 40-52, and the ranks are A,2,3,... are in increasing order with A as 1/14/27/40 (depending on suit), 2 as 2/15/28/41, and so on.
Note that the game stores the deck differently, however. First of all, the values are from 0 to 51 (just subtract one). The card order (this applies only after the deck appears but before any cards are dealt), starting at address 03DD and going on for 52 bytes, is implemented similar to that of a linked list. That is, the current value in the array tells you the position in the array whose next number is the next value. A slight issue occurs with the very first value; it is actually in 0x39C, since its otherwise normal spot is taken up by the end-of-deck marker 0xFF.
Here's a C++ file that shows the exact process, and a FCEUX Lua script that takes an existing position and shows a representation of the board (must be run after the deck appears but before any cards are dealt to the piles).

Thanks

Thanks to mmbossman for the published run.

Mukki: Judging...
Mukki: Accepting as an improvement to the currently published run.
fsvgm777: Processing...
I fixed the ROM filename as well.
Last Edited by adelikat on 10/15/2023 3:17 PM
Page History Latest diff List referrers